add seed and password to menu; reorganize
[electrum-nvc.git] / gui / qt / main_window.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 electrum.i18n import _, set_language
21 from electrum.util import print_error, print_msg
22 import os.path, json, ast, traceback
23 import shutil
24 import StringIO
25
26
27 import PyQt4
28 from PyQt4.QtGui import *
29 from PyQt4.QtCore import *
30 import PyQt4.QtCore as QtCore
31
32 from electrum.bitcoin import MIN_RELAY_TX_FEE, is_valid
33 from electrum.plugins import run_hook
34
35 import icons_rc
36
37 from electrum.wallet import format_satoshis
38 from electrum import Transaction
39 from electrum import mnemonic
40 from electrum import util, bitcoin, commands, Interface, Wallet
41 from electrum import SimpleConfig, Wallet, WalletStorage
42
43
44 from electrum import bmp, pyqrnative
45
46 from amountedit import AmountEdit
47 from network_dialog import NetworkDialog
48 from qrcodewidget import QRCodeWidget
49
50 from decimal import Decimal
51
52 import platform
53 import httplib
54 import socket
55 import webbrowser
56 import csv
57
58 if platform.system() == 'Windows':
59     MONOSPACE_FONT = 'Lucida Console'
60 elif platform.system() == 'Darwin':
61     MONOSPACE_FONT = 'Monaco'
62 else:
63     MONOSPACE_FONT = 'monospace'
64
65 from electrum import ELECTRUM_VERSION
66 import re
67
68 from util import *
69
70
71         
72
73
74
75 class StatusBarButton(QPushButton):
76     def __init__(self, icon, tooltip, func):
77         QPushButton.__init__(self, icon, '')
78         self.setToolTip(tooltip)
79         self.setFlat(True)
80         self.setMaximumWidth(25)
81         self.clicked.connect(func)
82         self.func = func
83         self.setIconSize(QSize(25,25))
84
85     def keyPressEvent(self, e):
86         if e.key() == QtCore.Qt.Key_Return:
87             apply(self.func,())
88
89
90
91
92
93
94
95
96
97
98 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], "receive":[[370], [370,200,130]] }
99
100 class ElectrumWindow(QMainWindow):
101     def changeEvent(self, event):
102         flags = self.windowFlags();
103         if event and event.type() == QtCore.QEvent.WindowStateChange:
104             if self.windowState() & QtCore.Qt.WindowMinimized:
105                 self.build_menu(True)
106                 # The only way to toggle the icon in the window managers taskbar is to use the Qt.Tooltip flag
107                 # The problem is that it somehow creates an (in)visible window that will stay active and prevent
108                 # Electrum from closing.
109                 # As for now I have no clue how to implement a proper 'hide to tray' functionality.
110                 # self.setWindowFlags(flags & ~Qt.ToolTip)
111             elif event.oldState() & QtCore.Qt.WindowMinimized:
112                 self.build_menu(False)
113                 #self.setWindowFlags(flags | Qt.ToolTip)
114
115     def build_menu(self, is_hidden = False):
116         m = QMenu()
117         if self.isMinimized():
118             m.addAction(_("Show"), self.showNormal)
119         else:
120             m.addAction(_("Hide"), self.showMinimized)
121
122         m.addSeparator()
123         m.addAction(_("Exit Electrum"), self.close)
124         self.tray.setContextMenu(m)
125
126     def tray_activated(self, reason):
127         if reason == QSystemTrayIcon.DoubleClick:
128             self.showNormal()
129
130
131     def __init__(self, config, network):
132         QMainWindow.__init__(self)
133
134         self.config = config
135         self.network = network
136
137         self._close_electrum = False
138         self.lite = None
139         self.current_account = self.config.get("current_account", None)
140
141         self.icon = QIcon(':icons/electrum.png')
142         self.tray = QSystemTrayIcon(self.icon, self)
143         self.tray.setToolTip('Electrum')
144         self.tray.activated.connect(self.tray_activated)
145
146         self.build_menu()
147         self.tray.show()
148         self.create_status_bar()
149
150         self.need_update = threading.Event()
151
152         self.expert_mode   = config.get('classic_expert_mode', False)
153         self.decimal_point = config.get('decimal_point', 8)
154         self.num_zeros     = int(config.get('num_zeros',0))
155
156         set_language(config.get('language'))
157
158         self.funds_error = False
159         self.completions = QStringListModel()
160
161         self.tabs = tabs = QTabWidget(self)
162         self.column_widths = self.config.get("column_widths", default_column_widths )
163         tabs.addTab(self.create_history_tab(), _('History') )
164         tabs.addTab(self.create_send_tab(), _('Send') )
165         tabs.addTab(self.create_receive_tab(), _('Receive') )
166         tabs.addTab(self.create_contacts_tab(), _('Contacts') )
167         tabs.addTab(self.create_console_tab(), _('Console') )
168         tabs.setMinimumSize(600, 400)
169         tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
170         self.setCentralWidget(tabs)
171
172         g = self.config.get("winpos-qt",[100, 100, 840, 400])
173         self.setGeometry(g[0], g[1], g[2], g[3])
174
175         self.init_menubar()
176
177         QShortcut(QKeySequence("Ctrl+W"), self, self.close)
178         QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
179         QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
180         QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
181         QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
182         
183         self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
184         self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.network.banner) )
185         self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
186
187         self.history_list.setFocus(True)
188
189         # network callbacks
190         self.network.register_callback('updated', lambda: self.need_update.set())
191         self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
192         self.network.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
193         self.network.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
194         self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
195         # set initial message
196         self.console.showMessage(self.network.banner)
197
198         # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
199         if platform.system() == 'Windows':
200             n = 3 if self.wallet.seed else 2
201             tabs.setCurrentIndex (n)
202             tabs.setCurrentIndex (0)
203
204         self.wallet = None
205         self.init_lite()
206
207
208     def go_full(self):
209         self.config.set_key('lite_mode', False, True)
210         self.mini.hide()
211         self.show()
212
213     def go_lite(self):
214         self.config.set_key('lite_mode', True, True)
215         self.hide()
216         self.mini.show()
217
218
219     def init_lite(self):
220         import lite_window
221         if not self.check_qt_version():
222             if self.config.get('lite_mode') is True:
223                 msg = "Electrum was unable to load the 'Lite GUI' because it needs Qt version >= 4.7.\nChanging your config to use the 'Classic' GUI"
224                 QMessageBox.warning(None, "Could not start Lite GUI.", msg)
225                 self.config.set_key('lite_mode', False, True)
226                 sys.exit(0)
227             self.mini = None
228             return
229
230         actuator = lite_window.MiniActuator(self)
231
232         # Should probably not modify the current path but instead
233         # change the behaviour of rsrc(...)
234         old_path = QDir.currentPath()
235         actuator.load_theme()
236
237         self.mini = lite_window.MiniWindow(actuator, self.go_full, self.config)
238
239         driver = lite_window.MiniDriver(self, self.mini)
240
241         # Reset path back to original value now that loading the GUI
242         # is completed.
243         QDir.setCurrent(old_path)
244
245         if self.config.get('lite_mode') is True:
246             self.go_lite()
247         else:
248             self.go_full()
249
250
251     def check_qt_version(self):
252         qtVersion = qVersion()
253         return int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7
254     
255
256
257     def load_wallet(self, wallet):
258         import electrum
259         self.wallet = wallet
260
261         title = 'Electrum ' + self.wallet.electrum_version + '  -  ' + self.wallet.storage.path
262         if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only'))
263         self.setWindowTitle( title )
264         self.update_wallet()
265         # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
266         self.notify_transactions()
267
268         # account selector
269         accounts = self.wallet.get_account_names()
270         self.account_selector.clear()
271         if len(accounts) > 1:
272             self.account_selector.addItems([_("All accounts")] + accounts.values())
273             self.account_selector.setCurrentIndex(0)
274             self.account_selector.show()
275         else:
276             self.account_selector.hide()
277
278         self.new_account.setEnabled(self.wallet.seed_version>4)
279
280         self.update_lock_icon()
281         self.update_buttons_on_seed()
282         self.update_console()
283
284         run_hook('load_wallet', wallet)
285
286
287     def select_wallet_file(self):
288         wallet_folder = self.wallet.storage.path
289         re.sub("(\/\w*.dat)$", "", wallet_folder)
290         file_name = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
291         return file_name
292
293
294     def open_wallet(self):
295
296         filename = self.select_wallet_file()
297         if not filename:
298             return
299
300         storage = WalletStorage({'wallet_path': filename})
301         if not storage.file_exists:
302             self.show_message("file not found "+ filename)
303             return
304
305         self.wallet.stop_threads()
306         
307         # create new wallet 
308         wallet = Wallet(storage)
309         wallet.start_threads(self.network)
310
311         self.load_wallet(wallet)
312
313
314
315     def backup_wallet(self):
316         import shutil
317         path = self.wallet.storage.path
318         wallet_folder = os.path.dirname(path)
319         new_filename, ok = QInputDialog.getText(self, _('Filename'), _('Current directory') + ': ' + wallet_folder + '\n' + _('Enter a filename for the copy of your wallet') + ':')
320         new_filename = unicode(new_filename)
321         if not ok or not new_filename:
322             return
323
324         new_path = os.path.join(wallet_folder, new_filename)
325         if new_path != path:
326             try:
327                 shutil.copy2(path, new_path)
328                 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
329             except (IOError, os.error), reason:
330                 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
331
332
333     def new_wallet(self):
334         import installwizard
335
336         wallet_folder = os.path.dirname(self.wallet.storage.path)
337         filename, ok = QInputDialog.getText(self, _('Filename'), _('Current directory') + ': ' + wallet_folder + '\n'+_('Enter a new file name') + ':')
338         filename = unicode(filename)
339         if not ok or not filename:
340             return
341         filename = os.path.join(wallet_folder, filename)
342
343         storage = WalletStorage({'wallet_path': filename})
344         assert not storage.file_exists
345
346         wizard = installwizard.InstallWizard(self.config, self.network, storage)
347         wallet = wizard.run()
348         if wallet: 
349             self.load_wallet(wallet)
350         
351
352
353     def init_menubar(self):
354         menubar = QMenuBar()
355
356         file_menu = menubar.addMenu(_("&File"))
357         open_wallet_action = file_menu.addAction(_("&Open"))
358         open_wallet_action.triggered.connect(self.open_wallet)
359
360         new_wallet_action = file_menu.addAction(_("&Create/Restore"))
361         new_wallet_action.triggered.connect(self.new_wallet)
362
363         wallet_backup = file_menu.addAction(_("&Copy"))
364         wallet_backup.triggered.connect(self.backup_wallet)
365
366         quit_item = file_menu.addAction(_("&Close"))
367         quit_item.triggered.connect(self.close)
368
369         wallet_menu = menubar.addMenu(_("&Wallet"))
370
371         new_contact = wallet_menu.addAction(_("&New contact"))
372         new_contact.triggered.connect(self.new_contact_dialog)
373
374         self.new_account = wallet_menu.addAction(_("&New account"))
375         self.new_account.triggered.connect(self.new_account_dialog)
376
377         wallet_menu.addSeparator()
378
379         pw = wallet_menu.addAction(_("&Password"))
380         pw.triggered.connect(self.change_password_dialog)
381
382         show_seed = wallet_menu.addAction(_("&Seed"))
383         show_seed.triggered.connect(self.show_seed_dialog)
384
385         show_mpk = wallet_menu.addAction(_("&Master Public Key"))
386         show_mpk.triggered.connect(self.show_master_public_key)
387
388         wallet_menu.addSeparator()
389
390         labels_menu = wallet_menu.addMenu(_("&Labels"))
391         import_labels = labels_menu.addAction(_("&Import"))
392         import_labels.triggered.connect(self.do_import_labels)
393         export_labels = labels_menu.addAction(_("&Export"))
394         export_labels.triggered.connect(self.do_export_labels)
395
396         keys_menu = wallet_menu.addMenu(_("&Private keys"))
397         import_keys = keys_menu.addAction(_("&Import"))
398         import_keys.triggered.connect(self.do_import_privkey)
399         export_keys = keys_menu.addAction(_("&Export"))
400         export_keys.triggered.connect(self.do_export_privkeys)
401
402         ex_history = wallet_menu.addAction(_("&Export History"))
403         ex_history.triggered.connect(self.do_export_history)
404
405
406
407         tools_menu = menubar.addMenu(_("&Tools"))
408
409         # Settings / Preferences are all reserved keywords in OSX using this as work around
410         preferences_name = _("Electrum preferences") if sys.platform == 'darwin' else _("Preferences")
411         preferences_menu = tools_menu.addAction(preferences_name)
412         preferences_menu.triggered.connect(self.settings_dialog)
413
414         network = tools_menu.addAction(_("&Network"))
415         network.triggered.connect(self.run_network_dialog)
416
417         plugins_labels = tools_menu.addAction(_("&Plugins"))
418         plugins_labels.triggered.connect(self.plugins_dialog)
419
420         tools_menu.addSeparator()
421
422         csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
423
424         csv_transaction_file = csv_transaction_menu.addAction(_("&From CSV file"))
425         csv_transaction_file.triggered.connect(self.do_process_from_csv_file)
426
427         csv_transaction_text = csv_transaction_menu.addAction(_("&From CSV text"))
428         csv_transaction_text.triggered.connect(self.do_process_from_csv_text)
429
430         raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
431
432         raw_transaction_file = raw_transaction_menu.addAction(_("&From file"))
433         raw_transaction_file.triggered.connect(self.do_process_from_file)
434
435         raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
436         raw_transaction_text.triggered.connect(self.do_process_from_text)
437
438
439         help_menu = menubar.addMenu(_("&Help"))
440         show_about = help_menu.addAction(_("&About"))
441         show_about.triggered.connect(self.show_about)
442         web_open = help_menu.addAction(_("&Official website")) 
443         web_open.triggered.connect(lambda: webbrowser.open("http://electrum.org"))
444
445         help_menu.addSeparator()
446         doc_open = help_menu.addAction(_("&Documentation"))
447         doc_open.triggered.connect(lambda: webbrowser.open("http://electrum.org/documentation.html"))
448         report_bug = help_menu.addAction(_("&Report Bug"))
449         report_bug.triggered.connect(self.show_report_bug)
450
451         self.setMenuBar(menubar)
452
453     def show_about(self):
454         QMessageBox.about(self, "Electrum",
455             _("Version")+" %s" % (self.wallet.electrum_version) + "\n\n" + _("Electrum's focus is speed, with low resource usage and simplifying Bitcoin. You do not need to perform regular backups, because your wallet can be recovered from a secret phrase that you can memorize or write on paper. Startup times are instant because it operates in conjunction with high-performance servers that handle the most complicated parts of the Bitcoin system."))
456
457     def show_report_bug(self):
458         QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
459             _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
460
461
462     def notify_transactions(self):
463         print_error("Notifying GUI")
464         if len(self.network.interface.pending_transactions_for_notifications) > 0:
465             # Combine the transactions if there are more then three
466             tx_amount = len(self.network.interface.pending_transactions_for_notifications)
467             if(tx_amount >= 3):
468                 total_amount = 0
469                 for tx in self.network.interface.pending_transactions_for_notifications:
470                     is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
471                     if(v > 0):
472                         total_amount += v
473
474                 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
475                                 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
476
477                 self.network.interface.pending_transactions_for_notifications = []
478             else:
479               for tx in self.network.interface.pending_transactions_for_notifications:
480                   if tx:
481                       self.network.interface.pending_transactions_for_notifications.remove(tx)
482                       is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
483                       if(v > 0):
484                           self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
485
486     def notify(self, message):
487         self.tray.showMessage("Electrum", message, QSystemTrayIcon.Information, 20000)
488
489
490
491     # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
492     def getOpenFileName(self, title, filter = ""):
493         directory = self.config.get('io_dir', os.path.expanduser('~'))
494         fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
495         if fileName and directory != os.path.dirname(fileName):
496             self.config.set_key('io_dir', os.path.dirname(fileName), True)
497         return fileName
498
499     def getSaveFileName(self, title, filename, filter = ""):
500         directory = self.config.get('io_dir', os.path.expanduser('~'))
501         path = os.path.join( directory, filename )
502         fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
503         if fileName and directory != os.path.dirname(fileName):
504             self.config.set_key('io_dir', os.path.dirname(fileName), True)
505         return fileName
506
507     def close(self):
508         QMainWindow.close(self)
509         run_hook('close_main_window')
510
511     def connect_slots(self, sender):
512         self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
513         self.previous_payto_e=''
514
515     def timer_actions(self):
516         if self.need_update.is_set():
517             self.update_wallet()
518             self.need_update.clear()
519         run_hook('timer_actions')
520     
521     def format_amount(self, x, is_diff=False, whitespaces=False):
522         return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
523
524     def read_amount(self, x):
525         if x in['.', '']: return None
526         p = pow(10, self.decimal_point)
527         return int( p * Decimal(x) )
528
529     def base_unit(self):
530         assert self.decimal_point in [5,8]
531         return "BTC" if self.decimal_point == 8 else "mBTC"
532
533
534     def update_status(self):
535         if self.network.interface and self.network.interface.is_connected:
536             if not self.wallet.up_to_date:
537                 text = _("Synchronizing...")
538                 icon = QIcon(":icons/status_waiting.png")
539             else:
540                 c, u = self.wallet.get_account_balance(self.current_account)
541                 text =  _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
542                 if u: text +=  " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
543
544                 r = {}
545                 run_hook('set_quote_text', c+u, r)
546                 quote = r.get(0)
547                 if quote:
548                     text += "  (%s)"%quote
549
550                 self.tray.setToolTip(text)
551                 icon = QIcon(":icons/status_connected.png")
552         else:
553             text = _("Not connected")
554             icon = QIcon(":icons/status_disconnected.png")
555
556         self.balance_label.setText(text)
557         self.status_button.setIcon( icon )
558
559
560     def update_wallet(self):
561         self.update_status()
562         if self.wallet.up_to_date or not self.network.interface.is_connected:
563             self.update_history_tab()
564             self.update_receive_tab()
565             self.update_contacts_tab()
566             self.update_completions()
567
568         
569     def create_history_tab(self):
570         self.history_list = l = MyTreeWidget(self)
571         l.setColumnCount(5)
572         for i,width in enumerate(self.column_widths['history']):
573             l.setColumnWidth(i, width)
574         l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
575         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), self.tx_label_clicked)
576         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.tx_label_changed)
577
578         l.customContextMenuRequested.connect(self.create_history_menu)
579         return l
580
581
582     def create_history_menu(self, position):
583         self.history_list.selectedIndexes() 
584         item = self.history_list.currentItem()
585         if not item: return
586         tx_hash = str(item.data(0, Qt.UserRole).toString())
587         if not tx_hash: return
588         menu = QMenu()
589         menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
590         menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
591         menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
592         menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
593
594
595     def show_transaction(self, tx):
596         import transaction_dialog
597         d = transaction_dialog.TxDialog(tx, self)
598         d.exec_()
599
600     def tx_label_clicked(self, item, column):
601         if column==2 and item.isSelected():
602             self.is_edit=True
603             item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
604             self.history_list.editItem( item, column )
605             item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
606             self.is_edit=False
607
608     def tx_label_changed(self, item, column):
609         if self.is_edit: 
610             return
611         self.is_edit=True
612         tx_hash = str(item.data(0, Qt.UserRole).toString())
613         tx = self.wallet.transactions.get(tx_hash)
614         text = unicode( item.text(2) )
615         self.wallet.set_label(tx_hash, text) 
616         if text: 
617             item.setForeground(2, QBrush(QColor('black')))
618         else:
619             text = self.wallet.get_default_label(tx_hash)
620             item.setText(2, text)
621             item.setForeground(2, QBrush(QColor('gray')))
622         self.is_edit=False
623
624
625     def edit_label(self, is_recv):
626         l = self.receive_list if is_recv else self.contacts_list
627         item = l.currentItem()
628         item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
629         l.editItem( item, 1 )
630         item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
631
632
633
634     def address_label_clicked(self, item, column, l, column_addr, column_label):
635         if column == column_label and item.isSelected():
636             is_editable = item.data(0, 32).toBool()
637             if not is_editable:
638                 return
639             addr = unicode( item.text(column_addr) )
640             label = unicode( item.text(column_label) )
641             item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
642             l.editItem( item, column )
643             item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
644
645
646     def address_label_changed(self, item, column, l, column_addr, column_label):
647         if column == column_label:
648             addr = unicode( item.text(column_addr) )
649             text = unicode( item.text(column_label) )
650             is_editable = item.data(0, 32).toBool()
651             if not is_editable:
652                 return
653
654             changed = self.wallet.set_label(addr, text)
655             if changed:
656                 self.update_history_tab()
657                 self.update_completions()
658                 
659             self.current_item_changed(item)
660
661         run_hook('item_changed', item, column)
662
663
664     def current_item_changed(self, a):
665         run_hook('current_item_changed', a)
666
667
668
669     def update_history_tab(self):
670
671         self.history_list.clear()
672         for item in self.wallet.get_tx_history(self.current_account):
673             tx_hash, conf, is_mine, value, fee, balance, timestamp = item
674             if conf > 0:
675                 try:
676                     time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
677                 except:
678                     time_str = _("unknown")
679
680             if conf == -1:
681                 time_str = 'unverified'
682                 icon = QIcon(":icons/unconfirmed.png")
683             elif conf == 0:
684                 time_str = 'pending'
685                 icon = QIcon(":icons/unconfirmed.png")
686             elif conf < 6:
687                 icon = QIcon(":icons/clock%d.png"%conf)
688             else:
689                 icon = QIcon(":icons/confirmed.png")
690
691             if value is not None:
692                 v_str = self.format_amount(value, True, whitespaces=True)
693             else:
694                 v_str = '--'
695
696             balance_str = self.format_amount(balance, whitespaces=True)
697             
698             if tx_hash:
699                 label, is_default_label = self.wallet.get_label(tx_hash)
700             else:
701                 label = _('Pruned transaction outputs')
702                 is_default_label = False
703
704             item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
705             item.setFont(2, QFont(MONOSPACE_FONT))
706             item.setFont(3, QFont(MONOSPACE_FONT))
707             item.setFont(4, QFont(MONOSPACE_FONT))
708             if value < 0:
709                 item.setForeground(3, QBrush(QColor("#BC1E1E")))
710             if tx_hash:
711                 item.setData(0, Qt.UserRole, tx_hash)
712                 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
713             if is_default_label:
714                 item.setForeground(2, QBrush(QColor('grey')))
715
716             item.setIcon(0, icon)
717             self.history_list.insertTopLevelItem(0,item)
718             
719
720         self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
721
722
723     def create_send_tab(self):
724         w = QWidget()
725
726         grid = QGridLayout()
727         grid.setSpacing(8)
728         grid.setColumnMinimumWidth(3,300)
729         grid.setColumnStretch(5,1)
730
731
732         self.payto_e = QLineEdit()
733         grid.addWidget(QLabel(_('Pay to')), 1, 0)
734         grid.addWidget(self.payto_e, 1, 1, 1, 3)
735             
736         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)
737
738         completer = QCompleter()
739         completer.setCaseSensitivity(False)
740         self.payto_e.setCompleter(completer)
741         completer.setModel(self.completions)
742
743         self.message_e = QLineEdit()
744         grid.addWidget(QLabel(_('Description')), 2, 0)
745         grid.addWidget(self.message_e, 2, 1, 1, 3)
746         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)
747
748         self.amount_e = AmountEdit(self.base_unit)
749         grid.addWidget(QLabel(_('Amount')), 3, 0)
750         grid.addWidget(self.amount_e, 3, 1, 1, 2)
751         grid.addWidget(HelpButton(
752                 _('Amount to be sent.') + '\n\n' \
753                     + _('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.') \
754                     + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.')), 3, 3)
755         
756         self.fee_e = AmountEdit(self.base_unit)
757         grid.addWidget(QLabel(_('Fee')), 4, 0)
758         grid.addWidget(self.fee_e, 4, 1, 1, 2) 
759         grid.addWidget(HelpButton(
760                 _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
761                     + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
762                     + _('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)
763
764
765         self.send_button = EnterButton(_("Send"), self.do_send)
766         grid.addWidget(self.send_button, 6, 1)
767
768         b = EnterButton(_("Clear"),self.do_clear)
769         grid.addWidget(b, 6, 2)
770
771         self.payto_sig = QLabel('')
772         grid.addWidget(self.payto_sig, 7, 0, 1, 4)
773
774         QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
775         QShortcut(QKeySequence("Down"), w, w.focusNextChild)
776         w.setLayout(grid) 
777
778         w2 = QWidget()
779         vbox = QVBoxLayout()
780         vbox.addWidget(w)
781         vbox.addStretch(1)
782         w2.setLayout(vbox)
783
784         def entry_changed( is_fee ):
785             self.funds_error = False
786
787             if self.amount_e.is_shortcut:
788                 self.amount_e.is_shortcut = False
789                 c, u = self.wallet.get_account_balance(self.current_account)
790                 inputs, total, fee = self.wallet.choose_tx_inputs( c + u, 0, self.current_account)
791                 fee = self.wallet.estimated_fee(inputs)
792                 amount = c + u - fee
793                 self.amount_e.setText( self.format_amount(amount) )
794                 self.fee_e.setText( self.format_amount( fee ) )
795                 return
796                 
797             amount = self.read_amount(str(self.amount_e.text()))
798             fee = self.read_amount(str(self.fee_e.text()))
799
800             if not is_fee: fee = None
801             if amount is None:
802                 return
803             inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee, self.current_account )
804             if not is_fee:
805                 self.fee_e.setText( self.format_amount( fee ) )
806             if inputs:
807                 palette = QPalette()
808                 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
809                 text = ""
810             else:
811                 palette = QPalette()
812                 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
813                 self.funds_error = True
814                 text = _( "Not enough funds" )
815                 c, u = self.wallet.get_frozen_balance()
816                 if c+u: text += ' (' + self.format_amount(c+u).strip() + self.base_unit() + ' ' +_("are frozen") + ')'
817
818             self.statusBar().showMessage(text)
819             self.amount_e.setPalette(palette)
820             self.fee_e.setPalette(palette)
821
822         self.amount_e.textChanged.connect(lambda: entry_changed(False) )
823         self.fee_e.textChanged.connect(lambda: entry_changed(True) )
824
825         run_hook('create_send_tab', grid)
826         return w2
827
828
829     def update_completions(self):
830         l = []
831         for addr,label in self.wallet.labels.items():
832             if addr in self.wallet.addressbook:
833                 l.append( label + '  <' + addr + '>')
834
835         run_hook('update_completions', l)
836         self.completions.setStringList(l)
837
838
839     def protected(func):
840         return lambda s, *args: s.do_protect(func, args)
841
842
843     def do_send(self):
844
845         label = unicode( self.message_e.text() )
846         r = unicode( self.payto_e.text() )
847         r = r.strip()
848
849         # label or alias, with address in brackets
850         m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
851         to_address = m.group(2) if m else r
852
853         if not is_valid(to_address):
854             QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
855             return
856
857         try:
858             amount = self.read_amount(unicode( self.amount_e.text()))
859         except:
860             QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
861             return
862         try:
863             fee = self.read_amount(unicode( self.fee_e.text()))
864         except:
865             QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
866             return
867
868         confirm_amount = self.config.get('confirm_amount', 100000000)
869         if amount >= confirm_amount:
870             if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : to_address}):
871                 return
872
873         self.send_tx(to_address, amount, fee, label)
874
875
876     @protected
877     def send_tx(self, to_address, amount, fee, label, password):
878
879         try:
880             tx = self.wallet.mktx_from_account( [(to_address, amount)], password, fee, self.current_account)
881         except BaseException, e:
882             traceback.print_exc(file=sys.stdout)
883             self.show_message(str(e))
884             return
885
886         if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
887             QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
888             return
889
890         if label: 
891             self.wallet.set_label(tx.hash(), label)
892
893         if tx.is_complete:
894             h = self.wallet.send_tx(tx)
895             waiting_dialog(lambda: False if self.wallet.tx_event.isSet() else _("Please wait..."))
896             status, msg = self.wallet.receive_tx( h )
897             if status:
898                 QMessageBox.information(self, '', _('Payment sent.')+'\n'+msg, _('OK'))
899                 self.do_clear()
900                 self.update_contacts_tab()
901             else:
902                 QMessageBox.warning(self, _('Error'), msg, _('OK'))
903         else:
904             filename = label + '.txn' if label else 'unsigned_%s.txn' % (time.mktime(time.gmtime()))
905             try:
906                 fileName = self.getSaveFileName(_("Select a transaction filename"), filename, "*.txn")
907                 with open(fileName,'w') as f:
908                     f.write(json.dumps(tx.as_dict(),indent=4) + '\n')
909                 QMessageBox.information(self, _('Unsigned transaction created'), _("Unsigned transaction was saved to file:") + " " +fileName, _('OK'))
910             except:
911                 QMessageBox.warning(self, _('Error'), _('Could not write transaction to file'), _('OK'))
912
913         # add recipient to addressbook
914         if to_address not in self.wallet.addressbook and not self.wallet.is_mine(to_address):
915             self.wallet.addressbook.append(to_address)
916
917
918
919
920     def set_url(self, url):
921         address, amount, label, message, signature, identity, url = util.parse_url(url)
922
923         if amount and self.base_unit() == 'mBTC': amount = str( 1000* Decimal(amount))
924
925         if self.mini:
926             self.mini.set_payment_fields(address, amount)
927
928         if label and self.wallet.labels.get(address) != label:
929             if self.question('Give label "%s" to address %s ?'%(label,address)):
930                 if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
931                     self.wallet.addressbook.append(address)
932                 self.wallet.set_label(address, label)
933
934         run_hook('set_url', url, self.show_message, self.question)
935
936         self.tabs.setCurrentIndex(1)
937         label = self.wallet.labels.get(address)
938         m_addr = label + '  <'+ address +'>' if label else address
939         self.payto_e.setText(m_addr)
940
941         self.message_e.setText(message)
942         if amount:
943             self.amount_e.setText(amount)
944
945         if identity:
946             self.set_frozen(self.payto_e,True)
947             self.set_frozen(self.amount_e,True)
948             self.set_frozen(self.message_e,True)
949             self.payto_sig.setText( '      '+_('The bitcoin URI was signed by')+' ' + identity )
950         else:
951             self.payto_sig.setVisible(False)
952
953     def do_clear(self):
954         self.payto_sig.setVisible(False)
955         for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
956             e.setText('')
957             self.set_frozen(e,False)
958         self.update_status()
959
960     def set_frozen(self,entry,frozen):
961         if frozen:
962             entry.setReadOnly(True)
963             entry.setFrame(False)
964             palette = QPalette()
965             palette.setColor(entry.backgroundRole(), QColor('lightgray'))
966             entry.setPalette(palette)
967         else:
968             entry.setReadOnly(False)
969             entry.setFrame(True)
970             palette = QPalette()
971             palette.setColor(entry.backgroundRole(), QColor('white'))
972             entry.setPalette(palette)
973
974
975     def toggle_freeze(self,addr):
976         if not addr: return
977         if addr in self.wallet.frozen_addresses:
978             self.wallet.unfreeze(addr)
979         else:
980             self.wallet.freeze(addr)
981         self.update_receive_tab()
982
983     def toggle_priority(self,addr):
984         if not addr: return
985         if addr in self.wallet.prioritized_addresses:
986             self.wallet.unprioritize(addr)
987         else:
988             self.wallet.prioritize(addr)
989         self.update_receive_tab()
990
991
992     def create_list_tab(self, headers):
993         "generic tab creation method"
994         l = MyTreeWidget(self)
995         l.setColumnCount( len(headers) )
996         l.setHeaderLabels( headers )
997
998         w = QWidget()
999         vbox = QVBoxLayout()
1000         w.setLayout(vbox)
1001
1002         vbox.setMargin(0)
1003         vbox.setSpacing(0)
1004         vbox.addWidget(l)
1005         buttons = QWidget()
1006         vbox.addWidget(buttons)
1007
1008         hbox = QHBoxLayout()
1009         hbox.setMargin(0)
1010         hbox.setSpacing(0)
1011         buttons.setLayout(hbox)
1012
1013         return l,w,hbox
1014
1015
1016     def create_receive_tab(self):
1017         l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1018         l.setContextMenuPolicy(Qt.CustomContextMenu)
1019         l.customContextMenuRequested.connect(self.create_receive_menu)
1020         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1021         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1022         self.connect(l, SIGNAL('currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)'), lambda a,b: self.current_item_changed(a))
1023         self.receive_list = l
1024         self.receive_buttons_hbox = hbox
1025         hbox.addStretch(1)
1026         return w
1027
1028
1029     def receive_tab_set_mode(self, i):
1030         self.save_column_widths()
1031         self.expert_mode = (i == 1)
1032         self.config.set_key('classic_expert_mode', self.expert_mode, True)
1033         self.update_receive_tab()
1034
1035
1036     def save_column_widths(self):
1037         if not self.expert_mode:
1038             widths = [ self.receive_list.columnWidth(0) ]
1039         else:
1040             widths = []
1041             for i in range(self.receive_list.columnCount() -1):
1042                 widths.append(self.receive_list.columnWidth(i))
1043         self.column_widths["receive"][self.expert_mode] = widths
1044         
1045         self.column_widths["history"] = []
1046         for i in range(self.history_list.columnCount() - 1):
1047             self.column_widths["history"].append(self.history_list.columnWidth(i))
1048
1049         self.column_widths["contacts"] = []
1050         for i in range(self.contacts_list.columnCount() - 1):
1051             self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1052
1053         self.config.set_key("column_widths", self.column_widths, True)
1054
1055
1056     def create_contacts_tab(self):
1057         l,w,hbox = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1058         l.setContextMenuPolicy(Qt.CustomContextMenu)
1059         l.customContextMenuRequested.connect(self.create_contact_menu)
1060         for i,width in enumerate(self.column_widths['contacts']):
1061             l.setColumnWidth(i, width)
1062
1063         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
1064         self.connect(l, SIGNAL('itemChanged(QTreeWidgetItem*, int)'), lambda a,b: self.address_label_changed(a,b,l,0,1))
1065         self.contacts_list = l
1066         self.contacts_buttons_hbox = hbox
1067         hbox.addStretch(1)
1068         return w
1069
1070
1071     def delete_imported_key(self, addr):
1072         if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1073             self.wallet.delete_imported_key(addr)
1074             self.update_receive_tab()
1075             self.update_history_tab()
1076
1077     def edit_account_label(self, k):
1078         text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':')
1079         if ok:
1080             label = unicode(text)
1081             self.wallet.set_label(k,label)
1082             self.update_receive_tab()
1083
1084     def create_account_menu(self, position, k, item):
1085         menu = QMenu()
1086         if item.isExpanded():
1087             menu.addAction(_("Minimize"), lambda: item.setExpanded(False))
1088         else:
1089             menu.addAction(_("Maximize"), lambda: item.setExpanded(True))
1090         menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1091         menu.addAction(_("View details"), lambda: self.show_account_details(k))
1092         menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1093
1094     def create_receive_menu(self, position):
1095         # fixme: this function apparently has a side effect.
1096         # if it is not called the menu pops up several times
1097         #self.receive_list.selectedIndexes() 
1098
1099         item = self.receive_list.itemAt(position)
1100         if not item: return
1101
1102         addr = unicode(item.text(0))
1103         if not is_valid(addr): 
1104             k = str(item.data(0,32).toString())
1105             if k:
1106                 self.create_account_menu(position, k, item)
1107             else:
1108                 item.setExpanded(not item.isExpanded())
1109             return 
1110
1111         menu = QMenu()
1112         menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1113         menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
1114         menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1115         menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1116         menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
1117         if addr in self.wallet.imported_keys:
1118             menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1119
1120         if self.expert_mode:
1121             t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
1122             menu.addAction(t, lambda: self.toggle_freeze(addr))
1123             t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
1124             menu.addAction(t, lambda: self.toggle_priority(addr))
1125             
1126         run_hook('receive_menu', menu)
1127         menu.exec_(self.receive_list.viewport().mapToGlobal(position))
1128
1129
1130     def payto(self, addr):
1131         if not addr: return
1132         label = self.wallet.labels.get(addr)
1133         m_addr = label + '  <' + addr + '>' if label else addr
1134         self.tabs.setCurrentIndex(1)
1135         self.payto_e.setText(m_addr)
1136         self.amount_e.setFocus()
1137
1138
1139     def delete_contact(self, x):
1140         if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1141             self.wallet.delete_contact(x)
1142             self.wallet.set_label(x, None)
1143             self.update_history_tab()
1144             self.update_contacts_tab()
1145             self.update_completions()
1146
1147
1148     def create_contact_menu(self, position):
1149         item = self.contacts_list.itemAt(position)
1150         if not item: return
1151         addr = unicode(item.text(0))
1152         label = unicode(item.text(1))
1153         is_editable = item.data(0,32).toBool()
1154         payto_addr = item.data(0,33).toString()
1155         menu = QMenu()
1156         menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1157         menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1158         menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
1159         if is_editable:
1160             menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1161             menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1162
1163         run_hook('create_contact_menu', menu, item)
1164         menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1165
1166
1167     def update_receive_item(self, item):
1168         item.setFont(0, QFont(MONOSPACE_FONT))
1169         address = str(item.data(0,0).toString())
1170         label = self.wallet.labels.get(address,'')
1171         item.setData(1,0,label)
1172         item.setData(0,32, True) # is editable
1173
1174         run_hook('update_receive_item', address, item)
1175                 
1176         c, u = self.wallet.get_addr_balance(address)
1177         balance = self.format_amount(c + u)
1178         item.setData(2,0,balance)
1179
1180         if self.expert_mode:
1181             if address in self.wallet.frozen_addresses: 
1182                 item.setBackgroundColor(0, QColor('lightblue'))
1183             elif address in self.wallet.prioritized_addresses: 
1184                 item.setBackgroundColor(0, QColor('lightgreen'))
1185         
1186
1187     def update_receive_tab(self):
1188         l = self.receive_list
1189         
1190         l.clear()
1191         l.setColumnHidden(2, not self.expert_mode)
1192         l.setColumnHidden(3, not self.expert_mode)
1193         for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
1194             l.setColumnWidth(i, width)
1195
1196         if self.current_account is None:
1197             account_items = self.wallet.accounts.items()
1198         elif self.current_account != -1:
1199             account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
1200         else:
1201             account_items = []
1202
1203         for k, account in account_items:
1204             name = self.wallet.get_account_name(k)
1205             c,u = self.wallet.get_account_balance(k)
1206             account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1207             l.addTopLevelItem(account_item)
1208             account_item.setExpanded(True)
1209             account_item.setData(0, 32, k)
1210
1211             if not self.wallet.is_seeded(k):
1212                 icon = QIcon(":icons/key.png")
1213                 account_item.setIcon(0, icon)
1214             
1215             for is_change in ([0,1] if self.expert_mode else [0]):
1216                 if self.expert_mode:
1217                     name = _("Receiving") if not is_change else _("Change")
1218                     seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1219                     account_item.addChild(seq_item)
1220                     if not is_change: seq_item.setExpanded(True)
1221                 else:
1222                     seq_item = account_item
1223                 is_red = False
1224                 gap = 0
1225
1226                 for address in account.get_addresses(is_change):
1227                     h = self.wallet.history.get(address,[])
1228             
1229                     if h == []:
1230                         gap += 1
1231                         if gap > self.wallet.gap_limit:
1232                             is_red = True
1233                     else:
1234                         gap = 0
1235
1236                     num_tx = '*' if h == ['*'] else "%d"%len(h)
1237                     item = QTreeWidgetItem( [ address, '', '', num_tx] )
1238                     self.update_receive_item(item)
1239                     if is_red:
1240                         item.setBackgroundColor(1, QColor('red'))
1241                     seq_item.addChild(item)
1242
1243
1244         if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
1245             c,u = self.wallet.get_imported_balance()
1246             account_item = QTreeWidgetItem( [ _('Imported'), '', self.format_amount(c+u), ''] )
1247             l.addTopLevelItem(account_item)
1248             account_item.setExpanded(True)
1249             for address in self.wallet.imported_keys.keys():
1250                 item = QTreeWidgetItem( [ address, '', '', ''] )
1251                 self.update_receive_item(item)
1252                 account_item.addChild(item)
1253                 
1254
1255         # we use column 1 because column 0 may be hidden
1256         l.setCurrentItem(l.topLevelItem(0),1)
1257
1258
1259     def update_contacts_tab(self):
1260         l = self.contacts_list
1261         l.clear()
1262
1263         for address in self.wallet.addressbook:
1264             label = self.wallet.labels.get(address,'')
1265             n = self.wallet.get_num_tx(address)
1266             item = QTreeWidgetItem( [ address, label, "%d"%n] )
1267             item.setFont(0, QFont(MONOSPACE_FONT))
1268             # 32 = label can be edited (bool)
1269             item.setData(0,32, True)
1270             # 33 = payto string
1271             item.setData(0,33, address)
1272             l.addTopLevelItem(item)
1273
1274         run_hook('update_contacts_tab', l)
1275         l.setCurrentItem(l.topLevelItem(0))
1276
1277
1278
1279     def create_console_tab(self):
1280         from console import Console
1281         self.console = console = Console()
1282         return console
1283
1284
1285     def update_console(self):
1286         console = self.console
1287         console.history = self.config.get("console-history",[])
1288         console.history_index = len(console.history)
1289
1290         console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1291         console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1292
1293         c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1294         methods = {}
1295         def mkfunc(f, method):
1296             return lambda *args: apply( f, (method, args, self.password_dialog ))
1297         for m in dir(c):
1298             if m[0]=='_' or m=='wallet' or m == 'interface': continue
1299             methods[m] = mkfunc(c._run, m)
1300             
1301         console.updateNamespace(methods)
1302
1303
1304     def change_account(self,s):
1305         if s == _("All accounts"):
1306             self.current_account = None
1307         else:
1308             accounts = self.wallet.get_account_names()
1309             for k, v in accounts.items():
1310                 if v == s:
1311                     self.current_account = k
1312         self.update_history_tab()
1313         self.update_status()
1314         self.update_receive_tab()
1315
1316     def create_status_bar(self):
1317
1318         sb = QStatusBar()
1319         sb.setFixedHeight(35)
1320         qtVersion = qVersion()
1321
1322         self.balance_label = QLabel("")
1323         sb.addWidget(self.balance_label)
1324
1325         from version_getter import UpdateLabel
1326         self.updatelabel = UpdateLabel(self.config, sb)
1327
1328         self.account_selector = QComboBox()
1329         self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account) 
1330         sb.addPermanentWidget(self.account_selector)
1331
1332         if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1333             sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1334
1335         self.lock_icon = QIcon()
1336         self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1337         sb.addPermanentWidget( self.password_button )
1338             
1339         sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1340         self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) 
1341         sb.addPermanentWidget( self.seed_button )
1342         self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog ) 
1343         sb.addPermanentWidget( self.status_button )
1344
1345         run_hook('create_status_bar', (sb,))
1346
1347         self.setStatusBar(sb)
1348
1349
1350     def update_lock_icon(self):
1351         icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1352         self.password_button.setIcon( icon )
1353
1354
1355     def update_buttons_on_seed(self):
1356         if not self.wallet.is_watching_only():
1357            self.seed_button.show()
1358            self.password_button.show()
1359            self.send_button.setText(_("Send"))
1360         else:
1361            self.password_button.hide()
1362            self.seed_button.hide()
1363            self.send_button.setText(_("Create unsigned transaction"))
1364
1365
1366     def change_password_dialog(self):
1367         from password_dialog import PasswordDialog
1368         d = PasswordDialog(self.wallet, self)
1369         d.run()
1370         self.update_lock_icon()
1371
1372
1373     def new_contact_dialog(self):
1374         text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
1375         address = unicode(text)
1376         if ok:
1377             if is_valid(address):
1378                 self.wallet.add_contact(address)
1379                 self.update_contacts_tab()
1380                 self.update_history_tab()
1381                 self.update_completions()
1382             else:
1383                 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1384
1385
1386     def new_account_dialog(self):
1387
1388         dialog = QDialog(self)
1389         dialog.setModal(1)
1390         dialog.setWindowTitle(_("New Account"))
1391
1392         addr = self.wallet.new_account_address()
1393         vbox = QVBoxLayout()
1394         msg = _("Electrum considers that an account exists only if it contains bitcoins.") + '\n' \
1395               + _("To create a new account, please send coins to the first address of that account.") + '\n' \
1396               + _("Note: you will need to wait for 2 confirmations before the account is created.")
1397         vbox.addWidget(QLabel(msg))
1398         vbox.addWidget(QLabel(_('Address')+':'))
1399         e = QLineEdit(addr)
1400         e.setReadOnly(True)
1401         vbox.addWidget(e)
1402
1403         vbox.addLayout(ok_cancel_buttons(dialog))
1404         dialog.setLayout(vbox)
1405         r = dialog.exec_()
1406         if r:
1407             self.payto(addr)
1408
1409             
1410
1411     def show_master_public_key_old(self):
1412         dialog = QDialog(self)
1413         dialog.setModal(1)
1414         dialog.setWindowTitle(_("Master Public Key"))
1415
1416         main_text = QTextEdit()
1417         main_text.setText(self.wallet.get_master_public_key())
1418         main_text.setReadOnly(True)
1419         main_text.setMaximumHeight(170)
1420         qrw = QRCodeWidget(self.wallet.get_master_public_key())
1421
1422         ok_button = QPushButton(_("OK"))
1423         ok_button.setDefault(True)
1424         ok_button.clicked.connect(dialog.accept)
1425
1426         main_layout = QGridLayout()
1427         main_layout.addWidget(QLabel(_('Your Master Public Key is:')), 0, 0, 1, 2)
1428
1429         main_layout.addWidget(main_text, 1, 0)
1430         main_layout.addWidget(qrw, 1, 1 )
1431
1432         vbox.addLayout(close_button(dialog))
1433         dialog.setLayout(vbox)
1434         dialog.exec_()
1435         
1436
1437     def show_master_public_key(self):
1438
1439         if self.wallet.seed_version == 4:
1440             self.show_master_public_keys_old()
1441             return
1442
1443         dialog = QDialog(self)
1444         dialog.setModal(1)
1445         dialog.setWindowTitle(_("Master Public Keys"))
1446
1447         chain_text = QTextEdit()
1448         chain_text.setReadOnly(True)
1449         chain_text.setMaximumHeight(170)
1450         chain_qrw = QRCodeWidget()
1451
1452         mpk_text = QTextEdit()
1453         mpk_text.setReadOnly(True)
1454         mpk_text.setMaximumHeight(170)
1455         mpk_qrw = QRCodeWidget()
1456
1457         main_layout = QGridLayout()
1458
1459         main_layout.addWidget(QLabel(_('Key')), 1, 0)
1460         main_layout.addWidget(mpk_text, 1, 1)
1461         main_layout.addWidget(mpk_qrw, 1, 2)
1462
1463         main_layout.addWidget(QLabel(_('Chain')), 2, 0)
1464         main_layout.addWidget(chain_text, 2, 1)
1465         main_layout.addWidget(chain_qrw, 2, 2)
1466
1467         def update(key):
1468             c, K, cK = self.wallet.master_public_keys[str(key)]
1469             chain_text.setText(c)
1470             chain_qrw.set_addr(c)
1471             chain_qrw.update_qr()
1472             mpk_text.setText(K)
1473             mpk_qrw.set_addr(c)
1474             mpk_qrw.update_qr()
1475             
1476         key_selector = QComboBox()
1477         keys = sorted(self.wallet.master_public_keys.keys())
1478         key_selector.addItems(keys)
1479
1480         main_layout.addWidget(QLabel(_('Derivation:')), 0, 0)
1481         main_layout.addWidget(key_selector, 0, 1)
1482         dialog.connect(key_selector,SIGNAL("activated(QString)"),update) 
1483
1484         update(keys[0])
1485
1486         vbox = QVBoxLayout()
1487         vbox.addLayout(main_layout)
1488         vbox.addLayout(close_button(dialog))
1489
1490         dialog.setLayout(vbox)
1491         dialog.exec_()
1492         
1493
1494     @protected
1495     def show_seed_dialog(self, password):
1496         if self.wallet.is_watching_only():
1497             QMessageBox.information(self, _('Message'), _('This is a watching-only wallet'), _('OK'))
1498             return
1499
1500         if self.wallet.seed:
1501             try:
1502                 seed = self.wallet.decode_seed(password)
1503             except:
1504                 QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1505                 return
1506             from seed_dialog import SeedDialog
1507             d = SeedDialog(self)
1508             d.show_seed(seed, self.wallet.imported_keys)
1509         else:
1510             l = {}
1511             for k in self.wallet.master_private_keys.keys():
1512                 pk = self.wallet.get_master_private_key(k, password)
1513                 l[k] = pk
1514             from seed_dialog import PrivateKeysDialog
1515             d = PrivateKeysDialog(self,l)
1516             d.exec_()
1517
1518
1519
1520
1521
1522     def show_qrcode(self, data, title = _("QR code")):
1523         if not data: return
1524         d = QDialog(self)
1525         d.setModal(1)
1526         d.setWindowTitle(title)
1527         d.setMinimumSize(270, 300)
1528         vbox = QVBoxLayout()
1529         qrw = QRCodeWidget(data)
1530         vbox.addWidget(qrw, 1)
1531         vbox.addWidget(QLabel(data), 0, Qt.AlignHCenter)
1532         hbox = QHBoxLayout()
1533         hbox.addStretch(1)
1534
1535         def print_qr(self):
1536             filename = "qrcode.bmp"
1537             bmp.save_qrcode(qrw.qr, filename)
1538             QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
1539
1540         b = QPushButton(_("Save"))
1541         hbox.addWidget(b)
1542         b.clicked.connect(print_qr)
1543
1544         b = QPushButton(_("Close"))
1545         hbox.addWidget(b)
1546         b.clicked.connect(d.accept)
1547         b.setDefault(True)
1548
1549         vbox.addLayout(hbox)
1550         d.setLayout(vbox)
1551         d.exec_()
1552
1553
1554     def do_protect(self, func, args):
1555         if self.wallet.use_encryption:
1556             password = self.password_dialog()
1557             if not password:
1558                 return
1559         else:
1560             password = None
1561             
1562         if args != (False,):
1563             args = (self,) + args + (password,)
1564         else:
1565             args = (self,password)
1566         apply( func, args)
1567
1568
1569     @protected
1570     def show_private_key(self, address, password):
1571         if not address: return
1572         try:
1573             pk_list = self.wallet.get_private_key(address, password)
1574         except BaseException, e:
1575             self.show_message(str(e))
1576             return
1577         QMessageBox.information(self, _('Private key'), _('Address')+ ': ' + address + '\n\n' + _('Private key') + ': ' + '\n'.join(pk_list), _('OK'))
1578
1579
1580     @protected
1581     def do_sign(self, address, message, signature, password):
1582         message = unicode(message.toPlainText())
1583         message = message.encode('utf-8')
1584         try:
1585             sig = self.wallet.sign_message(str(address.text()), message, password)
1586             signature.setText(sig)
1587         except BaseException, e:
1588             self.show_message(str(e))
1589
1590     def sign_message(self, address):
1591         if not address: return
1592         d = QDialog(self)
1593         d.setModal(1)
1594         d.setWindowTitle(_('Sign Message'))
1595         d.setMinimumSize(410, 290)
1596
1597         tab_widget = QTabWidget()
1598         tab = QWidget()
1599         layout = QGridLayout(tab)
1600
1601         sign_address = QLineEdit()
1602
1603         sign_address.setText(address)
1604         layout.addWidget(QLabel(_('Address')), 1, 0)
1605         layout.addWidget(sign_address, 1, 1)
1606
1607         sign_message = QTextEdit()
1608         layout.addWidget(QLabel(_('Message')), 2, 0)
1609         layout.addWidget(sign_message, 2, 1)
1610         layout.setRowStretch(2,3)
1611
1612         sign_signature = QTextEdit()
1613         layout.addWidget(QLabel(_('Signature')), 3, 0)
1614         layout.addWidget(sign_signature, 3, 1)
1615         layout.setRowStretch(3,1)
1616
1617
1618         hbox = QHBoxLayout()
1619         b = QPushButton(_("Sign"))
1620         hbox.addWidget(b)
1621         b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
1622         b = QPushButton(_("Close"))
1623         b.clicked.connect(d.accept)
1624         hbox.addWidget(b)
1625         layout.addLayout(hbox, 4, 1)
1626         tab_widget.addTab(tab, _("Sign"))
1627
1628
1629         tab = QWidget()
1630         layout = QGridLayout(tab)
1631
1632         verify_address = QLineEdit()
1633         layout.addWidget(QLabel(_('Address')), 1, 0)
1634         layout.addWidget(verify_address, 1, 1)
1635
1636         verify_message = QTextEdit()
1637         layout.addWidget(QLabel(_('Message')), 2, 0)
1638         layout.addWidget(verify_message, 2, 1)
1639         layout.setRowStretch(2,3)
1640
1641         verify_signature = QTextEdit()
1642         layout.addWidget(QLabel(_('Signature')), 3, 0)
1643         layout.addWidget(verify_signature, 3, 1)
1644         layout.setRowStretch(3,1)
1645
1646         def do_verify():
1647             message = unicode(verify_message.toPlainText())
1648             message = message.encode('utf-8')
1649             if self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), message):
1650                 self.show_message(_("Signature verified"))
1651             else:
1652                 self.show_message(_("Error: wrong signature"))
1653
1654         hbox = QHBoxLayout()
1655         b = QPushButton(_("Verify"))
1656         b.clicked.connect(do_verify)
1657         hbox.addWidget(b)
1658         b = QPushButton(_("Close"))
1659         b.clicked.connect(d.accept)
1660         hbox.addWidget(b)
1661         layout.addLayout(hbox, 4, 1)
1662         tab_widget.addTab(tab, _("Verify"))
1663
1664         vbox = QVBoxLayout()
1665         vbox.addWidget(tab_widget)
1666         d.setLayout(vbox)
1667         d.exec_()
1668
1669         
1670
1671
1672     def question(self, msg):
1673         return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
1674
1675     def show_message(self, msg):
1676         QMessageBox.information(self, _('Message'), msg, _('OK'))
1677
1678     def password_dialog(self ):
1679         d = QDialog(self)
1680         d.setModal(1)
1681
1682         pw = QLineEdit()
1683         pw.setEchoMode(2)
1684
1685         vbox = QVBoxLayout()
1686         msg = _('Please enter your password')
1687         vbox.addWidget(QLabel(msg))
1688
1689         grid = QGridLayout()
1690         grid.setSpacing(8)
1691         grid.addWidget(QLabel(_('Password')), 1, 0)
1692         grid.addWidget(pw, 1, 1)
1693         vbox.addLayout(grid)
1694
1695         vbox.addLayout(ok_cancel_buttons(d))
1696         d.setLayout(vbox)
1697
1698         run_hook('password_dialog', pw, grid, 1)
1699         if not d.exec_(): return
1700         return unicode(pw.text())
1701
1702
1703
1704
1705
1706
1707
1708
1709     def tx_from_text(self, txt):
1710         "json or raw hexadecimal"
1711         try:
1712             txt.decode('hex')
1713             tx = Transaction(txt)
1714             return tx
1715         except:
1716             pass
1717
1718         try:
1719             tx_dict = json.loads(str(txt))
1720             assert "hex" in tx_dict.keys()
1721             assert "complete" in tx_dict.keys()
1722             tx = Transaction(tx_dict["hex"], tx_dict["complete"])
1723             if not tx_dict["complete"]:
1724                 assert "input_info" in tx_dict.keys()
1725                 input_info = json.loads(tx_dict['input_info'])
1726                 tx.add_input_info(input_info)
1727             return tx
1728         except:
1729             pass
1730         
1731         QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
1732
1733
1734
1735     def read_tx_from_file(self):
1736         fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
1737         if not fileName:
1738             return
1739         try:
1740             with open(fileName, "r") as f:
1741                 file_content = f.read()
1742         except (ValueError, IOError, os.error), reason:
1743             QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1744
1745         return self.tx_from_text(file_content)
1746
1747
1748     @protected
1749     def sign_raw_transaction(self, tx, input_info, password):
1750         self.wallet.signrawtransaction(tx, input_info, [], password)
1751
1752     def do_process_from_text(self):
1753         text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
1754         if not text:
1755             return
1756         tx = self.tx_from_text(text)
1757         if tx:
1758             self.show_transaction(tx)
1759
1760     def do_process_from_file(self):
1761         tx = self.read_tx_from_file()
1762         if tx:
1763             self.show_transaction(tx)
1764
1765     def do_process_from_csvReader(self, csvReader):
1766         outputs = []
1767         try:
1768             for row in csvReader:
1769                 address = row[0]
1770                 amount = float(row[1])
1771                 amount = int(100000000*amount)
1772                 outputs.append((address, amount))
1773         except (ValueError, IOError, os.error), reason:
1774             QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1775             return
1776
1777         try:
1778             tx = self.wallet.make_unsigned_transaction(outputs, None, None)
1779         except BaseException, e:
1780             self.show_message(str(e))
1781             return
1782
1783         self.show_transaction(tx)
1784
1785     def do_process_from_csv_file(self):
1786         fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
1787         if not fileName:
1788             return
1789         try:
1790             with open(fileName, "r") as f:
1791                 csvReader = csv.reader(f)
1792                 self.do_process_from_csvReader(csvReader)
1793         except (ValueError, IOError, os.error), reason:
1794             QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
1795             return
1796
1797     def do_process_from_csv_text(self):
1798         text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
1799                                + _("Format: address, amount. One output per line"), _("Load CSV"))
1800         if not text:
1801             return
1802         f = StringIO.StringIO(text)
1803         csvReader = csv.reader(f)
1804         self.do_process_from_csvReader(csvReader)
1805
1806
1807
1808     @protected
1809     def do_export_privkeys(self, password):
1810         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.")))
1811
1812         try:
1813             select_export = _('Select file to export your private keys to')
1814             fileName = self.getSaveFileName(select_export, 'electrum-private-keys.csv', "*.csv")
1815             if fileName:
1816                 with open(fileName, "w+") as csvfile:
1817                     transaction = csv.writer(csvfile)
1818                     transaction.writerow(["address", "private_key"])
1819
1820                     addresses = self.wallet.addresses(True)
1821                     
1822                     for addr in addresses:
1823                         pk = "".join(self.wallet.get_private_key(addr, password))
1824                         transaction.writerow(["%34s"%addr,pk])
1825
1826                     self.show_message(_("Private keys exported."))
1827
1828         except (IOError, os.error), reason:
1829             export_error_label = _("Electrum was unable to produce a private key-export.")
1830             QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
1831
1832         except BaseException, e:
1833           self.show_message(str(e))
1834           return
1835
1836
1837     def do_import_labels(self):
1838         labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
1839         if not labelsFile: return
1840         try:
1841             f = open(labelsFile, 'r')
1842             data = f.read()
1843             f.close()
1844             for key, value in json.loads(data).items():
1845                 self.wallet.set_label(key, value)
1846             QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
1847         except (IOError, os.error), reason:
1848             QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
1849             
1850
1851     def do_export_labels(self):
1852         labels = self.wallet.labels
1853         try:
1854             fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
1855             if fileName:
1856                 with open(fileName, 'w+') as f:
1857                     json.dump(labels, f)
1858                 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
1859         except (IOError, os.error), reason:
1860             QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
1861
1862
1863     def do_export_history(self):
1864         from lite_window import csv_transaction
1865         csv_transaction(self.wallet)
1866
1867
1868     @protected
1869     def do_import_privkey(self, password):
1870         if not self.wallet.imported_keys:
1871             r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
1872                                          + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
1873                                          + _('Are you sure you understand what you are doing?'), 3, 4)
1874             if r == 4: return
1875
1876         text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
1877         if not text: return
1878
1879         text = str(text).split()
1880         badkeys = []
1881         addrlist = []
1882         for key in text:
1883             try:
1884                 addr = self.wallet.import_key(key, password)
1885             except BaseException as e:
1886                 badkeys.append(key)
1887                 continue
1888             if not addr: 
1889                 badkeys.append(key)
1890             else:
1891                 addrlist.append(addr)
1892         if addrlist:
1893             QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
1894         if badkeys:
1895             QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
1896         self.update_receive_tab()
1897         self.update_history_tab()
1898
1899
1900     def settings_dialog(self):
1901         d = QDialog(self)
1902         d.setWindowTitle(_('Electrum Settings'))
1903         d.setModal(1)
1904         vbox = QVBoxLayout()
1905
1906         tabs = QTabWidget(self)
1907         self.settings_tab = tabs
1908         vbox.addWidget(tabs)
1909
1910         tab1 = QWidget()
1911         grid_ui = QGridLayout(tab1)
1912         grid_ui.setColumnStretch(0,1)
1913         tabs.addTab(tab1, _('Display') )
1914
1915         nz_label = QLabel(_('Display zeros'))
1916         grid_ui.addWidget(nz_label, 0, 0)
1917         nz_e = AmountEdit(None,True)
1918         nz_e.setText("%d"% self.num_zeros)
1919         grid_ui.addWidget(nz_e, 0, 1)
1920         msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
1921         grid_ui.addWidget(HelpButton(msg), 0, 2)
1922         if not self.config.is_modifiable('num_zeros'):
1923             for w in [nz_e, nz_label]: w.setEnabled(False)
1924         
1925         lang_label=QLabel(_('Language') + ':')
1926         grid_ui.addWidget(lang_label, 1, 0)
1927         lang_combo = QComboBox()
1928         from electrum.i18n import languages
1929         lang_combo.addItems(languages.values())
1930         try:
1931             index = languages.keys().index(self.config.get("language",''))
1932         except:
1933             index = 0
1934         lang_combo.setCurrentIndex(index)
1935         grid_ui.addWidget(lang_combo, 1, 1)
1936         grid_ui.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
1937         if not self.config.is_modifiable('language'):
1938             for w in [lang_combo, lang_label]: w.setEnabled(False)
1939
1940         expert_cb = QCheckBox(_('Expert mode'))
1941         expert_cb.setChecked(self.expert_mode)
1942         grid_ui.addWidget(expert_cb, 3, 0)
1943         hh =  _('In expert mode, your client will:') + '\n'  \
1944             + _(' - Show change addresses in the Receive tab') + '\n'  \
1945             + _(' - Display the balance of each address') + '\n'  \
1946             + _(' - Add freeze/prioritize actions to addresses.') 
1947         grid_ui.addWidget(HelpButton(hh), 3, 2)
1948         grid_ui.setRowStretch(4,1)
1949
1950         # wallet tab
1951         tab2 = QWidget()
1952         grid_wallet = QGridLayout(tab2)
1953         grid_wallet.setColumnStretch(0,1)
1954         tabs.addTab(tab2, _('Wallet') )
1955         
1956         fee_label = QLabel(_('Transaction fee'))
1957         grid_wallet.addWidget(fee_label, 0, 0)
1958         fee_e = AmountEdit(self.base_unit)
1959         fee_e.setText(self.format_amount(self.wallet.fee).strip())
1960         grid_wallet.addWidget(fee_e, 0, 2)
1961         msg = _('Fee per kilobyte of transaction.') + ' ' \
1962             + _('Recommended value') + ': ' + self.format_amount(50000)
1963         grid_wallet.addWidget(HelpButton(msg), 0, 3)
1964         if not self.config.is_modifiable('fee_per_kb'):
1965             for w in [fee_e, fee_label]: w.setEnabled(False)
1966
1967         usechange_cb = QCheckBox(_('Use change addresses'))
1968         usechange_cb.setChecked(self.wallet.use_change)
1969         grid_wallet.addWidget(usechange_cb, 1, 0)
1970         grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 1, 3)
1971         if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
1972
1973         units = ['BTC', 'mBTC']
1974         unit_label = QLabel(_('Base unit'))
1975         grid_wallet.addWidget(unit_label, 3, 0)
1976         unit_combo = QComboBox()
1977         unit_combo.addItems(units)
1978         unit_combo.setCurrentIndex(units.index(self.base_unit()))
1979         grid_wallet.addWidget(unit_combo, 3, 2)
1980         grid_wallet.addWidget(HelpButton(_('Base unit of your wallet.')\
1981                                              + '\n1BTC=1000mBTC.\n' \
1982                                              + _(' This settings affects the fields in the Send tab')+' '), 3, 3)
1983         grid_wallet.setRowStretch(4,1)
1984
1985
1986         run_hook('create_settings_tab', tabs)
1987
1988         vbox.addLayout(ok_cancel_buttons(d))
1989         d.setLayout(vbox) 
1990
1991         # run the dialog
1992         if not d.exec_(): return
1993
1994         fee = unicode(fee_e.text())
1995         try:
1996             fee = self.read_amount(fee)
1997         except:
1998             QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
1999             return
2000
2001         self.wallet.set_fee(fee)
2002         
2003         nz = unicode(nz_e.text())
2004         try:
2005             nz = int( nz )
2006             if nz>8: nz=8
2007         except:
2008             QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2009             return
2010
2011         if self.num_zeros != nz:
2012             self.num_zeros = nz
2013             self.config.set_key('num_zeros', nz, True)
2014             self.update_history_tab()
2015             self.update_receive_tab()
2016
2017         usechange_result = usechange_cb.isChecked()
2018         if self.wallet.use_change != usechange_result:
2019             self.wallet.use_change = usechange_result
2020             self.config.set_key('use_change', self.wallet.use_change, True)
2021         
2022         unit_result = units[unit_combo.currentIndex()]
2023         if self.base_unit() != unit_result:
2024             self.decimal_point = 8 if unit_result == 'BTC' else 5
2025             self.config.set_key('decimal_point', self.decimal_point, True)
2026             self.update_history_tab()
2027             self.update_status()
2028         
2029         need_restart = False
2030
2031         lang_request = languages.keys()[lang_combo.currentIndex()]
2032         if lang_request != self.config.get('language'):
2033             self.config.set_key("language", lang_request, True)
2034             need_restart = True
2035             
2036
2037         run_hook('close_settings_dialog')
2038
2039         if need_restart:
2040             QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2041
2042         self.receive_tab_set_mode(expert_cb.isChecked())
2043
2044     def run_network_dialog(self):
2045         NetworkDialog(self.wallet.network, self.config, self).do_exec()
2046
2047     def closeEvent(self, event):
2048         g = self.geometry()
2049         self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
2050         self.save_column_widths()
2051         self.config.set_key("console-history", self.console.history[-50:], True)
2052         event.accept()
2053
2054
2055
2056     def plugins_dialog(self):
2057         from electrum.plugins import plugins
2058
2059         d = QDialog(self)
2060         d.setWindowTitle(_('Electrum Plugins'))
2061         d.setModal(1)
2062
2063         vbox = QVBoxLayout(d)
2064
2065         # plugins
2066         scroll = QScrollArea()
2067         scroll.setEnabled(True)
2068         scroll.setWidgetResizable(True)
2069         scroll.setMinimumSize(400,250)
2070         vbox.addWidget(scroll)
2071
2072         w = QWidget()
2073         scroll.setWidget(w)
2074         w.setMinimumHeight(len(plugins)*35)
2075
2076         grid = QGridLayout()
2077         grid.setColumnStretch(0,1)
2078         w.setLayout(grid)
2079
2080         def mk_toggle(cb, p):
2081             return lambda: cb.setChecked(p.toggle())
2082         for i, p in enumerate(plugins):
2083             try:
2084                 cb = QCheckBox(p.fullname())
2085                 cb.setDisabled(not p.is_available())
2086                 cb.setChecked(p.is_enabled())
2087                 cb.clicked.connect(mk_toggle(cb,p))
2088                 grid.addWidget(cb, i, 0)
2089                 if p.requires_settings():
2090                     b = EnterButton(_('Settings'), p.settings_dialog)
2091                     b.setEnabled( p.is_enabled() )
2092                     grid.addWidget(b, i, 1)
2093                 grid.addWidget(HelpButton(p.description()), i, 2)
2094             except:
2095                 print_msg(_("Error: cannot display plugin"), p)
2096                 traceback.print_exc(file=sys.stdout)
2097         grid.setRowStretch(i+1,1)
2098
2099         vbox.addLayout(close_button(d))
2100
2101         d.exec_()
2102
2103
2104     def show_account_details(self, k):
2105         d = QDialog(self)
2106         d.setWindowTitle(_('Account Details'))
2107         d.setModal(1)
2108
2109         vbox = QVBoxLayout(d)
2110         roots = self.wallet.get_roots(k)
2111
2112         name = self.wallet.get_account_name(k)
2113         label = QLabel('Name: ' + name)
2114         vbox.addWidget(label)
2115
2116         acctype = '2 of 2' if len(roots) == 2 else '2 of 3' if len(roots) == 3 else 'Single key'
2117         vbox.addWidget(QLabel('Type: ' + acctype))
2118
2119         label = QLabel('Derivation: ' + k)
2120         vbox.addWidget(label)
2121
2122         #for root in roots:
2123         #    mpk = self.wallet.master_public_keys[root]
2124         #    text = QTextEdit()
2125         #    text.setReadOnly(True)
2126         #    text.setMaximumHeight(120)
2127         #    text.setText(repr(mpk))
2128         #    vbox.addWidget(text)
2129
2130         vbox.addLayout(close_button(d))
2131         d.exec_()