Work in progress on Label syncing
[electrum-nvc.git] / gui / gui_classic.py
index bb4e3c6..133dd75 100644 (file)
@@ -41,6 +41,7 @@ except:
 from electrum.wallet import format_satoshis
 from electrum.bitcoin import Transaction, is_valid
 from electrum import mnemonic
+from electrum import util, bitcoin, commands
 
 import bmp, pyqrnative
 import exchange_rate
@@ -250,17 +251,18 @@ def waiting_dialog(f):
 def ok_cancel_buttons(dialog):
     hbox = QHBoxLayout()
     hbox.addStretch(1)
-    b = QPushButton("OK")
-    hbox.addWidget(b)
-    b.clicked.connect(dialog.accept)
     b = QPushButton("Cancel")
     hbox.addWidget(b)
     b.clicked.connect(dialog.reject)
+    b = QPushButton("OK")
+    hbox.addWidget(b)
+    b.clicked.connect(dialog.accept)
+    b.setDefault(True)
     return hbox
 
 
 default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], 
-       "receive":[[370],[370,200,130,130],[370,200,130,130]] }
+       "receive":[[370],[370,200,130]] }
 
 class ElectrumWindow(QMainWindow):
 
@@ -271,13 +273,14 @@ class ElectrumWindow(QMainWindow):
         self.config = config
         self.init_plugins()
 
-        self.wallet.interface.register_callback('updated', self.update_callback)
-        self.wallet.interface.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')) )
-        self.wallet.interface.register_callback('disconnected', self.update_callback)
-        self.wallet.interface.register_callback('disconnecting', self.update_callback)
+        self.create_status_bar()
+
+        self.wallet.interface.register_callback('updated', lambda: self.emit(QtCore.SIGNAL('update_wallet')))
+        self.wallet.interface.register_callback('banner', lambda: self.emit(QtCore.SIGNAL('banner_signal')))
+        self.wallet.interface.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
+        self.wallet.interface.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
 
         self.expert_mode = config.get('classic_expert_mode', False)
-        self.merchant_name = config.get('merchant_name', 'Invoice')
 
         set_language(config.get('language'))
 
@@ -294,7 +297,6 @@ class ElectrumWindow(QMainWindow):
         tabs.setMinimumSize(600, 400)
         tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
         self.setCentralWidget(tabs)
-        self.create_status_bar()
 
         g = self.config.get("winpos-qt",[100, 100, 840, 400])
         self.setGeometry(g[0], g[1], g[2], g[3])
@@ -307,7 +309,8 @@ class ElectrumWindow(QMainWindow):
         QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
         QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
         
-        self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet)
+        self.connect(self, QtCore.SIGNAL('update_wallet'), self.update_wallet)
+        self.connect(self, QtCore.SIGNAL('update_status'), self.update_status)
         self.connect(self, QtCore.SIGNAL('banner_signal'), lambda: self.console.showMessage(self.wallet.banner) )
         self.history_list.setFocus(True)
         
@@ -387,10 +390,8 @@ class ElectrumWindow(QMainWindow):
                     self.payto_e.setText(s)
 
 
-    def update_callback(self):
-        self.emit(QtCore.SIGNAL('updatesignal'))
 
-    def update_wallet(self):
+    def update_status(self):
         if self.wallet.interface and self.wallet.interface.is_connected:
             if not self.wallet.up_to_date:
                 text = _("Synchronizing...")
@@ -409,6 +410,8 @@ class ElectrumWindow(QMainWindow):
         self.statusBar().showMessage(text)
         self.status_button.setIcon( icon )
 
+    def update_wallet(self):
+        self.update_status()
         if self.wallet.up_to_date or not self.wallet.interface.is_connected:
             self.update_history_tab()
             self.update_receive_tab()
@@ -453,7 +456,7 @@ class ElectrumWindow(QMainWindow):
 
 
     def show_tx_details(self, tx):
-        dialog = QDialog(None)
+        dialog = QDialog(self)
         dialog.setModal(1)
         dialog.setWindowTitle(_("Transaction Details"))
         vbox = QVBoxLayout()
@@ -518,6 +521,9 @@ class ElectrumWindow(QMainWindow):
         text = unicode( item.text(2) )
         if text: 
             self.wallet.labels[tx_hash] = text
