Initial novacoin support
[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_nvc.i18n import _, set_language
21 from electrum_nvc.util import print_error, print_msg
22 import os.path, json, ast, traceback
23 import webbrowser
24 import shutil
25 import StringIO
26
27
28 import PyQt4
29 from PyQt4.QtGui import *
30 from PyQt4.QtCore import *
31 import PyQt4.QtCore as QtCore
32
33 from electrum_nvc.bitcoin import MIN_RELAY_TX_FEE, is_valid
34 from electrum_nvc.plugins import run_hook
35
36 import icons_rc
37
38 from electrum_nvc.util import format_satoshis
39 from electrum_nvc import Transaction
40 from electrum_nvc import mnemonic
41 from electrum_nvc import util, bitcoin, commands, Interface, Wallet
42 from electrum_nvc import SimpleConfig, Wallet, WalletStorage
43 from electrum_nvc import Imported_Wallet
44
45 from amountedit import AmountEdit, BTCAmountEdit, MyLineEdit
46 from network_dialog import NetworkDialog
47 from qrcodewidget import QRCodeWidget, QRDialog
48 from qrtextedit import QRTextEdit
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
66
67 # status of payment requests
68 PR_UNPAID  = 0
69 PR_EXPIRED = 1
70 PR_SENT    = 2     # sent but not propagated
71 PR_PAID    = 3     # send and propagated
72 PR_ERROR   = 4     # could not parse
73
74
75 from electrum_nvc import ELECTRUM_VERSION
76 import re
77
78 from util import MyTreeWidget, HelpButton, EnterButton, line_dialog, text_dialog, ok_cancel_buttons, close_button, WaitingDialog
79
80
81 def format_status(x):
82     if x == PR_UNPAID:
83         return _('Unpaid')
84     elif x == PR_PAID:
85         return _('Paid')
86     elif x == PR_EXPIRED:
87         return _('Expired')
88
89
90 class StatusBarButton(QPushButton):
91     def __init__(self, icon, tooltip, func):
92         QPushButton.__init__(self, icon, '')
93         self.setToolTip(tooltip)
94         self.setFlat(True)
95         self.setMaximumWidth(25)
96         self.clicked.connect(func)
97         self.func = func
98         self.setIconSize(QSize(25,25))
99
100     def keyPressEvent(self, e):
101         if e.key() == QtCore.Qt.Key_Return:
102             apply(self.func,())
103
104
105
106
107
108
109
110
111
112
113 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], "receive": [370,200,130] }
114
115 class ElectrumWindow(QMainWindow):
116
117
118
119     def __init__(self, config, network, gui_object):
120         QMainWindow.__init__(self)
121
122         self.config = config
123         self.network = network
124         self.gui_object = gui_object
125         self.tray = gui_object.tray
126         self.go_lite = gui_object.go_lite
127         self.lite = None
128
129         self.create_status_bar()
130         self.need_update = threading.Event()
131
132         self.decimal_point = config.get('decimal_point', 6)
133         self.num_zeros     = int(config.get('num_zeros',0))
134         self.invoices      = {}
135
136         set_language(config.get('language'))
137
138         self.completions = QStringListModel()
139
140         self.tabs = tabs = QTabWidget(self)
141         self.column_widths = self.config.get("column_widths_2", default_column_widths )
142         tabs.addTab(self.create_history_tab(), _('History') )
143         tabs.addTab(self.create_send_tab(), _('Send') )
144         tabs.addTab(self.create_receive_tab(), _('Receive') )
145         tabs.addTab(self.create_addresses_tab(), _('Addresses') )
146         tabs.addTab(self.create_contacts_tab(), _('Contacts') )
147         tabs.addTab(self.create_invoices_tab(), _('Invoices') )
148         tabs.addTab(self.create_console_tab(), _('Console') )
149         tabs.setMinimumSize(600, 400)
150         tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
151         self.setCentralWidget(tabs)
152
153         g = self.config.get("winpos-qt",[100, 100, 840, 400])
154         self.setGeometry(g[0], g[1], g[2], g[3])
155         if self.config.get("is_maximized"):
156             self.showMaximized()
157
158         self.setWindowIcon(QIcon(":icons/electrum.png"))
159         self.init_menubar()
160
161         QShortcut(QKeySequence("Ctrl+W"), self, self.close)
162         QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
163         QShortcut(QKeySequence("Ctrl+R"), self, self.update_wallet)
164         QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
165         QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
166
167         for i in range(tabs.count()):
168             QShortcut(QKeySequence("Alt+" + str(i + 1)), self, lambda i=i: tabs.setCurrentIndex(i))
169
170         self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
171         self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.network.banner) )
172         self.connect(self, QtCore.SIGNAL('transaction_signal'), lambda: self.notify_transactions() )
173         self.connect(self, QtCore.SIGNAL('payment_request_ok'), self.payment_request_ok)
174         self.connect(self, QtCore.SIGNAL('payment_request_error'), self.payment_request_error)
175
176         self.history_list.setFocus(True)
177
178         # network callbacks
179         if self.network:
180             self.network.register_callback('updated', lambda: self.need_update.set())
181             self.network.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
182             self.network.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
183             self.network.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
184             self.network.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
185
186             # set initial message
187             self.console.showMessage(self.network.banner)
188
189         self.wallet = None
190         self.payment_request = None
191
192     def update_account_selector(self):
193         # account selector
194         accounts = self.wallet.get_account_names()
195         self.account_selector.clear()
196         if len(accounts) > 1:
197             self.account_selector.addItems([_("All accounts")] + accounts.values())
198             self.account_selector.setCurrentIndex(0)
199             self.account_selector.show()
200         else:
201             self.account_selector.hide()
202
203
204     def load_wallet(self, wallet):
205         import electrum_nvc
206
207         self.wallet = wallet
208         self.update_wallet_format()
209
210         self.invoices = self.wallet.storage.get('invoices', {})
211         self.accounts_expanded = self.wallet.storage.get('accounts_expanded',{})
212         self.current_account = self.wallet.storage.get("current_account", None)
213         title = 'Electrum ' + self.wallet.electrum_version + '  -  ' + self.wallet.storage.path
214         if self.wallet.is_watching_only(): title += ' [%s]' % (_('watching only'))
215         self.setWindowTitle( title )
216         self.update_wallet()
217         # Once GUI has been initialized check if we want to announce something since the callback has been called before the GUI was initialized
218         self.notify_transactions()
219         self.update_account_selector()
220         # update menus
221         self.new_account_menu.setEnabled(self.wallet.can_create_accounts())
222         self.private_keys_menu.setEnabled(not self.wallet.is_watching_only())
223         self.password_menu.setEnabled(not self.wallet.is_watching_only())
224         self.seed_menu.setEnabled(self.wallet.has_seed())
225         self.mpk_menu.setEnabled(self.wallet.is_deterministic())
226         self.import_menu.setEnabled(self.wallet.can_import())
227
228         self.update_lock_icon()
229         self.update_buttons_on_seed()
230         self.update_console()
231
232         self.clear_receive_tab()
233         self.update_receive_tab()
234         run_hook('load_wallet', wallet)
235
236
237     def update_wallet_format(self):
238         # convert old-format imported keys
239         if self.wallet.imported_keys:
240             password = self.password_dialog(_("Please enter your password in order to update imported keys"))
241             try:
242                 self.wallet.convert_imported_keys(password)
243             except:
244                 self.show_message("error")
245
246
247     def open_wallet(self):
248         wallet_folder = self.wallet.storage.path
249         filename = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) )
250         if not filename:
251             return
252
253         storage = WalletStorage({'wallet_path': filename})
254         if not storage.file_exists:
255             self.show_message("file not found "+ filename)
256             return
257
258         self.wallet.stop_threads()
259
260         # create new wallet
261         wallet = Wallet(storage)
262         wallet.start_threads(self.network)
263
264         self.load_wallet(wallet)
265
266
267
268     def backup_wallet(self):
269         import shutil
270         path = self.wallet.storage.path
271         wallet_folder = os.path.dirname(path)
272         filename = unicode( QFileDialog.getSaveFileName(self, _('Enter a filename for the copy of your wallet'), wallet_folder) )
273         if not filename:
274             return
275
276         new_path = os.path.join(wallet_folder, filename)
277         if new_path != path:
278             try:
279                 shutil.copy2(path, new_path)
280                 QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
281             except (IOError, os.error), reason:
282                 QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
283
284
285     def new_wallet(self):
286         import installwizard
287
288         wallet_folder = os.path.dirname(self.wallet.storage.path)
289         i = 1
290         while True:
291             filename = "wallet_%d"%i
292             if filename in os.listdir(wallet_folder):
293                 i += 1
294             else:
295                 break
296
297         filename = line_dialog(self, _('New Wallet'), _('Enter file name') + ':', _('OK'), filename)
298         if not filename:
299             return
300
301         full_path = os.path.join(wallet_folder, filename)
302         storage = WalletStorage({'wallet_path': full_path})
303         if storage.file_exists:
304             QMessageBox.critical(None, "Error", _("File exists"))
305             return
306
307         wizard = installwizard.InstallWizard(self.config, self.network, storage)
308         wallet = wizard.run('new')
309         if wallet:
310             self.load_wallet(wallet)
311
312
313
314     def init_menubar(self):
315         menubar = QMenuBar()
316
317         file_menu = menubar.addMenu(_("&File"))
318         file_menu.addAction(_("&Open"), self.open_wallet).setShortcut(QKeySequence.Open)
319         file_menu.addAction(_("&New/Restore"), self.new_wallet).setShortcut(QKeySequence.New)
320         file_menu.addAction(_("&Save Copy"), self.backup_wallet).setShortcut(QKeySequence.SaveAs)
321         file_menu.addAction(_("&Quit"), self.close)
322
323         wallet_menu = menubar.addMenu(_("&Wallet"))
324         wallet_menu.addAction(_("&New contact"), self.new_contact_dialog)
325         self.new_account_menu = wallet_menu.addAction(_("&New account"), self.new_account_dialog)
326
327         wallet_menu.addSeparator()
328
329         self.password_menu = wallet_menu.addAction(_("&Password"), self.change_password_dialog)
330         self.seed_menu = wallet_menu.addAction(_("&Seed"), self.show_seed_dialog)
331         self.mpk_menu = wallet_menu.addAction(_("&Master Public Keys"), self.show_master_public_keys)
332
333         wallet_menu.addSeparator()
334         labels_menu = wallet_menu.addMenu(_("&Labels"))
335         labels_menu.addAction(_("&Import"), self.do_import_labels)
336         labels_menu.addAction(_("&Export"), self.do_export_labels)
337
338         self.private_keys_menu = wallet_menu.addMenu(_("&Private keys"))
339         self.private_keys_menu.addAction(_("&Sweep"), self.sweep_key_dialog)
340         self.import_menu = self.private_keys_menu.addAction(_("&Import"), self.do_import_privkey)
341         self.private_keys_menu.addAction(_("&Export"), self.export_privkeys_dialog)
342         wallet_menu.addAction(_("&Export History"), self.export_history_dialog)
343
344         tools_menu = menubar.addMenu(_("&Tools"))
345
346         # Settings / Preferences are all reserved keywords in OSX using this as work around
347         tools_menu.addAction(_("Electrum preferences") if sys.platform == 'darwin' else _("Preferences"), self.settings_dialog)
348         tools_menu.addAction(_("&Network"), self.run_network_dialog)
349         tools_menu.addAction(_("&Plugins"), self.plugins_dialog)
350         tools_menu.addSeparator()
351         tools_menu.addAction(_("&Sign/verify message"), self.sign_verify_message)
352         tools_menu.addAction(_("&Encrypt/decrypt message"), self.encrypt_message)
353         tools_menu.addSeparator()
354
355         csv_transaction_menu = tools_menu.addMenu(_("&Create transaction"))
356         csv_transaction_menu.addAction(_("&From CSV file"), self.do_process_from_csv_file)
357         csv_transaction_menu.addAction(_("&From CSV text"), self.do_process_from_csv_text)
358
359         raw_transaction_menu = tools_menu.addMenu(_("&Load transaction"))
360         raw_transaction_menu.addAction(_("&From file"), self.do_process_from_file)
361         raw_transaction_menu.addAction(_("&From text"), self.do_process_from_text)
362         raw_transaction_menu.addAction(_("&From the blockchain"), self.do_process_from_txid)
363         raw_transaction_menu.addAction(_("&From QR code"), self.read_tx_from_qrcode)
364         self.raw_transaction_menu = raw_transaction_menu
365
366         help_menu = menubar.addMenu(_("&Help"))
367         help_menu.addAction(_("&About"), self.show_about)
368         help_menu.addAction(_("&Official website"), lambda: webbrowser.open("http://electrum.org"))
369         help_menu.addSeparator()
370         help_menu.addAction(_("&Documentation"), lambda: webbrowser.open("http://electrum.org/documentation.html")).setShortcut(QKeySequence.HelpContents)
371         help_menu.addAction(_("&Report Bug"), self.show_report_bug)
372
373         self.setMenuBar(menubar)
374
375     def show_about(self):
376         QMessageBox.about(self, "Electrum-NVC",
377             _("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 Novacoin system."))
378
379     def show_report_bug(self):
380         QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"),
381             _("Please report any bugs as issues on github:")+" <a href=\"https://github.com/spesmilo/electrum/issues\">https://github.com/spesmilo/electrum/issues</a>")
382
383
384     def notify_transactions(self):
385         if not self.network or not self.network.is_connected():
386             return
387
388         print_error("Notifying GUI")
389         if len(self.network.pending_transactions_for_notifications) > 0:
390             # Combine the transactions if there are more then three
391             tx_amount = len(self.network.pending_transactions_for_notifications)
392             if(tx_amount >= 3):
393                 total_amount = 0
394                 for tx in self.network.pending_transactions_for_notifications:
395                     is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
396                     if(v > 0):
397                         total_amount += v
398
399                 self.notify(_("%(txs)s new transactions received. Total amount received in the new transactions %(amount)s %(unit)s") \
400                                 % { 'txs' : tx_amount, 'amount' : self.format_amount(total_amount), 'unit' : self.base_unit()})
401
402                 self.network.pending_transactions_for_notifications = []
403             else:
404               for tx in self.network.pending_transactions_for_notifications:
405                   if tx:
406                       self.network.pending_transactions_for_notifications.remove(tx)
407                       is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
408                       if(v > 0):
409                           self.notify(_("New transaction received. %(amount)s %(unit)s") % { 'amount' : self.format_amount(v), 'unit' : self.base_unit()})
410
411     def notify(self, message):
412         self.tray.showMessage("Electrum-NVC", message, QSystemTrayIcon.Information, 20000)
413
414
415
416     # custom wrappers for getOpenFileName and getSaveFileName, that remember the path selected by the user
417     def getOpenFileName(self, title, filter = ""):
418         directory = self.config.get('io_dir', unicode(os.path.expanduser('~')))
419         fileName = unicode( QFileDialog.getOpenFileName(self, title, directory, filter) )
420         if fileName and directory != os.path.dirname(fileName):
421             self.config.set_key('io_dir', os.path.dirname(fileName), True)
422         return fileName
423
424     def getSaveFileName(self, title, filename, filter = ""):
425         directory = self.config.get('io_dir', unicode(os.path.expanduser('~')))
426         path = os.path.join( directory, filename )
427         fileName = unicode( QFileDialog.getSaveFileName(self, title, path, filter) )
428         if fileName and directory != os.path.dirname(fileName):
429             self.config.set_key('io_dir', os.path.dirname(fileName), True)
430         return fileName
431
432     def close(self):
433         QMainWindow.close(self)
434         run_hook('close_main_window')
435
436     def connect_slots(self, sender):
437         self.connect(sender, QtCore.SIGNAL('timersignal'), self.timer_actions)
438         self.previous_payto_e=''
439
440     def timer_actions(self):
441         if self.need_update.is_set():
442             self.update_wallet()
443             self.need_update.clear()
444
445         run_hook('timer_actions')
446
447     def format_amount(self, x, is_diff=False, whitespaces=False):
448         return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
449
450
451     def get_decimal_point(self):
452         return self.decimal_point
453
454
455     def base_unit(self):
456         assert self.decimal_point in [2, 3, 6]
457         if self.decimal_point == 2:
458             return 'bits'
459         if self.decimal_point == 3:
460             return 'mNVC'
461         if self.decimal_point == 6:
462             return 'NVC'
463         raise Exception('Unknown base unit')
464
465     def update_status(self):
466         if self.network is None or not self.network.is_running():
467             text = _("Offline")
468             icon = QIcon(":icons/status_disconnected.png")
469
470         elif self.network.is_connected():
471             if not self.wallet.up_to_date:
472                 text = _("Synchronizing...")
473                 icon = QIcon(":icons/status_waiting.png")
474             elif self.network.server_lag > 1:
475                 text = _("Server is lagging (%d blocks)"%self.network.server_lag)
476                 icon = QIcon(":icons/status_lagging.png")
477             else:
478                 c, u = self.wallet.get_account_balance(self.current_account)
479                 text =  _( "Balance" ) + ": %s "%( self.format_amount(c) ) + self.base_unit()
480                 if u: text +=  " [%s unconfirmed]"%( self.format_amount(u,True).strip() )
481
482                 # append fiat balance and price from exchange rate plugin
483                 r = {}
484                 run_hook('get_fiat_status_text', c+u, r)
485                 quote = r.get(0)
486                 if quote:
487                     text += "%s"%quote
488
489                 self.tray.setToolTip(text)
490                 icon = QIcon(":icons/status_connected.png")
491         else:
492             text = _("Not connected")
493             icon = QIcon(":icons/status_disconnected.png")
494
495         self.balance_label.setText(text)
496         self.status_button.setIcon( icon )
497
498
499     def update_wallet(self):
500         self.update_status()
501         if self.wallet.up_to_date or not self.network or not self.network.is_connected():
502             self.update_history_tab()
503             self.update_receive_tab()
504             self.update_address_tab()
505             self.update_contacts_tab()
506             self.update_completions()
507             self.update_invoices_tab()
508
509
510     def create_history_tab(self):
511         self.history_list = l = MyTreeWidget(self)
512         l.setColumnCount(5)
513         for i,width in enumerate(self.column_widths['history']):
514             l.setColumnWidth(i, width)
515         l.setHeaderLabels( [ '', _('Date'), _('Description') , _('Amount'), _('Balance')] )
516         l.itemDoubleClicked.connect(self.tx_label_clicked)
517         l.itemChanged.connect(self.tx_label_changed)
518         l.customContextMenuRequested.connect(self.create_history_menu)
519         return l
520
521
522     def create_history_menu(self, position):
523         self.history_list.selectedIndexes()
524         item = self.history_list.currentItem()
525         be = self.config.get('block_explorer', 'explorer.novaco.in')
526         if be == 'explorer.novaco.in':
527             block_explorer = 'https://explorer.novaco.in/tx/'
528         elif be == 'novacoin.su':
529             block_explorer = 'http://novacoin.su/tx/'
530         if not item: return
531
532         tx_hash = str(item.data(0, Qt.UserRole).toString())
533         if not tx_hash: return
534         menu = QMenu()
535         menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
536         menu.addAction(_("Details"), lambda: self.show_transaction(self.wallet.transactions.get(tx_hash)))
537         menu.addAction(_("Edit description"), lambda: self.tx_label_clicked(item,2))
538         menu.addAction(_("View on explorer"), lambda: webbrowser.open(block_explorer + tx_hash))
539         menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
540
541
542     def show_transaction(self, tx):
543         import transaction_dialog
544         d = transaction_dialog.TxDialog(tx, self)
545         d.exec_()
546
547     def tx_label_clicked(self, item, column):
548         if column==2 and item.isSelected():
549             self.is_edit=True
550             item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
551             self.history_list.editItem( item, column )
552             item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
553             self.is_edit=False
554
555     def tx_label_changed(self, item, column):
556         if self.is_edit:
557             return
558         self.is_edit=True
559         tx_hash = str(item.data(0, Qt.UserRole).toString())
560         tx = self.wallet.transactions.get(tx_hash)
561         text = unicode( item.text(2) )
562         self.wallet.set_label(tx_hash, text)
563         if text:
564             item.setForeground(2, QBrush(QColor('black')))
565         else:
566             text = self.wallet.get_default_label(tx_hash)
567             item.setText(2, text)
568             item.setForeground(2, QBrush(QColor('gray')))
569         self.is_edit=False
570
571
572     def edit_label(self, is_recv):
573         l = self.address_list if is_recv else self.contacts_list
574         item = l.currentItem()
575         item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
576         l.editItem( item, 1 )
577         item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
578
579
580
581     def address_label_clicked(self, item, column, l, column_addr, column_label):
582         if column == column_label and item.isSelected():
583             is_editable = item.data(0, 32).toBool()
584             if not is_editable:
585                 return
586             addr = unicode( item.text(column_addr) )
587             label = unicode( item.text(column_label) )
588             item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
589             l.editItem( item, column )
590             item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
591
592
593     def address_label_changed(self, item, column, l, column_addr, column_label):
594         if column == column_label:
595             addr = unicode( item.text(column_addr) )
596             text = unicode( item.text(column_label) )
597             is_editable = item.data(0, 32).toBool()
598             if not is_editable:
599                 return
600
601             changed = self.wallet.set_label(addr, text)
602             if changed:
603                 self.update_history_tab()
604                 self.update_completions()
605
606             self.current_item_changed(item)
607
608         run_hook('item_changed', item, column)
609
610
611     def current_item_changed(self, a):
612         run_hook('current_item_changed', a)
613
614
615
616     def update_history_tab(self):
617
618         self.history_list.clear()
619         for item in self.wallet.get_tx_history(self.current_account):
620             tx_hash, conf, is_mine, value, fee, balance, timestamp = item
621             time_str = _("unknown")
622             if conf > 0:
623                 try:
624                     time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
625                 except Exception:
626                     time_str = _("error")
627
628             if conf == -1:
629                 time_str = 'unverified'
630                 icon = QIcon(":icons/unconfirmed.png")
631             elif conf == 0:
632                 time_str = 'pending'
633                 icon = QIcon(":icons/unconfirmed.png")
634             elif conf < 6:
635                 icon = QIcon(":icons/clock%d.png"%conf)
636             else:
637                 icon = QIcon(":icons/confirmed.png")
638
639             if value is not None:
640                 v_str = self.format_amount(value, True, whitespaces=True)
641             else:
642                 v_str = '--'
643
644             balance_str = self.format_amount(balance, whitespaces=True)
645
646             if tx_hash:
647                 label, is_default_label = self.wallet.get_label(tx_hash)
648             else:
649                 label = _('Pruned transaction outputs')
650                 is_default_label = False
651
652             item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] )
653             item.setFont(2, QFont(MONOSPACE_FONT))
654             item.setFont(3, QFont(MONOSPACE_FONT))
655             item.setFont(4, QFont(MONOSPACE_FONT))
656             if value < 0:
657                 item.setForeground(3, QBrush(QColor("#BC1E1E")))
658             if tx_hash:
659                 item.setData(0, Qt.UserRole, tx_hash)
660                 item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
661             if is_default_label:
662                 item.setForeground(2, QBrush(QColor('grey')))
663
664             item.setIcon(0, icon)
665             self.history_list.insertTopLevelItem(0,item)
666
667
668         self.history_list.setCurrentItem(self.history_list.topLevelItem(0))
669         run_hook('history_tab_update')
670
671
672     def create_receive_tab(self):
673         w = QWidget()
674         grid = QGridLayout(w)
675         grid.setColumnMinimumWidth(3, 300)
676         grid.setColumnStretch(5, 1)
677
678         self.receive_address_e = QLineEdit()
679         self.receive_address_e.setReadOnly(True)
680         grid.addWidget(QLabel(_('Receiving address')), 0, 0)
681         grid.addWidget(self.receive_address_e, 0, 1, 1, 3)
682         self.receive_address_e.textChanged.connect(self.update_receive_qr)
683
684         self.receive_message_e = QLineEdit()
685         grid.addWidget(QLabel(_('Message')), 1, 0)
686         grid.addWidget(self.receive_message_e, 1, 1, 1, 3)
687         self.receive_message_e.textChanged.connect(self.update_receive_qr)
688
689         self.receive_amount_e = BTCAmountEdit(self.get_decimal_point)
690         grid.addWidget(QLabel(_('Requested amount')), 2, 0)
691         grid.addWidget(self.receive_amount_e, 2, 1, 1, 2)
692         self.receive_amount_e.textChanged.connect(self.update_receive_qr)
693
694         self.save_request_button = QPushButton(_('Save'))
695         self.save_request_button.clicked.connect(self.save_payment_request)
696         grid.addWidget(self.save_request_button, 3, 1)
697         clear_button = QPushButton(_('New'))
698         clear_button.clicked.connect(self.new_receive_address)
699         grid.addWidget(clear_button, 3, 2)
700         grid.setRowStretch(4, 1)
701
702         self.receive_qr = QRCodeWidget(fixedSize=200)
703         grid.addWidget(self.receive_qr, 0, 4, 5, 2)
704
705         grid.setRowStretch(5, 1)
706
707         self.receive_requests_label = QLabel(_('Saved Requests'))
708         self.receive_list = MyTreeWidget(self)
709         self.receive_list.customContextMenuRequested.connect(self.receive_list_menu)
710         self.receive_list.currentItemChanged.connect(self.receive_item_changed)
711         self.receive_list.itemClicked.connect(self.receive_item_changed)
712         self.receive_list.setHeaderLabels( [_('Address'), _('Message'), _('Amount')] )
713         self.receive_list.setColumnWidth(0, 340)
714         h = self.receive_list.header()
715         h.setStretchLastSection(False)
716         h.setResizeMode(1, QHeaderView.Stretch)
717
718         grid.addWidget(self.receive_requests_label, 6, 0)
719         grid.addWidget(self.receive_list, 7, 0, 1, 6)
720         return w
721
722     def receive_item_changed(self, item):
723         if item is None:
724             return
725         addr = str(item.text(0))
726         amount, message = self.receive_requests[addr]
727         self.receive_address_e.setText(addr)
728         self.receive_message_e.setText(message)
729         self.receive_amount_e.setAmount(amount)
730
731
732     def receive_list_delete(self, item):
733         addr = str(item.text(0))
734         self.receive_requests.pop(addr)
735         self.update_receive_tab()
736         self.clear_receive_tab()
737
738     def receive_list_menu(self, position):
739         item = self.receive_list.itemAt(position)
740         menu = QMenu()
741         menu.addAction(_("Delete"), lambda: self.receive_list_delete(item))
742         menu.exec_(self.receive_list.viewport().mapToGlobal(position))
743
744     def save_payment_request(self):
745         addr = str(self.receive_address_e.text())
746         amount = self.receive_amount_e.get_amount()
747         message = str(self.receive_message_e.text())
748         if not message and not amount:
749             QMessageBox.warning(self, _('Error'), _('No message or amount'), _('OK'))
750             return
751         self.receive_requests = self.wallet.storage.get('receive_requests',{}) 
752         self.receive_requests[addr] = (amount, message)
753         self.wallet.storage.put('receive_requests', self.receive_requests)
754         self.update_receive_tab()
755
756     def new_receive_address(self):
757         domain = self.wallet.get_account_addresses(self.current_account, include_change=False)
758         for addr in domain:
759             if not self.wallet.history.get(addr) and addr not in self.receive_requests.keys():
760                 break
761         else:
762             if isinstance(self.wallet, Imported_Wallet):
763                 self.show_message(_('No more addresses in your wallet.'))
764                 return
765             if not self.question(_("Warning: The next address will not be recovered automatically if you restore your wallet from seed; you may need to add it manually.\n\nThis occurs because you have too many unused addresses in your wallet. To avoid this situation, use the existing addresses first.\n\nCreate anyway?")):
766                 return
767             addr = self.wallet.create_new_address(self.current_account, False)
768         self.receive_address_e.setText(addr)
769         self.receive_message_e.setText('')
770         self.receive_amount_e.setAmount(None)
771
772     def clear_receive_tab(self):
773         self.receive_requests = self.wallet.storage.get('receive_requests',{}) 
774         domain = self.wallet.get_account_addresses(self.current_account, include_change=False)
775         for addr in domain:
776             if not self.wallet.history.get(addr) and addr not in self.receive_requests.keys():
777                 break
778         else:
779             addr = ''
780         self.receive_address_e.setText(addr)
781         self.receive_message_e.setText('')
782         self.receive_amount_e.setAmount(None)
783
784     def receive_at(self, addr):
785         if not bitcoin.is_address(addr):
786             return
787         self.tabs.setCurrentIndex(2)
788         self.receive_address_e.setText(addr)
789
790     def update_receive_tab(self):
791         self.receive_requests = self.wallet.storage.get('receive_requests',{}) 
792         b = len(self.receive_requests) > 0
793         self.receive_list.setVisible(b)
794         self.receive_requests_label.setVisible(b)
795
796         self.receive_list.clear()
797         for address, v in self.receive_requests.items():
798             amount, message = v
799             item = QTreeWidgetItem( [ address, message, self.format_amount(amount) if amount else ""] )
800             item.setFont(0, QFont(MONOSPACE_FONT))
801             self.receive_list.addTopLevelItem(item)
802
803
804     def update_receive_qr(self):
805         import urlparse, urllib
806         addr = str(self.receive_address_e.text())
807         amount = self.receive_amount_e.get_amount()
808         message = unicode(self.receive_message_e.text()).encode('utf8')
809         self.save_request_button.setEnabled((amount is not None) or (message != ""))
810         if addr:
811             query = []
812             if amount:
813                 query.append('amount=%s'%format_satoshis(amount))
814             if message:
815                 query.append('message=%s'%urllib.quote(message))
816             p = urlparse.ParseResult(scheme='bitcoin', netloc='', path=addr, params='', query='&'.join(query), fragment='')
817             url = urlparse.urlunparse(p)
818         else:
819             url = ""
820         self.receive_qr.setData(url)
821         run_hook('update_receive_qr', addr, amount, message, url)
822
823
824     def create_send_tab(self):
825         w = QWidget()
826
827         self.send_grid = grid = QGridLayout(w)
828         grid.setSpacing(8)
829         grid.setColumnMinimumWidth(3,300)
830         grid.setColumnStretch(5,1)
831         grid.setRowStretch(8, 1)
832
833         from paytoedit import PayToEdit
834         self.amount_e = BTCAmountEdit(self.get_decimal_point)
835         self.payto_e = PayToEdit(self)
836         self.payto_help = HelpButton(_('Recipient of the funds.') + '\n\n' + _('You may enter a Novacoin 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 Novacoin address)'))
837         grid.addWidget(QLabel(_('Pay to')), 1, 0)
838         grid.addWidget(self.payto_e, 1, 1, 1, 3)
839         grid.addWidget(self.payto_help, 1, 4)
840
841         completer = QCompleter()
842         completer.setCaseSensitivity(False)
843         self.payto_e.setCompleter(completer)
844         completer.setModel(self.completions)
845
846         self.message_e = MyLineEdit()
847         self.message_help = 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.'))
848         grid.addWidget(QLabel(_('Description')), 2, 0)
849         grid.addWidget(self.message_e, 2, 1, 1, 3)
850         grid.addWidget(self.message_help, 2, 4)
851
852         self.from_label = QLabel(_('From'))
853         grid.addWidget(self.from_label, 3, 0)
854         self.from_list = MyTreeWidget(self)
855         self.from_list.setColumnCount(2)
856         self.from_list.setColumnWidth(0, 350)
857         self.from_list.setColumnWidth(1, 50)
858         self.from_list.setHeaderHidden(True)
859         self.from_list.setMaximumHeight(80)
860         self.from_list.setContextMenuPolicy(Qt.CustomContextMenu)
861         self.from_list.customContextMenuRequested.connect(self.from_list_menu)
862         grid.addWidget(self.from_list, 3, 1, 1, 3)
863         self.set_pay_from([])
864
865         self.amount_help = HelpButton(_('Amount to be sent.') + '\n\n' \
866                                       + _('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.') \
867                                       + '\n\n' + _('Keyboard shortcut: type "!" to send all your coins.'))
868         grid.addWidget(QLabel(_('Amount')), 4, 0)
869         grid.addWidget(self.amount_e, 4, 1, 1, 2)
870         grid.addWidget(self.amount_help, 4, 3)
871
872         self.fee_e = BTCAmountEdit(self.get_decimal_point)
873         grid.addWidget(QLabel(_('Fee')), 5, 0)
874         grid.addWidget(self.fee_e, 5, 1, 1, 2)
875         grid.addWidget(HelpButton(
876                 _('Novacoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
877                     + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
878                     + _('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)
879
880         self.send_button = EnterButton(_("Send"), self.do_send)
881         grid.addWidget(self.send_button, 6, 1)
882
883         b = EnterButton(_("Clear"), self.do_clear)
884         grid.addWidget(b, 6, 2)
885
886         self.payto_sig = QLabel('')
887         grid.addWidget(self.payto_sig, 7, 0, 1, 4)
888
889         #QShortcut(QKeySequence("Up"), w, w.focusPreviousChild)
890         #QShortcut(QKeySequence("Down"), w, w.focusNextChild)
891         w.setLayout(grid)
892
893         def entry_changed( is_fee ):
894
895             if self.amount_e.is_shortcut:
896                 self.amount_e.is_shortcut = False
897                 sendable = self.get_sendable_balance()
898                 # there is only one output because we are completely spending inputs
899                 inputs, total, fee = self.wallet.choose_tx_inputs( sendable, 0, 1, coins = self.get_coins())
900                 fee = self.wallet.estimated_fee(inputs, 1)
901                 amount = total - fee
902                 self.amount_e.setAmount(amount)
903                 self.amount_e.textEdited.emit("")
904                 self.fee_e.setAmount(fee)
905                 return
906
907             amount = self.amount_e.get_amount()
908             fee = self.fee_e.get_amount()
909             outputs = self.payto_e.get_outputs()
910
911             if not is_fee: 
912                 fee = None
913
914             if amount is None:
915                 self.fee_e.setAmount(None)
916                 not_enough_funds = False
917             else:
918                 inputs, total, fee = self.wallet.choose_tx_inputs(amount, fee, len(outputs), coins = self.get_coins())
919                 not_enough_funds = len(inputs) == 0
920                 if not is_fee:
921                     self.fee_e.setAmount(fee)
922                     
923             if not not_enough_funds:
924                 palette = QPalette()
925                 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
926                 text = ""
927             else:
928                 palette = QPalette()
929                 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
930                 text = _( "Not enough funds" )
931                 c, u = self.wallet.get_frozen_balance()
932                 if c+u: text += ' (' + self.format_amount(c+u).strip() + ' ' + self.base_unit() + ' ' +_("are frozen") + ')'
933
934             self.statusBar().showMessage(text)
935             self.amount_e.setPalette(palette)
936             self.fee_e.setPalette(palette)
937
938         self.amount_e.textChanged.connect(lambda: entry_changed(False) )
939         self.fee_e.textChanged.connect(lambda: entry_changed(True) )
940
941         run_hook('create_send_tab', grid)
942         return w
943
944     def from_list_delete(self, item):
945         i = self.from_list.indexOfTopLevelItem(item)
946         self.pay_from.pop(i)
947         self.redraw_from_list()
948
949     def from_list_menu(self, position):
950         item = self.from_list.itemAt(position)
951         menu = QMenu()
952         menu.addAction(_("Remove"), lambda: self.from_list_delete(item))
953         menu.exec_(self.from_list.viewport().mapToGlobal(position))
954
955     def set_pay_from(self, domain = None):
956         self.pay_from = [] if domain == [] else self.wallet.get_unspent_coins(domain)
957         self.redraw_from_list()
958
959     def redraw_from_list(self):
960         self.from_list.clear()
961         self.from_label.setHidden(len(self.pay_from) == 0)
962         self.from_list.setHidden(len(self.pay_from) == 0)
963
964         def format(x):
965             h = x.get('prevout_hash')
966             return h[0:8] + '...' + h[-8:] + ":%d"%x.get('prevout_n') + u'\t' + "%s"%x.get('address')
967
968         for item in self.pay_from:
969             self.from_list.addTopLevelItem(QTreeWidgetItem( [format(item), self.format_amount(item['value']) ]))
970
971     def update_completions(self):
972         l = []
973         for addr,label in self.wallet.labels.items():
974             if addr in self.wallet.addressbook:
975                 l.append( label + '  <' + addr + '>')
976
977         run_hook('update_completions', l)
978         self.completions.setStringList(l)
979
980
981     def protected(func):
982         return lambda s, *args: s.do_protect(func, args)
983
984
985     def read_send_tab(self):
986
987         if self.payment_request and self.payment_request.has_expired():
988             QMessageBox.warning(self, _('Error'), _('Payment request has expired'), _('OK'))
989             return
990
991         label = unicode( self.message_e.text() )
992
993         if self.payment_request:
994             outputs = self.payment_request.get_outputs()
995         else:
996             outputs = self.payto_e.get_outputs()
997
998         if not outputs:
999             QMessageBox.warning(self, _('Error'), _('No outputs'), _('OK'))
1000             return
1001
1002         for type, addr, amount in outputs:
1003             if addr is None:
1004                 QMessageBox.warning(self, _('Error'), _('Novacoin Address is None'), _('OK'))
1005                 return
1006             if type == 'op_return':
1007                 continue
1008             if type == 'address' and not bitcoin.is_address(addr):
1009                 QMessageBox.warning(self, _('Error'), _('Invalid Novacoin Address'), _('OK'))
1010                 return
1011             if amount is None:
1012                 QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
1013                 return
1014
1015         amount = sum(map(lambda x:x[2], outputs))
1016
1017         fee = self.fee_e.get_amount()
1018         if fee is None:
1019             QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
1020             return
1021
1022         confirm_amount = self.config.get('confirm_amount', 10000)
1023         if amount >= confirm_amount:
1024             o = '\n'.join(map(lambda x:x[1], outputs))
1025             if not self.question(_("send %(amount)s to %(address)s?")%{ 'amount' : self.format_amount(amount) + ' '+ self.base_unit(), 'address' : o}):
1026                 return
1027             
1028         confirm_fee = self.config.get('confirm_fee', 10000)
1029         if fee >= confirm_fee:
1030             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()}):
1031                 return
1032
1033         coins = self.get_coins()
1034         return outputs, fee, label, coins
1035
1036
1037     def do_send(self):
1038         r = self.read_send_tab()
1039         if not r:
1040             return
1041         outputs, fee, label, coins = r
1042         self.send_tx(outputs, fee, label, coins)
1043
1044
1045     @protected
1046     def send_tx(self, outputs, fee, label, coins, password):
1047         self.send_button.setDisabled(True)
1048
1049         # first, create an unsigned tx 
1050         try:
1051             tx = self.wallet.make_unsigned_transaction(outputs, fee, None, coins = coins)
1052             tx.error = None
1053         except Exception as e:
1054             traceback.print_exc(file=sys.stdout)
1055             self.show_message(str(e))
1056             self.send_button.setDisabled(False)
1057             return
1058
1059         # call hook to see if plugin needs gui interaction
1060         run_hook('send_tx', tx)
1061
1062         # sign the tx
1063         def sign_thread():
1064             if self.wallet.is_watching_only():
1065                 return tx
1066             keypairs = {}
1067             try:
1068                 self.wallet.add_keypairs(tx, keypairs, password)
1069                 self.wallet.sign_transaction(tx, keypairs, password)
1070             except Exception as e:
1071                 traceback.print_exc(file=sys.stdout)
1072                 tx.error = str(e)
1073             return tx
1074
1075         def sign_done(tx):
1076             if tx.error:
1077                 self.show_message(tx.error)
1078                 self.send_button.setDisabled(False)
1079                 return
1080             if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
1081                 QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
1082                 self.send_button.setDisabled(False)
1083                 return
1084             if label:
1085                 self.wallet.set_label(tx.hash(), label)
1086
1087             if not tx.is_complete() or self.config.get('show_before_broadcast'):
1088                 self.show_transaction(tx)
1089                 self.do_clear()
1090                 self.send_button.setDisabled(False)
1091                 return
1092
1093             self.broadcast_transaction(tx)
1094
1095         # keep a reference to WaitingDialog or the gui might crash
1096         self.waiting_dialog = WaitingDialog(self, 'Signing..', sign_thread, sign_done)
1097         self.waiting_dialog.start()
1098
1099
1100
1101     def broadcast_transaction(self, tx):
1102
1103         def broadcast_thread():
1104             pr = self.payment_request
1105             if pr is None:
1106                 return self.wallet.sendtx(tx)
1107
1108             if pr.has_expired():
1109                 self.payment_request = None
1110                 return False, _("Payment request has expired")
1111
1112             status, msg =  self.wallet.sendtx(tx)
1113             if not status:
1114                 return False, msg
1115
1116             self.invoices[pr.get_id()] = (pr.get_domain(), pr.get_memo(), pr.get_amount(), pr.get_expiration_date(), PR_PAID, tx.hash())
1117             self.wallet.storage.put('invoices', self.invoices)
1118             self.update_invoices_tab()
1119             self.payment_request = None
1120             refund_address = self.wallet.addresses()[0]
1121             ack_status, ack_msg = pr.send_ack(str(tx), refund_address)
1122             if ack_status:
1123                 msg = ack_msg
1124
1125             return status, msg
1126
1127         def broadcast_done(status, msg):
1128             if status:
1129                 QMessageBox.information(self, '', _('Payment sent.') + '\n' + msg, _('OK'))
1130                 self.do_clear()
1131             else:
1132                 QMessageBox.warning(self, _('Error'), msg, _('OK'))
1133             self.send_button.setDisabled(False)
1134
1135         self.waiting_dialog = WaitingDialog(self, 'Broadcasting..', broadcast_thread, broadcast_done)
1136         self.waiting_dialog.start()
1137
1138
1139
1140     def prepare_for_payment_request(self):
1141         self.tabs.setCurrentIndex(1)
1142         self.payto_e.is_pr = True
1143         for e in [self.payto_e, self.amount_e, self.message_e]:
1144             e.setFrozen(True)
1145         for h in [self.payto_help, self.amount_help, self.message_help]:
1146             h.hide()
1147         self.payto_e.setText(_("please wait..."))
1148         return True
1149
1150     def payment_request_ok(self):
1151         pr = self.payment_request
1152         pr_id = pr.get_id()
1153         if pr_id not in self.invoices:
1154             self.invoices[pr_id] = (pr.get_domain(), pr.get_memo(), pr.get_amount(), pr.get_expiration_date(), PR_UNPAID, None)
1155             self.wallet.storage.put('invoices', self.invoices)
1156             self.update_invoices_tab()
1157         else:
1158             print_error('invoice already in list')
1159
1160         status = self.invoices[pr_id][4]
1161         if status == PR_PAID:
1162             self.do_clear()
1163             self.show_message("invoice already paid")
1164             self.payment_request = None
1165             return
1166
1167         self.payto_help.show()
1168         self.payto_help.set_alt(lambda: self.show_pr_details(pr))
1169
1170         if not pr.has_expired():
1171             self.payto_e.setGreen()
1172         else:
1173             self.payto_e.setExpired()
1174
1175         self.payto_e.setText(pr.domain)
1176         self.amount_e.setText(self.format_amount(pr.get_amount()))
1177         self.message_e.setText(pr.get_memo())
1178
1179     def payment_request_error(self):
1180         self.do_clear()
1181         self.show_message(self.payment_request.error)
1182         self.payment_request = None
1183
1184     def pay_from_URI(self,URI):
1185         if not URI:
1186             return
1187         address, amount, label, message, request_url = util.parse_URI(URI)
1188         try:
1189             address, amount, label, message, request_url = util.parse_URI(URI)
1190         except Exception as e:
1191             QMessageBox.warning(self, _('Error'), _('Invalid novacoin URI:') + '\n' + str(e), _('OK'))
1192             return
1193
1194         self.tabs.setCurrentIndex(1)
1195
1196         if not request_url:
1197             if label:
1198                 if self.wallet.labels.get(address) != label:
1199                     if self.question(_('Save label "%s" for address %s ?'%(label,address))):
1200                         if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
1201                             self.wallet.addressbook.append(address)
1202                             self.wallet.set_label(address, label)
1203             else:
1204                 label = self.wallet.labels.get(address)
1205             if address:
1206                 self.payto_e.setText(label + '  <'+ address +'>' if label else address)
1207             if message:
1208                 self.message_e.setText(message)
1209             if amount:
1210                 self.amount_e.setAmount(amount)
1211             return
1212
1213         from electrum_nvc import paymentrequest
1214         def payment_request():
1215             self.payment_request = paymentrequest.PaymentRequest(self.config)
1216             self.payment_request.read(request_url)
1217             if self.payment_request.verify():
1218                 self.emit(SIGNAL('payment_request_ok'))
1219             else:
1220                 self.emit(SIGNAL('payment_request_error'))
1221
1222         self.pr_thread = threading.Thread(target=payment_request).start()
1223         self.prepare_for_payment_request()
1224
1225
1226
1227     def do_clear(self):
1228         self.payto_e.is_pr = False
1229         self.payto_sig.setVisible(False)
1230         for e in [self.payto_e, self.message_e, self.amount_e, self.fee_e]:
1231             e.setText('')
1232             e.setFrozen(False)
1233
1234         for h in [self.payto_help, self.amount_help, self.message_help]:
1235             h.show()
1236
1237         self.payto_help.set_alt(None)
1238         self.set_pay_from([])
1239         self.update_status()
1240
1241
1242
1243     def set_addrs_frozen(self,addrs,freeze):
1244         for addr in addrs:
1245             if not addr: continue
1246             if addr in self.wallet.frozen_addresses and not freeze:
1247                 self.wallet.unfreeze(addr)
1248             elif addr not in self.wallet.frozen_addresses and freeze:
1249                 self.wallet.freeze(addr)
1250         self.update_address_tab()
1251
1252
1253
1254     def create_list_tab(self, headers):
1255         "generic tab creation method"
1256         l = MyTreeWidget(self)
1257         l.setColumnCount( len(headers) )
1258         l.setHeaderLabels( headers )
1259
1260         w = QWidget()
1261         vbox = QVBoxLayout()
1262         w.setLayout(vbox)
1263
1264         vbox.setMargin(0)
1265         vbox.setSpacing(0)
1266         vbox.addWidget(l)
1267         buttons = QWidget()
1268         vbox.addWidget(buttons)
1269
1270         return l, w
1271
1272
1273     def create_addresses_tab(self):
1274         l, w = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
1275         for i,width in enumerate(self.column_widths['receive']):
1276             l.setColumnWidth(i, width)
1277         l.setContextMenuPolicy(Qt.CustomContextMenu)
1278         l.customContextMenuRequested.connect(self.create_receive_menu)
1279         l.setSelectionMode(QAbstractItemView.ExtendedSelection)
1280         l.itemDoubleClicked.connect(lambda a, b: self.address_label_clicked(a,b,l,0,1))
1281         l.itemChanged.connect(lambda a,b: self.address_label_changed(a,b,l,0,1))
1282         l.currentItemChanged.connect(lambda a,b: self.current_item_changed(a))
1283         self.address_list = l
1284         return w
1285
1286
1287
1288
1289     def save_column_widths(self):
1290         self.column_widths["receive"] = []
1291         for i in range(self.address_list.columnCount() -1):
1292             self.column_widths["receive"].append(self.address_list.columnWidth(i))
1293
1294         self.column_widths["history"] = []
1295         for i in range(self.history_list.columnCount() - 1):
1296             self.column_widths["history"].append(self.history_list.columnWidth(i))
1297
1298         self.column_widths["contacts"] = []
1299         for i in range(self.contacts_list.columnCount() - 1):
1300             self.column_widths["contacts"].append(self.contacts_list.columnWidth(i))
1301
1302         self.config.set_key("column_widths_2", self.column_widths, True)
1303
1304
1305     def create_contacts_tab(self):
1306         l, w = self.create_list_tab([_('Address'), _('Label'), _('Tx')])
1307         l.setContextMenuPolicy(Qt.CustomContextMenu)
1308         l.customContextMenuRequested.connect(self.create_contact_menu)
1309         for i,width in enumerate(self.column_widths['contacts']):
1310             l.setColumnWidth(i, width)
1311         l.itemDoubleClicked.connect(lambda a, b: self.address_label_clicked(a,b,l,0,1))
1312         l.itemChanged.connect(lambda a,b: self.address_label_changed(a,b,l,0,1))
1313         self.contacts_list = l
1314         return w
1315
1316
1317     def create_invoices_tab(self):
1318         l, w = self.create_list_tab([_('Requestor'), _('Memo'), _('Date'), _('Amount'), _('Status')])
1319         l.setColumnWidth(0, 150)
1320         l.setColumnWidth(2, 150)
1321         l.setColumnWidth(3, 150)
1322         h = l.header()
1323         h.setStretchLastSection(False)
1324         h.setResizeMode(1, QHeaderView.Stretch)
1325         l.setContextMenuPolicy(Qt.CustomContextMenu)
1326         l.customContextMenuRequested.connect(self.create_invoice_menu)
1327         self.invoices_list = l
1328         return w
1329
1330     def update_invoices_tab(self):
1331         invoices = self.wallet.storage.get('invoices', {})
1332         l = self.invoices_list
1333         l.clear()
1334         for key, value in sorted(invoices.items(), key=lambda x: -x[1][3]):
1335             domain, memo, amount, expiration_date, status, tx_hash = value
1336             if status == PR_UNPAID and expiration_date and expiration_date < time.time():
1337                 status = PR_EXPIRED
1338             date_str = datetime.datetime.fromtimestamp(expiration_date).isoformat(' ')[:-3]
1339             item = QTreeWidgetItem( [ domain, memo, date_str, self.format_amount(amount, whitespaces=True), format_status(status)] )
1340             item.setData(0, 32, key)
1341             item.setFont(0, QFont(MONOSPACE_FONT))
1342             item.setFont(3, QFont(MONOSPACE_FONT))
1343             l.addTopLevelItem(item)
1344         l.setCurrentItem(l.topLevelItem(0))
1345
1346     def delete_imported_key(self, addr):
1347         if self.question(_("Do you want to remove")+" %s "%addr +_("from your wallet?")):
1348             self.wallet.delete_imported_key(addr)
1349             self.update_address_tab()
1350             self.update_history_tab()
1351
1352     def edit_account_label(self, k):
1353         text, ok = QInputDialog.getText(self, _('Rename account'), _('Name') + ':', text = self.wallet.labels.get(k,''))
1354         if ok:
1355             label = unicode(text)
1356             self.wallet.set_label(k,label)
1357             self.update_address_tab()
1358
1359     def account_set_expanded(self, item, k, b):
1360         item.setExpanded(b)
1361         self.accounts_expanded[k] = b
1362
1363     def create_account_menu(self, position, k, item):
1364         menu = QMenu()
1365         if item.isExpanded():
1366             menu.addAction(_("Minimize"), lambda: self.account_set_expanded(item, k, False))
1367         else:
1368             menu.addAction(_("Maximize"), lambda: self.account_set_expanded(item, k, True))
1369         menu.addAction(_("Rename"), lambda: self.edit_account_label(k))
1370         if self.wallet.seed_version > 4:
1371             menu.addAction(_("View details"), lambda: self.show_account_details(k))
1372         if self.wallet.account_is_pending(k):
1373             menu.addAction(_("Delete"), lambda: self.delete_pending_account(k))
1374         menu.exec_(self.address_list.viewport().mapToGlobal(position))
1375
1376     def delete_pending_account(self, k):
1377         self.wallet.delete_pending_account(k)
1378         self.update_address_tab()
1379
1380     def create_receive_menu(self, position):
1381         # fixme: this function apparently has a side effect.
1382         # if it is not called the menu pops up several times
1383         #self.address_list.selectedIndexes()
1384
1385         selected = self.address_list.selectedItems()
1386         multi_select = len(selected) > 1
1387         addrs = [unicode(item.text(0)) for item in selected]
1388         if not multi_select:
1389             item = self.address_list.itemAt(position)
1390             if not item: return
1391
1392             addr = addrs[0]
1393             if not is_valid(addr):
1394                 k = str(item.data(0,32).toString())
1395                 if k:
1396                     self.create_account_menu(position, k, item)
1397                 else:
1398                     item.setExpanded(not item.isExpanded())
1399                 return
1400
1401         menu = QMenu()
1402         if not multi_select:
1403             menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
1404             menu.addAction(_("Request payment"), lambda: self.receive_at(addr))
1405             menu.addAction(_("Edit label"), lambda: self.edit_label(True))
1406             menu.addAction(_("Public keys"), lambda: self.show_public_keys(addr))
1407             if not self.wallet.is_watching_only():
1408                 menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
1409                 menu.addAction(_("Sign/verify message"), lambda: self.sign_verify_message(addr))
1410                 menu.addAction(_("Encrypt/decrypt message"), lambda: self.encrypt_message(addr))
1411             if self.wallet.is_imported(addr):
1412                 menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
1413
1414         if any(addr not in self.wallet.frozen_addresses for addr in addrs):
1415             menu.addAction(_("Freeze"), lambda: self.set_addrs_frozen(addrs, True))
1416         if any(addr in self.wallet.frozen_addresses for addr in addrs):
1417             menu.addAction(_("Unfreeze"), lambda: self.set_addrs_frozen(addrs, False))
1418
1419         def can_send(addr):
1420             return addr not in self.wallet.frozen_addresses and self.wallet.get_addr_balance(addr) != (0, 0)
1421         if any(can_send(addr) for addr in addrs):
1422             menu.addAction(_("Send From"), lambda: self.send_from_addresses(addrs))
1423
1424         run_hook('receive_menu', menu, addrs)
1425         menu.exec_(self.address_list.viewport().mapToGlobal(position))
1426
1427
1428     def get_sendable_balance(self):
1429         return sum(map(lambda x:x['value'], self.get_coins()))
1430
1431
1432     def get_coins(self):
1433         if self.pay_from:
1434             return self.pay_from
1435         else:
1436             domain = self.wallet.get_account_addresses(self.current_account)
1437             for i in self.wallet.frozen_addresses:
1438                 if i in domain: domain.remove(i)
1439             return self.wallet.get_unspent_coins(domain)
1440
1441
1442     def send_from_addresses(self, addrs):
1443         self.set_pay_from( addrs )
1444         self.tabs.setCurrentIndex(1)
1445
1446
1447     def payto(self, addr):
1448         if not addr: return
1449         label = self.wallet.labels.get(addr)
1450         m_addr = label + '  <' + addr + '>' if label else addr
1451         self.tabs.setCurrentIndex(1)
1452         self.payto_e.setText(m_addr)
1453         self.amount_e.setFocus()
1454
1455
1456     def delete_contact(self, x):
1457         if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
1458             self.wallet.delete_contact(x)
1459             self.wallet.set_label(x, None)
1460             self.update_history_tab()
1461             self.update_contacts_tab()
1462             self.update_completions()
1463
1464
1465     def create_contact_menu(self, position):
1466         item = self.contacts_list.itemAt(position)
1467         menu = QMenu()
1468         if not item:
1469             menu.addAction(_("New contact"), lambda: self.new_contact_dialog())
1470         else:
1471             addr = unicode(item.text(0))
1472             label = unicode(item.text(1))
1473             is_editable = item.data(0,32).toBool()
1474             payto_addr = item.data(0,33).toString()
1475             menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
1476             menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
1477             menu.addAction(_("QR code"), lambda: self.show_qrcode("novacoin:" + addr, _("Address")))
1478             if is_editable:
1479                 menu.addAction(_("Edit label"), lambda: self.edit_label(False))
1480                 menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
1481
1482         run_hook('create_contact_menu', menu, item)
1483         menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
1484
1485     def delete_invoice(self, key):
1486         self.invoices.pop(key)
1487         self.wallet.storage.put('invoices', self.invoices)
1488         self.update_invoices_tab()
1489
1490     def show_invoice(self, key):
1491         from electrum_nvc.paymentrequest import PaymentRequest
1492         domain, memo, value, expiration, status, tx_hash = self.invoices[key]
1493         pr = PaymentRequest(self.config)
1494         pr.read_file(key)
1495         pr.domain = domain
1496         pr.verify()
1497         self.show_pr_details(pr, tx_hash)
1498
1499     def show_pr_details(self, pr, tx_hash=None):
1500         msg = 'Domain: ' + pr.domain
1501         msg += '\nStatus: ' + pr.get_status()
1502         msg += '\nMemo: ' + pr.get_memo()
1503         msg += '\nPayment URL: ' + pr.payment_url
1504         msg += '\n\nOutputs:\n' + '\n'.join(map(lambda x: x[1] + ' ' + self.format_amount(x[2])+ self.base_unit(), pr.get_outputs()))
1505         if tx_hash:
1506             msg += '\n\nTransaction ID: ' + tx_hash
1507         QMessageBox.information(self, 'Invoice', msg , 'OK')
1508
1509     def do_pay_invoice(self, key):
1510         from electrum_nvc.paymentrequest import PaymentRequest
1511         domain, memo, value, expiration, status, tx_hash = self.invoices[key]
1512         pr = PaymentRequest(self.config)
1513         pr.read_file(key)
1514         pr.domain = domain
1515         self.payment_request = pr
1516         self.prepare_for_payment_request()
1517         if pr.verify():
1518             self.payment_request_ok()
1519         else:
1520             self.payment_request_error()
1521             
1522
1523     def create_invoice_menu(self, position):
1524         item = self.invoices_list.itemAt(position)
1525         if not item:
1526             return
1527         key = str(item.data(0, 32).toString())
1528         domain, memo, value, expiration, status, tx_hash = self.invoices[key]
1529         menu = QMenu()
1530         menu.addAction(_("Details"), lambda: self.show_invoice(key))
1531         if status == PR_UNPAID:
1532             menu.addAction(_("Pay Now"), lambda: self.do_pay_invoice(key))
1533         menu.addAction(_("Delete"), lambda: self.delete_invoice(key))
1534         menu.exec_(self.invoices_list.viewport().mapToGlobal(position))
1535
1536
1537
1538     def update_address_tab(self):
1539         l = self.address_list
1540         # extend the syntax for consistency
1541         l.addChild = l.addTopLevelItem
1542         l.insertChild = l.insertTopLevelItem
1543
1544         l.clear()
1545
1546         accounts = self.wallet.get_accounts()
1547         if self.current_account is None:
1548             account_items = sorted(accounts.items())
1549         else:
1550             account_items = [(self.current_account, accounts.get(self.current_account))]
1551
1552
1553         for k, account in account_items:
1554
1555             if len(accounts) > 1:
1556                 name = self.wallet.get_account_name(k)
1557                 c,u = self.wallet.get_account_balance(k)
1558                 account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
1559                 l.addTopLevelItem(account_item)
1560                 account_item.setExpanded(self.accounts_expanded.get(k, True))
1561                 account_item.setData(0, 32, k)
1562             else:
1563                 account_item = l
1564
1565             sequences = [0,1] if account.has_change() else [0]
1566             for is_change in sequences:
1567                 if len(sequences) > 1:
1568                     name = _("Receiving") if not is_change else _("Change")
1569                     seq_item = QTreeWidgetItem( [ name, '', '', '', ''] )
1570                     account_item.addChild(seq_item)
1571                     if not is_change: 
1572                         seq_item.setExpanded(True)
1573                 else:
1574                     seq_item = account_item
1575                     
1576                 used_item = QTreeWidgetItem( [ _("Used"), '', '', '', ''] )
1577                 used_flag = False
1578
1579                 addr_list = account.get_addresses(is_change)
1580                 for address in addr_list:
1581                     num, is_used = self.wallet.is_used(address)
1582                     label = self.wallet.labels.get(address,'')
1583                     c, u = self.wallet.get_addr_balance(address)
1584                     balance = self.format_amount(c + u)
1585                     item = QTreeWidgetItem( [ address, label, balance, "%d"%num] )
1586                     item.setFont(0, QFont(MONOSPACE_FONT))
1587                     item.setData(0, 32, True) # label can be edited
1588                     if address in self.wallet.frozen_addresses:
1589                         item.setBackgroundColor(0, QColor('lightblue'))
1590                     if self.wallet.is_beyond_limit(address, account, is_change):
1591                         item.setBackgroundColor(0, QColor('red'))
1592                     if is_used:
1593                         if not used_flag:
1594                             seq_item.insertChild(0, used_item)
1595                             used_flag = True
1596                         used_item.addChild(item)
1597                     else:
1598                         seq_item.addChild(item)
1599
1600         # we use column 1 because column 0 may be hidden
1601         l.setCurrentItem(l.topLevelItem(0),1)
1602
1603
1604     def update_contacts_tab(self):
1605         l = self.contacts_list
1606         l.clear()
1607
1608         for address in self.wallet.addressbook:
1609             label = self.wallet.labels.get(address,'')
1610             n = self.wallet.get_num_tx(address)
1611             item = QTreeWidgetItem( [ address, label, "%d"%n] )
1612             item.setFont(0, QFont(MONOSPACE_FONT))
1613             # 32 = label can be edited (bool)
1614             item.setData(0,32, True)
1615             # 33 = payto string
1616             item.setData(0,33, address)
1617             l.addTopLevelItem(item)
1618
1619         run_hook('update_contacts_tab', l)
1620         l.setCurrentItem(l.topLevelItem(0))
1621
1622
1623     def create_console_tab(self):
1624         from console import Console
1625         self.console = console = Console()
1626         return console
1627
1628
1629     def update_console(self):
1630         console = self.console
1631         console.history = self.config.get("console-history",[])
1632         console.history_index = len(console.history)
1633
1634         console.updateNamespace({'wallet' : self.wallet, 'network' : self.network, 'gui':self})
1635         console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
1636
1637         c = commands.Commands(self.wallet, self.network, lambda: self.console.set_json(True))
1638         methods = {}
1639         def mkfunc(f, method):
1640             return lambda *args: apply( f, (method, args, self.password_dialog ))
1641         for m in dir(c):
1642             if m[0]=='_' or m in ['network','wallet']: continue
1643             methods[m] = mkfunc(c._run, m)
1644
1645         console.updateNamespace(methods)
1646
1647
1648     def change_account(self,s):
1649         if s == _("All accounts"):
1650             self.current_account = None
1651         else:
1652             accounts = self.wallet.get_account_names()
1653             for k, v in accounts.items():
1654                 if v == s:
1655                     self.current_account = k
1656         self.update_history_tab()
1657         self.update_status()
1658         self.update_address_tab()
1659         self.update_receive_tab()
1660
1661     def create_status_bar(self):
1662
1663         sb = QStatusBar()
1664         sb.setFixedHeight(35)
1665         qtVersion = qVersion()
1666
1667         self.balance_label = QLabel("")
1668         sb.addWidget(self.balance_label)
1669
1670         from version_getter import UpdateLabel
1671         self.updatelabel = UpdateLabel(self.config, sb)
1672
1673         self.account_selector = QComboBox()
1674         self.account_selector.setSizeAdjustPolicy(QComboBox.AdjustToContents)
1675         self.connect(self.account_selector,SIGNAL("activated(QString)"),self.change_account)
1676         sb.addPermanentWidget(self.account_selector)
1677
1678         if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
1679             sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
1680
1681         self.lock_icon = QIcon()
1682         self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
1683         sb.addPermanentWidget( self.password_button )
1684
1685         sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
1686         self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
1687         sb.addPermanentWidget( self.seed_button )
1688         self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
1689         sb.addPermanentWidget( self.status_button )
1690
1691         run_hook('create_status_bar', (sb,))
1692
1693         self.setStatusBar(sb)
1694
1695
1696     def update_lock_icon(self):
1697         icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
1698         self.password_button.setIcon( icon )
1699
1700
1701     def update_buttons_on_seed(self):
1702         if self.wallet.has_seed():
1703            self.seed_button.show()
1704         else:
1705            self.seed_button.hide()
1706
1707         if not self.wallet.is_watching_only():
1708            self.password_button.show()
1709            self.send_button.setText(_("Send"))
1710         else:
1711            self.password_button.hide()
1712            self.send_button.setText(_("Create unsigned transaction"))
1713
1714
1715     def change_password_dialog(self):
1716         from password_dialog import PasswordDialog
1717         d = PasswordDialog(self.wallet, self)
1718         d.run()
1719         self.update_lock_icon()
1720
1721
1722     def new_contact_dialog(self):
1723
1724         d = QDialog(self)
1725         d.setWindowTitle(_("New Contact"))
1726         vbox = QVBoxLayout(d)
1727         vbox.addWidget(QLabel(_('New Contact')+':'))
1728
1729         grid = QGridLayout()
1730         line1 = QLineEdit()
1731         line2 = QLineEdit()
1732         grid.addWidget(QLabel(_("Address")), 1, 0)
1733         grid.addWidget(line1, 1, 1)
1734         grid.addWidget(QLabel(_("Name")), 2, 0)
1735         grid.addWidget(line2, 2, 1)
1736
1737         vbox.addLayout(grid)
1738         vbox.addLayout(ok_cancel_buttons(d))
1739
1740         if not d.exec_():
1741             return
1742
1743         address = str(line1.text())
1744         label = unicode(line2.text())
1745
1746         if not is_valid(address):
1747             QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
1748             return
1749
1750         self.wallet.add_contact(address)
1751         if label:
1752             self.wallet.set_label(address, label)
1753
1754         self.update_contacts_tab()
1755         self.update_history_tab()
1756         self.update_completions()
1757         self.tabs.setCurrentIndex(3)
1758
1759
1760     @protected
1761     def new_account_dialog(self, password):
1762
1763         dialog = QDialog(self)
1764         dialog.setModal(1)
1765         dialog.setWindowTitle(_("New Account"))
1766
1767         vbox = QVBoxLayout()
1768         vbox.addWidget(QLabel(_('Account name')+':'))
1769         e = QLineEdit()
1770         vbox.addWidget(e)
1771         msg = _("Note: Newly created accounts are 'pending' until they receive novacoins.") + " " \
1772             + _("You will need to wait for 2 confirmations until the correct balance is displayed and more addresses are created for that account.")
1773         l = QLabel(msg)
1774         l.setWordWrap(True)
1775         vbox.addWidget(l)
1776
1777         vbox.addLayout(ok_cancel_buttons(dialog))
1778         dialog.setLayout(vbox)
1779         r = dialog.exec_()
1780         if not r: return
1781
1782         name = str(e.text())
1783         if not name: return
1784
1785         self.wallet.create_pending_account(name, password)
1786         self.update_address_tab()
1787         self.tabs.setCurrentIndex(3)
1788
1789
1790
1791
1792     def show_master_public_keys(self):
1793
1794         dialog = QDialog(self)
1795         dialog.setModal(1)
1796         dialog.setWindowTitle(_("Master Public Keys"))
1797
1798         main_layout = QGridLayout()
1799         mpk_dict = self.wallet.get_master_public_keys()
1800         i = 0
1801         for key, value in mpk_dict.items():
1802             main_layout.addWidget(QLabel(key), i, 0)
1803             mpk_text = QTextEdit()
1804             mpk_text.setReadOnly(True)
1805             mpk_text.setMaximumHeight(170)
1806             mpk_text.setText(value)
1807             main_layout.addWidget(mpk_text, i + 1, 0)
1808             i += 2
1809
1810         vbox = QVBoxLayout()
1811         vbox.addLayout(main_layout)
1812         vbox.addLayout(close_button(dialog))
1813
1814         dialog.setLayout(vbox)
1815         dialog.exec_()
1816
1817
1818     @protected
1819     def show_seed_dialog(self, password):
1820         if not self.wallet.has_seed():
1821             QMessageBox.information(self, _('Message'), _('This wallet has no seed'), _('OK'))
1822             return
1823
1824         try:
1825             mnemonic = self.wallet.get_mnemonic(password)
1826         except Exception:
1827             QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
1828             return
1829         from seed_dialog import SeedDialog
1830         d = SeedDialog(self, mnemonic, self.wallet.has_imported_keys())
1831         d.exec_()
1832
1833
1834
1835     def show_qrcode(self, data, title = _("QR code")):
1836         if not data: 
1837             return
1838         d = QRDialog(data, self, title)
1839         d.exec_()
1840
1841
1842     def do_protect(self, func, args):
1843         if self.wallet.use_encryption:
1844             password = self.password_dialog()
1845             if not password:
1846                 return
1847         else:
1848             password = None
1849
1850         if args != (False,):
1851             args = (self,) + args + (password,)
1852         else:
1853             args = (self,password)
1854         apply( func, args)
1855
1856
1857     def show_public_keys(self, address):
1858         if not address: return
1859         try:
1860             pubkey_list = self.wallet.get_public_keys(address)
1861         except Exception as e:
1862             traceback.print_exc(file=sys.stdout)
1863             self.show_message(str(e))
1864             return
1865
1866         d = QDialog(self)
1867         d.setMinimumSize(600, 200)
1868         d.setModal(1)
1869         vbox = QVBoxLayout()
1870         vbox.addWidget( QLabel(_("Address") + ': ' + address))
1871         vbox.addWidget( QLabel(_("Public key") + ':'))
1872         keys = QRTextEdit()
1873         keys.setReadOnly(True)
1874         keys.setText('\n'.join(pubkey_list))
1875         vbox.addWidget(keys)
1876         vbox.addLayout(close_button(d))
1877         d.setLayout(vbox)
1878         d.exec_()
1879
1880     @protected
1881     def show_private_key(self, address, password):
1882         if not address: return
1883         try:
1884             pk_list = self.wallet.get_private_key(address, password)
1885         except Exception as e:
1886             traceback.print_exc(file=sys.stdout)
1887             self.show_message(str(e))
1888             return
1889
1890         d = QDialog(self)
1891         d.setMinimumSize(600, 200)
1892         d.setModal(1)
1893         vbox = QVBoxLayout()
1894         vbox.addWidget( QLabel(_("Address") + ': ' + address))
1895         vbox.addWidget( QLabel(_("Private key") + ':'))
1896         keys = QRTextEdit()
1897         keys.setReadOnly(True)
1898         keys.setText('\n'.join(pk_list))
1899         vbox.addWidget(keys)
1900         vbox.addLayout(close_button(d))
1901         d.setLayout(vbox)
1902         d.exec_()
1903
1904
1905     @protected
1906     def do_sign(self, address, message, signature, password):
1907         message = unicode(message.toPlainText())
1908         message = message.encode('utf-8')
1909         try:
1910             sig = self.wallet.sign_message(str(address.text()), message, password)
1911             signature.setText(sig)
1912         except Exception as e:
1913             self.show_message(str(e))
1914
1915     def do_verify(self, address, message, signature):
1916         message = unicode(message.toPlainText())
1917         message = message.encode('utf-8')
1918         if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message):
1919             self.show_message(_("Signature verified"))
1920         else:
1921             self.show_message(_("Error: wrong signature"))
1922
1923
1924     def sign_verify_message(self, address=''):
1925         d = QDialog(self)
1926         d.setModal(1)
1927         d.setWindowTitle(_('Sign/verify Message'))
1928         d.setMinimumSize(410, 290)
1929
1930         layout = QGridLayout(d)
1931
1932         message_e = QTextEdit()
1933         layout.addWidget(QLabel(_('Message')), 1, 0)
1934         layout.addWidget(message_e, 1, 1)
1935         layout.setRowStretch(2,3)
1936
1937         address_e = QLineEdit()
1938         address_e.setText(address)
1939         layout.addWidget(QLabel(_('Address')), 2, 0)
1940         layout.addWidget(address_e, 2, 1)
1941
1942         signature_e = QTextEdit()
1943         layout.addWidget(QLabel(_('Signature')), 3, 0)
1944         layout.addWidget(signature_e, 3, 1)
1945         layout.setRowStretch(3,1)
1946
1947         hbox = QHBoxLayout()
1948
1949         b = QPushButton(_("Sign"))
1950         b.clicked.connect(lambda: self.do_sign(address_e, message_e, signature_e))
1951         hbox.addWidget(b)
1952
1953         b = QPushButton(_("Verify"))
1954         b.clicked.connect(lambda: self.do_verify(address_e, message_e, signature_e))
1955         hbox.addWidget(b)
1956
1957         b = QPushButton(_("Close"))
1958         b.clicked.connect(d.accept)
1959         hbox.addWidget(b)
1960         layout.addLayout(hbox, 4, 1)
1961         d.exec_()
1962
1963
1964     @protected
1965     def do_decrypt(self, message_e, pubkey_e, encrypted_e, password):
1966         try:
1967             decrypted = self.wallet.decrypt_message(str(pubkey_e.text()), str(encrypted_e.toPlainText()), password)
1968             message_e.setText(decrypted)
1969         except Exception as e:
1970             self.show_message(str(e))
1971
1972
1973     def do_encrypt(self, message_e, pubkey_e, encrypted_e):
1974         message = unicode(message_e.toPlainText())
1975         message = message.encode('utf-8')
1976         try:
1977             encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text()))
1978             encrypted_e.setText(encrypted)
1979         except Exception as e:
1980             self.show_message(str(e))
1981
1982
1983
1984     def encrypt_message(self, address = ''):
1985         d = QDialog(self)
1986         d.setModal(1)
1987         d.setWindowTitle(_('Encrypt/decrypt Message'))
1988         d.setMinimumSize(610, 490)
1989
1990         layout = QGridLayout(d)
1991
1992         message_e = QTextEdit()
1993         layout.addWidget(QLabel(_('Message')), 1, 0)
1994         layout.addWidget(message_e, 1, 1)
1995         layout.setRowStretch(2,3)
1996
1997         pubkey_e = QLineEdit()
1998         if address:
1999             pubkey = self.wallet.get_public_keys(address)[0]
2000             pubkey_e.setText(pubkey)
2001         layout.addWidget(QLabel(_('Public key')), 2, 0)
2002         layout.addWidget(pubkey_e, 2, 1)
2003
2004         encrypted_e = QTextEdit()
2005         layout.addWidget(QLabel(_('Encrypted')), 3, 0)
2006         layout.addWidget(encrypted_e, 3, 1)
2007         layout.setRowStretch(3,1)
2008
2009         hbox = QHBoxLayout()
2010         b = QPushButton(_("Encrypt"))
2011         b.clicked.connect(lambda: self.do_encrypt(message_e, pubkey_e, encrypted_e))
2012         hbox.addWidget(b)
2013
2014         b = QPushButton(_("Decrypt"))
2015         b.clicked.connect(lambda: self.do_decrypt(message_e, pubkey_e, encrypted_e))
2016         hbox.addWidget(b)
2017
2018         b = QPushButton(_("Close"))
2019         b.clicked.connect(d.accept)
2020         hbox.addWidget(b)
2021
2022         layout.addLayout(hbox, 4, 1)
2023         d.exec_()
2024
2025
2026     def question(self, msg):
2027         return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
2028
2029     def show_message(self, msg):
2030         QMessageBox.information(self, _('Message'), msg, _('OK'))
2031
2032     def password_dialog(self, msg=None):
2033         d = QDialog(self)
2034         d.setModal(1)
2035         d.setWindowTitle(_("Enter Password"))
2036
2037         pw = QLineEdit()
2038         pw.setEchoMode(2)
2039
2040         vbox = QVBoxLayout()
2041         if not msg:
2042             msg = _('Please enter your password')
2043         vbox.addWidget(QLabel(msg))
2044
2045         grid = QGridLayout()
2046         grid.setSpacing(8)
2047         grid.addWidget(QLabel(_('Password')), 1, 0)
2048         grid.addWidget(pw, 1, 1)
2049         vbox.addLayout(grid)
2050
2051         vbox.addLayout(ok_cancel_buttons(d))
2052         d.setLayout(vbox)
2053
2054         run_hook('password_dialog', pw, grid, 1)
2055         if not d.exec_(): return
2056         return unicode(pw.text())
2057
2058
2059
2060
2061
2062
2063
2064
2065     def tx_from_text(self, txt):
2066         "json or raw hexadecimal"
2067         try:
2068             txt.decode('hex')
2069             is_hex = True
2070         except:
2071             is_hex = False
2072
2073         if is_hex:
2074             try:
2075                 return Transaction.deserialize(txt)
2076             except:
2077                 traceback.print_exc(file=sys.stdout)
2078                 QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
2079                 return
2080
2081         try:
2082             tx_dict = json.loads(str(txt))
2083             assert "hex" in tx_dict.keys()
2084             tx = Transaction.deserialize(tx_dict["hex"])
2085             #if tx_dict.has_key("input_info"):
2086             #    input_info = json.loads(tx_dict['input_info'])
2087             #    tx.add_input_info(input_info)
2088             return tx
2089         except Exception:
2090             traceback.print_exc(file=sys.stdout)
2091             QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
2092
2093
2094     def read_tx_from_qrcode(self):
2095         data = run_hook('scan_qr_hook')
2096         if not data:
2097             return
2098         # transactions are binary, but qrcode seems to return utf8...
2099         z = data.decode('utf8')
2100         s = ''
2101         for b in z:
2102             s += chr(ord(b))
2103         data = s.encode('hex')
2104         tx = self.tx_from_text(data)
2105         if not tx:
2106             return
2107         self.show_transaction(tx)
2108
2109
2110     def read_tx_from_file(self):
2111         fileName = self.getOpenFileName(_("Select your transaction file"), "*.txn")
2112         if not fileName:
2113             return
2114         try:
2115             with open(fileName, "r") as f:
2116                 file_content = f.read()
2117         except (ValueError, IOError, os.error), reason:
2118             QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
2119
2120         return self.tx_from_text(file_content)
2121
2122
2123     @protected
2124     def sign_raw_transaction(self, tx, password):
2125         try:
2126             self.wallet.signrawtransaction(tx, [], password)
2127         except Exception as e:
2128             traceback.print_exc(file=sys.stdout)
2129             QMessageBox.warning(self, _("Error"), str(e))
2130
2131     def do_process_from_text(self):
2132         text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
2133         if not text:
2134             return
2135         tx = self.tx_from_text(text)
2136         if tx:
2137             self.show_transaction(tx)
2138
2139     def do_process_from_file(self):
2140         tx = self.read_tx_from_file()
2141         if tx:
2142             self.show_transaction(tx)
2143
2144     def do_process_from_txid(self):
2145         from electrum_nvc import transaction
2146         txid, ok = QInputDialog.getText(self, _('Lookup transaction'), _('Transaction ID') + ':')
2147         if ok and txid:
2148             r = self.network.synchronous_get([ ('blockchain.transaction.get',[str(txid)]) ])[0]
2149             if r:
2150                 tx = transaction.Transaction.deserialize(r)
2151                 if tx:
2152                     self.show_transaction(tx)
2153                 else:
2154                     self.show_message("unknown transaction")
2155
2156     def do_process_from_csvReader(self, csvReader):
2157         outputs = []
2158         errors = []
2159         errtext = ""
2160         try:
2161             for position, row in enumerate(csvReader):
2162                 address = row[0]
2163                 if not is_address(address):
2164                     errors.append((position, address))
2165                     continue
2166                 amount = Decimal(row[1])
2167                 amount = int(1000000*amount)
2168                 outputs.append(('address', address, amount))
2169         except (ValueError, IOError, os.error), reason:
2170             QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
2171             return
2172         if errors != []:
2173             for x in errors:
2174                 errtext += "CSV Row " + str(x[0]+1) + ": " + x[1] + "\n"
2175             QMessageBox.critical(None, _("Invalid Addresses"), _("ABORTING! Invalid Addresses found:") + "\n\n" + errtext)
2176             return
2177
2178         try:
2179             tx = self.wallet.make_unsigned_transaction(outputs, None, None)
2180         except Exception as e:
2181             self.show_message(str(e))
2182             return
2183
2184         self.show_transaction(tx)
2185
2186     def do_process_from_csv_file(self):
2187         fileName = self.getOpenFileName(_("Select your transaction CSV"), "*.csv")
2188         if not fileName:
2189             return
2190         try:
2191             with open(fileName, "r") as f:
2192                 csvReader = csv.reader(f)
2193                 self.do_process_from_csvReader(csvReader)
2194         except (ValueError, IOError, os.error), reason:
2195             QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
2196             return
2197
2198     def do_process_from_csv_text(self):
2199         text = text_dialog(self, _('Input CSV'), _("Please enter a list of outputs.") + '\n' \
2200                                + _("Format: address, amount. One output per line"), _("Load CSV"))
2201         if not text:
2202             return
2203         f = StringIO.StringIO(text)
2204         csvReader = csv.reader(f)
2205         self.do_process_from_csvReader(csvReader)
2206
2207
2208
2209     @protected
2210     def export_privkeys_dialog(self, password):
2211         if self.wallet.is_watching_only():
2212             self.show_message(_("This is a watching-only wallet"))
2213             return
2214
2215         d = QDialog(self)
2216         d.setWindowTitle(_('Private keys'))
2217         d.setMinimumSize(850, 300)
2218         vbox = QVBoxLayout(d)
2219
2220         msg = "%s\n%s\n%s" % (_("WARNING: ALL your private keys are secret."), 
2221                               _("Exposing a single private key can compromise your entire wallet!"), 
2222                               _("In particular, DO NOT use 'redeem private key' services proposed by third parties."))
2223         vbox.addWidget(QLabel(msg))
2224
2225         e = QTextEdit()
2226         e.setReadOnly(True)
2227         vbox.addWidget(e)
2228
2229         defaultname = 'electrum-private-keys.csv'
2230         select_msg = _('Select file to export your private keys to')
2231         hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
2232         vbox.addLayout(hbox)
2233
2234         h, b = ok_cancel_buttons2(d, _('Export'))
2235         b.setEnabled(False)
2236         vbox.addLayout(h)
2237
2238         private_keys = {}
2239         addresses = self.wallet.addresses(True)
2240         done = False
2241         def privkeys_thread():
2242             for addr in addresses:
2243                 time.sleep(0.1)
2244                 if done: 
2245                     break
2246                 private_keys[addr] = "\n".join(self.wallet.get_private_key(addr, password))
2247                 d.emit(SIGNAL('computing_privkeys'))
2248             d.emit(SIGNAL('show_privkeys'))
2249
2250         def show_privkeys():
2251             s = "\n".join( map( lambda x: x[0] + "\t"+ x[1], private_keys.items()))
2252             e.setText(s)
2253             b.setEnabled(True)
2254
2255         d.connect(d, QtCore.SIGNAL('computing_privkeys'), lambda: e.setText("Please wait... %d/%d"%(len(private_keys),len(addresses))))
2256         d.connect(d, QtCore.SIGNAL('show_privkeys'), show_privkeys)
2257         threading.Thread(target=privkeys_thread).start()
2258
2259         if not d.exec_():
2260             done = True
2261             return
2262
2263         filename = filename_e.text()
2264         if not filename:
2265             return
2266
2267         try:
2268             self.do_export_privkeys(filename, private_keys, csv_button.isChecked())
2269         except (IOError, os.error), reason:
2270             export_error_label = _("Electrum was unable to produce a private key-export.")
2271             QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
2272
2273         except Exception as e:
2274             self.show_message(str(e))
2275             return
2276
2277         self.show_message(_("Private keys exported."))
2278
2279
2280     def do_export_privkeys(self, fileName, pklist, is_csv):
2281         with open(fileName, "w+") as f:
2282             if is_csv:
2283                 transaction = csv.writer(f)
2284                 transaction.writerow(["address", "private_key"])
2285                 for addr, pk in pklist.items():
2286                     transaction.writerow(["%34s"%addr,pk])
2287             else:
2288                 import json
2289                 f.write(json.dumps(pklist, indent = 4))
2290
2291
2292     def do_import_labels(self):
2293         labelsFile = self.getOpenFileName(_("Open labels file"), "*.dat")
2294         if not labelsFile: return
2295         try:
2296             f = open(labelsFile, 'r')
2297             data = f.read()
2298             f.close()
2299             for key, value in json.loads(data).items():
2300                 self.wallet.set_label(key, value)
2301             QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
2302         except (IOError, os.error), reason:
2303             QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
2304
2305
2306     def do_export_labels(self):
2307         labels = self.wallet.labels
2308         try:
2309             fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.dat', "*.dat")
2310             if fileName:
2311                 with open(fileName, 'w+') as f:
2312                     json.dump(labels, f)
2313                 QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
2314         except (IOError, os.error), reason:
2315             QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
2316
2317
2318     def export_history_dialog(self):
2319
2320         d = QDialog(self)
2321         d.setWindowTitle(_('Export History'))
2322         d.setMinimumSize(400, 200)
2323         vbox = QVBoxLayout(d)
2324
2325         defaultname = os.path.expanduser('~/electrum-nvc-history.csv')
2326         select_msg = _('Select file to export your wallet transactions to')
2327
2328         hbox, filename_e, csv_button = filename_field(self, self.config, defaultname, select_msg)
2329         vbox.addLayout(hbox)
2330
2331         vbox.addStretch(1)
2332
2333         h, b = ok_cancel_buttons2(d, _('Export'))
2334         vbox.addLayout(h)
2335         if not d.exec_():
2336             return
2337
2338         filename = filename_e.text()
2339         if not filename:
2340             return
2341
2342         try:
2343             self.do_export_history(self.wallet, filename, csv_button.isChecked())
2344         except (IOError, os.error), reason:
2345             export_error_label = _("Electrum was unable to produce a transaction export.")
2346             QMessageBox.critical(self, _("Unable to export history"), export_error_label + "\n" + str(reason))
2347             return
2348
2349         QMessageBox.information(self,_("History exported"), _("Your wallet history has been successfully exported."))
2350
2351
2352     def do_export_history(self, wallet, fileName, is_csv):
2353         history = wallet.get_tx_history()
2354         lines = []
2355         for item in history:
2356             tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
2357             if confirmations:
2358                 if timestamp is not None:
2359                     try:
2360                         time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
2361                     except [RuntimeError, TypeError, NameError] as reason:
2362                         time_string = "unknown"
2363                         pass
2364                 else:
2365                     time_string = "unknown"
2366             else:
2367                 time_string = "pending"
2368
2369             if value is not None:
2370                 value_string = format_satoshis(value, True)
2371             else:
2372                 value_string = '--'
2373
2374             if fee is not None:
2375                 fee_string = format_satoshis(fee, True)
2376             else:
2377                 fee_string = '0'
2378
2379             if tx_hash:
2380                 label, is_default_label = wallet.get_label(tx_hash)
2381                 label = label.encode('utf-8')
2382             else:
2383                 label = ""
2384
2385             balance_string = format_satoshis(balance, False)
2386             if is_csv:
2387                 lines.append([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
2388             else:
2389                 lines.append({'txid':tx_hash, 'date':"%16s"%time_string, 'label':label, 'value':value_string})
2390
2391         with open(fileName, "w+") as f:
2392             if is_csv:
2393                 transaction = csv.writer(f)
2394                 transaction.writerow(["transaction_hash","label", "confirmations", "value", "fee", "balance", "timestamp"])
2395                 for line in lines:
2396                     transaction.writerow(line)
2397             else:
2398                 import json
2399                 f.write(json.dumps(lines, indent = 4))
2400
2401
2402     def sweep_key_dialog(self):
2403         d = QDialog(self)
2404         d.setWindowTitle(_('Sweep private keys'))
2405         d.setMinimumSize(600, 300)
2406
2407         vbox = QVBoxLayout(d)
2408         vbox.addWidget(QLabel(_("Enter private keys")))
2409
2410         keys_e = QTextEdit()
2411         keys_e.setTabChangesFocus(True)
2412         vbox.addWidget(keys_e)
2413
2414         h, address_e = address_field(self.wallet.addresses())
2415         vbox.addLayout(h)
2416
2417         vbox.addStretch(1)
2418         hbox, button = ok_cancel_buttons2(d, _('Sweep'))
2419         vbox.addLayout(hbox)
2420         button.setEnabled(False)
2421
2422         def get_address():
2423             addr = str(address_e.text())
2424             if bitcoin.is_address(addr):
2425                 return addr
2426
2427         def get_pk():
2428             pk = str(keys_e.toPlainText()).strip()
2429             if Wallet.is_private_key(pk):
2430                 return pk.split()
2431
2432         f = lambda: button.setEnabled(get_address() is not None and get_pk() is not None)
2433         keys_e.textChanged.connect(f)
2434         address_e.textChanged.connect(f)
2435         if not d.exec_():
2436             return
2437
2438         fee = self.wallet.fee
2439         tx = Transaction.sweep(get_pk(), self.network, get_address(), fee)
2440         self.show_transaction(tx)
2441
2442
2443     @protected
2444     def do_import_privkey(self, password):
2445         if not self.wallet.has_imported_keys():
2446             r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
2447                                          + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
2448                                          + _('Are you sure you understand what you are doing?'), 3, 4)
2449             if r == 4: return
2450
2451         text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
2452         if not text: return
2453
2454         text = str(text).split()
2455         badkeys = []
2456         addrlist = []
2457         for key in text:
2458             try:
2459                 addr = self.wallet.import_key(key, password)
2460             except Exception as e:
2461                 badkeys.append(key)
2462                 continue
2463             if not addr:
2464                 badkeys.append(key)
2465             else:
2466                 addrlist.append(addr)
2467         if addrlist:
2468             QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
2469         if badkeys:
2470             QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
2471         self.update_address_tab()
2472         self.update_history_tab()
2473
2474
2475     def settings_dialog(self):
2476         d = QDialog(self)
2477         d.setWindowTitle(_('Electrum Settings'))
2478         d.setModal(1)
2479         vbox = QVBoxLayout()
2480         grid = QGridLayout()
2481         grid.setColumnStretch(0,1)
2482
2483         nz_label = QLabel(_('Display zeros') + ':')
2484         grid.addWidget(nz_label, 0, 0)
2485         nz_e = AmountEdit(None,True)
2486         nz_e.setText("%d"% self.num_zeros)
2487         grid.addWidget(nz_e, 0, 1)
2488         msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
2489         grid.addWidget(HelpButton(msg), 0, 2)
2490         if not self.config.is_modifiable('num_zeros'):
2491             for w in [nz_e, nz_label]: w.setEnabled(False)
2492
2493         lang_label=QLabel(_('Language') + ':')
2494         grid.addWidget(lang_label, 1, 0)
2495         lang_combo = QComboBox()
2496         from electrum_nvc.i18n import languages
2497         lang_combo.addItems(languages.values())
2498         try:
2499             index = languages.keys().index(self.config.get("language",''))
2500         except Exception:
2501             index = 0
2502         lang_combo.setCurrentIndex(index)
2503         grid.addWidget(lang_combo, 1, 1)
2504         grid.addWidget(HelpButton(_('Select which language is used in the GUI (after restart).')+' '), 1, 2)
2505         if not self.config.is_modifiable('language'):
2506             for w in [lang_combo, lang_label]: w.setEnabled(False)
2507
2508
2509         fee_label = QLabel(_('Transaction fee') + ':')
2510         grid.addWidget(fee_label, 2, 0)
2511         fee_e = BTCAmountEdit(self.get_decimal_point)
2512         fee_e.setAmount(self.wallet.fee)
2513         grid.addWidget(fee_e, 2, 1)
2514         msg = _('Fee per kilobyte of transaction.') + '\n' \
2515             + _('Recommended value') + ': ' + self.format_amount(1000) + ' ' + self.base_unit()
2516         grid.addWidget(HelpButton(msg), 2, 2)
2517         if not self.config.is_modifiable('fee_per_kb'):
2518             for w in [fee_e, fee_label]: w.setEnabled(False)
2519
2520         units = ['NVC', 'mNVC', 'bits']
2521         unit_label = QLabel(_('Base unit') + ':')
2522         grid.addWidget(unit_label, 3, 0)
2523         unit_combo = QComboBox()
2524         unit_combo.addItems(units)
2525         unit_combo.setCurrentIndex(units.index(self.base_unit()))
2526         grid.addWidget(unit_combo, 3, 1)
2527         grid.addWidget(HelpButton(_('Base unit of your wallet.')\
2528                                              + '\n1NVC=1000mNVC.\n' \
2529                                              + _(' These settings affects the fields in the Send tab')+' '), 3, 2)
2530
2531         usechange_cb = QCheckBox(_('Use change addresses'))
2532         usechange_cb.setChecked(self.wallet.use_change)
2533         grid.addWidget(usechange_cb, 4, 0)
2534         grid.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions.')+' '), 4, 2)
2535         if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
2536
2537         block_explorers = ['explorer.novaco.in', 'novacoin.su']
2538         block_ex_label = QLabel(_('Online Block Explorer') + ':')
2539         grid.addWidget(block_ex_label, 5, 0)
2540         block_ex_combo = QComboBox()
2541         block_ex_combo.addItems(block_explorers)
2542         block_ex_combo.setCurrentIndex(block_explorers.index(self.config.get('block_explorer', 'explorer.novaco.in')))
2543         grid.addWidget(block_ex_combo, 5, 1)
2544         grid.addWidget(HelpButton(_('Choose which online block explorer to use for functions that open a web browser')+' '), 5, 2)
2545
2546         show_tx = self.config.get('show_before_broadcast', False)
2547         showtx_cb = QCheckBox(_('Show before broadcast'))
2548         showtx_cb.setChecked(show_tx)
2549         grid.addWidget(showtx_cb, 6, 0)
2550         grid.addWidget(HelpButton(_('Display the details of your transactions before broadcasting it.')), 6, 2)
2551
2552         vbox.addLayout(grid)
2553         vbox.addStretch(1)
2554         vbox.addLayout(ok_cancel_buttons(d))
2555         d.setLayout(vbox)
2556
2557         # run the dialog
2558         if not d.exec_(): return
2559
2560         fee = fee_e.get_amount()
2561         if fee is None:
2562             QMessageBox.warning(self, _('Error'), _('Invalid value') +': %s'%fee, _('OK'))
2563             return
2564
2565         self.wallet.set_fee(fee)
2566
2567         nz = unicode(nz_e.text())
2568         try:
2569             nz = int( nz )
2570             if nz>8: nz=8
2571         except Exception:
2572             QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
2573             return
2574
2575         if self.num_zeros != nz:
2576             self.num_zeros = nz
2577             self.config.set_key('num_zeros', nz, True)
2578             self.update_history_tab()
2579             self.update_address_tab()
2580
2581         usechange_result = usechange_cb.isChecked()
2582         if self.wallet.use_change != usechange_result:
2583             self.wallet.use_change = usechange_result
2584             self.wallet.storage.put('use_change', self.wallet.use_change)
2585
2586         if showtx_cb.isChecked() != show_tx:
2587             self.config.set_key('show_before_broadcast', not show_tx)
2588
2589         unit_result = units[unit_combo.currentIndex()]
2590         if self.base_unit() != unit_result:
2591             if unit_result == 'NVC':
2592                 self.decimal_point = 6
2593             elif unit_result == 'mNVC':
2594                 self.decimal_point = 3
2595             elif unit_result == 'bits':
2596                 self.decimal_point = 2
2597             else:
2598                 raise Exception('Unknown base unit')
2599             self.config.set_key('decimal_point', self.decimal_point, True)
2600             self.update_history_tab()
2601             self.update_status()
2602
2603         need_restart = False
2604
2605         lang_request = languages.keys()[lang_combo.currentIndex()]
2606         if lang_request != self.config.get('language'):
2607             self.config.set_key("language", lang_request, True)
2608             need_restart = True
2609
2610         be_result = block_explorers[block_ex_combo.currentIndex()]
2611         self.config.set_key('block_explorer', be_result, True)
2612
2613         run_hook('close_settings_dialog')
2614
2615         if need_restart:
2616             QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
2617
2618
2619     def run_network_dialog(self):
2620         if not self.network:
2621             return
2622         NetworkDialog(self.wallet.network, self.config, self).do_exec()
2623
2624     def closeEvent(self, event):
2625         self.tray.hide()
2626         self.config.set_key("is_maximized", self.isMaximized())
2627         if not self.isMaximized():
2628             g = self.geometry()
2629             self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()])
2630         self.save_column_widths()
2631         self.config.set_key("console-history", self.console.history[-50:], True)
2632         self.wallet.storage.put('accounts_expanded', self.accounts_expanded)
2633         event.accept()
2634
2635
2636     def plugins_dialog(self):
2637         from electrum_nvc.plugins import plugins
2638
2639         d = QDialog(self)
2640         d.setWindowTitle(_('Electrum Plugins'))
2641         d.setModal(1)
2642
2643         vbox = QVBoxLayout(d)
2644
2645         # plugins
2646         scroll = QScrollArea()
2647         scroll.setEnabled(True)
2648         scroll.setWidgetResizable(True)
2649         scroll.setMinimumSize(400,250)
2650         vbox.addWidget(scroll)
2651
2652         w = QWidget()
2653         scroll.setWidget(w)
2654         w.setMinimumHeight(len(plugins)*35)
2655
2656         grid = QGridLayout()
2657         grid.setColumnStretch(0,1)
2658         w.setLayout(grid)
2659
2660         def do_toggle(cb, p, w):
2661             r = p.toggle()
2662             cb.setChecked(r)
2663             if w: w.setEnabled(r)
2664
2665         def mk_toggle(cb, p, w):
2666             return lambda: do_toggle(cb,p,w)
2667
2668         for i, p in enumerate(plugins):
2669             try:
2670                 cb = QCheckBox(p.fullname())
2671                 cb.setDisabled(not p.is_available())
2672                 cb.setChecked(p.is_enabled())
2673                 grid.addWidget(cb, i, 0)
2674                 if p.requires_settings():
2675                     w = p.settings_widget(self)
2676                     w.setEnabled( p.is_enabled() )
2677                     grid.addWidget(w, i, 1)
2678                 else:
2679                     w = None
2680                 cb.clicked.connect(mk_toggle(cb,p,w))
2681                 grid.addWidget(HelpButton(p.description()), i, 2)
2682             except Exception:
2683                 print_msg(_("Error: cannot display plugin"), p)
2684                 traceback.print_exc(file=sys.stdout)
2685         grid.setRowStretch(i+1,1)
2686
2687         vbox.addLayout(close_button(d))
2688
2689         d.exec_()
2690
2691
2692     def show_account_details(self, k):
2693         account = self.wallet.accounts[k]
2694
2695         d = QDialog(self)
2696         d.setWindowTitle(_('Account Details'))
2697         d.setModal(1)
2698
2699         vbox = QVBoxLayout(d)
2700         name = self.wallet.get_account_name(k)
2701         label = QLabel('Name: ' + name)
2702         vbox.addWidget(label)
2703
2704         vbox.addWidget(QLabel(_('Address type') + ': ' + account.get_type()))
2705
2706         vbox.addWidget(QLabel(_('Derivation') + ': ' + k))
2707
2708         vbox.addWidget(QLabel(_('Master Public Key:')))
2709
2710         text = QTextEdit()
2711         text.setReadOnly(True)
2712         text.setMaximumHeight(170)
2713         vbox.addWidget(text)
2714
2715         mpk_text = '\n'.join( account.get_master_pubkeys() )
2716         text.setText(mpk_text)
2717
2718         vbox.addLayout(close_button(d))
2719         d.exec_()