Removed accounts from menu
[electrum-nvc.git] / gui / gui_classic.py
1 #!/usr/bin/env python
2 #
3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2012 thomasv@gitorious
5 #
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.
10 #
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.
15 #
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/>.
18
19 import sys, time, datetime, re, threading
20 from i18n import _, set_language
21 from electrum.util import print_error, print_msg
22 import os.path, json, ast, traceback
23 import shutil
24
25
26 try:
27     import PyQt4
28 except:
29     sys.exit("Error: Could not import PyQt4 on Linux systems, you may try 'sudo apt-get install python-qt4'")
30
31 from PyQt4.QtGui import *
32 from PyQt4.QtCore import *
33 import PyQt4.QtCore as QtCore
34
35 from electrum.bitcoin import MIN_RELAY_TX_FEE
36
37 try:
38     import icons_rc
39 except:
40     sys.exit("Error: Could not import icons_rc.py, please generate it with: 'pyrcc4 icons.qrc -o gui/icons_rc.py'")
41
42 from electrum.wallet import format_satoshis
43 from electrum.bitcoin import Transaction, is_valid
44 from electrum import mnemonic
45 from electrum import util, bitcoin, commands
46
47 import bmp, pyqrnative
48 import exchange_rate
49
50 from amountedit import AmountEdit
51 from network_dialog import NetworkDialog
52 from qrcodewidget import QRCodeWidget
53
54 from decimal import Decimal
55
56 import platform
57 import httplib
58 import socket
59 import webbrowser
60 import csv
61
62 if platform.system() == 'Windows':
63     MONOSPACE_FONT = 'Lucida Console'
64 elif platform.system() == 'Darwin':
65     MONOSPACE_FONT = 'Monaco'
66 else:
67     MONOSPACE_FONT = 'monospace'
68
69 from electrum import ELECTRUM_VERSION
70 import re
71
72 from qt_util import *
73
74 class UpdateLabel(QLabel):
75     def __init__(self, config, parent=None):
76         QLabel.__init__(self, parent)
77         self.new_version = False
78
79         try:
80             con = httplib.HTTPConnection('electrum.org', 80, timeout=5)
81             con.request("GET", "/version")
82             res = con.getresponse()
83         except socket.error as msg:
84             print_error("Could not retrieve version information")
85             return
86             
87         if res.status == 200:
88             self.latest_version = res.read()
89             self.latest_version = self.latest_version.replace("\n","")
90             if(re.match('^\d+(\.\d+)*$', self.latest_version)):
91                 self.config = config
92                 self.current_version = ELECTRUM_VERSION
93                 if(self.compare_versions(self.latest_version, self.current_version) == 1):
94                     latest_seen = self.config.get("last_seen_version",ELECTRUM_VERSION)
95                     if(self.compare_versions(self.latest_version, latest_seen) == 1):
96                         self.new_version = True
97                         self.setText(_("New version available") + ": " + self.latest_version)
98
99
100     def compare_versions(self, version1, version2):
101         def normalize(v):
102             return [int(x) for x in re.sub(r'(\.0+)*$','', v).split(".")]
103         return cmp(normalize(version1), normalize(version2))
104
105     def ignore_this_version(self):
106         self.setText("")
107         self.config.set_key("last_seen_version", self.latest_version, True)
108         QMessageBox.information(self, _("Preference saved"), _("Notifications about this update will not be shown again."))
109         self.dialog.done(0)
110
111     def ignore_all_version(self):
112         self.setText("")
113         self.config.set_key("last_seen_version", "9.9.9", True)
114         QMessageBox.information(self, _("Preference saved"), _("No more notifications about version updates will be shown."))
115         self.dialog.done(0)
116   
117     def open_website(self):
118         webbrowser.open("http://electrum.org/download.html")
119         self.dialog.done(0)
120
121     def mouseReleaseEvent(self, event):
122         dialog = QDialog(self)
123         dialog.setWindowTitle(_('Electrum update'))
124         dialog.setModal(1)
125
126         main_layout = QGridLayout()
127         main_layout.addWidget(QLabel(_("A new version of Electrum is available:")+" " + self.latest_version), 0,0,1,3)
128         
129         ignore_version = QPushButton(_("Ignore this version"))
130         ignore_version.clicked.connect(self.ignore_this_version)
131
132         ignore_all_versions = QPushButton(_("Ignore all versions"))
133         ignore_all_versions.clicked.connect(self.ignore_all_version)
134
135         open_website = QPushButton(_("Goto download page"))
136         open_website.clicked.connect(self.open_website)
137
138         main_layout.addWidget(ignore_version, 1, 0)
139         main_layout.addWidget(ignore_all_versions, 1, 1)
140         main_layout.addWidget(open_website, 1, 2)
141
142         dialog.setLayout(main_layout)
143
144         self.dialog = dialog
145         
146         if not dialog.exec_(): return
147
148
149
150 class Timer(QtCore.QThread):
151     def run(self):
152         while True:
153             self.emit(QtCore.SIGNAL('timersignal'))
154             time.sleep(0.5)
155
156 class HelpButton(QPushButton):
157     def __init__(self, text):
158         QPushButton.__init__(self, '?')
159         self.setFocusPolicy(Qt.NoFocus)
160         self.setFixedWidth(20)
161         self.clicked.connect(lambda: QMessageBox.information(self, 'Help', text, 'OK') )
162
163
164 class EnterButton(QPushButton):
165     def __init__(self, text, func):
166         QPushButton.__init__(self, text)
167         self.func = func
168         self.clicked.connect(func)
169
170     def keyPressEvent(self, e):
171         if e.key() == QtCore.Qt.Key_Return:
172             apply(self.func,())
173
174 class MyTreeWidget(QTreeWidget):
175     def __init__(self, parent):
176         QTreeWidget.__init__(self, parent)
177         def ddfr(item):
178             if not item: return
179             for i in range(0,self.viewport().height()/5):
180                 if self.itemAt(QPoint(0,i*5)) == item:
181                     break
182             else:
183                 return
184             for j in range(0,30):
185                 if self.itemAt(QPoint(0,i*5 + j)) != item:
186                     break
187             self.emit(SIGNAL('customContextMenuRequested(const QPoint&)'), QPoint(50, i*5 + j - 1))
188
189         self.connect(self, SIGNAL('itemActivated(QTreeWidgetItem*, int)'), ddfr)
190         
191
192
193
194 class StatusBarButton(QPushButton):
195     def __init__(self, icon, tooltip, func):
196         QPushButton.__init__(self, icon, '')
197         self.setToolTip(tooltip)
198         self.setFlat(True)
199         self.setMaximumWidth(25)
200         self.clicked.connect(func)
201         self.func = func
202
203     def keyPressEvent(self, e):
204         if e.key() == QtCore.Qt.Key_Return:
205             apply(self.func,())
206
207
208
209
210
211 def waiting_dialog(f):
212
213     s = Timer()
214     s.start()
215     w = QDialog()
216     w.resize(200, 70)
217     w.setWindowTitle('Electrum')
218     l = QLabel('')
219     vbox = QVBoxLayout()
220     vbox.addWidget(l)
221     w.setLayout(vbox)
222     w.show()
223     def ff():
224         s = f()
225         if s: l.setText(s)
226         else: w.close()
227     w.connect(s, QtCore.SIGNAL('timersignal'), ff)
228     w.exec_()
229     w.destroy()
230
231
232
233
234
235
236 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], "receive":[[370], [370,200,130]] }
237
238 class ElectrumWindow(QMainWindow):
239
240     def __init__(self, wallet, config):
241         QMainWindow.__init__(self)
242         self.lite = None
243         self.wallet = wallet
244         self.config = config
245         self.current_account = self.config.get("current_account", None)
246
247         self.init_plugins()
248         self.create_status_bar()
249
250         self.need_update = threading.Event()
251         self.wallet.interface.register_callback('updated', lambda: self.need_update.set())
252         self.wallet.interface.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
253         self.wallet.interface.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
254         self.wallet.interface.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
255
256         self.expert_mode = config.get('classic_expert_mode', False)
257         self.decimal_point = config.get('decimal_point', 8)
258
259         set_language(config.get('language'))
260
261         self.funds_error = False
262         self.completions = QStringListModel()
263
264         self.tabs = tabs = QTabWidget(self)
265         self.column_widths = self.config.get("column_widths", default_column_widths )
266         tabs.addTab(self.create_history_tab(), _('History') )
267         tabs.addTab(self.create_send_tab(), _('Send') )
268         tabs.addTab(self.create_receive_tab(), _('Receive') )
269         tabs.addTab(self.create_contacts_tab(), _('Contacts') )
270         tabs.addTab(self.create_console_tab(), _('Console') )
271         tabs.setMinimumSize(600, 400)
272         tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
273         self.setCentralWidget(tabs)
274
275         g = self.config.get("winpos-qt",[100, 100, 840, 400])
276         self.setGeometry(g[0], g[1], g[2], g[3])
277         title = 'Electrum ' + self.wallet.electrum_version + '  -  ' + self.config.path
278         if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
279         self.setWindowTitle( title )
280
281         self.init_menubar()
282
283         QShortcut(QKeySequence("Ctrl+W"), self, self.close)
284         QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
285         QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
286         QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
287         QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
288         
289         self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
290         self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.wallet.interface.banner) )
291         self.history_list.setFocus(True)
292         
293         self.exchanger = exchange_rate.Exchanger(self)
294         self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
295
296         # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
297         if platform.system() == 'Windows':
298             n = 3 if self.wallet.seed else 2
299             tabs.setCurrentIndex (n)
300             tabs.setCurrentIndex (0)
301
302         # fix fee
303         if self.wallet.fee < 50000:
304             self.wallet.set_fee(50000)
305             self.show_message("Note: Your default fee was raised to 0.0005 BTC/kilobyte")
306
307         # set initial message
308         self.console.showMessage(self.wallet.interface.banner)
309
310         # plugins that need to change the GUI do it here
311         self.run_hook('init_gui')
312
313
314     def select_wallet_file(self):
315         file_name = self.getOpenFileName("Select your wallet file", "*.dat")
316         if not file_name:
317             return
318         else:
319           self.load_wallet(file_name)
320
321     # TODO: I rather import this from the lite gui, is that possible?
322     def backup_wallet(self):
323         try:
324           folderName = QFileDialog.getExistingDirectory(QWidget(), _('Select folder to save a copy of your wallet to'), os.path.expanduser('~/'))
325           if folderName:
326             # TODO: Can we get the current wallet file instead of bruteforcing the default one?
327             sourceFile = util.user_dir() + '/electrum.dat'
328             shutil.copy2(sourceFile, str(folderName))
329             QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(folderName))
330         except (IOError, os.error), reason:
331           QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
332
333     def init_menubar(self):
334         menubar = QMenuBar()
335
336         electrum_menu = menubar.addMenu(_("&File"))
337         open_wallet_action = electrum_menu.addAction(_("Open wallet"))
338         open_wallet_action.triggered.connect(self.select_wallet_file)
339
340         preferences_name = _("Preferences")
341         if sys.platform == 'darwin':
342             preferences_name = _("Electrum preferences") # Settings / Preferences are all reserved keywords in OSX using this as work around
343
344         preferences_menu = electrum_menu.addAction(preferences_name)
345         preferences_menu.triggered.connect(self.settings_dialog)
346         electrum_menu.addSeparator()
347
348         raw_transaction_menu = electrum_menu.addMenu(_("&Load raw transaction"))
349
350         raw_transaction_file = raw_transaction_menu.addAction(_("&From file"))
351         raw_transaction_file.triggered.connect(self.do_process_from_file)
352
353         raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
354         raw_transaction_text.triggered.connect(self.do_process_from_text)
355
356         electrum_menu.addSeparator()
357         quit_item = electrum_menu.addAction(_("&Close"))
358         quit_item.triggered.connect(self.close)
359
360         wallet_menu = menubar.addMenu(_("&Wallet"))
361         wallet_backup = wallet_menu.addAction(_("&Create backup"))
362         wallet_backup.triggered.connect(self.backup_wallet)
363
364         show_menu = wallet_menu.addMenu(_("Show"))
365
366         if self.wallet.seed:
367             show_seed = show_menu.addAction(_("&Seed"))
368             show_seed.triggered.connect(self.show_seed_dialog)
369
370         show_mpk = show_menu.addAction(_("&Master Public Key"))
371         show_mpk.triggered.connect(self.show_master_public_key)
372
373         wallet_menu.addSeparator()
374         new_contact = wallet_menu.addAction(_("&New contact"))
375         new_contact.triggered.connect(self.new_contact_dialog)
376
377         import_menu = menubar.addMenu(_("&Import"))
378         in_labels = import_menu.addAction(_("&Labels"))
379         in_labels.triggered.connect(self.do_import_labels)
380
381         in_private_keys = import_menu.addAction(_("&Private keys"))
382         in_private_keys.triggered.connect(self.do_import_privkey)
383
384         export_menu = menubar.addMenu(_("&Export"))
385         ex_private_keys = export_menu.addAction(_("&Private keys"))
386         ex_private_keys.triggered.connect(self.do_export_privkeys)
387
388         ex_history = export_menu.addAction(_("&History"))
389         ex_history.triggered.connect(self.do_export_history)
390
391         ex_labels = export_menu.addAction(_("&Labels"))
392         ex_labels.triggered.connect(self.do_export_labels)
393
394         help_menu = menubar.addMenu(_("&Help"))
395         doc_open = help_menu.addAction(_("&Documentation"))
396         doc_open.triggered.connect(lambda: webbrowser.open("http://electrum.org/documentation.html"))
397         web_open = help_menu.addAction(_("&Official website")) 
398         web_open.triggered.connect(lambda: webbrowser.open("http://electrum.org"))
399
400         self.setMenuBar(menubar)
401
402
403
404     def load_wallet(self, filename):
405         import electrum
406
407         config = electrum.SimpleConfig({'wallet_path': filename})
408         if not config.wallet_file_exists:
409             self.show_message("file not found "+ filename)
410             return
411
412         #self.wallet.verifier.stop()
413         interface = self.wallet.interface
414         verifier = self.wallet.verifier
415         self.wallet.synchronizer.stop()
416         
417         self.config = config
418         self.wallet = electrum.Wallet(self.config)
419         self.wallet.interface = interface
420         self.wallet.verifier = verifier
421
422         synchronizer = electrum.WalletSynchronizer(self.wallet, self.config)
423         synchronizer.start()
424
425         self.update_wallet()
426
427         
428
429     # plugins
430     def init_plugins(self):
431         import imp, pkgutil, __builtin__
432         if __builtin__.use_local_modules:
433             fp, pathname, description = imp.find_module('plugins')
434             plugin_names = [name for a, name, b in pkgutil.iter_modules([pathname])]
435             plugin_names = filter( lambda name: os.path.exists(os.path.join(pathname,name+'.py')), plugin_names)
436             imp.load_module('electrum_plugins', fp, pathname, description)
437             plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
438         else:
439             import electrum_plugins
440             plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
441             plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
442
443         self.plugins = []
444         for p in plugins:
445             try:
446                 self.plugins.append( p.Plugin(self) )
447             except:
448                 print_msg("Error:cannot initialize plugin",p)
449                 traceback.print_exc(file=sys.stdout)
450
451
452     def run_hook(self, name, *args):
453         for p in self.plugins:
454             if not p.is_enabled():
455                 continue
456             try:
457                 f = eval('p.'+name)
458             except:
459                 continue
460             try:
461                 apply(f, args)
462             except:
463                 print_error("Plugin error")
464                 traceback.print_exc(file=sys.stdout)
465                 
466         return
467
468         
469     def set_label(self, name, text = None):
470         changed = False
471         old_text = self.wallet.labels.get(name)
472         if text:
473             if old_text != text:
474                 self.wallet.labels[name] = text
475                 changed = True
476         else:
477             if old_text:
478                 self.wallet.labels.pop(name)
479                 changed = True
480         self.run_hook('set_label', name, text, changed)
481         return changed
482
483
484     # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
485     def getOpenFileName(self, title, filter = None):
486         directory = self.config.get('io_dir', os.path.expanduser('~'))
487         fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
488         if fileName and directory != os.path.dirname(fileName):
489             self.config.set_key('io_dir', os.path.dirname(fileName), True)
490         return fileName
491
492     def getSaveFileName(self, title, filename, filter = None):
493         directory = self.config.get('io_dir', os.path.expanduser('~'))
494         path = os.path.join( directory, filename )
495         fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
496         if fileName and directory != os.path.dirname(fileName):
497             self.config.set_key('io_dir', os.path.dirname(fileName), True)
498         return fileName
499
500
501
502     def close(self):
503         QMainWindow.close(self)
504         self.run_hook('close_main_window')
505
506     def connect_slots(self, sender):
507         self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
508         self.previous_payto_e=''
509
510     def timer_actions(self):
511         if self.need_update.is_set():
512             self.update_wallet()
513             self.need_update.clear()
514         self.run_hook('timer_actions')
515     
516     def format_amount(self, x, is_diff=False):
517         return format_satoshis(x, is_diff, self.wallet.num_zeros, self.decimal_point)
518
519     def read_amount(self, x):
520         if x in['.', '']: return None
521         p = pow(10, self.decimal_point)
522         return int( p * Decimal(x) )
523
524     def base_unit(self):
525         assert self.decimal_point in [5,8]
526         return "BTC" if self.decimal_point == 8 else "mBTC"
527
528     def update_status(self):
529         if self.wallet.interface and self.wallet.interface.is_connected:
530             if not self.wallet.up_to_date:
531                 text = _("Synchronizing...")
532                 icon = QIcon(":icons/status_waiting.png")
533             else:
534                 c, u = self.wallet.get_account_balance(self.current_account)
535                 text =  _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
536                 if u: text +=  " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
537                 text += self.create_quote_text(Decimal(c+u)/100000000)
538                 icon = QIcon(":icons/status_connected.png")
539         else:
540             text = _("Not connected")
541             icon = QIcon(":icons/status_disconnected.png")
542
543         self.balance_label.setText(text)
544         self.status_button.setIcon( icon )
545
546     def update_wallet(self):
547         self.update_status()
548         if self.wallet.up_to_date or not self.wallet.interface.is_connected:
549             self.update_history_tab()
550             self.update_receive_tab()
551             self.update_contacts_tab()
552             self.update_completions()
553
554
555     def create_quote_text(self, btc_balance):
556         quote_currency = self.config.get("currency", "None")
557         quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
558         if quote_balance is None:
559             quote_text = ""
560         else:
561             quote_text = "  (%.2f %s)" % (quote_balance, quote_currency)
562         return quote_text
563         
564     def create_history_tab(self):
565         self.history_list = l = MyTreeWidget(self)
566         l.setColumnCount(5)
567         for i,width in enumerate(self.column_widths['history']):
568             l.setColumnWidth(i, width)
569         l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
570         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
571         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
572
573         l.setContextMenuPolicy(Qt.CustomContextMenu)
574         l.customContextMenuRequested.connect(self.create_history_menu)
575         return l
576
577
578     def create_history_menu(self, position):
579         self.history_list.selectedIndexes() 
580         item = self.history_list.currentItem()
581         if not item: return
582         tx_hash = str(item.data(0, Qt.UserRole).toString())
583         if not tx_hash: return
584         menu = QMenu()
585         #menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
586         menu.addAction(_("Details"), lambda: self.show_tx_details(self.wallet.transactions.get(tx_hash)))
587         menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
588         menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
589
590
591     def show_tx_details(self, tx):
592         dialog = QDialog(self)
593         dialog.setModal(1)
594         dialog.setWindowTitle(_("Transaction Details"))
595         vbox = QVBoxLayout()
596         dialog.setLayout(vbox)
597         dialog.setMinimumSize(600,300)
598
599         tx_hash = tx.hash()
600         if tx_hash in self.wallet.transactions.keys():
601             is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
602             conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
603             if timestamp:
604                 time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
605             else:
606                 time_str = 'pending'
607         else:
608             is_mine = False
609
610         vbox.addWidget(QLabel("Transaction ID:"))
611         e  = QLineEdit(tx_hash)
612         e.setReadOnly(True)
613         vbox.addWidget(e)
614
615         vbox.addWidget(QLabel("Date: %s"%time_str))
616         vbox.addWidget(QLabel("Status: %d confirmations"%conf))
617         if is_mine:
618             if fee is not None: 
619                 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v-fee)))
620                 vbox.addWidget(QLabel("Transaction fee: %s"% self.format_amount(fee)))
621             else:
622                 vbox.addWidget(QLabel("Amount sent: %s"% self.format_amount(v)))
623                 vbox.addWidget(QLabel("Transaction fee: unknown"))
624         else:
625             vbox.addWidget(QLabel("Amount received: %s"% self.format_amount(v)))
626
627         vbox.addWidget( self.generate_transaction_information_widget(tx) )
628
629         ok_button = QPushButton(_("Close"))
630         ok_button.setDefault(True)
631         ok_button.clicked.connect(dialog.accept)
632         
633         hbox = QHBoxLayout()
634         hbox.addStretch(1)
635         hbox.addWidget(ok_button)
636         vbox.addLayout(hbox)
637         dialog.exec_()
638
639     def tx_label_clicked(self, item, column):
640         if column==2 and item.isSelected():
641             self.is_edit=True
642             item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
643             self.history_list.editItem( item, column )
644             item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
645             self.is_edit=False
646
647     def tx_label_changed(self, item, column):
648         if self.is_edit: 
649             return
650         self.is_edit=True
651         tx_hash = str(item.data(0, Qt.UserRole).toString())
652         tx = self.wallet.transactions.get(tx_hash)
653         text = unicode( item.text(2) )
654         self.set_label(tx_hash, text) 
655         if text: 
656             item.setForeground(2, QBrush(QColor('black')))
657         else:
658             text = self.wallet.get_default_label(tx_hash)
659             item.setText(2, text)
660             item.setForeground(2, QBrush(QColor('gray')))
661         self.is_edit=False
662
663
664     def edit_label(self, is_recv):
665         l = self.receive_list if is_recv else self.contacts_list
666         item = l.currentItem()
667         item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
668         l.editItem( item, 1 )
669         item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
670
671
672
673     def address_label_clicked(self, item, column, l, column_addr, column_label):
674         if column == column_label and item.isSelected():
675             is_editable = item.data(0, 32).toBool()
676             if not is_editable:
677                 return
678             addr = unicode( item.text(column_addr) )
679             label = unicode( item.text(column_label) )
680             item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
681             l.editItem( item, column )
682             item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
683
684
685     def address_label_changed(self, item, column, l, column_addr, column_label):
686         if column == column_label:
687             addr = unicode( item.text(column_addr) )
688             text = unicode( item.text(column_label) )
689             is_editable = item.data(0, 32).toBool()
690             if not is_editable:
691                 return
692
693             changed = self.set_label(addr, text)
694             if changed:
695                 self.update_history_tab()
696                 self.update_completions()
697                 
698             self.current_item_changed(item)
699
700         self.run_hook('item_changed', item, column)
701
702
703     def current_item_changed(self, a):
704         self.run_hook('current_item_changed', a)
705
706
707
708     def update_history_tab(self):
709
710         self.history_list.clear()
711         for item in self.wallet.get_tx_history(self.current_account):
712             tx_hash, conf, is_mine, value, fee, balance, timestamp = item
713             if conf > 0:
714                 try:
715                     time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
716                 except:
717                     time_str = "unknown"
718
719             if conf == -1:
720                 time_str = 'unverified'
721                 icon = QIcon(":icons/unconfirmed.png")
722             elif conf == 0:
723                 time_str = 'pending'
724                 icon = QIcon(":icons/unconfirmed.png")
725             elif conf < 6:
726                 icon = QIcon(":icons/clock%d.png"%conf)
727             else:
728                 icon = QIcon(":icons/confirmed.png")
729
730             if value is not None:
731                 v_str = self.format_amount(value, True)
732             else:
733                 v_str = '--'
734
735             balance_str = self.format_amount(balance)
736             
737             if tx_hash:
738                 label, is_default_label = self.wallet.get_label(tx_hash)
739             else:
740                 label = _('Pruned transaction outputs')
741                 is_default_label = False
742
743             item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
744             item.setFont(2, QFont(MONOSPACE_FONT))
745             item.setFont(3, QFont(MONOSPACE_FONT))
746             item.setFont(4, QFont(MONOSPACE_FONT))
747             if value < 0:
748                 item.setForeground(3, QBrush(QColor("#BC1E1E")))
749             if tx_hash:
750                 item.setData(0, Qt.UserRole, tx_hash)
751                 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
752             if is_default_label:
753                 item.setForeground(2, QBrush(QColor('grey')))
754
755             item.setIcon(0, icon)
756             self.history_list.insertTopLevelItem(0,item)
757             
758
759         self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
760
761
762     def create_send_tab(self):
763         w = QWidget()
764
765         grid = QGridLayout()
766         grid.setSpacing(8)
767         grid.setColumnMinimumWidth(3,300)
768         grid.setColumnStretch(5,1)
769
770
771         self.payto_e = QLineEdit()
772         grid.addWidget(QLabel(_('Pay to')), 1, 0)
773         grid.addWidget(self.payto_e, 1, 1, 1, 3)
774             
775         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)
776
777         completer = QCompleter()
778         completer.setCaseSensitivity(False)
779         self.payto_e.setCompleter(completer)
780         completer.setModel(self.completions)
781
782         self.message_e = QLineEdit()
783         grid.addWidget(QLabel(_('Description')), 2, 0)
784         grid.addWidget(self.message_e, 2, 1, 1, 3)
785         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)
786
787         self.amount_e = AmountEdit(self.base_unit)
788         grid.addWidget(QLabel(_('Amount')), 3, 0)
789         grid.addWidget(self.amount_e, 3, 1, 1, 2)
790         grid.addWidget(HelpButton(
791                 _('Amount to be sent.') + '\n\n' \
792                     + _('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.') \
793                     + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 3, 3)
794         
795         self.fee_e = AmountEdit(self.base_unit)
796         grid.addWidget(QLabel(_('Fee')), 4, 0)
797         grid.addWidget(self.fee_e, 4, 1, 1, 2) 
798         grid.addWidget(HelpButton(
799                 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
800                     + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
801                     + _('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)
802         b = ''
803         if self.wallet.seed: 
804             b = EnterButton(_("Send"), self.do_send)
805         else:
806             b = EnterButton(_("Create unsigned transaction"), self.do_send)
807         grid.addWidget(b, 6, 1)
808
809         b = EnterButton(_("Clear"),self.do_clear)
810         grid.addWidget(b, 6, 2)
811
812         self.payto_sig = QLabel('')
813         grid.addWidget(self.payto_sig, 7, 0, 1, 4)
814
815         QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
816         QShortcut(QKeySequence("Down"), w, w.focusNextChild)
817         w.setLayout(grid) 
818
819         w2 = QWidget()
820         vbox = QVBoxLayout()
821         vbox.addWidget(w)
822         vbox.addStretch(1)
823         w2.setLayout(vbox)
824
825         def entry_changed( is_fee ):
826             self.funds_error = False
827
828             if self.amount_e.is_shortcut:
829                 self.amount_e.is_shortcut = False
830                 c, u = self.wallet.get_account_balance(self.current_account)
831                 inputs, total, fee = self.wallet.choose_tx_inputs( c + u, 0, self.current_account)
832                 fee = self.wallet.estimated_fee(inputs)
833                 amount = c + u - fee
834                 self.amount_e.setText( self.format_amount(amount) )
835                 self.fee_e.setText( self.format_amount( fee ) )
836                 return
837                 
838             amount = self.read_amount(str(self.amount_e.text()))
839             fee = self.read_amount(str(self.fee_e.text()))
840
841             if not is_fee: fee = None
842             if amount is None:
843                 return
844             inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee, self.current_account )
845             if not is_fee:
846                 self.fee_e.setText( self.format_amount( fee ) )
847             if inputs:
848                 palette = QPalette()
849                 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
850                 text = ""
851             else:
852                 palette = QPalette()
853                 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
854                 self.funds_error = True
855                 text = _( "Not enough funds" )
856                 c, u = self.wallet.get_frozen_balance()
857                 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
858
859             self.statusBar().showMessage(text)
860             self.amount_e.setPalette(palette)
861             self.fee_e.setPalette(palette)
862
863         self.amount_e.textChanged.connect(lambda: entry_changed(False) )
864         self.fee_e.textChanged.connect(lambda: entry_changed(True) )
865
866         self.run_hook('create_send_tab', grid)
867         return w2
868
869
870     def update_completions(self):
871         l = []
872         for addr,label in self.wallet.labels.items():
873             if addr in self.wallet.addressbook:
874                 l.append( label + '  <' + addr + '>')
875
876         self.run_hook('update_completions', l)
877         self.completions.setStringList(l)
878
879
880     def protected(func):
881         return lambda s, *args: s.do_protect(func, args)
882
883
884     @protected
885     def do_send(self, password):
886
887         label = unicode( self.message_e.text() )
888         r = unicode( self.payto_e.text() )
889         r = r.strip()
890
891         # label or alias, with address in brackets
892         m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
893         to_address = m.group(2) if m else r
894
895         if not is_valid(to_address):
896             QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
897             return
898
899         try:
900             amount = self.read_amount(unicode( self.amount_e.text()))
901         except:
902             QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
903             return
904         try:
905             fee = self.read_amount(unicode( self.fee_e.text()))
906         except:
907             QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
908             return
909
910         try:
911             tx = self.wallet.mktx( [(to_address, amount)], password, fee, account=self.current_account)
912         except BaseException, e:
913             self.show_message(str(e))
914             return
915
916         if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
917             QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
918             return
919
920         self.run_hook('send_tx', tx)
921
922         if label: 
923             self.set_label(tx.hash(), label)
924
925         if tx.is_complete:
926             h = self.wallet.send_tx(tx)
927             waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
928             status, msg = self.wallet.receive_tx( h )
929             if status:
930                 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
931                 self.do_clear()
932                 self.update_contacts_tab()
933             else:
934                 QMessageBox.warning(self, _('Error'), msg, _('OK'))
935         else:
936             filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
937             try:
938                 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
939                 with open(fileName,'w') as f:
940                     f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
941                 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
942             except:
943                 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
944
945
946
947
948     def set_url(self, url):
949         address, amount, label, message, signature, identity, url = util.parse_url(url)
950         if self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
951
952         if label and self.wallet.labels.get(address) != label:
953             if self.question('Give label "%s" to address %s ?'%(label,address)):
954                 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
955                     self.wallet.addressbook.append(address)
956                 self.set_label(address, label)
957
958         self.run_hook('set_url', url, self.show_message, self.question)
959
960         self.tabs.setCurrentIndex(1)
961         label = self.wallet.labels.get(address)
962         m_addr = label + '  <'+ address +'>' if label else address
963         self.payto_e.setText(m_addr)
964
965         self.message_e.setText(message)
966         self.amount_e.setText(amount)
967         if identity:
968             self.set_frozen(self.payto_e,True)
969             self.set_frozen(self.amount_e,True)
970             self.set_frozen(self.message_e,True)
971             self.payto_sig.setText( '      The bitcoin URI was signed by ' + identity )
972         else:
973             self.payto_sig.setVisible(False)
974
975     def do_clear(self):
976         self.payto_sig.setVisible(False)
977         for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
978             e.setText('')
979             self.set_frozen(e,False)
980         self.update_status()
981
982     def set_frozen(self,entry,frozen):
983         if frozen:
984             entry.setReadOnly(True)
985             entry.setFrame(False)
986             palette = QPalette()
987             palette.setColor(entry.backgroundRole(), QColor('lightgray'))
988             entry.setPalette(palette)
989         else:
990             entry.setReadOnly(False)
991             entry.setFrame(True)
992             palette = QPalette()
993             palette.setColor(entry.backgroundRole(), QColor('white'))
994             entry.setPalette(palette)
995
996
997     def toggle_freeze(self,addr):
998         if not addr: return
999         if addr in self.wallet.frozen_addresses:
1000             self.wallet.unfreeze(addr)
1001         else:
1002             self.wallet.freeze(addr)
1003         self.update_receive_tab()
1004
1005     def toggle_priority(self,addr):
1006         if not addr: return
1007         if addr in self.wallet.prioritized_addresses:
1008             self.wallet.unprioritize(addr)
1009         else:
1010             self.wallet.prioritize(addr)
1011         self.update_receive_tab()
1012
1013
1014     def create_list_tab(self, headers):
1015         "generic tab creation method"
1016         l = MyTreeWidget(self)
1017         l.setColumnCount( len(headers) )
1018         l.setHeaderLabels( headers )
1019
1020         w = QWidget()
1021         vbox = QVBoxLayout()
1022         w.setLayout(vbox)
1023
1024         vbox.setMargin(0)
1025         vbox.setSpacing(0)
1026         vbox.addWidget(l)
1027         buttons = QWidget()
1028         vbox.addWidget(buttons)
1029
1030         hbox = QHBoxLayout()
1031         hbox.setMargin(0)
1032         hbox.setSpacing(0)
1033         buttons.setLayout(hbox)
1034
1035         return l,w,hbox
1036
1037
1038     def create_receive_tab(self):
1039         l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1040         l.setContextMenuPolicy(Qt.CustomContextMenu)
1041         l.customContextMenuRequested.connect(self.create_receive_menu)
1042         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1043         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1044         self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1045         self.receive_list = l
1046         self.receive_buttons_hbox = hbox
1047         hbox.addStretch(1)
1048         return w
1049
1050
1051     def receive_tab_set_mode(self, i):
1052         self.save_column_widths()
1053         self.expert_mode = (i == 1)
1054         self.config.set_key('classic_expert_mode', self.expert_mode, True)
1055         self.update_receive_tab()
1056
1057
1058     def save_column_widths(self):
1059         if not self.expert_mode:
1060             widths = [ self.receive_list.columnWidth(0) ]
1061         else:
1062             widths = []
1063             for i in range(self.receive_list.columnCount() -1):
1064                 widths.append(self.receive_list.columnWidth(i))
1065         self.column_widths["receive"][self.expert_mode] = widths
1066         
1067         self.column_widths["history"] = []
1068         for i in range(self.history_list.columnCount() - 1):
1069             self.column_widths["history"].append(self.history_list.columnWidth(i))
1070
1071         self.column_widths["contacts"] = []
1072         for i in range(self.contacts_list.columnCount() - 1):
1073             self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1074
1075         self.config.set_key("column_widths", self.column_widths, True)
1076
1077
1078     def create_contacts_tab(self):
1079         l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1080         l.setContextMenuPolicy(Qt.CustomContextMenu)
1081         l.customContextMenuRequested.connect(self.create_contact_menu)
1082         for i,width in enumerate(self.column_widths['contacts']):
1083             l.setColumnWidth(i, width)
1084
1085         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1086         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1087         self.contacts_list = l
1088         self.contacts_buttons_hbox = hbox
1089         hbox.addWidget(EnterButton(_("New"), self.new_contact_dialog))
1090         hbox.addStretch(1)
1091         return w
1092
1093
1094     def delete_imported_key(self, addr):
1095         if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1096             self.wallet.delete_imported_key(addr)
1097             self.update_receive_tab()
1098             self.update_history_tab()
1099
1100
1101     def create_receive_menu(self, position):
1102         # fixme: this function apparently has a side effect.
1103         # if it is not called the menu pops up several times
1104         #self.receive_list.selectedIndexes() 
1105
1106         item = self.receive_list.itemAt(position)
1107         if not item: return
1108         addr = unicode(item.text(0))
1109         if not is_valid(addr): 
1110             item.setExpanded(not item.isExpanded())
1111             return 
1112         menu = QMenu()
1113         menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1114         menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1115         menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1116         menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1117         menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1118         if addr in self.wallet.imported_keys:
1119             menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1120
1121         if self.expert_mode:
1122             t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1123             menu.addAction(t, lambda: self.toggle_freeze(addr))
1124             t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1125             menu.addAction(t, lambda: self.toggle_priority(addr))
1126             
1127         self.run_hook('receive_menu', menu)
1128         menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1129
1130
1131     def payto(self, addr):
1132         if not addr: return
1133         label = self.wallet.labels.get(addr)
1134         m_addr = label + '  <' + addr + '>' if label else addr
1135         self.tabs.setCurrentIndex(1)
1136         self.payto_e.setText(m_addr)
1137         self.amount_e.setFocus()
1138
1139
1140     def delete_contact(self, x):
1141         if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1142             self.wallet.delete_contact(x)
1143             self.set_label(x, None)
1144             self.update_history_tab()
1145             self.update_contacts_tab()
1146             self.update_completions()
1147
1148
1149     def create_contact_menu(self, position):
1150         item = self.contacts_list.itemAt(position)
1151         if not item: return
1152         addr = unicode(item.text(0))
1153         label = unicode(item.text(1))
1154         is_editable = item.data(0,32).toBool()
1155         payto_addr = item.data(0,33).toString()
1156         menu = QMenu()
1157         menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1158         menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1159         menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1160         if is_editable:
1161             menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1162             menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1163
1164         self.run_hook('create_contact_menu', menu, item)
1165         menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1166
1167
1168     def update_receive_item(self, item):
1169         item.setFont(0, QFont(MONOSPACE_FONT))
1170         address = str(item.data(0,0).toString())
1171         label = self.wallet.labels.get(address,'')
1172         item.setData(1,0,label)
1173         item.setData(0,32, True) # is editable
1174
1175         self.run_hook('update_receive_item', address, item)
1176                 
1177         c, u = self.wallet.get_addr_balance(address)
1178         balance = self.format_amount(c + u)
1179         item.setData(2,0,balance)
1180
1181         if self.expert_mode:
1182             if address in self.wallet.frozen_addresses: 
1183                 item.setBackgroundColor(0, QColor('lightblue'))
1184             elif address in self.wallet.prioritized_addresses: 
1185                 item.setBackgroundColor(0, QColor('lightgreen'))
1186         
1187
1188     def update_receive_tab(self):
1189         l = self.receive_list
1190         
1191         l.clear()
1192         l.setColumnHidden(2, not self.expert_mode)
1193         l.setColumnHidden(3, not self.expert_mode)
1194         for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1195             l.setColumnWidth(i, width)
1196
1197         if self.current_account is None:
1198             account_items = self.wallet.accounts.items()
1199         elif self.current_account != -1:
1200             account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1201         else:
1202             account_items = []
1203
1204         for k, account in account_items:
1205             name = account.get('name',str(k))
1206             c,u = self.wallet.get_account_balance(k)
1207             account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1208             l.addTopLevelItem(account_item)
1209             account_item.setExpanded(True)
1210             
1211             for is_change in ([0,1] if self.expert_mode else [0]):
1212                 if self.expert_mode:
1213                     name = "Receiving" if not is_change else "Change"
1214                     seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1215                     account_item.addChild(seq_item)
1216                     if not is_change: seq_item.setExpanded(True)
1217                 else:
1218                     seq_item = account_item
1219                 is_red = False
1220                 gap = 0
1221
1222                 for address in account[is_change]:
1223                     h = self.wallet.history.get(address,[])
1224             
1225                     if h == []:
1226                         gap += 1
1227                         if gap > self.wallet.gap_limit:
1228                             is_red = True
1229                     else:
1230                         gap = 0
1231
1232                     num_tx = '*' if h == ['*'] else "%d"%len(h)
1233                     item = QTreeWidgetItem( [ address, '', '', num_tx] )
1234                     self.update_receive_item(item)
1235                     if is_red:
1236                         item.setBackgroundColor(1, QColor('red'))
1237                     seq_item.addChild(item)
1238
1239
1240         if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1241             c,u = self.wallet.get_imported_balance()
1242             account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1243             l.addTopLevelItem(account_item)
1244             account_item.setExpanded(True)
1245             for address in self.wallet.imported_keys.keys():
1246                 item = QTreeWidgetItem( [ address, '', '', ''] )
1247                 self.update_receive_item(item)
1248                 account_item.addChild(item)
1249                 
1250
1251         # we use column 1 because column 0 may be hidden
1252         l.setCurrentItem(l.topLevelItem(0),1)
1253
1254
1255     def update_contacts_tab(self):
1256
1257         l = self.contacts_list
1258         l.clear()
1259
1260         for address in self.wallet.addressbook:
1261             label = self.wallet.labels.get(address,'')
1262             n = self.wallet.get_num_tx(address)
1263             item = QTreeWidgetItem( [ address, label, "%d"%n] )
1264             item.setFont(0, QFont(MONOSPACE_FONT))
1265             # 32 = label can be edited (bool)
1266             item.setData(0,32, True)
1267             # 33 = payto string
1268             item.setData(0,33, address)
1269             l.addTopLevelItem(item)
1270
1271         self.run_hook('update_contacts_tab', l)
1272         l.setCurrentItem(l.topLevelItem(0))
1273
1274
1275
1276     def create_console_tab(self):
1277         from qt_console import Console
1278         self.console = console = Console()
1279         self.console.history = self.config.get("console-history",[])
1280         self.console.history_index = len(self.console.history)
1281
1282         console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
1283         console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1284
1285         c = commands.Commands(self.wallet, self.wallet.interface, lambda: self.console.set_json(True))
1286         methods = {}
1287         def mkfunc(f, method):
1288             return lambda *args: apply( f, (method, args, self.password_dialog ))
1289         for m in dir(c):
1290             if m[0]=='_' or m=='wallet' or m == 'interface': continue
1291             methods[m] = mkfunc(c._run, m)
1292             
1293         console.updateNamespace(methods)
1294         return console
1295
1296     def change_account(self,s):
1297         if s == _("All accounts"):
1298             self.current_account = None
1299         else:
1300             accounts = self.wallet.get_accounts()
1301             for k, v in accounts.items():
1302                 if v == s:
1303                     self.current_account = k
1304         self.update_history_tab()
1305         self.update_status()
1306         self.update_receive_tab()
1307
1308     def create_status_bar(self):
1309
1310         sb = QStatusBar()
1311         sb.setFixedHeight(35)
1312         qtVersion = qVersion()
1313
1314         self.balance_label = QLabel("")
1315         sb.addWidget(self.balance_label)
1316
1317         update_notification = UpdateLabel(self.config)
1318         if(update_notification.new_version):
1319             sb.addPermanentWidget(update_notification)
1320
1321         accounts = self.wallet.get_accounts()
1322         if len(accounts) > 1:
1323             from_combo = QComboBox()
1324             from_combo.addItems([_("All accounts")] + accounts.values())
1325             from_combo.setCurrentIndex(0)
1326             self.connect(from_combo,SIGNAL("activated(QString)"),self.change_account) 
1327             sb.addPermanentWidget(from_combo)
1328
1329         if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1330             sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1331         if self.wallet.seed:
1332             self.lock_icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1333             self.password_button = StatusBarButton( self.lock_icon, _("Password"), lambda: self.change_password_dialog(self.wallet, self) )
1334             sb.addPermanentWidget( self.password_button )
1335         sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1336         if self.wallet.seed:
1337             sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
1338         self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog ) 
1339         sb.addPermanentWidget( self.status_button )
1340
1341         self.run_hook('create_status_bar', (sb,))
1342
1343         self.setStatusBar(sb)
1344         
1345     def go_lite(self):
1346         import gui_lite
1347         self.config.set_key('gui', 'lite', True)
1348         self.hide()
1349         if self.lite:
1350             self.lite.mini.show()
1351         else:
1352             self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
1353             self.lite.main(None)
1354
1355     def new_contact_dialog(self):
1356         text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1357         address = unicode(text)
1358         if ok:
1359             if is_valid(address):
1360                 self.wallet.add_contact(address)
1361                 self.update_contacts_tab()
1362                 self.update_history_tab()
1363                 self.update_completions()
1364             else:
1365                 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1366
1367     def show_master_public_key(self):
1368         dialog = QDialog(self)
1369         dialog.setModal(1)
1370         dialog.setWindowTitle(_("Master Public Key"))
1371
1372         main_text = QTextEdit()
1373         main_text.setText(self.wallet.get_master_public_key())
1374         main_text.setReadOnly(True)
1375         main_text.setMaximumHeight(170)
1376         qrw = QRCodeWidget(self.wallet.get_master_public_key())
1377
1378         ok_button = QPushButton(_("OK"))
1379         ok_button.setDefault(True)
1380         ok_button.clicked.connect(dialog.accept)
1381
1382         main_layout = QGridLayout()
1383         main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1384
1385         main_layout.addWidget(main_text, 1, 0)
1386         main_layout.addWidget(qrw, 1, 1 )
1387
1388         vbox = QVBoxLayout()
1389         vbox.addLayout(main_layout)
1390         hbox = QHBoxLayout()
1391         hbox.addStretch(1)
1392         hbox.addWidget(ok_button)
1393         vbox.addLayout(hbox)
1394
1395         dialog.setLayout(vbox)
1396         dialog.exec_()
1397         
1398
1399     @protected
1400     def show_seed_dialog(self, password):
1401         if not self.wallet.seed:
1402             QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
1403             return
1404         try:
1405             seed = self.wallet.decode_seed(password)
1406         except:
1407             QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1408             return
1409         self.show_seed(seed, self.wallet.imported_keys, self)
1410
1411
1412     @classmethod
1413     def show_seed(self, seed, imported_keys, parent=None):
1414         dialog = QDialog(parent)
1415         dialog.setModal(1)
1416         dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
1417
1418         brainwallet = ' '.join(mnemonic.mn_encode(seed))
1419
1420         label1 = QLabel(_("Your wallet generation seed is")+ ":")
1421
1422         seed_text = QTextEdit(brainwallet)
1423         seed_text.setReadOnly(True)
1424         seed_text.setMaximumHeight(130)
1425         
1426         msg2 =  _("Please write down or memorize these 12 words (order is important).") + " " \
1427               + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
1428               + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
1429               + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
1430         if imported_keys:
1431             msg2 += "<b>"+_("WARNING")+":</b> " + _("Your wallet contains imported keys. These keys cannot be recovered from seed.") + "</b><p>"
1432         label2 = QLabel(msg2)
1433         label2.setWordWrap(True)
1434
1435         logo = QLabel()
1436         logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
1437         logo.setMaximumWidth(60)
1438
1439         qrw = QRCodeWidget(seed)
1440
1441         ok_button = QPushButton(_("OK"))
1442         ok_button.setDefault(True)
1443         ok_button.clicked.connect(dialog.accept)
1444
1445         grid = QGridLayout()
1446         #main_layout.addWidget(logo, 0, 0)
1447
1448         grid.addWidget(logo, 0, 0)
1449         grid.addWidget(label1, 0, 1)
1450
1451         grid.addWidget(seed_text, 1, 0, 1, 2)
1452
1453         grid.addWidget(qrw, 0, 2, 2, 1)
1454
1455         vbox = QVBoxLayout()
1456         vbox.addLayout(grid)
1457         vbox.addWidget(label2)
1458
1459         hbox = QHBoxLayout()
1460         hbox.addStretch(1)
1461         hbox.addWidget(ok_button)
1462         vbox.addLayout(hbox)
1463
1464         dialog.setLayout(vbox)
1465         dialog.exec_()
1466
1467     def show_qrcode(self, data, title = "QR code"):
1468         if not data: return
1469         d = QDialog(self)
1470         d.setModal(1)
1471         d.setWindowTitle(title)
1472         d.setMinimumSize(270, 300)
1473         vbox = QVBoxLayout()
1474         qrw = QRCodeWidget(data)
1475         vbox.addWidget(qrw, 1)
1476         vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1477         hbox = QHBoxLayout()
1478         hbox.addStretch(1)
1479
1480         def print_qr(self):
1481             filename = "qrcode.bmp"
1482             bmp.save_qrcode(qrw.qr, filename)
1483             QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1484
1485         b = QPushButton(_("Save"))
1486         hbox.addWidget(b)
1487         b.clicked.connect(print_qr)
1488
1489         b = QPushButton(_("Close"))
1490         hbox.addWidget(b)
1491         b.clicked.connect(d.accept)
1492         b.setDefault(True)
1493
1494         vbox.addLayout(hbox)
1495         d.setLayout(vbox)
1496         d.exec_()
1497
1498
1499     def do_protect(self, func, args):
1500         if self.wallet.use_encryption:
1501             password = self.password_dialog()
1502             if not password:
1503                 return
1504         else:
1505             password = None
1506             
1507         if args != (False,):
1508             args = (self,) + args + (password,)
1509         else:
1510             args = (self,password)
1511         apply( func, args)
1512
1513
1514     @protected
1515     def show_private_key(self, address, password):
1516         if not address: return
1517         try:
1518             pk = self.wallet.get_private_key(address, password)
1519         except BaseException, e:
1520             self.show_message(str(e))
1521             return
1522         QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
1523
1524
1525     @protected
1526     def do_sign(self, address, message, signature, password):
1527         try:
1528             sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
1529             signature.setText(sig)
1530         except BaseException, e:
1531             self.show_message(str(e))
1532
1533     def sign_message(self, address):
1534         if not address: return
1535         d = QDialog(self)
1536         d.setModal(1)
1537         d.setWindowTitle(_('Sign Message'))
1538         d.setMinimumSize(410, 290)
1539
1540         tab_widget = QTabWidget()
1541         tab = QWidget()
1542         layout = QGridLayout(tab)
1543
1544         sign_address = QLineEdit()
1545
1546         sign_address.setText(address)
1547         layout.addWidget(QLabel(_('Address')), 1, 0)
1548         layout.addWidget(sign_address, 1, 1)
1549
1550         sign_message = QTextEdit()
1551         layout.addWidget(QLabel(_('Message')), 2, 0)
1552         layout.addWidget(sign_message, 2, 1)
1553         layout.setRowStretch(2,3)
1554
1555         sign_signature = QTextEdit()
1556         layout.addWidget(QLabel(_('Signature')), 3, 0)
1557         layout.addWidget(sign_signature, 3, 1)
1558         layout.setRowStretch(3,1)
1559
1560
1561         hbox = QHBoxLayout()
1562         b = QPushButton(_("Sign"))
1563         hbox.addWidget(b)
1564         b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1565         b = QPushButton(_("Close"))
1566         b.clicked.connect(d.accept)
1567         hbox.addWidget(b)
1568         layout.addLayout(hbox, 4, 1)
1569         tab_widget.addTab(tab, _("Sign"))
1570
1571
1572         tab = QWidget()
1573         layout = QGridLayout(tab)
1574
1575         verify_address = QLineEdit()
1576         layout.addWidget(QLabel(_('Address')), 1, 0)
1577         layout.addWidget(verify_address, 1, 1)
1578
1579         verify_message = QTextEdit()
1580         layout.addWidget(QLabel(_('Message')), 2, 0)
1581         layout.addWidget(verify_message, 2, 1)
1582         layout.setRowStretch(2,3)
1583
1584         verify_signature = QTextEdit()
1585         layout.addWidget(QLabel(_('Signature')), 3, 0)
1586         layout.addWidget(verify_signature, 3, 1)
1587         layout.setRowStretch(3,1)
1588
1589         def do_verify():
1590             if self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText())):
1591                 self.show_message(_("Signature verified"))
1592             else:
1593                 self.show_message(_("Error: wrong signature"))
1594
1595         hbox = QHBoxLayout()
1596         b = QPushButton(_("Verify"))
1597         b.clicked.connect(do_verify)
1598         hbox.addWidget(b)
1599         b = QPushButton(_("Close"))
1600         b.clicked.connect(d.accept)
1601         hbox.addWidget(b)
1602         layout.addLayout(hbox, 4, 1)
1603         tab_widget.addTab(tab, _("Verify"))
1604
1605         vbox = QVBoxLayout()
1606         vbox.addWidget(tab_widget)
1607         d.setLayout(vbox)
1608         d.exec_()
1609
1610         
1611
1612
1613     def question(self, msg):
1614         return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1615
1616     def show_message(self, msg):
1617         QMessageBox.information(self, _('Message'), msg, _('OK'))
1618
1619     def password_dialog(self ):
1620         d = QDialog(self)
1621         d.setModal(1)
1622
1623         pw = QLineEdit()
1624         pw.setEchoMode(2)
1625
1626         vbox = QVBoxLayout()
1627         msg = _('Please enter your password')
1628         vbox.addWidget(QLabel(msg))
1629
1630         grid = QGridLayout()
1631         grid.setSpacing(8)
1632         grid.addWidget(QLabel(_('Password')), 1, 0)
1633         grid.addWidget(pw, 1, 1)
1634         vbox.addLayout(grid)
1635
1636         vbox.addLayout(ok_cancel_buttons(d))
1637         d.setLayout(vbox)
1638
1639         self.run_hook('password_dialog', pw, grid, 1)
1640         if not d.exec_(): return
1641         return unicode(pw.text())
1642
1643
1644
1645
1646
1647     @staticmethod
1648     def change_password_dialog( wallet, parent=None ):
1649
1650         if not wallet.seed:
1651             QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
1652             return
1653
1654         d = QDialog(parent)
1655         d.setModal(1)
1656
1657         pw = QLineEdit()
1658         pw.setEchoMode(2)
1659         new_pw = QLineEdit()
1660         new_pw.setEchoMode(2)
1661         conf_pw = QLineEdit()
1662         conf_pw.setEchoMode(2)
1663
1664         vbox = QVBoxLayout()
1665         if parent:
1666             msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
1667                    +_('To disable wallet encryption, enter an empty new password.')) \
1668                    if wallet.use_encryption else _('Your wallet keys are not encrypted')
1669         else:
1670             msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
1671                   +_("Leave these fields empty if you want to disable encryption.")
1672         vbox.addWidget(QLabel(msg))
1673
1674         grid = QGridLayout()
1675         grid.setSpacing(8)
1676
1677         if wallet.use_encryption:
1678             grid.addWidget(QLabel(_('Password')), 1, 0)
1679             grid.addWidget(pw, 1, 1)
1680
1681         grid.addWidget(QLabel(_('New Password')), 2, 0)
1682         grid.addWidget(new_pw, 2, 1)
1683
1684         grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
1685         grid.addWidget(conf_pw, 3, 1)
1686         vbox.addLayout(grid)
1687
1688         vbox.addLayout(ok_cancel_buttons(d))
1689         d.setLayout(vbox) 
1690
1691         if not d.exec_(): return
1692
1693         password = unicode(pw.text()) if wallet.use_encryption else None
1694         new_password = unicode(new_pw.text())
1695         new_password2 = unicode(conf_pw.text())
1696
1697         try:
1698             seed = wallet.decode_seed(password)
1699         except:
1700             QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
1701             return
1702
1703         if new_password != new_password2:
1704             QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
1705             return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
1706
1707         try:
1708             wallet.update_password(seed, password, new_password)
1709         except:
1710             QMessageBox.warning(parent, _('Error'), _('Failed to update password'), _('OK'))
1711             return
1712
1713         QMessageBox.information(parent, _('Success'), _('Password was updated successfully'), _('OK'))
1714
1715         if parent: 
1716             icon = QIcon(":icons/lock.png") if wallet.use_encryption else QIcon(":icons/unlock.png")
1717             parent.password_button.setIcon( icon )
1718
1719
1720
1721     def generate_transaction_information_widget(self, tx):
1722         tabs = QTabWidget(self)
1723
1724         tab1 = QWidget()
1725         grid_ui = QGridLayout(tab1)
1726         grid_ui.setColumnStretch(0,1)
1727         tabs.addTab(tab1, _('Outputs') )
1728
1729         tree_widget = MyTreeWidget(self)
1730         tree_widget.setColumnCount(2)
1731         tree_widget.setHeaderLabels( [_('Address'), _('Amount')] )
1732         tree_widget.setColumnWidth(0, 300)
1733         tree_widget.setColumnWidth(1, 50)
1734
1735         for address, value in tx.outputs:
1736             item = QTreeWidgetItem( [address, "%s" % ( self.format_amount(value))] )
1737             tree_widget.addTopLevelItem(item)
1738
1739         tree_widget.setMaximumHeight(100)
1740
1741         grid_ui.addWidget(tree_widget)
1742
1743         tab2 = QWidget()
1744         grid_ui = QGridLayout(tab2)
1745         grid_ui.setColumnStretch(0,1)
1746         tabs.addTab(tab2, _('Inputs') )
1747         
1748         tree_widget = MyTreeWidget(self)
1749         tree_widget.setColumnCount(2)
1750         tree_widget.setHeaderLabels( [ _('Address'), _('Previous output')] )
1751
1752         for input_line in tx.inputs:
1753             item = QTreeWidgetItem( [ str(input_line["address"]), str(input_line["prevout_hash"])] )
1754             tree_widget.addTopLevelItem(item)
1755
1756         tree_widget.setMaximumHeight(100)
1757
1758         grid_ui.addWidget(tree_widget)
1759         return tabs
1760
1761
1762     def tx_dict_from_text(self, txt):
1763         try:
1764             tx_dict = json.loads(str(txt))
1765             assert "hex" in tx_dict.keys()
1766             assert "complete" in tx_dict.keys()
1767             if not tx_dict["complete"]:
1768                 assert "input_info" in tx_dict.keys()
1769         except:
1770             QMessageBox.critical(None, "Unable to parse transaction", _("Electrum was unable to parse your transaction"))
1771             return None
1772         return tx_dict
1773
1774
1775     def read_tx_from_file(self):
1776         fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1777         if not fileName:
1778             return
1779         try:
1780             with open(fileName, "r") as f:
1781                 file_content = f.read()
1782         except (ValueError, IOError, os.error), reason:
1783             QMessageBox.critical(None,"Unable to read file or no transaction found", _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1784
1785         return self.tx_dict_from_text(file_content)
1786
1787
1788     @protected
1789     def sign_raw_transaction(self, tx, input_info, dialog ="", password = ""):
1790         try:
1791             self.wallet.signrawtransaction(tx, input_info, [], password)
1792             
1793             fileName = self.getSaveFileName(_("Select where to save your signed transaction"), 'signed_%s.txn' % (tx.hash()[0:8]), "*.txn")
1794             if fileName:
1795                 with open(fileName, "w+") as f:
1796                     f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
1797                 self.show_message(_("Transaction saved successfully"))
1798                 if dialog:
1799                     dialog.done(0)
1800         except BaseException, e:
1801             self.show_message(str(e))
1802     
1803
1804     def send_raw_transaction(self, raw_tx, dialog = ""):
1805         result, result_message = self.wallet.sendtx( raw_tx )
1806         if result:
1807             self.show_message("Transaction successfully sent: %s" % (result_message))
1808             if dialog:
1809                 dialog.done(0)
1810         else:
1811             self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
1812
1813     def do_process_from_text(self):
1814         text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1815         if not text:
1816             return
1817         tx_dict = self.tx_dict_from_text(text)
1818         if tx_dict:
1819             self.create_process_transaction_window(tx_dict)
1820
1821     def do_process_from_file(self):
1822         tx_dict = self.read_tx_from_file()
1823         if tx_dict: 
1824             self.create_process_transaction_window(tx_dict)
1825
1826     def create_process_transaction_window(self, tx_dict):
1827         tx = Transaction(tx_dict["hex"])
1828             
1829         dialog = QDialog(self)
1830         dialog.setMinimumWidth(500)
1831         dialog.setWindowTitle(_('Process raw transaction'))
1832         dialog.setModal(1)
1833
1834         l = QGridLayout()
1835         dialog.setLayout(l)
1836
1837         l.addWidget(QLabel(_("Transaction status:")), 3,0)
1838         l.addWidget(QLabel(_("Actions")), 4,0)
1839
1840         if tx_dict["complete"] == False:
1841             l.addWidget(QLabel(_("Unsigned")), 3,1)
1842             if self.wallet.seed :
1843                 b = QPushButton("Sign transaction")
1844                 input_info = json.loads(tx_dict["input_info"])
1845                 b.clicked.connect(lambda: self.sign_raw_transaction(tx, input_info, dialog))
1846                 l.addWidget(b, 4, 1)
1847             else:
1848                 l.addWidget(QLabel(_("Wallet is de-seeded, can't sign.")), 4,1)
1849         else:
1850             l.addWidget(QLabel(_("Signed")), 3,1)
1851             b = QPushButton("Broadcast transaction")
1852             b.clicked.connect(lambda: self.send_raw_transaction(tx, dialog))
1853             l.addWidget(b,4,1)
1854
1855         l.addWidget( self.generate_transaction_information_widget(tx), 0,0,2,3)
1856         cancelButton = QPushButton(_("Cancel"))
1857         cancelButton.clicked.connect(lambda: dialog.done(0))
1858         l.addWidget(cancelButton, 4,2)
1859
1860         dialog.exec_()
1861
1862
1863     @protected
1864     def do_export_privkeys(self, password):
1865         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.")))
1866
1867         try:
1868             select_export = _('Select file to export your private keys to')
1869             fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1870             if fileName:
1871                 with open(fileName, "w+") as csvfile:
1872                     transaction = csv.writer(csvfile)
1873                     transaction.writerow(["address", "private_key"])
1874
1875                     
1876                     for addr, pk in self.wallet.get_private_keys(self.wallet.addresses(True), password).items():
1877                         transaction.writerow(["%34s"%addr,pk])
1878
1879                     self.show_message(_("Private keys exported."))
1880
1881         except (IOError, os.error), reason:
1882             export_error_label = _("Electrum was unable to produce a private key-export.")
1883             QMessageBox.critical(None,"Unable to create csv", export_error_label + "\n" + str(reason))
1884
1885         except BaseException, e:
1886           self.show_message(str(e))
1887           return
1888
1889
1890     def do_import_labels(self):
1891         labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1892         if not labelsFile: return
1893         try:
1894             f = open(labelsFile, 'r')
1895             data = f.read()
1896             f.close()
1897             for key, value in json.loads(data).items():
1898                 self.wallet.labels[key] = value
1899             self.wallet.save()
1900             QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1901         except (IOError, os.error), reason:
1902             QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1903             
1904
1905     def do_export_labels(self):
1906         labels = self.wallet.labels
1907         try:
1908             fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1909             if fileName:
1910                 with open(fileName, 'w+') as f:
1911                     json.dump(labels, f)
1912                 QMessageBox.information(None, "Labels exported", _("Your labels where exported to")+" '%s'" % str(fileName))
1913         except (IOError, os.error), reason:
1914             QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
1915
1916
1917     def do_export_history(self):
1918         from gui_lite import csv_transaction
1919         csv_transaction(self.wallet)
1920
1921
1922     @protected
1923     def do_import_privkey(self, password):
1924         if not self.wallet.imported_keys:
1925             r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1926                                          + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1927                                          + _('Are you sure you understand what you are doing?'), 3, 4)
1928             if r == 4: return
1929
1930         text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1931         if not text: return
1932
1933         text = str(text).split()
1934         badkeys = []
1935         addrlist = []
1936         for key in text:
1937             try:
1938                 addr = self.wallet.import_key(key, password)
1939             except BaseException as e:
1940                 badkeys.append(key)
1941                 continue
1942             if not addr: 
1943                 badkeys.append(key)
1944             else:
1945                 addrlist.append(addr)
1946         if addrlist:
1947             QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1948         if badkeys:
1949             QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1950         self.update_receive_tab()
1951         self.update_history_tab()
1952
1953
1954     def settings_dialog(self):
1955         d = QDialog(self)
1956         d.setWindowTitle(_('Electrum Settings'))
1957         d.setModal(1)
1958         vbox = QVBoxLayout()
1959
1960         tabs = QTabWidget(self)
1961         self.settings_tab = tabs
1962         vbox.addWidget(tabs)
1963
1964         tab1 = QWidget()
1965         grid_ui = QGridLayout(tab1)
1966         grid_ui.setColumnStretch(0,1)
1967         tabs.addTab(tab1, _('Display') )
1968
1969         nz_label = QLabel(_('Display zeros'))
1970         grid_ui.addWidget(nz_label, 0, 0)
1971         nz_e = AmountEdit(None,True)
1972         nz_e.setText("%d"% self.wallet.num_zeros)
1973         grid_ui.addWidget(nz_e, 0, 1)
1974         msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1975         grid_ui.addWidget(HelpButton(msg), 0, 2)
1976         if not self.config.is_modifiable('num_zeros'):
1977             for w in [nz_e, nz_label]: w.setEnabled(False)
1978         
1979         lang_label=QLabel(_('Language') + ':')
1980         grid_ui.addWidget(lang_label, 1, 0)
1981         lang_combo = QComboBox()
1982         from i18n import languages
1983         lang_combo.addItems(languages.values())
1984         try:
1985             index = languages.keys().index(self.config.get("language",''))
1986         except:
1987             index = 0
1988         lang_combo.setCurrentIndex(index)
1989         grid_ui.addWidget(lang_combo, 1, 1)
1990         grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1991         if not self.config.is_modifiable('language'):
1992             for w in [lang_combo, lang_label]: w.setEnabled(False)
1993
1994         currencies = self.exchanger.get_currencies()
1995         currencies.insert(0, "None")
1996
1997         cur_label=QLabel(_('Currency') + ':')
1998         grid_ui.addWidget(cur_label , 2, 0)
1999         cur_combo = QComboBox()
2000         cur_combo.addItems(currencies)
2001         try:
2002             index = currencies.index(self.config.get('currency', "None"))
2003         except:
2004             index = 0
2005         cur_combo.setCurrentIndex(index)
2006         grid_ui.addWidget(cur_combo, 2, 1)
2007         grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes.')+' '), 2, 2)
2008         
2009         expert_cb = QCheckBox(_('Expert mode'))
2010         expert_cb.setChecked(self.expert_mode)
2011         grid_ui.addWidget(expert_cb, 3, 0)
2012         hh =  _('In expert mode, your client will:') + '\n'  \
2013             + _(' - Show change addresses in the Receive tab') + '\n'  \
2014             + _(' - Display the balance of each address') + '\n'  \
2015             + _(' - Add freeze/prioritize actions to addresses.') 
2016         grid_ui.addWidget(HelpButton(hh), 3, 2)
2017         grid_ui.setRowStretch(4,1)
2018
2019         # wallet tab
2020         tab2 = QWidget()
2021         grid_wallet = QGridLayout(tab2)
2022         grid_wallet.setColumnStretch(0,1)
2023         tabs.addTab(tab2, _('Wallet') )
2024         
2025         fee_label = QLabel(_('Transaction fee'))
2026         grid_wallet.addWidget(fee_label, 0, 0)
2027         fee_e = AmountEdit(self.base_unit)
2028         fee_e.setText(self.format_amount(self.wallet.fee).strip())
2029         grid_wallet.addWidget(fee_e, 0, 2)
2030         msg = _('Fee per kilobyte of transaction.') + ' ' \
2031             + _('Recommended value') + ': ' + self.format_amount(50000)
2032         grid_wallet.addWidget(HelpButton(msg), 0, 3)
2033         if not self.config.is_modifiable('fee_per_kb'):
2034             for w in [fee_e, fee_label]: w.setEnabled(False)
2035
2036         usechange_cb = QCheckBox(_('Use change addresses'))
2037         usechange_cb.setChecked(self.wallet.use_change)
2038         grid_wallet.addWidget(usechange_cb, 1, 0)
2039         grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
2040         if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2041
2042         gap_label = QLabel(_('Gap limit'))
2043         grid_wallet.addWidget(gap_label, 2, 0)
2044         gap_e = AmountEdit(None,True)
2045         gap_e.setText("%d"% self.wallet.gap_limit)
2046         grid_wallet.addWidget(gap_e, 2, 2)
2047         msg =  _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
2048               + _('You may increase it if you need more receiving addresses.') + '\n\n' \
2049               + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
2050               + _('Given the current status of your address sequence, the minimum gap limit you can use is:')+' ' + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
2051               + _('Warning') + ': ' \
2052               + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
2053               + _('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' 
2054         grid_wallet.addWidget(HelpButton(msg), 2, 3)
2055         if not self.config.is_modifiable('gap_limit'):
2056             for w in [gap_e, gap_label]: w.setEnabled(False)
2057
2058         units = ['BTC', 'mBTC']
2059         unit_label = QLabel(_('Base unit'))
2060         grid_wallet.addWidget(unit_label, 3, 0)
2061         unit_combo = QComboBox()
2062         unit_combo.addItems(units)
2063         unit_combo.setCurrentIndex(units.index(self.base_unit()))
2064         grid_wallet.addWidget(unit_combo, 3, 2)
2065         grid_wallet.addWidget(HelpButton(_('Base unit of your wallet.')\
2066                                              + '\n1BTC=1000mBTC.\n' \
2067                                              + _(' This settings affects the fields in the Send tab')+' '), 3, 3)
2068         grid_wallet.setRowStretch(4,1)
2069
2070
2071         # import/export tab
2072         tab3 = QWidget()
2073         grid_io = QGridLayout(tab3)
2074         grid_io.setColumnStretch(0,1)
2075         tabs.addTab(tab3, _('Import/Export') )
2076         
2077         grid_io.addWidget(QLabel(_('Labels')), 1, 0)
2078         grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
2079         grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
2080         grid_io.addWidget(HelpButton(_('Export your labels as json')), 1, 3)
2081
2082         grid_io.addWidget(QLabel(_('History')), 2, 0)
2083         grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
2084         grid_io.addWidget(HelpButton(_('Export your transaction history as csv')), 2, 3)
2085
2086         grid_io.addWidget(QLabel(_('Private keys')), 3, 0)
2087
2088         grid_io.addWidget(EnterButton(_("Export"), self.do_export_privkeys), 3, 1)
2089         grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
2090         grid_io.addWidget(HelpButton(_('Import private key')), 3, 3)
2091
2092         grid_io.addWidget(QLabel(_('Master Public Key')), 4, 0)
2093         grid_io.addWidget(EnterButton(_("Show"), self.show_master_public_key), 4, 1)
2094         grid_io.addWidget(HelpButton(_('Your Master Public Key can be used to create receiving addresses, but not to sign transactions.') + ' ' \
2095                               + _('If you give it to someone, they will be able to see your transactions, but not to spend your money.') + ' ' \
2096                               + _('If you restore your wallet from it, a watching-only (deseeded) wallet will be created.')), 4, 3)
2097
2098
2099         grid_io.addWidget(QLabel(_("Load transaction")), 5, 0)
2100         grid_io.addWidget(EnterButton(_("From file"), self.do_process_from_file), 5, 1)
2101         grid_io.addWidget(EnterButton(_("From text"), self.do_process_from_text), 5, 2)
2102         grid_io.addWidget(HelpButton(_("This will give you the option to sign or broadcast a transaction based on it's status.")), 5, 3)
2103
2104         grid_io.setRowStretch(6,1)
2105
2106
2107         # plugins
2108         if self.plugins:
2109             tab5 = QScrollArea()
2110             tab5.setEnabled(True)
2111             tab5.setWidgetResizable(True)
2112
2113             grid_plugins = QGridLayout()
2114             grid_plugins.setColumnStretch(0,1)
2115
2116             w = QWidget()
2117             w.setLayout(grid_plugins)
2118             tab5.setWidget(w)
2119             tab5.setMaximumSize(tab3.size())  # optional
2120
2121             w.setMinimumHeight(len(self.plugins)*35)
2122
2123             tabs.addTab(tab5, _('Plugins') )
2124             def mk_toggle(cb, p):
2125                 return lambda: cb.setChecked(p.toggle())
2126             for i, p in enumerate(self.plugins):
2127                 try:
2128                     name, description = p.get_info()
2129                     cb = QCheckBox(name)
2130                     cb.setDisabled(not p.is_available())
2131                     cb.setChecked(p.is_enabled())
2132                     cb.clicked.connect(mk_toggle(cb,p))
2133                     grid_plugins.addWidget(cb, i, 0)
2134                     if p.requires_settings():
2135                         grid_plugins.addWidget(EnterButton(_('Settings'), p.settings_dialog), i, 1)
2136                     grid_plugins.addWidget(HelpButton(description), i, 2)
2137                 except:
2138                     print_msg("Error: cannot display plugin", p)
2139                     traceback.print_exc(file=sys.stdout)
2140             grid_plugins.setRowStretch(i+1,1)
2141
2142         self.run_hook('create_settings_tab', tabs)
2143
2144         vbox.addLayout(ok_cancel_buttons(d))
2145         d.setLayout(vbox) 
2146
2147         # run the dialog
2148         if not d.exec_(): return
2149
2150         fee = unicode(fee_e.text())
2151         try:
2152             fee = self.read_amount(fee)
2153         except:
2154             QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2155             return
2156
2157         self.wallet.set_fee(fee)
2158         
2159         nz = unicode(nz_e.text())
2160         try:
2161             nz = int( nz )
2162             if nz>8: nz=8
2163         except:
2164             QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2165             return
2166
2167         if self.wallet.num_zeros != nz:
2168             self.wallet.num_zeros = nz
2169             self.config.set_key('num_zeros', nz, True)
2170             self.update_history_tab()
2171             self.update_receive_tab()
2172
2173         usechange_result = usechange_cb.isChecked()
2174         if self.wallet.use_change != usechange_result:
2175             self.wallet.use_change = usechange_result
2176             self.config.set_key('use_change', self.wallet.use_change, True)
2177         
2178         unit_result = units[unit_combo.currentIndex()]
2179         if self.base_unit() != unit_result:
2180             self.decimal_point = 8 if unit_result == 'BTC' else 5
2181             self.config.set_key('decimal_point', self.decimal_point, True)
2182             self.update_history_tab()
2183             self.update_status()
2184         
2185         try:
2186             n = int(gap_e.text())
2187         except:
2188             QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2189             return
2190
2191         if self.wallet.gap_limit != n:
2192             r = self.wallet.change_gap_limit(n)
2193             if r:
2194                 self.update_receive_tab()
2195                 self.config.set_key('gap_limit', self.wallet.gap_limit, True)
2196             else:
2197                 QMessageBox.warning(self, _('Error'), _('Invalid value'), _('OK'))
2198
2199         need_restart = False
2200
2201         lang_request = languages.keys()[lang_combo.currentIndex()]
2202         if lang_request != self.config.get('language'):
2203             self.config.set_key("language", lang_request, True)
2204             need_restart = True
2205             
2206         cur_request = str(currencies[cur_combo.currentIndex()])
2207         if cur_request != self.config.get('currency', "None"):
2208             self.config.set_key('currency', cur_request, True)
2209             self.update_wallet()
2210
2211         self.run_hook('close_settings_dialog')
2212
2213         if need_restart:
2214             QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2215
2216         self.receive_tab_set_mode(expert_cb.isChecked())
2217
2218     def run_network_dialog(self):
2219         NetworkDialog(self.wallet.interface, self.config, self).do_exec()
2220
2221     def closeEvent(self, event):
2222         g = self.geometry()
2223         self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2224         self.save_column_widths()
2225         self.config.set_key("console-history",self.console.history[-50:])
2226         event.accept()
2227
2228
2229
2230
2231
2232 class ElectrumGui:
2233
2234     def __init__(self, wallet, config, app=None):
2235         self.wallet = wallet
2236         self.config = config
2237         if app is None:
2238             self.app = QApplication(sys.argv)
2239
2240
2241     def restore_or_create(self):
2242         msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
2243         r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
2244         if r==2: return None
2245         return 'restore' if r==1 else 'create'
2246
2247
2248     def verify_seed(self):
2249         r = self.seed_dialog(False)
2250         if r != self.wallet.seed:
2251             QMessageBox.warning(None, _('Error'), 'incorrect seed', 'OK')
2252             return False
2253         else:
2254             return True
2255         
2256
2257
2258     def seed_dialog(self, is_restore=True):
2259         d = QDialog()
2260         d.setModal(1)
2261
2262         vbox = QVBoxLayout()
2263         if is_restore:
2264             msg = _("Please enter your wallet seed (or your master public key if you want to create a watching-only wallet)." + ' ')
2265         else:
2266             msg = _("Your seed is important! To make sure that you have properly saved your seed, please type it here." + ' ')
2267
2268         msg += _("Your seed can be entered as a sequence of words, or as a hexadecimal string."+ '\n')
2269         
2270         label=QLabel(msg)
2271         label.setWordWrap(True)
2272         vbox.addWidget(label)
2273
2274         seed_e = QTextEdit()
2275         seed_e.setMaximumHeight(100)
2276         vbox.addWidget(seed_e)
2277
2278         if is_restore:
2279             grid = QGridLayout()
2280             grid.setSpacing(8)
2281             gap_e = AmountEdit(None, True)
2282             gap_e.setText("5")
2283             grid.addWidget(QLabel(_('Gap limit')), 2, 0)
2284             grid.addWidget(gap_e, 2, 1)
2285             grid.addWidget(HelpButton(_('Keep the default value unless you modified this parameter in your wallet.')), 2, 3)
2286             vbox.addLayout(grid)
2287
2288         vbox.addLayout(ok_cancel_buttons(d))
2289         d.setLayout(vbox) 
2290
2291         if not d.exec_(): return
2292
2293         try:
2294             seed = str(seed_e.toPlainText())
2295             seed.decode('hex')
2296         except:
2297             try:
2298                 seed = mnemonic.mn_decode( seed.split() )
2299             except:
2300                 QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
2301                 return
2302
2303         if not seed:
2304             QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
2305             return
2306
2307         if not is_restore:
2308             return seed
2309         else:
2310             try:
2311                 gap = int(unicode(gap_e.text()))
2312             except:
2313                 QMessageBox.warning(None, _('Error'), 'error', 'OK')
2314                 return
2315             return seed, gap
2316
2317
2318     def network_dialog(self):
2319         return NetworkDialog(self.wallet.interface, self.config, None).do_exec()
2320         
2321
2322     def show_seed(self):
2323         ElectrumWindow.show_seed(self.wallet.seed, self.wallet.imported_keys)
2324
2325     def password_dialog(self):
2326         if self.wallet.seed:
2327             ElectrumWindow.change_password_dialog(self.wallet)
2328
2329
2330     def restore_wallet(self):
2331         wallet = self.wallet
2332         # wait until we are connected, because the user might have selected another server
2333         if not wallet.interface.is_connected:
2334             waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
2335             waiting_dialog(waiting)
2336
2337         waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
2338             %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
2339
2340         wallet.set_up_to_date(False)
2341         wallet.interface.poke('synchronizer')
2342         waiting_dialog(waiting)
2343         if wallet.is_found():
2344             print_error( "Recovery successful" )
2345         else:
2346             QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
2347
2348         return True
2349
2350     def main(self,url):
2351         s = Timer()
2352         s.start()
2353         w = ElectrumWindow(self.wallet, self.config)
2354         if url: w.set_url(url)
2355         w.app = self.app
2356         w.connect_slots(s)
2357         w.update_wallet()
2358         w.show()
2359
2360         self.app.exec_()
2361
2362