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, wallet.num_zeros)
119 fee_string = format_satoshis(fee, True, wallet.num_zeros)
124 label, is_default_label = wallet.get_label(tx_hash)
128 balance_string = format_satoshis(balance, False, wallet.num_zeros)
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"))
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")
312 def context_menu(self):
314 themes_menu = view_menu.addMenu(_("&Themes"))
315 selected_theme = self.actuator.selected_theme()
316 theme_group = QActionGroup(self)
317 for theme_name in self.actuator.theme_names():
318 theme_action = themes_menu.addAction(theme_name)
319 theme_action.setCheckable(True)
320 if selected_theme == theme_name:
321 theme_action.setChecked(True)
322 class SelectThemeFunctor:
323 def __init__(self, theme_name, toggle_theme):
324 self.theme_name = theme_name
325 self.toggle_theme = toggle_theme
326 def __call__(self, checked):
328 self.toggle_theme(self.theme_name)
329 delegate = SelectThemeFunctor(theme_name, self.toggle_theme)
330 theme_action.toggled.connect(delegate)
331 theme_group.addAction(theme_action)
332 view_menu.addSeparator()
334 show_receiving = view_menu.addAction(_("Show Receiving addresses"))
335 show_receiving.setCheckable(True)
336 show_receiving.toggled.connect(self.toggle_receiving_layout)
337 show_receiving.setChecked(self.config.get("gui_show_receiving",False))
339 show_history = view_menu.addAction(_("Show History"))
340 show_history.setCheckable(True)
341 show_history.toggled.connect(self.show_history)
342 show_history.setChecked(self.config.get("gui_show_history",False))
348 def toggle_theme(self, theme_name):
349 old_path = QDir.currentPath()
350 self.actuator.change_theme(theme_name)
351 # Recompute style globally
352 qApp.style().unpolish(self)
353 qApp.style().polish(self)
354 QDir.setCurrent(old_path)
356 def closeEvent(self, event):
358 self.config.set_key("winpos-lite", [g.left(),g.top(),g.width(),g.height()],True)
360 super(MiniWindow, self).closeEvent(event)
363 def set_payment_fields(self, dest_address, amount):
364 self.address_input.setText(dest_address)
365 self.address_field_changed(dest_address)
366 self.amount_input.setText(amount)
371 def deactivate(self):
374 def set_quote_currency(self, currency):
375 """Set and display the fiat currency country."""
376 if currency not in self.quote_currencies:
378 self.quote_currencies.remove(currency)
379 self.quote_currencies.insert(0, currency)
380 self.refresh_balance()
382 def change_quote_currency(self, forward=True):
384 self.quote_currencies = \
385 self.quote_currencies[1:] + self.quote_currencies[0:1]
387 self.quote_currencies = \
388 self.quote_currencies[-1:] + self.quote_currencies[0:-1]
389 self.actuator.set_config_currency(self.quote_currencies[0])
390 self.refresh_balance()
392 def refresh_balance(self):
393 if self.btc_balance is None:
394 # Price has been discovered before wallet has been loaded
395 # and server connect... so bail.
397 self.set_balances(self.btc_balance)
398 self.amount_input_changed(self.amount_input.text())
400 def set_balances(self, btc_balance):
401 """Set the bitcoin balance and update the amount label accordingly."""
402 self.btc_balance = btc_balance
403 quote_text = self.create_quote_text(btc_balance)
405 quote_text = "(%s)" % quote_text
407 amount = self.actuator.g.format_amount(btc_balance)
408 unit = self.actuator.g.base_unit()
410 self.balance_label.set_balance_text(amount, unit, quote_text)
411 self.setWindowTitle("Electrum %s - %s %s" % (electrum_version, amount, unit))
413 def amount_input_changed(self, amount_text):
414 """Update the number of bitcoins displayed."""
415 self.check_button_status()
418 amount = D(str(amount_text))
419 except decimal.InvalidOperation:
420 self.balance_label.show_balance()
422 quote_text = self.create_quote_text(amount * bitcoin(1))
424 self.balance_label.set_amount_text(quote_text)
425 self.balance_label.show_amount()
427 self.balance_label.show_balance()
429 def create_quote_text(self, btc_balance):
430 """Return a string copy of the amount fiat currency the
431 user has in bitcoins."""
432 from electrum.plugins import run_hook
434 run_hook('set_quote_text', btc_balance, r)
438 if self.actuator.send(self.address_input.text(),
439 self.amount_input.text(), self):
440 self.address_input.setText("")
441 self.amount_input.setText("")
443 def check_button_status(self):
444 """Check that the bitcoin address is valid and that something
445 is entered in the amount before making the send button clickable."""
447 value = D(str(self.amount_input.text())) * 10**8
448 except decimal.InvalidOperation:
450 # self.address_input.property(...) returns a qVariant, not a bool.
451 # The == is needed to properly invoke a comparison.
452 if (self.address_input.property("isValid") == True and
453 value is not None and 0 < value <= self.btc_balance):
454 self.send_button.setDisabled(False)
456 self.send_button.setDisabled(True)
458 def address_field_changed(self, address):
459 # label or alias, with address in brackets
460 match2 = re.match("(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>",
463 address = match2.group(2)
464 self.address_input.setText(address)
466 if is_valid(address):
467 self.check_button_status()
468 self.address_input.setProperty("isValid", True)
469 self.recompute_style(self.address_input)
471 self.send_button.setDisabled(True)
472 self.address_input.setProperty("isValid", False)
473 self.recompute_style(self.address_input)
475 if len(address) == 0:
476 self.address_input.setProperty("isValid", None)
477 self.recompute_style(self.address_input)
479 def recompute_style(self, element):
480 self.style().unpolish(element)
481 self.style().polish(element)
483 def copy_address(self):
484 receive_popup = ReceivePopup(self.receive_button)
485 self.actuator.copy_address(receive_popup)
487 def update_completions(self, completions):
488 self.address_completions.setStringList(completions)
491 def update_history(self, tx_history):
493 self.history_list.empty()
495 for item in tx_history[-10:]:
496 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
497 label = self.actuator.g.wallet.get_label(tx_hash)[0]
498 #amount = D(value) / 10**8
499 v_str = format_satoshis(value, True)
500 self.history_list.append(label, v_str, age(timestamp))
503 def the_website(self):
504 webbrowser.open("http://electrum.org")
507 def toggle_receiving_layout(self, toggle_state):
509 self.receiving_box.show()
511 self.receiving_box.hide()
512 self.config.set_key("gui_show_receiving", toggle_state)
514 def show_history(self, toggle_state):
516 self.main_layout.setRowMinimumHeight(3,200)
517 self.history_list.show()
519 self.main_layout.setRowMinimumHeight(3,0)
520 self.history_list.hide()
521 self.config.set_key("gui_show_history", toggle_state)
523 class BalanceLabel(QLabel):
529 def __init__(self, change_quote_currency, parent=None):
530 super(QLabel, self).__init__(_("Connecting..."), parent)
531 self.change_quote_currency = change_quote_currency
532 self.state = self.SHOW_CONNECTING
533 self.balance_text = ""
534 self.amount_text = ""
537 def mousePressEvent(self, event):
538 """Change the fiat currency selection if window background is clicked."""
539 if self.state != self.SHOW_CONNECTING:
540 if event.button() == Qt.LeftButton:
541 self.change_quote_currency()
543 position = event.globalPos()
544 menu = self.parent.context_menu()
548 def set_balance_text(self, amount, unit, quote_text):
549 """Set the amount of bitcoins in the gui."""
550 if self.state == self.SHOW_CONNECTING:
551 self.state = self.SHOW_BALANCE
553 self.balance_text = "<span style='font-size: 18pt'>%s</span>"%amount\
554 + " <span style='font-size: 10pt'>%s</span>" % unit \
555 + " <span style='font-size: 10pt'>%s</span>" % quote_text
557 if self.state == self.SHOW_BALANCE:
558 self.setText(self.balance_text)
560 def set_amount_text(self, quote_text):
561 self.amount_text = "<span style='font-size: 10pt'>%s</span>" % quote_text
562 if self.state == self.SHOW_AMOUNT:
563 self.setText(self.amount_text)
565 def show_balance(self):
566 if self.state == self.SHOW_AMOUNT:
567 self.state = self.SHOW_BALANCE
568 self.setText(self.balance_text)
570 def show_amount(self):
571 if self.state == self.SHOW_BALANCE:
572 self.state = self.SHOW_AMOUNT
573 self.setText(self.amount_text)
575 def ok_cancel_buttons(dialog):
576 row_layout = QHBoxLayout()
577 row_layout.addStretch(1)
578 ok_button = QPushButton(_("OK"))
579 row_layout.addWidget(ok_button)
580 ok_button.clicked.connect(dialog.accept)
581 cancel_button = QPushButton(_("Cancel"))
582 row_layout.addWidget(cancel_button)
583 cancel_button.clicked.connect(dialog.reject)
586 class PasswordDialog(QDialog):
588 def __init__(self, parent):
589 super(QDialog, self).__init__(parent)
593 self.password_input = QLineEdit()
594 self.password_input.setEchoMode(QLineEdit.Password)
596 main_layout = QVBoxLayout(self)
597 message = _('Please enter your password')
598 main_layout.addWidget(QLabel(message))
602 grid.addWidget(QLabel(_('Password')), 1, 0)
603 grid.addWidget(self.password_input, 1, 1)
604 main_layout.addLayout(grid)
606 main_layout.addLayout(ok_cancel_buttons(self))
607 self.setLayout(main_layout)
612 return unicode(self.password_input.text())
614 class ReceivePopup(QDialog):
616 def leaveEvent(self, event):
619 def setup(self, address):
620 label = QLabel(_("Copied your Bitcoin address to the clipboard!"))
621 address_display = QLineEdit(address)
622 address_display.setReadOnly(True)
623 resize_line_edit_width(address_display, address)
625 main_layout = QVBoxLayout(self)
626 main_layout.addWidget(label)
627 main_layout.addWidget(address_display)
629 self.setMouseTracking(True)
630 self.setWindowTitle("Electrum - " + _("Receive Bitcoin payment"))
631 self.setWindowFlags(Qt.Window|Qt.FramelessWindowHint|
632 Qt.MSWindowsFixedSizeDialogHint)
633 self.layout().setSizeConstraint(QLayout.SetFixedSize)
634 #self.setFrameStyle(QFrame.WinPanel|QFrame.Raised)
635 #self.setAlignment(Qt.AlignCenter)
638 parent = self.parent()
639 top_left_pos = parent.mapToGlobal(parent.rect().bottomLeft())
640 self.move(top_left_pos)
641 center_mouse_pos = self.mapToGlobal(self.rect().center())
642 QCursor.setPos(center_mouse_pos)
646 """Initialize the definitions relating to themes and
647 sending/receiving bitcoins."""
650 def __init__(self, main_window):
651 """Retrieve the gui theme used in previous session."""
653 self.theme_name = self.g.config.get('litegui_theme','Cleanlook')
654 self.themes = load_theme_paths()
656 def load_theme(self):
657 """Load theme retrieved from wallet file."""
659 theme_prefix, theme_path = self.themes[self.theme_name]
661 util.print_error("Theme not found!", self.theme_name)
663 QDir.setCurrent(os.path.join(theme_prefix, theme_path))
664 with open(rsrc("style.css")) as style_file:
665 qApp.setStyleSheet(style_file.read())
667 def theme_names(self):
669 return sorted(self.themes.keys())
671 def selected_theme(self):
673 return self.theme_name
675 def change_theme(self, theme_name):
677 self.theme_name = theme_name
678 self.config.set_key('litegui_theme',theme_name)
681 def set_configured_currency(self, set_quote_currency):
682 """Set the inital fiat currency conversion country (USD/EUR/GBP) in
683 the GUI to what it was set to in the wallet."""
684 currency = self.g.config.get('currency')
685 # currency can be none when Electrum is used for the first
686 # time and no setting has been created yet.
687 if currency is not None:
688 set_quote_currency(currency)
690 def set_config_currency(self, conversion_currency):
691 """Change the wallet fiat currency country."""
692 self.g.config.set_key('conversion_currency',conversion_currency,True)
694 def copy_address(self, receive_popup):
695 """Copy the wallet addresses into the client."""
696 addrs = [addr for addr in self.g.wallet.addresses(True)
697 if not self.g.wallet.is_change(addr)]
698 # Select most recent addresses from gap limit
699 addrs = addrs[-self.g.wallet.gap_limit:]
700 copied_address = random.choice(addrs)
701 qApp.clipboard().setText(copied_address)
702 receive_popup.setup(copied_address)
703 receive_popup.popup()
705 def waiting_dialog(self, f):
710 w.setWindowTitle('Electrum')
711 l = QLabel(_('Sending transaction, please wait.'))
720 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
725 def send(self, address, amount, parent_window):
726 """Send bitcoins to the target address."""
727 dest_address = self.fetch_destination(address)
729 if dest_address is None or not is_valid(dest_address):
730 QMessageBox.warning(parent_window, _('Error'),
731 _('Invalid Bitcoin Address') + ':\n' + address, _('OK'))
734 convert_amount = lambda amount: \
735 int(D(unicode(amount)) * bitcoin(1))
736 amount = convert_amount(amount)
738 if self.g.wallet.use_encryption:
739 password_dialog = PasswordDialog(parent_window)
740 password = password_dialog.run()
748 if amount < bitcoin(1) / 10:
750 fee = bitcoin(1) / 1000
753 tx = self.g.wallet.mktx([(dest_address, amount)], password, fee)
754 except BaseException as error:
755 QMessageBox.warning(parent_window, _('Error'), str(error), _('OK'))
759 h = self.g.wallet.send_tx(tx)
761 self.waiting_dialog(lambda: False if self.g.wallet.tx_event.isSet() else _("Sending transaction, please wait..."))
763 status, message = self.g.wallet.receive_tx(h)
767 dumpf = tempfile.NamedTemporaryFile(delete=False)
770 print "Dumped error tx to", dumpf.name
771 QMessageBox.warning(parent_window, _('Error'), message, _('OK'))
774 TransactionWindow(message, self)
776 filename = 'unsigned_tx_%s' % (time.mktime(time.gmtime()))
778 fileName = QFileDialog.getSaveFileName(QWidget(), _("Select a transaction filename"), os.path.expanduser('~/%s' % (filename)))
779 with open(fileName,'w') as f:
780 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
781 QMessageBox.information(QWidget(), _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
782 except BaseException as e:
783 QMessageBox.warning(QWidget(), _('Error'), _('Could not write transaction to file: %s' % e), _('OK'))
786 def fetch_destination(self, address):
787 recipient = unicode(address).strip()
790 match1 = re.match("^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$",
793 # label or alias, with address in brackets
794 match2 = re.match("(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>",
799 self.g.wallet.get_alias(recipient, True,
800 self.show_message, self.question)
803 return match2.group(2)
811 class MiniDriver(QObject):
818 def __init__(self, main_window, mini_window):
819 super(QObject, self).__init__()
822 self.network = main_window.network
823 self.window = mini_window
825 self.network.register_callback('updated',self.update_callback)
826 self.network.register_callback('connected', self.update_callback)
827 self.network.register_callback('disconnected', self.update_callback)
832 self.connect(self, SIGNAL("updatesignal()"), self.update)
833 self.update_callback()
835 # This is a hack to workaround that Qt does not like changing the
836 # window properties from this other thread before the runloop has
838 def update_callback(self):
839 self.emit(SIGNAL("updatesignal()"))
842 if not self.network.interface:
844 elif not self.network.interface.is_connected:
847 if self.g.wallet is None:
849 elif not self.g.wallet.up_to_date:
853 self.update_balance()
854 self.update_completions()
855 self.update_history()
857 def initializing(self):
858 if self.state == self.INITIALIZING:
860 self.state = self.INITIALIZING
861 self.window.deactivate()
863 def connecting(self):
864 if self.state == self.CONNECTING:
866 self.state = self.CONNECTING
867 self.window.deactivate()
869 def synchronizing(self):
870 if self.state == self.SYNCHRONIZING:
872 self.state = self.SYNCHRONIZING
873 self.window.deactivate()
876 if self.state == self.READY:
878 self.state = self.READY
879 self.window.activate()
881 def update_balance(self):
882 conf_balance, unconf_balance = self.g.wallet.get_balance()
883 balance = D(conf_balance + unconf_balance)
884 self.window.set_balances(balance)
886 def update_completions(self):
888 for addr, label in self.g.wallet.labels.items():
889 if addr in self.g.wallet.addressbook:
890 completions.append("%s <%s>" % (label, addr))
891 self.window.update_completions(completions)
893 def update_history(self):
894 tx_history = self.g.wallet.get_tx_history()
895 self.window.update_history(tx_history)
898 if __name__ == "__main__":
899 app = QApplication(sys.argv)
900 with open(rsrc("style.css")) as style_file:
901 app.setStyleSheet(style_file.read())
903 sys.exit(app.exec_())