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)
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 # Creating the menu bar
293 electrum_menu = menubar.addMenu(_("&Electrum"))
295 quit_option = electrum_menu.addAction(_("&Close"))
297 quit_option.triggered.connect(self.close)
299 view_menu = menubar.addMenu(_("&View"))
300 extra_menu = menubar.addMenu(_("&Extra"))
302 backup_wallet_menu = extra_menu.addAction( _("&Create wallet backup"))
303 backup_wallet_menu.triggered.connect(lambda: backup_wallet(self.config.path))
305 export_csv = extra_menu.addAction( _("&Export transactions to CSV") )
306 export_csv.triggered.connect(lambda: csv_transaction(self.actuator.wallet))
308 master_key = extra_menu.addAction( _("Copy master public key to clipboard") )
309 master_key.triggered.connect(self.actuator.copy_master_public_key)
311 expert_gui = view_menu.addAction(_("&Classic GUI"))
312 expert_gui.triggered.connect(expand_callback)
313 themes_menu = view_menu.addMenu(_("&Themes"))
314 selected_theme = self.actuator.selected_theme()
315 theme_group = QActionGroup(self)
316 for theme_name in self.actuator.theme_names():
317 theme_action = themes_menu.addAction(theme_name)
318 theme_action.setCheckable(True)
319 if selected_theme == theme_name:
320 theme_action.setChecked(True)
321 class SelectThemeFunctor:
322 def __init__(self, theme_name, toggle_theme):
323 self.theme_name = theme_name
324 self.toggle_theme = toggle_theme
325 def __call__(self, checked):
327 self.toggle_theme(self.theme_name)
328 delegate = SelectThemeFunctor(theme_name, self.toggle_theme)
329 theme_action.toggled.connect(delegate)
330 theme_group.addAction(theme_action)
331 view_menu.addSeparator()
333 show_receiving = view_menu.addAction(_("Show Receiving addresses"))
334 show_receiving.setCheckable(True)
335 show_receiving.toggled.connect(self.toggle_receiving_layout)
337 show_receiving_toggle = self.config.get("gui_show_receiving",False)
338 show_receiving.setChecked(show_receiving_toggle)
339 self.show_receiving = show_receiving
341 self.toggle_receiving_layout(show_receiving_toggle)
344 show_history = view_menu.addAction(_("Show History"))
345 show_history.setCheckable(True)
346 show_history.toggled.connect(self.show_history)
348 help_menu = menubar.addMenu(_("&Help"))
349 the_website = help_menu.addAction(_("&Website"))
350 the_website.triggered.connect(self.the_website)
351 help_menu.addSeparator()
352 report_bug = help_menu.addAction(_("&Report Bug"))
353 report_bug.triggered.connect(self.show_report_bug)
354 show_about = help_menu.addAction(_("&About"))
355 show_about.triggered.connect(self.show_about)
356 main_layout.setMenuBar(menubar)
357 self.main_layout = main_layout
359 quit_shortcut = QShortcut(QKeySequence("Ctrl+Q"), self)
360 quit_shortcut.activated.connect(self.close)
361 close_shortcut = QShortcut(QKeySequence("Ctrl+W"), self)
362 close_shortcut.activated.connect(self.close)
364 g = self.config.get("winpos-lite",[4, 25, 351, 149])
365 self.setGeometry(g[0], g[1], g[2], g[3])
367 show_hist = self.config.get("gui_show_history",False)
368 show_history.setChecked(show_hist)
369 self.show_history(show_hist)
371 self.setWindowIcon(QIcon(":icons/electrum.png"))
372 self.setWindowTitle("Electrum")
373 self.setWindowFlags(Qt.Window|Qt.MSWindowsFixedSizeDialogHint)
374 self.layout().setSizeConstraint(QLayout.SetFixedSize)
375 self.setObjectName("main_window")
379 def toggle_theme(self, theme_name):
380 old_path = QDir.currentPath()
381 self.actuator.change_theme(theme_name)
382 # Recompute style globally
383 qApp.style().unpolish(self)
384 qApp.style().polish(self)
385 QDir.setCurrent(old_path)
387 def closeEvent(self, event):
389 self.config.set_key("winpos-lite", [g.left(),g.top(),g.width(),g.height()],True)
390 self.config.set_key("gui_show_history", self.history_list.isVisible(),True)
391 self.config.set_key("gui_show_receiving", self.receiving_box.isVisible(),True)
393 super(MiniWindow, self).closeEvent(event)
396 def set_payment_fields(self, dest_address, amount):
397 self.address_input.setText(dest_address)
398 self.address_field_changed(dest_address)
399 self.amount_input.setText(amount)
404 def deactivate(self):
407 def set_quote_currency(self, currency):
408 """Set and display the fiat currency country."""
409 if currency not in self.quote_currencies:
411 self.quote_currencies.remove(currency)
412 self.quote_currencies.insert(0, currency)
413 self.refresh_balance()
415 def change_quote_currency(self, forward=True):
417 self.quote_currencies = \
418 self.quote_currencies[1:] + self.quote_currencies[0:1]
420 self.quote_currencies = \
421 self.quote_currencies[-1:] + self.quote_currencies[0:-1]
422 self.actuator.set_config_currency(self.quote_currencies[0])
423 self.refresh_balance()
425 def refresh_balance(self):
426 if self.btc_balance is None:
427 # Price has been discovered before wallet has been loaded
428 # and server connect... so bail.
430 self.set_balances(self.btc_balance)
431 self.amount_input_changed(self.amount_input.text())
433 def set_balances(self, btc_balance):
434 """Set the bitcoin balance and update the amount label accordingly."""
435 self.btc_balance = btc_balance
436 quote_text = self.create_quote_text(btc_balance)
438 quote_text = "(%s)" % quote_text
439 btc_balance = "%.4f" % (btc_balance / bitcoin(1))
440 self.balance_label.set_balance_text(btc_balance, quote_text)
441 self.setWindowTitle("Electrum %s - %s BTC" % (electrum_version, btc_balance))
443 def amount_input_changed(self, amount_text):
444 """Update the number of bitcoins displayed."""
445 self.check_button_status()
448 amount = D(str(amount_text))
449 except decimal.InvalidOperation:
450 self.balance_label.show_balance()
452 quote_text = self.create_quote_text(amount * bitcoin(1))
454 self.balance_label.set_amount_text(quote_text)
455 self.balance_label.show_amount()
457 self.balance_label.show_balance()
459 def create_quote_text(self, btc_balance):
460 """Return a string copy of the amount fiat currency the
461 user has in bitcoins."""
462 quote_currency = self.quote_currencies[0]
463 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
464 if quote_balance is None:
467 quote_text = "%.2f %s" % ((quote_balance / bitcoin(1)),
472 if self.actuator.send(self.address_input.text(),
473 self.amount_input.text(), self):
474 self.address_input.setText("")
475 self.amount_input.setText("")
477 def check_button_status(self):
478 """Check that the bitcoin address is valid and that something
479 is entered in the amount before making the send button clickable."""
481 value = D(str(self.amount_input.text())) * 10**8
482 except decimal.InvalidOperation:
484 # self.address_input.property(...) returns a qVariant, not a bool.
485 # The == is needed to properly invoke a comparison.
486 if (self.address_input.property("isValid") == True and
487 value is not None and 0 < value <= self.btc_balance):
488 self.send_button.setDisabled(False)
490 self.send_button.setDisabled(True)
492 def address_field_changed(self, address):
493 # label or alias, with address in brackets
494 match2 = re.match("(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>",
497 address = match2.group(2)
498 self.address_input.setText(address)
500 if is_valid(address):
501 self.check_button_status()
502 self.address_input.setProperty("isValid", True)
503 self.recompute_style(self.address_input)
505 self.send_button.setDisabled(True)
506 self.address_input.setProperty("isValid", False)
507 self.recompute_style(self.address_input)
509 if len(address) == 0:
510 self.address_input.setProperty("isValid", None)
511 self.recompute_style(self.address_input)
513 def recompute_style(self, element):
514 self.style().unpolish(element)
515 self.style().polish(element)
517 def copy_address(self):
518 receive_popup = ReceivePopup(self.receive_button)
519 self.actuator.copy_address(receive_popup)
521 def update_completions(self, completions):
522 self.address_completions.setStringList(completions)
525 def update_history(self, tx_history):
527 self.history_list.empty()
529 for item in tx_history[-10:]:
530 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
531 label = self.actuator.wallet.get_label(tx_hash)[0]
532 #amount = D(value) / 10**8
533 v_str = format_satoshis(value, True)
534 self.history_list.append(label, v_str, age(timestamp))
537 self.actuator.acceptbit(self.quote_currencies[0])
539 def the_website(self):
540 webbrowser.open("http://electrum.org")
542 def show_about(self):
543 QMessageBox.about(self, "Electrum",
544 _("Version")+" %s" % (electrum_version) + "\n\n" + _("Electrum's focus is speed, with low resource usage and simplifying Bitcoin. You do not need to perform regular backups, because your wallet can be recovered from a secret phrase that you can memorize or write on paper. Startup times are instant because it operates in conjunction with high-performance servers that handle the most complicated parts of the Bitcoin system."))
546 def show_report_bug(self):
547 QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
548 _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
550 def toggle_receiving_layout(self, toggle_state):
552 self.receiving_box.show()
554 self.receiving_box.hide()
556 def show_history(self, toggle_state):
558 self.main_layout.setRowMinimumHeight(3,200)
559 self.history_list.show()
561 self.main_layout.setRowMinimumHeight(3,0)
562 self.history_list.hide()
564 class BalanceLabel(QLabel):
570 def __init__(self, change_quote_currency, parent=None):
571 super(QLabel, self).__init__(_("Connecting..."), parent)
572 self.change_quote_currency = change_quote_currency
573 self.state = self.SHOW_CONNECTING
574 self.balance_text = ""
575 self.amount_text = ""
577 def mousePressEvent(self, event):
578 """Change the fiat currency selection if window background is clicked."""
579 if self.state != self.SHOW_CONNECTING:
580 self.change_quote_currency(event.button() == Qt.LeftButton)
582 def set_balance_text(self, btc_balance, quote_text):
583 """Set the amount of bitcoins in the gui."""
584 if self.state == self.SHOW_CONNECTING:
585 self.state = self.SHOW_BALANCE
586 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)
587 if self.state == self.SHOW_BALANCE:
588 self.setText(self.balance_text)
590 def set_amount_text(self, quote_text):
591 self.amount_text = "<span style='font-size: 10pt'>%s</span>" % quote_text
592 if self.state == self.SHOW_AMOUNT:
593 self.setText(self.amount_text)
595 def show_balance(self):
596 if self.state == self.SHOW_AMOUNT:
597 self.state = self.SHOW_BALANCE
598 self.setText(self.balance_text)
600 def show_amount(self):
601 if self.state == self.SHOW_BALANCE:
602 self.state = self.SHOW_AMOUNT
603 self.setText(self.amount_text)
605 def ok_cancel_buttons(dialog):
606 row_layout = QHBoxLayout()
607 row_layout.addStretch(1)
608 ok_button = QPushButton(_("OK"))
609 row_layout.addWidget(ok_button)
610 ok_button.clicked.connect(dialog.accept)
611 cancel_button = QPushButton(_("Cancel"))
612 row_layout.addWidget(cancel_button)
613 cancel_button.clicked.connect(dialog.reject)
616 class PasswordDialog(QDialog):
618 def __init__(self, parent):
619 super(QDialog, self).__init__(parent)
623 self.password_input = QLineEdit()
624 self.password_input.setEchoMode(QLineEdit.Password)
626 main_layout = QVBoxLayout(self)
627 message = _('Please enter your password')
628 main_layout.addWidget(QLabel(message))
632 grid.addWidget(QLabel(_('Password')), 1, 0)
633 grid.addWidget(self.password_input, 1, 1)
634 main_layout.addLayout(grid)
636 main_layout.addLayout(ok_cancel_buttons(self))
637 self.setLayout(main_layout)
642 return unicode(self.password_input.text())
644 class ReceivePopup(QDialog):
646 def leaveEvent(self, event):
649 def setup(self, address):
650 label = QLabel(_("Copied your Bitcoin address to the clipboard!"))
651 address_display = QLineEdit(address)
652 address_display.setReadOnly(True)
653 resize_line_edit_width(address_display, address)
655 main_layout = QVBoxLayout(self)
656 main_layout.addWidget(label)
657 main_layout.addWidget(address_display)
659 self.setMouseTracking(True)
660 self.setWindowTitle("Electrum - " + _("Receive Bitcoin payment"))
661 self.setWindowFlags(Qt.Window|Qt.FramelessWindowHint|
662 Qt.MSWindowsFixedSizeDialogHint)
663 self.layout().setSizeConstraint(QLayout.SetFixedSize)
664 #self.setFrameStyle(QFrame.WinPanel|QFrame.Raised)
665 #self.setAlignment(Qt.AlignCenter)
668 parent = self.parent()
669 top_left_pos = parent.mapToGlobal(parent.rect().bottomLeft())
670 self.move(top_left_pos)
671 center_mouse_pos = self.mapToGlobal(self.rect().center())
672 QCursor.setPos(center_mouse_pos)
676 """Initialize the definitions relating to themes and
677 sending/receiving bitcoins."""
680 def __init__(self, config, wallet):
681 """Retrieve the gui theme used in previous session."""
684 self.theme_name = self.config.get('litegui_theme','Cleanlook')
685 self.themes = load_theme_paths()
687 def load_theme(self):
688 """Load theme retrieved from wallet file."""
690 theme_prefix, theme_path = self.themes[self.theme_name]
692 util.print_error("Theme not found!", self.theme_name)
694 QDir.setCurrent(os.path.join(theme_prefix, theme_path))
695 with open(rsrc("style.css")) as style_file:
696 qApp.setStyleSheet(style_file.read())
698 def theme_names(self):
700 return sorted(self.themes.keys())
702 def selected_theme(self):
704 return self.theme_name
706 def change_theme(self, theme_name):
708 self.theme_name = theme_name
709 self.config.set_key('litegui_theme',theme_name)
712 def set_configured_currency(self, set_quote_currency):
713 """Set the inital fiat currency conversion country (USD/EUR/GBP) in
714 the GUI to what it was set to in the wallet."""
715 currency = self.config.get('currency')
716 # currency can be none when Electrum is used for the first
717 # time and no setting has been created yet.
718 if currency is not None:
719 set_quote_currency(currency)
721 def set_config_currency(self, conversion_currency):
722 """Change the wallet fiat currency country."""
723 self.config.set_key('conversion_currency',conversion_currency,True)
725 def copy_address(self, receive_popup):
726 """Copy the wallet addresses into the client."""
727 addrs = [addr for addr in self.wallet.addresses(True)
728 if not self.wallet.is_change(addr)]
729 # Select most recent addresses from gap limit
730 addrs = addrs[-self.wallet.gap_limit:]
731 copied_address = random.choice(addrs)
732 qApp.clipboard().setText(copied_address)
733 receive_popup.setup(copied_address)
734 receive_popup.popup()
736 def waiting_dialog(self, f):
741 w.setWindowTitle('Electrum')
742 l = QLabel('Sending transaction, please wait.')
751 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
756 def send(self, address, amount, parent_window):
757 """Send bitcoins to the target address."""
758 dest_address = self.fetch_destination(address)
760 if dest_address is None or not is_valid(dest_address):
761 QMessageBox.warning(parent_window, _('Error'),
762 _('Invalid Bitcoin Address') + ':\n' + address, _('OK'))
765 convert_amount = lambda amount: \
766 int(D(unicode(amount)) * bitcoin(1))
767 amount = convert_amount(amount)
769 if self.wallet.use_encryption:
770 password_dialog = PasswordDialog(parent_window)
771 password = password_dialog.run()
779 if amount < bitcoin(1) / 10:
781 fee = bitcoin(1) / 1000
784 tx = self.wallet.mktx([(dest_address, amount)], password, fee)
785 except BaseException as error:
786 QMessageBox.warning(parent_window, _('Error'), str(error), _('OK'))
790 h = self.wallet.send_tx(tx)
792 self.waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Sending transaction, please wait..."))
794 status, message = self.wallet.receive_tx(h)
798 dumpf = tempfile.NamedTemporaryFile(delete=False)
801 print "Dumped error tx to", dumpf.name
802 QMessageBox.warning(parent_window, _('Error'), message, _('OK'))
805 TransactionWindow(message, self)
807 filename = 'unsigned_tx_%s' % (time.mktime(time.gmtime()))
809 fileName = QFileDialog.getSaveFileName(QWidget(), _("Select a transaction filename"), os.path.expanduser('~/%s' % (filename)))
810 with open(fileName,'w') as f:
811 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
812 QMessageBox.information(QWidget(), _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
813 except BaseException as e:
814 QMessageBox.warning(QWidget(), _('Error'), _('Could not write transaction to file: %s' % e), _('OK'))
817 def fetch_destination(self, address):
818 recipient = unicode(address).strip()
821 match1 = re.match("^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$",
824 # label or alias, with address in brackets
825 match2 = re.match("(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>",
830 self.wallet.get_alias(recipient, True,
831 self.show_message, self.question)
834 return match2.group(2)
839 def copy_master_public_key(self):
840 master_pubkey = self.wallet.get_master_public_key()
841 qApp.clipboard().setText(master_pubkey)
842 QMessageBox.information(None, _("Copy successful"), _("Your master public key has been copied to your clipboard."))
845 def acceptbit(self, currency):
846 master_pubkey = self.wallet.master_public_key
847 url = "http://acceptbit.com/mpk/%s/%s" % (master_pubkey, currency)
850 def show_seed_dialog(self):
851 ElectrumWindow.show_seed_dialog(self.wallet)
853 class MiniDriver(QObject):
860 def __init__(self, wallet, window):
861 super(QObject, self).__init__()
866 self.wallet.network.register_callback('updated',self.update_callback)
867 self.wallet.network.register_callback('connected', self.update_callback)
868 self.wallet.network.register_callback('disconnected', self.update_callback)
873 self.connect(self, SIGNAL("updatesignal()"), self.update)
874 self.update_callback()
876 # This is a hack to workaround that Qt does not like changing the
877 # window properties from this other thread before the runloop has
879 def update_callback(self):
880 self.emit(SIGNAL("updatesignal()"))
883 if not self.wallet.interface:
885 elif not self.wallet.interface.is_connected:
887 elif not self.wallet.up_to_date:
892 if self.wallet.up_to_date:
893 self.update_balance()
894 self.update_completions()
895 self.update_history()
897 def initializing(self):
898 if self.state == self.INITIALIZING:
900 self.state = self.INITIALIZING
901 self.window.deactivate()
903 def connecting(self):
904 if self.state == self.CONNECTING:
906 self.state = self.CONNECTING
907 self.window.deactivate()
909 def synchronizing(self):
910 if self.state == self.SYNCHRONIZING:
912 self.state = self.SYNCHRONIZING
913 self.window.deactivate()
916 if self.state == self.READY:
918 self.state = self.READY
919 self.window.activate()
921 def update_balance(self):
922 conf_balance, unconf_balance = self.wallet.get_balance()
923 balance = D(conf_balance + unconf_balance)
924 self.window.set_balances(balance)
926 def update_completions(self):
928 for addr, label in self.wallet.labels.items():
929 if addr in self.wallet.addressbook:
930 completions.append("%s <%s>" % (label, addr))
931 self.window.update_completions(completions)
933 def update_history(self):
934 tx_history = self.wallet.get_tx_history()
935 self.window.update_history(tx_history)
938 if __name__ == "__main__":
939 app = QApplication(sys.argv)
940 with open(rsrc("style.css")) as style_file:
941 app.setStyleSheet(style_file.read())
943 sys.exit(app.exec_())