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."""
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 self.layout.addWidget(QLabel("Your transaction has been sent.\nPlease enter a label for this transaction for future reference."))
225 self.label_edit = QLineEdit()
226 self.label_edit.setPlaceholderText(_("Transaction label"))
227 self.label_edit.setObjectName("label_input")
228 self.label_edit.setAttribute(Qt.WA_MacShowFocusRect, 0)
229 self.label_edit.setFocusPolicy(Qt.ClickFocus)
230 self.layout.addWidget(self.label_edit)
232 self.save_button = QPushButton(_("Save"))
233 self.layout.addWidget(self.save_button)
234 self.save_button.clicked.connect(self.set_label)
238 class MiniWindow(QDialog):
240 def __init__(self, actuator, expand_callback, config):
241 super(MiniWindow, self).__init__()
242 tx = "e08115d0f7819aee65b9d24f81ef9d46eb62bb67ddef5318156cbc3ceb7b703e"
244 self.actuator = actuator
246 self.btc_balance = None
247 self.quote_currencies = ["BRL", "CNY", "EUR", "GBP", "RUB", "USD"]
248 self.actuator.set_configured_currency(self.set_quote_currency)
249 self.exchanger = exchange_rate.Exchanger(self)
250 # Needed because price discovery is done in a different thread
251 # which needs to be sent back to this main one to update the GUI
252 self.connect(self, SIGNAL("refresh_balance()"), self.refresh_balance)
254 self.balance_label = BalanceLabel(self.change_quote_currency)
255 self.balance_label.setObjectName("balance_label")
258 # Bitcoin address code
259 self.address_input = QLineEdit()
260 self.address_input.setPlaceholderText(_("Enter a Bitcoin address or contact"))
261 self.address_input.setObjectName("address_input")
263 self.address_input.setFocusPolicy(Qt.ClickFocus)
265 self.address_input.textChanged.connect(self.address_field_changed)
266 resize_line_edit_width(self.address_input,
267 "1BtaFUr3qVvAmwrsuDuu5zk6e4s2rxd2Gy")
269 self.address_completions = QStringListModel()
270 address_completer = QCompleter(self.address_input)
271 address_completer.setCaseSensitivity(False)
272 address_completer.setModel(self.address_completions)
273 self.address_input.setCompleter(address_completer)
275 address_layout = QHBoxLayout()
276 address_layout.addWidget(self.address_input)
278 self.amount_input = QLineEdit()
279 self.amount_input.setPlaceholderText(_("... and amount"))
280 self.amount_input.setObjectName("amount_input")
282 self.amount_input.setFocusPolicy(Qt.ClickFocus)
283 # This is changed according to the user's displayed balance
284 self.amount_validator = QDoubleValidator(self.amount_input)
285 self.amount_validator.setNotation(QDoubleValidator.StandardNotation)
286 self.amount_validator.setDecimals(8)
287 self.amount_input.setValidator(self.amount_validator)
289 # This removes the very ugly OSX highlighting, please leave this in :D
290 self.address_input.setAttribute(Qt.WA_MacShowFocusRect, 0)
291 self.amount_input.setAttribute(Qt.WA_MacShowFocusRect, 0)
292 self.amount_input.textChanged.connect(self.amount_input_changed)
294 self.send_button = QPushButton(_("&Send"))
295 self.send_button.setObjectName("send_button")
296 self.send_button.setDisabled(True);
297 self.send_button.clicked.connect(self.send)
299 # Creating the receive button
300 self.receive_button = QPushButton(_("&Receive"))
301 self.receive_button.setObjectName("receive_button")
302 self.receive_button.setDefault(True)
304 main_layout = QGridLayout(self)
306 main_layout.addWidget(self.balance_label, 0, 0)
307 main_layout.addWidget(self.receive_button, 0, 1)
309 main_layout.addWidget(self.address_input, 1, 0)
311 main_layout.addWidget(self.amount_input, 2, 0)
312 main_layout.addWidget(self.send_button, 2, 1)
314 self.history_list = history_widget.HistoryWidget()
315 self.history_list.setObjectName("history")
316 self.history_list.hide()
317 self.history_list.setAlternatingRowColors(True)
319 main_layout.addWidget(self.history_list, 3, 0, 1, 2)
322 self.receiving = receiving_widget.ReceivingWidget(self)
323 self.receiving.setObjectName("receiving")
325 # Add to the right side
326 self.receiving_box = QGroupBox(_("Select a receiving address"))
327 extra_layout = QGridLayout()
329 # Checkbox to filter used addresses
330 hide_used = QCheckBox(_('Hide used addresses'))
331 hide_used.setChecked(True)
332 hide_used.stateChanged.connect(self.receiving.toggle_used)
334 # Events for receiving addresses
335 self.receiving.clicked.connect(self.receiving.copy_address)
336 self.receiving.itemDoubleClicked.connect(self.receiving.edit_label)
337 self.receiving.itemChanged.connect(self.receiving.update_label)
340 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)
342 extra_layout.addWidget(self.receiving, 1,0)
343 extra_layout.addWidget(hide_used, 2,0)
344 extra_layout.setColumnMinimumWidth(0,200)
346 self.receiving_box.setLayout(extra_layout)
347 main_layout.addWidget(self.receiving_box,0,3,-1,3)
348 self.receiving_box.hide()
350 self.receive_button.clicked.connect(self.toggle_receiving_layout)
352 # Creating the menu bar
354 electrum_menu = menubar.addMenu(_("&Bitcoin"))
356 electrum_menu.addSeparator()
358 quit_option = electrum_menu.addAction(_("&Quit"))
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.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()
394 show_history = view_menu.addAction(_("Show History"))
395 show_history.setCheckable(True)
396 show_history.toggled.connect(self.show_history)
398 help_menu = menubar.addMenu(_("&Help"))
399 the_website = help_menu.addAction(_("&Website"))
400 the_website.triggered.connect(self.the_website)
401 help_menu.addSeparator()
402 report_bug = help_menu.addAction(_("&Report Bug"))
403 report_bug.triggered.connect(self.show_report_bug)
404 show_about = help_menu.addAction(_("&About"))
405 show_about.triggered.connect(self.show_about)
406 main_layout.setMenuBar(menubar)
407 self.main_layout = main_layout
409 quit_shortcut = QShortcut(QKeySequence("Ctrl+Q"), self)
410 quit_shortcut.activated.connect(self.close)
411 close_shortcut = QShortcut(QKeySequence("Ctrl+W"), self)
412 close_shortcut.activated.connect(self.close)
414 g = self.config.get("winpos-lite",[4, 25, 351, 149])
415 self.setGeometry(g[0], g[1], g[2], g[3])
417 show_hist = self.config.get("gui_show_history",False)
418 show_history.setChecked(show_hist)
419 self.show_history(show_hist)
421 self.setWindowIcon(QIcon(":electrum.png"))
422 self.setWindowTitle("Electrum")
423 self.setWindowFlags(Qt.Window|Qt.MSWindowsFixedSizeDialogHint)
424 self.layout().setSizeConstraint(QLayout.SetFixedSize)
425 self.setObjectName("main_window")
428 def toggle_receiving_layout(self):
429 if self.receiving_box.isVisible():
430 self.receiving_box.hide()
431 self.receive_button.setProperty("isActive", False)
433 qApp.style().unpolish(self.receive_button)
434 qApp.style().polish(self.receive_button)
436 self.receiving_box.show()
437 self.receive_button.setProperty("isActive", 'true')
439 qApp.style().unpolish(self.receive_button)
440 qApp.style().polish(self.receive_button)
442 def toggle_theme(self, theme_name):
443 old_path = QDir.currentPath()
444 self.actuator.change_theme(theme_name)
445 # Recompute style globally
446 qApp.style().unpolish(self)
447 qApp.style().polish(self)
448 QDir.setCurrent(old_path)
450 def closeEvent(self, event):
452 self.config.set_key("winpos-lite", [g.left(),g.top(),g.width(),g.height()],True)
453 self.config.set_key("gui_show_history", self.history_list.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-desktop.com")
600 def show_about(self):
601 QMessageBox.about(self, "Electrum",
602 _("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."))
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 show_history(self, toggle_state):
610 self.main_layout.setRowMinimumHeight(3,200)
611 self.history_list.show()
613 self.main_layout.setRowMinimumHeight(3,0)
614 self.history_list.hide()
616 def backup_wallet(self):
618 folderName = QFileDialog.getExistingDirectory(QWidget(), 'Select folder to save a copy of your wallet to', os.path.expanduser('~/'))
620 sourceFile = util.user_dir() + '/electrum.dat'
621 shutil.copy2(sourceFile, str(folderName))
622 QMessageBox.information(None,"Wallet backup created", "A copy of your wallet file was created in '%s'" % str(folderName))
623 except (IOError, os.error), reason:
624 QMessageBox.critical(None,"Unable to create backup", "Electrum was unable copy your wallet file to the specified location.\n" + str(reason))
629 class BalanceLabel(QLabel):
635 def __init__(self, change_quote_currency, parent=None):
636 super(QLabel, self).__init__(_("Connecting..."), parent)
637 self.change_quote_currency = change_quote_currency
638 self.state = self.SHOW_CONNECTING
639 self.balance_text = ""
640 self.amount_text = ""
642 def mousePressEvent(self, event):
643 """Change the fiat currency selection if window background is clicked."""
644 if self.state != self.SHOW_CONNECTING:
645 self.change_quote_currency()
647 def set_balance_text(self, btc_balance, quote_text):
648 """Set the amount of bitcoins in the gui."""
649 if self.state == self.SHOW_CONNECTING:
650 self.state = self.SHOW_BALANCE
651 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)
652 if self.state == self.SHOW_BALANCE:
653 self.setText(self.balance_text)
655 def set_amount_text(self, quote_text):
656 self.amount_text = "<span style='font-size: 10pt'>%s</span>" % quote_text
657 if self.state == self.SHOW_AMOUNT:
658 self.setText(self.amount_text)
660 def show_balance(self):
661 if self.state == self.SHOW_AMOUNT:
662 self.state = self.SHOW_BALANCE
663 self.setText(self.balance_text)
665 def show_amount(self):
666 if self.state == self.SHOW_BALANCE:
667 self.state = self.SHOW_AMOUNT
668 self.setText(self.amount_text)
670 def ok_cancel_buttons(dialog):
671 row_layout = QHBoxLayout()
672 row_layout.addStretch(1)
673 ok_button = QPushButton(_("OK"))
674 row_layout.addWidget(ok_button)
675 ok_button.clicked.connect(dialog.accept)
676 cancel_button = QPushButton(_("Cancel"))
677 row_layout.addWidget(cancel_button)
678 cancel_button.clicked.connect(dialog.reject)
681 class PasswordDialog(QDialog):
683 def __init__(self, parent):
684 super(QDialog, self).__init__(parent)
688 self.password_input = QLineEdit()
689 self.password_input.setEchoMode(QLineEdit.Password)
691 main_layout = QVBoxLayout(self)
692 message = _('Please enter your password')
693 main_layout.addWidget(QLabel(message))
697 grid.addWidget(QLabel(_('Password')), 1, 0)
698 grid.addWidget(self.password_input, 1, 1)
699 main_layout.addLayout(grid)
701 main_layout.addLayout(ok_cancel_buttons(self))
702 self.setLayout(main_layout)
707 return unicode(self.password_input.text())
709 class ReceivePopup(QDialog):
711 def leaveEvent(self, event):
714 def setup(self, address):
715 label = QLabel(_("Copied your Bitcoin address to the clipboard!"))
716 address_display = QLineEdit(address)
717 address_display.setReadOnly(True)
718 resize_line_edit_width(address_display, address)
720 main_layout = QVBoxLayout(self)
721 main_layout.addWidget(label)
722 main_layout.addWidget(address_display)
724 self.setMouseTracking(True)
725 self.setWindowTitle("Electrum - " + _("Receive Bitcoin payment"))
726 self.setWindowFlags(Qt.Window|Qt.FramelessWindowHint|
727 Qt.MSWindowsFixedSizeDialogHint)
728 self.layout().setSizeConstraint(QLayout.SetFixedSize)
729 #self.setFrameStyle(QFrame.WinPanel|QFrame.Raised)
730 #self.setAlignment(Qt.AlignCenter)
733 parent = self.parent()
734 top_left_pos = parent.mapToGlobal(parent.rect().bottomLeft())
735 self.move(top_left_pos)
736 center_mouse_pos = self.mapToGlobal(self.rect().center())
737 QCursor.setPos(center_mouse_pos)
741 """Initialize the definitions relating to themes and
742 sending/recieving bitcoins."""
745 def __init__(self, wallet):
746 """Retrieve the gui theme used in previous session."""
748 self.theme_name = self.wallet.config.get('litegui_theme','Cleanlook')
749 self.themes = load_theme_paths()
751 def load_theme(self):
752 """Load theme retrieved from wallet file."""
754 theme_prefix, theme_path = self.themes[self.theme_name]
756 util.print_error("Theme not found!", self.theme_name)
758 QDir.setCurrent(os.path.join(theme_prefix, theme_path))
759 with open(rsrc("style.css")) as style_file:
760 qApp.setStyleSheet(style_file.read())
762 def theme_names(self):
764 return sorted(self.themes.keys())
766 def selected_theme(self):
768 return self.theme_name
770 def change_theme(self, theme_name):
772 self.theme_name = theme_name
773 self.wallet.config.set_key('litegui_theme',theme_name)
776 def set_configured_currency(self, set_quote_currency):
777 """Set the inital fiat currency conversion country (USD/EUR/GBP) in
778 the GUI to what it was set to in the wallet."""
779 currency = self.wallet.config.get('conversion_currency')
780 # currency can be none when Electrum is used for the first
781 # time and no setting has been created yet.
782 if currency is not None:
783 set_quote_currency(currency)
785 def set_config_currency(self, conversion_currency):
786 """Change the wallet fiat currency country."""
787 self.wallet.config.set_key('conversion_currency',conversion_currency,True)
789 def copy_address(self, receive_popup):
790 """Copy the wallet addresses into the client."""
791 addrs = [addr for addr in self.wallet.all_addresses()
792 if not self.wallet.is_change(addr)]
793 # Select most recent addresses from gap limit
794 addrs = addrs[-self.wallet.gap_limit:]
795 copied_address = random.choice(addrs)
796 qApp.clipboard().setText(copied_address)
797 receive_popup.setup(copied_address)
798 receive_popup.popup()
800 def waiting_dialog(self, f):
805 w.setWindowTitle('Electrum')
806 l = QLabel('Sending transaction, please wait.')
815 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
820 def send(self, address, amount, parent_window):
821 """Send bitcoins to the target address."""
822 dest_address = self.fetch_destination(address)
824 if dest_address is None or not self.wallet.is_valid(dest_address):
825 QMessageBox.warning(parent_window, _('Error'),
826 _('Invalid Bitcoin Address') + ':\n' + address, _('OK'))
829 convert_amount = lambda amount: \
830 int(D(unicode(amount)) * bitcoin(1))
831 amount = convert_amount(amount)
833 if self.wallet.use_encryption:
834 password_dialog = PasswordDialog(parent_window)
835 password = password_dialog.run()
843 if amount < bitcoin(1) / 10:
845 fee = bitcoin(1) / 1000
848 tx = self.wallet.mktx([(dest_address, amount)], "", password, fee)
849 except BaseException as error:
850 QMessageBox.warning(parent_window, _('Error'), str(error), _('OK'))
853 h = self.wallet.send_tx(tx)
855 self.waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Sending transaction, please wait..."))
857 status, message = self.wallet.receive_tx(h)
861 dumpf = tempfile.NamedTemporaryFile(delete=False)
864 print "Dumped error tx to", dumpf.name
865 QMessageBox.warning(parent_window, _('Error'), message, _('OK'))
868 TransactionWindow(message, self)
869 # QMessageBox.information(parent_window, '',
870 # _('Your transaction has been sent.') + '\n' + message, _('OK'))
873 def fetch_destination(self, address):
874 recipient = unicode(address).strip()
877 match1 = re.match("^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$",
880 # label or alias, with address in brackets
881 match2 = re.match("(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>",
886 self.wallet.get_alias(recipient, True,
887 self.show_message, self.question)
890 return match2.group(2)
894 def is_valid(self, address):
895 """Check if bitcoin address is valid."""
897 return self.wallet.is_valid(address)
899 def copy_master_public_key(self):
900 master_pubkey = self.wallet.master_public_key
901 qApp.clipboard().setText(master_pubkey)
902 QMessageBox.information(None,"Copy succesful", "Your public master key has been copied to your clipboard.")
905 def acceptbit(self, currency):
906 master_pubkey = self.wallet.master_public_key
907 url = "http://acceptbit.com/mpk/%s/%s" % (master_pubkey, currency)
910 def show_seed_dialog(self):
911 gui_qt.ElectrumWindow.show_seed_dialog(self.wallet)
913 class MiniDriver(QObject):
920 def __init__(self, wallet, window):
921 super(QObject, self).__init__()
926 self.wallet.interface.register_callback('updated',self.update_callback)
927 self.wallet.interface.register_callback('connected', self.update_callback)
928 self.wallet.interface.register_callback('disconnected', self.update_callback)
933 self.connect(self, SIGNAL("updatesignal()"), self.update)
934 self.update_callback()
936 # This is a hack to workaround that Qt does not like changing the
937 # window properties from this other thread before the runloop has
939 def update_callback(self):
940 self.emit(SIGNAL("updatesignal()"))
943 if not self.wallet.interface:
945 elif not self.wallet.interface.is_connected:
947 elif not self.wallet.up_to_date:
952 if self.wallet.up_to_date:
953 self.update_balance()
954 self.update_completions()
955 self.update_history()
957 def initializing(self):
958 if self.state == self.INITIALIZING:
960 self.state = self.INITIALIZING
961 self.window.deactivate()
963 def connecting(self):
964 if self.state == self.CONNECTING:
966 self.state = self.CONNECTING
967 self.window.deactivate()
969 def synchronizing(self):
970 if self.state == self.SYNCHRONIZING:
972 self.state = self.SYNCHRONIZING
973 self.window.deactivate()
976 if self.state == self.READY:
978 self.state = self.READY
979 self.window.activate()
981 def update_balance(self):
982 conf_balance, unconf_balance = self.wallet.get_balance()
983 balance = D(conf_balance + unconf_balance)
984 self.window.set_balances(balance)
986 def update_completions(self):
988 for addr, label in self.wallet.labels.items():
989 if addr in self.wallet.addressbook:
990 completions.append("%s <%s>" % (label, addr))
991 completions = completions + self.wallet.aliases.keys()
992 self.window.update_completions(completions)
994 def update_history(self):
995 tx_history = self.wallet.get_tx_history()
996 self.window.update_history(tx_history)
999 if __name__ == "__main__":
1000 app = QApplication(sys.argv)
1001 with open(rsrc("style.css")) as style_file:
1002 app.setStyleSheet(style_file.read())
1004 sys.exit(app.exec_())