3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2012 thomasv@gitorious
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 import sys, time, datetime, re
20 from i18n import _, set_language
21 from electrum.util import print_error, print_msg
22 import os.path, json, ast, traceback
23 from qrcodewidget import QRCodeWidget
28 sys.exit("Error: Could not import PyQt4 on Linux systems, you may try 'sudo apt-get install python-qt4'")
30 from PyQt4.QtGui import *
31 from PyQt4.QtCore import *
32 import PyQt4.QtCore as QtCore
33 import PyQt4.QtGui as QtGui
34 from electrum.interface import DEFAULT_SERVERS
39 sys.exit("Error: Could not import icons_rc.py, please generate it with: 'pyrcc4 icons.qrc -o gui/icons_rc.py'")
41 from electrum.wallet import format_satoshis
42 from electrum.bitcoin import Transaction, is_valid
43 from electrum import mnemonic
44 from electrum import util, bitcoin, commands
46 import bmp, pyqrnative
49 from decimal import Decimal
57 if platform.system() == 'Windows':
58 MONOSPACE_FONT = 'Lucida Console'
59 elif platform.system() == 'Darwin':
60 MONOSPACE_FONT = 'Monaco'
62 MONOSPACE_FONT = 'monospace'
64 ALIAS_REGEXP = '^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$'
66 from electrum import ELECTRUM_VERSION
69 class UpdateLabel(QtGui.QLabel):
70 def __init__(self, config, parent=None):
71 QtGui.QLabel.__init__(self, parent)
72 self.new_version = False
75 con = httplib.HTTPConnection('electrum.org', 80, timeout=5)
76 con.request("GET", "/version")
77 res = con.getresponse()
78 except socket.error as msg:
79 print_error("Could not retrieve version information")
83 self.latest_version = res.read()
84 self.latest_version = self.latest_version.replace("\n","")
85 if(re.match('^\d+(\.\d+)*$', self.latest_version)):
87 self.current_version = ELECTRUM_VERSION
88 if(self.compare_versions(self.latest_version, self.current_version) == 1):
89 latest_seen = self.config.get("last_seen_version",ELECTRUM_VERSION)
90 if(self.compare_versions(self.latest_version, latest_seen) == 1):
91 self.new_version = True
92 self.setText(_("New version available") + ": " + self.latest_version)
95 def compare_versions(self, version1, version2):
97 return [int(x) for x in re.sub(r'(\.0+)*$','', v).split(".")]
98 return cmp(normalize(version1), normalize(version2))
100 def ignore_this_version(self):
102 self.config.set_key("last_seen_version", self.latest_version, True)
103 QMessageBox.information(self, _("Preference saved"), _("Notifications about this update will not be shown again."))
106 def ignore_all_version(self):
108 self.config.set_key("last_seen_version", "9.9.9", True)
109 QMessageBox.information(self, _("Preference saved"), _("No more notifications about version updates will be shown."))
112 def open_website(self):
113 webbrowser.open("http://electrum.org/download.html")
116 def mouseReleaseEvent(self, event):
117 dialog = QDialog(self)
118 dialog.setWindowTitle(_('Electrum update'))
121 main_layout = QGridLayout()
122 main_layout.addWidget(QLabel(_("A new version of Electrum is available:")+" " + self.latest_version), 0,0,1,3)
124 ignore_version = QPushButton(_("Ignore this version"))
125 ignore_version.clicked.connect(self.ignore_this_version)
127 ignore_all_versions = QPushButton(_("Ignore all versions"))
128 ignore_all_versions.clicked.connect(self.ignore_all_version)
130 open_website = QPushButton(_("Goto download page"))
131 open_website.clicked.connect(self.open_website)
133 main_layout.addWidget(ignore_version, 1, 0)
134 main_layout.addWidget(ignore_all_versions, 1, 1)
135 main_layout.addWidget(open_website, 1, 2)
137 dialog.setLayout(main_layout)
141 if not dialog.exec_(): return
143 def numbify(entry, is_int = False):
144 text = unicode(entry.text()).strip()
145 pos = entry.cursorPosition()
147 if not is_int: chars +='.'
148 s = ''.join([i for i in text if i in chars])
152 s = s.replace('.','')
153 s = s[:p] + '.' + s[p:p+8]
155 amount = int( Decimal(s) * 100000000 )
164 entry.setCursorPosition(pos)
168 class Timer(QtCore.QThread):
171 self.emit(QtCore.SIGNAL('timersignal'))
174 class HelpButton(QPushButton):
175 def __init__(self, text):
176 QPushButton.__init__(self, '?')
177 self.setFocusPolicy(Qt.NoFocus)
178 self.setFixedWidth(20)
179 self.clicked.connect(lambda: QMessageBox.information(self, 'Help', text, 'OK') )
182 class EnterButton(QPushButton):
183 def __init__(self, text, func):
184 QPushButton.__init__(self, text)
186 self.clicked.connect(func)
188 def keyPressEvent(self, e):
189 if e.key() == QtCore.Qt.Key_Return:
192 class MyTreeWidget(QTreeWidget):
193 def __init__(self, parent):
194 QTreeWidget.__init__(self, parent)
197 for i in range(0,self.viewport().height()/5):
198 if self.itemAt(QPoint(0,i*5)) == item:
202 for j in range(0,30):
203 if self.itemAt(QPoint(0,i*5 + j)) != item:
205 self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
207 self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
212 class StatusBarButton(QPushButton):
213 def __init__(self, icon, tooltip, func):
214 QPushButton.__init__(self, icon, '')
215 self.setToolTip(tooltip)
217 self.setMaximumWidth(25)
218 self.clicked.connect(func)
221 def keyPressEvent(self, e):
222 if e.key() == QtCore.Qt.Key_Return:
230 def waiting_dialog(f):
236 w.setWindowTitle('Electrum')
246 w.connect(s, QtCore.SIGNAL('timersignal'), ff)
251 def ok_cancel_buttons(dialog):
254 b = QPushButton("Cancel")
256 b.clicked.connect(dialog.reject)
257 b = QPushButton("OK")
259 b.clicked.connect(dialog.accept)
264 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330],
265 "receive":[[370],[370,200,130]] }
267 class ElectrumWindow(QMainWindow):
269 def __init__(self, wallet, config):
270 QMainWindow.__init__(self)
276 self.wallet.interface.register_callback('updated', lambda: self.emit(QtCore.SIGNAL('update_wallet')))
277 self.wallet.interface.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
278 self.wallet.interface.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
279 self.wallet.interface.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
281 self.expert_mode = config.get('classic_expert_mode', False)
283 set_language(config.get('language'))
285 self.funds_error = False
286 self.completions = QStringListModel()
288 self.tabs = tabs = QTabWidget(self)
289 self.column_widths = self.config.get("column-widths", default_column_widths )
290 tabs.addTab(self.create_history_tab(), _('History') )
291 tabs.addTab(self.create_send_tab(), _('Send') )
292 tabs.addTab(self.create_receive_tab(), _('Receive') )
293 tabs.addTab(self.create_contacts_tab(), _('Contacts') )
294 tabs.addTab(self.create_console_tab(), _('Console') )
295 tabs.setMinimumSize(600, 400)
296 tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
297 self.setCentralWidget(tabs)
298 self.create_status_bar()
300 g = self.config.get("winpos-qt",[100, 100, 840, 400])
301 self.setGeometry(g[0], g[1], g[2], g[3])
302 title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
303 if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
304 self.setWindowTitle( title )
306 QShortcut(QKeySequence("Ctrl+W"), self, self.close)
307 QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
308 QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
309 QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
311 self.connect(self, QtCore.SIGNAL('update_wallet'), self.update_wallet)
312 self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
313 self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.wallet.banner) )
314 self.history_list.setFocus(True)
316 self.exchanger = exchange_rate.Exchanger(self)
317 self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
319 # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
320 if platform.system() == 'Windows':
321 n = 3 if self.wallet.seed else 2
322 tabs.setCurrentIndex (n)
323 tabs.setCurrentIndex (0)
325 # set initial message
326 self.console.showMessage(self.wallet.banner)
330 def init_plugins(self):
332 if os.path.exists("plugins"):
333 fp, pathname, description = imp.find_module('plugins')
334 imp.load_module('electrum_plugins', fp, pathname, description)
335 plugin_names = [name for a, name, b in pkgutil.iter_modules(['plugins'])]
336 self.plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
338 import electrum_plugins
339 plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
340 self.plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
342 self.plugin_hooks = {}
343 for p in self.plugins:
347 print_msg("Error:cannot initialize plugin",p)
348 traceback.print_exc(file=sys.stdout)
350 def set_hook(self, name, callback):
351 h = self.plugin_hooks.get(name, [])
353 self.plugin_hooks[name] = h
355 def unset_hook(self, name, callback):
356 h = self.plugin_hooks.get(name,[])
357 if callback in h: h.remove(callback)
358 self.plugin_hooks[name] = h
360 def run_hook(self, name, args):
361 for cb in self.plugin_hooks.get(name,[]):
365 # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
366 def getOpenFileName(self, title, filter = None):
367 directory = self.config.get('io_dir', os.path.expanduser('~'))
368 fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
369 if fileName and directory != os.path.dirname(fileName):
370 self.config.set_key('io_dir', os.path.dirname(fileName), True)
373 def getSaveFileName(self, title, filename, filter = None):
374 directory = self.config.get('io_dir', os.path.expanduser('~'))
375 path = os.path.join( directory, filename )
376 fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
377 if fileName and directory != os.path.dirname(fileName):
378 self.config.set_key('io_dir', os.path.dirname(fileName), True)
384 QMainWindow.close(self)
385 self.run_hook('close_main_window', (self,))
387 def connect_slots(self, sender):
388 self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
389 self.previous_payto_e=''
391 def timer_actions(self):
392 self.run_hook('timer_actions', (self,))
394 if self.payto_e.hasFocus():
396 r = unicode( self.payto_e.text() )
397 if r != self.previous_payto_e:
398 self.previous_payto_e = r
400 if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
402 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
406 s = r + ' <' + to_address + '>'
407 self.payto_e.setText(s)
411 def update_status(self):
412 if self.wallet.interface and self.wallet.interface.is_connected:
413 if not self.wallet.up_to_date:
414 text = _("Synchronizing...")
415 icon = QIcon(":icons/status_waiting.png")
417 c, u = self.wallet.get_balance()
418 text = _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
419 if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
420 text += self.create_quote_text(Decimal(c+u)/100000000)
421 icon = QIcon(":icons/status_connected.png")
423 text = _("Not connected")
424 icon = QIcon(":icons/status_disconnected.png")
426 self.status_text = text
427 self.statusBar().showMessage(text)
428 self.status_button.setIcon( icon )
430 def update_wallet(self):
432 if self.wallet.up_to_date or not self.wallet.interface.is_connected:
433 self.update_history_tab()
434 self.update_receive_tab()
435 self.update_contacts_tab()
436 self.update_completions()
439 def create_quote_text(self, btc_balance):
440 quote_currency = self.config.get("currency", "None")
441 quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
442 if quote_balance is None:
445 quote_text = " (%.2f %s)" % (quote_balance, quote_currency)
448 def create_history_tab(self):
449 self.history_list = l = MyTreeWidget(self)
451 for i,width in enumerate(self.column_widths['history']):
452 l.setColumnWidth(i, width)
453 l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
454 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
455 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
457 l.setContextMenuPolicy(Qt.CustomContextMenu)
458 l.customContextMenuRequested.connect(self.create_history_menu)
462 def create_history_menu(self, position):
463 self.history_list.selectedIndexes()
464 item = self.history_list.currentItem()
466 tx_hash = str(item.data(0, Qt.UserRole).toString())
467 if not tx_hash: return
469 #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
470 menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
471 menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
472 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
475 def show_tx_details(self, tx):
476 dialog = QDialog(self)
478 dialog.setWindowTitle(_("Transaction Details"))
480 dialog.setLayout(vbox)
481 dialog.setMinimumSize(600,300)
484 if tx_hash in self.wallet.transactions.keys():
485 is_mine, v, fee = self.wallet.get_tx_value(tx)
486 conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
488 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
494 vbox.addWidget(QLabel("Transaction ID:"))
495 e = QLineEdit(tx_hash)
499 vbox.addWidget(QLabel("Date: %s"%time_str))
500 vbox.addWidget(QLabel("Status: %d confirmations"%conf))
503 vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v-fee, False)))
504 vbox.addWidget(QLabel("Transaction fee: %s"% format_satoshis(fee, False)))
506 vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v, False)))
507 vbox.addWidget(QLabel("Transaction fee: unknown"))
509 vbox.addWidget(QLabel("Amount received: %s"% format_satoshis(v, False)))
511 vbox.addWidget( self.generate_transaction_information_widget(tx) )
513 ok_button = QPushButton(_("Close"))
514 ok_button.setDefault(True)
515 ok_button.clicked.connect(dialog.accept)
519 hbox.addWidget(ok_button)
523 def tx_label_clicked(self, item, column):
524 if column==2 and item.isSelected():
526 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
527 self.history_list.editItem( item, column )
528 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
531 def tx_label_changed(self, item, column):
535 tx_hash = str(item.data(0, Qt.UserRole).toString())
536 tx = self.wallet.transactions.get(tx_hash)
537 s = self.wallet.labels.get(tx_hash)
538 text = unicode( item.text(2) )
540 self.wallet.labels[tx_hash] = text
541 item.setForeground(2, QBrush(QColor('black')))
543 if s: self.wallet.labels.pop(tx_hash)
544 text = self.wallet.get_default_label(tx_hash)
545 item.setText(2, text)
546 item.setForeground(2, QBrush(QColor('gray')))
550 def edit_label(self, is_recv):
551 l = self.receive_list if is_recv else self.contacts_list
552 item = l.currentItem()
553 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
554 l.editItem( item, 1 )
555 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
559 def address_label_clicked(self, item, column, l, column_addr, column_label):
560 if column == column_label and item.isSelected():
561 addr = unicode( item.text(column_addr) )
562 label = unicode( item.text(column_label) )
563 if label in self.wallet.aliases.keys():
565 item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
566 l.editItem( item, column )
567 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
570 def address_label_changed(self, item, column, l, column_addr, column_label):
572 if column == column_label:
573 addr = unicode( item.text(column_addr) )
574 text = unicode( item.text(column_label) )
578 if text not in self.wallet.aliases.keys():
579 old_addr = self.wallet.labels.get(text)
581 self.wallet.labels[addr] = text
584 print_error("Error: This is one of your aliases")
585 label = self.wallet.labels.get(addr,'')
586 item.setText(column_label, QString(label))
588 s = self.wallet.labels.get(addr)
590 self.wallet.labels.pop(addr)
594 self.update_history_tab()
595 self.update_completions()
597 self.current_item_changed(item)
599 self.run_hook('item_changed',(self, item, column))
602 def current_item_changed(self, a):
603 self.run_hook('current_item_changed',(self, a))
607 def update_history_tab(self):
609 self.history_list.clear()
610 for item in self.wallet.get_tx_history():
611 tx_hash, conf, is_mine, value, fee, balance, timestamp = item
614 time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
620 icon = QIcon(":icons/unconfirmed.png")
622 icon = QIcon(":icons/clock%d.png"%conf)
624 icon = QIcon(":icons/confirmed.png")
627 icon = QIcon(":icons/unconfirmed.png")
629 if value is not None:
630 v_str = format_satoshis(value, True, self.wallet.num_zeros)
634 balance_str = format_satoshis(balance, False, self.wallet.num_zeros)
637 label, is_default_label = self.wallet.get_label(tx_hash)
639 label = _('Pruned transaction outputs')
640 is_default_label = False
642 item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
643 item.setFont(2, QFont(MONOSPACE_FONT))
644 item.setFont(3, QFont(MONOSPACE_FONT))
645 item.setFont(4, QFont(MONOSPACE_FONT))
647 item.setForeground(3, QBrush(QColor("#BC1E1E")))
649 item.setData(0, Qt.UserRole, tx_hash)
650 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
652 item.setForeground(2, QBrush(QColor('grey')))
654 item.setIcon(0, icon)
655 self.history_list.insertTopLevelItem(0,item)
658 self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
661 def create_send_tab(self):
666 grid.setColumnMinimumWidth(3,300)
667 grid.setColumnStretch(5,1)
669 self.payto_e = QLineEdit()
670 grid.addWidget(QLabel(_('Pay to')), 1, 0)
671 grid.addWidget(self.payto_e, 1, 1, 1, 3)
673 grid.addWidget(HelpButton(_('Recipient of the funds.') + '\n\n' + _('You may enter a Bitcoin address, a label from your list of contacts (a list of completions will be proposed), or an alias (email-like address that forwards to a Bitcoin address)')), 1, 4)
675 completer = QCompleter()
676 completer.setCaseSensitivity(False)
677 self.payto_e.setCompleter(completer)
678 completer.setModel(self.completions)
680 self.message_e = QLineEdit()
681 grid.addWidget(QLabel(_('Description')), 2, 0)
682 grid.addWidget(self.message_e, 2, 1, 1, 3)
683 grid.addWidget(HelpButton(_('Description of the transaction (not mandatory).') + '\n\n' + _('The description is not sent to the recipient of the funds. It is stored in your wallet file, and displayed in the \'History\' tab.')), 2, 4)
685 self.amount_e = QLineEdit()
686 grid.addWidget(QLabel(_('Amount')), 3, 0)
687 grid.addWidget(self.amount_e, 3, 1, 1, 2)
688 grid.addWidget(HelpButton(
689 _('Amount to be sent.') + '\n\n' \
690 + _('The amount will be displayed in red if you do not have enough funds in your wallet. Note that if you have frozen some of your addresses, the available funds will be lower than your total balance.')), 3, 3)
692 self.fee_e = QLineEdit()
693 grid.addWidget(QLabel(_('Fee')), 4, 0)
694 grid.addWidget(self.fee_e, 4, 1, 1, 2)
695 grid.addWidget(HelpButton(
696 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
697 + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
698 + _('A suggested fee is automatically added to this field. You may override it. The suggested fee increases with the size of the transaction.')), 4, 3)
701 b = EnterButton(_("Send"), self.do_send)
703 b = EnterButton(_("Create unsigned transaction"), self.do_send)
704 grid.addWidget(b, 6, 1)
706 b = EnterButton(_("Clear"),self.do_clear)
707 grid.addWidget(b, 6, 2)
709 self.payto_sig = QLabel('')
710 grid.addWidget(self.payto_sig, 7, 0, 1, 4)
712 QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
713 QShortcut(QKeySequence("Down"), w, w.focusNextChild)
722 def entry_changed( is_fee ):
723 self.funds_error = False
724 amount = numbify(self.amount_e)
725 fee = numbify(self.fee_e)
726 if not is_fee: fee = None
729 inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
731 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
734 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
735 text = self.status_text
738 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
739 self.funds_error = True
740 text = _( "Not enough funds" )
742 self.statusBar().showMessage(text)
743 self.amount_e.setPalette(palette)
744 self.fee_e.setPalette(palette)
746 self.amount_e.textChanged.connect(lambda: entry_changed(False) )
747 self.fee_e.textChanged.connect(lambda: entry_changed(True) )
749 self.run_hook('create_send_tab',(self,grid))
753 def update_completions(self):
755 for addr,label in self.wallet.labels.items():
756 if addr in self.wallet.addressbook:
757 l.append( label + ' <' + addr + '>')
758 l = l + self.wallet.aliases.keys()
760 self.completions.setStringList(l)
764 return lambda s, *args: s.do_protect(func, args)
768 def do_send(self, password):
770 label = unicode( self.message_e.text() )
771 r = unicode( self.payto_e.text() )
775 m1 = re.match(ALIAS_REGEXP, r)
776 # label or alias, with address in brackets
777 m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
780 to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
784 to_address = m2.group(2)
788 if not is_valid(to_address):
789 QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
793 amount = int( Decimal( unicode( self.amount_e.text())) * 100000000 )
795 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
798 fee = int( Decimal( unicode( self.fee_e.text())) * 100000000 )
800 QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
804 tx = self.wallet.mktx( [(to_address, amount)], password, fee)
805 except BaseException, e:
806 self.show_message(str(e))
809 self.run_hook('send_tx', (self.wallet, self, tx))
812 self.wallet.labels[tx.hash()] = label
815 h = self.wallet.send_tx(tx)
816 waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
817 status, msg = self.wallet.receive_tx( h )
819 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
821 self.update_contacts_tab()
823 QMessageBox.warning(self, _('Error'), msg, _('OK'))
825 filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
827 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
828 with open(fileName,'w') as f:
829 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
830 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
832 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
837 def set_url(self, url):
838 payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
839 self.tabs.setCurrentIndex(1)
840 label = self.wallet.labels.get(payto)
841 m_addr = label + ' <'+ payto+'>' if label else payto
842 self.payto_e.setText(m_addr)
844 self.message_e.setText(message)
845 self.amount_e.setText(amount)
847 self.set_frozen(self.payto_e,True)
848 self.set_frozen(self.amount_e,True)
849 self.set_frozen(self.message_e,True)
850 self.payto_sig.setText( ' The bitcoin URI was signed by ' + identity )
852 self.payto_sig.setVisible(False)
855 self.payto_sig.setVisible(False)
856 for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
858 self.set_frozen(e,False)
860 def set_frozen(self,entry,frozen):
862 entry.setReadOnly(True)
863 entry.setFrame(False)
865 palette.setColor(entry.backgroundRole(), QColor('lightgray'))
866 entry.setPalette(palette)
868 entry.setReadOnly(False)
871 palette.setColor(entry.backgroundRole(), QColor('white'))
872 entry.setPalette(palette)
875 def toggle_freeze(self,addr):
877 if addr in self.wallet.frozen_addresses:
878 self.wallet.unfreeze(addr)
880 self.wallet.freeze(addr)
881 self.update_receive_tab()
883 def toggle_priority(self,addr):
885 if addr in self.wallet.prioritized_addresses:
886 self.wallet.unprioritize(addr)
888 self.wallet.prioritize(addr)
889 self.update_receive_tab()
892 def create_list_tab(self, headers):
893 "generic tab creation method"
894 l = MyTreeWidget(self)
895 l.setColumnCount( len(headers) )
896 l.setHeaderLabels( headers )
906 vbox.addWidget(buttons)
911 buttons.setLayout(hbox)
916 def create_receive_tab(self):
917 l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
918 l.setContextMenuPolicy(Qt.CustomContextMenu)
919 l.customContextMenuRequested.connect(self.create_receive_menu)
920 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
921 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
922 self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
923 self.receive_list = l
924 self.receive_buttons_hbox = hbox
929 def receive_tab_set_mode(self, i):
930 self.save_column_widths()
931 self.expert_mode = (i == 1)
932 self.config.set_key('classic_expert_mode', self.expert_mode, True)
934 self.update_receive_tab()
937 def save_column_widths(self):
938 if not self.expert_mode:
939 widths = [ self.receive_list.columnWidth(0) ]
942 for i in range(self.receive_list.columnCount() -1):
943 widths.append(self.receive_list.columnWidth(i))
944 self.column_widths["receive"][self.expert_mode] = widths
946 self.column_widths["history"] = []
947 for i in range(self.history_list.columnCount() - 1):
948 self.column_widths["history"].append(self.history_list.columnWidth(i))
950 self.column_widths["contacts"] = []
951 for i in range(self.contacts_list.columnCount() - 1):
952 self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
955 def create_contacts_tab(self):
956 l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
957 l.setContextMenuPolicy(Qt.CustomContextMenu)
958 l.customContextMenuRequested.connect(self.create_contact_menu)
959 for i,width in enumerate(self.column_widths['contacts']):
960 l.setColumnWidth(i, width)
962 self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
963 self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
964 self.contacts_list = l
965 self.contacts_buttons_hbox = hbox
966 hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
971 def delete_imported_key(self, addr):
972 if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
973 self.wallet.imported_keys.pop(addr)
974 self.update_receive_tab()
975 self.update_history_tab()
979 def create_receive_menu(self, position):
980 # fixme: this function apparently has a side effect.
981 # if it is not called the menu pops up several times
982 #self.receive_list.selectedIndexes()
984 item = self.receive_list.itemAt(position)
986 addr = unicode(item.text(0))
987 if not is_valid(addr):
988 item.setExpanded(not item.isExpanded())
991 menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
992 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
993 menu.addAction(_("Edit label"), lambda: self.edit_label(True))
994 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
995 menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
996 if addr in self.wallet.imported_keys:
997 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1000 t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1001 menu.addAction(t, lambda: self.toggle_freeze(addr))
1002 t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1003 menu.addAction(t, lambda: self.toggle_priority(addr))
1005 self.run_hook('receive_menu', (self, menu,))
1006 menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1009 def payto(self, x, is_alias):
1016 label = self.wallet.labels.get(addr)
1017 m_addr = label + ' <' + addr + '>' if label else addr
1018 self.tabs.setCurrentIndex(1)
1019 self.payto_e.setText(m_addr)
1020 self.amount_e.setFocus()
1022 def delete_contact(self, x, is_alias):
1023 if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1024 if not is_alias and x in self.wallet.addressbook:
1025 self.wallet.addressbook.remove(x)
1026 if x in self.wallet.labels.keys():
1027 self.wallet.labels.pop(x)
1028 elif is_alias and x in self.wallet.aliases:
1029 self.wallet.aliases.pop(x)
1030 self.update_history_tab()
1031 self.update_contacts_tab()
1032 self.update_completions()
1034 def create_contact_menu(self, position):
1035 # fixme: this function apparently has a side effect.
1036 # if it is not called the menu pops up several times
1037 #self.contacts_list.selectedIndexes()
1039 item = self.contacts_list.itemAt(position)
1041 addr = unicode(item.text(0))
1042 label = unicode(item.text(1))
1043 is_alias = label in self.wallet.aliases.keys()
1044 x = label if is_alias else addr
1046 menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1047 menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
1048 menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1050 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1052 menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
1053 menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
1054 menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1057 def update_receive_item(self, item):
1058 item.setFont(0, QFont(MONOSPACE_FONT))
1059 address = str(item.data(0,0).toString())
1060 label = self.wallet.labels.get(address,'')
1061 item.setData(1,0,label)
1063 self.run_hook('update_receive_item', (self, address, item))
1065 c, u = self.wallet.get_addr_balance(address)
1066 balance = format_satoshis( c + u, False, self.wallet.num_zeros )
1067 item.setData(2,0,balance)
1069 if self.expert_mode:
1070 if address in self.wallet.frozen_addresses:
1071 item.setBackgroundColor(0, QColor('lightblue'))
1072 elif address in self.wallet.prioritized_addresses:
1073 item.setBackgroundColor(0, QColor('lightgreen'))
1076 def update_receive_tab(self):
1077 l = self.receive_list
1080 l.setColumnHidden(2, not self.expert_mode)
1081 l.setColumnHidden(3, not self.expert_mode)
1082 if not self.expert_mode:
1083 width = self.column_widths['receive'][0][0]
1084 l.setColumnWidth(0, width)
1086 for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1087 l.setColumnWidth(i, width)
1090 for k, account in self.wallet.accounts.items():
1091 name = account.get('name',str(k))
1092 c,u = self.wallet.get_account_balance(k)
1093 account_item = QTreeWidgetItem( [ name, '', format_satoshis(c+u), ''] )
1094 l.addTopLevelItem(account_item)
1095 account_item.setExpanded(True)
1098 for is_change in [0,1]:
1099 name = "Receiving" if not is_change else "Change"
1100 seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1101 account_item.addChild(seq_item)
1102 if not is_change: seq_item.setExpanded(True)
1106 for address in account[is_change]:
1107 h = self.wallet.history.get(address,[])
1112 if gap > self.wallet.gap_limit:
1117 num_tx = '*' if h == ['*'] else "%d"%len(h)
1118 item = QTreeWidgetItem( [ address, '', '', num_tx] )
1119 self.update_receive_item(item)
1121 item.setBackgroundColor(1, QColor('red'))
1122 seq_item.addChild(item)
1124 if self.wallet.imported_keys:
1125 c,u = self.wallet.get_imported_balance()
1126 account_item = QTreeWidgetItem( [ _('Imported'), '', format_satoshis(c+u), ''] )
1127 l.addTopLevelItem(account_item)
1128 account_item.setExpanded(True)
1129 for address in self.wallet.imported_keys.keys():
1130 item = QTreeWidgetItem( [ address, '', '', ''] )
1131 self.update_receive_item(item)
1132 account_item.addChild(item)
1135 # we use column 1 because column 0 may be hidden
1136 l.setCurrentItem(l.topLevelItem(0),1)
1138 def show_contact_details(self, m):
1139 a = self.wallet.aliases.get(m)
1141 if a[0] in self.wallet.authorities.keys():
1142 s = self.wallet.authorities.get(a[0])
1145 msg = _('Alias:')+' '+ m + '\n'+_('Target address:')+' '+ a[1] + '\n\n'+_('Signed by:')+' ' + s + '\n'+_('Signing address:')+' ' + a[0]
1146 QMessageBox.information(self, 'Alias', msg, 'OK')
1148 def update_contacts_tab(self):
1150 l = self.contacts_list
1154 for alias, v in self.wallet.aliases.items():
1156 alias_targets.append(target)
1157 item = QTreeWidgetItem( [ target, alias, '-'] )
1158 item.setBackgroundColor(0, QColor('lightgray'))
1159 l.addTopLevelItem(item)
1161 for address in self.wallet.addressbook:
1162 if address in alias_targets: continue
1163 label = self.wallet.labels.get(address,'')
1165 for tx in self.wallet.transactions.values():
1166 if address in map(lambda x: x[0], tx.outputs): n += 1
1168 item = QTreeWidgetItem( [ address, label, "%d"%n] )
1169 item.setFont(0, QFont(MONOSPACE_FONT))
1170 l.addTopLevelItem(item)
1172 l.setCurrentItem(l.topLevelItem(0))
1175 def create_console_tab(self):
1176 from qt_console import Console
1177 self.console = console = Console()
1178 self.console.history = self.config.get("console-history",[])
1179 self.console.history_index = len(self.console.history)
1181 console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1182 console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1184 c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1186 def mkfunc(f, method):
1187 return lambda *args: apply( f, (method, args, self.password_dialog ))
1189 if m[0]=='_' or m=='wallet' or m == 'interface': continue
1190 methods[m] = mkfunc(c._run, m)
1192 console.updateNamespace(methods)
1196 def create_status_bar(self):
1197 self.status_text = ""
1199 sb.setFixedHeight(35)
1200 qtVersion = qVersion()
1202 update_notification = UpdateLabel(self.config)
1203 if(update_notification.new_version):
1204 sb.addPermanentWidget(update_notification)
1206 if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1207 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1208 if self.wallet.seed:
1209 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), _("Password"), lambda: self.change_password_dialog(self.wallet, self) ) )
1210 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1211 if self.wallet.seed:
1212 sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
1213 self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), lambda: self.network_dialog(self.wallet, self) )
1214 sb.addPermanentWidget( self.status_button )
1216 self.setStatusBar(sb)
1220 self.config.set_key('gui', 'lite', True)
1223 self.lite.mini.show()
1225 self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1226 self.lite.main(None)
1228 def new_contact_dialog(self):
1229 text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1230 address = unicode(text)
1232 if is_valid(address):
1233 self.wallet.addressbook.append(address)
1235 self.update_contacts_tab()
1236 self.update_history_tab()
1237 self.update_completions()
1239 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1241 def show_master_public_key(self):
1242 dialog = QDialog(self)
1244 dialog.setWindowTitle(_("Master Public Key"))
1246 main_text = QTextEdit()
1247 main_text.setText(self.wallet.get_master_public_key())
1248 main_text.setReadOnly(True)
1249 main_text.setMaximumHeight(170)
1250 qrw = QRCodeWidget(self.wallet.get_master_public_key())
1252 ok_button = QPushButton(_("OK"))
1253 ok_button.setDefault(True)
1254 ok_button.clicked.connect(dialog.accept)
1256 main_layout = QGridLayout()
1257 main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1259 main_layout.addWidget(main_text, 1, 0)
1260 main_layout.addWidget(qrw, 1, 1 )
1262 vbox = QVBoxLayout()
1263 vbox.addLayout(main_layout)
1264 hbox = QHBoxLayout()
1266 hbox.addWidget(ok_button)
1267 vbox.addLayout(hbox)
1269 dialog.setLayout(vbox)
1274 def show_seed_dialog(self, password):
1275 if not self.wallet.seed:
1276 QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1279 seed = self.wallet.decode_seed(password)
1281 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1283 self.show_seed(seed, self)
1287 def show_seed(self, seed, parent=None):
1288 dialog = QDialog(parent)
1290 dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1292 brainwallet = ' '.join(mnemonic.mn_encode(seed))
1294 label1 = QLabel(_("Your wallet generation seed is")+ ":")
1296 seed_text = QTextEdit(brainwallet)
1297 seed_text.setReadOnly(True)
1298 seed_text.setMaximumHeight(130)
1300 msg2 = _("Please write down or memorize these 12 words (order is important).") + " " \
1301 + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1302 + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1303 + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1304 label2 = QLabel(msg2)
1305 label2.setWordWrap(True)
1308 logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1309 logo.setMaximumWidth(60)
1311 qrw = QRCodeWidget(seed)
1313 ok_button = QPushButton(_("OK"))
1314 ok_button.setDefault(True)
1315 ok_button.clicked.connect(dialog.accept)
1317 grid = QGridLayout()
1318 #main_layout.addWidget(logo, 0, 0)
1320 grid.addWidget(logo, 0, 0)
1321 grid.addWidget(label1, 0, 1)
1323 grid.addWidget(seed_text, 1, 0, 1, 2)
1325 grid.addWidget(qrw, 0, 2, 2, 1)
1327 vbox = QVBoxLayout()
1328 vbox.addLayout(grid)
1329 vbox.addWidget(label2)
1331 hbox = QHBoxLayout()
1333 hbox.addWidget(ok_button)
1334 vbox.addLayout(hbox)
1336 dialog.setLayout(vbox)
1339 def show_qrcode(self, data, title = "QR code"):
1343 d.setWindowTitle(title)
1344 d.setMinimumSize(270, 300)
1345 vbox = QVBoxLayout()
1346 qrw = QRCodeWidget(data)
1347 vbox.addWidget(qrw, 1)
1348 vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1349 hbox = QHBoxLayout()
1353 filename = "qrcode.bmp"
1354 bmp.save_qrcode(qrw.qr, filename)
1355 QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1357 b = QPushButton(_("Save"))
1359 b.clicked.connect(print_qr)
1361 b = QPushButton(_("Close"))
1363 b.clicked.connect(d.accept)
1366 vbox.addLayout(hbox)
1371 def do_protect(self, func, args):
1372 if self.wallet.use_encryption:
1373 password = self.password_dialog()
1379 if args != (False,):
1380 args = (self,) + args + (password,)
1382 args = (self,password)
1387 def show_private_key(self, address, password):
1388 if not address: return
1390 pk = self.wallet.get_private_key(address, password)
1391 except BaseException, e:
1392 self.show_message(str(e))
1394 QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1398 def do_sign(self, address, message, signature, password):
1400 sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1401 signature.setText(sig)
1402 except BaseException, e:
1403 self.show_message(str(e))
1405 def sign_message(self, address):
1406 if not address: return
1409 d.setWindowTitle(_('Sign Message'))
1410 d.setMinimumSize(410, 290)
1412 tab_widget = QTabWidget()
1414 layout = QGridLayout(tab)
1416 sign_address = QLineEdit()
1418 sign_address.setText(address)
1419 layout.addWidget(QLabel(_('Address')), 1, 0)
1420 layout.addWidget(sign_address, 1, 1)
1422 sign_message = QTextEdit()
1423 layout.addWidget(QLabel(_('Message')), 2, 0)
1424 layout.addWidget(sign_message, 2, 1)
1425 layout.setRowStretch(2,3)
1427 sign_signature = QTextEdit()
1428 layout.addWidget(QLabel(_('Signature')), 3, 0)
1429 layout.addWidget(sign_signature, 3, 1)
1430 layout.setRowStretch(3,1)
1433 hbox = QHBoxLayout()
1434 b = QPushButton(_("Sign"))
1436 b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1437 b = QPushButton(_("Close"))
1438 b.clicked.connect(d.accept)
1440 layout.addLayout(hbox, 4, 1)
1441 tab_widget.addTab(tab, _("Sign"))
1445 layout = QGridLayout(tab)
1447 verify_address = QLineEdit()
1448 layout.addWidget(QLabel(_('Address')), 1, 0)
1449 layout.addWidget(verify_address, 1, 1)
1451 verify_message = QTextEdit()
1452 layout.addWidget(QLabel(_('Message')), 2, 0)
1453 layout.addWidget(verify_message, 2, 1)
1454 layout.setRowStretch(2,3)
1456 verify_signature = QTextEdit()
1457 layout.addWidget(QLabel(_('Signature')), 3, 0)
1458 layout.addWidget(verify_signature, 3, 1)
1459 layout.setRowStretch(3,1)
1463 self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText()))
1464 self.show_message(_("Signature verified"))
1465 except BaseException, e:
1466 self.show_message(str(e))
1469 hbox = QHBoxLayout()
1470 b = QPushButton(_("Verify"))
1471 b.clicked.connect(do_verify)
1473 b = QPushButton(_("Close"))
1474 b.clicked.connect(d.accept)
1476 layout.addLayout(hbox, 4, 1)
1477 tab_widget.addTab(tab, _("Verify"))
1479 vbox = QVBoxLayout()
1480 vbox.addWidget(tab_widget)
1487 def question(self, msg):
1488 return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1490 def show_message(self, msg):
1491 QMessageBox.information(self, _('Message'), msg, _('OK'))
1493 def password_dialog(self ):
1500 vbox = QVBoxLayout()
1501 msg = _('Please enter your password')
1502 vbox.addWidget(QLabel(msg))
1504 grid = QGridLayout()
1506 grid.addWidget(QLabel(_('Password')), 1, 0)
1507 grid.addWidget(pw, 1, 1)
1508 vbox.addLayout(grid)
1510 vbox.addLayout(ok_cancel_buttons(d))
1513 if not d.exec_(): return
1514 return unicode(pw.text())
1521 def change_password_dialog( wallet, parent=None ):
1524 QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1532 new_pw = QLineEdit()
1533 new_pw.setEchoMode(2)
1534 conf_pw = QLineEdit()
1535 conf_pw.setEchoMode(2)
1537 vbox = QVBoxLayout()
1539 msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1540 +_('To disable wallet encryption, enter an empty new password.')) \
1541 if wallet.use_encryption else _('Your wallet keys are not encrypted')
1543 msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1544 +_("Leave these fields empty if you want to disable encryption.")
1545 vbox.addWidget(QLabel(msg))
1547 grid = QGridLayout()
1550 if wallet.use_encryption:
1551 grid.addWidget(QLabel(_('Password')), 1, 0)
1552 grid.addWidget(pw, 1, 1)
1554 grid.addWidget(QLabel(_('New Password')), 2, 0)
1555 grid.addWidget(new_pw, 2, 1)
1557 grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1558 grid.addWidget(conf_pw, 3, 1)
1559 vbox.addLayout(grid)
1561 vbox.addLayout(ok_cancel_buttons(d))
1564 if not d.exec_(): return
1566 password = unicode(pw.text()) if wallet.use_encryption else None
1567 new_password = unicode(new_pw.text())
1568 new_password2 = unicode(conf_pw.text())
1571 seed = wallet.decode_seed(password)
1573 QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1576 if new_password != new_password2:
1577 QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1578 return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1580 wallet.update_password(seed, password, new_password)
1583 def seed_dialog(wallet, parent=None):
1587 vbox = QVBoxLayout()
1588 msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
1589 vbox.addWidget(QLabel(msg))
1591 grid = QGridLayout()
1594 seed_e = QLineEdit()
1595 grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
1596 grid.addWidget(seed_e, 1, 1)
1600 grid.addWidget(QLabel(_('Gap limit')), 2, 0)
1601 grid.addWidget(gap_e, 2, 1)
1602 gap_e.textChanged.connect(lambda: numbify(gap_e,True))
1603 vbox.addLayout(grid)
1605 vbox.addLayout(ok_cancel_buttons(d))
1608 if not d.exec_(): return
1611 gap = int(unicode(gap_e.text()))
1613 QMessageBox.warning(None, _('Error'), 'error', 'OK')
1617 seed = str(seed_e.text())
1620 print_error("Warning: Not hex, trying decode")
1622 seed = mnemonic.mn_decode( seed.split(' ') )
1624 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
1628 QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
1633 def generate_transaction_information_widget(self, tx):
1634 tabs = QTabWidget(self)
1637 grid_ui = QGridLayout(tab1)
1638 grid_ui.setColumnStretch(0,1)
1639 tabs.addTab(tab1, _('Outputs') )
1641 tree_widget = MyTreeWidget(self)
1642 tree_widget.setColumnCount(2)
1643 tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1644 tree_widget.setColumnWidth(0, 300)
1645 tree_widget.setColumnWidth(1, 50)
1647 for address, value in tx.outputs:
1648 item = QTreeWidgetItem( [address, "%s" % ( format_satoshis(value))] )
1649 tree_widget.addTopLevelItem(item)
1651 tree_widget.setMaximumHeight(100)
1653 grid_ui.addWidget(tree_widget)
1656 grid_ui = QGridLayout(tab2)
1657 grid_ui.setColumnStretch(0,1)
1658 tabs.addTab(tab2, _('Inputs') )
1660 tree_widget = MyTreeWidget(self)
1661 tree_widget.setColumnCount(2)
1662 tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1664 for input_line in tx.inputs:
1665 item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1666 tree_widget.addTopLevelItem(item)
1668 tree_widget.setMaximumHeight(100)
1670 grid_ui.addWidget(tree_widget)
1674 def tx_dict_from_text(self, txt):
1676 tx_dict = json.loads(str(txt))
1677 assert "hex" in tx_dict.keys()
1678 assert "complete" in tx_dict.keys()
1679 if not tx_dict["complete"]:
1680 assert "input_info" in tx_dict.keys()
1682 QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1687 def read_tx_from_file(self):
1688 fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1692 with open(fileName, "r") as f:
1693 file_content = f.read()
1694 except (ValueError, IOError, os.error), reason:
1695 QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1697 return self.tx_dict_from_text(file_content)
1701 def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1703 self.wallet.signrawtransaction(tx, input_info, [], password)
1705 fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1707 with open(fileName, "w+") as f:
1708 f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1709 self.show_message(_("Transaction saved succesfully"))
1712 except BaseException, e:
1713 self.show_message(str(e))
1716 def send_raw_transaction(self, raw_tx, dialog = ""):
1717 result, result_message = self.wallet.sendtx( raw_tx )
1719 self.show_message("Transaction succesfully sent: %s" % (result_message))
1723 self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1725 def do_process_from_text(self):
1726 dialog = QDialog(self)
1727 dialog.setMinimumWidth(500)
1728 dialog.setWindowTitle(_('Input raw transaction'))
1732 l.addWidget(QLabel(_("Transaction:")))
1736 ok_button = QPushButton(_("Load transaction"))
1737 ok_button.setDefault(True)
1738 ok_button.clicked.connect(dialog.accept)
1739 l.addWidget(ok_button)
1742 tx_dict = self.tx_dict_from_text(unicode(txt.toPlainText()))
1744 self.create_process_transaction_window(tx_dict)
1746 def do_process_from_file(self):
1747 tx_dict = self.read_tx_from_file()
1749 self.create_process_transaction_window(tx_dict)
1751 def create_process_transaction_window(self, tx_dict):
1752 tx = Transaction(tx_dict["hex"])
1754 dialog = QDialog(self)
1755 dialog.setMinimumWidth(500)
1756 dialog.setWindowTitle(_('Process raw transaction'))
1762 l.addWidget(QLabel(_("Transaction status: ")), 3,0)
1763 l.addWidget(QLabel(_("Actions")), 4,0)
1765 if tx_dict["complete"] == False:
1766 l.addWidget(QLabel(_("Unsigned")), 3,1)
1767 if self.wallet.seed :
1768 b = QPushButton("Sign transaction")
1769 input_info = json.loads(tx_dict["input_info"])
1770 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1771 l.addWidget(b, 4, 1)
1773 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1775 l.addWidget(QLabel(_("Signed")), 3,1)
1776 b = QPushButton("Broadcast transaction")
1777 b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1780 l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1781 cancelButton = QPushButton(_("Cancel"))
1782 cancelButton.clicked.connect(lambda: dialog.done(0))
1783 l.addWidget(cancelButton, 4,2)
1789 def do_export_privkeys(self, password):
1790 self.show_message("%s\n%s\n%s" % (_("WARNING: ALL your private keys are secret."), _("Exposing a single private key can compromise your entire wallet!"), _("In particular, DO NOT use 'redeem private key' services proposed by third parties.")))
1793 select_export = _('Select file to export your private keys to')
1794 fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1796 with open(fileName, "w+") as csvfile:
1797 transaction = csv.writer(csvfile)
1798 transaction.writerow(["address", "private_key"])
1801 for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1802 transaction.writerow(["%34s"%addr,pk])
1804 self.show_message(_("Private keys exported."))
1806 except (IOError, os.error), reason:
1807 export_error_label = _("Electrum was unable to produce a private key-export.")
1808 QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1810 except BaseException, e:
1811 self.show_message(str(e))
1815 def do_import_labels(self):
1816 labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1817 if not labelsFile: return
1819 f = open(labelsFile, 'r')
1822 for key, value in json.loads(data).items():
1823 self.wallet.labels[key] = value
1825 QMessageBox.information(None, _("Labels imported"), _("Your labels where imported from")+" '%s'" % str(labelsFile))
1826 except (IOError, os.error), reason:
1827 QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1830 def do_export_labels(self):
1831 labels = self.wallet.labels
1833 fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1835 with open(fileName, 'w+') as f:
1836 json.dump(labels, f)
1837 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1838 except (IOError, os.error), reason:
1839 QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1842 def do_export_history(self):
1843 from gui_lite import csv_transaction
1844 csv_transaction(self.wallet)
1848 def do_import_privkey(self, password):
1849 if not self.wallet.imported_keys:
1850 r = QMessageBox.question(None, _('Warning'), _('Warning: Imported keys are not recoverable from seed.') + ' ' \
1851 + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '\n\n' \
1852 + _('Are you sure you understand what you are doing?'), 3, 4)
1855 text, ok = QInputDialog.getText(self, _('Import private key'), _('Private Key') + ':')
1857 sec = str(text).strip()
1859 addr = self.wallet.import_key(sec, password)
1861 QMessageBox.critical(None, _("Unable to import key"), "error")
1863 QMessageBox.information(None, _("Key imported"), addr)
1864 self.update_receive_tab()
1865 self.update_history_tab()
1866 except BaseException as e:
1867 QMessageBox.critical(None, _("Unable to import key"), str(e))
1870 def settings_dialog(self):
1872 d.setWindowTitle(_('Electrum Settings'))
1874 vbox = QVBoxLayout()
1876 tabs = QTabWidget(self)
1877 vbox.addWidget(tabs)
1880 grid_ui = QGridLayout(tab1)
1881 grid_ui.setColumnStretch(0,1)
1882 tabs.addTab(tab1, _('Display') )
1884 nz_label = QLabel(_('Display zeros'))
1885 grid_ui.addWidget(nz_label, 0, 0)
1887 nz_e.setText("%d"% self.wallet.num_zeros)
1888 grid_ui.addWidget(nz_e, 0, 1)
1889 msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1890 grid_ui.addWidget(HelpButton(msg), 0, 2)
1891 nz_e.textChanged.connect(lambda: numbify(nz_e,True))
1892 if not self.config.is_modifiable('num_zeros'):
1893 for w in [nz_e, nz_label]: w.setEnabled(False)
1895 lang_label=QLabel(_('Language') + ':')
1896 grid_ui.addWidget(lang_label, 1, 0)
1897 lang_combo = QComboBox()
1898 from i18n import languages
1899 lang_combo.addItems(languages.values())
1901 index = languages.keys().index(self.config.get("language",''))
1904 lang_combo.setCurrentIndex(index)
1905 grid_ui.addWidget(lang_combo, 1, 1)
1906 grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1907 if not self.config.is_modifiable('language'):
1908 for w in [lang_combo, lang_label]: w.setEnabled(False)
1910 currencies = self.exchanger.get_currencies()
1911 currencies.insert(0, "None")
1913 cur_label=QLabel(_('Currency') + ':')
1914 grid_ui.addWidget(cur_label , 2, 0)
1915 cur_combo = QComboBox()
1916 cur_combo.addItems(currencies)
1918 index = currencies.index(self.config.get('currency', "None"))
1921 cur_combo.setCurrentIndex(index)
1922 grid_ui.addWidget(cur_combo, 2, 1)
1923 grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
1925 view_label=QLabel(_('Receive Tab') + ':')
1926 grid_ui.addWidget(view_label , 3, 0)
1927 view_combo = QComboBox()
1928 view_combo.addItems([_('Simple'), _('Advanced')])
1929 view_combo.setCurrentIndex(self.expert_mode)
1930 grid_ui.addWidget(view_combo, 3, 1)
1931 hh = _('This selects the interaction mode of the "Receive" tab.')+' ' + '\n\n' \
1932 + _('Simple') + ': ' + _('Show only addresses and labels.') + '\n\n' \
1933 + _('Advanced') + ': ' + _('Show address balances and add extra menu items to freeze/prioritize addresses.') + '\n\n'
1935 grid_ui.addWidget(HelpButton(hh), 3, 2)
1936 grid_ui.setRowStretch(4,1)
1940 grid_wallet = QGridLayout(tab2)
1941 grid_wallet.setColumnStretch(0,1)
1942 tabs.addTab(tab2, _('Wallet') )
1944 fee_label = QLabel(_('Transaction fee'))
1945 grid_wallet.addWidget(fee_label, 0, 0)
1947 fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
1948 grid_wallet.addWidget(fee_e, 0, 2)
1949 msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
1950 + _('Recommended value') + ': 0.001'
1951 grid_wallet.addWidget(HelpButton(msg), 0, 3)
1952 fee_e.textChanged.connect(lambda: numbify(fee_e,False))
1953 if not self.config.is_modifiable('fee'):
1954 for w in [fee_e, fee_label]: w.setEnabled(False)
1956 usechange_label = QLabel(_('Use change addresses'))
1957 grid_wallet.addWidget(usechange_label, 1, 0)
1958 usechange_combo = QComboBox()
1959 usechange_combo.addItems([_('Yes'), _('No')])
1960 usechange_combo.setCurrentIndex(not self.wallet.use_change)
1961 grid_wallet.addWidget(usechange_combo, 1, 2)
1962 grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
1963 if not self.config.is_modifiable('use_change'): usechange_combo.setEnabled(False)
1965 gap_label = QLabel(_('Gap limit'))
1966 grid_wallet.addWidget(gap_label, 2, 0)
1968 gap_e.setText("%d"% self.wallet.gap_limit)
1969 grid_wallet.addWidget(gap_e, 2, 2)
1970 msg = _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
1971 + _('You may increase it if you need more receiving addresses.') + '\n\n' \
1972 + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
1973 + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
1974 + _('Warning') + ': ' \
1975 + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
1976 + _('Do not modify it if you do not understand what you are doing, or if you expect to recover your wallet without knowing it!') + '\n\n'
1977 grid_wallet.addWidget(HelpButton(msg), 2, 3)
1978 gap_e.textChanged.connect(lambda: numbify(nz_e,True))
1979 if not self.config.is_modifiable('gap_limit'):
1980 for w in [gap_e, gap_label]: w.setEnabled(False)
1982 grid_wallet.setRowStretch(3,1)
1987 grid_io = QGridLayout(tab3)
1988 grid_io.setColumnStretch(0,1)
1989 tabs.addTab(tab3, _('Import/Export') )
1991 grid_io.addWidget(QLabel(_('Labels')), 1, 0)
1992 grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
1993 grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
1994 grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
1996 grid_io.addWidget(QLabel(_('History')), 2, 0)
1997 grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
1998 grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
2000 grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
2002 grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
2003 grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
2004 grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
2006 grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
2007 grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
2008 grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
2009 + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
2010 + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
2013 grid_io.addWidget(QLabel(_("Load transaction")), 5, 0)
2014 grid_io.addWidget(EnterButton(_("From file"), self.do_process_from_file), 5, 1)
2015 grid_io.addWidget(EnterButton(_("From text"), self.do_process_from_text), 5, 2)
2016 grid_io.addWidget(HelpButton(_("This will give you the option to sign or broadcast a transaction based on it's status.")), 5, 3)
2018 grid_io.setRowStretch(5,1)
2023 tab5 = QScrollArea()
2024 grid_plugins = QGridLayout(tab5)
2025 grid_plugins.setColumnStretch(0,1)
2026 tabs.addTab(tab5, _('Plugins') )
2027 def mk_toggle(cb, p):
2028 return lambda: cb.setChecked(p.toggle(self))
2029 for i, p in enumerate(self.plugins):
2031 name, description = p.get_info()
2032 cb = QCheckBox(name)
2033 cb.setDisabled(not p.is_available())
2034 cb.setChecked(p.is_enabled())
2035 cb.clicked.connect(mk_toggle(cb,p))
2036 grid_plugins.addWidget(cb, i, 0)
2037 grid_plugins.addWidget(HelpButton(description), i, 2)
2039 print_msg("Error: cannot display plugin", p)
2040 traceback.print_exc(file=sys.stdout)
2041 grid_plugins.setRowStretch(i+1,1)
2043 vbox.addLayout(ok_cancel_buttons(d))
2047 if not d.exec_(): return
2049 fee = unicode(fee_e.text())
2051 fee = int( 100000000 * Decimal(fee) )
2053 QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2056 if self.wallet.fee != fee:
2057 self.wallet.fee = fee
2060 nz = unicode(nz_e.text())
2065 QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2068 if self.wallet.num_zeros != nz:
2069 self.wallet.num_zeros = nz
2070 self.config.set_key('num_zeros', nz, True)
2071 self.update_history_tab()
2072 self.update_receive_tab()
2074 usechange_result = usechange_combo.currentIndex() == 0
2075 if self.wallet.use_change != usechange_result:
2076 self.wallet.use_change = usechange_result
2077 self.config.set_key('use_change', self.wallet.use_change, True)
2080 n = int(gap_e.text())
2082 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2085 if self.wallet.gap_limit != n:
2086 r = self.wallet.change_gap_limit(n)
2088 self.update_receive_tab()
2089 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2091 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2093 need_restart = False
2095 lang_request = languages.keys()[lang_combo.currentIndex()]
2096 if lang_request != self.config.get('language'):
2097 self.config.set_key("language", lang_request, True)
2100 cur_request = str(currencies[cur_combo.currentIndex()])
2101 if cur_request != self.config.get('currency', "None"):
2102 self.config.set_key('currency', cur_request, True)
2103 self.update_wallet()
2106 QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2108 self.receive_tab_set_mode(view_combo.currentIndex())
2112 def network_dialog(wallet, parent=None):
2113 interface = wallet.interface
2115 if interface.is_connected:
2116 status = _("Connected to")+" %s\n%d "%(interface.host, wallet.verifier.height)+_("blocks")
2118 status = _("Not connected")
2119 server = interface.server
2122 status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
2123 server = interface.server
2125 plist, servers_list = interface.get_servers_list()
2129 d.setWindowTitle(_('Server'))
2130 d.setMinimumSize(375, 20)
2132 vbox = QVBoxLayout()
2135 hbox = QHBoxLayout()
2137 l.setPixmap(QPixmap(":icons/network.png"))
2140 hbox.addWidget(QLabel(status))
2142 vbox.addLayout(hbox)
2146 grid = QGridLayout()
2148 vbox.addLayout(grid)
2151 server_protocol = QComboBox()
2152 server_host = QLineEdit()
2153 server_host.setFixedWidth(200)
2154 server_port = QLineEdit()
2155 server_port.setFixedWidth(60)
2157 protocol_names = ['TCP', 'HTTP', 'TCP/SSL', 'HTTPS']
2158 protocol_letters = 'thsg'
2159 DEFAULT_PORTS = {'t':'50001', 's':'50002', 'h':'8081', 'g':'8082'}
2160 server_protocol.addItems(protocol_names)
2162 grid.addWidget(QLabel(_('Server') + ':'), 0, 0)
2163 grid.addWidget(server_protocol, 0, 1)
2164 grid.addWidget(server_host, 0, 2)
2165 grid.addWidget(server_port, 0, 3)
2167 def change_protocol(p):
2168 protocol = protocol_letters[p]
2169 host = unicode(server_host.text())
2170 pp = plist.get(host,DEFAULT_PORTS)
2171 if protocol not in pp.keys():
2172 protocol = pp.keys()[0]
2174 server_host.setText( host )
2175 server_port.setText( port )
2177 server_protocol.connect(server_protocol, SIGNAL('currentIndexChanged(int)'), change_protocol)
2179 label = _('Active Servers') if wallet.interface.servers else _('Default Servers')
2180 servers_list_widget = QTreeWidget(parent)
2181 servers_list_widget.setHeaderLabels( [ label, _('Type') ] )
2182 servers_list_widget.setMaximumHeight(150)
2183 servers_list_widget.setColumnWidth(0, 240)
2184 for _host in servers_list.keys():
2185 _type = 'P' if servers_list[_host].get('pruning') else 'F'
2186 servers_list_widget.addTopLevelItem(QTreeWidgetItem( [ _host, _type ] ))
2188 def change_server(host, protocol=None):
2189 pp = plist.get(host,DEFAULT_PORTS)
2191 port = pp.get(protocol)
2192 if not port: protocol = None
2195 if 't' in pp.keys():
2197 port = pp.get(protocol)
2199 protocol = pp.keys()[0]
2200 port = pp.get(protocol)
2202 server_host.setText( host )
2203 server_port.setText( port )
2204 server_protocol.setCurrentIndex(protocol_letters.index(protocol))
2206 if not plist: return
2207 for p in protocol_letters:
2208 i = protocol_letters.index(p)
2209 j = server_protocol.model().index(i,0)
2210 if p not in pp.keys():
2211 server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
2213 server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
2217 host, port, protocol = server.split(':')
2218 change_server(host,protocol)
2220 servers_list_widget.connect(servers_list_widget, SIGNAL('itemClicked(QTreeWidgetItem*, int)'), lambda x: change_server(unicode(x.text(0))))
2221 grid.addWidget(servers_list_widget, 1, 1, 1, 3)
2223 if not wallet.config.is_modifiable('server'):
2224 for w in [server_host, server_port, server_protocol, servers_list_widget]: w.setEnabled(False)
2227 autocycle_cb = QCheckBox(_('Try random servers if disconnected'))
2228 autocycle_cb.setChecked(wallet.config.get('auto_cycle', False))
2229 grid.addWidget(autocycle_cb, 3, 1, 3, 2)
2230 if not wallet.config.is_modifiable('auto_cycle'): autocycle_cb.setEnabled(False)
2233 proxy_mode = QComboBox()
2234 proxy_host = QLineEdit()
2235 proxy_host.setFixedWidth(200)
2236 proxy_port = QLineEdit()
2237 proxy_port.setFixedWidth(60)
2238 proxy_mode.addItems(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
2240 def check_for_disable(index = False):
2241 if proxy_mode.currentText() != 'NONE':
2242 proxy_host.setEnabled(True)
2243 proxy_port.setEnabled(True)
2245 proxy_host.setEnabled(False)
2246 proxy_port.setEnabled(False)
2249 proxy_mode.connect(proxy_mode, SIGNAL('currentIndexChanged(int)'), check_for_disable)
2251 if not wallet.config.is_modifiable('proxy'):
2252 for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
2254 proxy_config = interface.proxy if interface.proxy else { "mode":"none", "host":"localhost", "port":"8080"}
2255 proxy_mode.setCurrentIndex(proxy_mode.findText(str(proxy_config.get("mode").upper())))
2256 proxy_host.setText(proxy_config.get("host"))
2257 proxy_port.setText(proxy_config.get("port"))
2259 grid.addWidget(QLabel(_('Proxy') + ':'), 2, 0)
2260 grid.addWidget(proxy_mode, 2, 1)
2261 grid.addWidget(proxy_host, 2, 2)
2262 grid.addWidget(proxy_port, 2, 3)
2265 vbox.addLayout(ok_cancel_buttons(d))
2268 if not d.exec_(): return
2270 server = unicode( server_host.text() ) + ':' + unicode( server_port.text() ) + ':' + (protocol_letters[server_protocol.currentIndex()])
2271 if proxy_mode.currentText() != 'NONE':
2272 proxy = { u'mode':unicode(proxy_mode.currentText()).lower(), u'host':unicode(proxy_host.text()), u'port':unicode(proxy_port.text()) }
2276 wallet.config.set_key("proxy", proxy, True)
2277 wallet.config.set_key("server", server, True)
2278 interface.set_server(server, proxy)
2279 wallet.config.set_key('auto_cycle', autocycle_cb.isChecked(), True)
2282 def closeEvent(self, event):
2284 self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2285 self.save_column_widths()
2286 self.config.set_key("column-widths", self.column_widths, True)
2287 self.config.set_key("console-history",self.console.history[-50:])
2293 def __init__(self, wallet, config, app=None):
2294 self.wallet = wallet
2295 self.config = config
2297 self.app = QApplication(sys.argv)
2300 def restore_or_create(self):
2301 msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2302 r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2303 if r==2: return None
2304 return 'restore' if r==1 else 'create'
2306 def seed_dialog(self):
2307 return ElectrumWindow.seed_dialog( self.wallet )
2309 def network_dialog(self):
2310 return ElectrumWindow.network_dialog( self.wallet, parent=None )
2313 def show_seed(self):
2314 ElectrumWindow.show_seed(self.wallet.seed)
2317 def password_dialog(self):
2318 if self.wallet.seed:
2319 ElectrumWindow.change_password_dialog(self.wallet)
2322 def restore_wallet(self):
2323 wallet = self.wallet
2324 # wait until we are connected, because the user might have selected another server
2325 if not wallet.interface.is_connected:
2326 waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2327 waiting_dialog(waiting)
2329 waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2330 %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2332 wallet.set_up_to_date(False)
2333 wallet.interface.poke('synchronizer')
2334 waiting_dialog(waiting)
2335 if wallet.is_found():
2336 print_error( "Recovery successful" )
2338 QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2345 w = ElectrumWindow(self.wallet, self.config)
2346 if url: w.set_url(url)