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 util import get_resource_path as rsrc
16 from bitcoin import is_valid
27 import receiving_widget
32 from version import ELECTRUM_VERSION as electrum_version
33 from wallet import format_satoshis
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):
584 from util import format_satoshis, age
586 self.history_list.empty()
588 for item in tx_history[-10:]:
589 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
590 label = self.actuator.wallet.get_label(tx_hash)[0]
591 #amount = D(value) / 10**8
592 v_str = format_satoshis(value, True)
593 self.history_list.append(label, v_str, age(timestamp))
596 self.actuator.acceptbit(self.quote_currencies[0])
598 def the_website(self):
599 webbrowser.open("http://electrum.org")
601 def show_about(self):
602 QMessageBox.about(self, "Electrum",
603 _("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."))
605 def show_report_bug(self):
606 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
607 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
609 def toggle_receiving_layout(self, toggle_state):
611 self.receiving_box.show()
613 self.receiving_box.hide()
615 def show_history(self, toggle_state):
617 self.main_layout.setRowMinimumHeight(3,200)
618 self.history_list.show()
620 self.main_layout.setRowMinimumHeight(3,0)
621 self.history_list.hide()
623 def backup_wallet(self):
625 folderName = QFileDialog.getExistingDirectory(QWidget(), _('Select folder to save a copy of your wallet to'), os.path.expanduser('~/'))
627 sourceFile = util.user_dir() + '/electrum.dat'
628 shutil.copy2(sourceFile, str(folderName))
629 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(folderName))
630 except (IOError, os.error), reason:
631 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
636 class BalanceLabel(QLabel):
642 def __init__(self, change_quote_currency, parent=None):
643 super(QLabel, self).__init__(_("Connecting..."), parent)
644 self.change_quote_currency = change_quote_currency
645 self.state = self.SHOW_CONNECTING
646 self.balance_text = ""
647 self.amount_text = ""
649 def mousePressEvent(self, event):
650 """Change the fiat currency selection if window background is clicked."""
651 if self.state != self.SHOW_CONNECTING:
652 self.change_quote_currency()
654 def set_balance_text(self, btc_balance, quote_text):
655 """Set the amount of bitcoins in the gui."""
656 if self.state == self.SHOW_CONNECTING:
657 self.state = self.SHOW_BALANCE
658 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)
659 if self.state == self.SHOW_BALANCE:
660 self.setText(self.balance_text)
662 def set_amount_text(self, quote_text):
663 self.amount_text = "<span style='font-size: 10pt'>%s</span>" % quote_text
664 if self.state == self.SHOW_AMOUNT:
665 self.setText(self.amount_text)
667 def show_balance(self):
668 if self.state == self.SHOW_AMOUNT:
669 self.state = self.SHOW_BALANCE
670 self.setText(self.balance_text)
672 def show_amount(self):
673 if self.state == self.SHOW_BALANCE:
674 self.state = self.SHOW_AMOUNT
675 self.setText(self.amount_text)
677 def ok_cancel_buttons(dialog):
678 row_layout = QHBoxLayout()
679 row_layout.addStretch(1)
680 ok_button = QPushButton(_("OK"))
681 row_layout.addWidget(ok_button)
682 ok_button.clicked.connect(dialog.accept)
683 cancel_button = QPushButton(_("Cancel"))
684 row_layout.addWidget(cancel_button)
685 cancel_button.clicked.connect(dialog.reject)
688 class PasswordDialog(QDialog):
690 def __init__(self, parent):
691 super(QDialog, self).__init__(parent)
695 self.password_input = QLineEdit()
696 self.password_input.setEchoMode(QLineEdit.Password)
698 main_layout = QVBoxLayout(self)
699 message = _('Please enter your password')
700 main_layout.addWidget(QLabel(message))
704 grid.addWidget(QLabel(_('Password')), 1, 0)
705 grid.addWidget(self.password_input, 1, 1)
706 main_layout.addLayout(grid)
708 main_layout.addLayout(ok_cancel_buttons(self))
709 self.setLayout(main_layout)
714 return unicode(self.password_input.text())
716 class ReceivePopup(QDialog):
718 def leaveEvent(self, event):
721 def setup(self, address):
722 label = QLabel(_("Copied your Bitcoin address to the clipboard!"))
723 address_display = QLineEdit(address)
724 address_display.setReadOnly(True)
725 resize_line_edit_width(address_display, address)
727 main_layout = QVBoxLayout(self)
728 main_layout.addWidget(label)
729 main_layout.addWidget(address_display)
731 self.setMouseTracking(True)
732 self.setWindowTitle("Electrum - " + _("Receive Bitcoin payment"))
733 self.setWindowFlags(Qt.Window|Qt.FramelessWindowHint|
734 Qt.MSWindowsFixedSizeDialogHint)
735 self.layout().setSizeConstraint(QLayout.SetFixedSize)
736 #self.setFrameStyle(QFrame.WinPanel|QFrame.Raised)
737 #self.setAlignment(Qt.AlignCenter)
740 parent = self.parent()
741 top_left_pos = parent.mapToGlobal(parent.rect().bottomLeft())
742 self.move(top_left_pos)
743 center_mouse_pos = self.mapToGlobal(self.rect().center())
744 QCursor.setPos(center_mouse_pos)
748 """Initialize the definitions relating to themes and
749 sending/receiving bitcoins."""
752 def __init__(self, wallet):
753 """Retrieve the gui theme used in previous session."""
755 self.theme_name = self.wallet.config.get('litegui_theme','Cleanlook')
756 self.themes = load_theme_paths()
758 def load_theme(self):
759 """Load theme retrieved from wallet file."""
761 theme_prefix, theme_path = self.themes[self.theme_name]
763 util.print_error("Theme not found!", self.theme_name)
765 QDir.setCurrent(os.path.join(theme_prefix, theme_path))
766 with open(rsrc("style.css")) as style_file:
767 qApp.setStyleSheet(style_file.read())
769 def theme_names(self):
771 return sorted(self.themes.keys())
773 def selected_theme(self):
775 return self.theme_name
777 def change_theme(self, theme_name):
779 self.theme_name = theme_name
780 self.wallet.config.set_key('litegui_theme',theme_name)
783 def set_configured_currency(self, set_quote_currency):
784 """Set the inital fiat currency conversion country (USD/EUR/GBP) in
785 the GUI to what it was set to in the wallet."""
786 currency = self.wallet.config.get('conversion_currency')
787 # currency can be none when Electrum is used for the first
788 # time and no setting has been created yet.
789 if currency is not None:
790 set_quote_currency(currency)
792 def set_config_currency(self, conversion_currency):
793 """Change the wallet fiat currency country."""
794 self.wallet.config.set_key('conversion_currency',conversion_currency,True)
796 def copy_address(self, receive_popup):
797 """Copy the wallet addresses into the client."""
798 addrs = [addr for addr in self.wallet.all_addresses()
799 if not self.wallet.is_change(addr)]
800 # Select most recent addresses from gap limit
801 addrs = addrs[-self.wallet.gap_limit:]
802 copied_address = random.choice(addrs)
803 qApp.clipboard().setText(copied_address)
804 receive_popup.setup(copied_address)
805 receive_popup.popup()
807 def waiting_dialog(self, f):
812 w.setWindowTitle('Electrum')
813 l = QLabel('Sending transaction, please wait.')
822 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
827 def send(self, address, amount, parent_window):
828 """Send bitcoins to the target address."""
829 dest_address = self.fetch_destination(address)
831 if dest_address is None or not is_valid(dest_address):
832 QMessageBox.warning(parent_window, _('Error'),
833 _('Invalid Bitcoin Address') + ':\n' + address, _('OK'))
836 convert_amount = lambda amount: \
837 int(D(unicode(amount)) * bitcoin(1))
838 amount = convert_amount(amount)
840 if self.wallet.use_encryption:
841 password_dialog = PasswordDialog(parent_window)
842 password = password_dialog.run()
850 if amount < bitcoin(1) / 10:
852 fee = bitcoin(1) / 1000
855 tx = self.wallet.mktx([(dest_address, amount)], password, fee)
856 except BaseException as error:
857 QMessageBox.warning(parent_window, _('Error'), str(error), _('OK'))
860 h = self.wallet.send_tx(tx)
862 self.waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Sending transaction, please wait..."))
864 status, message = self.wallet.receive_tx(h)
868 dumpf = tempfile.NamedTemporaryFile(delete=False)
871 print "Dumped error tx to", dumpf.name
872 QMessageBox.warning(parent_window, _('Error'), message, _('OK'))
875 TransactionWindow(message, self)
876 # QMessageBox.information(parent_window, '',
877 # _('Your transaction has been sent.') + '\n' + message, _('OK'))
880 def fetch_destination(self, address):
881 recipient = unicode(address).strip()
884 match1 = re.match("^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$",
887 # label or alias, with address in brackets
888 match2 = re.match("(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>",
893 self.wallet.get_alias(recipient, True,
894 self.show_message, self.question)
897 return match2.group(2)
902 def copy_master_public_key(self):
903 master_pubkey = self.wallet.master_public_key
904 qApp.clipboard().setText(master_pubkey)
905 QMessageBox.information(None, _("Copy successful"), _("Your master public key has been copied to your clipboard."))
908 def acceptbit(self, currency):
909 master_pubkey = self.wallet.master_public_key
910 url = "http://acceptbit.com/mpk/%s/%s" % (master_pubkey, currency)
913 def show_seed_dialog(self):
914 gui_qt.ElectrumWindow.show_seed_dialog(self.wallet)
916 class MiniDriver(QObject):
923 def __init__(self, wallet, window):
924 super(QObject, self).__init__()
929 self.wallet.interface.register_callback('updated',self.update_callback)
930 self.wallet.interface.register_callback('connected', self.update_callback)
931 self.wallet.interface.register_callback('disconnected', self.update_callback)
936 self.connect(self, SIGNAL("updatesignal()"), self.update)
937 self.update_callback()
939 # This is a hack to workaround that Qt does not like changing the
940 # window properties from this other thread before the runloop has
942 def update_callback(self):
943 self.emit(SIGNAL("updatesignal()"))
946 if not self.wallet.interface:
948 elif not self.wallet.interface.is_connected:
950 elif not self.wallet.up_to_date:
955 if self.wallet.up_to_date:
956 self.update_balance()
957 self.update_completions()
958 self.update_history()
960 def initializing(self):
961 if self.state == self.INITIALIZING:
963 self.state = self.INITIALIZING
964 self.window.deactivate()
966 def connecting(self):
967 if self.state == self.CONNECTING:
969 self.state = self.CONNECTING
970 self.window.deactivate()
972 def synchronizing(self):
973 if self.state == self.SYNCHRONIZING:
975 self.state = self.SYNCHRONIZING
976 self.window.deactivate()
979 if self.state == self.READY:
981 self.state = self.READY
982 self.window.activate()
984 def update_balance(self):
985 conf_balance, unconf_balance = self.wallet.get_balance()
986 balance = D(conf_balance + unconf_balance)
987 self.window.set_balances(balance)
989 def update_completions(self):
991 for addr, label in self.wallet.labels.items():
992 if addr in self.wallet.addressbook:
993 completions.append("%s <%s>" % (label, addr))
994 completions = completions + self.wallet.aliases.keys()
995 self.window.update_completions(completions)
997 def update_history(self):
998 tx_history = self.wallet.get_tx_history()
999 self.window.update_history(tx_history)
1002 if __name__ == "__main__":
1003 app = QApplication(sys.argv)
1004 with open(rsrc("style.css")) as style_file:
1005 app.setStyleSheet(style_file.read())
1007 sys.exit(app.exec_())