3 # Let's do some dep checking and handle missing ones gracefully
5 from PyQt4.QtCore import *
6 from PyQt4.QtGui import *
7 from PyQt4.Qt import Qt
8 import PyQt4.QtCore as QtCore
11 print "You need to have PyQT installed to run Electrum in graphical mode."
12 print "If you have pip installed try 'sudo pip install pyqt' if you are on Debian/Ubuntu try 'sudo apt-get install python-qt4'."
15 from decimal import Decimal as D
16 from electrum.util import get_resource_path as rsrc
17 from electrum.bitcoin import is_valid
18 from electrum.i18n import _
25 from electrum.wallet import Wallet, WalletStorage
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
36 from main_window import ElectrumWindow
41 bitcoin = lambda v: v * 100000000
43 def IconButton(filename, parent=None):
44 pixmap = QPixmap(filename)
46 return QPushButton(icon, "", parent)
51 self.emit(SIGNAL('timersignal'))
54 def resize_line_edit_width(line_edit, text_input):
55 metrics = QFontMetrics(qApp.font())
56 # Create an extra character to add some space on the end
58 line_edit.setMinimumWidth(metrics.width(text_input))
60 def load_theme_name(theme_path):
62 with open(os.path.join(theme_path, "name.cfg")) as name_cfg_file:
63 return name_cfg_file.read().rstrip("\n").strip()
68 def theme_dirs_from_prefix(prefix):
69 if not os.path.exists(prefix):
72 for potential_theme in os.listdir(prefix):
73 theme_full_path = os.path.join(prefix, potential_theme)
74 theme_css = os.path.join(theme_full_path, "style.css")
75 if not os.path.exists(theme_css):
77 theme_name = load_theme_name(theme_full_path)
78 if theme_name is None:
80 theme_paths[theme_name] = prefix, potential_theme
83 def load_theme_paths():
85 prefixes = (util.local_data_dir(), util.appdata_dir())
86 for prefix in prefixes:
87 theme_paths.update(theme_dirs_from_prefix(prefix))
91 def csv_transaction(wallet):
93 select_export = _('Select file to export your wallet transactions to')
94 fileName = QFileDialog.getSaveFileName(QWidget(), select_export, os.path.expanduser('~/electrum-history.csv'), "*.csv")
96 with open(fileName, "w+") as csvfile:
97 transaction = csv.writer(csvfile)
98 transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
99 for item in wallet.get_tx_history():
100 tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
102 if timestamp is not None:
104 time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
105 except [RuntimeError, TypeError, NameError] as reason:
106 time_string = "unknown"
109 time_string = "unknown"
111 time_string = "pending"
113 if value is not None:
114 value_string = format_satoshis(value, True)
119 fee_string = format_satoshis(fee, True)
124 label, is_default_label = wallet.get_label(tx_hash)
128 balance_string = format_satoshis(balance, False)
129 transaction.writerow([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
130 QMessageBox.information(None,_("CSV Export created"), _("Your CSV export has been successfully created."))
131 except (IOError, os.error), reason:
132 export_error_label = _("Electrum was unable to produce a transaction export.")
133 QMessageBox.critical(None,_("Unable to create csv"), export_error_label + "\n" + str(reason))
137 class TransactionWindow(QDialog):
140 label = unicode(self.label_edit.text())
141 self.parent.wallet.labels[self.tx_id] = label
143 super(TransactionWindow, self).accept()
145 def __init__(self, transaction_id, parent):
146 super(TransactionWindow, self).__init__()
148 self.tx_id = str(transaction_id)
153 self.setWindowTitle(_("Transaction successfully sent"))
155 self.layout = QGridLayout(self)
156 history_label = "%s\n%s" % (_("Your transaction has been sent."), _("Please enter a label for this transaction for future reference."))
157 self.layout.addWidget(QLabel(history_label))
159 self.label_edit = QLineEdit()
160 self.label_edit.setPlaceholderText(_("Transaction label"))
161 self.label_edit.setObjectName("label_input")
162 self.label_edit.setAttribute(Qt.WA_MacShowFocusRect, 0)
163 self.label_edit.setFocusPolicy(Qt.ClickFocus)
164 self.layout.addWidget(self.label_edit)
166 self.save_button = QPushButton(_("Save"))
167 self.layout.addWidget(self.save_button)
168 self.save_button.clicked.connect(self.set_label)
172 class MiniWindow(QDialog):
174 def __init__(self, actuator, expand_callback, config):
175 super(MiniWindow, self).__init__()
177 self.actuator = actuator
179 self.btc_balance = None
180 self.quote_currencies = ["BRL", "CNY", "EUR", "GBP", "RUB", "USD"]
181 self.actuator.set_configured_currency(self.set_quote_currency)
183 # Needed because price discovery is done in a different thread
184 # which needs to be sent back to this main one to update the GUI
185 self.connect(self, SIGNAL("refresh_balance()"), self.refresh_balance)
187 self.balance_label = BalanceLabel(self.change_quote_currency, self)
188 self.balance_label.setObjectName("balance_label")
191 # Bitcoin address code
192 self.address_input = QLineEdit()
193 self.address_input.setPlaceholderText(_("Enter a Bitcoin address or contact"))
194 self.address_input.setObjectName("address_input")
196 self.address_input.setFocusPolicy(Qt.ClickFocus)
198 self.address_input.textChanged.connect(self.address_field_changed)
199 resize_line_edit_width(self.address_input,
200 "1BtaFUr3qVvAmwrsuDuu5zk6e4s2rxd2Gy")
202 self.address_completions = QStringListModel()
203 address_completer = QCompleter(self.address_input)
204 address_completer.setCaseSensitivity(False)
205 address_completer.setModel(self.address_completions)
206 self.address_input.setCompleter(address_completer)
208 address_layout = QHBoxLayout()
209 address_layout.addWidget(self.address_input)
211 self.amount_input = QLineEdit()
212 self.amount_input.setPlaceholderText(_("... and amount") + " (%s)"%self.actuator.g.base_unit())
213 self.amount_input.setObjectName("amount_input")
215 self.amount_input.setFocusPolicy(Qt.ClickFocus)
216 # This is changed according to the user's displayed balance
217 self.amount_validator = QDoubleValidator(self.amount_input)
218 self.amount_validator.setNotation(QDoubleValidator.StandardNotation)
219 self.amount_validator.setDecimals(8)
220 self.amount_input.setValidator(self.amount_validator)
222 # This removes the very ugly OSX highlighting, please leave this in :D
223 self.address_input.setAttribute(Qt.WA_MacShowFocusRect, 0)
224 self.amount_input.setAttribute(Qt.WA_MacShowFocusRect, 0)
225 self.amount_input.textChanged.connect(self.amount_input_changed)
227 #if self.actuator.g.wallet.seed:
228 self.send_button = QPushButton(_("&Send"))
230 # self.send_button = QPushButton(_("&Create"))
232 self.send_button.setObjectName("send_button")
233 self.send_button.setDisabled(True);
234 self.send_button.clicked.connect(self.send)
236 # Creating the receive button
237 self.switch_button = QPushButton( QIcon(":icons/switchgui.png"),'' )
238 self.switch_button.setMaximumWidth(25)
239 self.switch_button.setFlat(True)
240 self.switch_button.clicked.connect(expand_callback)
242 main_layout = QGridLayout(self)
244 main_layout.addWidget(self.balance_label, 0, 0, 1, 3)
245 main_layout.addWidget(self.switch_button, 0, 3)
247 main_layout.addWidget(self.address_input, 1, 0, 1, 4)
248 main_layout.addWidget(self.amount_input, 2, 0, 1, 2)
249 main_layout.addWidget(self.send_button, 2, 2, 1, 2)
251 self.send_button.setMaximumWidth(125)
253 self.history_list = history_widget.HistoryWidget()
254 self.history_list.setObjectName("history")
255 self.history_list.hide()
256 self.history_list.setAlternatingRowColors(True)
258 main_layout.addWidget(self.history_list, 3, 0, 1, 4)
260 self.receiving = receiving_widget.ReceivingWidget(self)
261 self.receiving.setObjectName("receiving")
263 # Add to the right side
264 self.receiving_box = QGroupBox(_("Select a receiving address"))
265 extra_layout = QGridLayout()
267 # Checkbox to filter used addresses
268 hide_used = QCheckBox(_('Hide used addresses'))
269 hide_used.setChecked(True)
270 hide_used.stateChanged.connect(self.receiving.toggle_used)
272 # Events for receiving addresses
273 self.receiving.clicked.connect(self.receiving.copy_address)
274 self.receiving.itemDoubleClicked.connect(self.receiving.edit_label)
275 self.receiving.itemChanged.connect(self.receiving.update_label)
279 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)
281 extra_layout.addWidget(self.receiving, 1,0)
282 extra_layout.addWidget(hide_used, 2,0)
283 extra_layout.setColumnMinimumWidth(0,200)
285 self.receiving_box.setLayout(extra_layout)
286 main_layout.addWidget(self.receiving_box,0,4,-1,3)
287 self.receiving_box.hide()
289 self.main_layout = main_layout
291 quit_shortcut = QShortcut(QKeySequence("Ctrl+Q"), self)
292 quit_shortcut.activated.connect(self.close)
293 close_shortcut = QShortcut(QKeySequence("Ctrl+W"), self)
294 close_shortcut.activated.connect(self.close)
296 g = self.config.get("winpos-lite",[4, 25, 351, 149])
297 self.setGeometry(g[0], g[1], g[2], g[3])
299 show_hist = self.config.get("gui_show_history",False)
300 self.show_history(show_hist)
301 show_hist = self.config.get("gui_show_receiving",False)
302 self.toggle_receiving_layout(show_hist)
304 self.setWindowIcon(QIcon(":icons/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 context_menu(self):
313 themes_menu = view_menu.addMenu(_("&Themes"))
314 selected_theme = self.actuator.selected_theme()
315 theme_group = QActionGroup(self)
316 for theme_name in self.actuator.theme_names():
317 theme_action = themes_menu.addAction(theme_name)
318 theme_action.setCheckable(True)
319 if selected_theme == theme_name:
320 theme_action.setChecked(True)
321 class SelectThemeFunctor:
322 def __init__(self, theme_name, toggle_theme):
323 self.theme_name = theme_name
324 self.toggle_theme = toggle_theme
325 def __call__(self, checked):
327 self.toggle_theme(self.theme_name)
328 delegate = SelectThemeFunctor(theme_name, self.toggle_theme)
329 theme_action.toggled.connect(delegate)
330 theme_group.addAction(theme_action)
331 view_menu.addSeparator()
333 show_receiving = view_menu.addAction(_("Show Receiving addresses"))
334 show_receiving.setCheckable(True)
335 show_receiving.toggled.connect(self.toggle_receiving_layout)
336 show_receiving.setChecked(self.config.get("gui_show_receiving",False))
338 show_history = view_menu.addAction(_("Show History"))
339 show_history.setCheckable(True)
340 show_history.toggled.connect(self.show_history)
341 show_history.setChecked(self.config.get("gui_show_history",False))
347 def toggle_theme(self, theme_name):
348 old_path = QDir.currentPath()
349 self.actuator.change_theme(theme_name)
350 # Recompute style globally
351 qApp.style().unpolish(self)
352 qApp.style().polish(self)
353 QDir.setCurrent(old_path)
355 def closeEvent(self, event):
357 self.config.set_key("winpos-lite", [g.left(),g.top(),g.width(),g.height()],True)
358 self.actuator.g.closeEvent(event)
361 def set_payment_fields(self, dest_address, amount):
362 self.address_input.setText(dest_address)
363 self.address_field_changed(dest_address)
364 self.amount_input.setText(amount)
369 def deactivate(self):
372 def set_quote_currency(self, currency):
373 """Set and display the fiat currency country."""
374 if currency not in self.quote_currencies:
376 self.quote_currencies.remove(currency)
377 self.quote_currencies.insert(0, currency)
378 self.refresh_balance()
380 def change_quote_currency(self, forward=True):
382 self.quote_currencies = \
383 self.quote_currencies[1:] + self.quote_currencies[0:1]
385 self.quote_currencies = \
386 self.quote_currencies[-1:] + self.quote_currencies[0:-1]
387 self.actuator.set_config_currency(self.quote_currencies[0])
388 self.refresh_balance()
390 def refresh_balance(self):
391 if self.btc_balance is None:
392 # Price has been discovered before wallet has been loaded
393 # and server connect... so bail.
395 self.set_balances(self.btc_balance)
396 self.amount_input_changed(self.amount_input.text())
398 def set_balances(self, btc_balance):
399 """Set the bitcoin balance and update the amount label accordingly."""
400 self.btc_balance = btc_balance
401 quote_text = self.create_quote_text(btc_balance)
403 quote_text = "(%s)" % quote_text
405 amount = self.actuator.g.format_amount(btc_balance)
406 unit = self.actuator.g.base_unit()
408 self.balance_label.set_balance_text(amount, unit, quote_text)
409 self.setWindowTitle("Electrum %s - %s %s" % (electrum_version, amount, unit))
411 def amount_input_changed(self, amount_text):
412 """Update the number of bitcoins displayed."""
413 self.check_button_status()
416 amount = D(str(amount_text)) * (10**self.actuator.g.decimal_point)
417 except decimal.InvalidOperation:
418 self.balance_label.show_balance()
420 quote_text = self.create_quote_text(amount)
422 self.balance_label.set_amount_text(quote_text)
423 self.balance_label.show_amount()
425 self.balance_label.show_balance()
427 def create_quote_text(self, btc_balance):
428 """Return a string copy of the amount fiat currency the
429 user has in bitcoins."""
430 from electrum.plugins import run_hook
432 run_hook('set_quote_text', btc_balance, r)
436 if self.actuator.send(self.address_input.text(),
437 self.amount_input.text(), self):
438 self.address_input.setText("")
439 self.amount_input.setText("")
441 def check_button_status(self):
442 """Check that the bitcoin address is valid and that something
443 is entered in the amount before making the send button clickable."""
445 value = D(str(self.amount_input.text())) * (10**self.actuator.g.decimal_point)
446 except decimal.InvalidOperation:
448 # self.address_input.property(...) returns a qVariant, not a bool.
449 # The == is needed to properly invoke a comparison.
450 if (self.address_input.property("isValid") == True and
451 value is not None and 0 < value <= self.btc_balance):
452 self.send_button.setDisabled(False)
454 self.send_button.setDisabled(True)
456 def address_field_changed(self, address):
457 # label or alias, with address in brackets
458 match2 = re.match("(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>",
461 address = match2.group(2)
462 self.address_input.setText(address)
464 if is_valid(address):
465 self.check_button_status()
466 self.address_input.setProperty("isValid", True)
467 self.recompute_style(self.address_input)
469 self.send_button.setDisabled(True)
470 self.address_input.setProperty("isValid", False)
471 self.recompute_style(self.address_input)
473 if len(address) == 0:
474 self.address_input.setProperty("isValid", None)
475 self.recompute_style(self.address_input)
477 def recompute_style(self, element):
478 self.style().unpolish(element)
479 self.style().polish(element)
481 def copy_address(self):
482 receive_popup = ReceivePopup(self.receive_button)
483 self.actuator.copy_address(receive_popup)
485 def update_completions(self, completions):
486 self.address_completions.setStringList(completions)
489 def update_history(self, tx_history):
491 self.history_list.empty()
493 for item in tx_history[-10:]:
494 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
495 label = self.actuator.g.wallet.get_label(tx_hash)[0]
496 v_str = self.actuator.g.format_amount(value, True)
497 self.history_list.append(label, v_str, age(timestamp))
500 def the_website(self):
501 webbrowser.open("http://electrum.org")
504 def toggle_receiving_layout(self, toggle_state):
506 self.receiving_box.show()
508 self.receiving_box.hide()
509 self.config.set_key("gui_show_receiving", toggle_state)
511 def show_history(self, toggle_state):
513 self.main_layout.setRowMinimumHeight(3,200)
514 self.history_list.show()
516 self.main_layout.setRowMinimumHeight(3,0)
517 self.history_list.hide()
518 self.config.set_key("gui_show_history", toggle_state)
520 class BalanceLabel(QLabel):
526 def __init__(self, change_quote_currency, parent=None):
527 super(QLabel, self).__init__(_("Connecting..."), parent)
528 self.change_quote_currency = change_quote_currency
529 self.state = self.SHOW_CONNECTING
530 self.balance_text = ""
531 self.amount_text = ""
534 def mousePressEvent(self, event):
535 """Change the fiat currency selection if window background is clicked."""
536 if self.state != self.SHOW_CONNECTING:
537 if event.button() == Qt.LeftButton:
538 self.change_quote_currency()
540 position = event.globalPos()
541 menu = self.parent.context_menu()
545 def set_balance_text(self, amount, unit, quote_text):
546 """Set the amount of bitcoins in the gui."""
547 if self.state == self.SHOW_CONNECTING:
548 self.state = self.SHOW_BALANCE
550 self.balance_text = "<span style='font-size: 18pt'>%s</span>"%amount\
551 + " <span style='font-size: 10pt'>%s</span>" % unit \
552 + " <span style='font-size: 10pt'>%s</span>" % quote_text
554 if self.state == self.SHOW_BALANCE:
555 self.setText(self.balance_text)
557 def set_amount_text(self, quote_text):
558 self.amount_text = "<span style='font-size: 10pt'>%s</span>" % quote_text
559 if self.state == self.SHOW_AMOUNT:
560 self.setText(self.amount_text)
562 def show_balance(self):
563 if self.state == self.SHOW_AMOUNT:
564 self.state = self.SHOW_BALANCE
565 self.setText(self.balance_text)
567 def show_amount(self):
568 if self.state == self.SHOW_BALANCE:
569 self.state = self.SHOW_AMOUNT
570 self.setText(self.amount_text)
572 def ok_cancel_buttons(dialog):
573 row_layout = QHBoxLayout()
574 row_layout.addStretch(1)
575 ok_button = QPushButton(_("OK"))
576 row_layout.addWidget(ok_button)
577 ok_button.clicked.connect(dialog.accept)
578 cancel_button = QPushButton(_("Cancel"))
579 row_layout.addWidget(cancel_button)
580 cancel_button.clicked.connect(dialog.reject)
583 class PasswordDialog(QDialog):
585 def __init__(self, parent):
586 super(QDialog, self).__init__(parent)
590 self.password_input = QLineEdit()
591 self.password_input.setEchoMode(QLineEdit.Password)
593 main_layout = QVBoxLayout(self)
594 message = _('Please enter your password')
595 main_layout.addWidget(QLabel(message))
599 grid.addWidget(QLabel(_('Password')), 1, 0)
600 grid.addWidget(self.password_input, 1, 1)
601 main_layout.addLayout(grid)
603 main_layout.addLayout(ok_cancel_buttons(self))
604 self.setLayout(main_layout)
609 return unicode(self.password_input.text())
611 class ReceivePopup(QDialog):
613 def leaveEvent(self, event):
616 def setup(self, address):
617 label = QLabel(_("Copied your Bitcoin address to the clipboard!"))
618 address_display = QLineEdit(address)
619 address_display.setReadOnly(True)
620 resize_line_edit_width(address_display, address)
622 main_layout = QVBoxLayout(self)
623 main_layout.addWidget(label)
624 main_layout.addWidget(address_display)
626 self.setMouseTracking(True)
627 self.setWindowTitle("Electrum - " + _("Receive Bitcoin payment"))
628 self.setWindowFlags(Qt.Window|Qt.FramelessWindowHint|
629 Qt.MSWindowsFixedSizeDialogHint)
630 self.layout().setSizeConstraint(QLayout.SetFixedSize)
631 #self.setFrameStyle(QFrame.WinPanel|QFrame.Raised)
632 #self.setAlignment(Qt.AlignCenter)
635 parent = self.parent()
636 top_left_pos = parent.mapToGlobal(parent.rect().bottomLeft())
637 self.move(top_left_pos)
638 center_mouse_pos = self.mapToGlobal(self.rect().center())
639 QCursor.setPos(center_mouse_pos)
643 """Initialize the definitions relating to themes and
644 sending/receiving bitcoins."""
647 def __init__(self, main_window):
648 """Retrieve the gui theme used in previous session."""
650 self.theme_name = self.g.config.get('litegui_theme','Cleanlook')
651 self.themes = load_theme_paths()
653 def load_theme(self):
654 """Load theme retrieved from wallet file."""
656 theme_prefix, theme_path = self.themes[self.theme_name]
658 util.print_error("Theme not found!", self.theme_name)
660 QDir.setCurrent(os.path.join(theme_prefix, theme_path))
661 with open(rsrc("style.css")) as style_file:
662 qApp.setStyleSheet(style_file.read())
664 def theme_names(self):
666 return sorted(self.themes.keys())
668 def selected_theme(self):
670 return self.theme_name
672 def change_theme(self, theme_name):
674 self.theme_name = theme_name
675 self.g.config.set_key('litegui_theme',theme_name)
678 def set_configured_currency(self, set_quote_currency):
679 """Set the inital fiat currency conversion country (USD/EUR/GBP) in
680 the GUI to what it was set to in the wallet."""
681 currency = self.g.config.get('currency')
682 # currency can be none when Electrum is used for the first
683 # time and no setting has been created yet.
684 if currency is not None:
685 set_quote_currency(currency)
687 def set_config_currency(self, conversion_currency):
688 """Change the wallet fiat currency country."""
689 self.g.config.set_key('currency',conversion_currency,True)
690 self.g.update_status()
692 def copy_address(self, receive_popup):
693 """Copy the wallet addresses into the client."""
694 addrs = [addr for addr in self.g.wallet.addresses(True)
695 if not self.g.wallet.is_change(addr)]
696 # Select most recent addresses from gap limit
697 addrs = addrs[-self.g.wallet.gap_limit:]
698 copied_address = random.choice(addrs)
699 qApp.clipboard().setText(copied_address)
700 receive_popup.setup(copied_address)
701 receive_popup.popup()
703 def waiting_dialog(self, f):
708 w.setWindowTitle('Electrum')
709 l = QLabel(_('Sending transaction, please wait.'))
718 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
723 def send(self, address, amount, parent_window):
724 """Send bitcoins to the target address."""
725 dest_address = self.fetch_destination(address)
727 if dest_address is None or not is_valid(dest_address):
728 QMessageBox.warning(parent_window, _('Error'),
729 _('Invalid Bitcoin Address') + ':\n' + address, _('OK'))
732 amount = D(unicode(amount)) * (10*self.g.decimal_point)
733 print "amount", amount
736 if self.g.wallet.use_encryption:
737 password_dialog = PasswordDialog(parent_window)
738 password = password_dialog.run()
746 if amount < bitcoin(1) / 10:
748 fee = bitcoin(1) / 1000
751 tx = self.g.wallet.mktx([(dest_address, amount)], password, fee)
752 except Exception as error:
753 QMessageBox.warning(parent_window, _('Error'), str(error), _('OK'))
757 h = self.g.wallet.send_tx(tx)
759 self.waiting_dialog(lambda: False if self.g.wallet.tx_event.isSet() else _("Sending transaction, please wait..."))
761 status, message = self.g.wallet.receive_tx(h)
765 dumpf = tempfile.NamedTemporaryFile(delete=False)
768 print "Dumped error tx to", dumpf.name
769 QMessageBox.warning(parent_window, _('Error'), message, _('OK'))
772 TransactionWindow(message, self)
774 filename = 'unsigned_tx_%s' % (time.mktime(time.gmtime()))
776 fileName = QFileDialog.getSaveFileName(QWidget(), _("Select a transaction filename"), os.path.expanduser('~/%s' % (filename)))
777 with open(fileName,'w') as f:
778 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
779 QMessageBox.information(QWidget(), _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
780 except Exception as e:
781 QMessageBox.warning(QWidget(), _('Error'), _('Could not write transaction to file: %s' % e), _('OK'))
784 def fetch_destination(self, address):
785 recipient = unicode(address).strip()
788 match1 = re.match("^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$",
791 # label or alias, with address in brackets
792 match2 = re.match("(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>",
797 self.g.wallet.get_alias(recipient, True,
798 self.show_message, self.question)
801 return match2.group(2)
809 class MiniDriver(QObject):
816 def __init__(self, main_window, mini_window):
817 super(QObject, self).__init__()
820 self.network = main_window.network
821 self.window = mini_window
824 self.network.register_callback('updated',self.update_callback)
825 self.network.register_callback('connected', self.update_callback)
826 self.network.register_callback('disconnected', self.update_callback)
831 self.connect(self, SIGNAL("updatesignal()"), self.update)
832 self.update_callback()
834 # This is a hack to workaround that Qt does not like changing the
835 # window properties from this other thread before the runloop has
837 def update_callback(self):
838 self.emit(SIGNAL("updatesignal()"))
843 elif not self.network.interface:
845 elif not self.network.interface.is_connected:
848 if self.g.wallet is None:
850 elif not self.g.wallet.up_to_date:
854 self.update_balance()
855 self.update_completions()
856 self.update_history()
857 self.window.receiving.update_list()
860 def initializing(self):
861 if self.state == self.INITIALIZING:
863 self.state = self.INITIALIZING
864 self.window.deactivate()
866 def connecting(self):
867 if self.state == self.CONNECTING:
869 self.state = self.CONNECTING
870 self.window.deactivate()
872 def synchronizing(self):
873 if self.state == self.SYNCHRONIZING:
875 self.state = self.SYNCHRONIZING
876 self.window.deactivate()
879 if self.state == self.READY:
881 self.state = self.READY
882 self.window.activate()
884 def update_balance(self):
885 conf_balance, unconf_balance = self.g.wallet.get_balance()
886 balance = D(conf_balance + unconf_balance)
887 self.window.set_balances(balance)
889 def update_completions(self):
891 for addr, label in self.g.wallet.labels.items():
892 if addr in self.g.wallet.addressbook:
893 completions.append("%s <%s>" % (label, addr))
894 self.window.update_completions(completions)
896 def update_history(self):
897 tx_history = self.g.wallet.get_tx_history()
898 self.window.update_history(tx_history)
901 if __name__ == "__main__":
902 app = QApplication(sys.argv)
903 with open(rsrc("style.css")) as style_file:
904 app.setStyleSheet(style_file.read())
906 sys.exit(app.exec_())