add switch-gui button for qt
[electrum-nvc.git] / lib / gui_qt.py
index adcadf6..6c113e9 100644 (file)
@@ -19,6 +19,7 @@
 import sys, time, datetime, re
 from i18n import _
 from util import print_error
+import os.path, json, util
 
 try:
     import PyQt4
@@ -38,6 +39,7 @@ except:
 
 from wallet import format_satoshis
 import bmp, mnemonic, pyqrnative, qrscanner
+import exchange_rate
 
 from decimal import Decimal
 
@@ -294,6 +296,7 @@ class ElectrumWindow(QMainWindow):
 
     def __init__(self, wallet, config):
         QMainWindow.__init__(self)
+        self.lite = None
         self.wallet = wallet
         self.config = config
         self.wallet.interface.register_callback('updated', self.update_callback)
@@ -334,6 +337,9 @@ class ElectrumWindow(QMainWindow):
         self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet)
         #self.connect(self, SIGNAL('editamount'), self.edit_amount)
         self.history_list.setFocus(True)
+        
+        self.exchanger = exchange_rate.Exchanger(self)
+        self.connect(self, SIGNAL("refresh_balance()"), self.update_wallet)
 
         # dark magic fix by flatfly; https://bitcointalk.org/index.php?topic=73651.msg959913#msg959913
         if platform.system() == 'Windows':
@@ -383,14 +389,13 @@ class ElectrumWindow(QMainWindow):
                 c, u = self.wallet.get_balance()
                 text =  _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
                 if u: text +=  "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
+                text += self.create_quote_text(Decimal(c+u)/100000000)
                 icon = QIcon(":icons/status_connected.png")
         else:
             text = _( "Not connected" )
             icon = QIcon(":icons/status_disconnected.png")
 
-        if self.funds_error:
-            text = _( "Not enough funds" )
-
+        self.status_text = text
         self.statusBar().showMessage(text)
         self.status_button.setIcon( icon )
 
@@ -401,7 +406,15 @@ class ElectrumWindow(QMainWindow):
             self.update_contacts_tab()
             self.update_completions()
 
-
+    def create_quote_text(self, btc_balance):
+        quote_currency = self.config.get("currency", "None")
+        quote_balance = self.exchanger.exchange(btc_balance, quote_currency)
+        if quote_balance is None:
+            quote_text = ""
+        else:
+            quote_text = "  (%.2f %s)" % (quote_balance, quote_currency)
+        return quote_text
+        
     def create_history_tab(self):
         self.history_list = l = MyTreeWidget(self)
         l.setColumnCount(5)
@@ -423,7 +436,7 @@ class ElectrumWindow(QMainWindow):
         self.history_list.selectedIndexes() 
         item = self.history_list.currentItem()
         if not item: return
-        tx_hash = str(item.toolTip(0))
+        tx_hash = str(item.data(0, Qt.UserRole).toString())
         if not tx_hash: return
         menu = QMenu()
         menu.addAction(_("Copy ID to Clipboard"), lambda: self.app.clipboard().setText(tx_hash))
@@ -604,7 +617,8 @@ class ElectrumWindow(QMainWindow):
             if value < 0:
                 item.setForeground(3, QBrush(QColor("#BC1E1E")))
             if tx_hash:
-                item.setToolTip(0, tx_hash)
+                item.setData(0, Qt.UserRole, tx_hash)
+                item.setToolTip(0, "%d %s\nTxId:%s" % (conf, _('Confirmations'), tx_hash) )
             if is_default_label:
                 item.setForeground(2, QBrush(QColor('grey')))
 
@@ -703,13 +717,16 @@ class ElectrumWindow(QMainWindow):
             if inputs:
                 palette = QPalette()
                 palette.setColor(self.amount_e.foregroundRole(), QColor('black'))
+                text = self.status_text
             else:
                 palette = QPalette()
                 palette.setColor(self.amount_e.foregroundRole(), QColor('red'))
                 self.funds_error = True
+                text = _( "Not enough funds" )
+
+            self.statusBar().showMessage(text)
             self.amount_e.setPalette(palette)
             self.fee_e.setPalette(palette)
-            self.update_wallet()
 
         self.amount_e.textChanged.connect(lambda: entry_changed(False) )
         self.fee_e.textChanged.connect(lambda: entry_changed(True) )
@@ -908,6 +925,14 @@ class ElectrumWindow(QMainWindow):
         return w
 
 
