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 self.actuator.change_theme(theme_name)
352 # Recompute style globally
353 qApp.style().unpolish(self)
354 qApp.style().polish(self)
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_exchange(self, use_exchange):
374 if use_exchange not in self.use_exchanges:
376 self.use_exchanges.remove(use_exchange)
377 self.use_exchanges.insert(0, use_exchange)
378 self.refresh_balance()
380 def set_quote_currency(self, currency):
381 """Set and display the fiat currency country."""
382 if currency not in self.quote_currencies:
384 self.quote_currencies.remove(currency)
385 self.quote_currencies.insert(0, currency)
386 self.refresh_balance()
388 def change_quote_currency(self, forward=True):
390 self.quote_currencies = \
391 self.quote_currencies[1:] + self.quote_currencies[0:1]
393 self.quote_currencies = \
394 self.quote_currencies[-1:] + self.quote_currencies[0:-1]
395 self.actuator.set_config_currency(self.quote_currencies[0])
396 self.refresh_balance()
398 def refresh_balance(self):
399 if self.btc_balance is None:
400 # Price has been discovered before wallet has been loaded
401 # and server connect... so bail.
403 self.set_balances(self.btc_balance)
404 self.amount_input_changed(self.amount_input.text())
406 def set_balances(self, btc_balance):
407 """Set the bitcoin balance and update the amount label accordingly."""
408 self.btc_balance = btc_balance
409 quote_text = self.create_quote_text(btc_balance)
411 quote_text = "(%s)" % quote_text
413 amount = self.actuator.g.format_amount(btc_balance)
414 unit = self.actuator.g.base_unit()
416 self.balance_label.set_balance_text(amount, unit, quote_text)
417 self.setWindowTitle("Electrum %s - %s %s" % (electrum_version, amount, unit))
419 def amount_input_changed(self, amount_text):
420 """Update the number of bitcoins displayed."""
421 self.check_button_status()
424 amount = D(str(amount_text)) * (10**self.actuator.g.decimal_point)
425 except decimal.InvalidOperation:
426 self.balance_label.show_balance()
428 quote_text = self.create_quote_text(amount)
430 self.balance_label.set_amount_text(quote_text)
431 self.balance_label.show_amount()
433 self.balance_label.show_balance()
435 def create_quote_text(self, btc_balance):
436 """Return a string copy of the amount fiat currency the
437 user has in bitcoins."""
438 from electrum.plugins import run_hook
440 run_hook('get_fiat_balance_text', btc_balance, r)
444 if self.actuator.send(self.address_input.text(),
445 self.amount_input.text(), self):
446 self.address_input.setText("")
447 self.amount_input.setText("")
449 def check_button_status(self):
450 """Check that the bitcoin address is valid and that something
451 is entered in the amount before making the send button clickable."""
453 value = D(str(self.amount_input.text())) * (10**self.actuator.g.decimal_point)
454 except decimal.InvalidOperation:
456 # self.address_input.property(...) returns a qVariant, not a bool.
457 # The == is needed to properly invoke a comparison.
458 if (self.address_input.property("isValid") == True and
459 value is not None and 0 < value <= self.btc_balance):
460 self.send_button.setDisabled(False)
462 self.send_button.setDisabled(True)
464 def address_field_changed(self, address):
465 # label or alias, with address in brackets
466 match2 = re.match("(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>",
469 address = match2.group(2)
470 self.address_input.setText(address)
472 if is_valid(address):
473 self.check_button_status()
474 self.address_input.setProperty("isValid", True)
475 self.recompute_style(self.address_input)
477 self.send_button.setDisabled(True)
478 self.address_input.setProperty("isValid", False)
479 self.recompute_style(self.address_input)
481 if len(address) == 0:
482 self.address_input.setProperty("isValid", None)
483 self.recompute_style(self.address_input)
485 def recompute_style(self, element):
486 self.style().unpolish(element)
487 self.style().polish(element)
489 def copy_address(self):
490 receive_popup = ReceivePopup(self.receive_button)
491 self.actuator.copy_address(receive_popup)
493 def update_completions(self, completions):
494 self.address_completions.setStringList(completions)
497 def update_history(self, tx_history):
499 self.history_list.empty()
501 for item in tx_history[-10:]:
502 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
503 label = self.actuator.g.wallet.get_label(tx_hash)[0]
504 v_str = self.actuator.g.format_amount(value, True)
505 self.history_list.append(label, v_str, age(timestamp))
508 def the_website(self):
509 webbrowser.open("http://electrum.org")
512 def toggle_receiving_layout(self, toggle_state):
514 self.receiving_box.show()
516 self.receiving_box.hide()
517 self.config.set_key("gui_show_receiving", toggle_state)
519 def show_history(self, toggle_state):
521 self.main_layout.setRowMinimumHeight(3,200)
522 self.history_list.show()
524 self.main_layout.setRowMinimumHeight(3,0)
525 self.history_list.hide()
526 self.config.set_key("gui_show_history", toggle_state)
528 class BalanceLabel(QLabel):
534 def __init__(self, change_quote_currency, parent=None):
535 super(QLabel, self).__init__(_("Connecting..."), parent)
536 self.change_quote_currency = change_quote_currency
537 self.state = self.SHOW_CONNECTING
538 self.balance_text = ""
539 self.amount_text = ""
542 def mousePressEvent(self, event):
543 """Change the fiat currency selection if window background is clicked."""
544 if self.state != self.SHOW_CONNECTING:
545 if event.button() == Qt.LeftButton:
546 self.change_quote_currency()
548 position = event.globalPos()
549 menu = self.parent.context_menu()
553 def set_balance_text(self, amount, unit, quote_text):
554 """Set the amount of bitcoins in the gui."""
555 if self.state == self.SHOW_CONNECTING:
556 self.state = self.SHOW_BALANCE
558 self.balance_text = "<span style='font-size: 18pt'>%s</span>"%amount\
559 + " <span style='font-size: 10pt'>%s</span>" % unit \
560 + " <span style='font-size: 10pt'>%s</span>" % quote_text
562 if self.state == self.SHOW_BALANCE:
563 self.setText(self.balance_text)
565 def set_amount_text(self, quote_text):
566 self.amount_text = "<span style='font-size: 10pt'>%s</span>" % quote_text
567 if self.state == self.SHOW_AMOUNT:
568 self.setText(self.amount_text)
570 def show_balance(self):
571 if self.state == self.SHOW_AMOUNT:
572 self.state = self.SHOW_BALANCE
573 self.setText(self.balance_text)
575 def show_amount(self):
576 if self.state == self.SHOW_BALANCE:
577 self.state = self.SHOW_AMOUNT
578 self.setText(self.amount_text)
580 def ok_cancel_buttons(dialog):
581 row_layout = QHBoxLayout()
582 row_layout.addStretch(1)
583 ok_button = QPushButton(_("OK"))
584 row_layout.addWidget(ok_button)
585 ok_button.clicked.connect(dialog.accept)
586 cancel_button = QPushButton(_("Cancel"))
587 row_layout.addWidget(cancel_button)
588 cancel_button.clicked.connect(dialog.reject)
591 class PasswordDialog(QDialog):
593 def __init__(self, parent):
594 super(QDialog, self).__init__(parent)
598 self.password_input = QLineEdit()
599 self.password_input.setEchoMode(QLineEdit.Password)
601 main_layout = QVBoxLayout(self)
602 message = _('Please enter your password')
603 main_layout.addWidget(QLabel(message))
607 grid.addWidget(QLabel(_('Password')), 1, 0)
608 grid.addWidget(self.password_input, 1, 1)
609 main_layout.addLayout(grid)
611 main_layout.addLayout(ok_cancel_buttons(self))
612 self.setLayout(main_layout)
617 return unicode(self.password_input.text())
619 class ReceivePopup(QDialog):
621 def leaveEvent(self, event):
624 def setup(self, address):
625 label = QLabel(_("Copied your Bitcoin address to the clipboard!"))
626 address_display = QLineEdit(address)
627 address_display.setReadOnly(True)
628 resize_line_edit_width(address_display, address)
630 main_layout = QVBoxLayout(self)
631 main_layout.addWidget(label)
632 main_layout.addWidget(address_display)
634 self.setMouseTracking(True)
635 self.setWindowTitle("Electrum - " + _("Receive Bitcoin payment"))
636 self.setWindowFlags(Qt.Window|Qt.FramelessWindowHint|
637 Qt.MSWindowsFixedSizeDialogHint)
638 self.layout().setSizeConstraint(QLayout.SetFixedSize)
639 #self.setFrameStyle(QFrame.WinPanel|QFrame.Raised)
640 #self.setAlignment(Qt.AlignCenter)
643 parent = self.parent()
644 top_left_pos = parent.mapToGlobal(parent.rect().bottomLeft())
645 self.move(top_left_pos)
646 center_mouse_pos = self.mapToGlobal(self.rect().center())
647 QCursor.setPos(center_mouse_pos)
651 """Initialize the definitions relating to themes and
652 sending/receiving bitcoins."""
655 def __init__(self, main_window):
656 """Retrieve the gui theme used in previous session."""
658 self.theme_name = self.g.config.get('litegui_theme','Cleanlook')
659 self.themes = load_theme_paths()
662 def load_theme(self):
663 """Load theme retrieved from wallet file."""
665 theme_prefix, theme_path = self.themes[self.theme_name]
667 util.print_error("Theme not found!", self.theme_name)
669 full_theme_path = "%s/%s/style.css" % (theme_prefix, theme_path)
670 with open(full_theme_path) as style_file:
671 qApp.setStyleSheet(style_file.read())
673 def theme_names(self):
675 return sorted(self.themes.keys())
677 def selected_theme(self):
679 return self.theme_name
681 def change_theme(self, theme_name):
683 self.theme_name = theme_name
684 self.g.config.set_key('litegui_theme',theme_name)
687 def set_configured_exchange(self, set_exchange):
688 use_exchange = self.g.config.get('use_exchange')
689 if use_exchange is not None:
690 set_exchange(use_exchange)
692 def set_configured_currency(self, set_quote_currency):
693 """Set the inital fiat currency conversion country (USD/EUR/GBP) in
694 the GUI to what it was set to in the wallet."""
695 currency = self.g.config.get('currency')
696 # currency can be none when Electrum is used for the first
697 # time and no setting has been created yet.
698 if currency is not None:
699 set_quote_currency(currency)
701 def set_config_exchange(self, conversion_exchange):
702 self.g.config.set_key('exchange',conversion_exchange,True)
703 self.g.update_status()
705 def set_config_currency(self, conversion_currency):
706 """Change the wallet fiat currency country."""
707 self.g.config.set_key('currency',conversion_currency,True)
708 self.g.update_status()
710 def copy_address(self, receive_popup):
711 """Copy the wallet addresses into the client."""
712 addrs = [addr for addr in self.g.wallet.addresses(True)
713 if not self.g.wallet.is_change(addr)]
714 # Select most recent addresses from gap limit
715 addrs = addrs[-self.g.wallet.gap_limit:]
716 copied_address = random.choice(addrs)
717 qApp.clipboard().setText(copied_address)
718 receive_popup.setup(copied_address)
719 receive_popup.popup()
721 def waiting_dialog(self, f):
726 w.setWindowTitle('Electrum')
727 l = QLabel(_('Sending transaction, please wait.'))
736 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
741 def send(self, address, amount, parent_window):
742 """Send bitcoins to the target address."""
743 dest_address = self.fetch_destination(address)
745 if dest_address is None or not is_valid(dest_address):
746 QMessageBox.warning(parent_window, _('Error'),
747 _('Invalid Bitcoin Address') + ':\n' + address, _('OK'))
750 amount = D(unicode(amount)) * (10*self.g.decimal_point)
751 print "amount", amount
754 if self.g.wallet.use_encryption:
755 password_dialog = PasswordDialog(parent_window)
756 password = password_dialog.run()
764 if amount < bitcoin(1) / 10:
766 fee = bitcoin(1) / 1000
769 tx = self.g.wallet.mktx([(dest_address, amount)], password, fee)
770 except Exception as error:
771 QMessageBox.warning(parent_window, _('Error'), str(error), _('OK'))
775 h = self.g.wallet.send_tx(tx)
777 self.waiting_dialog(lambda: False if self.g.wallet.tx_event.isSet() else _("Sending transaction, please wait..."))
779 status, message = self.g.wallet.receive_tx(h, tx)
783 dumpf = tempfile.NamedTemporaryFile(delete=False)
786 print "Dumped error tx to", dumpf.name
787 QMessageBox.warning(parent_window, _('Error'), message, _('OK'))
790 TransactionWindow(message, self)
792 filename = 'unsigned_tx_%s' % (time.mktime(time.gmtime()))
794 fileName = QFileDialog.getSaveFileName(QWidget(), _("Select a transaction filename"), os.path.expanduser('~/%s' % (filename)))
795 with open(fileName,'w') as f:
796 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
797 QMessageBox.information(QWidget(), _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
798 except Exception as e:
799 QMessageBox.warning(QWidget(), _('Error'), _('Could not write transaction to file: %s' % e), _('OK'))
802 def fetch_destination(self, address):
803 recipient = unicode(address).strip()
806 match1 = re.match("^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$",
809 # label or alias, with address in brackets
810 match2 = re.match("(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>",
815 self.g.wallet.get_alias(recipient, True,
816 self.show_message, self.question)
819 return match2.group(2)
827 class MiniDriver(QObject):
834 def __init__(self, main_window, mini_window):
835 super(QObject, self).__init__()
838 self.network = main_window.network
839 self.window = mini_window
842 self.network.register_callback('updated',self.update_callback)
843 self.network.register_callback('connected', self.update_callback)
844 self.network.register_callback('disconnected', self.update_callback)
849 self.connect(self, SIGNAL("updatesignal()"), self.update)
850 self.update_callback()
852 # This is a hack to workaround that Qt does not like changing the
853 # window properties from this other thread before the runloop has
855 def update_callback(self):
856 self.emit(SIGNAL("updatesignal()"))
861 elif not self.network.interface:
863 elif not self.network.interface.is_connected:
866 if self.g.wallet is None:
868 elif not self.g.wallet.up_to_date:
872 self.update_balance()
873 self.update_completions()
874 self.update_history()
875 self.window.receiving.update_list()
878 def initializing(self):
879 if self.state == self.INITIALIZING:
881 self.state = self.INITIALIZING
882 self.window.deactivate()
884 def connecting(self):
885 if self.state == self.CONNECTING:
887 self.state = self.CONNECTING
888 self.window.deactivate()
890 def synchronizing(self):
891 if self.state == self.SYNCHRONIZING:
893 self.state = self.SYNCHRONIZING
894 self.window.deactivate()
897 if self.state == self.READY:
899 self.state = self.READY
900 self.window.activate()
902 def update_balance(self):
903 conf_balance, unconf_balance = self.g.wallet.get_balance()
904 balance = D(conf_balance + unconf_balance)
905 self.window.set_balances(balance)
907 def update_completions(self):
909 for addr, label in self.g.wallet.labels.items():
910 if addr in self.g.wallet.addressbook:
911 completions.append("%s <%s>" % (label, addr))
912 self.window.update_completions(completions)
914 def update_history(self):
915 tx_history = self.g.wallet.get_tx_history()
916 self.window.update_history(tx_history)
919 if __name__ == "__main__":
920 app = QApplication(sys.argv)
921 with open(rsrc("style.css")) as style_file:
922 app.setStyleSheet(style_file.read())
924 sys.exit(app.exec_())