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