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)
125 label = label.encode('utf-8')
129 balance_string = format_satoshis(balance, False)
130 transaction.writerow([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
131 QMessageBox.information(None,_("CSV Export created"), _("Your CSV export has been successfully created."))
132 except (IOError, os.error), reason:
133 export_error_label = _("Electrum was unable to produce a transaction export.")
134 QMessageBox.critical(None,_("Unable to create csv"), export_error_label + "\n" + str(reason))
138 class TransactionWindow(QDialog):
141 label = unicode(self.label_edit.text())
142 self.parent.wallet.labels[self.tx_id] = label
144 super(TransactionWindow, self).accept()
146 def __init__(self, transaction_id, parent):
147 super(TransactionWindow, self).__init__()
149 self.tx_id = str(transaction_id)
154 self.setWindowTitle(_("Transaction successfully sent"))
156 self.layout = QGridLayout(self)
157 history_label = "%s\n%s" % (_("Your transaction has been sent."), _("Please enter a label for this transaction for future reference."))
158 self.layout.addWidget(QLabel(history_label))
160 self.label_edit = QLineEdit()
161 self.label_edit.setPlaceholderText(_("Transaction label"))
162 self.label_edit.setObjectName("label_input")
163 self.label_edit.setAttribute(Qt.WA_MacShowFocusRect, 0)
164 self.label_edit.setFocusPolicy(Qt.ClickFocus)
165 self.layout.addWidget(self.label_edit)
167 self.save_button = QPushButton(_("Save"))
168 self.layout.addWidget(self.save_button)
169 self.save_button.clicked.connect(self.set_label)
173 class MiniWindow(QDialog):
175 def __init__(self, actuator, expand_callback, config):
176 super(MiniWindow, self).__init__()
178 self.actuator = actuator
180 self.btc_balance = None
181 self.quote_currencies = ["BRL", "CNY", "EUR", "GBP", "RUB", "USD"]
182 self.actuator.set_configured_currency(self.set_quote_currency)
184 # Needed because price discovery is done in a different thread
185 # which needs to be sent back to this main one to update the GUI
186 self.connect(self, SIGNAL("refresh_balance()"), self.refresh_balance)
188 self.balance_label = BalanceLabel(self.change_quote_currency, self)
189 self.balance_label.setObjectName("balance_label")
192 # Bitcoin address code
193 self.address_input = QLineEdit()
194 self.address_input.setPlaceholderText(_("Enter a Bitcoin address or contact"))
195 self.address_input.setObjectName("address_input")
197 self.address_input.setFocusPolicy(Qt.ClickFocus)
199 self.address_input.textChanged.connect(self.address_field_changed)
200 resize_line_edit_width(self.address_input,
201 "1BtaFUr3qVvAmwrsuDuu5zk6e4s2rxd2Gy")
203 self.address_completions = QStringListModel()
204 address_completer = QCompleter(self.address_input)
205 address_completer.setCaseSensitivity(False)
206 address_completer.setModel(self.address_completions)
207 self.address_input.setCompleter(address_completer)
209 address_layout = QHBoxLayout()
210 address_layout.addWidget(self.address_input)
212 self.amount_input = QLineEdit()
213 self.amount_input.setPlaceholderText(_("... and amount") + " (%s)"%self.actuator.g.base_unit())
214 self.amount_input.setObjectName("amount_input")
216 self.amount_input.setFocusPolicy(Qt.ClickFocus)
217 # This is changed according to the user's displayed balance
218 self.amount_validator = QDoubleValidator(self.amount_input)
219 self.amount_validator.setNotation(QDoubleValidator.StandardNotation)
220 self.amount_validator.setDecimals(8)
221 self.amount_input.setValidator(self.amount_validator)
223 # This removes the very ugly OSX highlighting, please leave this in :D
224 self.address_input.setAttribute(Qt.WA_MacShowFocusRect, 0)
225 self.amount_input.setAttribute(Qt.WA_MacShowFocusRect, 0)
226 self.amount_input.textChanged.connect(self.amount_input_changed)
228 #if self.actuator.g.wallet.seed:
229 self.send_button = QPushButton(_("&Send"))
231 # self.send_button = QPushButton(_("&Create"))
233 self.send_button.setObjectName("send_button")
234 self.send_button.setDisabled(True);
235 self.send_button.clicked.connect(self.send)
237 # Creating the receive button
238 self.switch_button = QPushButton( QIcon(":icons/switchgui.png"),'' )
239 self.switch_button.setMaximumWidth(25)
240 self.switch_button.setFlat(True)
241 self.switch_button.clicked.connect(expand_callback)
243 main_layout = QGridLayout(self)
245 main_layout.addWidget(self.balance_label, 0, 0, 1, 3)
246 main_layout.addWidget(self.switch_button, 0, 3)
248 main_layout.addWidget(self.address_input, 1, 0, 1, 4)
249 main_layout.addWidget(self.amount_input, 2, 0, 1, 2)
250 main_layout.addWidget(self.send_button, 2, 2, 1, 2)
252 self.send_button.setMaximumWidth(125)
254 self.history_list = history_widget.HistoryWidget()
255 self.history_list.setObjectName("history")
256 self.history_list.hide()
257 self.history_list.setAlternatingRowColors(True)
259 main_layout.addWidget(self.history_list, 3, 0, 1, 4)
261 self.receiving = receiving_widget.ReceivingWidget(self)
262 self.receiving.setObjectName("receiving")
264 # Add to the right side
265 self.receiving_box = QGroupBox(_("Select a receiving address"))
266 extra_layout = QGridLayout()
268 # Checkbox to filter used addresses
269 hide_used = QCheckBox(_('Hide used addresses'))
270 hide_used.setChecked(True)
271 hide_used.stateChanged.connect(self.receiving.toggle_used)
273 # Events for receiving addresses
274 self.receiving.clicked.connect(self.receiving.copy_address)
275 self.receiving.itemDoubleClicked.connect(self.receiving.edit_label)
276 self.receiving.itemChanged.connect(self.receiving.update_label)
280 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)
282 extra_layout.addWidget(self.receiving, 1,0)
283 extra_layout.addWidget(hide_used, 2,0)
284 extra_layout.setColumnMinimumWidth(0,200)
286 self.receiving_box.setLayout(extra_layout)
287 main_layout.addWidget(self.receiving_box,0,4,-1,3)
288 self.receiving_box.hide()
290 self.main_layout = main_layout
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 self.show_history(show_hist)
302 show_hist = self.config.get("gui_show_receiving",False)
303 self.toggle_receiving_layout(show_hist)
305 self.setWindowIcon(QIcon(":icons/electrum.png"))
306 self.setWindowTitle("Electrum")
307 self.setWindowFlags(Qt.Window|Qt.MSWindowsFixedSizeDialogHint)
308 self.layout().setSizeConstraint(QLayout.SetFixedSize)
309 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)
359 self.actuator.g.closeEvent(event)
362 def set_payment_fields(self, dest_address, amount):
363 self.address_input.setText(dest_address)
364 self.address_field_changed(dest_address)
365 self.amount_input.setText(amount)
370 def deactivate(self):
373 def set_quote_currency(self, currency):
374 """Set and display the fiat currency country."""
375 if currency not in self.quote_currencies:
377 self.quote_currencies.remove(currency)
378 self.quote_currencies.insert(0, currency)
379 self.refresh_balance()
381 def change_quote_currency(self, forward=True):
383 self.quote_currencies = \
384 self.quote_currencies[1:] + self.quote_currencies[0:1]
386 self.quote_currencies = \
387 self.quote_currencies[-1:] + self.quote_currencies[0:-1]
388 self.actuator.set_config_currency(self.quote_currencies[0])
389 self.refresh_balance()
391 def refresh_balance(self):
392 if self.btc_balance is None:
393 # Price has been discovered before wallet has been loaded
394 # and server connect... so bail.
396 self.set_balances(self.btc_balance)
397 self.amount_input_changed(self.amount_input.text())
399 def set_balances(self, btc_balance):
400 """Set the bitcoin balance and update the amount label accordingly."""
401 self.btc_balance = btc_balance
402 quote_text = self.create_quote_text(btc_balance)
404 quote_text = "(%s)" % quote_text
406 amount = self.actuator.g.format_amount(btc_balance)
407 unit = self.actuator.g.base_unit()
409 self.balance_label.set_balance_text(amount, unit, quote_text)
410 self.setWindowTitle("Electrum %s - %s %s" % (electrum_version, amount, unit))
412 def amount_input_changed(self, amount_text):
413 """Update the number of bitcoins displayed."""
414 self.check_button_status()
417 amount = D(str(amount_text)) * (10**self.actuator.g.decimal_point)
418 except decimal.InvalidOperation:
419 self.balance_label.show_balance()
421 quote_text = self.create_quote_text(amount)
423 self.balance_label.set_amount_text(quote_text)
424 self.balance_label.show_amount()
426 self.balance_label.show_balance()
428 def create_quote_text(self, btc_balance):
429 """Return a string copy of the amount fiat currency the
430 user has in bitcoins."""
431 from electrum.plugins import run_hook
433 run_hook('set_quote_text', btc_balance, r)
437 if self.actuator.send(self.address_input.text(),
438 self.amount_input.text(), self):
439 self.address_input.setText("")
440 self.amount_input.setText("")
442 def check_button_status(self):
443 """Check that the bitcoin address is valid and that something
444 is entered in the amount before making the send button clickable."""
446 value = D(str(self.amount_input.text())) * (10**self.actuator.g.decimal_point)
447 except decimal.InvalidOperation:
449 # self.address_input.property(...) returns a qVariant, not a bool.
450 # The == is needed to properly invoke a comparison.
451 if (self.address_input.property("isValid") == True and
452 value is not None and 0 < value <= self.btc_balance):
453 self.send_button.setDisabled(False)
455 self.send_button.setDisabled(True)
457 def address_field_changed(self, address):
458 # label or alias, with address in brackets
459 match2 = re.match("(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>",
462 address = match2.group(2)
463 self.address_input.setText(address)
465 if is_valid(address):
466 self.check_button_status()
467 self.address_input.setProperty("isValid", True)
468 self.recompute_style(self.address_input)
470 self.send_button.setDisabled(True)
471 self.address_input.setProperty("isValid", False)
472 self.recompute_style(self.address_input)
474 if len(address) == 0:
475 self.address_input.setProperty("isValid", None)
476 self.recompute_style(self.address_input)
478 def recompute_style(self, element):
479 self.style().unpolish(element)
480 self.style().polish(element)
482 def copy_address(self):
483 receive_popup = ReceivePopup(self.receive_button)
484 self.actuator.copy_address(receive_popup)
486 def update_completions(self, completions):
487 self.address_completions.setStringList(completions)
490 def update_history(self, tx_history):
492 self.history_list.empty()
494 for item in tx_history[-10:]:
495 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
496 label = self.actuator.g.wallet.get_label(tx_hash)[0]
497 v_str = self.actuator.g.format_amount(value, True)
498 self.history_list.append(label, v_str, age(timestamp))
501 def the_website(self):
502 webbrowser.open("http://electrum.org")
505 def toggle_receiving_layout(self, toggle_state):
507 self.receiving_box.show()
509 self.receiving_box.hide()
510 self.config.set_key("gui_show_receiving", toggle_state)
512 def show_history(self, toggle_state):
514 self.main_layout.setRowMinimumHeight(3,200)
515 self.history_list.show()
517 self.main_layout.setRowMinimumHeight(3,0)
518 self.history_list.hide()
519 self.config.set_key("gui_show_history", toggle_state)
521 class BalanceLabel(QLabel):
527 def __init__(self, change_quote_currency, parent=None):
528 super(QLabel, self).__init__(_("Connecting..."), parent)
529 self.change_quote_currency = change_quote_currency
530 self.state = self.SHOW_CONNECTING
531 self.balance_text = ""
532 self.amount_text = ""
535 def mousePressEvent(self, event):
536 """Change the fiat currency selection if window background is clicked."""
537 if self.state != self.SHOW_CONNECTING:
538 if event.button() == Qt.LeftButton:
539 self.change_quote_currency()
541 position = event.globalPos()
542 menu = self.parent.context_menu()
546 def set_balance_text(self, amount, unit, quote_text):
547 """Set the amount of bitcoins in the gui."""
548 if self.state == self.SHOW_CONNECTING:
549 self.state = self.SHOW_BALANCE
551 self.balance_text = "<span style='font-size: 18pt'>%s</span>"%amount\
552 + " <span style='font-size: 10pt'>%s</span>" % unit \
553 + " <span style='font-size: 10pt'>%s</span>" % quote_text
555 if self.state == self.SHOW_BALANCE:
556 self.setText(self.balance_text)
558 def set_amount_text(self, quote_text):
559 self.amount_text = "<span style='font-size: 10pt'>%s</span>" % quote_text
560 if self.state == self.SHOW_AMOUNT:
561 self.setText(self.amount_text)
563 def show_balance(self):
564 if self.state == self.SHOW_AMOUNT:
565 self.state = self.SHOW_BALANCE
566 self.setText(self.balance_text)
568 def show_amount(self):
569 if self.state == self.SHOW_BALANCE:
570 self.state = self.SHOW_AMOUNT
571 self.setText(self.amount_text)
573 def ok_cancel_buttons(dialog):
574 row_layout = QHBoxLayout()
575 row_layout.addStretch(1)
576 ok_button = QPushButton(_("OK"))
577 row_layout.addWidget(ok_button)
578 ok_button.clicked.connect(dialog.accept)
579 cancel_button = QPushButton(_("Cancel"))
580 row_layout.addWidget(cancel_button)
581 cancel_button.clicked.connect(dialog.reject)
584 class PasswordDialog(QDialog):
586 def __init__(self, parent):
587 super(QDialog, self).__init__(parent)
591 self.password_input = QLineEdit()
592 self.password_input.setEchoMode(QLineEdit.Password)
594 main_layout = QVBoxLayout(self)
595 message = _('Please enter your password')
596 main_layout.addWidget(QLabel(message))
600 grid.addWidget(QLabel(_('Password')), 1, 0)
601 grid.addWidget(self.password_input, 1, 1)
602 main_layout.addLayout(grid)
604 main_layout.addLayout(ok_cancel_buttons(self))
605 self.setLayout(main_layout)
610 return unicode(self.password_input.text())
612 class ReceivePopup(QDialog):
614 def leaveEvent(self, event):
617 def setup(self, address):
618 label = QLabel(_("Copied your Bitcoin address to the clipboard!"))
619 address_display = QLineEdit(address)
620 address_display.setReadOnly(True)
621 resize_line_edit_width(address_display, address)
623 main_layout = QVBoxLayout(self)
624 main_layout.addWidget(label)
625 main_layout.addWidget(address_display)
627 self.setMouseTracking(True)
628 self.setWindowTitle("Electrum - " + _("Receive Bitcoin payment"))
629 self.setWindowFlags(Qt.Window|Qt.FramelessWindowHint|
630 Qt.MSWindowsFixedSizeDialogHint)
631 self.layout().setSizeConstraint(QLayout.SetFixedSize)
632 #self.setFrameStyle(QFrame.WinPanel|QFrame.Raised)
633 #self.setAlignment(Qt.AlignCenter)
636 parent = self.parent()
637 top_left_pos = parent.mapToGlobal(parent.rect().bottomLeft())
638 self.move(top_left_pos)
639 center_mouse_pos = self.mapToGlobal(self.rect().center())
640 QCursor.setPos(center_mouse_pos)
644 """Initialize the definitions relating to themes and
645 sending/receiving bitcoins."""
648 def __init__(self, main_window):
649 """Retrieve the gui theme used in previous session."""
651 self.theme_name = self.g.config.get('litegui_theme','Cleanlook')
652 self.themes = load_theme_paths()
654 def load_theme(self):
655 """Load theme retrieved from wallet file."""
657 theme_prefix, theme_path = self.themes[self.theme_name]
659 util.print_error("Theme not found!", self.theme_name)
661 QDir.setCurrent(os.path.join(theme_prefix, theme_path))
662 with open(rsrc("style.css")) as style_file:
663 qApp.setStyleSheet(style_file.read())
665 def theme_names(self):
667 return sorted(self.themes.keys())
669 def selected_theme(self):
671 return self.theme_name
673 def change_theme(self, theme_name):
675 self.theme_name = theme_name
676 self.g.config.set_key('litegui_theme',theme_name)
679 def set_configured_currency(self, set_quote_currency):
680 """Set the inital fiat currency conversion country (USD/EUR/GBP) in
681 the GUI to what it was set to in the wallet."""
682 currency = self.g.config.get('currency')
683 # currency can be none when Electrum is used for the first
684 # time and no setting has been created yet.
685 if currency is not None:
686 set_quote_currency(currency)
688 def set_config_currency(self, conversion_currency):
689 """Change the wallet fiat currency country."""
690 self.g.config.set_key('currency',conversion_currency,True)
691 self.g.update_status()
693 def copy_address(self, receive_popup):
694 """Copy the wallet addresses into the client."""
695 addrs = [addr for addr in self.g.wallet.addresses(True)
696 if not self.g.wallet.is_change(addr)]
697 # Select most recent addresses from gap limit
698 addrs = addrs[-self.g.wallet.gap_limit:]
699 copied_address = random.choice(addrs)
700 qApp.clipboard().setText(copied_address)
701 receive_popup.setup(copied_address)
702 receive_popup.popup()
704 def waiting_dialog(self, f):
709 w.setWindowTitle('Electrum')
710 l = QLabel(_('Sending transaction, please wait.'))
719 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
724 def send(self, address, amount, parent_window):
725 """Send bitcoins to the target address."""
726 dest_address = self.fetch_destination(address)
728 if dest_address is None or not is_valid(dest_address):
729 QMessageBox.warning(parent_window, _('Error'),
730 _('Invalid Bitcoin Address') + ':\n' + address, _('OK'))
733 amount = D(unicode(amount)) * (10*self.g.decimal_point)
734 print "amount", amount
737 if self.g.wallet.use_encryption:
738 password_dialog = PasswordDialog(parent_window)
739 password = password_dialog.run()
747 if amount < bitcoin(1) / 10:
749 fee = bitcoin(1) / 1000
752 tx = self.g.wallet.mktx([(dest_address, amount)], password, fee)
753 except Exception as error:
754 QMessageBox.warning(parent_window, _('Error'), str(error), _('OK'))
758 h = self.g.wallet.send_tx(tx)
760 self.waiting_dialog(lambda: False if self.g.wallet.tx_event.isSet() else _("Sending transaction, please wait..."))
762 status, message = self.g.wallet.receive_tx(h, tx)
766 dumpf = tempfile.NamedTemporaryFile(delete=False)
769 print "Dumped error tx to", dumpf.name
770 QMessageBox.warning(parent_window, _('Error'), message, _('OK'))
773 TransactionWindow(message, self)
775 filename = 'unsigned_tx_%s' % (time.mktime(time.gmtime()))
777 fileName = QFileDialog.getSaveFileName(QWidget(), _("Select a transaction filename"), os.path.expanduser('~/%s' % (filename)))
778 with open(fileName,'w') as f:
779 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
780 QMessageBox.information(QWidget(), _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
781 except Exception as e:
782 QMessageBox.warning(QWidget(), _('Error'), _('Could not write transaction to file: %s' % e), _('OK'))
785 def fetch_destination(self, address):
786 recipient = unicode(address).strip()
789 match1 = re.match("^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$",
792 # label or alias, with address in brackets
793 match2 = re.match("(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>",
798 self.g.wallet.get_alias(recipient, True,
799 self.show_message, self.question)
802 return match2.group(2)
810 class MiniDriver(QObject):
817 def __init__(self, main_window, mini_window):
818 super(QObject, self).__init__()
821 self.network = main_window.network
822 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()"))
844 elif not self.network.interface:
846 elif not self.network.interface.is_connected:
849 if self.g.wallet is None:
851 elif not self.g.wallet.up_to_date:
855 self.update_balance()
856 self.update_completions()
857 self.update_history()
858 self.window.receiving.update_list()
861 def initializing(self):
862 if self.state == self.INITIALIZING:
864 self.state = self.INITIALIZING
865 self.window.deactivate()
867 def connecting(self):
868 if self.state == self.CONNECTING:
870 self.state = self.CONNECTING
871 self.window.deactivate()
873 def synchronizing(self):
874 if self.state == self.SYNCHRONIZING:
876 self.state = self.SYNCHRONIZING
877 self.window.deactivate()
880 if self.state == self.READY:
882 self.state = self.READY
883 self.window.activate()
885 def update_balance(self):
886 conf_balance, unconf_balance = self.g.wallet.get_balance()
887 balance = D(conf_balance + unconf_balance)
888 self.window.set_balances(balance)
890 def update_completions(self):
892 for addr, label in self.g.wallet.labels.items():
893 if addr in self.g.wallet.addressbook:
894 completions.append("%s <%s>" % (label, addr))
895 self.window.update_completions(completions)
897 def update_history(self):
898 tx_history = self.g.wallet.get_tx_history()
899 self.window.update_history(tx_history)
902 if __name__ == "__main__":
903 app = QApplication(sys.argv)
904 with open(rsrc("style.css")) as style_file:
905 app.setStyleSheet(style_file.read())
907 sys.exit(app.exec_())