+            # Label changed
+            self.run_hook('label_changed',(self, str(tx_hash), text))
+
             item.setForeground(2, QBrush(QColor('black')))
         else:
             if s: self.wallet.labels.pop(tx_hash)
@@ -529,10 +535,9 @@ class ElectrumWindow(QMainWindow):
 
     def edit_label(self, is_recv):
         l = self.receive_list if is_recv else self.contacts_list
-        c = 2 if is_recv else 1
         item = l.currentItem()
         item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
-        l.editItem( item, c )
+        l.editItem( item, 1 )
         item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
 
 
@@ -561,6 +566,7 @@ class ElectrumWindow(QMainWindow):
                     if old_addr != addr:
                         self.wallet.labels[addr] = text
                         changed = True
+                        self.run_hook('label_changed',(self, addr, text))
                 else:
                     print_error("Error: This is one of your aliases")
                     label = self.wallet.labels.get(addr,'')
@@ -741,8 +747,12 @@ class ElectrumWindow(QMainWindow):
         self.completions.setStringList(l)
 
 
+    def protected(func):
+        return lambda s, *args: s.do_protect(func, args)
 
-    def do_send(self):
+
+    @protected
+    def do_send(self, password):
 
         label = unicode( self.message_e.text() )
         r = unicode( self.payto_e.text() )
@@ -777,20 +787,13 @@ class ElectrumWindow(QMainWindow):
             QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
             return
 
-        if self.wallet.use_encryption:
-            password = self.password_dialog()
-            if not password:
-                return
-        else:
-            password = None
-
         try:
             tx = self.wallet.mktx( [(to_address, amount)], password, fee)
         except BaseException, e:
             self.show_message(str(e))
             return
 
-        self.run_hook('send_tx', (wallet, self, tx))
+        self.run_hook('send_tx', (self.wallet, self, tx))
 
         if label: 
             self.wallet.labels[tx.hash()] = label
@@ -898,7 +901,7 @@ class ElectrumWindow(QMainWindow):
 
 
     def create_receive_tab(self):
-        l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _(''), _('Balance'), _('Tx')])
+        l,w,hbox = self.create_list_tab([ _('Address'), _('Label'), _('Balance'), _('Tx')])
         l.setContextMenuPolicy(Qt.CustomContextMenu)
         l.customContextMenuRequested.connect(self.create_receive_menu)
         self.connect(l, SIGNAL('itemDoubleClicked(QTreeWidgetItem*, int)'), lambda a, b: self.address_label_clicked(a,b,l,0,1))
@@ -968,11 +971,14 @@ class ElectrumWindow(QMainWindow):
         item = self.receive_list.itemAt(position)
         if not item: return
         addr = unicode(item.text(0))
+        if not is_valid(addr): 
+            item.setExpanded(not item.isExpanded())
+            return 
         menu = QMenu()
         menu.addAction(_("Copy to clipboard"), lambda: self.app.clipboard().setText(addr))
-        menu.addAction(_("QR code"), lambda: ElectrumWindow.show_qrcode("bitcoin:" + addr, _("Address")) )
+        menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")) )
         menu.addAction(_("Edit label"), lambda: self.edit_label(True))
-        menu.addAction(_("Private key"), lambda: self.view_private_key(addr))
+        menu.addAction(_("Private key"), lambda: self.show_private_key(addr))
         menu.addAction(_("Sign message"), lambda: self.sign_message(addr))
         if addr in self.wallet.imported_keys:
             menu.addAction(_("Remove from wallet"), lambda: self.delete_imported_key(addr))
@@ -1036,6 +1042,7 @@ class ElectrumWindow(QMainWindow):
 
 
     def update_receive_item(self, item):
+        item.setFont(0, QFont(MONOSPACE_FONT))
         address = str(item.data(0,0).toString())
         label = self.wallet.labels.get(address,'')
         item.setData(1,0,label)
@@ -1044,7 +1051,7 @@ class ElectrumWindow(QMainWindow):
                 
         c, u = self.wallet.get_addr_balance(address)
         balance = format_satoshis( c + u, False, self.wallet.num_zeros )
-        item.setData(3,0,balance)
+        item.setData(2,0,balance)
 
         if self.expert_mode:
             if address in self.wallet.frozen_addresses: 
