3 # Let's do some dep checking and handle missing ones gracefully
5 from PyQt4.QtCore import *
6 from PyQt4.QtGui import *
7 from PyQt4.Qt import Qt
8 import PyQt4.QtCore as QtCore
11 print "You need to have PyQT installed to run Electrum in graphical mode."
12 print "If you have pip installed try 'sudo pip install pyqt' if you are on Debian/Ubuntu try 'sudo apt-get install python-qt4'."
15 from decimal import Decimal as D
16 from electrum.util import get_resource_path as rsrc
17 from electrum.bitcoin import is_valid
26 from electrum.wallet import Wallet, WalletStorage
29 import receiving_widget
30 from electrum import util
34 from electrum.version import ELECTRUM_VERSION as electrum_version
35 from electrum.util import format_satoshis, age
41 bitcoin = lambda v: v * 100000000
43 def IconButton(filename, parent=None):
44 pixmap = QPixmap(filename)
46 return QPushButton(icon, "", parent)
51 self.emit(SIGNAL('timersignal'))
54 def resize_line_edit_width(line_edit, text_input):
55 metrics = QFontMetrics(qApp.font())
56 # Create an extra character to add some space on the end
58 line_edit.setMinimumWidth(metrics.width(text_input))
60 def load_theme_name(theme_path):
62 with open(os.path.join(theme_path, "name.cfg")) as name_cfg_file:
63 return name_cfg_file.read().rstrip("\n").strip()
68 def theme_dirs_from_prefix(prefix):
69 if not os.path.exists(prefix):
72 for potential_theme in os.listdir(prefix):
73 theme_full_path = os.path.join(prefix, potential_theme)
74 theme_css = os.path.join(theme_full_path, "style.css")
75 if not os.path.exists(theme_css):
77 theme_name = load_theme_name(theme_full_path)
78 if theme_name is None:
80 theme_paths[theme_name] = prefix, potential_theme
83 def load_theme_paths():
85 prefixes = (util.local_data_dir(), util.appdata_dir())
86 for prefix in prefixes:
87 theme_paths.update(theme_dirs_from_prefix(prefix))
91 def csv_transaction(wallet):
93 select_export = _('Select file to export your wallet transactions to')
94 fileName = QFileDialog.getSaveFileName(QWidget(), select_export, os.path.expanduser('~/electrum-history.csv'), "*.csv")
96 with open(fileName, "w+") as csvfile:
97 transaction = csv.writer(csvfile)
98 transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
99 for item in wallet.get_tx_history():
100 tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
102 if timestamp is not None:
104 time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
105 except [RuntimeError, TypeError, NameError] as reason:
106 time_string = "unknown"
109 time_string = "unknown"
111 time_string = "pending"
113 if value is not None:
114 value_string = format_satoshis(value, True, wallet.num_zeros)
119 fee_string = format_satoshis(fee, True, wallet.num_zeros)
124 label, is_default_label = wallet.get_label(tx_hash)
128 balance_string = format_satoshis(balance, False, wallet.num_zeros)
129 transaction.writerow([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
130 QMessageBox.information(None,"CSV Export created", "Your CSV export has been successfully created.")
131 except (IOError, os.error), reason:
132 export_error_label = _("Electrum was unable to produce a transaction export.")
133 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
136 class ElectrumGui(QObject):
138 def __init__(self, config, interface, blockchain, expert=None):
139 super(QObject, self).__init__()
142 self.check_qt_version()
144 self.interface = interface
145 self.blockchain = blockchain
150 def check_qt_version(self):
151 qtVersion = qVersion()
152 if not(int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
153 app = QApplication(sys.argv)
154 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")
155 self.config.set_key('gui','classic',True)
161 if self.expert is None:
162 self.app = QApplication(sys.argv)
163 storage = WalletStorage(self.config)
164 if not storage.file_exists:
166 self.wallet = Wallet(storage)
167 self.wallet.start_threads(self.interface, self.blockchain)
169 self.app = self.expert.app
170 self.wallet = self.expert.wallet
172 actuator = MiniActuator(self.config, self.wallet)
173 # Should probably not modify the current path but instead
174 # change the behaviour of rsrc(...)
175 old_path = QDir.currentPath()
176 actuator.load_theme()
178 self.mini = MiniWindow(actuator, self.expand, self.config)
179 driver = MiniDriver(self.wallet, self.mini)
181 # Reset path back to original value now that loading the GUI
183 QDir.setCurrent(old_path)
188 if self.expert is None:
189 self.expert = gui_classic.ElectrumWindow(self.config)
190 self.expert.load_wallet(self.wallet)
191 self.expert.app = self.app
194 self.expert.connect_slots(timer)
195 self.expert.update_wallet()
201 """Hide the lite mode window and show pro-mode."""
202 self.config.set_key('gui', 'classic', True)
206 def set_url(self, url):
207 payto, amount, label, message, signature, identity, url = \
208 self.wallet.parse_url(url, self.show_message, self.show_question)
209 self.mini.set_payment_fields(payto, amount)
211 def show_message(self, message):
212 QMessageBox.information(self.mini, _("Message"), message, _("OK"))
214 def show_question(self, message):
215 choice = QMessageBox.question(self.mini, _("Message"), message,
216 QMessageBox.Yes|QMessageBox.No,
218 return choice == QMessageBox.Yes
220 def restore_or_create(self):
221 qt_gui_object = gui_classic.ElectrumGui(self.wallet, self.app)
222 return qt_gui_object.restore_or_create()
224 class TransactionWindow(QDialog):
227 label = unicode(self.label_edit.text())
228 self.parent.wallet.labels[self.tx_id] = label
230 super(TransactionWindow, self).accept()
232 def __init__(self, transaction_id, parent):
233 super(TransactionWindow, self).__init__()
235 self.tx_id = str(transaction_id)
240 self.setWindowTitle(_("Transaction successfully sent"))
242 self.layout = QGridLayout(self)
243 history_label = "%s\n%s" % (_("Your transaction has been sent."), _("Please enter a label for this transaction for future reference."))
244 self.layout.addWidget(QLabel(history_label))
246 self.label_edit = QLineEdit()
247 self.label_edit.setPlaceholderText(_("Transaction label"))
248 self.label_edit.setObjectName("label_input")
249 self.label_edit.setAttribute(Qt.WA_MacShowFocusRect, 0)
250 self.label_edit.setFocusPolicy(Qt.ClickFocus)
251 self.layout.addWidget(self.label_edit)
253 self.save_button = QPushButton(_("Save"))
254 self.layout.addWidget(self.save_button)
255 self.save_button.clicked.connect(self.set_label)
259 class MiniWindow(QDialog):
261 def __init__(self, actuator, expand_callback, config):
262 super(MiniWindow, self).__init__()
263 tx = "e08115d0f7819aee65b9d24f81ef9d46eb62bb67ddef5318156cbc3ceb7b703e"
265 self.actuator = actuator
267 self.btc_balance = None
268 self.quote_currencies = ["BRL", "CNY", "EUR", "GBP", "RUB", "USD"]
269 self.actuator.set_configured_currency(self.set_quote_currency)
270 self.exchanger = exchange_rate.Exchanger(self)
271 # Needed because price discovery is done in a different thread
272 # which needs to be sent back to this main one to update the GUI
273 self.connect(self, SIGNAL("refresh_balance()"), self.refresh_balance)
275 self.balance_label = BalanceLabel(self.change_quote_currency)
276 self.balance_label.setObjectName("balance_label")
279 # Bitcoin address code
280 self.address_input = QLineEdit()
281 self.address_input.setPlaceholderText(_("Enter a Bitcoin address or contact"))
282 self.address_input.setObjectName("address_input")
284 self.address_input.setFocusPolicy(Qt.ClickFocus)
286 self.address_input.textChanged.connect(self.address_field_changed)
287 resize_line_edit_width(self.address_input,
288 "1BtaFUr3qVvAmwrsuDuu5zk6e4s2rxd2Gy")
290 self.address_completions = QStringListModel()
291 address_completer = QCompleter(self.address_input)
292 address_completer.setCaseSensitivity(False)
293 address_completer.setModel(self.address_completions)
294 self.address_input.setCompleter(address_completer)
296 address_layout = QHBoxLayout()
297 address_layout.addWidget(self.address_input)
299 self.amount_input = QLineEdit()
300 self.amount_input.setPlaceholderText(_("... and amount"))
301 self.amount_input.setObjectName("amount_input")
303 self.amount_input.setFocusPolicy(Qt.ClickFocus)
304 # This is changed according to the user's displayed balance
305 self.amount_validator = QDoubleValidator(self.amount_input)
306 self.amount_validator.setNotation(QDoubleValidator.StandardNotation)
307 self.amount_validator.setDecimals(8)
308 self.amount_input.setValidator(self.amount_validator)
310 # This removes the very ugly OSX highlighting, please leave this in :D
311 self.address_input.setAttribute(Qt.WA_MacShowFocusRect, 0)
312 self.amount_input.setAttribute(Qt.WA_MacShowFocusRect, 0)
313 self.amount_input.textChanged.connect(self.amount_input_changed)
315 if self.actuator.wallet.seed:
316 self.send_button = QPushButton(_("&Send"))
318 self.send_button = QPushButton(_("&Create"))
320 self.send_button.setObjectName("send_button")
321 self.send_button.setDisabled(True);
322 self.send_button.clicked.connect(self.send)
324 # Creating the receive button
325 self.switch_button = QPushButton( QIcon(":icons/switchgui.png"),'' )
326 self.switch_button.setMaximumWidth(25)
327 self.switch_button.setFlat(True)
328 self.switch_button.clicked.connect(expand_callback)
330 main_layout = QGridLayout(self)
332 main_layout.addWidget(self.balance_label, 0, 0, 1, 3)
333 main_layout.addWidget(self.switch_button, 0, 3)
335 main_layout.addWidget(self.address_input, 1, 0, 1, 4)
336 main_layout.addWidget(self.amount_input, 2, 0, 1, 2)
337 main_layout.addWidget(self.send_button, 2, 2, 1, 2)
339 self.send_button.setMaximumWidth(125)
341 self.history_list = history_widget.HistoryWidget()
342 self.history_list.setObjectName("history")
343 self.history_list.hide()
344 self.history_list.setAlternatingRowColors(True)
346 main_layout.addWidget(self.history_list, 3, 0, 1, 4)
348 self.receiving = receiving_widget.ReceivingWidget(self)
349 self.receiving.setObjectName("receiving")
351 # Add to the right side
352 self.receiving_box = QGroupBox(_("Select a receiving address"))
353 extra_layout = QGridLayout()
355 # Checkbox to filter used addresses
356 hide_used = QCheckBox(_('Hide used addresses'))
357 hide_used.setChecked(True)
358 hide_used.stateChanged.connect(self.receiving.toggle_used)
360 # Events for receiving addresses
361 self.receiving.clicked.connect(self.receiving.copy_address)
362 self.receiving.itemDoubleClicked.connect(self.receiving.edit_label)
363 self.receiving.itemChanged.connect(self.receiving.update_label)
367 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)
369 extra_layout.addWidget(self.receiving, 1,0)
370 extra_layout.addWidget(hide_used, 2,0)
371 extra_layout.setColumnMinimumWidth(0,200)
373 self.receiving_box.setLayout(extra_layout)
374 main_layout.addWidget(self.receiving_box,0,4,-1,3)
375 self.receiving_box.hide()
377 # Creating the menu bar
379 electrum_menu = menubar.addMenu(_("&Electrum"))
381 quit_option = electrum_menu.addAction(_("&Close"))
383 quit_option.triggered.connect(self.close)
385 view_menu = menubar.addMenu(_("&View"))
386 extra_menu = menubar.addMenu(_("&Extra"))
388 backup_wallet_menu = extra_menu.addAction( _("&Create wallet backup"))
389 backup_wallet_menu.triggered.connect(lambda: backup_wallet(self.config.path))
391 export_csv = extra_menu.addAction( _("&Export transactions to CSV") )
392 export_csv.triggered.connect(lambda: csv_transaction(self.actuator.wallet))
394 master_key = extra_menu.addAction( _("Copy master public key to clipboard") )
395 master_key.triggered.connect(self.actuator.copy_master_public_key)
397 expert_gui = view_menu.addAction(_("&Classic GUI"))
398 expert_gui.triggered.connect(expand_callback)
399 themes_menu = view_menu.addMenu(_("&Themes"))
400 selected_theme = self.actuator.selected_theme()
401 theme_group = QActionGroup(self)
402 for theme_name in self.actuator.theme_names():
403 theme_action = themes_menu.addAction(theme_name)
404 theme_action.setCheckable(True)
405 if selected_theme == theme_name:
406 theme_action.setChecked(True)
407 class SelectThemeFunctor:
408 def __init__(self, theme_name, toggle_theme):
409 self.theme_name = theme_name
410 self.toggle_theme = toggle_theme
411 def __call__(self, checked):
413 self.toggle_theme(self.theme_name)
414 delegate = SelectThemeFunctor(theme_name, self.toggle_theme)
415 theme_action.toggled.connect(delegate)
416 theme_group.addAction(theme_action)
417 view_menu.addSeparator()
419 show_receiving = view_menu.addAction(_("Show Receiving addresses"))
420 show_receiving.setCheckable(True)
421 show_receiving.toggled.connect(self.toggle_receiving_layout)
423 show_receiving_toggle = self.config.get("gui_show_receiving",False)
424 show_receiving.setChecked(show_receiving_toggle)
425 self.show_receiving = show_receiving
427 self.toggle_receiving_layout(show_receiving_toggle)
430 show_history = view_menu.addAction(_("Show History"))
431 show_history.setCheckable(True)
432 show_history.toggled.connect(self.show_history)
434 help_menu = menubar.addMenu(_("&Help"))
435 the_website = help_menu.addAction(_("&Website"))
436 the_website.triggered.connect(self.the_website)
437 help_menu.addSeparator()
438 report_bug = help_menu.addAction(_("&Report Bug"))
439 report_bug.triggered.connect(self.show_report_bug)
440 show_about = help_menu.addAction(_("&About"))
441 show_about.triggered.connect(self.show_about)
442 main_layout.setMenuBar(menubar)
443 self.main_layout = main_layout
445 quit_shortcut = QShortcut(QKeySequence("Ctrl+Q"), self)
446 quit_shortcut.activated.connect(self.close)
447 close_shortcut = QShortcut(QKeySequence("Ctrl+W"), self)
448 close_shortcut.activated.connect(self.close)
450 g = self.config.get("winpos-lite",[4, 25, 351, 149])
451 self.setGeometry(g[0], g[1], g[2], g[3])
453 show_hist = self.config.get("gui_show_history",False)
454 show_history.setChecked(show_hist)
455 self.show_history(show_hist)
457 self.setWindowIcon(QIcon(":icons/electrum.png"))
458 self.setWindowTitle("Electrum")
459 self.setWindowFlags(Qt.Window|Qt.MSWindowsFixedSizeDialogHint)
460 self.layout().setSizeConstraint(QLayout.SetFixedSize)
461 self.setObjectName("main_window")
465 def toggle_theme(self, theme_name):
466 old_path = QDir.currentPath()
467 self.actuator.change_theme(theme_name)
468 # Recompute style globally
469 qApp.style().unpolish(self)
470 qApp.style().polish(self)
471 QDir.setCurrent(old_path)
473 def closeEvent(self, event):
475 self.config.set_key("winpos-lite", [g.left(),g.top(),g.width(),g.height()],True)
476 self.config.set_key("gui_show_history", self.history_list.isVisible(),True)
477 self.config.set_key("gui_show_receiving", self.receiving_box.isVisible(),True)
479 super(MiniWindow, self).closeEvent(event)
482 def set_payment_fields(self, dest_address, amount):
483 self.address_input.setText(dest_address)
484 self.address_field_changed(dest_address)
485 self.amount_input.setText(amount)
490 def deactivate(self):
493 def set_quote_currency(self, currency):
494 """Set and display the fiat currency country."""
495 if currency not in self.quote_currencies:
497 self.quote_currencies.remove(currency)
498 self.quote_currencies.insert(0, currency)
499 self.refresh_balance()
501 def change_quote_currency(self, forward=True):
503 self.quote_currencies = \
504 self.quote_currencies[1:] + self.quote_currencies[0:1]
506 self.quote_currencies = \
507 self.quote_currencies[-1:] + self.quote_currencies[0:-1]
508 self.actuator.set_config_currency(self.quote_currencies[0])
509 self.refresh_balance()
511 def refresh_balance(self):
512 if self.btc_balance is None:
513 # Price has been discovered before wallet has been loaded
514 # and server connect... so bail.
516 self.set_balances(self.btc_balance)
517 self.amount_input_changed(self.amount_input.text())
519 def set_balances(self, btc_balance):
520 """Set the bitcoin balance and update the amount label accordingly."""
521 self.btc_balance = btc_balance
522 quote_text = self.create_quote_text(btc_balance)
524 quote_text = "(%s)" % quote_text
525 btc_balance = "%.4f" % (btc_balance / bitcoin(1))
526 self.balance_label.set_balance_text(btc_balance, quote_text)
527 self.setWindowTitle("Electrum %s - %s BTC" % (electrum_version, btc_balance))
529 def amount_input_changed(self, amount_text):
530 """Update the number of bitcoins displayed."""
531 self.check_button_status()
534 amount = D(str(amount_text))
535 except decimal.InvalidOperation:
536 self.balance_label.show_balance()
538 quote_text = self.create_quote_text(amount * bitcoin(1))
540 self.balance_label.set_amount_text(quote_text)
541 self.balance_label.show_amount()
543 self.balance_label.show_balance()
545 def create_quote_text(self, btc_balance):
546 """Return a string copy of the amount fiat currency the
547 user has in bitcoins."""
548 quote_currency = self.quote_currencies[0]
549 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
550 if quote_balance is None:
553 quote_text = "%.2f %s" % ((quote_balance / bitcoin(1)),
558 if self.actuator.send(self.address_input.text(),
559 self.amount_input.text(), self):
560 self.address_input.setText("")
561 self.amount_input.setText("")
563 def check_button_status(self):
564 """Check that the bitcoin address is valid and that something
565 is entered in the amount before making the send button clickable."""
567 value = D(str(self.amount_input.text())) * 10**8
568 except decimal.InvalidOperation:
570 # self.address_input.property(...) returns a qVariant, not a bool.
571 # The == is needed to properly invoke a comparison.
572 if (self.address_input.property("isValid") == True and
573 value is not None and 0 < value <= self.btc_balance):
574 self.send_button.setDisabled(False)
576 self.send_button.setDisabled(True)
578 def address_field_changed(self, address):
579 # label or alias, with address in brackets
580 match2 = re.match("(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>",
583 address = match2.group(2)
584 self.address_input.setText(address)
586 if is_valid(address):
587 self.check_button_status()
588 self.address_input.setProperty("isValid", True)
589 self.recompute_style(self.address_input)
591 self.send_button.setDisabled(True)
592 self.address_input.setProperty("isValid", False)
593 self.recompute_style(self.address_input)
595 if len(address) == 0:
596 self.address_input.setProperty("isValid", None)
597 self.recompute_style(self.address_input)
599 def recompute_style(self, element):
600 self.style().unpolish(element)
601 self.style().polish(element)
603 def copy_address(self):
604 receive_popup = ReceivePopup(self.receive_button)
605 self.actuator.copy_address(receive_popup)
607 def update_completions(self, completions):
608 self.address_completions.setStringList(completions)
611 def update_history(self, tx_history):
613 self.history_list.empty()
615 for item in tx_history[-10:]:
616 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
617 label = self.actuator.wallet.get_label(tx_hash)[0]
618 #amount = D(value) / 10**8
619 v_str = format_satoshis(value, True)
620 self.history_list.append(label, v_str, age(timestamp))
623 self.actuator.acceptbit(self.quote_currencies[0])
625 def the_website(self):
626 webbrowser.open("http://electrum.org")
628 def show_about(self):
629 QMessageBox.about(self, "Electrum",
630 _("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."))
632 def show_report_bug(self):
633 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
634 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
636 def toggle_receiving_layout(self, toggle_state):
638 self.receiving_box.show()
640 self.receiving_box.hide()
642 def show_history(self, toggle_state):
644 self.main_layout.setRowMinimumHeight(3,200)
645 self.history_list.show()
647 self.main_layout.setRowMinimumHeight(3,0)
648 self.history_list.hide()
650 class BalanceLabel(QLabel):
656 def __init__(self, change_quote_currency, parent=None):
657 super(QLabel, self).__init__(_("Connecting..."), parent)
658 self.change_quote_currency = change_quote_currency
659 self.state = self.SHOW_CONNECTING
660 self.balance_text = ""
661 self.amount_text = ""
663 def mousePressEvent(self, event):
664 """Change the fiat currency selection if window background is clicked."""
665 if self.state != self.SHOW_CONNECTING:
666 self.change_quote_currency(event.button() == Qt.LeftButton)
668 def set_balance_text(self, btc_balance, quote_text):
669 """Set the amount of bitcoins in the gui."""
670 if self.state == self.SHOW_CONNECTING:
671 self.state = self.SHOW_BALANCE
672 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)
673 if self.state == self.SHOW_BALANCE:
674 self.setText(self.balance_text)
676 def set_amount_text(self, quote_text):
677 self.amount_text = "<span style='font-size: 10pt'>%s</span>" % quote_text
678 if self.state == self.SHOW_AMOUNT:
679 self.setText(self.amount_text)
681 def show_balance(self):
682 if self.state == self.SHOW_AMOUNT:
683 self.state = self.SHOW_BALANCE
684 self.setText(self.balance_text)
686 def show_amount(self):
687 if self.state == self.SHOW_BALANCE:
688 self.state = self.SHOW_AMOUNT
689 self.setText(self.amount_text)
691 def ok_cancel_buttons(dialog):
692 row_layout = QHBoxLayout()
693 row_layout.addStretch(1)
694 ok_button = QPushButton(_("OK"))
695 row_layout.addWidget(ok_button)
696 ok_button.clicked.connect(dialog.accept)
697 cancel_button = QPushButton(_("Cancel"))
698 row_layout.addWidget(cancel_button)
699 cancel_button.clicked.connect(dialog.reject)
702 class PasswordDialog(QDialog):
704 def __init__(self, parent):
705 super(QDialog, self).__init__(parent)
709 self.password_input = QLineEdit()
710 self.password_input.setEchoMode(QLineEdit.Password)
712 main_layout = QVBoxLayout(self)
713 message = _('Please enter your password')
714 main_layout.addWidget(QLabel(message))
718 grid.addWidget(QLabel(_('Password')), 1, 0)
719 grid.addWidget(self.password_input, 1, 1)
720 main_layout.addLayout(grid)
722 main_layout.addLayout(ok_cancel_buttons(self))
723 self.setLayout(main_layout)
728 return unicode(self.password_input.text())
730 class ReceivePopup(QDialog):
732 def leaveEvent(self, event):
735 def setup(self, address):
736 label = QLabel(_("Copied your Bitcoin address to the clipboard!"))
737 address_display = QLineEdit(address)
738 address_display.setReadOnly(True)
739 resize_line_edit_width(address_display, address)
741 main_layout = QVBoxLayout(self)
742 main_layout.addWidget(label)
743 main_layout.addWidget(address_display)
745 self.setMouseTracking(True)
746 self.setWindowTitle("Electrum - " + _("Receive Bitcoin payment"))
747 self.setWindowFlags(Qt.Window|Qt.FramelessWindowHint|
748 Qt.MSWindowsFixedSizeDialogHint)
749 self.layout().setSizeConstraint(QLayout.SetFixedSize)
750 #self.setFrameStyle(QFrame.WinPanel|QFrame.Raised)
751 #self.setAlignment(Qt.AlignCenter)
754 parent = self.parent()
755 top_left_pos = parent.mapToGlobal(parent.rect().bottomLeft())
756 self.move(top_left_pos)
757 center_mouse_pos = self.mapToGlobal(self.rect().center())
758 QCursor.setPos(center_mouse_pos)
762 """Initialize the definitions relating to themes and
763 sending/receiving bitcoins."""
766 def __init__(self, config, wallet):
767 """Retrieve the gui theme used in previous session."""
770 self.theme_name = self.config.get('litegui_theme','Cleanlook')
771 self.themes = load_theme_paths()
773 def load_theme(self):
774 """Load theme retrieved from wallet file."""
776 theme_prefix, theme_path = self.themes[self.theme_name]
778 util.print_error("Theme not found!", self.theme_name)
780 QDir.setCurrent(os.path.join(theme_prefix, theme_path))
781 with open(rsrc("style.css")) as style_file:
782 qApp.setStyleSheet(style_file.read())
784 def theme_names(self):
786 return sorted(self.themes.keys())
788 def selected_theme(self):
790 return self.theme_name
792 def change_theme(self, theme_name):
794 self.theme_name = theme_name
795 self.config.set_key('litegui_theme',theme_name)
798 def set_configured_currency(self, set_quote_currency):
799 """Set the inital fiat currency conversion country (USD/EUR/GBP) in
800 the GUI to what it was set to in the wallet."""
801 currency = self.config.get('currency')
802 # currency can be none when Electrum is used for the first
803 # time and no setting has been created yet.
804 if currency is not None:
805 set_quote_currency(currency)
807 def set_config_currency(self, conversion_currency):
808 """Change the wallet fiat currency country."""
809 self.config.set_key('conversion_currency',conversion_currency,True)
811 def copy_address(self, receive_popup):
812 """Copy the wallet addresses into the client."""
813 addrs = [addr for addr in self.wallet.addresses(True)
814 if not self.wallet.is_change(addr)]
815 # Select most recent addresses from gap limit
816 addrs = addrs[-self.wallet.gap_limit:]
817 copied_address = random.choice(addrs)
818 qApp.clipboard().setText(copied_address)
819 receive_popup.setup(copied_address)
820 receive_popup.popup()
822 def waiting_dialog(self, f):
827 w.setWindowTitle('Electrum')
828 l = QLabel('Sending transaction, please wait.')
837 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
842 def send(self, address, amount, parent_window):
843 """Send bitcoins to the target address."""
844 dest_address = self.fetch_destination(address)
846 if dest_address is None or not is_valid(dest_address):
847 QMessageBox.warning(parent_window, _('Error'),
848 _('Invalid Bitcoin Address') + ':\n' + address, _('OK'))
851 convert_amount = lambda amount: \
852 int(D(unicode(amount)) * bitcoin(1))
853 amount = convert_amount(amount)
855 if self.wallet.use_encryption:
856 password_dialog = PasswordDialog(parent_window)
857 password = password_dialog.run()
865 if amount < bitcoin(1) / 10:
867 fee = bitcoin(1) / 1000
870 tx = self.wallet.mktx([(dest_address, amount)], password, fee)
871 except BaseException as error:
872 QMessageBox.warning(parent_window, _('Error'), str(error), _('OK'))
876 h = self.wallet.send_tx(tx)
878 self.waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Sending transaction, please wait..."))
880 status, message = self.wallet.receive_tx(h)
884 dumpf = tempfile.NamedTemporaryFile(delete=False)
887 print "Dumped error tx to", dumpf.name
888 QMessageBox.warning(parent_window, _('Error'), message, _('OK'))
891 TransactionWindow(message, self)
893 filename = 'unsigned_tx_%s' % (time.mktime(time.gmtime()))
895 fileName = QFileDialog.getSaveFileName(QWidget(), _("Select a transaction filename"), os.path.expanduser('~/%s' % (filename)))
896 with open(fileName,'w') as f:
897 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
898 QMessageBox.information(QWidget(), _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
899 except BaseException as e:
900 QMessageBox.warning(QWidget(), _('Error'), _('Could not write transaction to file: %s' % e), _('OK'))
903 def fetch_destination(self, address):
904 recipient = unicode(address).strip()
907 match1 = re.match("^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$",
910 # label or alias, with address in brackets
911 match2 = re.match("(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>",
916 self.wallet.get_alias(recipient, True,
917 self.show_message, self.question)
920 return match2.group(2)
925 def copy_master_public_key(self):
926 master_pubkey = self.wallet.get_master_public_key()
927 qApp.clipboard().setText(master_pubkey)
928 QMessageBox.information(None, _("Copy successful"), _("Your master public key has been copied to your clipboard."))
931 def acceptbit(self, currency):
932 master_pubkey = self.wallet.master_public_key
933 url = "http://acceptbit.com/mpk/%s/%s" % (master_pubkey, currency)
936 def show_seed_dialog(self):
937 gui_classic.ElectrumWindow.show_seed_dialog(self.wallet)
939 class MiniDriver(QObject):
946 def __init__(self, wallet, window):
947 super(QObject, self).__init__()
952 self.wallet.interface.register_callback('updated',self.update_callback)
953 self.wallet.interface.register_callback('connected', self.update_callback)
954 self.wallet.interface.register_callback('disconnected', self.update_callback)
959 self.connect(self, SIGNAL("updatesignal()"), self.update)
960 self.update_callback()
962 # This is a hack to workaround that Qt does not like changing the
963 # window properties from this other thread before the runloop has
965 def update_callback(self):
966 self.emit(SIGNAL("updatesignal()"))
969 if not self.wallet.interface:
971 elif not self.wallet.interface.is_connected:
973 elif not self.wallet.up_to_date:
978 if self.wallet.up_to_date:
979 self.update_balance()
980 self.update_completions()
981 self.update_history()
983 def initializing(self):
984 if self.state == self.INITIALIZING:
986 self.state = self.INITIALIZING
987 self.window.deactivate()
989 def connecting(self):
990 if self.state == self.CONNECTING:
992 self.state = self.CONNECTING
993 self.window.deactivate()
995 def synchronizing(self):
996 if self.state == self.SYNCHRONIZING:
998 self.state = self.SYNCHRONIZING
999 self.window.deactivate()
1002 if self.state == self.READY:
1004 self.state = self.READY
1005 self.window.activate()
1007 def update_balance(self):
1008 conf_balance, unconf_balance = self.wallet.get_balance()
1009 balance = D(conf_balance + unconf_balance)
1010 self.window.set_balances(balance)
1012 def update_completions(self):
1014 for addr, label in self.wallet.labels.items():
1015 if addr in self.wallet.addressbook:
1016 completions.append("%s <%s>" % (label, addr))
1017 self.window.update_completions(completions)
1019 def update_history(self):
1020 tx_history = self.wallet.get_tx_history()
1021 self.window.update_history(tx_history)
1024 if __name__ == "__main__":
1025 app = QApplication(sys.argv)
1026 with open(rsrc("style.css")) as style_file:
1027 app.setStyleSheet(style_file.read())
1029 sys.exit(app.exec_())