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__()
176 tx = "e08115d0f7819aee65b9d24f81ef9d46eb62bb67ddef5318156cbc3ceb7b703e"
178 self.actuator = actuator
180 self.btc_balance = None
181 self.quote_currencies = ["BRL", "CNY", "EUR", "GBP", "RUB", "USD"]
182 self.actuator.set_configured_currency(self.set_quote_currency)
183 #self.exchanger = exchange_rate.Exchanger(self)
184 # Needed because price discovery is done in a different thread
185 # which needs to be sent back to this main one to update the GUI
186 self.connect(self, SIGNAL("refresh_balance()"), self.refresh_balance)
188 self.balance_label = BalanceLabel(self.change_quote_currency, self)
189 self.balance_label.setObjectName("balance_label")
192 # Bitcoin address code
193 self.address_input = QLineEdit()
194 self.address_input.setPlaceholderText(_("Enter a Bitcoin address or contact"))
195 self.address_input.setObjectName("address_input")
197 self.address_input.setFocusPolicy(Qt.ClickFocus)
199 self.address_input.textChanged.connect(self.address_field_changed)
200 resize_line_edit_width(self.address_input,
201 "1BtaFUr3qVvAmwrsuDuu5zk6e4s2rxd2Gy")
203 self.address_completions = QStringListModel()
204 address_completer = QCompleter(self.address_input)
205 address_completer.setCaseSensitivity(False)
206 address_completer.setModel(self.address_completions)
207 self.address_input.setCompleter(address_completer)
209 address_layout = QHBoxLayout()
210 address_layout.addWidget(self.address_input)
212 self.amount_input = QLineEdit()
213 self.amount_input.setPlaceholderText(_("... and amount"))
214 self.amount_input.setObjectName("amount_input")
216 self.amount_input.setFocusPolicy(Qt.ClickFocus)
217 # This is changed according to the user's displayed balance
218 self.amount_validator = QDoubleValidator(self.amount_input)
219 self.amount_validator.setNotation(QDoubleValidator.StandardNotation)
220 self.amount_validator.setDecimals(8)
221 self.amount_input.setValidator(self.amount_validator)
223 # This removes the very ugly OSX highlighting, please leave this in :D
224 self.address_input.setAttribute(Qt.WA_MacShowFocusRect, 0)
225 self.amount_input.setAttribute(Qt.WA_MacShowFocusRect, 0)
226 self.amount_input.textChanged.connect(self.amount_input_changed)
228 if self.actuator.wallet.seed:
229 self.send_button = QPushButton(_("&Send"))
231 self.send_button = QPushButton(_("&Create"))
233 self.send_button.setObjectName("send_button")
234 self.send_button.setDisabled(True);
235 self.send_button.clicked.connect(self.send)
237 # Creating the receive button
238 self.switch_button = QPushButton( QIcon(":icons/switchgui.png"),'' )
239 self.switch_button.setMaximumWidth(25)
240 self.switch_button.setFlat(True)
241 self.switch_button.clicked.connect(expand_callback)
243 main_layout = QGridLayout(self)
245 main_layout.addWidget(self.balance_label, 0, 0, 1, 3)
246 main_layout.addWidget(self.switch_button, 0, 3)
248 main_layout.addWidget(self.address_input, 1, 0, 1, 4)
249 main_layout.addWidget(self.amount_input, 2, 0, 1, 2)
250 main_layout.addWidget(self.send_button, 2, 2, 1, 2)
252 self.send_button.setMaximumWidth(125)
254 self.history_list = history_widget.HistoryWidget()
255 self.history_list.setObjectName("history")
256 self.history_list.hide()
257 self.history_list.setAlternatingRowColors(True)
259 main_layout.addWidget(self.history_list, 3, 0, 1, 4)
261 self.receiving = receiving_widget.ReceivingWidget(self)
262 self.receiving.setObjectName("receiving")
264 # Add to the right side
265 self.receiving_box = QGroupBox(_("Select a receiving address"))
266 extra_layout = QGridLayout()
268 # Checkbox to filter used addresses
269 hide_used = QCheckBox(_('Hide used addresses'))
270 hide_used.setChecked(True)
271 hide_used.stateChanged.connect(self.receiving.toggle_used)
273 # Events for receiving addresses
274 self.receiving.clicked.connect(self.receiving.copy_address)
275 self.receiving.itemDoubleClicked.connect(self.receiving.edit_label)
276 self.receiving.itemChanged.connect(self.receiving.update_label)
280 extra_layout.addWidget( QLabel(_('Selecting an address will copy it to the clipboard.') + '\n' + _('Double clicking the label will allow you to edit it.') ),0,0)
282 extra_layout.addWidget(self.receiving, 1,0)
283 extra_layout.addWidget(hide_used, 2,0)
284 extra_layout.setColumnMinimumWidth(0,200)
286 self.receiving_box.setLayout(extra_layout)
287 main_layout.addWidget(self.receiving_box,0,4,-1,3)
288 self.receiving_box.hide()
290 self.main_layout = main_layout
292 quit_shortcut = QShortcut(QKeySequence("Ctrl+Q"), self)
293 quit_shortcut.activated.connect(self.close)
294 close_shortcut = QShortcut(QKeySequence("Ctrl+W"), self)
295 close_shortcut.activated.connect(self.close)
297 g = self.config.get("winpos-lite",[4, 25, 351, 149])
298 self.setGeometry(g[0], g[1], g[2], g[3])
300 show_hist = self.config.get("gui_show_history",False)
301 self.show_history(show_hist)
302 show_hist = self.config.get("gui_show_receiving",False)
303 self.toggle_receiving_layout(show_hist)
305 self.setWindowIcon(QIcon(":icons/electrum.png"))
306 self.setWindowTitle("Electrum")
307 self.setWindowFlags(Qt.Window|Qt.MSWindowsFixedSizeDialogHint)
308 self.layout().setSizeConstraint(QLayout.SetFixedSize)
309 self.setObjectName("main_window")
313 def context_menu(self):
315 themes_menu = view_menu.addMenu(_("&Themes"))
316 selected_theme = self.actuator.selected_theme()
317 theme_group = QActionGroup(self)
318 for theme_name in self.actuator.theme_names():
319 theme_action = themes_menu.addAction(theme_name)
320 theme_action.setCheckable(True)
321 if selected_theme == theme_name:
322 theme_action.setChecked(True)
323 class SelectThemeFunctor:
324 def __init__(self, theme_name, toggle_theme):
325 self.theme_name = theme_name
326 self.toggle_theme = toggle_theme
327 def __call__(self, checked):
329 self.toggle_theme(self.theme_name)
330 delegate = SelectThemeFunctor(theme_name, self.toggle_theme)
331 theme_action.toggled.connect(delegate)
332 theme_group.addAction(theme_action)
333 view_menu.addSeparator()
335 show_receiving = view_menu.addAction(_("Show Receiving addresses"))
336 show_receiving.setCheckable(True)
337 show_receiving.toggled.connect(self.toggle_receiving_layout)
338 show_receiving.setChecked(self.config.get("gui_show_receiving",False))
340 show_history = view_menu.addAction(_("Show History"))
341 show_history.setCheckable(True)
342 show_history.toggled.connect(self.show_history)
343 show_history.setChecked(self.config.get("gui_show_history",False))
349 def toggle_theme(self, theme_name):
350 old_path = QDir.currentPath()
351 self.actuator.change_theme(theme_name)
352 # Recompute style globally
353 qApp.style().unpolish(self)
354 qApp.style().polish(self)
355 QDir.setCurrent(old_path)
357 def closeEvent(self, event):
359 self.config.set_key("winpos-lite", [g.left(),g.top(),g.width(),g.height()],True)
361 super(MiniWindow, self).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_quote_currency(self, currency):
376 """Set and display the fiat currency country."""
377 if currency not in self.quote_currencies:
379 self.quote_currencies.remove(currency)
380 self.quote_currencies.insert(0, currency)
381 self.refresh_balance()
383 def change_quote_currency(self, forward=True):
385 self.quote_currencies = \
386 self.quote_currencies[1:] + self.quote_currencies[0:1]
388 self.quote_currencies = \
389 self.quote_currencies[-1:] + self.quote_currencies[0:-1]
390 self.actuator.set_config_currency(self.quote_currencies[0])
391 self.refresh_balance()
393 def refresh_balance(self):
394 if self.btc_balance is None:
395 # Price has been discovered before wallet has been loaded
396 # and server connect... so bail.
398 self.set_balances(self.btc_balance)
399 self.amount_input_changed(self.amount_input.text())
401 def set_balances(self, btc_balance):
402 """Set the bitcoin balance and update the amount label accordingly."""
403 self.btc_balance = btc_balance
404 quote_text = self.create_quote_text(btc_balance)
406 quote_text = "(%s)" % quote_text
407 btc_balance = "%.4f" % (btc_balance / bitcoin(1))
408 self.balance_label.set_balance_text(btc_balance, quote_text)
409 self.setWindowTitle("Electrum %s - %s BTC" % (electrum_version, btc_balance))
411 def amount_input_changed(self, amount_text):
412 """Update the number of bitcoins displayed."""
413 self.check_button_status()
416 amount = D(str(amount_text))
417 except decimal.InvalidOperation:
418 self.balance_label.show_balance()
420 quote_text = self.create_quote_text(amount * bitcoin(1))
422 self.balance_label.set_amount_text(quote_text)
423 self.balance_label.show_amount()
425 self.balance_label.show_balance()
427 def create_quote_text(self, btc_balance):
428 """Return a string copy of the amount fiat currency the
429 user has in bitcoins."""
430 quote_currency = self.quote_currencies[0]
431 quote_balance = None #self.exchanger.exchange(btc_balance, quote_currency)
432 if quote_balance is None:
435 quote_text = "%.2f %s" % ((quote_balance / bitcoin(1)),
440 if self.actuator.send(self.address_input.text(),
441 self.amount_input.text(), self):
442 self.address_input.setText("")
443 self.amount_input.setText("")
445 def check_button_status(self):
446 """Check that the bitcoin address is valid and that something
447 is entered in the amount before making the send button clickable."""
449 value = D(str(self.amount_input.text())) * 10**8
450 except decimal.InvalidOperation:
452 # self.address_input.property(...) returns a qVariant, not a bool.
453 # The == is needed to properly invoke a comparison.
454 if (self.address_input.property("isValid") == True and
455 value is not None and 0 < value <= self.btc_balance):
456 self.send_button.setDisabled(False)
458 self.send_button.setDisabled(True)
460 def address_field_changed(self, address):
461 # label or alias, with address in brackets
462 match2 = re.match("(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>",
465 address = match2.group(2)
466 self.address_input.setText(address)
468 if is_valid(address):
469 self.check_button_status()
470 self.address_input.setProperty("isValid", True)
471 self.recompute_style(self.address_input)
473 self.send_button.setDisabled(True)
474 self.address_input.setProperty("isValid", False)
475 self.recompute_style(self.address_input)
477 if len(address) == 0:
478 self.address_input.setProperty("isValid", None)
479 self.recompute_style(self.address_input)
481 def recompute_style(self, element):
482 self.style().unpolish(element)
483 self.style().polish(element)
485 def copy_address(self):
486 receive_popup = ReceivePopup(self.receive_button)
487 self.actuator.copy_address(receive_popup)
489 def update_completions(self, completions):
490 self.address_completions.setStringList(completions)
493 def update_history(self, tx_history):
495 self.history_list.empty()
497 for item in tx_history[-10:]:
498 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
499 label = self.actuator.wallet.get_label(tx_hash)[0]
500 #amount = D(value) / 10**8
501 v_str = format_satoshis(value, True)
502 self.history_list.append(label, v_str, age(timestamp))
505 self.actuator.acceptbit(self.quote_currencies[0])
507 def the_website(self):
508 webbrowser.open("http://electrum.org")
511 def toggle_receiving_layout(self, toggle_state):
513 self.receiving_box.show()
515 self.receiving_box.hide()
516 self.config.set_key("gui_show_receiving", toggle_state)
518 def show_history(self, toggle_state):
520 self.main_layout.setRowMinimumHeight(3,200)
521 self.history_list.show()
523 self.main_layout.setRowMinimumHeight(3,0)
524 self.history_list.hide()
525 self.config.set_key("gui_show_history", toggle_state)
527 class BalanceLabel(QLabel):
533 def __init__(self, change_quote_currency, parent=None):
534 super(QLabel, self).__init__(_("Connecting..."), parent)
535 self.change_quote_currency = change_quote_currency
536 self.state = self.SHOW_CONNECTING
537 self.balance_text = ""
538 self.amount_text = ""
541 def mousePressEvent(self, event):
542 """Change the fiat currency selection if window background is clicked."""
543 if self.state != self.SHOW_CONNECTING:
544 if event.button() == Qt.LeftButton:
545 self.change_quote_currency()
547 position = event.globalPos()
548 menu = self.parent.context_menu()
552 def set_balance_text(self, btc_balance, quote_text):
553 """Set the amount of bitcoins in the gui."""
554 if self.state == self.SHOW_CONNECTING:
555 self.state = self.SHOW_BALANCE
556 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)
557 if self.state == self.SHOW_BALANCE:
558 self.setText(self.balance_text)
560 def set_amount_text(self, quote_text):
561 self.amount_text = "<span style='font-size: 10pt'>%s</span>" % quote_text
562 if self.state == self.SHOW_AMOUNT:
563 self.setText(self.amount_text)
565 def show_balance(self):
566 if self.state == self.SHOW_AMOUNT:
567 self.state = self.SHOW_BALANCE
568 self.setText(self.balance_text)
570 def show_amount(self):
571 if self.state == self.SHOW_BALANCE:
572 self.state = self.SHOW_AMOUNT
573 self.setText(self.amount_text)
575 def ok_cancel_buttons(dialog):
576 row_layout = QHBoxLayout()
577 row_layout.addStretch(1)
578 ok_button = QPushButton(_("OK"))
579 row_layout.addWidget(ok_button)
580 ok_button.clicked.connect(dialog.accept)
581 cancel_button = QPushButton(_("Cancel"))
582 row_layout.addWidget(cancel_button)
583 cancel_button.clicked.connect(dialog.reject)
586 class PasswordDialog(QDialog):
588 def __init__(self, parent):
589 super(QDialog, self).__init__(parent)
593 self.password_input = QLineEdit()
594 self.password_input.setEchoMode(QLineEdit.Password)
596 main_layout = QVBoxLayout(self)
597 message = _('Please enter your password')
598 main_layout.addWidget(QLabel(message))
602 grid.addWidget(QLabel(_('Password')), 1, 0)
603 grid.addWidget(self.password_input, 1, 1)
604 main_layout.addLayout(grid)
606 main_layout.addLayout(ok_cancel_buttons(self))
607 self.setLayout(main_layout)
612 return unicode(self.password_input.text())
614 class ReceivePopup(QDialog):
616 def leaveEvent(self, event):
619 def setup(self, address):
620 label = QLabel(_("Copied your Bitcoin address to the clipboard!"))
621 address_display = QLineEdit(address)
622 address_display.setReadOnly(True)
623 resize_line_edit_width(address_display, address)
625 main_layout = QVBoxLayout(self)
626 main_layout.addWidget(label)
627 main_layout.addWidget(address_display)
629 self.setMouseTracking(True)
630 self.setWindowTitle("Electrum - " + _("Receive Bitcoin payment"))
631 self.setWindowFlags(Qt.Window|Qt.FramelessWindowHint|
632 Qt.MSWindowsFixedSizeDialogHint)
633 self.layout().setSizeConstraint(QLayout.SetFixedSize)
634 #self.setFrameStyle(QFrame.WinPanel|QFrame.Raised)
635 #self.setAlignment(Qt.AlignCenter)
638 parent = self.parent()
639 top_left_pos = parent.mapToGlobal(parent.rect().bottomLeft())
640 self.move(top_left_pos)
641 center_mouse_pos = self.mapToGlobal(self.rect().center())
642 QCursor.setPos(center_mouse_pos)
646 """Initialize the definitions relating to themes and
647 sending/receiving bitcoins."""
650 def __init__(self, config, wallet):
651 """Retrieve the gui theme used in previous session."""
654 self.theme_name = self.config.get('litegui_theme','Cleanlook')
655 self.themes = load_theme_paths()
657 def load_theme(self):
658 """Load theme retrieved from wallet file."""
660 theme_prefix, theme_path = self.themes[self.theme_name]
662 util.print_error("Theme not found!", self.theme_name)
664 QDir.setCurrent(os.path.join(theme_prefix, theme_path))
665 with open(rsrc("style.css")) as style_file:
666 qApp.setStyleSheet(style_file.read())
668 def theme_names(self):
670 return sorted(self.themes.keys())
672 def selected_theme(self):
674 return self.theme_name
676 def change_theme(self, theme_name):
678 self.theme_name = theme_name
679 self.config.set_key('litegui_theme',theme_name)
682 def set_configured_currency(self, set_quote_currency):
683 """Set the inital fiat currency conversion country (USD/EUR/GBP) in
684 the GUI to what it was set to in the wallet."""
685 currency = self.config.get('currency')
686 # currency can be none when Electrum is used for the first
687 # time and no setting has been created yet.
688 if currency is not None:
689 set_quote_currency(currency)
691 def set_config_currency(self, conversion_currency):
692 """Change the wallet fiat currency country."""
693 self.config.set_key('conversion_currency',conversion_currency,True)
695 def copy_address(self, receive_popup):
696 """Copy the wallet addresses into the client."""
697 addrs = [addr for addr in self.wallet.addresses(True)
698 if not self.wallet.is_change(addr)]
699 # Select most recent addresses from gap limit
700 addrs = addrs[-self.wallet.gap_limit:]
701 copied_address = random.choice(addrs)
702 qApp.clipboard().setText(copied_address)
703 receive_popup.setup(copied_address)
704 receive_popup.popup()
706 def waiting_dialog(self, f):
711 w.setWindowTitle('Electrum')
712 l = QLabel(_('Sending transaction, please wait.'))
721 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
726 def send(self, address, amount, parent_window):
727 """Send bitcoins to the target address."""
728 dest_address = self.fetch_destination(address)
730 if dest_address is None or not is_valid(dest_address):
731 QMessageBox.warning(parent_window, _('Error'),
732 _('Invalid Bitcoin Address') + ':\n' + address, _('OK'))
735 convert_amount = lambda amount: \
736 int(D(unicode(amount)) * bitcoin(1))
737 amount = convert_amount(amount)
739 if self.wallet.use_encryption:
740 password_dialog = PasswordDialog(parent_window)
741 password = password_dialog.run()
749 if amount < bitcoin(1) / 10:
751 fee = bitcoin(1) / 1000
754 tx = self.wallet.mktx([(dest_address, amount)], password, fee)
755 except BaseException as error:
756 QMessageBox.warning(parent_window, _('Error'), str(error), _('OK'))
760 h = self.wallet.send_tx(tx)
762 self.waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Sending transaction, please wait..."))
764 status, message = self.wallet.receive_tx(h)
768 dumpf = tempfile.NamedTemporaryFile(delete=False)
771 print "Dumped error tx to", dumpf.name
772 QMessageBox.warning(parent_window, _('Error'), message, _('OK'))
775 TransactionWindow(message, self)
777 filename = 'unsigned_tx_%s' % (time.mktime(time.gmtime()))
779 fileName = QFileDialog.getSaveFileName(QWidget(), _("Select a transaction filename"), os.path.expanduser('~/%s' % (filename)))
780 with open(fileName,'w') as f:
781 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
782 QMessageBox.information(QWidget(), _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
783 except BaseException as e:
784 QMessageBox.warning(QWidget(), _('Error'), _('Could not write transaction to file: %s' % e), _('OK'))
787 def fetch_destination(self, address):
788 recipient = unicode(address).strip()
791 match1 = re.match("^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$",
794 # label or alias, with address in brackets
795 match2 = re.match("(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>",
800 self.wallet.get_alias(recipient, True,
801 self.show_message, self.question)
804 return match2.group(2)
809 def copy_master_public_key(self):
810 master_pubkey = self.wallet.get_master_public_key()
811 qApp.clipboard().setText(master_pubkey)
812 QMessageBox.information(None, _("Copy successful"), _("Your master public key has been copied to your clipboard."))
815 def acceptbit(self, currency):
816 master_pubkey = self.wallet.master_public_key
817 url = "http://acceptbit.com/mpk/%s/%s" % (master_pubkey, currency)
820 def show_seed_dialog(self):
821 ElectrumWindow.show_seed_dialog(self.wallet)
823 class MiniDriver(QObject):
830 def __init__(self, wallet, window):
831 super(QObject, self).__init__()
834 self.network = wallet.network
837 self.wallet.network.register_callback('updated',self.update_callback)
838 self.wallet.network.register_callback('connected', self.update_callback)
839 self.wallet.network.register_callback('disconnected', self.update_callback)
844 self.connect(self, SIGNAL("updatesignal()"), self.update)
845 self.update_callback()
847 # This is a hack to workaround that Qt does not like changing the
848 # window properties from this other thread before the runloop has
850 def update_callback(self):
851 self.emit(SIGNAL("updatesignal()"))
854 if not self.network.interface:
856 elif not self.network.interface.is_connected:
858 elif not self.wallet.up_to_date:
863 if self.wallet.up_to_date:
864 self.update_balance()
865 self.update_completions()
866 self.update_history()
868 def initializing(self):
869 if self.state == self.INITIALIZING:
871 self.state = self.INITIALIZING
872 self.window.deactivate()
874 def connecting(self):
875 if self.state == self.CONNECTING:
877 self.state = self.CONNECTING
878 self.window.deactivate()
880 def synchronizing(self):
881 if self.state == self.SYNCHRONIZING:
883 self.state = self.SYNCHRONIZING
884 self.window.deactivate()
887 if self.state == self.READY:
889 self.state = self.READY
890 self.window.activate()
892 def update_balance(self):
893 conf_balance, unconf_balance = self.wallet.get_balance()
894 balance = D(conf_balance + unconf_balance)
895 self.window.set_balances(balance)
897 def update_completions(self):
899 for addr, label in self.wallet.labels.items():
900 if addr in self.wallet.addressbook:
901 completions.append("%s <%s>" % (label, addr))
902 self.window.update_completions(completions)
904 def update_history(self):
905 tx_history = self.wallet.get_tx_history()
906 self.window.update_history(tx_history)
909 if __name__ == "__main__":
910 app = QApplication(sys.argv)
911 with open(rsrc("style.css")) as style_file:
912 app.setStyleSheet(style_file.read())
914 sys.exit(app.exec_())