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