@@ -1057,8 +1064,8 @@ class ElectrumWindow(QMainWindow):
         l = self.receive_list
         
         l.clear()
+        l.setColumnHidden(2, not self.expert_mode)
         l.setColumnHidden(3, not self.expert_mode)
-        l.setColumnHidden(4, not self.expert_mode)
         if not self.expert_mode:
             width = self.column_widths['receive'][0][0]
             l.setColumnWidth(0, width)
@@ -1070,7 +1077,7 @@ class ElectrumWindow(QMainWindow):
         for k, account in self.wallet.accounts.items():
             name = account.get('name',str(k))
             c,u = self.wallet.get_account_balance(k)
-            account_item = QTreeWidgetItem( [ name, '', '', format_satoshis(c+u), ''] )
+            account_item = QTreeWidgetItem( [ name, '', format_satoshis(c+u), ''] )
             l.addTopLevelItem(account_item)
             account_item.setExpanded(True)
             
@@ -1095,9 +1102,7 @@ class ElectrumWindow(QMainWindow):
                             gap = 0
 
                     num_tx = '*' if h == ['*'] else "%d"%len(h)
-                    item = QTreeWidgetItem( [ address, '', '', '', num_tx] )
-                    item.setFont(0, QFont(MONOSPACE_FONT))
-                    item.setFont(2, QFont(MONOSPACE_FONT))
+                    item = QTreeWidgetItem( [ address, '', '', num_tx] )
                     self.update_receive_item(item)
                     if is_red:
                         item.setBackgroundColor(1, QColor('red'))
@@ -1105,13 +1110,11 @@ class ElectrumWindow(QMainWindow):
 
         if self.wallet.imported_keys:
             c,u = self.wallet.get_imported_balance()
-            account_item = QTreeWidgetItem( [ _('Imported'), '', '', format_satoshis(c+u), ''] )
+            account_item = QTreeWidgetItem( [ _('Imported'), '', format_satoshis(c+u), ''] )
             l.addTopLevelItem(account_item)
             account_item.setExpanded(True)
             for address in self.wallet.imported_keys.keys():
-                item = QTreeWidgetItem( [ address, '', '', '', ''] )
-                item.setFont(0, QFont(MONOSPACE_FONT))
-                item.setFont(2, QFont(MONOSPACE_FONT))
+                item = QTreeWidgetItem( [ address, '', '', ''] )
                 self.update_receive_item(item)
                 account_item.addChild(item)
                 
@@ -1158,7 +1161,6 @@ class ElectrumWindow(QMainWindow):
 
     def create_console_tab(self):
         from qt_console import Console
-        from electrum import util, bitcoin, commands
         self.console = console = Console()
         self.console.history = self.config.get("console-history",[])
         self.console.history_index = len(self.console.history)
@@ -1194,10 +1196,12 @@ class ElectrumWindow(QMainWindow):
             sb.addPermanentWidget( StatusBarButton( QIcon(":icons/lock.png"), _("Password"), lambda: self.change_password_dialog(self.wallet, self) ) )
         sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
         if self.wallet.seed:
-            sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), lambda: self.show_seed_dialog(self.wallet, self) ) )
+            sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
         self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), lambda: self.network_dialog(self.wallet, self) ) 
         sb.addPermanentWidget( self.status_button )
 
+        self.run_hook('create_status_bar', (sb,))
+
         self.setStatusBar(sb)
         
     def go_lite(self):
@@ -1224,7 +1228,7 @@ class ElectrumWindow(QMainWindow):
                 QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
 
     def show_master_public_key(self):
-        dialog = QDialog(None)
+        dialog = QDialog(self)
         dialog.setModal(1)
         dialog.setWindowTitle(_("Master Public Key"))
 
@@ -1232,7 +1236,7 @@ class ElectrumWindow(QMainWindow):
         main_text.setText(self.wallet.get_master_public_key())
         main_text.setReadOnly(True)
         main_text.setMaximumHeight(170)
-        qrw = QRCodeWidget(self.wallet.get_master_public_key(), 6)
+        qrw = QRCodeWidget(self.wallet.get_master_public_key())
 
         ok_button = QPushButton(_("OK"))
         ok_button.setDefault(True)
@@ -1255,30 +1259,22 @@ class ElectrumWindow(QMainWindow):
         dialog.exec_()
         
 
