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.use_exchanges = ["Blockchain", "CoinDesk"]
182 self.quote_currencies = ["BRL", "CNY", "EUR", "GBP", "RUB", "USD"]
183 self.actuator.set_configured_currency(self.set_quote_currency)
184 self.actuator.set_configured_exchange(self.set_exchange)
186 # Needed because price discovery is done in a different thread
187 # which needs to be sent back to this main one to update the GUI
188 self.connect(self, SIGNAL("refresh_balance()"), self.refresh_balance)
190 self.balance_label = BalanceLabel(self.change_quote_currency, self)
191 self.balance_label.setObjectName("balance_label")
194 # Bitcoin address code
195 self.address_input = QLineEdit()
196 self.address_input.setPlaceholderText(_("Enter a Bitcoin address or contact"))
197 self.address_input.setObjectName("address_input")
199 self.address_input.setFocusPolicy(Qt.ClickFocus)
201 self.address_input.textChanged.connect(self.address_field_changed)
202 resize_line_edit_width(self.address_input,
203 "1BtaFUr3qVvAmwrsuDuu5zk6e4s2rxd2Gy")
205 self.address_completions = QStringListModel()
206 address_completer = QCompleter(self.address_input)
207 address_completer.setCaseSensitivity(False)
208 address_completer.setModel(self.address_completions)
209 self.address_input.setCompleter(address_completer)
211 address_layout = QHBoxLayout()
212 address_layout.addWidget(self.address_input)
214 self.amount_input = QLineEdit()
215 self.amount_input.setPlaceholderText(_("... and amount") + " (%s)"%self.actuator.g.base_unit())
216 self.amount_input.setObjectName("amount_input")
218 self.amount_input.setFocusPolicy(Qt.ClickFocus)
219 # This is changed according to the user's displayed balance
220 self.amount_validator = QDoubleValidator(self.amount_input)
221 self.amount_validator.setNotation(QDoubleValidator.StandardNotation)
222 self.amount_validator.setDecimals(8)
223 self.amount_input.setValidator(self.amount_validator)
225 # This removes the very ugly OSX highlighting, please leave this in :D
226 self.address_input.setAttribute(Qt.WA_MacShowFocusRect, 0)
227 self.amount_input.setAttribute(Qt.WA_MacShowFocusRect, 0)
228 self.amount_input.textChanged.connect(self.amount_input_changed)
230 #if self.actuator.g.wallet.seed:
231 self.send_button = QPushButton(_("&Send"))
233 # self.send_button = QPushButton(_("&Create"))
235 self.send_button.setObjectName("send_button")
236 self.send_button.setDisabled(True);
237 self.send_button.clicked.connect(self.send)
239 # Creating the receive button
240 self.switch_button = QPushButton( QIcon(":icons/switchgui.png"),'' )
241 self.switch_button.setMaximumWidth(25)
242 self.switch_button.setFlat(True)
243 self.switch_button.clicked.connect(expand_callback)
245 main_layout = QGridLayout(self)
247 main_layout.addWidget(self.balance_label, 0, 0, 1, 3)
248 main_layout.addWidget(self.switch_button, 0, 3)
250 main_layout.addWidget(self.address_input, 1, 0, 1, 4)
251 main_layout.addWidget(self.amount_input, 2, 0, 1, 2)
252 main_layout.addWidget(self.send_button, 2, 2, 1, 2)
254 self.send_button.setMaximumWidth(125)
256 self.history_list = history_widget.HistoryWidget()
257 self.history_list.setObjectName("history")
258 self.history_list.hide()
259 self.history_list.setAlternatingRowColors(True)
261 main_layout.addWidget(self.history_list, 3, 0, 1, 4)
263 self.receiving = receiving_widget.ReceivingWidget(self)
264 self.receiving.setObjectName("receiving")
266 # Add to the right side
267 self.receiving_box = QGroupBox(_("Select a receiving address"))
268 extra_layout = QGridLayout()
270 # Checkbox to filter used addresses
271 hide_used = QCheckBox(_('Hide used addresses'))
272 hide_used.setChecked(True)
273 hide_used.stateChanged.connect(self.receiving.toggle_used)
275 # Events for receiving addresses
276 self.receiving.clicked.connect(self.receiving.copy_address)
277 self.receiving.itemDoubleClicked.connect(self.receiving.edit_label)
278 self.receiving.itemChanged.connect(self.receiving.update_label)
282 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)
284 extra_layout.addWidget(self.receiving, 1,0)
285 extra_layout.addWidget(hide_used, 2,0)
286 extra_layout.setColumnMinimumWidth(0,200)
288 self.receiving_box.setLayout(extra_layout)
289 main_layout.addWidget(self.receiving_box,0,4,-1,3)
290 self.receiving_box.hide()
292 self.main_layout = main_layout
294 quit_shortcut = QShortcut(QKeySequence("Ctrl+Q"), self)
295 quit_shortcut.activated.connect(self.close)
296 close_shortcut = QShortcut(QKeySequence("Ctrl+W"), self)
297 close_shortcut.activated.connect(self.close)
299 g = self.config.get("winpos-lite",[4, 25, 351, 149])
300 self.setGeometry(g[0], g[1], g[2], g[3])
302 show_hist = self.config.get("gui_show_history",False)
303 self.show_history(show_hist)
304 show_hist = self.config.get("gui_show_receiving",False)
305 self.toggle_receiving_layout(show_hist)
307 self.setWindowIcon(QIcon(":icons/electrum.png"))
308 self.setWindowTitle("Electrum")
309 self.setWindowFlags(Qt.Window|Qt.MSWindowsFixedSizeDialogHint)
310 self.layout().setSizeConstraint(QLayout.SetFixedSize)
311 self.setObjectName("main_window")
314 def context_menu(self):
316 themes_menu = view_menu.addMenu(_("&Themes"))
317 selected_theme = self.actuator.selected_theme()
318 theme_group = QActionGroup(self)
319 for theme_name in self.actuator.theme_names():
320 theme_action = themes_menu.addAction(theme_name)
321 theme_action.setCheckable(True)
322 if selected_theme == theme_name:
323 theme_action.setChecked(True)
324 class SelectThemeFunctor:
325 def __init__(self, theme_name, toggle_theme):
326 self.theme_name = theme_name
327 self.toggle_theme = toggle_theme
328 def __call__(self, checked):
330 self.toggle_theme(self.theme_name)
331 delegate = SelectThemeFunctor(theme_name, self.toggle_theme)
332 theme_action.toggled.connect(delegate)
333 theme_group.addAction(theme_action)
334 view_menu.addSeparator()
336 show_receiving = view_menu.addAction(_("Show Receiving addresses"))
337 show_receiving.setCheckable(True)
338 show_receiving.toggled.connect(self.toggle_receiving_layout)
339 show_receiving.setChecked(self.config.get("gui_show_receiving",False))
341 show_history = view_menu.addAction(_("Show History"))
342 show_history.setCheckable(True)
343 show_history.toggled.connect(self.show_history)
344 show_history.setChecked(self.config.get("gui_show_history",False))
350 def toggle_theme(self, theme_name):
351 old_path = QDir.currentPath()
352 self.actuator.change_theme(theme_name)
353 # Recompute style globally
354 qApp.style().unpolish(self)
355 qApp.style().polish(self)
356 QDir.setCurrent(old_path)
358 def closeEvent(self, event):
360 self.config.set_key("winpos-lite", [g.left(),g.top(),g.width(),g.height()],True)
361 self.actuator.g.closeEvent(event)
364 def set_payment_fields(self, dest_address, amount):
365 self.address_input.setText(dest_address)
366 self.address_field_changed(dest_address)
367 self.amount_input.setText(amount)
372 def deactivate(self):
375 def set_exchange(self, use_exchange):
376 if use_exchange not in self.use_exchanges:
378 self.use_exchanges.remove(use_exchange)
379 self.use_exchanges.insert(0, use_exchange)
380 self.refresh_balance()
382 def set_quote_currency(self, currency):
383 """Set and display the fiat currency country."""
384 if currency not in self.quote_currencies:
386 self.quote_currencies.remove(currency)
387 self.quote_currencies.insert(0, currency)
388 self.refresh_balance()
390 def change_quote_currency(self, forward=True):
392 self.quote_currencies = \
393 self.quote_currencies[1:] + self.quote_currencies[0:1]
395 self.quote_currencies = \
396 self.quote_currencies[-1:] + self.quote_currencies[0:-1]
397 self.actuator.set_config_currency(self.quote_currencies[0])
398 self.refresh_balance()
400 def refresh_balance(self):
401 if self.btc_balance is None:
402 # Price has been discovered before wallet has been loaded
403 # and server connect... so bail.
405 self.set_balances(self.btc_balance)
406 self.amount_input_changed(self.amount_input.text())
408 def set_balances(self, btc_balance):
409 """Set the bitcoin balance and update the amount label accordingly."""
410 self.btc_balance = btc_balance
411 quote_text = self.create_quote_text(btc_balance)
413 quote_text = "(%s)" % quote_text
415 amount = self.actuator.g.format_amount(btc_balance)
416 unit = self.actuator.g.base_unit()
418 self.balance_label.set_balance_text(amount, unit, quote_text)
419 self.setWindowTitle("Electrum %s - %s %s" % (electrum_version, amount, unit))
421 def amount_input_changed(self, amount_text):
422 """Update the number of bitcoins displayed."""
423 self.check_button_status()
426 amount = D(str(amount_text)) * (10**self.actuator.g.decimal_point)
427 except decimal.InvalidOperation:
428 self.balance_label.show_balance()
430 quote_text = self.create_quote_text(amount)
432 self.balance_label.set_amount_text(quote_text)
433 self.balance_label.show_amount()
435 self.balance_label.show_balance()
437 def create_quote_text(self, btc_balance):
438 """Return a string copy of the amount fiat currency the
439 user has in bitcoins."""
440 from electrum.plugins import run_hook
442 run_hook('set_quote_text', btc_balance, r)
446 if self.actuator.send(self.address_input.text(),
447 self.amount_input.text(), self):
448 self.address_input.setText("")
449 self.amount_input.setText("")
451 def check_button_status(self):
452 """Check that the bitcoin address is valid and that something
453 is entered in the amount before making the send button clickable."""
455 value = D(str(self.amount_input.text())) * (10**self.actuator.g.decimal_point)
456 except decimal.InvalidOperation:
458 # self.address_input.property(...) returns a qVariant, not a bool.
459 # The == is needed to properly invoke a comparison.
460 if (self.address_input.property("isValid") == True and
461 value is not None and 0 < value <= self.btc_balance):
462 self.send_button.setDisabled(False)
464 self.send_button.setDisabled(True)
466 def address_field_changed(self, address):
467 # label or alias, with address in brackets
468 match2 = re.match("(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>",
471 address = match2.group(2)
472 self.address_input.setText(address)
474 if is_valid(address):
475 self.check_button_status()
476 self.address_input.setProperty("isValid", True)
477 self.recompute_style(self.address_input)
479 self.send_button.setDisabled(True)
480 self.address_input.setProperty("isValid", False)
481 self.recompute_style(self.address_input)
483 if len(address) == 0:
484 self.address_input.setProperty("isValid", None)
485 self.recompute_style(self.address_input)
487 def recompute_style(self, element):
488 self.style().unpolish(element)
489 self.style().polish(element)
491 def copy_address(self):
492 receive_popup = ReceivePopup(self.receive_button)
493 self.actuator.copy_address(receive_popup)
495 def update_completions(self, completions):
496 self.address_completions.setStringList(completions)
499 def update_history(self, tx_history):
501 self.history_list.empty()
503 for item in tx_history[-10:]:
504 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
505 label = self.actuator.g.wallet.get_label(tx_hash)[0]
506 v_str = self.actuator.g.format_amount(value, True)
507 self.history_list.append(label, v_str, age(timestamp))
510 def the_website(self):
511 webbrowser.open("http://electrum.org")
514 def toggle_receiving_layout(self, toggle_state):
516 self.receiving_box.show()
518 self.receiving_box.hide()
519 self.config.set_key("gui_show_receiving", toggle_state)
521 def show_history(self, toggle_state):
523 self.main_layout.setRowMinimumHeight(3,200)
524 self.history_list.show()
526 self.main_layout.setRowMinimumHeight(3,0)
527 self.history_list.hide()
528 self.config.set_key("gui_show_history", toggle_state)
530 class BalanceLabel(QLabel):
536 def __init__(self, change_quote_currency, parent=None):
537 super(QLabel, self).__init__(_("Connecting..."), parent)
538 self.change_quote_currency = change_quote_currency
539 self.state = self.SHOW_CONNECTING
540 self.balance_text = ""
541 self.amount_text = ""
544 def mousePressEvent(self, event):
545 """Change the fiat currency selection if window background is clicked."""
546 if self.state != self.SHOW_CONNECTING:
547 if event.button() == Qt.LeftButton:
548 self.change_quote_currency()
550 position = event.globalPos()
551 menu = self.parent.context_menu()
555 def set_balance_text(self, amount, unit, quote_text):
556 """Set the amount of bitcoins in the gui."""
557 if self.state == self.SHOW_CONNECTING:
558 self.state = self.SHOW_BALANCE
560 self.balance_text = "<span style='font-size: 18pt'>%s</span>"%amount\
561 + " <span style='font-size: 10pt'>%s</span>" % unit \
562 + " <span style='font-size: 10pt'>%s</span>" % quote_text
564 if self.state == self.SHOW_BALANCE:
565 self.setText(self.balance_text)
567 def set_amount_text(self, quote_text):
568 self.amount_text = "<span style='font-size: 10pt'>%s</span>" % quote_text
569 if self.state == self.SHOW_AMOUNT:
570 self.setText(self.amount_text)
572 def show_balance(self):
573 if self.state == self.SHOW_AMOUNT:
574 self.state = self.SHOW_BALANCE
575 self.setText(self.balance_text)
577 def show_amount(self):
578 if self.state == self.SHOW_BALANCE:
579 self.state = self.SHOW_AMOUNT
580 self.setText(self.amount_text)
582 def ok_cancel_buttons(dialog):
583 row_layout = QHBoxLayout()
584 row_layout.addStretch(1)
585 ok_button = QPushButton(_("OK"))
586 row_layout.addWidget(ok_button)
587 ok_button.clicked.connect(dialog.accept)
588 cancel_button = QPushButton(_("Cancel"))
589 row_layout.addWidget(cancel_button)
590 cancel_button.clicked.connect(dialog.reject)
593 class PasswordDialog(QDialog):
595 def __init__(self, parent):
596 super(QDialog, self).__init__(parent)
600 self.password_input = QLineEdit()
601 self.password_input.setEchoMode(QLineEdit.Password)
603 main_layout = QVBoxLayout(self)
604 message = _('Please enter your password')
605 main_layout.addWidget(QLabel(message))
609 grid.addWidget(QLabel(_('Password')), 1, 0)
610 grid.addWidget(self.password_input, 1, 1)
611 main_layout.addLayout(grid)
613 main_layout.addLayout(ok_cancel_buttons(self))
614 self.setLayout(main_layout)
619 return unicode(self.password_input.text())
621 class ReceivePopup(QDialog):
623 def leaveEvent(self, event):
626 def setup(self, address):
627 label = QLabel(_("Copied your Bitcoin address to the clipboard!"))
628 address_display = QLineEdit(address)
629 address_display.setReadOnly(True)
630 resize_line_edit_width(address_display, address)
632 main_layout = QVBoxLayout(self)
633 main_layout.addWidget(label)
634 main_layout.addWidget(address_display)
636 self.setMouseTracking(True)
637 self.setWindowTitle("Electrum - " + _("Receive Bitcoin payment"))
638 self.setWindowFlags(Qt.Window|Qt.FramelessWindowHint|
639 Qt.MSWindowsFixedSizeDialogHint)
640 self.layout().setSizeConstraint(QLayout.SetFixedSize)
641 #self.setFrameStyle(QFrame.WinPanel|QFrame.Raised)
642 #self.setAlignment(Qt.AlignCenter)
645 parent = self.parent()
646 top_left_pos = parent.mapToGlobal(parent.rect().bottomLeft())
647 self.move(top_left_pos)
648 center_mouse_pos = self.mapToGlobal(self.rect().center())
649 QCursor.setPos(center_mouse_pos)
653 """Initialize the definitions relating to themes and
654 sending/receiving bitcoins."""
657 def __init__(self, main_window):
658 """Retrieve the gui theme used in previous session."""
660 self.theme_name = self.g.config.get('litegui_theme','Cleanlook')
661 self.themes = load_theme_paths()
663 def load_theme(self):
664 """Load theme retrieved from wallet file."""
666 theme_prefix, theme_path = self.themes[self.theme_name]
668 util.print_error("Theme not found!", self.theme_name)
670 QDir.setCurrent(os.path.join(theme_prefix, theme_path))
671 with open(rsrc("style.css")) as style_file:
672 qApp.setStyleSheet(style_file.read())
674 def theme_names(self):
676 return sorted(self.themes.keys())
678 def selected_theme(self):
680 return self.theme_name
682 def change_theme(self, theme_name):
684 self.theme_name = theme_name
685 self.g.config.set_key('litegui_theme',theme_name)
688 def set_configured_exchange(self, set_exchange):
689 use_exchange = self.g.config.get('use_exchange')
690 if use_exchange is not None:
691 set_exchange(use_exchange)
693 def set_configured_currency(self, set_quote_currency):
694 """Set the inital fiat currency conversion country (USD/EUR/GBP) in
695 the GUI to what it was set to in the wallet."""
696 currency = self.g.config.get('currency')
697 # currency can be none when Electrum is used for the first
698 # time and no setting has been created yet.
699 if currency is not None:
700 set_quote_currency(currency)
702 def set_config_exchange(self, conversion_exchange):
703 self.g.config.set_key('exchange',conversion_exchange,True)
704 self.g.update_status()
706 def set_config_currency(self, conversion_currency):
707 """Change the wallet fiat currency country."""
708 self.g.config.set_key('currency',conversion_currency,True)
709 self.g.update_status()
711 def copy_address(self, receive_popup):
712 """Copy the wallet addresses into the client."""
713 addrs = [addr for addr in self.g.wallet.addresses(True)
714 if not self.g.wallet.is_change(addr)]
715 # Select most recent addresses from gap limit
716 addrs = addrs[-self.g.wallet.gap_limit:]
717 copied_address = random.choice(addrs)
718 qApp.clipboard().setText(copied_address)
719 receive_popup.setup(copied_address)
720 receive_popup.popup()
722 def waiting_dialog(self, f):
727 w.setWindowTitle('Electrum')
728 l = QLabel(_('Sending transaction, please wait.'))
737 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
742 def send(self, address, amount, parent_window):
743 """Send bitcoins to the target address."""
744 dest_address = self.fetch_destination(address)
746 if dest_address is None or not is_valid(dest_address):
747 QMessageBox.warning(parent_window, _('Error'),
748 _('Invalid Bitcoin Address') + ':\n' + address, _('OK'))
751 amount = D(unicode(amount)) * (10*self.g.decimal_point)
752 print "amount", amount
755 if self.g.wallet.use_encryption:
756 password_dialog = PasswordDialog(parent_window)
757 password = password_dialog.run()
765 if amount < bitcoin(1) / 10:
767 fee = bitcoin(1) / 1000
770 tx = self.g.wallet.mktx([(dest_address, amount)], password, fee)
771 except Exception as error:
772 QMessageBox.warning(parent_window, _('Error'), str(error), _('OK'))
776 h = self.g.wallet.send_tx(tx)
778 self.waiting_dialog(lambda: False if self.g.wallet.tx_event.isSet() else _("Sending transaction, please wait..."))
780 status, message = self.g.wallet.receive_tx(h, tx)
784 dumpf = tempfile.NamedTemporaryFile(delete=False)
787 print "Dumped error tx to", dumpf.name
788 QMessageBox.warning(parent_window, _('Error'), message, _('OK'))
791 TransactionWindow(message, self)
793 filename = 'unsigned_tx_%s' % (time.mktime(time.gmtime()))
795 fileName = QFileDialog.getSaveFileName(QWidget(), _("Select a transaction filename"), os.path.expanduser('~/%s' % (filename)))
796 with open(fileName,'w') as f:
797 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
798 QMessageBox.information(QWidget(), _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
799 except Exception as e:
800 QMessageBox.warning(QWidget(), _('Error'), _('Could not write transaction to file: %s' % e), _('OK'))
803 def fetch_destination(self, address):
804 recipient = unicode(address).strip()
807 match1 = re.match("^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$",
810 # label or alias, with address in brackets
811 match2 = re.match("(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>",
816 self.g.wallet.get_alias(recipient, True,
817 self.show_message, self.question)
820 return match2.group(2)
828 class MiniDriver(QObject):
835 def __init__(self, main_window, mini_window):
836 super(QObject, self).__init__()
839 self.network = main_window.network
840 self.window = mini_window
843 self.network.register_callback('updated',self.update_callback)
844 self.network.register_callback('connected', self.update_callback)
845 self.network.register_callback('disconnected', self.update_callback)
850 self.connect(self, SIGNAL("updatesignal()"), self.update)
851 self.update_callback()
853 # This is a hack to workaround that Qt does not like changing the
854 # window properties from this other thread before the runloop has
856 def update_callback(self):
857 self.emit(SIGNAL("updatesignal()"))
862 elif not self.network.interface:
864 elif not self.network.interface.is_connected:
867 if self.g.wallet is None:
869 elif not self.g.wallet.up_to_date:
873 self.update_balance()
874 self.update_completions()
875 self.update_history()
876 self.window.receiving.update_list()
879 def initializing(self):
880 if self.state == self.INITIALIZING:
882 self.state = self.INITIALIZING
883 self.window.deactivate()
885 def connecting(self):
886 if self.state == self.CONNECTING:
888 self.state = self.CONNECTING
889 self.window.deactivate()
891 def synchronizing(self):
892 if self.state == self.SYNCHRONIZING:
894 self.state = self.SYNCHRONIZING
895 self.window.deactivate()
898 if self.state == self.READY:
900 self.state = self.READY
901 self.window.activate()
903 def update_balance(self):
904 conf_balance, unconf_balance = self.g.wallet.get_balance()
905 balance = D(conf_balance + unconf_balance)
906 self.window.set_balances(balance)
908 def update_completions(self):
910 for addr, label in self.g.wallet.labels.items():
911 if addr in self.g.wallet.addressbook:
912 completions.append("%s <%s>" % (label, addr))
913 self.window.update_completions(completions)
915 def update_history(self):
916 tx_history = self.g.wallet.get_tx_history()
917 self.window.update_history(tx_history)
920 if __name__ == "__main__":
921 app = QApplication(sys.argv)
922 with open(rsrc("style.css")) as style_file:
923 app.setStyleSheet(style_file.read())
925 sys.exit(app.exec_())