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()
665 def load_theme(self):
666 """Load theme retrieved from wallet file."""
668 theme_prefix, theme_path = self.themes[self.theme_name]
670 util.print_error("Theme not found!", self.theme_name)
672 full_theme_path = ("%s/%s/style.css" % (theme_prefix, theme_path))
673 with open(rsrc(full_theme_path)) as style_file:
674 qApp.setStyleSheet(style_file.read())
676 def theme_names(self):
678 return sorted(self.themes.keys())
680 def selected_theme(self):
682 return self.theme_name
684 def change_theme(self, theme_name):
686 self.theme_name = theme_name
687 self.g.config.set_key('litegui_theme',theme_name)
690 def set_configured_exchange(self, set_exchange):
691 use_exchange = self.g.config.get('use_exchange')
692 if use_exchange is not None:
693 set_exchange(use_exchange)
695 def set_configured_currency(self, set_quote_currency):
696 """Set the inital fiat currency conversion country (USD/EUR/GBP) in
697 the GUI to what it was set to in the wallet."""
698 currency = self.g.config.get('currency')
699 # currency can be none when Electrum is used for the first
700 # time and no setting has been created yet.
701 if currency is not None:
702 set_quote_currency(currency)
704 def set_config_exchange(self, conversion_exchange):
705 self.g.config.set_key('exchange',conversion_exchange,True)
706 self.g.update_status()
708 def set_config_currency(self, conversion_currency):
709 """Change the wallet fiat currency country."""
710 self.g.config.set_key('currency',conversion_currency,True)
711 self.g.update_status()
713 def copy_address(self, receive_popup):
714 """Copy the wallet addresses into the client."""
715 addrs = [addr for addr in self.g.wallet.addresses(True)
716 if not self.g.wallet.is_change(addr)]
717 # Select most recent addresses from gap limit
718 addrs = addrs[-self.g.wallet.gap_limit:]
719 copied_address = random.choice(addrs)
720 qApp.clipboard().setText(copied_address)
721 receive_popup.setup(copied_address)
722 receive_popup.popup()
724 def waiting_dialog(self, f):
729 w.setWindowTitle('Electrum')
730 l = QLabel(_('Sending transaction, please wait.'))
739 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
744 def send(self, address, amount, parent_window):
745 """Send bitcoins to the target address."""
746 dest_address = self.fetch_destination(address)
748 if dest_address is None or not is_valid(dest_address):
749 QMessageBox.warning(parent_window, _('Error'),
750 _('Invalid Bitcoin Address') + ':\n' + address, _('OK'))
753 amount = D(unicode(amount)) * (10*self.g.decimal_point)
754 print "amount", amount
757 if self.g.wallet.use_encryption:
758 password_dialog = PasswordDialog(parent_window)
759 password = password_dialog.run()
767 if amount < bitcoin(1) / 10:
769 fee = bitcoin(1) / 1000
772 tx = self.g.wallet.mktx([(dest_address, amount)], password, fee)
773 except Exception as error:
774 QMessageBox.warning(parent_window, _('Error'), str(error), _('OK'))
778 h = self.g.wallet.send_tx(tx)
780 self.waiting_dialog(lambda: False if self.g.wallet.tx_event.isSet() else _("Sending transaction, please wait..."))
782 status, message = self.g.wallet.receive_tx(h, tx)
786 dumpf = tempfile.NamedTemporaryFile(delete=False)
789 print "Dumped error tx to", dumpf.name
790 QMessageBox.warning(parent_window, _('Error'), message, _('OK'))
793 TransactionWindow(message, self)
795 filename = 'unsigned_tx_%s' % (time.mktime(time.gmtime()))
797 fileName = QFileDialog.getSaveFileName(QWidget(), _("Select a transaction filename"), os.path.expanduser('~/%s' % (filename)))
798 with open(fileName,'w') as f:
799 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
800 QMessageBox.information(QWidget(), _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
801 except Exception as e:
802 QMessageBox.warning(QWidget(), _('Error'), _('Could not write transaction to file: %s' % e), _('OK'))
805 def fetch_destination(self, address):
806 recipient = unicode(address).strip()
809 match1 = re.match("^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$",
812 # label or alias, with address in brackets
813 match2 = re.match("(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>",
818 self.g.wallet.get_alias(recipient, True,
819 self.show_message, self.question)
822 return match2.group(2)
830 class MiniDriver(QObject):
837 def __init__(self, main_window, mini_window):
838 super(QObject, self).__init__()
841 self.network = main_window.network
842 self.window = mini_window
845 self.network.register_callback('updated',self.update_callback)
846 self.network.register_callback('connected', self.update_callback)
847 self.network.register_callback('disconnected', self.update_callback)
852 self.connect(self, SIGNAL("updatesignal()"), self.update)
853 self.update_callback()
855 # This is a hack to workaround that Qt does not like changing the
856 # window properties from this other thread before the runloop has
858 def update_callback(self):
859 self.emit(SIGNAL("updatesignal()"))
864 elif not self.network.interface:
866 elif not self.network.interface.is_connected:
869 if self.g.wallet is None:
871 elif not self.g.wallet.up_to_date:
875 self.update_balance()
876 self.update_completions()
877 self.update_history()
878 self.window.receiving.update_list()
881 def initializing(self):
882 if self.state == self.INITIALIZING:
884 self.state = self.INITIALIZING
885 self.window.deactivate()
887 def connecting(self):
888 if self.state == self.CONNECTING:
890 self.state = self.CONNECTING
891 self.window.deactivate()
893 def synchronizing(self):
894 if self.state == self.SYNCHRONIZING:
896 self.state = self.SYNCHRONIZING
897 self.window.deactivate()
900 if self.state == self.READY:
902 self.state = self.READY
903 self.window.activate()
905 def update_balance(self):
906 conf_balance, unconf_balance = self.g.wallet.get_balance()
907 balance = D(conf_balance + unconf_balance)
908 self.window.set_balances(balance)
910 def update_completions(self):
912 for addr, label in self.g.wallet.labels.items():
913 if addr in self.g.wallet.addressbook:
914 completions.append("%s <%s>" % (label, addr))
915 self.window.update_completions(completions)
917 def update_history(self):
918 tx_history = self.g.wallet.get_tx_history()
919 self.window.update_history(tx_history)
922 if __name__ == "__main__":
923 app = QApplication(sys.argv)
924 with open(rsrc("style.css")) as style_file:
925 app.setStyleSheet(style_file.read())
927 sys.exit(app.exec_())