-    @classmethod
-    def show_seed_dialog(self, wallet, parent=None):
-        if not wallet.seed:
+    @protected
+    def show_seed_dialog(self, password):
+        if not self.wallet.seed:
             QMessageBox.information(parent, _('Message'), _('No seed'), _('OK'))
             return
-
-        if wallet.use_encryption:
-            password = parent.password_dialog()
-            if not password:
-                return
-        else:
-            password = None
-            
         try:
-            seed = wallet.decode_seed(password)
+            seed = self.wallet.decode_seed(password)
         except:
             QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
             return
+        self.show_seed(seed, self)
 
-        self.show_seed(seed)
 
     @classmethod
-    def show_seed(self, seed):
-        dialog = QDialog(None)
+    def show_seed(self, seed, parent=None):
+        dialog = QDialog(parent)
         dialog.setModal(1)
         dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
 
@@ -1301,7 +1297,7 @@ class ElectrumWindow(QMainWindow):
         logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
         logo.setMaximumWidth(60)
 
-        qrw = QRCodeWidget(seed, 4)
+        qrw = QRCodeWidget(seed)
 
         ok_button = QPushButton(_("OK"))
         ok_button.setDefault(True)
@@ -1329,10 +1325,9 @@ class ElectrumWindow(QMainWindow):
         dialog.setLayout(vbox)
         dialog.exec_()
 
-    @staticmethod
-    def show_qrcode(data, title = "QR code"):
+    def show_qrcode(self, data, title = "QR code"):
         if not data: return
-        d = QDialog(None)
+        d = QDialog(self)
         d.setModal(1)
         d.setWindowTitle(title)
         d.setMinimumSize(270, 300)
@@ -1361,25 +1356,42 @@ class ElectrumWindow(QMainWindow):
         d.setLayout(vbox)
         d.exec_()
 
-    def view_private_key(self,address):
-        if not address: return
+
+    def do_protect(self, func, args):
         if self.wallet.use_encryption:
             password = self.password_dialog()
             if not password:
                 return
         else:
             password = None
+            
+        if args != (False,):
+            args = (self,) + args + (password,)
+        else:
+            args = (self,password)
+        apply( func, args)
 
+
+    @protected
+    def show_private_key(self, address, password):
+        if not address: return
         try:
             pk = self.wallet.get_private_key(address, password)
         except BaseException, e:
             self.show_message(str(e))
             return
-
         QMessageBox.information(self, _('Private key'), 'Address'+ ': ' + address + '\n\n' + _('Private key') + ': ' + pk, _('OK'))
 
 
-    def sign_message(self,address):
+    @protected
+    def do_sign(self, address, message, signature, password):
+        try:
+            sig = self.wallet.sign_message(str(address.text()), str(message.toPlainText()), password)
+            signature.setText(sig)
+        except BaseException, e:
+            self.show_message(str(e))
+
+    def sign_message(self, address):
         if not address: return
         d = QDialog(self)
         d.setModal(1)
@@ -1406,25 +1418,11 @@ class ElectrumWindow(QMainWindow):
         layout.addWidget(sign_signature, 3, 1)
         layout.setRowStretch(3,1)
 
-        def do_sign():
-            if self.wallet.use_encryption:
-                password = self.password_dialog()
-                if not password:
-                    return
-            else:
-                password = None
-
-            try:
-                signature = self.wallet.sign_message(str(sign_address.text()), str(sign_message.toPlainText()), password)
-                sign_signature.setText(signature)
-            except BaseException, e:
-                self.show_message(str(e))
-                return
 
         hbox = QHBoxLayout()
         b = QPushButton(_("Sign"))
         hbox.addWidget(b)
-        b.clicked.connect(do_sign)
+        b.clicked.connect(lambda: self.do_sign(sign_address, sign_message, sign_signature))
         b = QPushButton(_("Close"))
         b.clicked.connect(d.accept)
         hbox.addWidget(b)
@@ -1635,8 +1633,8 @@ class ElectrumWindow(QMainWindow):
         tree_widget.setColumnWidth(0, 300)
         tree_widget.setColumnWidth(1, 50)
 
