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