+    def delete_imported_key(self, addr):
+        if self.question("Do you want to remove %s from your wallet?"%addr):
+            self.wallet.imported_keys.pop(addr)
+            self.update_receive_tab()
+            self.update_history_tab()
+            self.wallet.save()
+
+
     def create_receive_menu(self, position):
         # fixme: this function apparently has a side effect.
         # if it is not called the menu pops up several times
@@ -923,6 +948,8 @@ class ElectrumWindow(QMainWindow):
         menu.addAction(_("View QR"), lambda: ElectrumWindow.show_qrcode("Address","bitcoin:"+addr) )
         menu.addAction(_("Edit label"), lambda: self.edit_label(True))
         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))
 
         if self.receive_tab_mode == 1:
             t = _("Unfreeze") if addr in self.wallet.frozen_addresses else _("Freeze")
@@ -1106,8 +1133,12 @@ class ElectrumWindow(QMainWindow):
         return textbox
 
     def create_status_bar(self):
+        self.status_text = ""
         sb = QStatusBar()
         sb.setFixedHeight(35)
+        qtVersion = qVersion()
+        if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
+            sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), "Switch to Lite Mode", self.go_lite ) )
         if self.wallet.seed:
             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 ) )
@@ -1116,6 +1147,15 @@ class ElectrumWindow(QMainWindow):
         self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), "Network", lambda: self.network_dialog(self.wallet, self) ) 
         sb.addPermanentWidget( self.status_button )
         self.setStatusBar(sb)
+        
+    def go_lite(self):
+        import gui_lite
+        self.hide()
+        if self.lite:
+            self.lite.mini.show()
+        else:
+            self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
+            self.lite.main(None)
 
     def new_contact_dialog(self):
         text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
@@ -1145,10 +1185,9 @@ class ElectrumWindow(QMainWindow):
             password = None
             
         try:
-            seed = wallet.pw_decode(wallet.seed, password)
+            seed = wallet.decode_seed(password)
         except:
-            QMessageBox.warning(parent, _('Error'),
-                                _('Incorrect Password'), _('OK'))
+            QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
             return
 
         dialog = QDialog(None)
@@ -1231,24 +1270,27 @@ class ElectrumWindow(QMainWindow):
         d = QDialog(self)
         d.setModal(1)
         d.setWindowTitle('Sign Message')
-        d.setMinimumSize(270, 350)
+        d.setMinimumSize(410, 290)
 
         tab_widget = QTabWidget()
         tab = QWidget()
         layout = QGridLayout(tab)
 
         sign_address = QLineEdit()
+
         sign_address.setText(address)
         layout.addWidget(QLabel(_('Address')), 1, 0)
         layout.addWidget(sign_address, 1, 1)
 
         sign_message = QTextEdit()
         layout.addWidget(QLabel(_('Message')), 2, 0)
-        layout.addWidget(sign_message, 2, 1, 2, 1)
+        layout.addWidget(sign_message, 2, 1)
+        layout.setRowStretch(2,3)
 
-        sign_signature = QLineEdit()
+        sign_signature = QTextEdit()
         layout.addWidget(QLabel(_('Signature')), 3, 0)
         layout.addWidget(sign_signature, 3, 1)
+        layout.setRowStretch(3,1)
 
         def do_sign():
             if self.wallet.use_encryption:
@@ -1259,7 +1301,7 @@ class ElectrumWindow(QMainWindow):
                 password = None
 
             try:
-                signature = self.wallet.sign_message(sign_address.text(), str(sign_message.toPlainText()), password)
+                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))
@@ -1285,15 +1327,17 @@ class ElectrumWindow(QMainWindow):
 
         verify_message = QTextEdit()
         layout.addWidget(QLabel(_('Message')), 2, 0)
-        layout.addWidget(verify_message, 2, 1, 2, 1)
+        layout.addWidget(verify_message, 2, 1)
+        layout.setRowStretch(2,3)
 
-        verify_signature = QLineEdit()
+        verify_signature = QTextEdit()
         layout.addWidget(QLabel(_('Signature')), 3, 0)
         layout.addWidget(verify_signature, 3, 1)
+        layout.setRowStretch(3,1)
 
         def do_verify():
             try:
-                self.wallet.verify_message(verify_address.text(), verify_signature.text(), str(verify_message.toPlainText()))
+                self.wallet.verify_message(verify_address.text(), str(verify_signature.toPlainText()), str(verify_message.toPlainText()))
                 self.show_message("Signature verified")
             except BaseException, e:
                 self.show_message(str(e))
