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