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 interface import DEFAULT_SERVERS
19 from util import get_resource_path as rsrc
35 bitcoin = lambda v: v * 100000000
37 def IconButton(filename, parent=None):
38 pixmap = QPixmap(filename)
40 return QPushButton(icon, "", parent)
45 self.emit(SIGNAL('timersignal'))
48 def resize_line_edit_width(line_edit, text_input):
49 metrics = QFontMetrics(qApp.font())
50 # Create an extra character to add some space on the end
52 line_edit.setMinimumWidth(metrics.width(text_input))
54 def load_theme_name(theme_path):
56 with open(os.path.join(theme_path, "name.cfg")) as name_cfg_file:
57 return name_cfg_file.read().rstrip("\n").strip()
62 def theme_dirs_from_prefix(prefix):
63 if not os.path.exists(prefix):
66 for potential_theme in os.listdir(prefix):
67 theme_full_path = os.path.join(prefix, potential_theme)
68 theme_css = os.path.join(theme_full_path, "style.css")
69 if not os.path.exists(theme_css):
71 theme_name = load_theme_name(theme_full_path)
72 if theme_name is None:
74 theme_paths[theme_name] = prefix, potential_theme
77 def load_theme_paths():
79 prefixes = (util.local_data_dir(), util.appdata_dir())
80 for prefix in prefixes:
81 theme_paths.update(theme_dirs_from_prefix(prefix))
85 class ElectrumGui(QObject):
87 def __init__(self, wallet, config):
88 super(QObject, self).__init__()
92 self.check_qt_version()
93 self.app = QApplication(sys.argv)
94 self.wallet.interface.register_callback('peers', self.server_list_changed)
97 def check_qt_version(self):
98 qtVersion = qVersion()
99 if not(int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
100 app = QApplication(sys.argv)
101 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")
102 self.config.set_key('gui','classic',True)
107 actuator = MiniActuator(self.wallet)
108 self.connect(self, SIGNAL("updateservers()"),
109 actuator.update_servers_list)
110 # Should probably not modify the current path but instead
111 # change the behaviour of rsrc(...)
112 old_path = QDir.currentPath()
113 actuator.load_theme()
115 self.mini = MiniWindow(actuator, self.expand, self.config)
116 driver = MiniDriver(self.wallet, self.mini)
118 # Reset path back to original value now that loading the GUI
120 QDir.setCurrent(old_path)
127 self.expert = gui_qt.ElectrumWindow(self.wallet, self.config)
128 self.expert.app = self.app
129 self.expert.connect_slots(timer)
130 self.expert.update_wallet()
133 def server_list_changed(self):
134 self.emit(SIGNAL("updateservers()"))
137 """Hide the lite mode window and show pro-mode."""
141 def set_url(self, url):
142 payto, amount, label, message, signature, identity, url = \
143 self.wallet.parse_url(url, self.show_message, self.show_question)
144 self.mini.set_payment_fields(payto, amount)
146 def show_message(self, message):
147 QMessageBox.information(self.mini, _("Message"), message, _("OK"))
149 def show_question(self, message):
150 choice = QMessageBox.question(self.mini, _("Message"), message,
151 QMessageBox.Yes|QMessageBox.No,
153 return choice == QMessageBox.Yes
155 def restore_or_create(self):
156 qt_gui_object = gui_qt.ElectrumGui(self.wallet, self.app)
157 return qt_gui_object.restore_or_create()
159 class MiniWindow(QDialog):
161 def __init__(self, actuator, expand_callback, config):
162 super(MiniWindow, self).__init__()
164 self.actuator = actuator
167 self.btc_balance = None
168 self.quote_currencies = ["EUR", "USD", "GBP"]
169 self.actuator.set_configured_currency(self.set_quote_currency)
170 self.exchanger = exchange_rate.Exchanger(self)
171 # Needed because price discovery is done in a different thread
172 # which needs to be sent back to this main one to update the GUI
173 self.connect(self, SIGNAL("refresh_balance()"), self.refresh_balance)
175 self.balance_label = BalanceLabel(self.change_quote_currency)
176 self.balance_label.setObjectName("balance_label")
178 self.receive_button = QPushButton(_("&Receive"))
179 self.receive_button.setObjectName("receive_button")
180 self.receive_button.setDefault(True)
181 self.receive_button.clicked.connect(self.copy_address)
183 # Bitcoin address code
184 self.address_input = QLineEdit()
185 self.address_input.setPlaceholderText(_("Enter a Bitcoin address..."))
186 self.address_input.setObjectName("address_input")
189 self.address_input.textEdited.connect(self.address_field_changed)
190 resize_line_edit_width(self.address_input,
191 "1BtaFUr3qVvAmwrsuDuu5zk6e4s2rxd2Gy")
193 self.address_completions = QStringListModel()
194 address_completer = QCompleter(self.address_input)
195 address_completer.setCaseSensitivity(False)
196 address_completer.setModel(self.address_completions)
197 self.address_input.setCompleter(address_completer)
199 address_layout = QHBoxLayout()
200 address_layout.addWidget(self.address_input)
202 self.amount_input = QLineEdit()
203 self.amount_input.setPlaceholderText(_("... and amount"))
204 self.amount_input.setObjectName("amount_input")
205 # This is changed according to the user's displayed balance
206 self.amount_validator = QDoubleValidator(self.amount_input)
207 self.amount_validator.setNotation(QDoubleValidator.StandardNotation)
208 self.amount_validator.setDecimals(8)
209 self.amount_input.setValidator(self.amount_validator)
211 # This removes the very ugly OSX highlighting, please leave this in :D
212 self.address_input.setAttribute(Qt.WA_MacShowFocusRect, 0)
213 self.amount_input.setAttribute(Qt.WA_MacShowFocusRect, 0)
214 self.amount_input.textChanged.connect(self.amount_input_changed)
216 self.send_button = QPushButton(_("&Send"))
217 self.send_button.setObjectName("send_button")
218 self.send_button.setDisabled(True);
219 self.send_button.clicked.connect(self.send)
221 main_layout = QGridLayout(self)
223 main_layout.addWidget(self.balance_label, 0, 0)
224 main_layout.addWidget(self.receive_button, 0, 1)
226 main_layout.addWidget(self.address_input, 1, 0, 1, -1)
228 main_layout.addWidget(self.amount_input, 2, 0)
229 main_layout.addWidget(self.send_button, 2, 1)
231 self.history_list = history_widget.HistoryWidget()
232 self.history_list.setObjectName("history")
233 self.history_list.hide()
234 self.history_list.setAlternatingRowColors(True)
235 main_layout.addWidget(self.history_list, 3, 0, 1, -1)
238 electrum_menu = menubar.addMenu(_("&Bitcoin"))
240 servers_menu = electrum_menu.addMenu(_("&Servers"))
241 servers_group = QActionGroup(self)
242 self.actuator.set_servers_gui_stuff(servers_menu, servers_group)
243 self.actuator.populate_servers_menu()
244 electrum_menu.addSeparator()
246 brain_seed = electrum_menu.addAction(_("&BrainWallet Info"))
247 brain_seed.triggered.connect(self.actuator.show_seed_dialog)
248 quit_option = electrum_menu.addAction(_("&Quit"))
249 quit_option.triggered.connect(self.close)
251 view_menu = menubar.addMenu(_("&View"))
252 extra_menu = menubar.addMenu(_("&Extra"))
254 backup_wallet = extra_menu.addAction( _("&Create wallet backup"))
255 backup_wallet.triggered.connect(self.backup_wallet)
257 expert_gui = view_menu.addAction(_("&Classic GUI"))
258 expert_gui.triggered.connect(expand_callback)
259 themes_menu = view_menu.addMenu(_("&Themes"))
260 selected_theme = self.actuator.selected_theme()
261 theme_group = QActionGroup(self)
262 for theme_name in self.actuator.theme_names():
263 theme_action = themes_menu.addAction(theme_name)
264 theme_action.setCheckable(True)
265 if selected_theme == theme_name:
266 theme_action.setChecked(True)
267 class SelectThemeFunctor:
268 def __init__(self, theme_name, toggle_theme):
269 self.theme_name = theme_name
270 self.toggle_theme = toggle_theme
271 def __call__(self, checked):
273 self.toggle_theme(self.theme_name)
274 delegate = SelectThemeFunctor(theme_name, self.toggle_theme)
275 theme_action.toggled.connect(delegate)
276 theme_group.addAction(theme_action)
277 view_menu.addSeparator()
278 show_history = view_menu.addAction(_("Show History"))
279 show_history.setCheckable(True)
280 show_history.toggled.connect(self.show_history)
282 help_menu = menubar.addMenu(_("&Help"))
283 the_website = help_menu.addAction(_("&Website"))
284 the_website.triggered.connect(self.the_website)
285 help_menu.addSeparator()
286 report_bug = help_menu.addAction(_("&Report Bug"))
287 report_bug.triggered.connect(self.show_report_bug)
288 show_about = help_menu.addAction(_("&About"))
289 show_about.triggered.connect(self.show_about)
290 main_layout.setMenuBar(menubar)
292 quit_shortcut = QShortcut(QKeySequence("Ctrl+Q"), self)
293 quit_shortcut.activated.connect(self.close)
294 close_shortcut = QShortcut(QKeySequence("Ctrl+W"), self)
295 close_shortcut.activated.connect(self.close)
297 g = self.config.get("winpos-lite",[4, 25, 351, 149])
298 self.setGeometry(g[0], g[1], g[2], g[3])
300 show_hist = self.config.get("gui_show_history",False)
301 show_history.setChecked(show_hist)
302 self.show_history(show_hist)
304 self.setWindowIcon(QIcon(":electrum.png"))
305 self.setWindowTitle("Electrum")
306 self.setWindowFlags(Qt.Window|Qt.MSWindowsFixedSizeDialogHint)
307 self.layout().setSizeConstraint(QLayout.SetFixedSize)
308 self.setObjectName("main_window")
311 def toggle_theme(self, theme_name):
312 old_path = QDir.currentPath()
313 self.actuator.change_theme(theme_name)
314 # Recompute style globally
315 qApp.style().unpolish(self)
316 qApp.style().polish(self)
317 QDir.setCurrent(old_path)
319 def closeEvent(self, event):
321 self.config.set_key("winpos-lite", [g.left(),g.top(),g.width(),g.height()],True)
322 self.config.set_key("gui_show_history", self.history_list.isVisible(),True)
324 super(MiniWindow, self).closeEvent(event)
327 def set_payment_fields(self, dest_address, amount):
328 self.address_input.setText(dest_address)
329 self.address_field_changed(dest_address)
330 self.amount_input.setText(amount)
335 def deactivate(self):
338 def set_quote_currency(self, currency):
339 """Set and display the fiat currency country."""
340 assert currency in self.quote_currencies
341 self.quote_currencies.remove(currency)
342 self.quote_currencies.insert(0, currency)
343 self.refresh_balance()
345 def change_quote_currency(self):
346 self.quote_currencies = \
347 self.quote_currencies[1:] + self.quote_currencies[0:1]
348 self.actuator.set_config_currency(self.quote_currencies[0])
349 self.refresh_balance()
351 def refresh_balance(self):
352 if self.btc_balance is None:
353 # Price has been discovered before wallet has been loaded
354 # and server connect... so bail.
356 self.set_balances(self.btc_balance)
357 self.amount_input_changed(self.amount_input.text())
359 def set_balances(self, btc_balance):
360 """Set the bitcoin balance and update the amount label accordingly."""
361 self.btc_balance = btc_balance
362 quote_text = self.create_quote_text(btc_balance)
364 quote_text = "(%s)" % quote_text
365 btc_balance = "%.2f" % (btc_balance / bitcoin(1))
366 self.balance_label.set_balance_text(btc_balance, quote_text)
367 self.setWindowTitle("Electrum - %s BTC" % btc_balance)
369 def amount_input_changed(self, amount_text):
370 """Update the number of bitcoins displayed."""
371 self.check_button_status()
374 amount = D(str(amount_text))
375 except decimal.InvalidOperation:
376 self.balance_label.show_balance()
378 quote_text = self.create_quote_text(amount * bitcoin(1))
380 self.balance_label.set_amount_text(quote_text)
381 self.balance_label.show_amount()
383 self.balance_label.show_balance()
385 def create_quote_text(self, btc_balance):
386 """Return a string copy of the amount fiat currency the
387 user has in bitcoins."""
388 quote_currency = self.quote_currencies[0]
389 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
390 if quote_balance is None:
393 quote_text = "%.2f %s" % ((quote_balance / bitcoin(1)),
398 if self.actuator.send(self.address_input.text(),
399 self.amount_input.text(), self):
400 self.address_input.setText("")
401 self.amount_input.setText("")
403 def check_button_status(self):
404 """Check that the bitcoin address is valid and that something
405 is entered in the amount before making the send button clickable."""
407 value = D(str(self.amount_input.text())) * 10**8
408 except decimal.InvalidOperation:
410 # self.address_input.property(...) returns a qVariant, not a bool.
411 # The == is needed to properly invoke a comparison.
412 if (self.address_input.property("isValid") == True and
413 value is not None and 0 < value <= self.btc_balance):
414 self.send_button.setDisabled(False)
416 self.send_button.setDisabled(True)
418 def address_field_changed(self, address):
419 if self.actuator.is_valid(address):
420 self.check_button_status()
421 self.address_input.setProperty("isValid", True)
422 self.recompute_style(self.address_input)
424 self.send_button.setDisabled(True)
425 self.address_input.setProperty("isValid", False)
426 self.recompute_style(self.address_input)
428 if len(address) == 0:
429 self.address_input.setProperty("isValid", None)
430 self.recompute_style(self.address_input)
432 def recompute_style(self, element):
433 self.style().unpolish(element)
434 self.style().polish(element)
436 def copy_address(self):
437 receive_popup = ReceivePopup(self.receive_button)
438 self.actuator.copy_address(receive_popup)
440 def update_completions(self, completions):
441 self.address_completions.setStringList(completions)
443 def update_history(self, tx_history):
444 from util import format_satoshis
445 for item in tx_history[-10:]:
446 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
447 label = self.actuator.wallet.get_label(tx_hash)[0]
448 #amount = D(value) / 10**8
449 v_str = format_satoshis(value, True)
450 self.history_list.append(label, v_str)
453 self.actuator.acceptbit(self.quote_currencies[0])
455 def the_website(self):
456 webbrowser.open("http://electrum-desktop.com")
458 def show_about(self):
459 QMessageBox.about(self, "Electrum",
460 _("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"))
462 def show_report_bug(self):
463 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
464 _("Email bug reports to %s") % "genjix" + "@" + "riseup.net")
466 def show_history(self, toggle_state):
468 self.history_list.show()
470 self.history_list.hide()
472 def backup_wallet(self):
474 folderName = QFileDialog.getExistingDirectory(QWidget(), 'Select folder to save a copy of your wallet to', os.path.expanduser('~/'))
476 sourceFile = util.user_dir() + '/electrum.dat'
477 shutil.copy2(sourceFile, str(folderName))
478 QMessageBox.information(None,"Wallet backup created", "A copy of your wallet file was created in '%s'" % str(folderName))
479 except (IOError, os.error), reason:
480 QMessageBox.critical(None,"Unable to create backup", "Electrum was unable copy your wallet file to the specified location.\n" + str(reason))
485 class BalanceLabel(QLabel):
491 def __init__(self, change_quote_currency, parent=None):
492 super(QLabel, self).__init__(_("Connecting..."), parent)
493 self.change_quote_currency = change_quote_currency
494 self.state = self.SHOW_CONNECTING
495 self.balance_text = ""
496 self.amount_text = ""
498 def mousePressEvent(self, event):
499 """Change the fiat currency selection if window background is clicked."""
500 if self.state != self.SHOW_CONNECTING:
501 self.change_quote_currency()
503 def set_balance_text(self, btc_balance, quote_text):
504 """Set the amount of bitcoins in the gui."""
505 if self.state == self.SHOW_CONNECTING:
506 self.state = self.SHOW_BALANCE
507 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)
508 if self.state == self.SHOW_BALANCE:
509 self.setText(self.balance_text)
511 def set_amount_text(self, quote_text):
512 self.amount_text = "<span style='font-size: 10pt'>%s</span>" % quote_text
513 if self.state == self.SHOW_AMOUNT:
514 self.setText(self.amount_text)
516 def show_balance(self):
517 if self.state == self.SHOW_AMOUNT:
518 self.state = self.SHOW_BALANCE
519 self.setText(self.balance_text)
521 def show_amount(self):
522 if self.state == self.SHOW_BALANCE:
523 self.state = self.SHOW_AMOUNT
524 self.setText(self.amount_text)
526 def ok_cancel_buttons(dialog):
527 row_layout = QHBoxLayout()
528 row_layout.addStretch(1)
529 ok_button = QPushButton(_("OK"))
530 row_layout.addWidget(ok_button)
531 ok_button.clicked.connect(dialog.accept)
532 cancel_button = QPushButton(_("Cancel"))
533 row_layout.addWidget(cancel_button)
534 cancel_button.clicked.connect(dialog.reject)
537 class PasswordDialog(QDialog):
539 def __init__(self, parent):
540 super(QDialog, self).__init__(parent)
544 self.password_input = QLineEdit()
545 self.password_input.setEchoMode(QLineEdit.Password)
547 main_layout = QVBoxLayout(self)
548 message = _('Please enter your password')
549 main_layout.addWidget(QLabel(message))
553 grid.addWidget(QLabel(_('Password')), 1, 0)
554 grid.addWidget(self.password_input, 1, 1)
555 main_layout.addLayout(grid)
557 main_layout.addLayout(ok_cancel_buttons(self))
558 self.setLayout(main_layout)
563 return unicode(self.password_input.text())
565 class ReceivePopup(QDialog):
567 def leaveEvent(self, event):
570 def setup(self, address):
571 label = QLabel(_("Copied your Bitcoin address to the clipboard!"))
572 address_display = QLineEdit(address)
573 address_display.setReadOnly(True)
574 resize_line_edit_width(address_display, address)
576 main_layout = QVBoxLayout(self)
577 main_layout.addWidget(label)
578 main_layout.addWidget(address_display)
580 self.setMouseTracking(True)
581 self.setWindowTitle("Electrum - " + _("Receive Bitcoin payment"))
582 self.setWindowFlags(Qt.Window|Qt.FramelessWindowHint|
583 Qt.MSWindowsFixedSizeDialogHint)
584 self.layout().setSizeConstraint(QLayout.SetFixedSize)
585 #self.setFrameStyle(QFrame.WinPanel|QFrame.Raised)
586 #self.setAlignment(Qt.AlignCenter)
589 parent = self.parent()
590 top_left_pos = parent.mapToGlobal(parent.rect().bottomLeft())
591 self.move(top_left_pos)
592 center_mouse_pos = self.mapToGlobal(self.rect().center())
593 QCursor.setPos(center_mouse_pos)
597 """Initialize the definitions relating to themes and
598 sending/recieving bitcoins."""
601 def __init__(self, wallet):
602 """Retrieve the gui theme used in previous session."""
604 self.theme_name = self.wallet.config.get('litegui_theme','Cleanlook')
605 self.themes = load_theme_paths()
607 def load_theme(self):
608 """Load theme retrieved from wallet file."""
610 theme_prefix, theme_path = self.themes[self.theme_name]
612 util.print_error("Theme not found!", self.theme_name)
614 QDir.setCurrent(os.path.join(theme_prefix, theme_path))
615 with open(rsrc("style.css")) as style_file:
616 qApp.setStyleSheet(style_file.read())
618 def theme_names(self):
620 return sorted(self.themes.keys())
622 def selected_theme(self):
624 return self.theme_name
626 def change_theme(self, theme_name):
628 self.theme_name = theme_name
629 self.wallet.config.set_key('litegui_theme',theme_name)
632 def set_configured_currency(self, set_quote_currency):
633 """Set the inital fiat currency conversion country (USD/EUR/GBP) in
634 the GUI to what it was set to in the wallet."""
635 currency = self.wallet.config.get('conversion_currency')
636 # currency can be none when Electrum is used for the first
637 # time and no setting has been created yet.
638 if currency is not None:
639 set_quote_currency(currency)
641 def set_config_currency(self, conversion_currency):
642 """Change the wallet fiat currency country."""
643 self.wallet.config.set_key('conversion_currency',conversion_currency,True)
645 def set_servers_gui_stuff(self, servers_menu, servers_group):
646 self.servers_menu = servers_menu
647 self.servers_group = servers_group
649 def populate_servers_menu(self):
650 interface = self.wallet.interface
651 if not interface.servers:
652 print "No servers loaded yet."
653 self.servers_list = []
654 for server_string in DEFAULT_SERVERS:
655 host, port, protocol = server_string.split(':')
656 transports = [(protocol,port)]
657 self.servers_list.append((host, transports))
659 print "Servers loaded."
660 self.servers_list = interface.servers
661 server_names = [details[0] for details in self.servers_list]
662 current_server = interface.server.split(":")[0]
663 for server_name in server_names:
664 server_action = self.servers_menu.addAction(server_name)
665 server_action.setCheckable(True)
666 if server_name == current_server:
667 server_action.setChecked(True)
668 class SelectServerFunctor:
669 def __init__(self, server_name, server_selected):
670 self.server_name = server_name
671 self.server_selected = server_selected
672 def __call__(self, checked):
674 # call server_selected
675 self.server_selected(self.server_name)
676 delegate = SelectServerFunctor(server_name, self.server_selected)
677 server_action.toggled.connect(delegate)
678 self.servers_group.addAction(server_action)
680 def update_servers_list(self):
681 # Clear servers_group
682 for action in self.servers_group.actions():
683 self.servers_group.removeAction(action)
684 self.populate_servers_menu()
686 def server_selected(self, server_name):
687 match = [transports for (host, transports) in self.servers_list
688 if host == server_name]
689 assert len(match) == 1
691 # Default to TCP if available else use anything
692 # TODO: protocol should be selectable.
693 tcp_port = [port for (protocol, port) in match if protocol == "t"]
694 if len(tcp_port) == 0:
695 protocol = match[0][0]
700 server_line = "%s:%s:%s" % (server_name, port, protocol)
702 # Should this have exception handling?
703 self.wallet.interface.set_server(server_line, self.wallet.config.get("proxy"))
705 def copy_address(self, receive_popup):
706 """Copy the wallet addresses into the client."""
707 addrs = [addr for addr in self.wallet.all_addresses()
708 if not self.wallet.is_change(addr)]
709 # Select most recent addresses from gap limit
710 addrs = addrs[-self.wallet.gap_limit:]
711 copied_address = random.choice(addrs)
712 qApp.clipboard().setText(copied_address)
713 receive_popup.setup(copied_address)
714 receive_popup.popup()
716 def waiting_dialog(self, f):
721 w.setWindowTitle('Electrum')
722 l = QLabel('Sending transaction, please wait.')
731 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
735 def send(self, address, amount, parent_window):
736 """Send bitcoins to the target address."""
737 dest_address = self.fetch_destination(address)
739 if dest_address is None or not self.wallet.is_valid(dest_address):
740 QMessageBox.warning(parent_window, _('Error'),
741 _('Invalid Bitcoin Address') + ':\n' + address, _('OK'))
744 convert_amount = lambda amount: \
745 int(D(unicode(amount)) * bitcoin(1))
746 amount = convert_amount(amount)
748 if self.wallet.use_encryption:
749 password_dialog = PasswordDialog(parent_window)
750 password = password_dialog.run()
758 if amount < bitcoin(1) / 10:
760 fee = bitcoin(1) / 1000
763 tx = self.wallet.mktx(dest_address, amount, "", password, fee)
764 except BaseException as error:
765 QMessageBox.warning(parent_window, _('Error'), str(error), _('OK'))
768 h = self.wallet.send_tx(tx)
770 self.waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Sending transaction, please wait..."))
772 status, message = self.wallet.receive_tx(h)
776 dumpf = tempfile.NamedTemporaryFile(delete=False)
779 print "Dumped error tx to", dumpf.name
780 QMessageBox.warning(parent_window, _('Error'), message, _('OK'))
783 QMessageBox.information(parent_window, '',
784 _('Your transaction has been sent.') + '\n' + message, _('OK'))
787 def fetch_destination(self, address):
788 recipient = unicode(address).strip()
791 match1 = re.match("^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$",
794 # label or alias, with address in brackets
795 match2 = re.match("(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>",
800 self.wallet.get_alias(recipient, True,
801 self.show_message, self.question)
804 return match2.group(2)
808 def is_valid(self, address):
809 """Check if bitcoin address is valid."""
810 return self.wallet.is_valid(address)
812 def acceptbit(self, currency):
813 master_pubkey = self.wallet.master_public_key
814 url = "http://acceptbit.com/mpk/%s/%s" % (master_pubkey, currency)
817 def show_seed_dialog(self):
818 gui_qt.ElectrumWindow.show_seed_dialog(self.wallet)
820 class MiniDriver(QObject):
827 def __init__(self, wallet, window):
828 super(QObject, self).__init__()
833 self.wallet.interface.register_callback('updated',self.update_callback)
834 self.wallet.interface.register_callback('connected', self.update_callback)
835 self.wallet.interface.register_callback('disconnected', self.update_callback)
840 self.connect(self, SIGNAL("updatesignal()"), self.update)
841 self.update_callback()
843 # This is a hack to workaround that Qt does not like changing the
844 # window properties from this other thread before the runloop has
846 def update_callback(self):
847 self.emit(SIGNAL("updatesignal()"))
850 if not self.wallet.interface:
852 elif not self.wallet.interface.is_connected:
854 elif not self.wallet.up_to_date:
859 if self.wallet.up_to_date:
860 self.update_balance()
861 self.update_completions()
862 self.update_history()
864 def initializing(self):
865 if self.state == self.INITIALIZING:
867 self.state = self.INITIALIZING
868 self.window.deactivate()
870 def connecting(self):
871 if self.state == self.CONNECTING:
873 self.state = self.CONNECTING
874 self.window.deactivate()
876 def synchronizing(self):
877 if self.state == self.SYNCHRONIZING:
879 self.state = self.SYNCHRONIZING
880 self.window.deactivate()
883 if self.state == self.READY:
885 self.state = self.READY
886 self.window.activate()
888 def update_balance(self):
889 conf_balance, unconf_balance = self.wallet.get_balance()
890 balance = D(conf_balance + unconf_balance)
891 self.window.set_balances(balance)
893 def update_completions(self):
895 for addr, label in self.wallet.labels.items():
896 if addr in self.wallet.addressbook:
897 completions.append("%s <%s>" % (label, addr))
898 completions = completions + self.wallet.aliases.keys()
899 self.window.update_completions(completions)
901 def update_history(self):
902 tx_history = self.wallet.get_tx_history()
903 self.window.update_history(tx_history)
905 if __name__ == "__main__":
906 app = QApplication(sys.argv)
907 with open(rsrc("style.css")) as style_file:
908 app.setStyleSheet(style_file.read())
910 sys.exit(app.exec_())