@@ -1424,7 +1468,7 @@ class ElectrumWindow(QMainWindow):
         new_password2 = unicode(conf_pw.text())
 
         try:
-            seed = wallet.pw_decode( wallet.seed, password)
+            seed = wallet.decode_seed(password)
         except:
             QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
             return
@@ -1489,6 +1533,61 @@ class ElectrumWindow(QMainWindow):
         return True
 
 
+    def do_import_labels(self):
+        labelsFile = QFileDialog.getOpenFileName(QWidget(), "Open text file", util.user_dir(), self.tr("Text Files (labels.dat)"))
+        if not labelsFile: return
+        try:
+            f = open(labelsFile, 'r')
+            data = f.read()
+            f.close()
+            self.wallet.labels = json.loads(data)
+            self.wallet.save()
+            QMessageBox.information(None, "Labels imported", "Your labels where imported from '%s'" % str(labelsFile))
+        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_labels(self):
+        labels = self.wallet.labels
+        try:
+            labelsFile = util.user_dir() + '/labels.dat'
+            f = open(labelsFile, 'w+')
+            json.dump(labels, f)
+            f.close()
+            QMessageBox.information(None, "Labels exported", "Your labels where exported to '%s'" % str(labelsFile))
+        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):
+        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' \
+                                         + _('Are you sure you understand what you are doing?'), 3, 4)
+            if r == 4: return
+
+        text, ok = QInputDialog.getText(self, _('Import private key'), _('Private Key') + ':')
+        if not ok: return
+        sec = str(text)
+        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:
+                QMessageBox.critical(None, "Unable to import key", "error")
+            else:
+                QMessageBox.information(None, "Key imported", addr)
+                self.update_receive_tab()
+                self.update_history_tab()
+        except BaseException as e:
+            QMessageBox.critical(None, "Unable to import key", str(e))
 
     def settings_dialog(self):
         d = QDialog(self)
@@ -1499,27 +1598,10 @@ class ElectrumWindow(QMainWindow):
         tabs = QTabWidget(self)
         vbox.addWidget(tabs)
 
-        tab = QWidget()
-        grid_wallet = QGridLayout(tab)
-        grid_wallet.setColumnStretch(0,1)
-        tabs.addTab(tab, _('Wallet') )
-        
-        tab2 = QWidget()
-        grid_ui = QGridLayout(tab2)
+        tab1 = QWidget()
+        grid_ui = QGridLayout(tab1)
         grid_ui.setColumnStretch(0,1)
-        tabs.addTab(tab2, _('Display') )
-
-        fee_label = QLabel(_('Transaction fee'))
-        grid_wallet.addWidget(fee_label, 2, 0)
-        fee_e = QLineEdit()
-        fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
-        grid_wallet.addWidget(fee_e, 2, 1)
-        msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
-            + _('Recommended value') + ': 0.001'
-        grid_wallet.addWidget(HelpButton(msg), 2, 2)
-        fee_e.textChanged.connect(lambda: numbify(fee_e,False))
-        if not self.config.is_modifiable('fee'):
-            for w in [fee_e, fee_label]: w.setEnabled(False)
+        tabs.addTab(tab1, _('Display') )
 
         nz_label = QLabel(_('Display zeros'))
         grid_ui.addWidget(nz_label, 3, 0)
@@ -1531,54 +1613,23 @@ class ElectrumWindow(QMainWindow):
         nz_e.textChanged.connect(lambda: numbify(nz_e,True))
         if not self.config.is_modifiable('num_zeros'):
             for w in [nz_e, nz_label]: w.setEnabled(False)
-
-
-        usechange_label = QLabel(_('Use change addresses'))
-        grid_wallet.addWidget(usechange_label, 5, 0)
-        usechange_combo = QComboBox()
-        usechange_combo.addItems(['Yes', 'No'])
-        usechange_combo.setCurrentIndex(not self.wallet.use_change)
-        grid_wallet.addWidget(usechange_combo, 5, 1)
-        grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions. ')), 5, 2)
-        if not self.config.is_modifiable('use_change'): usechange_combo.setEnabled(False)
-
-        gap_label = QLabel(_('Gap limit'))
-        grid_wallet.addWidget(gap_label, 6, 0)
-        gap_e = QLineEdit()
-        gap_e.setText("%d"% self.wallet.gap_limit)
-        grid_wallet.addWidget(gap_e, 6, 1)
-        msg =  _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
-              + _('You may increase it if you need more receiving addresses.') + '\n\n' \
-              + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
-              + _('Given the current status of your address sequence, the minimum gap limit you can use is: ') + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
-              + _('Warning') + ': ' \
-              + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
-              + _('Do not modify it if you do not understand what you are doing, or if you expect to recover your wallet without knowing it!') + '\n\n' 
-        grid_wallet.addWidget(HelpButton(msg), 6, 2)
-        gap_e.textChanged.connect(lambda: numbify(nz_e,True))
-        if not self.config.is_modifiable('gap_limit'):
-            for w in [gap_e, gap_label]: w.setEnabled(False)
         
         gui_label=QLabel(_('Default GUI') + ':')
         grid_ui.addWidget(gui_label , 7, 0)
         gui_combo = QComboBox()
