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
33 from wallet import format_satoshis
37 bitcoin = lambda v: v * 100000000
39 def IconButton(filename, parent=None):
40 pixmap = QPixmap(filename)
42 return QPushButton(icon, "", parent)
47 self.emit(SIGNAL('timersignal'))
50 def resize_line_edit_width(line_edit, text_input):
51 metrics = QFontMetrics(qApp.font())
52 # Create an extra character to add some space on the end
54 line_edit.setMinimumWidth(metrics.width(text_input))
56 def load_theme_name(theme_path):
58 with open(os.path.join(theme_path, "name.cfg")) as name_cfg_file:
59 return name_cfg_file.read().rstrip("\n").strip()
64 def theme_dirs_from_prefix(prefix):
65 if not os.path.exists(prefix):
68 for potential_theme in os.listdir(prefix):
69 theme_full_path = os.path.join(prefix, potential_theme)
70 theme_css = os.path.join(theme_full_path, "style.css")
71 if not os.path.exists(theme_css):
73 theme_name = load_theme_name(theme_full_path)
74 if theme_name is None:
76 theme_paths[theme_name] = prefix, potential_theme
79 def load_theme_paths():
81 prefixes = (util.local_data_dir(), util.appdata_dir())
82 for prefix in prefixes:
83 theme_paths.update(theme_dirs_from_prefix(prefix))
87 class ElectrumGui(QObject):
89 def __init__(self, wallet, config):
90 super(QObject, self).__init__()
94 self.check_qt_version()
95 self.app = QApplication(sys.argv)
98 def check_qt_version(self):
99 qtVersion = qVersion()
100 if not(int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
101 app = QApplication(sys.argv)
102 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")
103 self.config.set_key('gui','classic',True)
108 actuator = MiniActuator(self.wallet)
109 # Should probably not modify the current path but instead
110 # change the behaviour of rsrc(...)
111 old_path = QDir.currentPath()
112 actuator.load_theme()
114 self.mini = MiniWindow(actuator, self.expand, self.config)
115 driver = MiniDriver(self.wallet, self.mini)
117 # Reset path back to original value now that loading the GUI
119 QDir.setCurrent(old_path)
126 self.expert = gui_qt.ElectrumWindow(self.wallet, self.config)
127 self.expert.app = self.app
128 self.expert.connect_slots(timer)
129 self.expert.update_wallet()
133 """Hide the lite mode window and show pro-mode."""
137 def set_url(self, url):
138 payto, amount, label, message, signature, identity, url = \
139 self.wallet.parse_url(url, self.show_message, self.show_question)
140 self.mini.set_payment_fields(payto, amount)
142 def show_message(self, message):
143 QMessageBox.information(self.mini, _("Message"), message, _("OK"))
145 def show_question(self, message):
146 choice = QMessageBox.question(self.mini, _("Message"), message,
147 QMessageBox.Yes|QMessageBox.No,
149 return choice == QMessageBox.Yes
151 def restore_or_create(self):
152 qt_gui_object = gui_qt.ElectrumGui(self.wallet, self.app)
153 return qt_gui_object.restore_or_create()
155 class MiniWindow(QDialog):
157 def __init__(self, actuator, expand_callback, config):
158 super(MiniWindow, self).__init__()
160 self.actuator = actuator
163 self.btc_balance = None
164 self.quote_currencies = ["EUR", "USD", "GBP"]
165 self.actuator.set_configured_currency(self.set_quote_currency)
166 self.exchanger = exchange_rate.Exchanger(self)
167 # Needed because price discovery is done in a different thread
168 # which needs to be sent back to this main one to update the GUI
169 self.connect(self, SIGNAL("refresh_balance()"), self.refresh_balance)
171 self.balance_label = BalanceLabel(self.change_quote_currency)
172 self.balance_label.setObjectName("balance_label")
174 self.receive_button = QPushButton(_("&Receive"))
175 self.receive_button.setObjectName("receive_button")
176 self.receive_button.setDefault(True)
177 self.receive_button.clicked.connect(self.copy_address)
179 # Bitcoin address code
180 self.address_input = QLineEdit()
181 self.address_input.setPlaceholderText(_("Enter a Bitcoin address..."))
182 self.address_input.setObjectName("address_input")
185 self.address_input.textEdited.connect(self.address_field_changed)
186 resize_line_edit_width(self.address_input,
187 "1BtaFUr3qVvAmwrsuDuu5zk6e4s2rxd2Gy")
189 self.address_completions = QStringListModel()
190 address_completer = QCompleter(self.address_input)
191 address_completer.setCaseSensitivity(False)
192 address_completer.setModel(self.address_completions)
193 self.address_input.setCompleter(address_completer)
195 address_layout = QHBoxLayout()
196 address_layout.addWidget(self.address_input)
198 self.amount_input = QLineEdit()
199 self.amount_input.setPlaceholderText(_("... and amount"))
200 self.amount_input.setObjectName("amount_input")
201 # This is changed according to the user's displayed balance
202 self.amount_validator = QDoubleValidator(self.amount_input)
203 self.amount_validator.setNotation(QDoubleValidator.StandardNotation)
204 self.amount_validator.setDecimals(8)
205 self.amount_input.setValidator(self.amount_validator)
207 # This removes the very ugly OSX highlighting, please leave this in :D
208 self.address_input.setAttribute(Qt.WA_MacShowFocusRect, 0)
209 self.amount_input.setAttribute(Qt.WA_MacShowFocusRect, 0)
210 self.amount_input.textChanged.connect(self.amount_input_changed)
212 self.send_button = QPushButton(_("&Send"))
213 self.send_button.setObjectName("send_button")
214 self.send_button.setDisabled(True);
215 self.send_button.clicked.connect(self.send)
217 main_layout = QGridLayout(self)
219 main_layout.addWidget(self.balance_label, 0, 0)
220 main_layout.addWidget(self.receive_button, 0, 1)
222 main_layout.addWidget(self.address_input, 1, 0, 1, -1)
224 main_layout.addWidget(self.amount_input, 2, 0)
225 main_layout.addWidget(self.send_button, 2, 1)
227 self.history_list = history_widget.HistoryWidget()
228 self.history_list.setObjectName("history")
229 self.history_list.hide()
230 self.history_list.setAlternatingRowColors(True)
231 main_layout.addWidget(self.history_list, 3, 0, 1, -1)
234 electrum_menu = menubar.addMenu(_("&Bitcoin"))
236 electrum_menu.addSeparator()
238 brain_seed = electrum_menu.addAction(_("&BrainWallet Info"))
239 brain_seed.triggered.connect(self.actuator.show_seed_dialog)
240 quit_option = electrum_menu.addAction(_("&Quit"))
241 quit_option.triggered.connect(self.close)
243 view_menu = menubar.addMenu(_("&View"))
244 extra_menu = menubar.addMenu(_("&Extra"))
246 backup_wallet = extra_menu.addAction( _("&Create wallet backup"))
247 backup_wallet.triggered.connect(self.backup_wallet)
249 export_csv = extra_menu.addAction( _("&Export transactions to CSV") )
250 export_csv.triggered.connect(self.actuator.csv_transaction)
252 expert_gui = view_menu.addAction(_("&Classic GUI"))
253 expert_gui.triggered.connect(expand_callback)
254 themes_menu = view_menu.addMenu(_("&Themes"))
255 selected_theme = self.actuator.selected_theme()
256 theme_group = QActionGroup(self)
257 for theme_name in self.actuator.theme_names():
258 theme_action = themes_menu.addAction(theme_name)
259 theme_action.setCheckable(True)
260 if selected_theme == theme_name:
261 theme_action.setChecked(True)
262 class SelectThemeFunctor:
263 def __init__(self, theme_name, toggle_theme):
264 self.theme_name = theme_name
265 self.toggle_theme = toggle_theme
266 def __call__(self, checked):
268 self.toggle_theme(self.theme_name)
269 delegate = SelectThemeFunctor(theme_name, self.toggle_theme)
270 theme_action.toggled.connect(delegate)
271 theme_group.addAction(theme_action)
272 view_menu.addSeparator()
273 show_history = view_menu.addAction(_("Show History"))
274 show_history.setCheckable(True)
275 show_history.toggled.connect(self.show_history)
277 help_menu = menubar.addMenu(_("&Help"))
278 the_website = help_menu.addAction(_("&Website"))
279 the_website.triggered.connect(self.the_website)
280 help_menu.addSeparator()
281 report_bug = help_menu.addAction(_("&Report Bug"))
282 report_bug.triggered.connect(self.show_report_bug)
283 show_about = help_menu.addAction(_("&About"))
284 show_about.triggered.connect(self.show_about)
285 main_layout.setMenuBar(menubar)
287 quit_shortcut = QShortcut(QKeySequence("Ctrl+Q"), self)
288 quit_shortcut.activated.connect(self.close)
289 close_shortcut = QShortcut(QKeySequence("Ctrl+W"), self)
290 close_shortcut.activated.connect(self.close)
292 g = self.config.get("winpos-lite",[4, 25, 351, 149])
293 self.setGeometry(g[0], g[1], g[2], g[3])
295 show_hist = self.config.get("gui_show_history",False)
296 show_history.setChecked(show_hist)
297 self.show_history(show_hist)
299 self.setWindowIcon(QIcon(":electrum.png"))
300 self.setWindowTitle("Electrum")
301 self.setWindowFlags(Qt.Window|Qt.MSWindowsFixedSizeDialogHint)
302 self.layout().setSizeConstraint(QLayout.SetFixedSize)
303 self.setObjectName("main_window")
306 def toggle_theme(self, theme_name):
307 old_path = QDir.currentPath()
308 self.actuator.change_theme(theme_name)
309 # Recompute style globally
310 qApp.style().unpolish(self)
311 qApp.style().polish(self)
312 QDir.setCurrent(old_path)
314 def closeEvent(self, event):
316 self.config.set_key("winpos-lite", [g.left(),g.top(),g.width(),g.height()],True)
317 self.config.set_key("gui_show_history", self.history_list.isVisible(),True)
319 super(MiniWindow, self).closeEvent(event)
322 def set_payment_fields(self, dest_address, amount):
323 self.address_input.setText(dest_address)
324 self.address_field_changed(dest_address)
325 self.amount_input.setText(amount)
330 def deactivate(self):
333 def set_quote_currency(self, currency):
334 """Set and display the fiat currency country."""
335 assert currency in self.quote_currencies
336 self.quote_currencies.remove(currency)
337 self.quote_currencies.insert(0, currency)
338 self.refresh_balance()
340 def change_quote_currency(self):
341 self.quote_currencies = \
342 self.quote_currencies[1:] + self.quote_currencies[0:1]
343 self.actuator.set_config_currency(self.quote_currencies[0])
344 self.refresh_balance()
346 def refresh_balance(self):
347 if self.btc_balance is None:
348 # Price has been discovered before wallet has been loaded
349 # and server connect... so bail.
351 self.set_balances(self.btc_balance)
352 self.amount_input_changed(self.amount_input.text())
354 def set_balances(self, btc_balance):
355 """Set the bitcoin balance and update the amount label accordingly."""
356 self.btc_balance = btc_balance
357 quote_text = self.create_quote_text(btc_balance)
359 quote_text = "(%s)" % quote_text
360 btc_balance = "%.2f" % (btc_balance / bitcoin(1))
361 self.balance_label.set_balance_text(btc_balance, quote_text)
362 self.setWindowTitle("Electrum - %s BTC" % btc_balance)
364 def amount_input_changed(self, amount_text):
365 """Update the number of bitcoins displayed."""
366 self.check_button_status()
369 amount = D(str(amount_text))
370 except decimal.InvalidOperation:
371 self.balance_label.show_balance()
373 quote_text = self.create_quote_text(amount * bitcoin(1))
375 self.balance_label.set_amount_text(quote_text)
376 self.balance_label.show_amount()
378 self.balance_label.show_balance()
380 def create_quote_text(self, btc_balance):
381 """Return a string copy of the amount fiat currency the
382 user has in bitcoins."""
383 quote_currency = self.quote_currencies[0]
384 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
385 if quote_balance is None:
388 quote_text = "%.2f %s" % ((quote_balance / bitcoin(1)),
393 if self.actuator.send(self.address_input.text(),
394 self.amount_input.text(), self):
395 self.address_input.setText("")
396 self.amount_input.setText("")
398 def check_button_status(self):
399 """Check that the bitcoin address is valid and that something
400 is entered in the amount before making the send button clickable."""
402 value = D(str(self.amount_input.text())) * 10**8
403 except decimal.InvalidOperation:
405 # self.address_input.property(...) returns a qVariant, not a bool.
406 # The == is needed to properly invoke a comparison.
407 if (self.address_input.property("isValid") == True and
408 value is not None and 0 < value <= self.btc_balance):
409 self.send_button.setDisabled(False)
411 self.send_button.setDisabled(True)
413 def address_field_changed(self, address):
414 if self.actuator.is_valid(address):
415 self.check_button_status()
416 self.address_input.setProperty("isValid", True)
417 self.recompute_style(self.address_input)
419 self.send_button.setDisabled(True)
420 self.address_input.setProperty("isValid", False)
421 self.recompute_style(self.address_input)
423 if len(address) == 0:
424 self.address_input.setProperty("isValid", None)
425 self.recompute_style(self.address_input)
427 def recompute_style(self, element):
428 self.style().unpolish(element)
429 self.style().polish(element)
431 def copy_address(self):
432 receive_popup = ReceivePopup(self.receive_button)
433 self.actuator.copy_address(receive_popup)
435 def update_completions(self, completions):
436 self.address_completions.setStringList(completions)
438 def update_history(self, tx_history):
439 from util import format_satoshis
440 for item in tx_history[-10:]:
441 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
442 label = self.actuator.wallet.get_label(tx_hash)[0]
443 #amount = D(value) / 10**8
444 v_str = format_satoshis(value, True)
445 self.history_list.append(label, v_str)
448 self.actuator.acceptbit(self.quote_currencies[0])
450 def the_website(self):
451 webbrowser.open("http://electrum-desktop.com")
453 def show_about(self):
454 QMessageBox.about(self, "Electrum",
455 _("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.\n\nSend donations to 1JwTMv4GWaPdf931N6LNPJeZBfZgZJ3zX1"))
457 def show_report_bug(self):
458 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
459 _("Email bug reports to %s") % "genjix" + "@" + "riseup.net")
461 def show_history(self, toggle_state):
463 self.history_list.show()
465 self.history_list.hide()
467 def backup_wallet(self):
469 folderName = QFileDialog.getExistingDirectory(QWidget(), 'Select folder to save a copy of your wallet to', os.path.expanduser('~/'))
471 sourceFile = util.user_dir() + '/electrum.dat'
472 shutil.copy2(sourceFile, str(folderName))
473 QMessageBox.information(None,"Wallet backup created", "A copy of your wallet file was created in '%s'" % str(folderName))
474 except (IOError, os.error), reason:
475 QMessageBox.critical(None,"Unable to create backup", "Electrum was unable copy your wallet file to the specified location.\n" + str(reason))
480 class BalanceLabel(QLabel):
486 def __init__(self, change_quote_currency, parent=None):
487 super(QLabel, self).__init__(_("Connecting..."), parent)
488 self.change_quote_currency = change_quote_currency
489 self.state = self.SHOW_CONNECTING
490 self.balance_text = ""
491 self.amount_text = ""
493 def mousePressEvent(self, event):
494 """Change the fiat currency selection if window background is clicked."""
495 if self.state != self.SHOW_CONNECTING:
496 self.change_quote_currency()
498 def set_balance_text(self, btc_balance, quote_text):
499 """Set the amount of bitcoins in the gui."""
500 if self.state == self.SHOW_CONNECTING:
501 self.state = self.SHOW_BALANCE
502 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)
503 if self.state == self.SHOW_BALANCE:
504 self.setText(self.balance_text)
506 def set_amount_text(self, quote_text):
507 self.amount_text = "<span style='font-size: 10pt'>%s</span>" % quote_text
508 if self.state == self.SHOW_AMOUNT:
509 self.setText(self.amount_text)
511 def show_balance(self):
512 if self.state == self.SHOW_AMOUNT:
513 self.state = self.SHOW_BALANCE
514 self.setText(self.balance_text)
516 def show_amount(self):
517 if self.state == self.SHOW_BALANCE:
518 self.state = self.SHOW_AMOUNT
519 self.setText(self.amount_text)
521 def ok_cancel_buttons(dialog):
522 row_layout = QHBoxLayout()
523 row_layout.addStretch(1)
524 ok_button = QPushButton(_("OK"))
525 row_layout.addWidget(ok_button)
526 ok_button.clicked.connect(dialog.accept)
527 cancel_button = QPushButton(_("Cancel"))
528 row_layout.addWidget(cancel_button)
529 cancel_button.clicked.connect(dialog.reject)
532 class PasswordDialog(QDialog):
534 def __init__(self, parent):
535 super(QDialog, self).__init__(parent)
539 self.password_input = QLineEdit()
540 self.password_input.setEchoMode(QLineEdit.Password)
542 main_layout = QVBoxLayout(self)
543 message = _('Please enter your password')
544 main_layout.addWidget(QLabel(message))
548 grid.addWidget(QLabel(_('Password')), 1, 0)
549 grid.addWidget(self.password_input, 1, 1)
550 main_layout.addLayout(grid)
552 main_layout.addLayout(ok_cancel_buttons(self))
553 self.setLayout(main_layout)
558 return unicode(self.password_input.text())
560 class ReceivePopup(QDialog):
562 def leaveEvent(self, event):
565 def setup(self, address):
566 label = QLabel(_("Copied your Bitcoin address to the clipboard!"))
567 address_display = QLineEdit(address)
568 address_display.setReadOnly(True)
569 resize_line_edit_width(address_display, address)
571 main_layout = QVBoxLayout(self)
572 main_layout.addWidget(label)
573 main_layout.addWidget(address_display)
575 self.setMouseTracking(True)
576 self.setWindowTitle("Electrum - " + _("Receive Bitcoin payment"))
577 self.setWindowFlags(Qt.Window|Qt.FramelessWindowHint|
578 Qt.MSWindowsFixedSizeDialogHint)
579 self.layout().setSizeConstraint(QLayout.SetFixedSize)
580 #self.setFrameStyle(QFrame.WinPanel|QFrame.Raised)
581 #self.setAlignment(Qt.AlignCenter)
584 parent = self.parent()
585 top_left_pos = parent.mapToGlobal(parent.rect().bottomLeft())
586 self.move(top_left_pos)
587 center_mouse_pos = self.mapToGlobal(self.rect().center())
588 QCursor.setPos(center_mouse_pos)
592 """Initialize the definitions relating to themes and
593 sending/recieving bitcoins."""
596 def __init__(self, wallet):
597 """Retrieve the gui theme used in previous session."""
599 self.theme_name = self.wallet.config.get('litegui_theme','Cleanlook')
600 self.themes = load_theme_paths()
602 def load_theme(self):
603 """Load theme retrieved from wallet file."""
605 theme_prefix, theme_path = self.themes[self.theme_name]
607 util.print_error("Theme not found!", self.theme_name)
609 QDir.setCurrent(os.path.join(theme_prefix, theme_path))
610 with open(rsrc("style.css")) as style_file:
611 qApp.setStyleSheet(style_file.read())
613 def theme_names(self):
615 return sorted(self.themes.keys())
617 def selected_theme(self):
619 return self.theme_name
621 def change_theme(self, theme_name):
623 self.theme_name = theme_name
624 self.wallet.config.set_key('litegui_theme',theme_name)
627 def set_configured_currency(self, set_quote_currency):
628 """Set the inital fiat currency conversion country (USD/EUR/GBP) in
629 the GUI to what it was set to in the wallet."""
630 currency = self.wallet.config.get('conversion_currency')
631 # currency can be none when Electrum is used for the first
632 # time and no setting has been created yet.
633 if currency is not None:
634 set_quote_currency(currency)
636 def set_config_currency(self, conversion_currency):
637 """Change the wallet fiat currency country."""
638 self.wallet.config.set_key('conversion_currency',conversion_currency,True)
640 def copy_address(self, receive_popup):
641 """Copy the wallet addresses into the client."""
642 addrs = [addr for addr in self.wallet.all_addresses()
643 if not self.wallet.is_change(addr)]
644 # Select most recent addresses from gap limit
645 addrs = addrs[-self.wallet.gap_limit:]
646 copied_address = random.choice(addrs)
647 qApp.clipboard().setText(copied_address)
648 receive_popup.setup(copied_address)
649 receive_popup.popup()
651 def waiting_dialog(self, f):
656 w.setWindowTitle('Electrum')
657 l = QLabel('Sending transaction, please wait.')
666 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
670 def csv_transaction(self):
672 fileName = QFileDialog.getSaveFileName(QWidget(), 'Select file to export your wallet transactions to', os.path.expanduser('~/'), "*.csv")
674 with open(fileName, "w+") as csvfile:
675 transaction = csv.writer(csvfile)
676 transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
677 for item in self.wallet.get_tx_history():
678 tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
680 if timestamp is not None:
682 time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
683 except [RuntimeError, TypeError, NameError] as reason:
684 time_string = "unknown"
687 time_string = "unknown"
689 time_string = "pending"
691 if value is not None:
692 value_string = format_satoshis(value, True, self.wallet.num_zeros)
697 fee_string = format_satoshis(fee, True, self.wallet.num_zeros)
702 label, is_default_label = self.wallet.get_label(tx_hash)
706 balance_string = format_satoshis(balance, False, self.wallet.num_zeros)
707 transaction.writerow([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
708 QMessageBox.information(None,"CSV Export created", "Your CSV export has been succesfully created.")
709 except (IOError, os.error), reason:
710 QMessageBox.critical(None,"Unable to create csv", "Electrum was unable to produce a transaction export.\n" + str(reason))
712 def send(self, address, amount, parent_window):
713 """Send bitcoins to the target address."""
714 dest_address = self.fetch_destination(address)
716 if dest_address is None or not self.wallet.is_valid(dest_address):
717 QMessageBox.warning(parent_window, _('Error'),
718 _('Invalid Bitcoin Address') + ':\n' + address, _('OK'))
721 convert_amount = lambda amount: \
722 int(D(unicode(amount)) * bitcoin(1))
723 amount = convert_amount(amount)
725 if self.wallet.use_encryption:
726 password_dialog = PasswordDialog(parent_window)
727 password = password_dialog.run()
735 if amount < bitcoin(1) / 10:
737 fee = bitcoin(1) / 1000
740 tx = self.wallet.mktx([(dest_address, amount)], "", password, fee)
741 except BaseException as error:
742 QMessageBox.warning(parent_window, _('Error'), str(error), _('OK'))
745 h = self.wallet.send_tx(tx)
747 self.waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Sending transaction, please wait..."))
749 status, message = self.wallet.receive_tx(h)
753 dumpf = tempfile.NamedTemporaryFile(delete=False)
756 print "Dumped error tx to", dumpf.name
757 QMessageBox.warning(parent_window, _('Error'), message, _('OK'))
760 QMessageBox.information(parent_window, '',
761 _('Your transaction has been sent.') + '\n' + message, _('OK'))
764 def fetch_destination(self, address):
765 recipient = unicode(address).strip()
768 match1 = re.match("^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$",
771 # label or alias, with address in brackets
772 match2 = re.match("(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>",
777 self.wallet.get_alias(recipient, True,
778 self.show_message, self.question)
781 return match2.group(2)
785 def is_valid(self, address):
786 """Check if bitcoin address is valid."""
787 return self.wallet.is_valid(address)
789 def acceptbit(self, currency):
790 master_pubkey = self.wallet.master_public_key
791 url = "http://acceptbit.com/mpk/%s/%s" % (master_pubkey, currency)
794 def show_seed_dialog(self):
795 gui_qt.ElectrumWindow.show_seed_dialog(self.wallet)
797 class MiniDriver(QObject):
804 def __init__(self, wallet, window):
805 super(QObject, self).__init__()
810 self.wallet.interface.register_callback('updated',self.update_callback)
811 self.wallet.interface.register_callback('connected', self.update_callback)
812 self.wallet.interface.register_callback('disconnected', self.update_callback)
817 self.connect(self, SIGNAL("updatesignal()"), self.update)
818 self.update_callback()
820 # This is a hack to workaround that Qt does not like changing the
821 # window properties from this other thread before the runloop has
823 def update_callback(self):
824 self.emit(SIGNAL("updatesignal()"))
827 if not self.wallet.interface:
829 elif not self.wallet.interface.is_connected:
831 elif not self.wallet.up_to_date:
836 if self.wallet.up_to_date:
837 self.update_balance()
838 self.update_completions()
839 self.update_history()
841 def initializing(self):
842 if self.state == self.INITIALIZING:
844 self.state = self.INITIALIZING
845 self.window.deactivate()
847 def connecting(self):
848 if self.state == self.CONNECTING:
850 self.state = self.CONNECTING
851 self.window.deactivate()
853 def synchronizing(self):
854 if self.state == self.SYNCHRONIZING:
856 self.state = self.SYNCHRONIZING
857 self.window.deactivate()
860 if self.state == self.READY:
862 self.state = self.READY
863 self.window.activate()
865 def update_balance(self):
866 conf_balance, unconf_balance = self.wallet.get_balance()
867 balance = D(conf_balance + unconf_balance)
868 self.window.set_balances(balance)
870 def update_completions(self):
872 for addr, label in self.wallet.labels.items():
873 if addr in self.wallet.addressbook:
874 completions.append("%s <%s>" % (label, addr))
875 completions = completions + self.wallet.aliases.keys()
876 self.window.update_completions(completions)
878 def update_history(self):
879 tx_history = self.wallet.get_tx_history()
880 self.window.update_history(tx_history)
882 if __name__ == "__main__":
883 app = QApplication(sys.argv)
884 with open(rsrc("style.css")) as style_file:
885 app.setStyleSheet(style_file.read())
887 sys.exit(app.exec_())