3 # Let's do some dep checking and handle missing ones gracefully
5 from PyQt4.QtCore import *
6 from PyQt4.QtGui import *
7 import PyQt4.QtCore as QtCore
10 print "You need to have PyQT installed to run Electrum in graphical mode."
11 print "If you have pip installed try 'sudo pip install pyqt' if you are on Debian/Ubuntu try 'sudo apt-get install python-qt4'."
14 from decimal import Decimal as D
15 from electrum.util import get_resource_path as rsrc
16 from electrum.bitcoin import is_valid
24 from electrum import wallet
27 import receiving_widget
28 from electrum import util
32 from electrum.version import ELECTRUM_VERSION as electrum_version
33 from electrum.util import format_satoshis, age
37 bitcoin = lambda v: v * 100000000
39 def IconButton(filename, parent=None):
40 pixmap = QPixmap(filename)
42 return QPushButton(icon, "", parent)
47 self.emit(SIGNAL('timersignal'))
50 def resize_line_edit_width(line_edit, text_input):
51 metrics = QFontMetrics(qApp.font())
52 # Create an extra character to add some space on the end
54 line_edit.setMinimumWidth(metrics.width(text_input))
56 def load_theme_name(theme_path):
58 with open(os.path.join(theme_path, "name.cfg")) as name_cfg_file:
59 return name_cfg_file.read().rstrip("\n").strip()
64 def theme_dirs_from_prefix(prefix):
65 if not os.path.exists(prefix):
68 for potential_theme in os.listdir(prefix):
69 theme_full_path = os.path.join(prefix, potential_theme)
70 theme_css = os.path.join(theme_full_path, "style.css")
71 if not os.path.exists(theme_css):
73 theme_name = load_theme_name(theme_full_path)
74 if theme_name is None:
76 theme_paths[theme_name] = prefix, potential_theme
79 def load_theme_paths():
81 prefixes = (util.local_data_dir(), util.appdata_dir())
82 for prefix in prefixes:
83 theme_paths.update(theme_dirs_from_prefix(prefix))
87 def csv_transaction(wallet):
89 select_export = _('Select file to export your wallet transactions to')
90 fileName = QFileDialog.getSaveFileName(QWidget(), select_export, os.path.expanduser('~/electrum-history.csv'), "*.csv")
92 with open(fileName, "w+") as csvfile:
93 transaction = csv.writer(csvfile)
94 transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
95 for item in wallet.get_tx_history():
96 tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
98 if timestamp is not None:
100 time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
101 except [RuntimeError, TypeError, NameError] as reason:
102 time_string = "unknown"
105 time_string = "unknown"
107 time_string = "pending"
109 if value is not None:
110 value_string = format_satoshis(value, True, wallet.num_zeros)
115 fee_string = format_satoshis(fee, True, wallet.num_zeros)
120 label, is_default_label = wallet.get_label(tx_hash)
124 balance_string = format_satoshis(balance, False, wallet.num_zeros)
125 transaction.writerow([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
126 QMessageBox.information(None,"CSV Export created", "Your CSV export has been successfully created.")
127 except (IOError, os.error), reason:
128 export_error_label = _("Electrum was unable to produce a transaction export.")
129 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
132 class ElectrumGui(QObject):
134 def __init__(self, wallet, config, expert=None):
135 super(QObject, self).__init__()
139 self.check_qt_version()
141 if self.expert != None:
142 self.app = self.expert.app
144 self.app = QApplication(sys.argv)
146 def check_qt_version(self):
147 qtVersion = qVersion()
148 if not(int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
149 app = QApplication(sys.argv)
150 QMessageBox.warning(None,"Could not start Lite GUI.", "Electrum was unable to load the 'Lite GUI' because it needs Qt version >= 4.7.\nChanging your config to use the 'Classic' GUI")
151 self.config.set_key('gui','classic',True)
156 actuator = MiniActuator(self.wallet)
157 # Should probably not modify the current path but instead
158 # change the behaviour of rsrc(...)
159 old_path = QDir.currentPath()
160 actuator.load_theme()
162 self.mini = MiniWindow(actuator, self.expand, self.config)
163 driver = MiniDriver(self.wallet, self.mini)
165 # Reset path back to original value now that loading the GUI
167 QDir.setCurrent(old_path)
172 if self.expert == None:
175 self.expert = gui_qt.ElectrumWindow(self.wallet, self.config)
176 self.expert.app = self.app
177 self.expert.connect_slots(timer)
178 self.expert.update_wallet()
182 """Hide the lite mode window and show pro-mode."""
183 self.config.set_key('gui', 'classic', True)
187 def set_url(self, url):
188 payto, amount, label, message, signature, identity, url = \
189 self.wallet.parse_url(url, self.show_message, self.show_question)
190 self.mini.set_payment_fields(payto, amount)
192 def show_message(self, message):
193 QMessageBox.information(self.mini, _("Message"), message, _("OK"))
195 def show_question(self, message):
196 choice = QMessageBox.question(self.mini, _("Message"), message,
197 QMessageBox.Yes|QMessageBox.No,
199 return choice == QMessageBox.Yes
201 def restore_or_create(self):
202 qt_gui_object = gui_qt.ElectrumGui(self.wallet, self.app)
203 return qt_gui_object.restore_or_create()
205 class TransactionWindow(QDialog):
208 label = unicode(self.label_edit.text())
209 self.parent.wallet.labels[self.tx_id] = label
211 super(TransactionWindow, self).accept()
213 def __init__(self, transaction_id, parent):
214 super(TransactionWindow, self).__init__()
216 self.tx_id = str(transaction_id)
221 self.setWindowTitle(_("Transaction successfully sent"))
223 self.layout = QGridLayout(self)
224 history_label = "%s\n%s" % (_("Your transaction has been sent."), _("Please enter a label for this transaction for future reference."))
225 self.layout.addWidget(QLabel(history_label))
227 self.label_edit = QLineEdit()
228 self.label_edit.setPlaceholderText(_("Transaction label"))
229 self.label_edit.setObjectName("label_input")
230 self.label_edit.setAttribute(Qt.WA_MacShowFocusRect, 0)
231 self.label_edit.setFocusPolicy(Qt.ClickFocus)
232 self.layout.addWidget(self.label_edit)
234 self.save_button = QPushButton(_("Save"))
235 self.layout.addWidget(self.save_button)
236 self.save_button.clicked.connect(self.set_label)
240 class MiniWindow(QDialog):
242 def __init__(self, actuator, expand_callback, config):
243 super(MiniWindow, self).__init__()
244 tx = "e08115d0f7819aee65b9d24f81ef9d46eb62bb67ddef5318156cbc3ceb7b703e"
246 self.actuator = actuator
248 self.btc_balance = None
249 self.quote_currencies = ["BRL", "CNY", "EUR", "GBP", "RUB", "USD"]
250 self.actuator.set_configured_currency(self.set_quote_currency)
251 self.exchanger = exchange_rate.Exchanger(self)
252 # Needed because price discovery is done in a different thread
253 # which needs to be sent back to this main one to update the GUI
254 self.connect(self, SIGNAL("refresh_balance()"), self.refresh_balance)
256 self.balance_label = BalanceLabel(self.change_quote_currency)
257 self.balance_label.setObjectName("balance_label")
260 # Bitcoin address code
261 self.address_input = QLineEdit()
262 self.address_input.setPlaceholderText(_("Enter a Bitcoin address or contact"))
263 self.address_input.setObjectName("address_input")
265 self.address_input.setFocusPolicy(Qt.ClickFocus)
267 self.address_input.textChanged.connect(self.address_field_changed)
268 resize_line_edit_width(self.address_input,
269 "1BtaFUr3qVvAmwrsuDuu5zk6e4s2rxd2Gy")
271 self.address_completions = QStringListModel()
272 address_completer = QCompleter(self.address_input)
273 address_completer.setCaseSensitivity(False)
274 address_completer.setModel(self.address_completions)
275 self.address_input.setCompleter(address_completer)
277 address_layout = QHBoxLayout()
278 address_layout.addWidget(self.address_input)
280 self.amount_input = QLineEdit()
281 self.amount_input.setPlaceholderText(_("... and amount"))
282 self.amount_input.setObjectName("amount_input")
284 self.amount_input.setFocusPolicy(Qt.ClickFocus)
285 # This is changed according to the user's displayed balance
286 self.amount_validator = QDoubleValidator(self.amount_input)
287 self.amount_validator.setNotation(QDoubleValidator.StandardNotation)
288 self.amount_validator.setDecimals(8)
289 self.amount_input.setValidator(self.amount_validator)
291 # This removes the very ugly OSX highlighting, please leave this in :D
292 self.address_input.setAttribute(Qt.WA_MacShowFocusRect, 0)
293 self.amount_input.setAttribute(Qt.WA_MacShowFocusRect, 0)
294 self.amount_input.textChanged.connect(self.amount_input_changed)
296 self.send_button = QPushButton(_("&Send"))
297 self.send_button.setObjectName("send_button")
298 self.send_button.setDisabled(True);
299 self.send_button.clicked.connect(self.send)
301 # Creating the receive button
302 self.switch_button = QPushButton( QIcon(":icons/switchgui.png"),'' )
303 self.switch_button.setMaximumWidth(25)
304 self.switch_button.setFlat(True)
305 self.switch_button.clicked.connect(expand_callback)
307 main_layout = QGridLayout(self)
309 main_layout.addWidget(self.balance_label, 0, 0, 1, 3)
310 main_layout.addWidget(self.switch_button, 0, 3)
312 main_layout.addWidget(self.address_input, 1, 0, 1, 4)
313 main_layout.addWidget(self.amount_input, 2, 0, 1, 2)
314 main_layout.addWidget(self.send_button, 2, 2, 1, 2)
316 self.send_button.setMaximumWidth(125)
318 self.history_list = history_widget.HistoryWidget()
319 self.history_list.setObjectName("history")
320 self.history_list.hide()
321 self.history_list.setAlternatingRowColors(True)
323 main_layout.addWidget(self.history_list, 3, 0, 1, 4)
325 self.receiving = receiving_widget.ReceivingWidget(self)
326 self.receiving.setObjectName("receiving")
328 # Add to the right side
329 self.receiving_box = QGroupBox(_("Select a receiving address"))
330 extra_layout = QGridLayout()
332 # Checkbox to filter used addresses
333 hide_used = QCheckBox(_('Hide used addresses'))
334 hide_used.setChecked(True)
335 hide_used.stateChanged.connect(self.receiving.toggle_used)
337 # Events for receiving addresses
338 self.receiving.clicked.connect(self.receiving.copy_address)
339 self.receiving.itemDoubleClicked.connect(self.receiving.edit_label)
340 self.receiving.itemChanged.connect(self.receiving.update_label)
344 extra_layout.addWidget( QLabel(_('Selecting an address will copy it to the clipboard.') + '\n' + _('Double clicking the label will allow you to edit it.') ),0,0)
346 extra_layout.addWidget(self.receiving, 1,0)
347 extra_layout.addWidget(hide_used, 2,0)
348 extra_layout.setColumnMinimumWidth(0,200)
350 self.receiving_box.setLayout(extra_layout)
351 main_layout.addWidget(self.receiving_box,0,4,-1,3)
352 self.receiving_box.hide()
354 # Creating the menu bar
356 electrum_menu = menubar.addMenu(_("&Electrum"))
358 quit_option = electrum_menu.addAction(_("&Close"))
360 quit_option.triggered.connect(self.close)
362 view_menu = menubar.addMenu(_("&View"))
363 extra_menu = menubar.addMenu(_("&Extra"))
365 backup_wallet = extra_menu.addAction( _("&Create wallet backup"))
366 backup_wallet.triggered.connect(self.backup_wallet)
368 export_csv = extra_menu.addAction( _("&Export transactions to CSV") )
369 export_csv.triggered.connect(lambda: csv_transaction(self.actuator.wallet))
371 master_key = extra_menu.addAction( _("Copy master public key to clipboard") )
372 master_key.triggered.connect(self.actuator.copy_master_public_key)
374 expert_gui = view_menu.addAction(_("&Classic GUI"))
375 expert_gui.triggered.connect(expand_callback)
376 themes_menu = view_menu.addMenu(_("&Themes"))
377 selected_theme = self.actuator.selected_theme()
378 theme_group = QActionGroup(self)
379 for theme_name in self.actuator.theme_names():
380 theme_action = themes_menu.addAction(theme_name)
381 theme_action.setCheckable(True)
382 if selected_theme == theme_name:
383 theme_action.setChecked(True)
384 class SelectThemeFunctor:
385 def __init__(self, theme_name, toggle_theme):
386 self.theme_name = theme_name
387 self.toggle_theme = toggle_theme
388 def __call__(self, checked):
390 self.toggle_theme(self.theme_name)
391 delegate = SelectThemeFunctor(theme_name, self.toggle_theme)
392 theme_action.toggled.connect(delegate)
393 theme_group.addAction(theme_action)
394 view_menu.addSeparator()
396 show_receiving = view_menu.addAction(_("Show Receiving addresses"))
397 show_receiving.setCheckable(True)
398 show_receiving.toggled.connect(self.toggle_receiving_layout)
400 show_receiving_toggle = self.config.get("gui_show_receiving",False)
401 show_receiving.setChecked(show_receiving_toggle)
402 self.show_receiving = show_receiving
404 self.toggle_receiving_layout(show_receiving_toggle)
407 show_history = view_menu.addAction(_("Show History"))
408 show_history.setCheckable(True)
409 show_history.toggled.connect(self.show_history)
411 help_menu = menubar.addMenu(_("&Help"))
412 the_website = help_menu.addAction(_("&Website"))
413 the_website.triggered.connect(self.the_website)
414 help_menu.addSeparator()
415 report_bug = help_menu.addAction(_("&Report Bug"))
416 report_bug.triggered.connect(self.show_report_bug)
417 show_about = help_menu.addAction(_("&About"))
418 show_about.triggered.connect(self.show_about)
419 main_layout.setMenuBar(menubar)
420 self.main_layout = main_layout
422 quit_shortcut = QShortcut(QKeySequence("Ctrl+Q"), self)
423 quit_shortcut.activated.connect(self.close)
424 close_shortcut = QShortcut(QKeySequence("Ctrl+W"), self)
425 close_shortcut.activated.connect(self.close)
427 g = self.config.get("winpos-lite",[4, 25, 351, 149])
428 self.setGeometry(g[0], g[1], g[2], g[3])
430 show_hist = self.config.get("gui_show_history",False)
431 show_history.setChecked(show_hist)
432 self.show_history(show_hist)
434 self.setWindowIcon(QIcon(":electrum.png"))
435 self.setWindowTitle("Electrum")
436 self.setWindowFlags(Qt.Window|Qt.MSWindowsFixedSizeDialogHint)
437 self.layout().setSizeConstraint(QLayout.SetFixedSize)
438 self.setObjectName("main_window")
442 def toggle_theme(self, theme_name):
443 old_path = QDir.currentPath()
444 self.actuator.change_theme(theme_name)
445 # Recompute style globally
446 qApp.style().unpolish(self)
447 qApp.style().polish(self)
448 QDir.setCurrent(old_path)
450 def closeEvent(self, event):
452 self.config.set_key("winpos-lite", [g.left(),g.top(),g.width(),g.height()],True)
453 self.config.set_key("gui_show_history", self.history_list.isVisible(),True)
454 self.config.set_key("gui_show_receiving", self.receiving_box.isVisible(),True)
456 super(MiniWindow, self).closeEvent(event)
459 def set_payment_fields(self, dest_address, amount):
460 self.address_input.setText(dest_address)
461 self.address_field_changed(dest_address)
462 self.amount_input.setText(amount)
467 def deactivate(self):
470 def set_quote_currency(self, currency):
471 """Set and display the fiat currency country."""
472 assert currency in self.quote_currencies
473 self.quote_currencies.remove(currency)
474 self.quote_currencies.insert(0, currency)
475 self.refresh_balance()
477 def change_quote_currency(self):
478 self.quote_currencies = \
479 self.quote_currencies[1:] + self.quote_currencies[0:1]
480 self.actuator.set_config_currency(self.quote_currencies[0])
481 self.refresh_balance()
483 def refresh_balance(self):
484 if self.btc_balance is None:
485 # Price has been discovered before wallet has been loaded
486 # and server connect... so bail.
488 self.set_balances(self.btc_balance)
489 self.amount_input_changed(self.amount_input.text())
491 def set_balances(self, btc_balance):
492 """Set the bitcoin balance and update the amount label accordingly."""
493 self.btc_balance = btc_balance
494 quote_text = self.create_quote_text(btc_balance)
496 quote_text = "(%s)" % quote_text
497 btc_balance = "%.2f" % (btc_balance / bitcoin(1))
498 self.balance_label.set_balance_text(btc_balance, quote_text)
499 self.setWindowTitle("Electrum %s - %s BTC" % (electrum_version, btc_balance))
501 def amount_input_changed(self, amount_text):
502 """Update the number of bitcoins displayed."""
503 self.check_button_status()
506 amount = D(str(amount_text))
507 except decimal.InvalidOperation:
508 self.balance_label.show_balance()
510 quote_text = self.create_quote_text(amount * bitcoin(1))
512 self.balance_label.set_amount_text(quote_text)
513 self.balance_label.show_amount()
515 self.balance_label.show_balance()
517 def create_quote_text(self, btc_balance):
518 """Return a string copy of the amount fiat currency the
519 user has in bitcoins."""
520 quote_currency = self.quote_currencies[0]
521 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
522 if quote_balance is None:
525 quote_text = "%.2f %s" % ((quote_balance / bitcoin(1)),
530 if self.actuator.send(self.address_input.text(),
531 self.amount_input.text(), self):
532 self.address_input.setText("")
533 self.amount_input.setText("")
535 def check_button_status(self):
536 """Check that the bitcoin address is valid and that something
537 is entered in the amount before making the send button clickable."""
539 value = D(str(self.amount_input.text())) * 10**8
540 except decimal.InvalidOperation:
542 # self.address_input.property(...) returns a qVariant, not a bool.
543 # The == is needed to properly invoke a comparison.
544 if (self.address_input.property("isValid") == True and
545 value is not None and 0 < value <= self.btc_balance):
546 self.send_button.setDisabled(False)
548 self.send_button.setDisabled(True)
550 def address_field_changed(self, address):
551 # label or alias, with address in brackets
552 match2 = re.match("(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>",
555 address = match2.group(2)
556 self.address_input.setText(address)
558 if is_valid(address):
559 self.check_button_status()
560 self.address_input.setProperty("isValid", True)
561 self.recompute_style(self.address_input)
563 self.send_button.setDisabled(True)
564 self.address_input.setProperty("isValid", False)
565 self.recompute_style(self.address_input)
567 if len(address) == 0:
568 self.address_input.setProperty("isValid", None)
569 self.recompute_style(self.address_input)
571 def recompute_style(self, element):
572 self.style().unpolish(element)
573 self.style().polish(element)
575 def copy_address(self):
576 receive_popup = ReceivePopup(self.receive_button)
577 self.actuator.copy_address(receive_popup)
579 def update_completions(self, completions):
580 self.address_completions.setStringList(completions)
583 def update_history(self, tx_history):
585 self.history_list.empty()
587 for item in tx_history[-10:]:
588 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
589 label = self.actuator.wallet.get_label(tx_hash)[0]
590 #amount = D(value) / 10**8
591 v_str = format_satoshis(value, True)
592 self.history_list.append(label, v_str, age(timestamp))
595 self.actuator.acceptbit(self.quote_currencies[0])
597 def the_website(self):
598 webbrowser.open("http://electrum.org")
600 def show_about(self):
601 QMessageBox.about(self, "Electrum",
602 _("Version")+" %s" % (electrum_version) + "\n\n" + _("Electrum's focus is speed, with low resource usage and simplifying Bitcoin. You do not need to perform regular backups, because your wallet can be recovered from a secret phrase that you can memorize or write on paper. Startup times are instant because it operates in conjunction with high-performance servers that handle the most complicated parts of the Bitcoin system."))
604 def show_report_bug(self):
605 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
606 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
608 def toggle_receiving_layout(self, toggle_state):
610 self.receiving_box.show()
612 self.receiving_box.hide()
614 def show_history(self, toggle_state):
616 self.main_layout.setRowMinimumHeight(3,200)
617 self.history_list.show()
619 self.main_layout.setRowMinimumHeight(3,0)
620 self.history_list.hide()
622 def backup_wallet(self):
624 folderName = QFileDialog.getExistingDirectory(QWidget(), _('Select folder to save a copy of your wallet to'), os.path.expanduser('~/'))
626 sourceFile = util.user_dir() + '/electrum.dat'
627 shutil.copy2(sourceFile, str(folderName))
628 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(folderName))
629 except (IOError, os.error), reason:
630 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
635 class BalanceLabel(QLabel):
641 def __init__(self, change_quote_currency, parent=None):
642 super(QLabel, self).__init__(_("Connecting..."), parent)
643 self.change_quote_currency = change_quote_currency
644 self.state = self.SHOW_CONNECTING
645 self.balance_text = ""
646 self.amount_text = ""
648 def mousePressEvent(self, event):
649 """Change the fiat currency selection if window background is clicked."""
650 if self.state != self.SHOW_CONNECTING:
651 self.change_quote_currency()
653 def set_balance_text(self, btc_balance, quote_text):
654 """Set the amount of bitcoins in the gui."""
655 if self.state == self.SHOW_CONNECTING:
656 self.state = self.SHOW_BALANCE
657 self.balance_text = "<span style='font-size: 18pt'>%s</span> <span style='font-size: 10pt'>BTC</span> <span style='font-size: 10pt'>%s</span>" % (btc_balance, quote_text)
658 if self.state == self.SHOW_BALANCE:
659 self.setText(self.balance_text)
661 def set_amount_text(self, quote_text):
662 self.amount_text = "<span style='font-size: 10pt'>%s</span>" % quote_text
663 if self.state == self.SHOW_AMOUNT:
664 self.setText(self.amount_text)
666 def show_balance(self):
667 if self.state == self.SHOW_AMOUNT:
668 self.state = self.SHOW_BALANCE
669 self.setText(self.balance_text)
671 def show_amount(self):
672 if self.state == self.SHOW_BALANCE:
673 self.state = self.SHOW_AMOUNT
674 self.setText(self.amount_text)
676 def ok_cancel_buttons(dialog):
677 row_layout = QHBoxLayout()
678 row_layout.addStretch(1)
679 ok_button = QPushButton(_("OK"))
680 row_layout.addWidget(ok_button)
681 ok_button.clicked.connect(dialog.accept)
682 cancel_button = QPushButton(_("Cancel"))
683 row_layout.addWidget(cancel_button)
684 cancel_button.clicked.connect(dialog.reject)
687 class PasswordDialog(QDialog):
689 def __init__(self, parent):
690 super(QDialog, self).__init__(parent)
694 self.password_input = QLineEdit()
695 self.password_input.setEchoMode(QLineEdit.Password)
697 main_layout = QVBoxLayout(self)
698 message = _('Please enter your password')
699 main_layout.addWidget(QLabel(message))
703 grid.addWidget(QLabel(_('Password')), 1, 0)
704 grid.addWidget(self.password_input, 1, 1)
705 main_layout.addLayout(grid)
707 main_layout.addLayout(ok_cancel_buttons(self))
708 self.setLayout(main_layout)
713 return unicode(self.password_input.text())
715 class ReceivePopup(QDialog):
717 def leaveEvent(self, event):
720 def setup(self, address):
721 label = QLabel(_("Copied your Bitcoin address to the clipboard!"))
722 address_display = QLineEdit(address)
723 address_display.setReadOnly(True)
724 resize_line_edit_width(address_display, address)
726 main_layout = QVBoxLayout(self)
727 main_layout.addWidget(label)
728 main_layout.addWidget(address_display)
730 self.setMouseTracking(True)
731 self.setWindowTitle("Electrum - " + _("Receive Bitcoin payment"))
732 self.setWindowFlags(Qt.Window|Qt.FramelessWindowHint|
733 Qt.MSWindowsFixedSizeDialogHint)
734 self.layout().setSizeConstraint(QLayout.SetFixedSize)
735 #self.setFrameStyle(QFrame.WinPanel|QFrame.Raised)
736 #self.setAlignment(Qt.AlignCenter)
739 parent = self.parent()
740 top_left_pos = parent.mapToGlobal(parent.rect().bottomLeft())
741 self.move(top_left_pos)
742 center_mouse_pos = self.mapToGlobal(self.rect().center())
743 QCursor.setPos(center_mouse_pos)
747 """Initialize the definitions relating to themes and
748 sending/receiving bitcoins."""
751 def __init__(self, wallet):
752 """Retrieve the gui theme used in previous session."""
754 self.theme_name = self.wallet.config.get('litegui_theme','Cleanlook')
755 self.themes = load_theme_paths()
757 def load_theme(self):
758 """Load theme retrieved from wallet file."""
760 theme_prefix, theme_path = self.themes[self.theme_name]
762 util.print_error("Theme not found!", self.theme_name)
764 QDir.setCurrent(os.path.join(theme_prefix, theme_path))
765 with open(rsrc("style.css")) as style_file:
766 qApp.setStyleSheet(style_file.read())
768 def theme_names(self):
770 return sorted(self.themes.keys())
772 def selected_theme(self):
774 return self.theme_name
776 def change_theme(self, theme_name):
778 self.theme_name = theme_name
779 self.wallet.config.set_key('litegui_theme',theme_name)
782 def set_configured_currency(self, set_quote_currency):
783 """Set the inital fiat currency conversion country (USD/EUR/GBP) in
784 the GUI to what it was set to in the wallet."""
785 currency = self.wallet.config.get('conversion_currency')
786 # currency can be none when Electrum is used for the first
787 # time and no setting has been created yet.
788 if currency is not None:
789 set_quote_currency(currency)
791 def set_config_currency(self, conversion_currency):
792 """Change the wallet fiat currency country."""
793 self.wallet.config.set_key('conversion_currency',conversion_currency,True)
795 def copy_address(self, receive_popup):
796 """Copy the wallet addresses into the client."""
797 addrs = [addr for addr in self.wallet.addresses(True)
798 if not self.wallet.is_change(addr)]
799 # Select most recent addresses from gap limit
800 addrs = addrs[-self.wallet.gap_limit:]
801 copied_address = random.choice(addrs)
802 qApp.clipboard().setText(copied_address)
803 receive_popup.setup(copied_address)
804 receive_popup.popup()
806 def waiting_dialog(self, f):
811 w.setWindowTitle('Electrum')
812 l = QLabel('Sending transaction, please wait.')
821 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
826 def send(self, address, amount, parent_window):
827 """Send bitcoins to the target address."""
828 dest_address = self.fetch_destination(address)
830 if dest_address is None or not is_valid(dest_address):
831 QMessageBox.warning(parent_window, _('Error'),
832 _('Invalid Bitcoin Address') + ':\n' + address, _('OK'))
835 convert_amount = lambda amount: \
836 int(D(unicode(amount)) * bitcoin(1))
837 amount = convert_amount(amount)
839 if self.wallet.use_encryption:
840 password_dialog = PasswordDialog(parent_window)
841 password = password_dialog.run()
849 if amount < bitcoin(1) / 10:
851 fee = bitcoin(1) / 1000
854 tx = self.wallet.mktx([(dest_address, amount)], password, fee)
855 except BaseException as error:
856 QMessageBox.warning(parent_window, _('Error'), str(error), _('OK'))
859 h = self.wallet.send_tx(tx)
861 self.waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Sending transaction, please wait..."))
863 status, message = self.wallet.receive_tx(h)
867 dumpf = tempfile.NamedTemporaryFile(delete=False)
870 print "Dumped error tx to", dumpf.name
871 QMessageBox.warning(parent_window, _('Error'), message, _('OK'))
874 TransactionWindow(message, self)
875 # QMessageBox.information(parent_window, '',
876 # _('Your transaction has been sent.') + '\n' + message, _('OK'))
879 def fetch_destination(self, address):
880 recipient = unicode(address).strip()
883 match1 = re.match("^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$",
886 # label or alias, with address in brackets
887 match2 = re.match("(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>",
892 self.wallet.get_alias(recipient, True,
893 self.show_message, self.question)
896 return match2.group(2)
901 def copy_master_public_key(self):
902 master_pubkey = self.wallet.master_public_key
903 qApp.clipboard().setText(master_pubkey)
904 QMessageBox.information(None, _("Copy successful"), _("Your master public key has been copied to your clipboard."))
907 def acceptbit(self, currency):
908 master_pubkey = self.wallet.master_public_key
909 url = "http://acceptbit.com/mpk/%s/%s" % (master_pubkey, currency)
912 def show_seed_dialog(self):
913 gui_qt.ElectrumWindow.show_seed_dialog(self.wallet)
915 class MiniDriver(QObject):
922 def __init__(self, wallet, window):
923 super(QObject, self).__init__()
928 self.wallet.interface.register_callback('updated',self.update_callback)
929 self.wallet.interface.register_callback('connected', self.update_callback)
930 self.wallet.interface.register_callback('disconnected', self.update_callback)
935 self.connect(self, SIGNAL("updatesignal()"), self.update)
936 self.update_callback()
938 # This is a hack to workaround that Qt does not like changing the
939 # window properties from this other thread before the runloop has
941 def update_callback(self):
942 self.emit(SIGNAL("updatesignal()"))
945 if not self.wallet.interface:
947 elif not self.wallet.interface.is_connected:
949 elif not self.wallet.up_to_date:
954 if self.wallet.up_to_date:
955 self.update_balance()
956 self.update_completions()
957 self.update_history()
959 def initializing(self):
960 if self.state == self.INITIALIZING:
962 self.state = self.INITIALIZING
963 self.window.deactivate()
965 def connecting(self):
966 if self.state == self.CONNECTING:
968 self.state = self.CONNECTING
969 self.window.deactivate()
971 def synchronizing(self):
972 if self.state == self.SYNCHRONIZING:
974 self.state = self.SYNCHRONIZING
975 self.window.deactivate()
978 if self.state == self.READY:
980 self.state = self.READY
981 self.window.activate()
983 def update_balance(self):
984 conf_balance, unconf_balance = self.wallet.get_balance()
985 balance = D(conf_balance + unconf_balance)
986 self.window.set_balances(balance)
988 def update_completions(self):
990 for addr, label in self.wallet.labels.items():
991 if addr in self.wallet.addressbook:
992 completions.append("%s <%s>" % (label, addr))
993 completions = completions + self.wallet.aliases.keys()
994 self.window.update_completions(completions)
996 def update_history(self):
997 tx_history = self.wallet.get_tx_history()
998 self.window.update_history(tx_history)
1001 if __name__ == "__main__":
1002 app = QApplication(sys.argv)
1003 with open(rsrc("style.css")) as style_file:
1004 app.setStyleSheet(style_file.read())
1006 sys.exit(app.exec_())