-        gui_combo.addItems(['Lite', 'Classic', 'Gtk', 'Text'])
+        gui_combo.addItems(['Lite', 'Classic'])
         index = gui_combo.findText(self.config.get("gui","classic").capitalize())
         if index==-1: index = 1
         gui_combo.setCurrentIndex(index)
         grid_ui.addWidget(gui_combo, 7, 1)
-        grid_ui.addWidget(HelpButton(_('Select which GUI mode to use at start up. ')), 7, 2)
+        grid_ui.addWidget(HelpButton(_('Select which GUI mode to use at start up.'+'\n'+'Note: use the command line to access the "text" and "gtk" GUIs')), 7, 2)
         if not self.config.is_modifiable('gui'):
             for w in [gui_combo, gui_label]: w.setEnabled(False)
 
         lang_label=QLabel(_('Language') + ':')
         grid_ui.addWidget(lang_label , 8, 0)
         lang_combo = QComboBox()
-        languages = {'':_('Default'), 'br':_('Brasilian'), 'cs':_('Czech'), 'de':_('German'),
-                     'eo':_('Esperanto'), 'en':_('English'), 'es':_('Spanish'), 'fr':_('French'),
-                     'it':_('Italian'), 'lv':_('Latvian'), 'nl':_('Dutch'), 'ru':_('Russian'),
-                     'sl':_('Slovenian'), 'vi':_('Vietnamese'), 'zh':_('Chinese')
-                     }
+        from i18n import languages
         lang_combo.addItems(languages.values())
         try:
             index = languages.keys().index(self.config.get("language",''))
@@ -1590,20 +1641,101 @@ class ElectrumWindow(QMainWindow):
         if not self.config.is_modifiable('language'):
             for w in [lang_combo, lang_label]: w.setEnabled(False)
 
+        currencies = self.exchanger.get_currencies()
+        currencies.insert(0, "None")
 
+        cur_label=QLabel(_('Currency') + ':')
+        grid_ui.addWidget(cur_label , 9, 0)
+        cur_combo = QComboBox()
+        cur_combo.addItems(currencies)
+        try:
+            index = currencies.index(self.config.get('currency', "None"))
+        except:
+            index = 0
+        cur_combo.setCurrentIndex(index)
+        grid_ui.addWidget(cur_combo, 9, 1)
+        grid_ui.addWidget(HelpButton(_('Select which currency is used for quotes. ')), 9, 2)
+        
         view_label=QLabel(_('Receive Tab') + ':')
-        grid_ui.addWidget(view_label , 9, 0)
+        grid_ui.addWidget(view_label , 10, 0)
         view_combo = QComboBox()
         view_combo.addItems([_('Simple'), _('Advanced'), _('Point of Sale')])
         view_combo.setCurrentIndex(self.receive_tab_mode)
-        grid_ui.addWidget(view_combo, 9, 1)
+        grid_ui.addWidget(view_combo, 10, 1)
         hh = _('This selects the interaction mode of the "Receive" tab. ') + '\n\n' \
              + _('Simple') +   ': ' + _('Show only addresses and labels.') + '\n\n' \
              + _('Advanced') + ': ' + _('Show address balances and add extra menu items to freeze/prioritize addresses.') + '\n\n' \
              + _('Point of Sale') + ': ' + _('Show QR code window and amounts requested for each address. Add menu item to request amount.') + '\n\n' 
         
-        grid_ui.addWidget(HelpButton(hh), 9, 2)
+        grid_ui.addWidget(HelpButton(hh), 10, 2)
+
+        # wallet tab
+        tab2 = QWidget()
+        grid_wallet = QGridLayout(tab2)
+        grid_wallet.setColumnStretch(0,1)
+        tabs.addTab(tab2, _('Wallet') )
         
