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
40 bitcoin = lambda v: v * 100000000
42 def IconButton(filename, parent=None):
43 pixmap = QPixmap(filename)
45 return QPushButton(icon, "", parent)
50 self.emit(SIGNAL('timersignal'))
53 def resize_line_edit_width(line_edit, text_input):
54 metrics = QFontMetrics(qApp.font())
55 # Create an extra character to add some space on the end
57 line_edit.setMinimumWidth(metrics.width(text_input))
59 def load_theme_name(theme_path):
61 with open(os.path.join(theme_path, "name.cfg")) as name_cfg_file:
62 return name_cfg_file.read().rstrip("\n").strip()
67 def theme_dirs_from_prefix(prefix):
68 if not os.path.exists(prefix):
71 for potential_theme in os.listdir(prefix):
72 theme_full_path = os.path.join(prefix, potential_theme)
73 theme_css = os.path.join(theme_full_path, "style.css")
74 if not os.path.exists(theme_css):
76 theme_name = load_theme_name(theme_full_path)
77 if theme_name is None:
79 theme_paths[theme_name] = prefix, potential_theme
82 def load_theme_paths():
84 prefixes = (util.local_data_dir(), util.appdata_dir())
85 for prefix in prefixes:
86 theme_paths.update(theme_dirs_from_prefix(prefix))
90 def csv_transaction(wallet):
92 select_export = _('Select file to export your wallet transactions to')
93 fileName = QFileDialog.getSaveFileName(QWidget(), select_export, os.path.expanduser('~/electrum-history.csv'), "*.csv")
95 with open(fileName, "w+") as csvfile:
96 transaction = csv.writer(csvfile)
97 transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
98 for item in wallet.get_tx_history():
99 tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
101 if timestamp is not None:
103 time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
104 except [RuntimeError, TypeError, NameError] as reason:
105 time_string = "unknown"
108 time_string = "unknown"
110 time_string = "pending"
112 if value is not None:
113 value_string = format_satoshis(value, True, wallet.num_zeros)
118 fee_string = format_satoshis(fee, True, wallet.num_zeros)
123 label, is_default_label = wallet.get_label(tx_hash)
127 balance_string = format_satoshis(balance, False, wallet.num_zeros)
128 transaction.writerow([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
129 QMessageBox.information(None,"CSV Export created", "Your CSV export has been successfully created.")
130 except (IOError, os.error), reason:
131 export_error_label = _("Electrum was unable to produce a transaction export.")
132 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
135 class ElectrumGui(QObject):
137 def __init__(self, wallet, config, expert=None):
138 super(QObject, self).__init__()
142 self.check_qt_version()
144 if self.expert != None:
145 self.app = self.expert.app
147 self.app = QApplication(sys.argv)
149 def check_qt_version(self):
150 qtVersion = qVersion()
151 if not(int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
152 app = QApplication(sys.argv)
153 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")
154 self.config.set_key('gui','classic',True)
159 actuator = MiniActuator(self.wallet)
160 # Should probably not modify the current path but instead
161 # change the behaviour of rsrc(...)
162 old_path = QDir.currentPath()
163 actuator.load_theme()
165 self.mini = MiniWindow(actuator, self.expand, self.config)
166 driver = MiniDriver(self.wallet, self.mini)
168 # Reset path back to original value now that loading the GUI
170 QDir.setCurrent(old_path)
175 if self.expert == None:
178 self.expert = gui_classic.ElectrumWindow(self.wallet, self.config)
179 self.expert.app = self.app
180 self.expert.connect_slots(timer)
181 self.expert.update_wallet()
185 """Hide the lite mode window and show pro-mode."""
186 self.config.set_key('gui', 'classic', True)
190 def set_url(self, url):
191 payto, amount, label, message, signature, identity, url = \
192 self.wallet.parse_url(url, self.show_message, self.show_question)
193 self.mini.set_payment_fields(payto, amount)
195 def show_message(self, message):
196 QMessageBox.information(self.mini, _("Message"), message, _("OK"))
198 def show_question(self, message):
199 choice = QMessageBox.question(self.mini, _("Message"), message,
200 QMessageBox.Yes|QMessageBox.No,
202 return choice == QMessageBox.Yes
204 def restore_or_create(self):
205 qt_gui_object = gui_classic.ElectrumGui(self.wallet, self.app)
206 return qt_gui_object.restore_or_create()
208 class TransactionWindow(QDialog):
211 label = unicode(self.label_edit.text())
212 self.parent.wallet.labels[self.tx_id] = label
214 super(TransactionWindow, self).accept()
216 def __init__(self, transaction_id, parent):
217 super(TransactionWindow, self).__init__()
219 self.tx_id = str(transaction_id)
224 self.setWindowTitle(_("Transaction successfully sent"))
226 self.layout = QGridLayout(self)
227 history_label = "%s\n%s" % (_("Your transaction has been sent."), _("Please enter a label for this transaction for future reference."))
228 self.layout.addWidget(QLabel(history_label))
230 self.label_edit = QLineEdit()
231 self.label_edit.setPlaceholderText(_("Transaction label"))
232 self.label_edit.setObjectName("label_input")
233 self.label_edit.setAttribute(Qt.WA_MacShowFocusRect, 0)
234 self.label_edit.setFocusPolicy(Qt.ClickFocus)
235 self.layout.addWidget(self.label_edit)
237 self.save_button = QPushButton(_("Save"))
238 self.layout.addWidget(self.save_button)
239 self.save_button.clicked.connect(self.set_label)
243 class MiniWindow(QDialog):
245 def __init__(self, actuator, expand_callback, config):
246 super(MiniWindow, self).__init__()
247 tx = "e08115d0f7819aee65b9d24f81ef9d46eb62bb67ddef5318156cbc3ceb7b703e"
249 self.actuator = actuator
251 self.btc_balance = None
252 self.quote_currencies = ["BRL", "CNY", "EUR", "GBP", "RUB", "USD"]
253 self.actuator.set_configured_currency(self.set_quote_currency)
254 self.exchanger = exchange_rate.Exchanger(self)
255 # Needed because price discovery is done in a different thread
256 # which needs to be sent back to this main one to update the GUI
257 self.connect(self, SIGNAL("refresh_balance()"), self.refresh_balance)
259 self.balance_label = BalanceLabel(self.change_quote_currency)
260 self.balance_label.setObjectName("balance_label")
263 # Bitcoin address code
264 self.address_input = QLineEdit()
265 self.address_input.setPlaceholderText(_("Enter a Bitcoin address or contact"))
266 self.address_input.setObjectName("address_input")
268 self.address_input.setFocusPolicy(Qt.ClickFocus)
270 self.address_input.textChanged.connect(self.address_field_changed)
271 resize_line_edit_width(self.address_input,
272 "1BtaFUr3qVvAmwrsuDuu5zk6e4s2rxd2Gy")
274 self.address_completions = QStringListModel()
275 address_completer = QCompleter(self.address_input)
276 address_completer.setCaseSensitivity(False)
277 address_completer.setModel(self.address_completions)
278 self.address_input.setCompleter(address_completer)
280 address_layout = QHBoxLayout()
281 address_layout.addWidget(self.address_input)
283 self.amount_input = QLineEdit()
284 self.amount_input.setPlaceholderText(_("... and amount"))
285 self.amount_input.setObjectName("amount_input")
287 self.amount_input.setFocusPolicy(Qt.ClickFocus)
288 # This is changed according to the user's displayed balance
289 self.amount_validator = QDoubleValidator(self.amount_input)
290 self.amount_validator.setNotation(QDoubleValidator.StandardNotation)
291 self.amount_validator.setDecimals(8)
292 self.amount_input.setValidator(self.amount_validator)
294 # This removes the very ugly OSX highlighting, please leave this in :D
295 self.address_input.setAttribute(Qt.WA_MacShowFocusRect, 0)
296 self.amount_input.setAttribute(Qt.WA_MacShowFocusRect, 0)
297 self.amount_input.textChanged.connect(self.amount_input_changed)
299 if self.actuator.wallet.seed:
300 self.send_button = QPushButton(_("&Send"))
302 self.send_button = QPushButton(_("&Create"))
304 self.send_button.setObjectName("send_button")
305 self.send_button.setDisabled(True);
306 self.send_button.clicked.connect(self.send)
308 # Creating the receive button
309 self.switch_button = QPushButton( QIcon(":icons/switchgui.png"),'' )
310 self.switch_button.setMaximumWidth(25)
311 self.switch_button.setFlat(True)
312 self.switch_button.clicked.connect(expand_callback)
314 main_layout = QGridLayout(self)
316 main_layout.addWidget(self.balance_label, 0, 0, 1, 3)
317 main_layout.addWidget(self.switch_button, 0, 3)
319 main_layout.addWidget(self.address_input, 1, 0, 1, 4)
320 main_layout.addWidget(self.amount_input, 2, 0, 1, 2)
321 main_layout.addWidget(self.send_button, 2, 2, 1, 2)
323 self.send_button.setMaximumWidth(125)
325 self.history_list = history_widget.HistoryWidget()
326 self.history_list.setObjectName("history")
327 self.history_list.hide()
328 self.history_list.setAlternatingRowColors(True)
330 main_layout.addWidget(self.history_list, 3, 0, 1, 4)
332 self.receiving = receiving_widget.ReceivingWidget(self)
333 self.receiving.setObjectName("receiving")
335 # Add to the right side
336 self.receiving_box = QGroupBox(_("Select a receiving address"))
337 extra_layout = QGridLayout()
339 # Checkbox to filter used addresses
340 hide_used = QCheckBox(_('Hide used addresses'))
341 hide_used.setChecked(True)
342 hide_used.stateChanged.connect(self.receiving.toggle_used)
344 # Events for receiving addresses
345 self.receiving.clicked.connect(self.receiving.copy_address)
346 self.receiving.itemDoubleClicked.connect(self.receiving.edit_label)
347 self.receiving.itemChanged.connect(self.receiving.update_label)
351 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)
353 extra_layout.addWidget(self.receiving, 1,0)
354 extra_layout.addWidget(hide_used, 2,0)
355 extra_layout.setColumnMinimumWidth(0,200)
357 self.receiving_box.setLayout(extra_layout)
358 main_layout.addWidget(self.receiving_box,0,4,-1,3)
359 self.receiving_box.hide()
361 # Creating the menu bar
363 electrum_menu = menubar.addMenu(_("&Electrum"))
365 quit_option = electrum_menu.addAction(_("&Close"))
367 quit_option.triggered.connect(self.close)
369 view_menu = menubar.addMenu(_("&View"))
370 extra_menu = menubar.addMenu(_("&Extra"))
372 backup_wallet_menu = extra_menu.addAction( _("&Create wallet backup"))
373 backup_wallet_menu.triggered.connect(backup_wallet)
375 export_csv = extra_menu.addAction( _("&Export transactions to CSV") )
376 export_csv.triggered.connect(lambda: csv_transaction(self.actuator.wallet))
378 master_key = extra_menu.addAction( _("Copy master public key to clipboard") )
379 master_key.triggered.connect(self.actuator.copy_master_public_key)
381 expert_gui = view_menu.addAction(_("&Classic GUI"))
382 expert_gui.triggered.connect(expand_callback)
383 themes_menu = view_menu.addMenu(_("&Themes"))
384 selected_theme = self.actuator.selected_theme()
385 theme_group = QActionGroup(self)
386 for theme_name in self.actuator.theme_names():
387 theme_action = themes_menu.addAction(theme_name)
388 theme_action.setCheckable(True)
389 if selected_theme == theme_name:
390 theme_action.setChecked(True)
391 class SelectThemeFunctor:
392 def __init__(self, theme_name, toggle_theme):
393 self.theme_name = theme_name
394 self.toggle_theme = toggle_theme
395 def __call__(self, checked):
397 self.toggle_theme(self.theme_name)
398 delegate = SelectThemeFunctor(theme_name, self.toggle_theme)
399 theme_action.toggled.connect(delegate)
400 theme_group.addAction(theme_action)
401 view_menu.addSeparator()
403 show_receiving = view_menu.addAction(_("Show Receiving addresses"))
404 show_receiving.setCheckable(True)
405 show_receiving.toggled.connect(self.toggle_receiving_layout)
407 show_receiving_toggle = self.config.get("gui_show_receiving",False)
408 show_receiving.setChecked(show_receiving_toggle)
409 self.show_receiving = show_receiving
411 self.toggle_receiving_layout(show_receiving_toggle)
414 show_history = view_menu.addAction(_("Show History"))
415 show_history.setCheckable(True)
416 show_history.toggled.connect(self.show_history)
418 help_menu = menubar.addMenu(_("&Help"))
419 the_website = help_menu.addAction(_("&Website"))
420 the_website.triggered.connect(self.the_website)
421 help_menu.addSeparator()
422 report_bug = help_menu.addAction(_("&Report Bug"))
423 report_bug.triggered.connect(self.show_report_bug)
424 show_about = help_menu.addAction(_("&About"))
425 show_about.triggered.connect(self.show_about)
426 main_layout.setMenuBar(menubar)
427 self.main_layout = main_layout
429 quit_shortcut = QShortcut(QKeySequence("Ctrl+Q"), self)
430 quit_shortcut.activated.connect(self.close)
431 close_shortcut = QShortcut(QKeySequence("Ctrl+W"), self)
432 close_shortcut.activated.connect(self.close)
434 g = self.config.get("winpos-lite",[4, 25, 351, 149])
435 self.setGeometry(g[0], g[1], g[2], g[3])
437 show_hist = self.config.get("gui_show_history",False)
438 show_history.setChecked(show_hist)
439 self.show_history(show_hist)
441 self.setWindowIcon(QIcon(":electrum.png"))
442 self.setWindowTitle("Electrum")
443 self.setWindowFlags(Qt.Window|Qt.MSWindowsFixedSizeDialogHint)
444 self.layout().setSizeConstraint(QLayout.SetFixedSize)
445 self.setObjectName("main_window")
449 def toggle_theme(self, theme_name):
450 old_path = QDir.currentPath()
451 self.actuator.change_theme(theme_name)
452 # Recompute style globally
453 qApp.style().unpolish(self)
454 qApp.style().polish(self)
455 QDir.setCurrent(old_path)
457 def closeEvent(self, event):
459 self.config.set_key("winpos-lite", [g.left(),g.top(),g.width(),g.height()],True)
460 self.config.set_key("gui_show_history", self.history_list.isVisible(),True)
461 self.config.set_key("gui_show_receiving", self.receiving_box.isVisible(),True)
463 super(MiniWindow, self).closeEvent(event)
466 def set_payment_fields(self, dest_address, amount):
467 self.address_input.setText(dest_address)
468 self.address_field_changed(dest_address)
469 self.amount_input.setText(amount)
474 def deactivate(self):
477 def set_quote_currency(self, currency):
478 """Set and display the fiat currency country."""
479 if currency not in self.quote_currencies:
481 self.quote_currencies.remove(currency)
482 self.quote_currencies.insert(0, currency)
483 self.refresh_balance()
485 def change_quote_currency(self):
486 self.quote_currencies = \
487 self.quote_currencies[1:] + self.quote_currencies[0:1]
488 self.actuator.set_config_currency(self.quote_currencies[0])
489 self.refresh_balance()
491 def refresh_balance(self):
492 if self.btc_balance is None:
493 # Price has been discovered before wallet has been loaded
494 # and server connect... so bail.
496 self.set_balances(self.btc_balance)
497 self.amount_input_changed(self.amount_input.text())
499 def set_balances(self, btc_balance):
500 """Set the bitcoin balance and update the amount label accordingly."""
501 self.btc_balance = btc_balance
502 quote_text = self.create_quote_text(btc_balance)
504 quote_text = "(%s)" % quote_text
505 btc_balance = "%.4f" % (btc_balance / bitcoin(1))
506 self.balance_label.set_balance_text(btc_balance, quote_text)
507 self.setWindowTitle("Electrum %s - %s BTC" % (electrum_version, btc_balance))
509 def amount_input_changed(self, amount_text):
510 """Update the number of bitcoins displayed."""
511 self.check_button_status()
514 amount = D(str(amount_text))
515 except decimal.InvalidOperation:
516 self.balance_label.show_balance()
518 quote_text = self.create_quote_text(amount * bitcoin(1))
520 self.balance_label.set_amount_text(quote_text)
521 self.balance_label.show_amount()
523 self.balance_label.show_balance()
525 def create_quote_text(self, btc_balance):
526 """Return a string copy of the amount fiat currency the
527 user has in bitcoins."""
528 quote_currency = self.quote_currencies[0]
529 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
530 if quote_balance is None:
533 quote_text = "%.2f %s" % ((quote_balance / bitcoin(1)),
538 if self.actuator.send(self.address_input.text(),
539 self.amount_input.text(), self):
540 self.address_input.setText("")
541 self.amount_input.setText("")
543 def check_button_status(self):
544 """Check that the bitcoin address is valid and that something
545 is entered in the amount before making the send button clickable."""
547 value = D(str(self.amount_input.text())) * 10**8
548 except decimal.InvalidOperation:
550 # self.address_input.property(...) returns a qVariant, not a bool.
551 # The == is needed to properly invoke a comparison.
552 if (self.address_input.property("isValid") == True and
553 value is not None and 0 < value <= self.btc_balance):
554 self.send_button.setDisabled(False)
556 self.send_button.setDisabled(True)
558 def address_field_changed(self, address):
559 # label or alias, with address in brackets
560 match2 = re.match("(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>",
563 address = match2.group(2)
564 self.address_input.setText(address)
566 if is_valid(address):
567 self.check_button_status()
568 self.address_input.setProperty("isValid", True)
569 self.recompute_style(self.address_input)
571 self.send_button.setDisabled(True)
572 self.address_input.setProperty("isValid", False)
573 self.recompute_style(self.address_input)
575 if len(address) == 0:
576 self.address_input.setProperty("isValid", None)
577 self.recompute_style(self.address_input)
579 def recompute_style(self, element):
580 self.style().unpolish(element)
581 self.style().polish(element)
583 def copy_address(self):
584 receive_popup = ReceivePopup(self.receive_button)
585 self.actuator.copy_address(receive_popup)
587 def update_completions(self, completions):
588 self.address_completions.setStringList(completions)
591 def update_history(self, tx_history):
593 self.history_list.empty()
595 for item in tx_history[-10:]:
596 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
597 label = self.actuator.wallet.get_label(tx_hash)[0]
598 #amount = D(value) / 10**8
599 v_str = format_satoshis(value, True)
600 self.history_list.append(label, v_str, age(timestamp))
603 self.actuator.acceptbit(self.quote_currencies[0])
605 def the_website(self):
606 webbrowser.open("http://electrum.org")
608 def show_about(self):
609 QMessageBox.about(self, "Electrum",
610 _("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."))
612 def show_report_bug(self):
613 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
614 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
616 def toggle_receiving_layout(self, toggle_state):
618 self.receiving_box.show()
620 self.receiving_box.hide()
622 def show_history(self, toggle_state):
624 self.main_layout.setRowMinimumHeight(3,200)
625 self.history_list.show()
627 self.main_layout.setRowMinimumHeight(3,0)
628 self.history_list.hide()
630 class BalanceLabel(QLabel):
636 def __init__(self, change_quote_currency, parent=None):
637 super(QLabel, self).__init__(_("Connecting..."), parent)
638 self.change_quote_currency = change_quote_currency
639 self.state = self.SHOW_CONNECTING
640 self.balance_text = ""
641 self.amount_text = ""
643 def mousePressEvent(self, event):
644 """Change the fiat currency selection if window background is clicked."""
645 if self.state != self.SHOW_CONNECTING:
646 self.change_quote_currency()
648 def set_balance_text(self, btc_balance, quote_text):
649 """Set the amount of bitcoins in the gui."""
650 if self.state == self.SHOW_CONNECTING:
651 self.state = self.SHOW_BALANCE
652 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)
653 if self.state == self.SHOW_BALANCE:
654 self.setText(self.balance_text)
656 def set_amount_text(self, quote_text):
657 self.amount_text = "<span style='font-size: 10pt'>%s</span>" % quote_text
658 if self.state == self.SHOW_AMOUNT:
659 self.setText(self.amount_text)
661 def show_balance(self):
662 if self.state == self.SHOW_AMOUNT:
663 self.state = self.SHOW_BALANCE
664 self.setText(self.balance_text)
666 def show_amount(self):
667 if self.state == self.SHOW_BALANCE:
668 self.state = self.SHOW_AMOUNT
669 self.setText(self.amount_text)
671 def ok_cancel_buttons(dialog):
672 row_layout = QHBoxLayout()
673 row_layout.addStretch(1)
674 ok_button = QPushButton(_("OK"))
675 row_layout.addWidget(ok_button)
676 ok_button.clicked.connect(dialog.accept)
677 cancel_button = QPushButton(_("Cancel"))
678 row_layout.addWidget(cancel_button)
679 cancel_button.clicked.connect(dialog.reject)
682 class PasswordDialog(QDialog):
684 def __init__(self, parent):
685 super(QDialog, self).__init__(parent)
689 self.password_input = QLineEdit()
690 self.password_input.setEchoMode(QLineEdit.Password)
692 main_layout = QVBoxLayout(self)
693 message = _('Please enter your password')
694 main_layout.addWidget(QLabel(message))
698 grid.addWidget(QLabel(_('Password')), 1, 0)
699 grid.addWidget(self.password_input, 1, 1)
700 main_layout.addLayout(grid)
702 main_layout.addLayout(ok_cancel_buttons(self))
703 self.setLayout(main_layout)
708 return unicode(self.password_input.text())
710 class ReceivePopup(QDialog):
712 def leaveEvent(self, event):
715 def setup(self, address):
716 label = QLabel(_("Copied your Bitcoin address to the clipboard!"))
717 address_display = QLineEdit(address)
718 address_display.setReadOnly(True)
719 resize_line_edit_width(address_display, address)
721 main_layout = QVBoxLayout(self)
722 main_layout.addWidget(label)
723 main_layout.addWidget(address_display)
725 self.setMouseTracking(True)
726 self.setWindowTitle("Electrum - " + _("Receive Bitcoin payment"))
727 self.setWindowFlags(Qt.Window|Qt.FramelessWindowHint|
728 Qt.MSWindowsFixedSizeDialogHint)
729 self.layout().setSizeConstraint(QLayout.SetFixedSize)
730 #self.setFrameStyle(QFrame.WinPanel|QFrame.Raised)
731 #self.setAlignment(Qt.AlignCenter)
734 parent = self.parent()
735 top_left_pos = parent.mapToGlobal(parent.rect().bottomLeft())
736 self.move(top_left_pos)
737 center_mouse_pos = self.mapToGlobal(self.rect().center())
738 QCursor.setPos(center_mouse_pos)
742 """Initialize the definitions relating to themes and
743 sending/receiving bitcoins."""
746 def __init__(self, wallet):
747 """Retrieve the gui theme used in previous session."""
749 self.theme_name = self.wallet.config.get('litegui_theme','Cleanlook')
750 self.themes = load_theme_paths()
752 def load_theme(self):
753 """Load theme retrieved from wallet file."""
755 theme_prefix, theme_path = self.themes[self.theme_name]
757 util.print_error("Theme not found!", self.theme_name)
759 QDir.setCurrent(os.path.join(theme_prefix, theme_path))
760 with open(rsrc("style.css")) as style_file:
761 qApp.setStyleSheet(style_file.read())
763 def theme_names(self):
765 return sorted(self.themes.keys())
767 def selected_theme(self):
769 return self.theme_name
771 def change_theme(self, theme_name):
773 self.theme_name = theme_name
774 self.wallet.config.set_key('litegui_theme',theme_name)
777 def set_configured_currency(self, set_quote_currency):
778 """Set the inital fiat currency conversion country (USD/EUR/GBP) in
779 the GUI to what it was set to in the wallet."""
780 currency = self.wallet.config.get('currency')
781 # currency can be none when Electrum is used for the first
782 # time and no setting has been created yet.
783 if currency is not None:
784 set_quote_currency(currency)
786 def set_config_currency(self, conversion_currency):
787 """Change the wallet fiat currency country."""
788 self.wallet.config.set_key('conversion_currency',conversion_currency,True)
790 def copy_address(self, receive_popup):
791 """Copy the wallet addresses into the client."""
792 addrs = [addr for addr in self.wallet.addresses(True)
793 if not self.wallet.is_change(addr)]
794 # Select most recent addresses from gap limit
795 addrs = addrs[-self.wallet.gap_limit:]
796 copied_address = random.choice(addrs)
797 qApp.clipboard().setText(copied_address)
798 receive_popup.setup(copied_address)
799 receive_popup.popup()
801 def waiting_dialog(self, f):
806 w.setWindowTitle('Electrum')
807 l = QLabel('Sending transaction, please wait.')
816 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
821 def send(self, address, amount, parent_window):
822 """Send bitcoins to the target address."""
823 dest_address = self.fetch_destination(address)
825 if dest_address is None or not is_valid(dest_address):
826 QMessageBox.warning(parent_window, _('Error'),
827 _('Invalid Bitcoin Address') + ':\n' + address, _('OK'))
830 convert_amount = lambda amount: \
831 int(D(unicode(amount)) * bitcoin(1))
832 amount = convert_amount(amount)
834 if self.wallet.use_encryption:
835 password_dialog = PasswordDialog(parent_window)
836 password = password_dialog.run()
844 if amount < bitcoin(1) / 10:
846 fee = bitcoin(1) / 1000
849 tx = self.wallet.mktx([(dest_address, amount)], password, fee)
850 except BaseException as error:
851 QMessageBox.warning(parent_window, _('Error'), str(error), _('OK'))
855 h = self.wallet.send_tx(tx)
857 self.waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Sending transaction, please wait..."))
859 status, message = self.wallet.receive_tx(h)
863 dumpf = tempfile.NamedTemporaryFile(delete=False)
866 print "Dumped error tx to", dumpf.name
867 QMessageBox.warning(parent_window, _('Error'), message, _('OK'))
870 TransactionWindow(message, self)
872 filename = 'unsigned_tx_%s' % (time.mktime(time.gmtime()))
874 fileName = QFileDialog.getSaveFileName(QWidget(), _("Select a transaction filename"), os.path.expanduser('~/%s' % (filename)))
875 with open(fileName,'w') as f:
876 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
877 QMessageBox.information(QWidget(), _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
878 except BaseException as e:
879 QMessageBox.warning(QWidget(), _('Error'), _('Could not write transaction to file: %s' % e), _('OK'))
882 def fetch_destination(self, address):
883 recipient = unicode(address).strip()
886 match1 = re.match("^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$",
889 # label or alias, with address in brackets
890 match2 = re.match("(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>",
895 self.wallet.get_alias(recipient, True,
896 self.show_message, self.question)
899 return match2.group(2)
904 def copy_master_public_key(self):
905 master_pubkey = self.wallet.get_master_public_key()
906 qApp.clipboard().setText(master_pubkey)
907 QMessageBox.information(None, _("Copy successful"), _("Your master public key has been copied to your clipboard."))
910 def acceptbit(self, currency):
911 master_pubkey = self.wallet.master_public_key
912 url = "http://acceptbit.com/mpk/%s/%s" % (master_pubkey, currency)
915 def show_seed_dialog(self):
916 gui_classic.ElectrumWindow.show_seed_dialog(self.wallet)
918 class MiniDriver(QObject):
925 def __init__(self, wallet, window):
926 super(QObject, self).__init__()
931 self.wallet.interface.register_callback('updated',self.update_callback)
932 self.wallet.interface.register_callback('connected', self.update_callback)
933 self.wallet.interface.register_callback('disconnected', self.update_callback)
938 self.connect(self, SIGNAL("updatesignal()"), self.update)
939 self.update_callback()
941 # This is a hack to workaround that Qt does not like changing the
942 # window properties from this other thread before the runloop has
944 def update_callback(self):
945 self.emit(SIGNAL("updatesignal()"))
948 if not self.wallet.interface:
950 elif not self.wallet.interface.is_connected:
952 elif not self.wallet.up_to_date:
957 if self.wallet.up_to_date:
958 self.update_balance()
959 self.update_completions()
960 self.update_history()
962 def initializing(self):
963 if self.state == self.INITIALIZING:
965 self.state = self.INITIALIZING
966 self.window.deactivate()
968 def connecting(self):
969 if self.state == self.CONNECTING:
971 self.state = self.CONNECTING
972 self.window.deactivate()
974 def synchronizing(self):
975 if self.state == self.SYNCHRONIZING:
977 self.state = self.SYNCHRONIZING
978 self.window.deactivate()
981 if self.state == self.READY:
983 self.state = self.READY
984 self.window.activate()
986 def update_balance(self):
987 conf_balance, unconf_balance = self.wallet.get_balance()
988 balance = D(conf_balance + unconf_balance)
989 self.window.set_balances(balance)
991 def update_completions(self):
993 for addr, label in self.wallet.labels.items():
994 if addr in self.wallet.addressbook:
995 completions.append("%s <%s>" % (label, addr))
996 self.window.update_completions(completions)
998 def update_history(self):
999 tx_history = self.wallet.get_tx_history()
1000 self.window.update_history(tx_history)
1003 if __name__ == "__main__":
1004 app = QApplication(sys.argv)
1005 with open(rsrc("style.css")) as style_file:
1006 app.setStyleSheet(style_file.read())
1008 sys.exit(app.exec_())