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, expert=None):
135 super(QObject, self).__init__()
139 self.check_qt_version()
141 if self.expert != None:
142 self.app = self.expert.app
144 self.app = QApplication(sys.argv)
146 def check_qt_version(self):
147 qtVersion = qVersion()
148 if not(int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
149 app = QApplication(sys.argv)
150 QMessageBox.warning(None,"Could not start Lite GUI.", "Electrum was unable to load the 'Lite GUI' because it needs Qt version >= 4.7.\nChanging your config to use the 'Classic' GUI")
151 self.config.set_key('gui','classic',True)
156 actuator = MiniActuator(self.wallet)
157 # Should probably not modify the current path but instead
158 # change the behaviour of rsrc(...)
159 old_path = QDir.currentPath()
160 actuator.load_theme()
162 self.mini = MiniWindow(actuator, self.expand, self.config)
163 driver = MiniDriver(self.wallet, self.mini)
165 # Reset path back to original value now that loading the GUI
167 QDir.setCurrent(old_path)
172 if self.expert == None:
175 self.expert = gui_qt.ElectrumWindow(self.wallet, self.config)
176 self.expert.app = self.app
177 self.expert.connect_slots(timer)
178 self.expert.update_wallet()
182 """Hide the lite mode window and show pro-mode."""
183 self.config.set_key('gui', 'classic', True)
187 def set_url(self, url):
188 payto, amount, label, message, signature, identity, url = \
189 self.wallet.parse_url(url, self.show_message, self.show_question)
190 self.mini.set_payment_fields(payto, amount)
192 def show_message(self, message):
193 QMessageBox.information(self.mini, _("Message"), message, _("OK"))
195 def show_question(self, message):
196 choice = QMessageBox.question(self.mini, _("Message"), message,
197 QMessageBox.Yes|QMessageBox.No,
199 return choice == QMessageBox.Yes
201 def restore_or_create(self):
202 qt_gui_object = gui_qt.ElectrumGui(self.wallet, self.app)
203 return qt_gui_object.restore_or_create()
205 class TransactionWindow(QDialog):
208 label = unicode(self.label_edit.text())
209 self.parent.wallet.labels[self.tx_id] = label
211 super(TransactionWindow, self).accept()
213 def __init__(self, transaction_id, parent):
214 super(TransactionWindow, self).__init__()
216 self.tx_id = str(transaction_id)
221 self.setWindowTitle("Transaction successfully sent")
223 self.layout = QGridLayout(self)
224 self.layout.addWidget(QLabel("Your transaction has been sent.\nPlease enter a label for this transaction for future reference."))
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.history_list = history_widget.HistoryWidget()
316 self.history_list.setObjectName("history")
317 self.history_list.hide()
318 self.history_list.setAlternatingRowColors(True)
320 main_layout.addWidget(self.history_list, 3, 0, 1, 4)
323 self.receiving = receiving_widget.ReceivingWidget(self)
324 self.receiving.setObjectName("receiving")
326 # Add to the right side
327 self.receiving_box = QGroupBox(_("Select a receiving address"))
328 extra_layout = QGridLayout()
330 # Checkbox to filter used addresses
331 hide_used = QCheckBox(_('Hide used addresses'))
332 hide_used.setChecked(True)
333 hide_used.stateChanged.connect(self.receiving.toggle_used)
335 # Events for receiving addresses
336 self.receiving.clicked.connect(self.receiving.copy_address)
337 self.receiving.itemDoubleClicked.connect(self.receiving.edit_label)
338 self.receiving.itemChanged.connect(self.receiving.update_label)
341 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)
343 extra_layout.addWidget(self.receiving, 1,0)
344 extra_layout.addWidget(hide_used, 2,0)
345 extra_layout.setColumnMinimumWidth(0,200)
347 self.receiving_box.setLayout(extra_layout)
348 main_layout.addWidget(self.receiving_box,0,4,-1,3)
349 self.receiving_box.hide()
351 # Creating the menu bar
353 electrum_menu = menubar.addMenu(_("&Bitcoin"))
355 electrum_menu.addSeparator()
357 quit_option = electrum_menu.addAction(_("&Quit"))
358 quit_option.triggered.connect(self.close)
360 view_menu = menubar.addMenu(_("&View"))
361 extra_menu = menubar.addMenu(_("&Extra"))
363 backup_wallet = extra_menu.addAction( _("&Create wallet backup"))
364 backup_wallet.triggered.connect(self.backup_wallet)
366 export_csv = extra_menu.addAction( _("&Export transactions to CSV") )
367 export_csv.triggered.connect(lambda: csv_transaction(self.wallet))
369 master_key = extra_menu.addAction( _("Copy master public key to clipboard") )
370 master_key.triggered.connect(self.actuator.copy_master_public_key)
372 expert_gui = view_menu.addAction(_("&Classic GUI"))
373 expert_gui.triggered.connect(expand_callback)
374 themes_menu = view_menu.addMenu(_("&Themes"))
375 selected_theme = self.actuator.selected_theme()
376 theme_group = QActionGroup(self)
377 for theme_name in self.actuator.theme_names():
378 theme_action = themes_menu.addAction(theme_name)
379 theme_action.setCheckable(True)
380 if selected_theme == theme_name:
381 theme_action.setChecked(True)
382 class SelectThemeFunctor:
383 def __init__(self, theme_name, toggle_theme):
384 self.theme_name = theme_name
385 self.toggle_theme = toggle_theme
386 def __call__(self, checked):
388 self.toggle_theme(self.theme_name)
389 delegate = SelectThemeFunctor(theme_name, self.toggle_theme)
390 theme_action.toggled.connect(delegate)
391 theme_group.addAction(theme_action)
392 view_menu.addSeparator()
394 show_history = view_menu.addAction(_("Receive"))
395 show_history.setCheckable(True)
396 show_history.toggled.connect(self.toggle_receiving_layout)
398 show_history = view_menu.addAction(_("Show History"))
399 show_history.setCheckable(True)
400 show_history.toggled.connect(self.show_history)
402 help_menu = menubar.addMenu(_("&Help"))
403 the_website = help_menu.addAction(_("&Website"))
404 the_website.triggered.connect(self.the_website)
405 help_menu.addSeparator()
406 report_bug = help_menu.addAction(_("&Report Bug"))
407 report_bug.triggered.connect(self.show_report_bug)
408 show_about = help_menu.addAction(_("&About"))
409 show_about.triggered.connect(self.show_about)
410 main_layout.setMenuBar(menubar)
411 self.main_layout = main_layout
413 quit_shortcut = QShortcut(QKeySequence("Ctrl+Q"), self)
414 quit_shortcut.activated.connect(self.close)
415 close_shortcut = QShortcut(QKeySequence("Ctrl+W"), self)
416 close_shortcut.activated.connect(self.close)
418 g = self.config.get("winpos-lite",[4, 25, 351, 149])
419 self.setGeometry(g[0], g[1], g[2], g[3])
421 show_hist = self.config.get("gui_show_history",False)
422 show_history.setChecked(show_hist)
423 self.show_history(show_hist)
425 self.setWindowIcon(QIcon(":electrum.png"))
426 self.setWindowTitle("Electrum")
427 self.setWindowFlags(Qt.Window|Qt.MSWindowsFixedSizeDialogHint)
428 self.layout().setSizeConstraint(QLayout.SetFixedSize)
429 self.setObjectName("main_window")
432 def toggle_receiving_layout(self):
433 if self.receiving_box.isVisible():
434 self.receiving_box.hide()
436 self.receiving_box.show()
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_())