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