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
25 from electrum import wallet
28 import receiving_widget
29 from electrum import util
33 from electrum.version import ELECTRUM_VERSION as electrum_version
34 from electrum.util import format_satoshis, age
38 bitcoin = lambda v: v * 100000000
40 def IconButton(filename, parent=None):
41 pixmap = QPixmap(filename)
43 return QPushButton(icon, "", parent)
48 self.emit(SIGNAL('timersignal'))
51 def resize_line_edit_width(line_edit, text_input):
52 metrics = QFontMetrics(qApp.font())
53 # Create an extra character to add some space on the end
55 line_edit.setMinimumWidth(metrics.width(text_input))
57 def load_theme_name(theme_path):
59 with open(os.path.join(theme_path, "name.cfg")) as name_cfg_file:
60 return name_cfg_file.read().rstrip("\n").strip()
65 def theme_dirs_from_prefix(prefix):
66 if not os.path.exists(prefix):
69 for potential_theme in os.listdir(prefix):
70 theme_full_path = os.path.join(prefix, potential_theme)
71 theme_css = os.path.join(theme_full_path, "style.css")
72 if not os.path.exists(theme_css):
74 theme_name = load_theme_name(theme_full_path)
75 if theme_name is None:
77 theme_paths[theme_name] = prefix, potential_theme
80 def load_theme_paths():
82 prefixes = (util.local_data_dir(), util.appdata_dir())
83 for prefix in prefixes:
84 theme_paths.update(theme_dirs_from_prefix(prefix))
88 def csv_transaction(wallet):
90 select_export = _('Select file to export your wallet transactions to')
91 fileName = QFileDialog.getSaveFileName(QWidget(), select_export, os.path.expanduser('~/electrum-history.csv'), "*.csv")
93 with open(fileName, "w+") as csvfile:
94 transaction = csv.writer(csvfile)
95 transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
96 for item in wallet.get_tx_history():
97 tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
99 if timestamp is not None:
101 time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
102 except [RuntimeError, TypeError, NameError] as reason:
103 time_string = "unknown"
106 time_string = "unknown"
108 time_string = "pending"
110 if value is not None:
111 value_string = format_satoshis(value, True, wallet.num_zeros)
116 fee_string = format_satoshis(fee, True, wallet.num_zeros)
121 label, is_default_label = wallet.get_label(tx_hash)
125 balance_string = format_satoshis(balance, False, wallet.num_zeros)
126 transaction.writerow([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
127 QMessageBox.information(None,"CSV Export created", "Your CSV export has been successfully created.")
128 except (IOError, os.error), reason:
129 export_error_label = _("Electrum was unable to produce a transaction export.")
130 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
133 class ElectrumGui(QObject):
135 def __init__(self, wallet, config, expert=None):
136 super(QObject, self).__init__()
140 self.check_qt_version()
142 if self.expert != None:
143 self.app = self.expert.app
145 self.app = QApplication(sys.argv)
147 def check_qt_version(self):
148 qtVersion = qVersion()
149 if not(int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
150 app = QApplication(sys.argv)
151 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")
152 self.config.set_key('gui','classic',True)
157 actuator = MiniActuator(self.wallet)
158 # Should probably not modify the current path but instead
159 # change the behaviour of rsrc(...)
160 old_path = QDir.currentPath()
161 actuator.load_theme()
163 self.mini = MiniWindow(actuator, self.expand, self.config)
164 driver = MiniDriver(self.wallet, self.mini)
166 # Reset path back to original value now that loading the GUI
168 QDir.setCurrent(old_path)
173 if self.expert == None:
176 self.expert = gui_classic.ElectrumWindow(self.wallet, self.config)
177 self.expert.app = self.app
178 self.expert.connect_slots(timer)
179 self.expert.update_wallet()
183 """Hide the lite mode window and show pro-mode."""
184 self.config.set_key('gui', 'classic', True)
188 def set_url(self, url):
189 payto, amount, label, message, signature, identity, url = \
190 self.wallet.parse_url(url, self.show_message, self.show_question)
191 self.mini.set_payment_fields(payto, amount)
193 def show_message(self, message):
194 QMessageBox.information(self.mini, _("Message"), message, _("OK"))
196 def show_question(self, message):
197 choice = QMessageBox.question(self.mini, _("Message"), message,
198 QMessageBox.Yes|QMessageBox.No,
200 return choice == QMessageBox.Yes
202 def restore_or_create(self):
203 qt_gui_object = gui_classic.ElectrumGui(self.wallet, self.app)
204 return qt_gui_object.restore_or_create()
206 class TransactionWindow(QDialog):
209 label = unicode(self.label_edit.text())
210 self.parent.wallet.labels[self.tx_id] = label
212 super(TransactionWindow, self).accept()
214 def __init__(self, transaction_id, parent):
215 super(TransactionWindow, self).__init__()
217 self.tx_id = str(transaction_id)
222 self.setWindowTitle(_("Transaction successfully sent"))
224 self.layout = QGridLayout(self)
225 history_label = "%s\n%s" % (_("Your transaction has been sent."), _("Please enter a label for this transaction for future reference."))
226 self.layout.addWidget(QLabel(history_label))
228 self.label_edit = QLineEdit()
229 self.label_edit.setPlaceholderText(_("Transaction label"))
230 self.label_edit.setObjectName("label_input")
231 self.label_edit.setAttribute(Qt.WA_MacShowFocusRect, 0)
232 self.label_edit.setFocusPolicy(Qt.ClickFocus)
233 self.layout.addWidget(self.label_edit)
235 self.save_button = QPushButton(_("Save"))
236 self.layout.addWidget(self.save_button)
237 self.save_button.clicked.connect(self.set_label)
241 class MiniWindow(QDialog):
243 def __init__(self, actuator, expand_callback, config):
244 super(MiniWindow, self).__init__()
245 tx = "e08115d0f7819aee65b9d24f81ef9d46eb62bb67ddef5318156cbc3ceb7b703e"
247 self.actuator = actuator
249 self.btc_balance = None
250 self.quote_currencies = ["BRL", "CNY", "EUR", "GBP", "RUB", "USD"]
251 self.actuator.set_configured_currency(self.set_quote_currency)
252 self.exchanger = exchange_rate.Exchanger(self)
253 # Needed because price discovery is done in a different thread
254 # which needs to be sent back to this main one to update the GUI
255 self.connect(self, SIGNAL("refresh_balance()"), self.refresh_balance)
257 self.balance_label = BalanceLabel(self.change_quote_currency)
258 self.balance_label.setObjectName("balance_label")
261 # Bitcoin address code
262 self.address_input = QLineEdit()
263 self.address_input.setPlaceholderText(_("Enter a Bitcoin address or contact"))
264 self.address_input.setObjectName("address_input")
266 self.address_input.setFocusPolicy(Qt.ClickFocus)
268 self.address_input.textChanged.connect(self.address_field_changed)
269 resize_line_edit_width(self.address_input,
270 "1BtaFUr3qVvAmwrsuDuu5zk6e4s2rxd2Gy")
272 self.address_completions = QStringListModel()
273 address_completer = QCompleter(self.address_input)
274 address_completer.setCaseSensitivity(False)
275 address_completer.setModel(self.address_completions)
276 self.address_input.setCompleter(address_completer)
278 address_layout = QHBoxLayout()
279 address_layout.addWidget(self.address_input)
281 self.amount_input = QLineEdit()
282 self.amount_input.setPlaceholderText(_("... and amount"))
283 self.amount_input.setObjectName("amount_input")
285 self.amount_input.setFocusPolicy(Qt.ClickFocus)
286 # This is changed according to the user's displayed balance
287 self.amount_validator = QDoubleValidator(self.amount_input)
288 self.amount_validator.setNotation(QDoubleValidator.StandardNotation)
289 self.amount_validator.setDecimals(8)
290 self.amount_input.setValidator(self.amount_validator)
292 # This removes the very ugly OSX highlighting, please leave this in :D
293 self.address_input.setAttribute(Qt.WA_MacShowFocusRect, 0)
294 self.amount_input.setAttribute(Qt.WA_MacShowFocusRect, 0)
295 self.amount_input.textChanged.connect(self.amount_input_changed)
297 if self.actuator.wallet.seed:
298 self.send_button = QPushButton(_("&Send"))
300 self.send_button = QPushButton(_("&Create"))
302 self.send_button.setObjectName("send_button")
303 self.send_button.setDisabled(True);
304 self.send_button.clicked.connect(self.send)
306 # Creating the receive button
307 self.switch_button = QPushButton( QIcon(":icons/switchgui.png"),'' )
308 self.switch_button.setMaximumWidth(25)
309 self.switch_button.setFlat(True)
310 self.switch_button.clicked.connect(expand_callback)
312 main_layout = QGridLayout(self)
314 main_layout.addWidget(self.balance_label, 0, 0, 1, 3)
315 main_layout.addWidget(self.switch_button, 0, 3)
317 main_layout.addWidget(self.address_input, 1, 0, 1, 4)
318 main_layout.addWidget(self.amount_input, 2, 0, 1, 2)
319 main_layout.addWidget(self.send_button, 2, 2, 1, 2)
321 self.send_button.setMaximumWidth(125)
323 self.history_list = history_widget.HistoryWidget()
324 self.history_list.setObjectName("history")
325 self.history_list.hide()
326 self.history_list.setAlternatingRowColors(True)
328 main_layout.addWidget(self.history_list, 3, 0, 1, 4)
330 self.receiving = receiving_widget.ReceivingWidget(self)
331 self.receiving.setObjectName("receiving")
333 # Add to the right side
334 self.receiving_box = QGroupBox(_("Select a receiving address"))
335 extra_layout = QGridLayout()
337 # Checkbox to filter used addresses
338 hide_used = QCheckBox(_('Hide used addresses'))
339 hide_used.setChecked(True)
340 hide_used.stateChanged.connect(self.receiving.toggle_used)
342 # Events for receiving addresses
343 self.receiving.clicked.connect(self.receiving.copy_address)
344 self.receiving.itemDoubleClicked.connect(self.receiving.edit_label)
345 self.receiving.itemChanged.connect(self.receiving.update_label)
349 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)
351 extra_layout.addWidget(self.receiving, 1,0)
352 extra_layout.addWidget(hide_used, 2,0)
353 extra_layout.setColumnMinimumWidth(0,200)
355 self.receiving_box.setLayout(extra_layout)
356 main_layout.addWidget(self.receiving_box,0,4,-1,3)
357 self.receiving_box.hide()
359 # Creating the menu bar
361 electrum_menu = menubar.addMenu(_("&Electrum"))
363 quit_option = electrum_menu.addAction(_("&Close"))
365 quit_option.triggered.connect(self.close)
367 view_menu = menubar.addMenu(_("&View"))
368 extra_menu = menubar.addMenu(_("&Extra"))
370 backup_wallet = extra_menu.addAction( _("&Create wallet backup"))
371 backup_wallet.triggered.connect(self.backup_wallet)
373 export_csv = extra_menu.addAction( _("&Export transactions to CSV") )
374 export_csv.triggered.connect(lambda: csv_transaction(self.actuator.wallet))
376 master_key = extra_menu.addAction( _("Copy master public key to clipboard") )
377 master_key.triggered.connect(self.actuator.copy_master_public_key)
379 expert_gui = view_menu.addAction(_("&Classic GUI"))
380 expert_gui.triggered.connect(expand_callback)
381 themes_menu = view_menu.addMenu(_("&Themes"))
382 selected_theme = self.actuator.selected_theme()
383 theme_group = QActionGroup(self)
384 for theme_name in self.actuator.theme_names():
385 theme_action = themes_menu.addAction(theme_name)
386 theme_action.setCheckable(True)
387 if selected_theme == theme_name:
388 theme_action.setChecked(True)
389 class SelectThemeFunctor:
390 def __init__(self, theme_name, toggle_theme):
391 self.theme_name = theme_name
392 self.toggle_theme = toggle_theme
393 def __call__(self, checked):
395 self.toggle_theme(self.theme_name)
396 delegate = SelectThemeFunctor(theme_name, self.toggle_theme)
397 theme_action.toggled.connect(delegate)
398 theme_group.addAction(theme_action)
399 view_menu.addSeparator()
401 show_receiving = view_menu.addAction(_("Show Receiving addresses"))
402 show_receiving.setCheckable(True)
403 show_receiving.toggled.connect(self.toggle_receiving_layout)
405 show_receiving_toggle = self.config.get("gui_show_receiving",False)
406 show_receiving.setChecked(show_receiving_toggle)
407 self.show_receiving = show_receiving
409 self.toggle_receiving_layout(show_receiving_toggle)
412 show_history = view_menu.addAction(_("Show History"))
413 show_history.setCheckable(True)
414 show_history.toggled.connect(self.show_history)
416 help_menu = menubar.addMenu(_("&Help"))
417 the_website = help_menu.addAction(_("&Website"))
418 the_website.triggered.connect(self.the_website)
419 help_menu.addSeparator()
420 report_bug = help_menu.addAction(_("&Report Bug"))
421 report_bug.triggered.connect(self.show_report_bug)
422 show_about = help_menu.addAction(_("&About"))
423 show_about.triggered.connect(self.show_about)
424 main_layout.setMenuBar(menubar)
425 self.main_layout = main_layout
427 quit_shortcut = QShortcut(QKeySequence("Ctrl+Q"), self)
428 quit_shortcut.activated.connect(self.close)
429 close_shortcut = QShortcut(QKeySequence("Ctrl+W"), self)
430 close_shortcut.activated.connect(self.close)
432 g = self.config.get("winpos-lite",[4, 25, 351, 149])
433 self.setGeometry(g[0], g[1], g[2], g[3])
435 show_hist = self.config.get("gui_show_history",False)
436 show_history.setChecked(show_hist)
437 self.show_history(show_hist)
439 self.setWindowIcon(QIcon(":electrum.png"))
440 self.setWindowTitle("Electrum")
441 self.setWindowFlags(Qt.Window|Qt.MSWindowsFixedSizeDialogHint)
442 self.layout().setSizeConstraint(QLayout.SetFixedSize)
443 self.setObjectName("main_window")
447 def toggle_theme(self, theme_name):
448 old_path = QDir.currentPath()
449 self.actuator.change_theme(theme_name)
450 # Recompute style globally
451 qApp.style().unpolish(self)
452 qApp.style().polish(self)
453 QDir.setCurrent(old_path)
455 def closeEvent(self, event):
457 self.config.set_key("winpos-lite", [g.left(),g.top(),g.width(),g.height()],True)
458 self.config.set_key("gui_show_history", self.history_list.isVisible(),True)
459 self.config.set_key("gui_show_receiving", self.receiving_box.isVisible(),True)
461 super(MiniWindow, self).closeEvent(event)
464 def set_payment_fields(self, dest_address, amount):
465 self.address_input.setText(dest_address)
466 self.address_field_changed(dest_address)
467 self.amount_input.setText(amount)
472 def deactivate(self):
475 def set_quote_currency(self, currency):
476 """Set and display the fiat currency country."""
477 assert currency in self.quote_currencies
478 self.quote_currencies.remove(currency)
479 self.quote_currencies.insert(0, currency)
480 self.refresh_balance()
482 def change_quote_currency(self):
483 self.quote_currencies = \
484 self.quote_currencies[1:] + self.quote_currencies[0:1]
485 self.actuator.set_config_currency(self.quote_currencies[0])
486 self.refresh_balance()
488 def refresh_balance(self):
489 if self.btc_balance is None:
490 # Price has been discovered before wallet has been loaded
491 # and server connect... so bail.
493 self.set_balances(self.btc_balance)
494 self.amount_input_changed(self.amount_input.text())
496 def set_balances(self, btc_balance):
497 """Set the bitcoin balance and update the amount label accordingly."""
498 self.btc_balance = btc_balance
499 quote_text = self.create_quote_text(btc_balance)
501 quote_text = "(%s)" % quote_text
502 btc_balance = "%.4f" % (btc_balance / bitcoin(1))
503 self.balance_label.set_balance_text(btc_balance, quote_text)
504 self.setWindowTitle("Electrum %s - %s BTC" % (electrum_version, btc_balance))
506 def amount_input_changed(self, amount_text):
507 """Update the number of bitcoins displayed."""
508 self.check_button_status()
511 amount = D(str(amount_text))
512 except decimal.InvalidOperation:
513 self.balance_label.show_balance()
515 quote_text = self.create_quote_text(amount * bitcoin(1))
517 self.balance_label.set_amount_text(quote_text)
518 self.balance_label.show_amount()
520 self.balance_label.show_balance()
522 def create_quote_text(self, btc_balance):
523 """Return a string copy of the amount fiat currency the
524 user has in bitcoins."""
525 quote_currency = self.quote_currencies[0]
526 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
527 if quote_balance is None:
530 quote_text = "%.2f %s" % ((quote_balance / bitcoin(1)),
535 if self.actuator.send(self.address_input.text(),
536 self.amount_input.text(), self):
537 self.address_input.setText("")
538 self.amount_input.setText("")
540 def check_button_status(self):
541 """Check that the bitcoin address is valid and that something
542 is entered in the amount before making the send button clickable."""
544 value = D(str(self.amount_input.text())) * 10**8
545 except decimal.InvalidOperation:
547 # self.address_input.property(...) returns a qVariant, not a bool.
548 # The == is needed to properly invoke a comparison.
549 if (self.address_input.property("isValid") == True and
550 value is not None and 0 < value <= self.btc_balance):
551 self.send_button.setDisabled(False)
553 self.send_button.setDisabled(True)
555 def address_field_changed(self, address):
556 # label or alias, with address in brackets
557 match2 = re.match("(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>",
560 address = match2.group(2)
561 self.address_input.setText(address)
563 if is_valid(address):
564 self.check_button_status()
565 self.address_input.setProperty("isValid", True)
566 self.recompute_style(self.address_input)
568 self.send_button.setDisabled(True)
569 self.address_input.setProperty("isValid", False)
570 self.recompute_style(self.address_input)
572 if len(address) == 0:
573 self.address_input.setProperty("isValid", None)
574 self.recompute_style(self.address_input)
576 def recompute_style(self, element):
577 self.style().unpolish(element)
578 self.style().polish(element)
580 def copy_address(self):
581 receive_popup = ReceivePopup(self.receive_button)
582 self.actuator.copy_address(receive_popup)
584 def update_completions(self, completions):
585 self.address_completions.setStringList(completions)
588 def update_history(self, tx_history):
590 self.history_list.empty()
592 for item in tx_history[-10:]:
593 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
594 label = self.actuator.wallet.get_label(tx_hash)[0]
595 #amount = D(value) / 10**8
596 v_str = format_satoshis(value, True)
597 self.history_list.append(label, v_str, age(timestamp))
600 self.actuator.acceptbit(self.quote_currencies[0])
602 def the_website(self):
603 webbrowser.open("http://electrum.org")
605 def show_about(self):
606 QMessageBox.about(self, "Electrum",
607 _("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."))
609 def show_report_bug(self):
610 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
611 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
613 def toggle_receiving_layout(self, toggle_state):
615 self.receiving_box.show()
617 self.receiving_box.hide()
619 def show_history(self, toggle_state):
621 self.main_layout.setRowMinimumHeight(3,200)
622 self.history_list.show()
624 self.main_layout.setRowMinimumHeight(3,0)
625 self.history_list.hide()
627 def backup_wallet(self):
629 folderName = QFileDialog.getExistingDirectory(QWidget(), _('Select folder to save a copy of your wallet to'), os.path.expanduser('~/'))
631 sourceFile = util.user_dir() + '/electrum.dat'
632 shutil.copy2(sourceFile, str(folderName))
633 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(folderName))
634 except (IOError, os.error), reason:
635 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
640 class BalanceLabel(QLabel):
646 def __init__(self, change_quote_currency, parent=None):
647 super(QLabel, self).__init__(_("Connecting..."), parent)
648 self.change_quote_currency = change_quote_currency
649 self.state = self.SHOW_CONNECTING
650 self.balance_text = ""
651 self.amount_text = ""
653 def mousePressEvent(self, event):
654 """Change the fiat currency selection if window background is clicked."""
655 if self.state != self.SHOW_CONNECTING:
656 self.change_quote_currency()
658 def set_balance_text(self, btc_balance, quote_text):
659 """Set the amount of bitcoins in the gui."""
660 if self.state == self.SHOW_CONNECTING:
661 self.state = self.SHOW_BALANCE
662 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)
663 if self.state == self.SHOW_BALANCE:
664 self.setText(self.balance_text)
666 def set_amount_text(self, quote_text):
667 self.amount_text = "<span style='font-size: 10pt'>%s</span>" % quote_text
668 if self.state == self.SHOW_AMOUNT:
669 self.setText(self.amount_text)
671 def show_balance(self):
672 if self.state == self.SHOW_AMOUNT:
673 self.state = self.SHOW_BALANCE
674 self.setText(self.balance_text)
676 def show_amount(self):
677 if self.state == self.SHOW_BALANCE:
678 self.state = self.SHOW_AMOUNT
679 self.setText(self.amount_text)
681 def ok_cancel_buttons(dialog):
682 row_layout = QHBoxLayout()
683 row_layout.addStretch(1)
684 ok_button = QPushButton(_("OK"))
685 row_layout.addWidget(ok_button)
686 ok_button.clicked.connect(dialog.accept)
687 cancel_button = QPushButton(_("Cancel"))
688 row_layout.addWidget(cancel_button)
689 cancel_button.clicked.connect(dialog.reject)
692 class PasswordDialog(QDialog):
694 def __init__(self, parent):
695 super(QDialog, self).__init__(parent)
699 self.password_input = QLineEdit()
700 self.password_input.setEchoMode(QLineEdit.Password)
702 main_layout = QVBoxLayout(self)
703 message = _('Please enter your password')
704 main_layout.addWidget(QLabel(message))
708 grid.addWidget(QLabel(_('Password')), 1, 0)
709 grid.addWidget(self.password_input, 1, 1)
710 main_layout.addLayout(grid)
712 main_layout.addLayout(ok_cancel_buttons(self))
713 self.setLayout(main_layout)
718 return unicode(self.password_input.text())
720 class ReceivePopup(QDialog):
722 def leaveEvent(self, event):
725 def setup(self, address):
726 label = QLabel(_("Copied your Bitcoin address to the clipboard!"))
727 address_display = QLineEdit(address)
728 address_display.setReadOnly(True)
729 resize_line_edit_width(address_display, address)
731 main_layout = QVBoxLayout(self)
732 main_layout.addWidget(label)
733 main_layout.addWidget(address_display)
735 self.setMouseTracking(True)
736 self.setWindowTitle("Electrum - " + _("Receive Bitcoin payment"))
737 self.setWindowFlags(Qt.Window|Qt.FramelessWindowHint|
738 Qt.MSWindowsFixedSizeDialogHint)
739 self.layout().setSizeConstraint(QLayout.SetFixedSize)
740 #self.setFrameStyle(QFrame.WinPanel|QFrame.Raised)
741 #self.setAlignment(Qt.AlignCenter)
744 parent = self.parent()
745 top_left_pos = parent.mapToGlobal(parent.rect().bottomLeft())
746 self.move(top_left_pos)
747 center_mouse_pos = self.mapToGlobal(self.rect().center())
748 QCursor.setPos(center_mouse_pos)
752 """Initialize the definitions relating to themes and
753 sending/receiving bitcoins."""
756 def __init__(self, wallet):
757 """Retrieve the gui theme used in previous session."""
759 self.theme_name = self.wallet.config.get('litegui_theme','Cleanlook')
760 self.themes = load_theme_paths()
762 def load_theme(self):
763 """Load theme retrieved from wallet file."""
765 theme_prefix, theme_path = self.themes[self.theme_name]
767 util.print_error("Theme not found!", self.theme_name)
769 QDir.setCurrent(os.path.join(theme_prefix, theme_path))
770 with open(rsrc("style.css")) as style_file:
771 qApp.setStyleSheet(style_file.read())
773 def theme_names(self):
775 return sorted(self.themes.keys())
777 def selected_theme(self):
779 return self.theme_name
781 def change_theme(self, theme_name):
783 self.theme_name = theme_name
784 self.wallet.config.set_key('litegui_theme',theme_name)
787 def set_configured_currency(self, set_quote_currency):
788 """Set the inital fiat currency conversion country (USD/EUR/GBP) in
789 the GUI to what it was set to in the wallet."""
790 currency = self.wallet.config.get('conversion_currency')
791 # currency can be none when Electrum is used for the first
792 # time and no setting has been created yet.
793 if currency is not None:
794 set_quote_currency(currency)
796 def set_config_currency(self, conversion_currency):
797 """Change the wallet fiat currency country."""
798 self.wallet.config.set_key('conversion_currency',conversion_currency,True)
800 def copy_address(self, receive_popup):
801 """Copy the wallet addresses into the client."""
802 addrs = [addr for addr in self.wallet.addresses(True)
803 if not self.wallet.is_change(addr)]
804 # Select most recent addresses from gap limit
805 addrs = addrs[-self.wallet.gap_limit:]
806 copied_address = random.choice(addrs)
807 qApp.clipboard().setText(copied_address)
808 receive_popup.setup(copied_address)
809 receive_popup.popup()
811 def waiting_dialog(self, f):
816 w.setWindowTitle('Electrum')
817 l = QLabel('Sending transaction, please wait.')
826 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
831 def send(self, address, amount, parent_window):
832 """Send bitcoins to the target address."""
833 dest_address = self.fetch_destination(address)
835 if dest_address is None or not is_valid(dest_address):
836 QMessageBox.warning(parent_window, _('Error'),
837 _('Invalid Bitcoin Address') + ':\n' + address, _('OK'))
840 convert_amount = lambda amount: \
841 int(D(unicode(amount)) * bitcoin(1))
842 amount = convert_amount(amount)
844 if self.wallet.use_encryption:
845 password_dialog = PasswordDialog(parent_window)
846 password = password_dialog.run()
854 if amount < bitcoin(1) / 10:
856 fee = bitcoin(1) / 1000
859 tx = self.wallet.mktx([(dest_address, amount)], password, fee)
860 except BaseException as error:
861 QMessageBox.warning(parent_window, _('Error'), str(error), _('OK'))
865 h = self.wallet.send_tx(tx)
867 self.waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Sending transaction, please wait..."))
869 status, message = self.wallet.receive_tx(h)
873 dumpf = tempfile.NamedTemporaryFile(delete=False)
876 print "Dumped error tx to", dumpf.name
877 QMessageBox.warning(parent_window, _('Error'), message, _('OK'))
880 TransactionWindow(message, self)
882 filename = 'unsigned_tx_%s' % (time.mktime(time.gmtime()))
884 fileName = QFileDialog.getSaveFileName(QWidget(), _("Select a transaction filename"), os.path.expanduser('~/%s' % (filename)))
885 with open(fileName,'w') as f:
886 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
887 QMessageBox.information(QWidget(), _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
888 except BaseException as e:
889 QMessageBox.warning(QWidget(), _('Error'), _('Could not write transaction to file: %s' % e), _('OK'))
892 def fetch_destination(self, address):
893 recipient = unicode(address).strip()
896 match1 = re.match("^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$",
899 # label or alias, with address in brackets
900 match2 = re.match("(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>",
905 self.wallet.get_alias(recipient, True,
906 self.show_message, self.question)
909 return match2.group(2)
914 def copy_master_public_key(self):
915 master_pubkey = self.wallet.get_master_public_key()
916 qApp.clipboard().setText(master_pubkey)
917 QMessageBox.information(None, _("Copy successful"), _("Your master public key has been copied to your clipboard."))
920 def acceptbit(self, currency):
921 master_pubkey = self.wallet.master_public_key
922 url = "http://acceptbit.com/mpk/%s/%s" % (master_pubkey, currency)
925 def show_seed_dialog(self):
926 gui_classic.ElectrumWindow.show_seed_dialog(self.wallet)
928 class MiniDriver(QObject):
935 def __init__(self, wallet, window):
936 super(QObject, self).__init__()
941 self.wallet.interface.register_callback('updated',self.update_callback)
942 self.wallet.interface.register_callback('connected', self.update_callback)
943 self.wallet.interface.register_callback('disconnected', self.update_callback)
948 self.connect(self, SIGNAL("updatesignal()"), self.update)
949 self.update_callback()
951 # This is a hack to workaround that Qt does not like changing the
952 # window properties from this other thread before the runloop has
954 def update_callback(self):
955 self.emit(SIGNAL("updatesignal()"))
958 if not self.wallet.interface:
960 elif not self.wallet.interface.is_connected:
962 elif not self.wallet.up_to_date:
967 if self.wallet.up_to_date:
968 self.update_balance()
969 self.update_completions()
970 self.update_history()
972 def initializing(self):
973 if self.state == self.INITIALIZING:
975 self.state = self.INITIALIZING
976 self.window.deactivate()
978 def connecting(self):
979 if self.state == self.CONNECTING:
981 self.state = self.CONNECTING
982 self.window.deactivate()
984 def synchronizing(self):
985 if self.state == self.SYNCHRONIZING:
987 self.state = self.SYNCHRONIZING
988 self.window.deactivate()
991 if self.state == self.READY:
993 self.state = self.READY
994 self.window.activate()
996 def update_balance(self):
997 conf_balance, unconf_balance = self.wallet.get_balance()
998 balance = D(conf_balance + unconf_balance)
999 self.window.set_balances(balance)
1001 def update_completions(self):
1003 for addr, label in self.wallet.labels.items():
1004 if addr in self.wallet.addressbook:
1005 completions.append("%s <%s>" % (label, addr))
1006 self.window.update_completions(completions)
1008 def update_history(self):
1009 tx_history = self.wallet.get_tx_history()
1010 self.window.update_history(tx_history)
1013 if __name__ == "__main__":
1014 app = QApplication(sys.argv)
1015 with open(rsrc("style.css")) as style_file:
1016 app.setStyleSheet(style_file.read())
1018 sys.exit(app.exec_())