+        fee_label = QLabel(_('Transaction fee'))
+        grid_wallet.addWidget(fee_label, 0, 0)
+        fee_e = QLineEdit()
+        fee_e.setText("%s"% str( Decimal( self.wallet.fee)/100000000 ) )
+        grid_wallet.addWidget(fee_e, 0, 1)
+        msg = _('Fee per transaction input. Transactions involving multiple inputs tend to require a higher fee.') + ' ' \
+            + _('Recommended value') + ': 0.001'
+        grid_wallet.addWidget(HelpButton(msg), 0, 2)
+        fee_e.textChanged.connect(lambda: numbify(fee_e,False))
+        if not self.config.is_modifiable('fee'):
+            for w in [fee_e, fee_label]: w.setEnabled(False)
+
+        usechange_label = QLabel(_('Use change addresses'))
+        grid_wallet.addWidget(usechange_label, 1, 0)
+        usechange_combo = QComboBox()
+        usechange_combo.addItems(['Yes', 'No'])
+        usechange_combo.setCurrentIndex(not self.wallet.use_change)
+        grid_wallet.addWidget(usechange_combo, 1, 1)
+        grid_wallet.addWidget(HelpButton(_('Using change addresses makes it more difficult for other people to track your transactions. ')), 1, 2)
+        if not self.config.is_modifiable('use_change'): usechange_combo.setEnabled(False)
+
+        gap_label = QLabel(_('Gap limit'))
+        grid_wallet.addWidget(gap_label, 2, 0)
+        gap_e = QLineEdit()
+        gap_e.setText("%d"% self.wallet.gap_limit)
+        grid_wallet.addWidget(gap_e, 2, 1)
+        msg =  _('The gap limit is the maximal number of contiguous unused addresses in your sequence of receiving addresses.') + '\n' \
+              + _('You may increase it if you need more receiving addresses.') + '\n\n' \
+              + _('Your current gap limit is') + ': %d'%self.wallet.gap_limit + '\n' \
+              + _('Given the current status of your address sequence, the minimum gap limit you can use is: ') + '%d'%self.wallet.min_acceptable_gap() + '\n\n' \
+              + _('Warning') + ': ' \
+              + _('The gap limit parameter must be provided in order to recover your wallet from seed.') + ' ' \
+              + _('Do not modify it if you do not understand what you are doing, or if you expect to recover your wallet without knowing it!') + '\n\n' 
+        grid_wallet.addWidget(HelpButton(msg), 2, 2)
+        gap_e.textChanged.connect(lambda: numbify(nz_e,True))
+        if not self.config.is_modifiable('gap_limit'):
+            for w in [gap_e, gap_label]: w.setEnabled(False)
+
+        grid_wallet.setRowStretch(3,1)
+
+
+        # wallet tab
+        tab3 = QWidget()
+        grid_io = QGridLayout(tab3)
+        grid_io.setColumnStretch(0,1)
+        tabs.addTab(tab3, _('Import/Export') )
+        
+        grid_io.addWidget(QLabel(_('Labels')), 1, 0)
+        grid_io.addWidget(EnterButton(_("Export"), self.do_export_labels), 1, 1)
+        grid_io.addWidget(EnterButton(_("Import"), self.do_import_labels), 1, 2)
+        grid_io.addWidget(HelpButton('Export your labels as json'), 1, 3)
+
+        grid_io.addWidget(QLabel(_('History')), 2, 0)
+        grid_io.addWidget(EnterButton(_("Export"), self.do_export_history), 2, 1)
+        grid_io.addWidget(HelpButton('Export your transaction history as csv'), 2, 3)
+
+        grid_io.addWidget(QLabel(_('Private key')), 3, 0)
+        grid_io.addWidget(EnterButton(_("Import"), self.do_import_privkey), 3, 2)
+        grid_io.addWidget(HelpButton('Import private key'), 3, 3)
+
+        grid_io.setRowStretch(4,1)
         vbox.addLayout(ok_cancel_buttons(d))
         d.setLayout(vbox) 
 
@@ -1665,6 +1797,11 @@ class ElectrumWindow(QMainWindow):
         if lang_request != self.config.get('language'):
             self.config.set_key("language", lang_request, True)
             need_restart = True
+            
+        cur_request = str(currencies[cur_combo.currentIndex()])
+        if cur_request != self.config.get('currency', "None"):
+            self.config.set_key('currency', cur_request, True)
+            self.update_wallet()
 
         if need_restart:
             QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))