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'."
17 from decimal import Decimal as D
18 from util import get_resource_path as rsrc
29 import receiving_widget
34 from version import ELECTRUM_VERSION as electrum_version
35 from wallet import format_satoshis
39 bitcoin = lambda v: v * 100000000
41 def IconButton(filename, parent=None):
42 pixmap = QPixmap(filename)
44 return QPushButton(icon, "", parent)
49 self.emit(SIGNAL('timersignal'))
52 def resize_line_edit_width(line_edit, text_input):
53 metrics = QFontMetrics(qApp.font())
54 # Create an extra character to add some space on the end
56 line_edit.setMinimumWidth(metrics.width(text_input))
58 def load_theme_name(theme_path):
60 with open(os.path.join(theme_path, "name.cfg")) as name_cfg_file:
61 return name_cfg_file.read().rstrip("\n").strip()
66 def theme_dirs_from_prefix(prefix):
67 if not os.path.exists(prefix):
70 for potential_theme in os.listdir(prefix):
71 theme_full_path = os.path.join(prefix, potential_theme)
72 theme_css = os.path.join(theme_full_path, "style.css")
73 if not os.path.exists(theme_css):
75 theme_name = load_theme_name(theme_full_path)
76 if theme_name is None:
78 theme_paths[theme_name] = prefix, potential_theme
81 def load_theme_paths():
83 prefixes = (util.local_data_dir(), util.appdata_dir())
84 for prefix in prefixes:
85 theme_paths.update(theme_dirs_from_prefix(prefix))
89 def csv_transaction(wallet):
91 fileName = QFileDialog.getSaveFileName(QWidget(), 'Select file to export your wallet transactions to', os.path.expanduser('~/'), "*.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 succesfully created.")
128 except (IOError, os.error), reason:
129 QMessageBox.critical(None,"Unable to create csv", "Electrum was unable to produce a transaction export.\n" + str(reason))
132 class ElectrumGui(QObject):
134 def __init__(self, wallet, config):
135 super(QObject, self).__init__()
139 self.check_qt_version()
140 self.app = QApplication(sys.argv)
143 def check_qt_version(self):
144 qtVersion = qVersion()
145 if not(int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
146 app = QApplication(sys.argv)
147 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")
148 self.config.set_key('gui','classic',True)
153 actuator = MiniActuator(self.wallet)
154 # Should probably not modify the current path but instead
155 # change the behaviour of rsrc(...)
156 old_path = QDir.currentPath()
157 actuator.load_theme()
159 self.mini = MiniWindow(actuator, self.expand, self.config)
160 driver = MiniDriver(self.wallet, self.mini)
162 # Reset path back to original value now that loading the GUI
164 QDir.setCurrent(old_path)
171 self.expert = gui_qt.ElectrumWindow(self.wallet, self.config)
172 self.expert.app = self.app
173 self.expert.connect_slots(timer)
174 self.expert.update_wallet()
178 """Hide the lite mode window and show pro-mode."""
182 def set_url(self, url):
183 payto, amount, label, message, signature, identity, url = \
184 self.wallet.parse_url(url, self.show_message, self.show_question)
185 self.mini.set_payment_fields(payto, amount)
187 def show_message(self, message):
188 QMessageBox.information(self.mini, _("Message"), message, _("OK"))
190 def show_question(self, message):
191 choice = QMessageBox.question(self.mini, _("Message"), message,
192 QMessageBox.Yes|QMessageBox.No,
194 return choice == QMessageBox.Yes
196 def restore_or_create(self):
197 qt_gui_object = gui_qt.ElectrumGui(self.wallet, self.app)
198 return qt_gui_object.restore_or_create()
200 class TransactionWindow(QDialog):
203 label = unicode(self.label_edit.text())
204 self.parent.wallet.labels[self.tx_id] = label
206 super(TransactionWindow, self).accept()
208 def __init__(self, transaction_id, parent):
209 super(TransactionWindow, self).__init__()
211 self.tx_id = str(transaction_id)
216 self.setWindowTitle("Transaction successfully sent")
218 self.layout = QGridLayout(self)
219 self.layout.addWidget(QLabel("Your transaction has been sent.\nPlease enter a label for this transaction for future reference."))
221 self.label_edit = QLineEdit()
222 self.label_edit.setPlaceholderText(_("Transaction label"))
223 self.label_edit.setObjectName("label_input")
224 self.label_edit.setAttribute(Qt.WA_MacShowFocusRect, 0)
225 self.label_edit.setFocusPolicy(Qt.ClickFocus)
226 self.layout.addWidget(self.label_edit)
228 self.save_button = QPushButton(_("Save"))
229 self.layout.addWidget(self.save_button)
230 self.save_button.clicked.connect(self.set_label)
234 class MiniWindow(QDialog):
236 def __init__(self, actuator, expand_callback, config):
237 super(MiniWindow, self).__init__()
238 tx = "e08115d0f7819aee65b9d24f81ef9d46eb62bb67ddef5318156cbc3ceb7b703e"
240 self.actuator = actuator
242 self.btc_balance = None
243 self.quote_currencies = ["BRL", "CNY", "EUR", "GBP", "RUB", "USD"]
244 self.actuator.set_configured_currency(self.set_quote_currency)
245 self.exchanger = exchange_rate.Exchanger(self)
246 # Needed because price discovery is done in a different thread
247 # which needs to be sent back to this main one to update the GUI
248 self.connect(self, SIGNAL("refresh_balance()"), self.refresh_balance)
250 self.balance_label = BalanceLabel(self.change_quote_currency)
251 self.balance_label.setObjectName("balance_label")
254 # Bitcoin address code
255 self.address_input = QLineEdit()
256 self.address_input.setPlaceholderText(_("Enter a Bitcoin address or contact"))
257 self.address_input.setObjectName("address_input")
259 self.address_input.setFocusPolicy(Qt.ClickFocus)
261 self.address_input.textChanged.connect(self.address_field_changed)
262 resize_line_edit_width(self.address_input,
263 "1BtaFUr3qVvAmwrsuDuu5zk6e4s2rxd2Gy")
265 self.address_completions = QStringListModel()
266 address_completer = QCompleter(self.address_input)
267 address_completer.setCaseSensitivity(False)
268 address_completer.setModel(self.address_completions)
269 self.address_input.setCompleter(address_completer)
271 address_layout = QHBoxLayout()
272 address_layout.addWidget(self.address_input)
274 self.amount_input = QLineEdit()
275 self.amount_input.setPlaceholderText(_("... and amount"))
276 self.amount_input.setObjectName("amount_input")
278 self.amount_input.setFocusPolicy(Qt.ClickFocus)
279 # This is changed according to the user's displayed balance
280 self.amount_validator = QDoubleValidator(self.amount_input)
281 self.amount_validator.setNotation(QDoubleValidator.StandardNotation)
282 self.amount_validator.setDecimals(8)
283 self.amount_input.setValidator(self.amount_validator)
285 # This removes the very ugly OSX highlighting, please leave this in :D
286 self.address_input.setAttribute(Qt.WA_MacShowFocusRect, 0)
287 self.amount_input.setAttribute(Qt.WA_MacShowFocusRect, 0)
288 self.amount_input.textChanged.connect(self.amount_input_changed)
290 self.send_button = QPushButton(_("&Send"))
291 self.send_button.setObjectName("send_button")
292 self.send_button.setDisabled(True);
293 self.send_button.clicked.connect(self.send)
295 # Creating the receive button
296 self.receive_button = QPushButton(_("&Receive"))
297 self.receive_button.setObjectName("receive_button")
298 self.receive_button.setDefault(True)
300 main_layout = QGridLayout(self)
302 main_layout.addWidget(self.balance_label, 0, 0)
303 main_layout.addWidget(self.receive_button, 0, 1)
305 main_layout.addWidget(self.address_input, 1, 0)
307 main_layout.addWidget(self.amount_input, 2, 0)
308 main_layout.addWidget(self.send_button, 2, 1)
310 self.history_list = history_widget.HistoryWidget()
311 self.history_list.setObjectName("history")
312 self.history_list.hide()
313 self.history_list.setAlternatingRowColors(True)
315 main_layout.addWidget(self.history_list, 3, 0, 1, 2)
318 self.receiving = receiving_widget.ReceivingWidget(self)
319 self.receiving.setObjectName("receiving")
321 # Add to the right side
322 self.receiving_box = QGroupBox(_("Select a receiving address"))
323 extra_layout = QGridLayout()
325 # Checkbox to filter used addresses
326 hide_used = QCheckBox(_('Hide used addresses'))
327 hide_used.setChecked(True)
328 hide_used.stateChanged.connect(self.receiving.toggle_used)
330 # Events for receiving addresses
331 self.receiving.clicked.connect(self.receiving.copy_address)
332 self.receiving.itemDoubleClicked.connect(self.receiving.edit_label)
333 self.receiving.itemChanged.connect(self.receiving.update_label)
336 extra_layout.addWidget( QLabel(_('Selecting an address will copy it to the clipboard.\nDouble clicking the label will allow you to edit it.') ),0,0)
338 extra_layout.addWidget(self.receiving, 1,0)
339 extra_layout.addWidget(hide_used, 2,0)
340 extra_layout.setColumnMinimumWidth(0,200)
342 self.receiving_box.setLayout(extra_layout)
343 main_layout.addWidget(self.receiving_box,0,3,-1,3)
344 self.receiving_box.hide()
346 self.receive_button.clicked.connect(self.toggle_receiving_layout)
348 # Creating the menu bar
350 electrum_menu = menubar.addMenu(_("&Bitcoin"))
352 electrum_menu.addSeparator()
354 quit_option = electrum_menu.addAction(_("&Quit"))
355 quit_option.triggered.connect(self.close)
357 view_menu = menubar.addMenu(_("&View"))
358 extra_menu = menubar.addMenu(_("&Extra"))
360 backup_wallet = extra_menu.addAction( _("&Create wallet backup"))
361 backup_wallet.triggered.connect(self.backup_wallet)
363 export_csv = extra_menu.addAction( _("&Export transactions to CSV") )
364 export_csv.triggered.connect(lambda: csv_transaction(self.wallet))
366 master_key = extra_menu.addAction( _("Copy master public key to clipboard") )
367 master_key.triggered.connect(self.actuator.copy_master_public_key)
369 expert_gui = view_menu.addAction(_("&Classic GUI"))
370 expert_gui.triggered.connect(expand_callback)
371 themes_menu = view_menu.addMenu(_("&Themes"))
372 selected_theme = self.actuator.selected_theme()
373 theme_group = QActionGroup(self)
374 for theme_name in self.actuator.theme_names():
375 theme_action = themes_menu.addAction(theme_name)
376 theme_action.setCheckable(True)
377 if selected_theme == theme_name:
378 theme_action.setChecked(True)
379 class SelectThemeFunctor:
380 def __init__(self, theme_name, toggle_theme):
381 self.theme_name = theme_name
382 self.toggle_theme = toggle_theme
383 def __call__(self, checked):
385 self.toggle_theme(self.theme_name)
386 delegate = SelectThemeFunctor(theme_name, self.toggle_theme)
387 theme_action.toggled.connect(delegate)
388 theme_group.addAction(theme_action)
389 view_menu.addSeparator()
390 show_history = view_menu.addAction(_("Show History"))
391 show_history.setCheckable(True)
392 show_history.toggled.connect(self.show_history)
394 help_menu = menubar.addMenu(_("&Help"))
395 the_website = help_menu.addAction(_("&Website"))
396 the_website.triggered.connect(self.the_website)
397 help_menu.addSeparator()
398 report_bug = help_menu.addAction(_("&Report Bug"))
399 report_bug.triggered.connect(self.show_report_bug)
400 show_about = help_menu.addAction(_("&About"))
401 show_about.triggered.connect(self.show_about)
402 main_layout.setMenuBar(menubar)
403 self.main_layout = main_layout
405 quit_shortcut = QShortcut(QKeySequence("Ctrl+Q"), self)
406 quit_shortcut.activated.connect(self.close)
407 close_shortcut = QShortcut(QKeySequence("Ctrl+W"), self)
408 close_shortcut.activated.connect(self.close)
410 g = self.config.get("winpos-lite",[4, 25, 351, 149])
411 self.setGeometry(g[0], g[1], g[2], g[3])
413 show_hist = self.config.get("gui_show_history",False)
414 show_history.setChecked(show_hist)
415 self.show_history(show_hist)
417 self.setWindowIcon(QIcon(":electrum.png"))
418 self.setWindowTitle("Electrum")
419 self.setWindowFlags(Qt.Window|Qt.MSWindowsFixedSizeDialogHint)
420 self.layout().setSizeConstraint(QLayout.SetFixedSize)
421 self.setObjectName("main_window")
424 def toggle_receiving_layout(self):
425 if self.receiving_box.isVisible():
426 self.receiving_box.hide()
427 self.receive_button.setProperty("isActive", False)
429 qApp.style().unpolish(self.receive_button)
430 qApp.style().polish(self.receive_button)
432 self.receiving_box.show()
433 self.receive_button.setProperty("isActive", 'true')
435 qApp.style().unpolish(self.receive_button)
436 qApp.style().polish(self.receive_button)
438 def toggle_theme(self, theme_name):
439 old_path = QDir.currentPath()
440 self.actuator.change_theme(theme_name)
441 # Recompute style globally
442 qApp.style().unpolish(self)
443 qApp.style().polish(self)
444 QDir.setCurrent(old_path)
446 def closeEvent(self, event):
448 self.config.set_key("winpos-lite", [g.left(),g.top(),g.width(),g.height()],True)
449 self.config.set_key("gui_show_history", self.history_list.isVisible(),True)
451 super(MiniWindow, self).closeEvent(event)
454 def set_payment_fields(self, dest_address, amount):
455 self.address_input.setText(dest_address)
456 self.address_field_changed(dest_address)
457 self.amount_input.setText(amount)
462 def deactivate(self):
465 def set_quote_currency(self, currency):
466 """Set and display the fiat currency country."""
467 assert currency in self.quote_currencies
468 self.quote_currencies.remove(currency)
469 self.quote_currencies.insert(0, currency)
470 self.refresh_balance()
472 def change_quote_currency(self):
473 self.quote_currencies = \
474 self.quote_currencies[1:] + self.quote_currencies[0:1]
475 self.actuator.set_config_currency(self.quote_currencies[0])
476 self.refresh_balance()
478 def refresh_balance(self):
479 if self.btc_balance is None:
480 # Price has been discovered before wallet has been loaded
481 # and server connect... so bail.
483 self.set_balances(self.btc_balance)
484 self.amount_input_changed(self.amount_input.text())
486 def set_balances(self, btc_balance):
487 """Set the bitcoin balance and update the amount label accordingly."""
488 self.btc_balance = btc_balance
489 quote_text = self.create_quote_text(btc_balance)
491 quote_text = "(%s)" % quote_text
492 btc_balance = "%.2f" % (btc_balance / bitcoin(1))
493 self.balance_label.set_balance_text(btc_balance, quote_text)
494 self.setWindowTitle("Electrum %s - %s BTC" % (electrum_version, btc_balance))
496 def amount_input_changed(self, amount_text):
497 """Update the number of bitcoins displayed."""
498 self.check_button_status()
501 amount = D(str(amount_text))
502 except decimal.InvalidOperation:
503 self.balance_label.show_balance()
505 quote_text = self.create_quote_text(amount * bitcoin(1))
507 self.balance_label.set_amount_text(quote_text)
508 self.balance_label.show_amount()
510 self.balance_label.show_balance()
512 def create_quote_text(self, btc_balance):
513 """Return a string copy of the amount fiat currency the
514 user has in bitcoins."""
515 quote_currency = self.quote_currencies[0]
516 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
517 if quote_balance is None:
520 quote_text = "%.2f %s" % ((quote_balance / bitcoin(1)),
525 if self.actuator.send(self.address_input.text(),
526 self.amount_input.text(), self):
527 self.address_input.setText("")
528 self.amount_input.setText("")
530 def check_button_status(self):
531 """Check that the bitcoin address is valid and that something
532 is entered in the amount before making the send button clickable."""
534 value = D(str(self.amount_input.text())) * 10**8
535 except decimal.InvalidOperation:
537 # self.address_input.property(...) returns a qVariant, not a bool.
538 # The == is needed to properly invoke a comparison.
539 if (self.address_input.property("isValid") == True and
540 value is not None and 0 < value <= self.btc_balance):
541 self.send_button.setDisabled(False)
543 self.send_button.setDisabled(True)
545 def address_field_changed(self, address):
546 # label or alias, with address in brackets
547 match2 = re.match("(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>",
550 address = match2.group(2)
551 self.address_input.setText(address)
553 if self.actuator.is_valid(address):
554 self.check_button_status()
555 self.address_input.setProperty("isValid", True)
556 self.recompute_style(self.address_input)
558 self.send_button.setDisabled(True)
559 self.address_input.setProperty("isValid", False)
560 self.recompute_style(self.address_input)
562 if len(address) == 0:
563 self.address_input.setProperty("isValid", None)
564 self.recompute_style(self.address_input)
566 def recompute_style(self, element):
567 self.style().unpolish(element)
568 self.style().polish(element)
570 def copy_address(self):
571 receive_popup = ReceivePopup(self.receive_button)
572 self.actuator.copy_address(receive_popup)
574 def update_completions(self, completions):
575 self.address_completions.setStringList(completions)
578 def update_history(self, tx_history):
579 from util import format_satoshis, age
581 self.history_list.empty()
583 for item in tx_history[-10:]:
584 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
585 label = self.actuator.wallet.get_label(tx_hash)[0]
586 #amount = D(value) / 10**8
587 v_str = format_satoshis(value, True)
588 self.history_list.append(label, v_str, age(timestamp))
591 self.actuator.acceptbit(self.quote_currencies[0])
593 def the_website(self):
594 webbrowser.open("http://electrum-desktop.com")
596 def show_about(self):
597 QMessageBox.about(self, "Electrum",
598 _("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 conjuction with high-performance servers that handle the most complicated parts of the Bitcoin system."))
600 def show_report_bug(self):
601 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
602 _("Please report any bugs as issues on github: <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>"))
604 def show_history(self, toggle_state):
606 self.main_layout.setRowMinimumHeight(3,200)
607 self.history_list.show()
609 self.main_layout.setRowMinimumHeight(3,0)
610 self.history_list.hide()
612 def backup_wallet(self):
614 folderName = QFileDialog.getExistingDirectory(QWidget(), 'Select folder to save a copy of your wallet to', os.path.expanduser('~/'))
616 sourceFile = util.user_dir() + '/electrum.dat'
617 shutil.copy2(sourceFile, str(folderName))
618 QMessageBox.information(None,"Wallet backup created", "A copy of your wallet file was created in '%s'" % str(folderName))
619 except (IOError, os.error), reason:
620 QMessageBox.critical(None,"Unable to create backup", "Electrum was unable copy your wallet file to the specified location.\n" + str(reason))
625 class BalanceLabel(QLabel):
631 def __init__(self, change_quote_currency, parent=None):
632 super(QLabel, self).__init__(_("Connecting..."), parent)
633 self.change_quote_currency = change_quote_currency
634 self.state = self.SHOW_CONNECTING
635 self.balance_text = ""
636 self.amount_text = ""
638 def mousePressEvent(self, event):
639 """Change the fiat currency selection if window background is clicked."""
640 if self.state != self.SHOW_CONNECTING:
641 self.change_quote_currency()
643 def set_balance_text(self, btc_balance, quote_text):
644 """Set the amount of bitcoins in the gui."""
645 if self.state == self.SHOW_CONNECTING:
646 self.state = self.SHOW_BALANCE
647 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)
648 if self.state == self.SHOW_BALANCE:
649 self.setText(self.balance_text)
651 def set_amount_text(self, quote_text):
652 self.amount_text = "<span style='font-size: 10pt'>%s</span>" % quote_text
653 if self.state == self.SHOW_AMOUNT:
654 self.setText(self.amount_text)
656 def show_balance(self):
657 if self.state == self.SHOW_AMOUNT:
658 self.state = self.SHOW_BALANCE
659 self.setText(self.balance_text)
661 def show_amount(self):
662 if self.state == self.SHOW_BALANCE:
663 self.state = self.SHOW_AMOUNT
664 self.setText(self.amount_text)
666 def ok_cancel_buttons(dialog):
667 row_layout = QHBoxLayout()
668 row_layout.addStretch(1)
669 ok_button = QPushButton(_("OK"))
670 row_layout.addWidget(ok_button)
671 ok_button.clicked.connect(dialog.accept)
672 cancel_button = QPushButton(_("Cancel"))
673 row_layout.addWidget(cancel_button)
674 cancel_button.clicked.connect(dialog.reject)
677 class PasswordDialog(QDialog):
679 def __init__(self, parent):
680 super(QDialog, self).__init__(parent)
684 self.password_input = QLineEdit()
685 self.password_input.setEchoMode(QLineEdit.Password)
687 main_layout = QVBoxLayout(self)
688 message = _('Please enter your password')
689 main_layout.addWidget(QLabel(message))
693 grid.addWidget(QLabel(_('Password')), 1, 0)
694 grid.addWidget(self.password_input, 1, 1)
695 main_layout.addLayout(grid)
697 main_layout.addLayout(ok_cancel_buttons(self))
698 self.setLayout(main_layout)
703 return unicode(self.password_input.text())
705 class ReceivePopup(QDialog):
707 def leaveEvent(self, event):
710 def setup(self, address):
711 label = QLabel(_("Copied your Bitcoin address to the clipboard!"))
712 address_display = QLineEdit(address)
713 address_display.setReadOnly(True)
714 resize_line_edit_width(address_display, address)
716 main_layout = QVBoxLayout(self)
717 main_layout.addWidget(label)
718 main_layout.addWidget(address_display)
720 self.setMouseTracking(True)
721 self.setWindowTitle("Electrum - " + _("Receive Bitcoin payment"))
722 self.setWindowFlags(Qt.Window|Qt.FramelessWindowHint|
723 Qt.MSWindowsFixedSizeDialogHint)
724 self.layout().setSizeConstraint(QLayout.SetFixedSize)
725 #self.setFrameStyle(QFrame.WinPanel|QFrame.Raised)
726 #self.setAlignment(Qt.AlignCenter)
729 parent = self.parent()
730 top_left_pos = parent.mapToGlobal(parent.rect().bottomLeft())
731 self.move(top_left_pos)
732 center_mouse_pos = self.mapToGlobal(self.rect().center())
733 QCursor.setPos(center_mouse_pos)
737 """Initialize the definitions relating to themes and
738 sending/recieving bitcoins."""
741 def __init__(self, wallet):
742 """Retrieve the gui theme used in previous session."""
744 self.theme_name = self.wallet.config.get('litegui_theme','Cleanlook')
745 self.themes = load_theme_paths()
747 def load_theme(self):
748 """Load theme retrieved from wallet file."""
750 theme_prefix, theme_path = self.themes[self.theme_name]
752 util.print_error("Theme not found!", self.theme_name)
754 QDir.setCurrent(os.path.join(theme_prefix, theme_path))
755 with open(rsrc("style.css")) as style_file:
756 qApp.setStyleSheet(style_file.read())
758 def theme_names(self):
760 return sorted(self.themes.keys())
762 def selected_theme(self):
764 return self.theme_name
766 def change_theme(self, theme_name):
768 self.theme_name = theme_name
769 self.wallet.config.set_key('litegui_theme',theme_name)
772 def set_configured_currency(self, set_quote_currency):
773 """Set the inital fiat currency conversion country (USD/EUR/GBP) in
774 the GUI to what it was set to in the wallet."""
775 currency = self.wallet.config.get('conversion_currency')
776 # currency can be none when Electrum is used for the first
777 # time and no setting has been created yet.
778 if currency is not None:
779 set_quote_currency(currency)
781 def set_config_currency(self, conversion_currency):
782 """Change the wallet fiat currency country."""
783 self.wallet.config.set_key('conversion_currency',conversion_currency,True)
785 def copy_address(self, receive_popup):
786 """Copy the wallet addresses into the client."""
787 addrs = [addr for addr in self.wallet.all_addresses()
788 if not self.wallet.is_change(addr)]
789 # Select most recent addresses from gap limit
790 addrs = addrs[-self.wallet.gap_limit:]
791 copied_address = random.choice(addrs)
792 qApp.clipboard().setText(copied_address)
793 receive_popup.setup(copied_address)
794 receive_popup.popup()
796 def waiting_dialog(self, f):
801 w.setWindowTitle('Electrum')
802 l = QLabel('Sending transaction, please wait.')
811 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
816 def send(self, address, amount, parent_window):
817 """Send bitcoins to the target address."""
818 dest_address = self.fetch_destination(address)
820 if dest_address is None or not self.wallet.is_valid(dest_address):
821 QMessageBox.warning(parent_window, _('Error'),
822 _('Invalid Bitcoin Address') + ':\n' + address, _('OK'))
825 convert_amount = lambda amount: \
826 int(D(unicode(amount)) * bitcoin(1))
827 amount = convert_amount(amount)
829 if self.wallet.use_encryption:
830 password_dialog = PasswordDialog(parent_window)
831 password = password_dialog.run()
839 if amount < bitcoin(1) / 10:
841 fee = bitcoin(1) / 1000
844 tx = self.wallet.mktx([(dest_address, amount)], "", password, fee)
845 except BaseException as error:
846 QMessageBox.warning(parent_window, _('Error'), str(error), _('OK'))
849 h = self.wallet.send_tx(tx)
851 self.waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Sending transaction, please wait..."))
853 status, message = self.wallet.receive_tx(h)
857 dumpf = tempfile.NamedTemporaryFile(delete=False)
860 print "Dumped error tx to", dumpf.name
861 QMessageBox.warning(parent_window, _('Error'), message, _('OK'))
864 TransactionWindow(message, self)
865 # QMessageBox.information(parent_window, '',
866 # _('Your transaction has been sent.') + '\n' + message, _('OK'))
869 def fetch_destination(self, address):
870 recipient = unicode(address).strip()
873 match1 = re.match("^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$",
876 # label or alias, with address in brackets
877 match2 = re.match("(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>",
882 self.wallet.get_alias(recipient, True,
883 self.show_message, self.question)
886 return match2.group(2)
890 def is_valid(self, address):
891 """Check if bitcoin address is valid."""
893 return self.wallet.is_valid(address)
895 def copy_master_public_key(self):
896 master_pubkey = self.wallet.master_public_key
897 qApp.clipboard().setText(master_pubkey)
898 QMessageBox.information(None,"Copy succesful", "Your public master key has been copied to your clipboard.")
901 def acceptbit(self, currency):
902 master_pubkey = self.wallet.master_public_key
903 url = "http://acceptbit.com/mpk/%s/%s" % (master_pubkey, currency)
906 def show_seed_dialog(self):
907 gui_qt.ElectrumWindow.show_seed_dialog(self.wallet)
909 class MiniDriver(QObject):
916 def __init__(self, wallet, window):
917 super(QObject, self).__init__()
922 self.wallet.interface.register_callback('updated',self.update_callback)
923 self.wallet.interface.register_callback('connected', self.update_callback)
924 self.wallet.interface.register_callback('disconnected', self.update_callback)
929 self.connect(self, SIGNAL("updatesignal()"), self.update)
930 self.update_callback()
932 # This is a hack to workaround that Qt does not like changing the
933 # window properties from this other thread before the runloop has
935 def update_callback(self):
936 self.emit(SIGNAL("updatesignal()"))
939 if not self.wallet.interface:
941 elif not self.wallet.interface.is_connected:
943 elif not self.wallet.up_to_date:
948 if self.wallet.up_to_date:
949 self.update_balance()
950 self.update_completions()
951 self.update_history()
953 def initializing(self):
954 if self.state == self.INITIALIZING:
956 self.state = self.INITIALIZING
957 self.window.deactivate()
959 def connecting(self):
960 if self.state == self.CONNECTING:
962 self.state = self.CONNECTING
963 self.window.deactivate()
965 def synchronizing(self):
966 if self.state == self.SYNCHRONIZING:
968 self.state = self.SYNCHRONIZING
969 self.window.deactivate()
972 if self.state == self.READY:
974 self.state = self.READY
975 self.window.activate()
977 def update_balance(self):
978 conf_balance, unconf_balance = self.wallet.get_balance()
979 balance = D(conf_balance + unconf_balance)
980 self.window.set_balances(balance)
982 def update_completions(self):
984 for addr, label in self.wallet.labels.items():
985 if addr in self.wallet.addressbook:
986 completions.append("%s <%s>" % (label, addr))
987 completions = completions + self.wallet.aliases.keys()
988 self.window.update_completions(completions)
990 def update_history(self):
991 tx_history = self.wallet.get_tx_history()
992 self.window.update_history(tx_history)
995 if __name__ == "__main__":
996 app = QApplication(sys.argv)
997 with open(rsrc("style.css")) as style_file:
998 app.setStyleSheet(style_file.read())
1000 sys.exit(app.exec_())