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)
182 #self.exchanger = exchange_rate.Exchanger(self)
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.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
406 btc_balance = "%.4f" % (btc_balance / bitcoin(1))
407 self.balance_label.set_balance_text(btc_balance, quote_text)
408 self.setWindowTitle("Electrum %s - %s BTC" % (electrum_version, btc_balance))
410 def amount_input_changed(self, amount_text):
411 """Update the number of bitcoins displayed."""
412 self.check_button_status()
415 amount = D(str(amount_text))
416 except decimal.InvalidOperation:
417 self.balance_label.show_balance()
419 quote_text = self.create_quote_text(amount * bitcoin(1))
421 self.balance_label.set_amount_text(quote_text)
422 self.balance_label.show_amount()
424 self.balance_label.show_balance()
426 def create_quote_text(self, btc_balance):
427 """Return a string copy of the amount fiat currency the
428 user has in bitcoins."""
429 quote_currency = self.quote_currencies[0]
430 quote_balance = None #self.exchanger.exchange(btc_balance, quote_currency)
431 if quote_balance is None:
434 quote_text = "%.2f %s" % ((quote_balance / bitcoin(1)),
439 if self.actuator.send(self.address_input.text(),
440 self.amount_input.text(), self):
441 self.address_input.setText("")
442 self.amount_input.setText("")
444 def check_button_status(self):
445 """Check that the bitcoin address is valid and that something
446 is entered in the amount before making the send button clickable."""
448 value = D(str(self.amount_input.text())) * 10**8
449 except decimal.InvalidOperation:
451 # self.address_input.property(...) returns a qVariant, not a bool.
452 # The == is needed to properly invoke a comparison.
453 if (self.address_input.property("isValid") == True and
454 value is not None and 0 < value <= self.btc_balance):
455 self.send_button.setDisabled(False)
457 self.send_button.setDisabled(True)
459 def address_field_changed(self, address):
460 # label or alias, with address in brackets
461 match2 = re.match("(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>",
464 address = match2.group(2)
465 self.address_input.setText(address)
467 if is_valid(address):
468 self.check_button_status()
469 self.address_input.setProperty("isValid", True)
470 self.recompute_style(self.address_input)
472 self.send_button.setDisabled(True)
473 self.address_input.setProperty("isValid", False)
474 self.recompute_style(self.address_input)
476 if len(address) == 0:
477 self.address_input.setProperty("isValid", None)
478 self.recompute_style(self.address_input)
480 def recompute_style(self, element):
481 self.style().unpolish(element)
482 self.style().polish(element)
484 def copy_address(self):
485 receive_popup = ReceivePopup(self.receive_button)
486 self.actuator.copy_address(receive_popup)
488 def update_completions(self, completions):
489 self.address_completions.setStringList(completions)
492 def update_history(self, tx_history):
494 self.history_list.empty()
496 for item in tx_history[-10:]:
497 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
498 label = self.actuator.wallet.get_label(tx_hash)[0]
499 #amount = D(value) / 10**8
500 v_str = format_satoshis(value, True)
501 self.history_list.append(label, v_str, age(timestamp))
504 def the_website(self):
505 webbrowser.open("http://electrum.org")
508 def toggle_receiving_layout(self, toggle_state):
510 self.receiving_box.show()
512 self.receiving_box.hide()
513 self.config.set_key("gui_show_receiving", toggle_state)
515 def show_history(self, toggle_state):
517 self.main_layout.setRowMinimumHeight(3,200)
518 self.history_list.show()
520 self.main_layout.setRowMinimumHeight(3,0)
521 self.history_list.hide()
522 self.config.set_key("gui_show_history", toggle_state)
524 class BalanceLabel(QLabel):
530 def __init__(self, change_quote_currency, parent=None):
531 super(QLabel, self).__init__(_("Connecting..."), parent)
532 self.change_quote_currency = change_quote_currency
533 self.state = self.SHOW_CONNECTING
534 self.balance_text = ""
535 self.amount_text = ""
538 def mousePressEvent(self, event):
539 """Change the fiat currency selection if window background is clicked."""
540 if self.state != self.SHOW_CONNECTING:
541 if event.button() == Qt.LeftButton:
542 self.change_quote_currency()
544 position = event.globalPos()
545 menu = self.parent.context_menu()
549 def set_balance_text(self, btc_balance, quote_text):
550 """Set the amount of bitcoins in the gui."""
551 if self.state == self.SHOW_CONNECTING:
552 self.state = self.SHOW_BALANCE
553 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)
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, config, wallet):
648 """Retrieve the gui theme used in previous session."""
651 self.theme_name = self.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.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.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.config.set_key('conversion_currency',conversion_currency,True)
692 def copy_address(self, receive_popup):
693 """Copy the wallet addresses into the client."""
694 addrs = [addr for addr in self.wallet.addresses(True)
695 if not self.wallet.is_change(addr)]
696 # Select most recent addresses from gap limit
697 addrs = addrs[-self.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 convert_amount = lambda amount: \
733 int(D(unicode(amount)) * bitcoin(1))
734 amount = convert_amount(amount)
736 if self.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.wallet.mktx([(dest_address, amount)], password, fee)
752 except BaseException as error:
753 QMessageBox.warning(parent_window, _('Error'), str(error), _('OK'))
757 h = self.wallet.send_tx(tx)
759 self.waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Sending transaction, please wait..."))
761 status, message = self.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 BaseException 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.wallet.get_alias(recipient, True,
798 self.show_message, self.question)
801 return match2.group(2)
809 class MiniDriver(QObject):
816 def __init__(self, wallet, window):
817 super(QObject, self).__init__()
820 self.network = wallet.network
823 self.wallet.network.register_callback('updated',self.update_callback)
824 self.wallet.network.register_callback('connected', self.update_callback)
825 self.wallet.network.register_callback('disconnected', self.update_callback)
830 self.connect(self, SIGNAL("updatesignal()"), self.update)
831 self.update_callback()
833 # This is a hack to workaround that Qt does not like changing the
834 # window properties from this other thread before the runloop has
836 def update_callback(self):
837 self.emit(SIGNAL("updatesignal()"))
840 if not self.network.interface:
842 elif not self.network.interface.is_connected:
844 elif not self.wallet.up_to_date:
849 if self.wallet.up_to_date:
850 self.update_balance()
851 self.update_completions()
852 self.update_history()
854 def initializing(self):
855 if self.state == self.INITIALIZING:
857 self.state = self.INITIALIZING
858 self.window.deactivate()
860 def connecting(self):
861 if self.state == self.CONNECTING:
863 self.state = self.CONNECTING
864 self.window.deactivate()
866 def synchronizing(self):
867 if self.state == self.SYNCHRONIZING:
869 self.state = self.SYNCHRONIZING
870 self.window.deactivate()
873 if self.state == self.READY:
875 self.state = self.READY
876 self.window.activate()
878 def update_balance(self):
879 conf_balance, unconf_balance = self.wallet.get_balance()
880 balance = D(conf_balance + unconf_balance)
881 self.window.set_balances(balance)
883 def update_completions(self):
885 for addr, label in self.wallet.labels.items():
886 if addr in self.wallet.addressbook:
887 completions.append("%s <%s>" % (label, addr))
888 self.window.update_completions(completions)
890 def update_history(self):
891 tx_history = self.wallet.get_tx_history()
892 self.window.update_history(tx_history)
895 if __name__ == "__main__":
896 app = QApplication(sys.argv)
897 with open(rsrc("style.css")) as style_file:
898 app.setStyleSheet(style_file.read())
900 sys.exit(app.exec_())