-        for output in tx.d["outputs"]:
-            item = QTreeWidgetItem( ["%s" %(output["address"]), "%s" % ( format_satoshis(output["value"]))] )
+        for address, value in tx.outputs:
+            item = QTreeWidgetItem( [address, "%s" % ( format_satoshis(value))] )
             tree_widget.addTopLevelItem(item)
 
         tree_widget.setMaximumHeight(100)
@@ -1688,14 +1686,8 @@ class ElectrumWindow(QMainWindow):
         return self.tx_dict_from_text(file_content)
 
 
-    def sign_raw_transaction(self, tx, input_info):
-        if self.wallet.use_encryption:
-            password = self.password_dialog()
-            if not password:
-                return
-        else:
-            password = None
-
+    @protected
+    def sign_raw_transaction(self, tx, input_info, password):
         try:
             self.wallet.signrawtransaction(tx, input_info, [], password)
             
@@ -1792,15 +1784,10 @@ class ElectrumWindow(QMainWindow):
             self.create_send_transaction_window(tx_dict)
 
 
-    def do_export_privkeys(self):
+    @protected
+    def do_export_privkeys(self, password):
         self.show_message("%s\n%s\n%s" % (_("WARNING: ALL your private keys are secret."),  _("Exposing a single private key can compromise your entire wallet!"), _("In particular, DO NOT use 'redeem private key' services proposed by third parties.")))
 
-        if self.wallet.use_encryption:
-            password = self.password_dialog()
-            if not password:
-                return
-        else:
-            password = None
         try:
             select_export = _('Select file to export your private keys to')
             fileName = QFileDialog.getSaveFileName(QWidget(), select_export, os.path.expanduser('~/electrum-private-keys.csv'), "*.csv")
@@ -1839,7 +1826,6 @@ class ElectrumWindow(QMainWindow):
             QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
         
 
-
     def do_export_labels(self):
         labels = self.wallet.labels
         try:
@@ -1851,11 +1837,14 @@ class ElectrumWindow(QMainWindow):
         except (IOError, os.error), reason:
             QMessageBox.critical(None, "Unable to export labels", _("Electrum was unable to export your labels.")+"\n" + str(reason))
 
+
     def do_export_history(self):
         from gui_lite import csv_transaction
         csv_transaction(self.wallet)
 
-    def do_import_privkey(self):
+
+    @protected
+    def do_import_privkey(self, password):
         if not self.wallet.imported_keys:
             r = QMessageBox.question(None, _('Warning'), _('Warning: Imported keys are not recoverable from seed.') + ' ' \
                                          + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '\n\n' \
@@ -1865,12 +1854,6 @@ class ElectrumWindow(QMainWindow):
         text, ok = QInputDialog.getText(self, _('Import private key'), _('Private Key') + ':')
         if not ok: return
         sec = str(text).strip()
-        if self.wallet.use_encryption:
-            password = self.password_dialog()
-            if not password:
-                return
-        else:
-            password = None
         try:
             addr = self.wallet.import_key(sec, password)
             if not addr:
@@ -1882,6 +1865,7 @@ class ElectrumWindow(QMainWindow):
         except BaseException as e:
             QMessageBox.critical(None, _("Unable to import key"), str(e))
 
+
     def settings_dialog(self):
         d = QDialog(self)
         d.setWindowTitle(_('Electrum Settings'))
@@ -1889,6 +1873,7 @@ class ElectrumWindow(QMainWindow):
         vbox = QVBoxLayout()
 
         tabs = QTabWidget(self)
+        self.settings_tab = tabs
         vbox.addWidget(tabs)
 
         tab1 = QWidget()
@@ -2063,6 +2048,8 @@ class ElectrumWindow(QMainWindow):
                     traceback.print_exc(file=sys.stdout)
             grid_plugins.setRowStretch(i+1,1)
 
+        self.run_hook('create_settings_tab', (self,tabs,))
+
         vbox.addLayout(ok_cancel_buttons(d))
         d.setLayout(vbox) 
 
@@ -2334,11 +2321,12 @@ class ElectrumGui:
         
 
     def show_seed(self):
-        ElectrumWindow.show_seed_dialog(self.wallet)
+        ElectrumWindow.show_seed(self.wallet.seed)
 
 
     def password_dialog(self):
-        ElectrumWindow.change_password_dialog(self.wallet)
+        if self.wallet.seed:
+            ElectrumWindow.change_password_dialog(self.wallet)
 
 
     def restore_wallet(self):