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
26 import receiving_widget
31 from version import ELECTRUM_VERSION as electrum_version
32 from wallet import format_satoshis
36 bitcoin = lambda v: v * 100000000
38 def IconButton(filename, parent=None):
39 pixmap = QPixmap(filename)
41 return QPushButton(icon, "", parent)
46 self.emit(SIGNAL('timersignal'))
49 def resize_line_edit_width(line_edit, text_input):
50 metrics = QFontMetrics(qApp.font())
51 # Create an extra character to add some space on the end
53 line_edit.setMinimumWidth(metrics.width(text_input))
55 def load_theme_name(theme_path):
57 with open(os.path.join(theme_path, "name.cfg")) as name_cfg_file:
58 return name_cfg_file.read().rstrip("\n").strip()
63 def theme_dirs_from_prefix(prefix):
64 if not os.path.exists(prefix):
67 for potential_theme in os.listdir(prefix):
68 theme_full_path = os.path.join(prefix, potential_theme)
69 theme_css = os.path.join(theme_full_path, "style.css")
70 if not os.path.exists(theme_css):
72 theme_name = load_theme_name(theme_full_path)
73 if theme_name is None:
75 theme_paths[theme_name] = prefix, potential_theme
78 def load_theme_paths():
80 prefixes = (util.local_data_dir(), util.appdata_dir())
81 for prefix in prefixes:
82 theme_paths.update(theme_dirs_from_prefix(prefix))
86 def csv_transaction(wallet):
88 select_export = _('Select file to export your wallet transactions to')
89 fileName = QFileDialog.getSaveFileName(QWidget(), select_export, os.path.expanduser('~/electrum-history.csv'), "*.csv")
91 with open(fileName, "w+") as csvfile:
92 transaction = csv.writer(csvfile)
93 transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
94 for item in wallet.get_tx_history():
95 tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
97 if timestamp is not None:
99 time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
100 except [RuntimeError, TypeError, NameError] as reason:
101 time_string = "unknown"
104 time_string = "unknown"
106 time_string = "pending"
108 if value is not None:
109 value_string = format_satoshis(value, True, wallet.num_zeros)
114 fee_string = format_satoshis(fee, True, wallet.num_zeros)
119 label, is_default_label = wallet.get_label(tx_hash)
123 balance_string = format_satoshis(balance, False, wallet.num_zeros)
124 transaction.writerow([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
125 QMessageBox.information(None,"CSV Export created", "Your CSV export has been successfully created.")
126 except (IOError, os.error), reason:
127 export_error_label = _("Electrum was unable to produce a transaction export.")
128 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
131 class ElectrumGui(QObject):
133 def __init__(self, wallet, config, expert=None):
134 super(QObject, self).__init__()
138 self.check_qt_version()
140 if self.expert != None:
141 self.app = self.expert.app
143 self.app = QApplication(sys.argv)
145 def check_qt_version(self):
146 qtVersion = qVersion()
147 if not(int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
148 app = QApplication(sys.argv)
149 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")
150 self.config.set_key('gui','classic',True)
155 actuator = MiniActuator(self.wallet)
156 # Should probably not modify the current path but instead
157 # change the behaviour of rsrc(...)
158 old_path = QDir.currentPath()
159 actuator.load_theme()
161 self.mini = MiniWindow(actuator, self.expand, self.config)
162 driver = MiniDriver(self.wallet, self.mini)
164 # Reset path back to original value now that loading the GUI
166 QDir.setCurrent(old_path)
171 if self.expert == None:
174 self.expert = gui_qt.ElectrumWindow(self.wallet, self.config)
175 self.expert.app = self.app
176 self.expert.connect_slots(timer)
177 self.expert.update_wallet()
181 """Hide the lite mode window and show pro-mode."""
182 self.config.set_key('gui', 'classic', True)
186 def set_url(self, url):
187 payto, amount, label, message, signature, identity, url = \
188 self.wallet.parse_url(url, self.show_message, self.show_question)
189 self.mini.set_payment_fields(payto, amount)
191 def show_message(self, message):
192 QMessageBox.information(self.mini, _("Message"), message, _("OK"))
194 def show_question(self, message):
195 choice = QMessageBox.question(self.mini, _("Message"), message,
196 QMessageBox.Yes|QMessageBox.No,
198 return choice == QMessageBox.Yes
200 def restore_or_create(self):
201 qt_gui_object = gui_qt.ElectrumGui(self.wallet, self.app)
202 return qt_gui_object.restore_or_create()
204 class TransactionWindow(QDialog):
207 label = unicode(self.label_edit.text())
208 self.parent.wallet.labels[self.tx_id] = label
210 super(TransactionWindow, self).accept()
212 def __init__(self, transaction_id, parent):
213 super(TransactionWindow, self).__init__()
215 self.tx_id = str(transaction_id)
220 self.setWindowTitle(_("Transaction successfully sent"))
222 self.layout = QGridLayout(self)
223 history_label = "%s\n%s" % (_("Your transaction has been sent."), _("Please enter a label for this transaction for future reference."))
224 self.layout.addWidget(QLabel(history_label))
226 self.label_edit = QLineEdit()
227 self.label_edit.setPlaceholderText(_("Transaction label"))
228 self.label_edit.setObjectName("label_input")
229 self.label_edit.setAttribute(Qt.WA_MacShowFocusRect, 0)
230 self.label_edit.setFocusPolicy(Qt.ClickFocus)
231 self.layout.addWidget(self.label_edit)
233 self.save_button = QPushButton(_("Save"))
234 self.layout.addWidget(self.save_button)
235 self.save_button.clicked.connect(self.set_label)
239 class MiniWindow(QDialog):
241 def __init__(self, actuator, expand_callback, config):
242 super(MiniWindow, self).__init__()
243 tx = "e08115d0f7819aee65b9d24f81ef9d46eb62bb67ddef5318156cbc3ceb7b703e"
245 self.actuator = actuator
247 self.btc_balance = None
248 self.quote_currencies = ["BRL", "CNY", "EUR", "GBP", "RUB", "USD"]
249 self.actuator.set_configured_currency(self.set_quote_currency)
250 self.exchanger = exchange_rate.Exchanger(self)
251 # Needed because price discovery is done in a different thread
252 # which needs to be sent back to this main one to update the GUI
253 self.connect(self, SIGNAL("refresh_balance()"), self.refresh_balance)
255 self.balance_label = BalanceLabel(self.change_quote_currency)
256 self.balance_label.setObjectName("balance_label")
259 # Bitcoin address code
260 self.address_input = QLineEdit()
261 self.address_input.setPlaceholderText(_("Enter a Bitcoin address or contact"))
262 self.address_input.setObjectName("address_input")
264 self.address_input.setFocusPolicy(Qt.ClickFocus)
266 self.address_input.textChanged.connect(self.address_field_changed)
267 resize_line_edit_width(self.address_input,
268 "1BtaFUr3qVvAmwrsuDuu5zk6e4s2rxd2Gy")
270 self.address_completions = QStringListModel()
271 address_completer = QCompleter(self.address_input)
272 address_completer.setCaseSensitivity(False)
273 address_completer.setModel(self.address_completions)
274 self.address_input.setCompleter(address_completer)
276 address_layout = QHBoxLayout()
277 address_layout.addWidget(self.address_input)
279 self.amount_input = QLineEdit()
280 self.amount_input.setPlaceholderText(_("... and amount"))
281 self.amount_input.setObjectName("amount_input")
283 self.amount_input.setFocusPolicy(Qt.ClickFocus)
284 # This is changed according to the user's displayed balance
285 self.amount_validator = QDoubleValidator(self.amount_input)
286 self.amount_validator.setNotation(QDoubleValidator.StandardNotation)
287 self.amount_validator.setDecimals(8)
288 self.amount_input.setValidator(self.amount_validator)
290 # This removes the very ugly OSX highlighting, please leave this in :D
291 self.address_input.setAttribute(Qt.WA_MacShowFocusRect, 0)
292 self.amount_input.setAttribute(Qt.WA_MacShowFocusRect, 0)
293 self.amount_input.textChanged.connect(self.amount_input_changed)
295 self.send_button = QPushButton(_("&Send"))
296 self.send_button.setObjectName("send_button")
297 self.send_button.setDisabled(True);
298 self.send_button.clicked.connect(self.send)
300 # Creating the receive button
301 self.switch_button = QPushButton( QIcon(":icons/switchgui.png"),'' )
302 self.switch_button.setMaximumWidth(25)
303 self.switch_button.setFlat(True)
304 self.switch_button.clicked.connect(expand_callback)
306 main_layout = QGridLayout(self)
308 main_layout.addWidget(self.balance_label, 0, 0, 1, 3)
309 main_layout.addWidget(self.switch_button, 0, 3)
311 main_layout.addWidget(self.address_input, 1, 0, 1, 4)
312 main_layout.addWidget(self.amount_input, 2, 0, 1, 2)
313 main_layout.addWidget(self.send_button, 2, 2, 1, 2)
315 self.send_button.setMaximumWidth(125)
317 self.history_list = history_widget.HistoryWidget()
318 self.history_list.setObjectName("history")
319 self.history_list.hide()
320 self.history_list.setAlternatingRowColors(True)
322 main_layout.addWidget(self.history_list, 3, 0, 1, 4)
324 self.receiving = receiving_widget.ReceivingWidget(self)
325 self.receiving.setObjectName("receiving")
327 # Add to the right side
328 self.receiving_box = QGroupBox(_("Select a receiving address"))
329 extra_layout = QGridLayout()
331 # Checkbox to filter used addresses
332 hide_used = QCheckBox(_('Hide used addresses'))
333 hide_used.setChecked(True)
334 hide_used.stateChanged.connect(self.receiving.toggle_used)
336 # Events for receiving addresses
337 self.receiving.clicked.connect(self.receiving.copy_address)
338 self.receiving.itemDoubleClicked.connect(self.receiving.edit_label)
339 self.receiving.itemChanged.connect(self.receiving.update_label)
343 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)
345 extra_layout.addWidget(self.receiving, 1,0)
346 extra_layout.addWidget(hide_used, 2,0)
347 extra_layout.setColumnMinimumWidth(0,200)
349 self.receiving_box.setLayout(extra_layout)
350 main_layout.addWidget(self.receiving_box,0,4,-1,3)
351 self.receiving_box.hide()
353 # Creating the menu bar
355 electrum_menu = menubar.addMenu(_("&Electrum"))
357 quit_option = electrum_menu.addAction(_("&Close"))
359 quit_option.triggered.connect(self.close)
361 view_menu = menubar.addMenu(_("&View"))
362 extra_menu = menubar.addMenu(_("&Extra"))
364 backup_wallet = extra_menu.addAction( _("&Create wallet backup"))
365 backup_wallet.triggered.connect(self.backup_wallet)
367 export_csv = extra_menu.addAction( _("&Export transactions to CSV") )
368 export_csv.triggered.connect(lambda: csv_transaction(self.actuator.wallet))
370 master_key = extra_menu.addAction( _("Copy master public key to clipboard") )
371 master_key.triggered.connect(self.actuator.copy_master_public_key)
373 expert_gui = view_menu.addAction(_("&Classic GUI"))
374 expert_gui.triggered.connect(expand_callback)
375 themes_menu = view_menu.addMenu(_("&Themes"))
376 selected_theme = self.actuator.selected_theme()
377 theme_group = QActionGroup(self)
378 for theme_name in self.actuator.theme_names():
379 theme_action = themes_menu.addAction(theme_name)
380 theme_action.setCheckable(True)
381 if selected_theme == theme_name:
382 theme_action.setChecked(True)
383 class SelectThemeFunctor:
384 def __init__(self, theme_name, toggle_theme):
385 self.theme_name = theme_name
386 self.toggle_theme = toggle_theme
387 def __call__(self, checked):
389 self.toggle_theme(self.theme_name)
390 delegate = SelectThemeFunctor(theme_name, self.toggle_theme)
391 theme_action.toggled.connect(delegate)
392 theme_group.addAction(theme_action)
393 view_menu.addSeparator()
395 show_receiving = view_menu.addAction(_("Show Receiving addresses"))
396 show_receiving.setCheckable(True)
397 show_receiving.toggled.connect(self.toggle_receiving_layout)
399 show_receiving_toggle = self.config.get("gui_show_receiving",False)
400 show_receiving.setChecked(show_receiving_toggle)
401 self.show_receiving = show_receiving
403 self.toggle_receiving_layout(show_receiving_toggle)
406 show_history = view_menu.addAction(_("Show History"))
407 show_history.setCheckable(True)
408 show_history.toggled.connect(self.show_history)
410 help_menu = menubar.addMenu(_("&Help"))
411 the_website = help_menu.addAction(_("&Website"))
412 the_website.triggered.connect(self.the_website)
413 help_menu.addSeparator()
414 report_bug = help_menu.addAction(_("&Report Bug"))
415 report_bug.triggered.connect(self.show_report_bug)
416 show_about = help_menu.addAction(_("&About"))
417 show_about.triggered.connect(self.show_about)
418 main_layout.setMenuBar(menubar)
419 self.main_layout = main_layout
421 quit_shortcut = QShortcut(QKeySequence("Ctrl+Q"), self)
422 quit_shortcut.activated.connect(self.close)
423 close_shortcut = QShortcut(QKeySequence("Ctrl+W"), self)
424 close_shortcut.activated.connect(self.close)
426 g = self.config.get("winpos-lite",[4, 25, 351, 149])
427 self.setGeometry(g[0], g[1], g[2], g[3])
429 show_hist = self.config.get("gui_show_history",False)
430 show_history.setChecked(show_hist)
431 self.show_history(show_hist)
433 self.setWindowIcon(QIcon(":electrum.png"))
434 self.setWindowTitle("Electrum")
435 self.setWindowFlags(Qt.Window|Qt.MSWindowsFixedSizeDialogHint)
436 self.layout().setSizeConstraint(QLayout.SetFixedSize)
437 self.setObjectName("main_window")
441 def toggle_theme(self, theme_name):
442 old_path = QDir.currentPath()
443 self.actuator.change_theme(theme_name)
444 # Recompute style globally
445 qApp.style().unpolish(self)
446 qApp.style().polish(self)
447 QDir.setCurrent(old_path)
449 def closeEvent(self, event):
451 self.config.set_key("winpos-lite", [g.left(),g.top(),g.width(),g.height()],True)
452 self.config.set_key("gui_show_history", self.history_list.isVisible(),True)
453 self.config.set_key("gui_show_receiving", self.receiving_box.isVisible(),True)
455 super(MiniWindow, self).closeEvent(event)
458 def set_payment_fields(self, dest_address, amount):
459 self.address_input.setText(dest_address)
460 self.address_field_changed(dest_address)
461 self.amount_input.setText(amount)
466 def deactivate(self):
469 def set_quote_currency(self, currency):
470 """Set and display the fiat currency country."""
471 assert currency in self.quote_currencies
472 self.quote_currencies.remove(currency)
473 self.quote_currencies.insert(0, currency)
474 self.refresh_balance()
476 def change_quote_currency(self):
477 self.quote_currencies = \
478 self.quote_currencies[1:] + self.quote_currencies[0:1]
479 self.actuator.set_config_currency(self.quote_currencies[0])
480 self.refresh_balance()
482 def refresh_balance(self):
483 if self.btc_balance is None:
484 # Price has been discovered before wallet has been loaded
485 # and server connect... so bail.
487 self.set_balances(self.btc_balance)
488 self.amount_input_changed(self.amount_input.text())
490 def set_balances(self, btc_balance):
491 """Set the bitcoin balance and update the amount label accordingly."""
492 self.btc_balance = btc_balance
493 quote_text = self.create_quote_text(btc_balance)
495 quote_text = "(%s)" % quote_text
496 btc_balance = "%.2f" % (btc_balance / bitcoin(1))
497 self.balance_label.set_balance_text(btc_balance, quote_text)
498 self.setWindowTitle("Electrum %s - %s BTC" % (electrum_version, btc_balance))
500 def amount_input_changed(self, amount_text):
501 """Update the number of bitcoins displayed."""
502 self.check_button_status()
505 amount = D(str(amount_text))
506 except decimal.InvalidOperation:
507 self.balance_label.show_balance()
509 quote_text = self.create_quote_text(amount * bitcoin(1))
511 self.balance_label.set_amount_text(quote_text)
512 self.balance_label.show_amount()
514 self.balance_label.show_balance()
516 def create_quote_text(self, btc_balance):
517 """Return a string copy of the amount fiat currency the
518 user has in bitcoins."""
519 quote_currency = self.quote_currencies[0]
520 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
521 if quote_balance is None:
524 quote_text = "%.2f %s" % ((quote_balance / bitcoin(1)),
529 if self.actuator.send(self.address_input.text(),
530 self.amount_input.text(), self):
531 self.address_input.setText("")
532 self.amount_input.setText("")
534 def check_button_status(self):
535 """Check that the bitcoin address is valid and that something
536 is entered in the amount before making the send button clickable."""
538 value = D(str(self.amount_input.text())) * 10**8
539 except decimal.InvalidOperation:
541 # self.address_input.property(...) returns a qVariant, not a bool.
542 # The == is needed to properly invoke a comparison.
543 if (self.address_input.property("isValid") == True and
544 value is not None and 0 < value <= self.btc_balance):
545 self.send_button.setDisabled(False)
547 self.send_button.setDisabled(True)
549 def address_field_changed(self, address):
550 # label or alias, with address in brackets
551 match2 = re.match("(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>",
554 address = match2.group(2)
555 self.address_input.setText(address)
557 if self.actuator.is_valid(address):
558 self.check_button_status()
559 self.address_input.setProperty("isValid", True)
560 self.recompute_style(self.address_input)
562 self.send_button.setDisabled(True)
563 self.address_input.setProperty("isValid", False)
564 self.recompute_style(self.address_input)
566 if len(address) == 0:
567 self.address_input.setProperty("isValid", None)
568 self.recompute_style(self.address_input)
570 def recompute_style(self, element):
571 self.style().unpolish(element)
572 self.style().polish(element)
574 def copy_address(self):
575 receive_popup = ReceivePopup(self.receive_button)
576 self.actuator.copy_address(receive_popup)
578 def update_completions(self, completions):
579 self.address_completions.setStringList(completions)
582 def update_history(self, tx_history):
583 from util import format_satoshis, age
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.all_addresses()
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 self.wallet.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)
900 def is_valid(self, address):
901 """Check if bitcoin address is valid."""
903 return self.wallet.is_valid(address)
905 def copy_master_public_key(self):
906 master_pubkey = self.wallet.master_public_key
907 qApp.clipboard().setText(master_pubkey)
908 QMessageBox.information(None,"Copy successful", "Your master public key has been copied to your clipboard.")
911 def acceptbit(self, currency):
912 master_pubkey = self.wallet.master_public_key
913 url = "http://acceptbit.com/mpk/%s/%s" % (master_pubkey, currency)
916 def show_seed_dialog(self):
917 gui_qt.ElectrumWindow.show_seed_dialog(self.wallet)
919 class MiniDriver(QObject):
926 def __init__(self, wallet, window):
927 super(QObject, self).__init__()
932 self.wallet.interface.register_callback('updated',self.update_callback)
933 self.wallet.interface.register_callback('connected', self.update_callback)
934 self.wallet.interface.register_callback('disconnected', self.update_callback)
939 self.connect(self, SIGNAL("updatesignal()"), self.update)
940 self.update_callback()
942 # This is a hack to workaround that Qt does not like changing the
943 # window properties from this other thread before the runloop has
945 def update_callback(self):
946 self.emit(SIGNAL("updatesignal()"))
949 if not self.wallet.interface:
951 elif not self.wallet.interface.is_connected:
953 elif not self.wallet.up_to_date:
958 if self.wallet.up_to_date:
959 self.update_balance()
960 self.update_completions()
961 self.update_history()
963 def initializing(self):
964 if self.state == self.INITIALIZING:
966 self.state = self.INITIALIZING
967 self.window.deactivate()
969 def connecting(self):
970 if self.state == self.CONNECTING:
972 self.state = self.CONNECTING
973 self.window.deactivate()
975 def synchronizing(self):
976 if self.state == self.SYNCHRONIZING:
978 self.state = self.SYNCHRONIZING
979 self.window.deactivate()
982 if self.state == self.READY:
984 self.state = self.READY
985 self.window.activate()
987 def update_balance(self):
988 conf_balance, unconf_balance = self.wallet.get_balance()
989 balance = D(conf_balance + unconf_balance)
990 self.window.set_balances(balance)
992 def update_completions(self):
994 for addr, label in self.wallet.labels.items():
995 if addr in self.wallet.addressbook:
996 completions.append("%s <%s>" % (label, addr))
997 completions = completions + self.wallet.aliases.keys()
998 self.window.update_completions(completions)
1000 def update_history(self):
1001 tx_history = self.wallet.get_tx_history()
1002 self.window.update_history(tx_history)
1005 if __name__ == "__main__":
1006 app = QApplication(sys.argv)
1007 with open(rsrc("style.css")) as style_file:
1008 app.setStyleSheet(style_file.read())
1010 sys.exit(app.exec_())