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 _
26 from electrum.wallet import Wallet, WalletStorage
29 import receiving_widget
30 from electrum import util
34 from electrum.version import ELECTRUM_VERSION as electrum_version
35 from electrum.util import format_satoshis, age
37 from main_window import ElectrumWindow
42 bitcoin = lambda v: v * 100000000
44 def IconButton(filename, parent=None):
45 pixmap = QPixmap(filename)
47 return QPushButton(icon, "", parent)
52 self.emit(SIGNAL('timersignal'))
55 def resize_line_edit_width(line_edit, text_input):
56 metrics = QFontMetrics(qApp.font())
57 # Create an extra character to add some space on the end
59 line_edit.setMinimumWidth(metrics.width(text_input))
61 def load_theme_name(theme_path):
63 with open(os.path.join(theme_path, "name.cfg")) as name_cfg_file:
64 return name_cfg_file.read().rstrip("\n").strip()
69 def theme_dirs_from_prefix(prefix):
70 if not os.path.exists(prefix):
73 for potential_theme in os.listdir(prefix):
74 theme_full_path = os.path.join(prefix, potential_theme)
75 theme_css = os.path.join(theme_full_path, "style.css")
76 if not os.path.exists(theme_css):
78 theme_name = load_theme_name(theme_full_path)
79 if theme_name is None:
81 theme_paths[theme_name] = prefix, potential_theme
84 def load_theme_paths():
86 prefixes = (util.local_data_dir(), util.appdata_dir())
87 for prefix in prefixes:
88 theme_paths.update(theme_dirs_from_prefix(prefix))
92 def csv_transaction(wallet):
94 select_export = _('Select file to export your wallet transactions to')
95 fileName = QFileDialog.getSaveFileName(QWidget(), select_export, os.path.expanduser('~/electrum-history.csv'), "*.csv")
97 with open(fileName, "w+") as csvfile:
98 transaction = csv.writer(csvfile)
99 transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
100 for item in wallet.get_tx_history():
101 tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
103 if timestamp is not None:
105 time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
106 except [RuntimeError, TypeError, NameError] as reason:
107 time_string = "unknown"
110 time_string = "unknown"
112 time_string = "pending"
114 if value is not None:
115 value_string = format_satoshis(value, True, wallet.num_zeros)
120 fee_string = format_satoshis(fee, True, wallet.num_zeros)
125 label, is_default_label = wallet.get_label(tx_hash)
129 balance_string = format_satoshis(balance, False, wallet.num_zeros)
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__()
177 tx = "e08115d0f7819aee65b9d24f81ef9d46eb62bb67ddef5318156cbc3ceb7b703e"
179 self.actuator = actuator
181 self.btc_balance = None
182 self.quote_currencies = ["BRL", "CNY", "EUR", "GBP", "RUB", "USD"]
183 self.actuator.set_configured_currency(self.set_quote_currency)
184 self.exchanger = exchange_rate.Exchanger(self)
185 # Needed because price discovery is done in a different thread
186 # which needs to be sent back to this main one to update the GUI
187 self.connect(self, SIGNAL("refresh_balance()"), self.refresh_balance)
189 self.balance_label = BalanceLabel(self.change_quote_currency, self)
190 self.balance_label.setObjectName("balance_label")
193 # Bitcoin address code
194 self.address_input = QLineEdit()
195 self.address_input.setPlaceholderText(_("Enter a Bitcoin address or contact"))
196 self.address_input.setObjectName("address_input")
198 self.address_input.setFocusPolicy(Qt.ClickFocus)
200 self.address_input.textChanged.connect(self.address_field_changed)
201 resize_line_edit_width(self.address_input,
202 "1BtaFUr3qVvAmwrsuDuu5zk6e4s2rxd2Gy")
204 self.address_completions = QStringListModel()
205 address_completer = QCompleter(self.address_input)
206 address_completer.setCaseSensitivity(False)
207 address_completer.setModel(self.address_completions)
208 self.address_input.setCompleter(address_completer)
210 address_layout = QHBoxLayout()
211 address_layout.addWidget(self.address_input)
213 self.amount_input = QLineEdit()
214 self.amount_input.setPlaceholderText(_("... and amount"))
215 self.amount_input.setObjectName("amount_input")
217 self.amount_input.setFocusPolicy(Qt.ClickFocus)
218 # This is changed according to the user's displayed balance
219 self.amount_validator = QDoubleValidator(self.amount_input)
220 self.amount_validator.setNotation(QDoubleValidator.StandardNotation)
221 self.amount_validator.setDecimals(8)
222 self.amount_input.setValidator(self.amount_validator)
224 # This removes the very ugly OSX highlighting, please leave this in :D
225 self.address_input.setAttribute(Qt.WA_MacShowFocusRect, 0)
226 self.amount_input.setAttribute(Qt.WA_MacShowFocusRect, 0)
227 self.amount_input.textChanged.connect(self.amount_input_changed)
229 if self.actuator.wallet.seed:
230 self.send_button = QPushButton(_("&Send"))
232 self.send_button = QPushButton(_("&Create"))
234 self.send_button.setObjectName("send_button")
235 self.send_button.setDisabled(True);
236 self.send_button.clicked.connect(self.send)
238 # Creating the receive button
239 self.switch_button = QPushButton( QIcon(":icons/switchgui.png"),'' )
240 self.switch_button.setMaximumWidth(25)
241 self.switch_button.setFlat(True)
242 self.switch_button.clicked.connect(expand_callback)
244 main_layout = QGridLayout(self)
246 main_layout.addWidget(self.balance_label, 0, 0, 1, 3)
247 main_layout.addWidget(self.switch_button, 0, 3)
249 main_layout.addWidget(self.address_input, 1, 0, 1, 4)
250 main_layout.addWidget(self.amount_input, 2, 0, 1, 2)
251 main_layout.addWidget(self.send_button, 2, 2, 1, 2)
253 self.send_button.setMaximumWidth(125)
255 self.history_list = history_widget.HistoryWidget()
256 self.history_list.setObjectName("history")
257 self.history_list.hide()
258 self.history_list.setAlternatingRowColors(True)
260 main_layout.addWidget(self.history_list, 3, 0, 1, 4)
262 self.receiving = receiving_widget.ReceivingWidget(self)
263 self.receiving.setObjectName("receiving")
265 # Add to the right side
266 self.receiving_box = QGroupBox(_("Select a receiving address"))
267 extra_layout = QGridLayout()
269 # Checkbox to filter used addresses
270 hide_used = QCheckBox(_('Hide used addresses'))
271 hide_used.setChecked(True)
272 hide_used.stateChanged.connect(self.receiving.toggle_used)
274 # Events for receiving addresses
275 self.receiving.clicked.connect(self.receiving.copy_address)
276 self.receiving.itemDoubleClicked.connect(self.receiving.edit_label)
277 self.receiving.itemChanged.connect(self.receiving.update_label)
281 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)
283 extra_layout.addWidget(self.receiving, 1,0)
284 extra_layout.addWidget(hide_used, 2,0)
285 extra_layout.setColumnMinimumWidth(0,200)
287 self.receiving_box.setLayout(extra_layout)
288 main_layout.addWidget(self.receiving_box,0,4,-1,3)
289 self.receiving_box.hide()
291 self.main_layout = main_layout
293 quit_shortcut = QShortcut(QKeySequence("Ctrl+Q"), self)
294 quit_shortcut.activated.connect(self.close)
295 close_shortcut = QShortcut(QKeySequence("Ctrl+W"), self)
296 close_shortcut.activated.connect(self.close)
298 g = self.config.get("winpos-lite",[4, 25, 351, 149])
299 self.setGeometry(g[0], g[1], g[2], g[3])
301 show_hist = self.config.get("gui_show_history",False)
302 self.show_history(show_hist)
303 show_hist = self.config.get("gui_show_receiving",False)
304 self.toggle_receiving_layout(show_hist)
306 self.setWindowIcon(QIcon(":icons/electrum.png"))
307 self.setWindowTitle("Electrum")
308 self.setWindowFlags(Qt.Window|Qt.MSWindowsFixedSizeDialogHint)
309 self.layout().setSizeConstraint(QLayout.SetFixedSize)
310 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)
362 super(MiniWindow, self).closeEvent(event)
365 def set_payment_fields(self, dest_address, amount):
366 self.address_input.setText(dest_address)
367 self.address_field_changed(dest_address)
368 self.amount_input.setText(amount)
373 def deactivate(self):
376 def set_quote_currency(self, currency):
377 """Set and display the fiat currency country."""
378 if currency not in self.quote_currencies:
380 self.quote_currencies.remove(currency)
381 self.quote_currencies.insert(0, currency)
382 self.refresh_balance()
384 def change_quote_currency(self, forward=True):
386 self.quote_currencies = \
387 self.quote_currencies[1:] + self.quote_currencies[0:1]
389 self.quote_currencies = \
390 self.quote_currencies[-1:] + self.quote_currencies[0:-1]
391 self.actuator.set_config_currency(self.quote_currencies[0])
392 self.refresh_balance()
394 def refresh_balance(self):
395 if self.btc_balance is None:
396 # Price has been discovered before wallet has been loaded
397 # and server connect... so bail.
399 self.set_balances(self.btc_balance)
400 self.amount_input_changed(self.amount_input.text())
402 def set_balances(self, btc_balance):
403 """Set the bitcoin balance and update the amount label accordingly."""
404 self.btc_balance = btc_balance
405 quote_text = self.create_quote_text(btc_balance)
407 quote_text = "(%s)" % quote_text
408 btc_balance = "%.4f" % (btc_balance / bitcoin(1))
409 self.balance_label.set_balance_text(btc_balance, quote_text)
410 self.setWindowTitle("Electrum %s - %s BTC" % (electrum_version, btc_balance))
412 def amount_input_changed(self, amount_text):
413 """Update the number of bitcoins displayed."""
414 self.check_button_status()
417 amount = D(str(amount_text))
418 except decimal.InvalidOperation:
419 self.balance_label.show_balance()
421 quote_text = self.create_quote_text(amount * bitcoin(1))
423 self.balance_label.set_amount_text(quote_text)
424 self.balance_label.show_amount()
426 self.balance_label.show_balance()
428 def create_quote_text(self, btc_balance):
429 """Return a string copy of the amount fiat currency the
430 user has in bitcoins."""
431 quote_currency = self.quote_currencies[0]
432 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
433 if quote_balance is None:
436 quote_text = "%.2f %s" % ((quote_balance / bitcoin(1)),
441 if self.actuator.send(self.address_input.text(),
442 self.amount_input.text(), self):
443 self.address_input.setText("")
444 self.amount_input.setText("")
446 def check_button_status(self):
447 """Check that the bitcoin address is valid and that something
448 is entered in the amount before making the send button clickable."""
450 value = D(str(self.amount_input.text())) * 10**8
451 except decimal.InvalidOperation:
453 # self.address_input.property(...) returns a qVariant, not a bool.
454 # The == is needed to properly invoke a comparison.
455 if (self.address_input.property("isValid") == True and
456 value is not None and 0 < value <= self.btc_balance):
457 self.send_button.setDisabled(False)
459 self.send_button.setDisabled(True)
461 def address_field_changed(self, address):
462 # label or alias, with address in brackets
463 match2 = re.match("(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>",
466 address = match2.group(2)
467 self.address_input.setText(address)
469 if is_valid(address):
470 self.check_button_status()
471 self.address_input.setProperty("isValid", True)
472 self.recompute_style(self.address_input)
474 self.send_button.setDisabled(True)
475 self.address_input.setProperty("isValid", False)
476 self.recompute_style(self.address_input)
478 if len(address) == 0:
479 self.address_input.setProperty("isValid", None)
480 self.recompute_style(self.address_input)
482 def recompute_style(self, element):
483 self.style().unpolish(element)
484 self.style().polish(element)
486 def copy_address(self):
487 receive_popup = ReceivePopup(self.receive_button)
488 self.actuator.copy_address(receive_popup)
490 def update_completions(self, completions):
491 self.address_completions.setStringList(completions)
494 def update_history(self, tx_history):
496 self.history_list.empty()
498 for item in tx_history[-10:]:
499 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
500 label = self.actuator.wallet.get_label(tx_hash)[0]
501 #amount = D(value) / 10**8
502 v_str = format_satoshis(value, True)
503 self.history_list.append(label, v_str, age(timestamp))
506 self.actuator.acceptbit(self.quote_currencies[0])
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, btc_balance, quote_text):
554 """Set the amount of bitcoins in the gui."""
555 if self.state == self.SHOW_CONNECTING:
556 self.state = self.SHOW_BALANCE
557 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)
558 if self.state == self.SHOW_BALANCE:
559 self.setText(self.balance_text)
561 def set_amount_text(self, quote_text):
562 self.amount_text = "<span style='font-size: 10pt'>%s</span>" % quote_text
563 if self.state == self.SHOW_AMOUNT:
564 self.setText(self.amount_text)
566 def show_balance(self):
567 if self.state == self.SHOW_AMOUNT:
568 self.state = self.SHOW_BALANCE
569 self.setText(self.balance_text)
571 def show_amount(self):
572 if self.state == self.SHOW_BALANCE:
573 self.state = self.SHOW_AMOUNT
574 self.setText(self.amount_text)
576 def ok_cancel_buttons(dialog):
577 row_layout = QHBoxLayout()
578 row_layout.addStretch(1)
579 ok_button = QPushButton(_("OK"))
580 row_layout.addWidget(ok_button)
581 ok_button.clicked.connect(dialog.accept)
582 cancel_button = QPushButton(_("Cancel"))
583 row_layout.addWidget(cancel_button)
584 cancel_button.clicked.connect(dialog.reject)
587 class PasswordDialog(QDialog):
589 def __init__(self, parent):
590 super(QDialog, self).__init__(parent)
594 self.password_input = QLineEdit()
595 self.password_input.setEchoMode(QLineEdit.Password)
597 main_layout = QVBoxLayout(self)
598 message = _('Please enter your password')
599 main_layout.addWidget(QLabel(message))
603 grid.addWidget(QLabel(_('Password')), 1, 0)
604 grid.addWidget(self.password_input, 1, 1)
605 main_layout.addLayout(grid)
607 main_layout.addLayout(ok_cancel_buttons(self))
608 self.setLayout(main_layout)
613 return unicode(self.password_input.text())
615 class ReceivePopup(QDialog):
617 def leaveEvent(self, event):
620 def setup(self, address):
621 label = QLabel(_("Copied your Bitcoin address to the clipboard!"))
622 address_display = QLineEdit(address)
623 address_display.setReadOnly(True)
624 resize_line_edit_width(address_display, address)
626 main_layout = QVBoxLayout(self)
627 main_layout.addWidget(label)
628 main_layout.addWidget(address_display)
630 self.setMouseTracking(True)
631 self.setWindowTitle("Electrum - " + _("Receive Bitcoin payment"))
632 self.setWindowFlags(Qt.Window|Qt.FramelessWindowHint|
633 Qt.MSWindowsFixedSizeDialogHint)
634 self.layout().setSizeConstraint(QLayout.SetFixedSize)
635 #self.setFrameStyle(QFrame.WinPanel|QFrame.Raised)
636 #self.setAlignment(Qt.AlignCenter)
639 parent = self.parent()
640 top_left_pos = parent.mapToGlobal(parent.rect().bottomLeft())
641 self.move(top_left_pos)
642 center_mouse_pos = self.mapToGlobal(self.rect().center())
643 QCursor.setPos(center_mouse_pos)
647 """Initialize the definitions relating to themes and
648 sending/receiving bitcoins."""
651 def __init__(self, config, wallet):
652 """Retrieve the gui theme used in previous session."""
655 self.theme_name = self.config.get('litegui_theme','Cleanlook')
656 self.themes = load_theme_paths()
658 def load_theme(self):
659 """Load theme retrieved from wallet file."""
661 theme_prefix, theme_path = self.themes[self.theme_name]
663 util.print_error("Theme not found!", self.theme_name)
665 QDir.setCurrent(os.path.join(theme_prefix, theme_path))
666 with open(rsrc("style.css")) as style_file:
667 qApp.setStyleSheet(style_file.read())
669 def theme_names(self):
671 return sorted(self.themes.keys())
673 def selected_theme(self):
675 return self.theme_name
677 def change_theme(self, theme_name):
679 self.theme_name = theme_name
680 self.config.set_key('litegui_theme',theme_name)
683 def set_configured_currency(self, set_quote_currency):
684 """Set the inital fiat currency conversion country (USD/EUR/GBP) in
685 the GUI to what it was set to in the wallet."""
686 currency = self.config.get('currency')
687 # currency can be none when Electrum is used for the first
688 # time and no setting has been created yet.
689 if currency is not None:
690 set_quote_currency(currency)
692 def set_config_currency(self, conversion_currency):
693 """Change the wallet fiat currency country."""
694 self.config.set_key('conversion_currency',conversion_currency,True)
696 def copy_address(self, receive_popup):
697 """Copy the wallet addresses into the client."""
698 addrs = [addr for addr in self.wallet.addresses(True)
699 if not self.wallet.is_change(addr)]
700 # Select most recent addresses from gap limit
701 addrs = addrs[-self.wallet.gap_limit:]
702 copied_address = random.choice(addrs)
703 qApp.clipboard().setText(copied_address)
704 receive_popup.setup(copied_address)
705 receive_popup.popup()
707 def waiting_dialog(self, f):
712 w.setWindowTitle('Electrum')
713 l = QLabel(_('Sending transaction, please wait.'))
722 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
727 def send(self, address, amount, parent_window):
728 """Send bitcoins to the target address."""
729 dest_address = self.fetch_destination(address)
731 if dest_address is None or not is_valid(dest_address):
732 QMessageBox.warning(parent_window, _('Error'),
733 _('Invalid Bitcoin Address') + ':\n' + address, _('OK'))
736 convert_amount = lambda amount: \
737 int(D(unicode(amount)) * bitcoin(1))
738 amount = convert_amount(amount)
740 if self.wallet.use_encryption:
741 password_dialog = PasswordDialog(parent_window)
742 password = password_dialog.run()
750 if amount < bitcoin(1) / 10:
752 fee = bitcoin(1) / 1000
755 tx = self.wallet.mktx([(dest_address, amount)], password, fee)
756 except BaseException as error:
757 QMessageBox.warning(parent_window, _('Error'), str(error), _('OK'))
761 h = self.wallet.send_tx(tx)
763 self.waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Sending transaction, please wait..."))
765 status, message = self.wallet.receive_tx(h)
769 dumpf = tempfile.NamedTemporaryFile(delete=False)
772 print "Dumped error tx to", dumpf.name
773 QMessageBox.warning(parent_window, _('Error'), message, _('OK'))
776 TransactionWindow(message, self)
778 filename = 'unsigned_tx_%s' % (time.mktime(time.gmtime()))
780 fileName = QFileDialog.getSaveFileName(QWidget(), _("Select a transaction filename"), os.path.expanduser('~/%s' % (filename)))
781 with open(fileName,'w') as f:
782 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
783 QMessageBox.information(QWidget(), _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
784 except BaseException as e:
785 QMessageBox.warning(QWidget(), _('Error'), _('Could not write transaction to file: %s' % e), _('OK'))
788 def fetch_destination(self, address):
789 recipient = unicode(address).strip()
792 match1 = re.match("^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$",
795 # label or alias, with address in brackets
796 match2 = re.match("(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>",
801 self.wallet.get_alias(recipient, True,
802 self.show_message, self.question)
805 return match2.group(2)
810 def copy_master_public_key(self):
811 master_pubkey = self.wallet.get_master_public_key()
812 qApp.clipboard().setText(master_pubkey)
813 QMessageBox.information(None, _("Copy successful"), _("Your master public key has been copied to your clipboard."))
816 def acceptbit(self, currency):
817 master_pubkey = self.wallet.master_public_key
818 url = "http://acceptbit.com/mpk/%s/%s" % (master_pubkey, currency)
821 def show_seed_dialog(self):
822 ElectrumWindow.show_seed_dialog(self.wallet)
824 class MiniDriver(QObject):
831 def __init__(self, wallet, window):
832 super(QObject, self).__init__()
835 self.network = wallet.network
838 self.wallet.network.register_callback('updated',self.update_callback)
839 self.wallet.network.register_callback('connected', self.update_callback)
840 self.wallet.network.register_callback('disconnected', self.update_callback)
845 self.connect(self, SIGNAL("updatesignal()"), self.update)
846 self.update_callback()
848 # This is a hack to workaround that Qt does not like changing the
849 # window properties from this other thread before the runloop has
851 def update_callback(self):
852 self.emit(SIGNAL("updatesignal()"))
855 if not self.network.interface:
857 elif not self.network.interface.is_connected:
859 elif not self.wallet.up_to_date:
864 if self.wallet.up_to_date:
865 self.update_balance()
866 self.update_completions()
867 self.update_history()
869 def initializing(self):
870 if self.state == self.INITIALIZING:
872 self.state = self.INITIALIZING
873 self.window.deactivate()
875 def connecting(self):
876 if self.state == self.CONNECTING:
878 self.state = self.CONNECTING
879 self.window.deactivate()
881 def synchronizing(self):
882 if self.state == self.SYNCHRONIZING:
884 self.state = self.SYNCHRONIZING
885 self.window.deactivate()
888 if self.state == self.READY:
890 self.state = self.READY
891 self.window.activate()
893 def update_balance(self):
894 conf_balance, unconf_balance = self.wallet.get_balance()
895 balance = D(conf_balance + unconf_balance)
896 self.window.set_balances(balance)
898 def update_completions(self):
900 for addr, label in self.wallet.labels.items():
901 if addr in self.wallet.addressbook:
902 completions.append("%s <%s>" % (label, addr))
903 self.window.update_completions(completions)
905 def update_history(self):
906 tx_history = self.wallet.get_tx_history()
907 self.window.update_history(tx_history)
910 if __name__ == "__main__":
911 app = QApplication(sys.argv)
912 with open(rsrc("style.css")) as style_file:
913 app.setStyleSheet(style_file.read())
915 sys.exit(app.exec_())