URI: 
       tFix merge conflict - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit dfd48319a38dd4d8a8afd874eb55175cd8aad590
   DIR parent 1cda13a407eaeff901f7bed289395573b0e5c3d8
  HTML Author: Maran <maran.hidskes@gmail.com>
       Date:   Sat, 16 Mar 2013 21:38:12 +0100
       
       Fix merge conflict
       
       Diffstat:
         M .gitignore                          |       4 ++--
         M MANIFEST.in                         |       2 ++
         M RELEASE-NOTES                       |       3 +--
         M contrib/build-wine/build-electrum-… |       2 +-
         M contrib/build-wine/build-electrum.… |       2 +-
         M contrib/build-wine/deterministic.s… |      60 ++++++++++++++++++++++++++------
         A contrib/build-wine/electrum.nsi     |     104 +++++++++++++++++++++++++++++++
         D contrib/build-wine/electrum.nsis    |     104 -------------------------------
         M contrib/build-wine/prepare-wine.sh  |       2 +-
         M electrum                            |      31 ++++++++++++++++++-------------
         M gui/__init__.py                     |       1 +
         M gui/exchange_rate.py                |       5 ++++-
         M gui/gui_classic.py                  |     320 ++++++++++++++-----------------
         M gui/gui_gtk.py                      |      81 +++++++++++++++++++++----------
         A gui/plugins.py                      |      32 +++++++++++++++++++++++++++++++
         A icons/electrum.ico                  |       0 
         M lib/bitcoin.py                      |      93 ++++++++++++++++++++-----------
         M lib/commands.py                     |       3 ++-
         M lib/deserialize.py                  |       4 +++-
         M lib/interface.py                    |       4 ++--
         M lib/util.py                         |      35 ++++++++++++++++++++++++++++++-
         M lib/verifier.py                     |      34 +++++++++++++++++++++----------
         M lib/wallet.py                       |     271 +++++--------------------------
         M make_packages                       |       2 +-
         A plugins/aliases.py                  |     199 +++++++++++++++++++++++++++++++
         M plugins/pointofsale.py              |     192 +++++++++++++------------------
         M plugins/qrscanner.py                |     103 ++++++++++++++-----------------
         A plugins/virtualkeyboard.py          |      65 +++++++++++++++++++++++++++++++
         M setup-release.py                    |       2 ++
       
       29 files changed, 971 insertions(+), 789 deletions(-)
       ---
   DIR diff --git a/.gitignore b/.gitignore
       t@@ -4,4 +4,5 @@ lib/icons_rc.py
        build/
        dist/
        *.egg/
       -/electrum.py
       -\ No newline at end of file
       +/electrum.py
       +contrib/pyinstaller/
   DIR diff --git a/MANIFEST.in b/MANIFEST.in
       t@@ -22,3 +22,5 @@ include scripts/servers
        include scripts/validate_tx
        include scripts/watch_address
        recursive-include data *
       +recursive-include locale *.mo
       +recursive-include docs *
   DIR diff --git a/RELEASE-NOTES b/RELEASE-NOTES
       t@@ -35,8 +35,7 @@ For an example, see Gavin's tutorial: https://gist.github.com/gavinandresen/3966
          1. user creates an unsigned transaction using the online (watching-only) wallet.
          2. unsigned transaction is copied to the offline computer, and signed by the offline wallet.
          3. signed transaction is copied to the online computer, broadcasted by the online client.
       -
       -* Raw transactions can also be loaded/signed/broadcasted via the GUI.
       +  4. All these steps can be done via the command line interface or the classic GUI. 
        
        * Many command line commands have been renamed in order to make the syntax consistent with bitcoind.
        
   DIR diff --git a/contrib/build-wine/build-electrum-git.sh b/contrib/build-wine/build-electrum-git.sh
       t@@ -58,7 +58,7 @@ $PYTHON "C:/pyinstaller/pyinstaller.py" --noconfirm --ascii -w --onefile "C:/ele
        $PYTHON "C:/pyinstaller/pyinstaller.py" --noconfirm --ascii -w deterministic.spec
        
        # For building NSIS installer, run:
       -wine "$WINEPREFIX/drive_c/Program Files (x86)/NSIS/makensis.exe" electrum.nsis
       +wine "$WINEPREFIX/drive_c/Program Files (x86)/NSIS/makensis.exe" electrum.nsi
        #wine $WINEPREFIX/drive_c/Program\ Files\ \(x86\)/NSIS/makensis.exe electrum.nsis
        
        DATE=`date +"%Y%m%d"`
   DIR diff --git a/contrib/build-wine/build-electrum.sh b/contrib/build-wine/build-electrum.sh
       t@@ -40,7 +40,7 @@ $PYTHON "C:/pyinstaller/pyinstaller.py" --noconfirm --ascii -w --onefile "C:/ele
        $PYTHON "C:/pyinstaller/pyinstaller.py" --noconfirm --ascii -w deterministic.spec
        
        # For building NSIS installer, run:
       -wine "$WINEPREFIX/drive_c/Program Files (x86)/NSIS/makensis.exe" electrum.nsis
       +wine "$WINEPREFIX/drive_c/Program Files (x86)/NSIS/makensis.exe" electrum.nsi
        #wine $WINEPREFIX/drive_c/Program\ Files\ \(x86\)/NSIS/makensis.exe electrum.nsis
        
        cd dist
   DIR diff --git a/contrib/build-wine/deterministic.spec b/contrib/build-wine/deterministic.spec
       t@@ -1,24 +1,64 @@
        # -*- mode: python -*-
       -a = Analysis(['C:/electrum/electrum'],
       -             pathex=['Z:\\electrum-wine'],
       -             hiddenimports=[],
       -             excludes=['Tkinter'],
       +
       +# We don't put these files in to actually include them in the script but to make the Analysis method scan them for imports
       +a = Analysis(['electrum', 'gui/gui_classic.py', 'gui/gui_lite.py', 'gui/gui_text.py',
       +              'lib/util.py', 'lib/wallet.py', 'lib/simple_config.py',
       +              'lib/bitcoin.py', 'lib/deserialize.py' 
       +              ],
       +             hiddenimports=["lib","gui"], 
       +             pathex=['lib:gui:plugins'],
                     hookspath=None)
       -pyz = PYZ(a.pure, level=0)
       +
       +##### include mydir in distribution #######
       +def extra_datas(mydir):
       +    def rec_glob(p, files):
       +        import os
       +        import glob
       +        for d in glob.glob(p):
       +            if os.path.isfile(d):
       +                files.append(d)
       +            rec_glob("%s/*" % d, files)
       +    files = []
       +    rec_glob("%s/*" % mydir, files)
       +    extra_datas = []
       +    for f in files:
       +        extra_datas.append((f, f, 'DATA'))
       +
       +    return extra_datas
       +###########################################
       +
       +# append dirs
       +
       +# Theme data
       +a.datas += extra_datas('data')
       +
       +# Localization
       +a.datas += extra_datas('locale')
       +
       +# Py folders that are needed because of the magic import finding
       +a.datas += extra_datas('gui')
       +a.datas += extra_datas('lib')
       +a.datas += extra_datas('plugins')
       +
       +pyz = PYZ(a.pure)
        exe = EXE(pyz,
                  a.scripts,
                  exclude_binaries=1,
                  name=os.path.join('build\\pyi.win32\\electrum', 'electrum.exe'),
       -          debug=False,
       +          debug=True,
                  strip=None,
       -          upx=True,
       -          console=False )
       +          upx=False,
       +          icon='icons/electrum.ico',
       +          console=True)
       +          # The console True makes an annoying black box pop up, but it does make Electrum accept command line options.
       +
        coll = COLLECT(exe,
                       a.binaries,
                       a.zipfiles,
                       a.datas,
                       strip=None,
                       upx=True,
       +               debug=False,
       +               icon='icons/electrum.ico',
       +               console=True,
                       name=os.path.join('dist', 'electrum'))
       -app = BUNDLE(coll,
       -             name=os.path.join('dist', 'electrum.app'))
   DIR diff --git a/contrib/build-wine/electrum.nsi b/contrib/build-wine/electrum.nsi
       t@@ -0,0 +1,104 @@
       +;--------------------------------
       +;Include Modern UI
       +
       +  !include "MUI2.nsh"
       +
       +;--------------------------------
       +;General
       +
       +  ;Name and file
       +  Name "Electrum"
       +  OutFile "dist/electrum-setup.exe"
       +
       +  ;Default installation folder
       +  InstallDir "$PROGRAMFILES\Electrum"
       +  
       +  ;Get installation folder from registry if available
       +  InstallDirRegKey HKCU "Software\Electrum" ""
       +
       +  ;Request application privileges for Windows Vista
       +  RequestExecutionLevel admin
       +
       +;--------------------------------
       +;Variables
       +
       +;--------------------------------
       +;Interface Settings
       +
       +  !define MUI_ABORTWARNING
       +
       +;--------------------------------
       +;Pages
       +
       +  ;!insertmacro MUI_PAGE_LICENSE "tmp/LICENCE"
       +  ;!insertmacro MUI_PAGE_COMPONENTS
       +  !insertmacro MUI_PAGE_DIRECTORY
       +  
       +  ;Start Menu Folder Page Configuration
       +  !define MUI_STARTMENUPAGE_REGISTRY_ROOT "HKCU" 
       +  !define MUI_STARTMENUPAGE_REGISTRY_KEY "Software\Electrum" 
       +  !define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "Start Menu Folder"
       +  
       +  ;!insertmacro MUI_PAGE_STARTMENU Application $StartMenuFolder
       +  
       +  !insertmacro MUI_PAGE_INSTFILES
       +  
       +  !insertmacro MUI_UNPAGE_CONFIRM
       +  !insertmacro MUI_UNPAGE_INSTFILES
       +
       +;--------------------------------
       +;Languages
       + 
       +  !insertmacro MUI_LANGUAGE "English"
       +
       +;--------------------------------
       +;Installer Sections
       +
       +Section
       +
       +  SetOutPath "$INSTDIR"
       +  
       +  ;ADD YOUR OWN FILES HERE...
       +  file /r dist\electrum\*.*
       +  
       +  ;Store installation folder
       +  WriteRegStr HKCU "Software\Electrum" "" $INSTDIR
       +  
       +  ;Create uninstaller
       +  WriteUninstaller "$INSTDIR\Uninstall.exe"
       +
       +
       +  CreateShortCut "$DESKTOP\Electrum.lnk" "$INSTDIR\electrum.exe" ""
       +
       +  ;create start-menu items
       +  CreateDirectory "$SMPROGRAMS\Electrum"
       +  CreateShortCut "$SMPROGRAMS\Electrum\Uninstall.lnk" "$INSTDIR\Uninstall.exe" "" "$INSTDIR\Uninstall.exe" 0
       +  CreateShortCut "$SMPROGRAMS\Electrum\Electrum.lnk" "$INSTDIR\electrum.exe" "" "$INSTDIR\electrum.exe" 0
       +
       +SectionEnd
       +
       +;--------------------------------
       +;Descriptions
       +
       +  ;Assign language strings to sections
       +  ;!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
       +  ;  !insertmacro MUI_DESCRIPTION_TEXT ${SecDummy} $(DESC_SecDummy)
       +  ;!insertmacro MUI_FUNCTION_DESCRIPTION_END
       + 
       +;--------------------------------
       +;Uninstaller Section
       +
       +Section "Uninstall"
       +
       +  ;ADD YOUR OWN FILES HERE...
       +  RMDir /r "$INSTDIR\*.*"
       +
       +  RMDir "$INSTDIR"
       +  
       +  Delete "$DESKTOP\Electrum.lnk"
       +  Delete "$SMPROGRAMS\Electrum\*.*"
       +  RmDir  "$SMPROGRAMS\Electrum"
       +    
       +  DeleteRegKey /ifempty HKCU "Software\Electrum"
       +
       +SectionEnd
   DIR diff --git a/contrib/build-wine/electrum.nsis b/contrib/build-wine/electrum.nsis
       t@@ -1,104 +0,0 @@
       -;--------------------------------
       -;Include Modern UI
       -
       -  !include "MUI2.nsh"
       -
       -;--------------------------------
       -;General
       -
       -  ;Name and file
       -  Name "Electrum"
       -  OutFile "dist/electrum-setup.exe"
       -
       -  ;Default installation folder
       -  InstallDir "$PROGRAMFILES\Electrum"
       -  
       -  ;Get installation folder from registry if available
       -  InstallDirRegKey HKCU "Software\Electrum" ""
       -
       -  ;Request application privileges for Windows Vista
       -  RequestExecutionLevel admin
       -
       -;--------------------------------
       -;Variables
       -
       -;--------------------------------
       -;Interface Settings
       -
       -  !define MUI_ABORTWARNING
       -
       -;--------------------------------
       -;Pages
       -
       -  !insertmacro MUI_PAGE_LICENSE "tmp/LICENCE"
       -  ;!insertmacro MUI_PAGE_COMPONENTS
       -  !insertmacro MUI_PAGE_DIRECTORY
       -  
       -  ;Start Menu Folder Page Configuration
       -  !define MUI_STARTMENUPAGE_REGISTRY_ROOT "HKCU" 
       -  !define MUI_STARTMENUPAGE_REGISTRY_KEY "Software\Electrum" 
       -  !define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "Start Menu Folder"
       -  
       -  ;!insertmacro MUI_PAGE_STARTMENU Application $StartMenuFolder
       -  
       -  !insertmacro MUI_PAGE_INSTFILES
       -  
       -  !insertmacro MUI_UNPAGE_CONFIRM
       -  !insertmacro MUI_UNPAGE_INSTFILES
       -
       -;--------------------------------
       -;Languages
       - 
       -  !insertmacro MUI_LANGUAGE "English"
       -
       -;--------------------------------
       -;Installer Sections
       -
       -Section
       -
       -  SetOutPath "$INSTDIR"
       -  
       -  ;ADD YOUR OWN FILES HERE...
       -  file /r dist\electrum\*.*
       -  
       -  ;Store installation folder
       -  WriteRegStr HKCU "Software\Electrum" "" $INSTDIR
       -  
       -  ;Create uninstaller
       -  WriteUninstaller "$INSTDIR\Uninstall.exe"
       -
       -
       -  CreateShortCut "$DESKTOP\Electrum.lnk" "$INSTDIR\electrum.exe" ""
       -
       -  ;create start-menu items
       -  CreateDirectory "$SMPROGRAMS\Electrum"
       -  CreateShortCut "$SMPROGRAMS\Electrum\Uninstall.lnk" "$INSTDIR\Uninstall.exe" "" "$INSTDIR\Uninstall.exe" 0
       -  CreateShortCut "$SMPROGRAMS\Electrum\Electrum.lnk" "$INSTDIR\electrum.exe" "" "$INSTDIR\electrum.exe" 0
       -
       -SectionEnd
       -
       -;--------------------------------
       -;Descriptions
       -
       -  ;Assign language strings to sections
       -  ;!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
       -  ;  !insertmacro MUI_DESCRIPTION_TEXT ${SecDummy} $(DESC_SecDummy)
       -  ;!insertmacro MUI_FUNCTION_DESCRIPTION_END
       - 
       -;--------------------------------
       -;Uninstaller Section
       -
       -Section "Uninstall"
       -
       -  ;ADD YOUR OWN FILES HERE...
       -  RMDir /r "$INSTDIR\*.*"
       -
       -  RMDir "$INSTDIR"
       -  
       -  Delete "$DESKTOP\Electrum.lnk"
       -  Delete "$SMPROGRAMS\Electrum\*.*"
       -  RmDir  "$SMPROGRAMS\Electrum"
       -    
       -  DeleteRegKey /ifempty HKCU "Software\Electrum"
       -
       -SectionEnd
   DIR diff --git a/contrib/build-wine/prepare-wine.sh b/contrib/build-wine/prepare-wine.sh
       t@@ -4,7 +4,7 @@
        PYTHON_URL=http://www.python.org/ftp/python/2.6.6/python-2.6.6.msi
        PYQT4_URL=http://sourceforge.net/projects/pyqt/files/PyQt4/PyQt-4.9.5/PyQt-Py2.6-x86-gpl-4.9.5-1.exe
        PYWIN32_URL=http://sourceforge.net/projects/pywin32/files/pywin32/Build%20218/pywin32-218.win32-py2.6.exe/download
       -PYINSTALLER_URL=https://github.com/downloads/pyinstaller/pyinstaller/pyinstaller-2.0.zip
       +PYINSTALLER_URL=http://downloads.sourceforge.net/project/pyinstaller/2.0/pyinstaller-2.0.zip
        NSIS_URL=http://prdownloads.sourceforge.net/nsis/nsis-2.46-setup.exe?download
        #ZBAR_URL=http://sourceforge.net/projects/zbar/files/zbar/0.10/zbar-0.10-setup.exe/download
        
   DIR diff --git a/electrum b/electrum
       t@@ -34,20 +34,20 @@ except ImportError:
            sys.exit("Error: AES does not seem to be installed. Try 'sudo pip install slowaes'")
        
        
       +is_local = os.path.dirname(os.path.realpath(__file__)) == os.getcwd()
        is_android = 'ANDROID_DATA' in os.environ
        
       +import __builtin__
       +__builtin__.use_local_modules = is_local or is_android
       +
        # load local module as electrum
       -if os.path.exists("lib") or is_android:
       +if __builtin__.use_local_modules:
            import imp
       -    fp, pathname, description = imp.find_module('lib')
       -    imp.load_module('electrum', fp, pathname, description)
       -    fp, pathname, description = imp.find_module('gui')
       -    imp.load_module('electrum_gui', fp, pathname, description)
       -
       +    imp.load_module('electrum', *imp.find_module('lib'))
       +    imp.load_module('electrum_gui', *imp.find_module('gui'))
        
        from electrum import *
        
       -
        # get password routine
        def prompt_password(prompt, confirm=True):
            import getpass
       t@@ -162,6 +162,7 @@ if __name__ == '__main__':
                        gui.show_seed()
        
                verifier = WalletVerifier(interface, config)
       +        verifier.start()
                wallet.set_verifier(verifier)
                synchronizer = WalletSynchronizer(wallet, config)
                synchronizer.start()
       t@@ -181,7 +182,6 @@ if __name__ == '__main__':
                    gui.password_dialog()
        
                wallet.save()
       -        verifier.start()
                gui.main(url)
                wallet.save()
        
       t@@ -236,14 +236,16 @@ if __name__ == '__main__':
                        wallet.seed = None
                        wallet.init_sequence(str(seed))
                    else:
       -                wallet.seed = str(seed)
       -                wallet.init_mpk( wallet.seed )
       +                wallet.init_seed( str(seed) )
        
                    if not options.offline:
                        interface = Interface(config)
       -                interface.start(wait=True)
       +                if not interface.start(wait=True):
       +                    print_msg("Not connected, aborting.")
       +                    sys.exit(1)
                        wallet.interface = interface
                        verifier = WalletVerifier(interface, config)
       +                verifier.start()
                        wallet.set_verifier(verifier)
        
                        print_msg("Recovering wallet...")
       t@@ -345,7 +347,7 @@ if __name__ == '__main__':
                sys.exit(1)
        
            if max_args < 0:
       -        if len(args) > min_args:
       +        if len(args) > min_args + 1:
                    message = ' '.join(args[min_args:])
                    print_msg("Warning: Final argument was reconstructed from several arguments:", repr(message))
                    args = args[0:min_args] + [ message ]
       t@@ -358,9 +360,12 @@ if __name__ == '__main__':
            if cmd not in offline_commands and not options.offline:
                interface = Interface(config)
                interface.register_callback('connected', lambda: sys.stderr.write("Connected to " + interface.connection_msg + "\n"))
       -        interface.start()
       +        if not interface.start(wait=True):
       +            print_msg("Not connected, aborting.")
       +            sys.exit(1)
                wallet.interface = interface
                verifier = WalletVerifier(interface, config)
       +        verifier.start()
                wallet.set_verifier(verifier)
                synchronizer = WalletSynchronizer(wallet, config)
                synchronizer.start()
   DIR diff --git a/gui/__init__.py b/gui/__init__.py
       t@@ -1 +1,2 @@
        # do not remove this file
       +from plugins import BasePlugin
   DIR diff --git a/gui/exchange_rate.py b/gui/exchange_rate.py
       t@@ -36,7 +36,10 @@ class Exchanger(threading.Thread):
                response = connection.getresponse()
                if response.reason == httplib.responses[httplib.NOT_FOUND]:
                    return
       -        response = json.loads(response.read())
       +        try:
       +            response = json.loads(response.read())
       +        except:
       +            return
                quote_currencies = {}
                try:
                    for r in response:
   DIR diff --git a/gui/gui_classic.py b/gui/gui_classic.py
       t@@ -61,8 +61,6 @@ elif platform.system() == 'Darwin':
        else:
            MONOSPACE_FONT = 'monospace'
        
       -ALIAS_REGEXP = '^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$'    
       -
        from electrum import ELECTRUM_VERSION
        import re
        
       t@@ -226,7 +224,6 @@ class StatusBarButton(QPushButton):
        
        
        
       -
        def waiting_dialog(f):
        
            s = Timer()
       t@@ -248,19 +245,35 @@ def waiting_dialog(f):
            w.destroy()
        
        
       -def ok_cancel_buttons(dialog):
       +def ok_cancel_buttons(dialog, ok_label=_("OK") ):
            hbox = QHBoxLayout()
            hbox.addStretch(1)
       -    b = QPushButton("Cancel")
       +    b = QPushButton(_("Cancel"))
            hbox.addWidget(b)
            b.clicked.connect(dialog.reject)
       -    b = QPushButton("OK")
       +    b = QPushButton(ok_label)
            hbox.addWidget(b)
            b.clicked.connect(dialog.accept)
            b.setDefault(True)
            return hbox
        
        
       +def text_dialog(parent, title, label, ok_label):
       +    dialog = QDialog(parent)
       +    dialog.setMinimumWidth(500)
       +    dialog.setWindowTitle(title)
       +    dialog.setModal(1)
       +    l = QVBoxLayout()
       +    dialog.setLayout(l)
       +    l.addWidget(QLabel(label))
       +    txt = QTextEdit()
       +    l.addWidget(txt)
       +    l.addLayout(ok_cancel_buttons(dialog, ok_label))
       +    if dialog.exec_():
       +        return unicode(txt.toPlainText())
       +
       +
       +
        default_column_widths = { "history":[40,140,350,140], "contacts":[350,330], 
                "receive":[[370],[370,200,130]] }
        
       t@@ -327,46 +340,44 @@ class ElectrumWindow(QMainWindow):
                self.console.showMessage(self.wallet.banner)
        
                # plugins that need to change the GUI do it here
       -        self.run_hook('init')
       +        self.run_hook('init_gui')
        
        
            # plugins
            def init_plugins(self):
       -        import imp, pkgutil
       -        if os.path.exists("plugins"):
       +        import imp, pkgutil, __builtin__
       +        if __builtin__.use_local_modules:
                    fp, pathname, description = imp.find_module('plugins')
       +            plugin_names = [name for a, name, b in pkgutil.iter_modules([pathname])]
       +            plugin_names = filter( lambda name: os.path.exists(os.path.join(pathname,name+'.py')), plugin_names)
                    imp.load_module('electrum_plugins', fp, pathname, description)
       -            plugin_names = [name for a, name, b in pkgutil.iter_modules(['plugins'])]
       -            self.plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
       +            plugins = map(lambda name: imp.load_source('electrum_plugins.'+name, os.path.join(pathname,name+'.py')), plugin_names)
                else:
                    import electrum_plugins
                    plugin_names = [name for a, name, b in pkgutil.iter_modules(electrum_plugins.__path__)]
       -            self.plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
       +            plugins = [ __import__('electrum_plugins.'+name, fromlist=['electrum_plugins']) for name in plugin_names]
        
       -        self.plugin_hooks = {}
       -        for p in self.plugins:
       +        self.plugins = []
       +        for p in plugins:
                    try:
       -                p.init(self)
       +                self.plugins.append( p.Plugin(self) )
                    except:
                        print_msg("Error:cannot initialize plugin",p)
                        traceback.print_exc(file=sys.stdout)
        
       -    def set_hook(self, name, callback):
       -        h = self.plugin_hooks.get(name, [])
       -        h.append(callback)
       -        self.plugin_hooks[name] = h
       -
       -    def unset_hook(self, name, callback):
       -        h = self.plugin_hooks.get(name,[])
       -        if callback in h: h.remove(callback)
       -        self.plugin_hooks[name] = h
       -
       -    def run_hook(self, name, args = ()):
       -        args = (self,) + args
       -        for cb in self.plugin_hooks.get(name,[]):
       -            apply(cb, args)
        
       +    def run_hook(self, name, *args):
       +        for p in self.plugins:
       +            if not p.is_enabled():
       +                continue
       +            try:
       +                f = eval('p.'+name)
       +            except:
       +                continue
       +            apply(f, args)
       +        return
        
       +        
            def set_label(self, name, text = None):
                changed = False
                old_text = self.wallet.labels.get(name)
       t@@ -378,8 +389,7 @@ class ElectrumWindow(QMainWindow):
                    if old_text:
                        self.wallet.labels.pop(name)
                        changed = True
       -        self.run_hook('set_label', (name, text, changed))
       -
       +        self.run_hook('set_label', name, text, changed)
                return changed
        
        
       t@@ -412,23 +422,6 @@ class ElectrumWindow(QMainWindow):
            def timer_actions(self):
                self.run_hook('timer_actions')
                    
       -        if self.payto_e.hasFocus():
       -            return
       -        r = unicode( self.payto_e.text() )
       -        if r != self.previous_payto_e:
       -            self.previous_payto_e = r
       -            r = r.strip()
       -            if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
       -                try:
       -                    to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
       -                except:
       -                    return
       -                if to_address:
       -                    s = r + '  <' + to_address + '>'
       -                    self.payto_e.setText(s)
       -
       -
       -
            def update_status(self):
                if self.wallet.interface and self.wallet.interface.is_connected:
                    if not self.wallet.up_to_date:
       t@@ -520,7 +513,7 @@ class ElectrumWindow(QMainWindow):
                vbox.addWidget(QLabel("Date: %s"%time_str))
                vbox.addWidget(QLabel("Status: %d confirmations"%conf))
                if is_mine:
       -            if fee: 
       +            if fee is not None: 
                        vbox.addWidget(QLabel("Amount sent: %s"% format_satoshis(v-fee, False)))
                        vbox.addWidget(QLabel("Transaction fee: %s"% format_satoshis(fee, False)))
                    else:
       t@@ -577,10 +570,11 @@ class ElectrumWindow(QMainWindow):
        
            def address_label_clicked(self, item, column, l, column_addr, column_label):
                if column == column_label and item.isSelected():
       +            is_editable = item.data(0, 32).toBool()
       +            if not is_editable:
       +                return
                    addr = unicode( item.text(column_addr) )
                    label = unicode( item.text(column_label) )
       -            if label in self.wallet.aliases.keys():
       -                return
                    item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
                    l.editItem( item, column )
                    item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
       t@@ -590,26 +584,22 @@ class ElectrumWindow(QMainWindow):
                if column == column_label:
                    addr = unicode( item.text(column_addr) )
                    text = unicode( item.text(column_label) )
       -            changed = False
       -
       -            if text in self.wallet.aliases.keys():
       -                print_error("Error: This is one of your aliases")
       -                label = self.wallet.labels.get(addr,'')
       -                item.setText(column_label, QString(label))
       +            is_editable = item.data(0, 32).toBool()
       +            if not is_editable:
       +                return
        
       -            else:
       -                changed = self.set_label(addr, text)
       -                if changed:
       -                    self.update_history_tab()
       -                    self.update_completions()
       +            changed = self.set_label(addr, text)
       +            if changed:
       +                self.update_history_tab()
       +                self.update_completions()
                        
                    self.current_item_changed(item)
        
       -        self.run_hook('item_changed', (item, column))
       +        self.run_hook('item_changed', item, column)
        
        
            def current_item_changed(self, a):
       -        self.run_hook('current_item_changed', (a,))
       +        self.run_hook('current_item_changed', a)
        
        
        
       t@@ -755,7 +745,7 @@ class ElectrumWindow(QMainWindow):
                self.amount_e.textChanged.connect(lambda: entry_changed(False) )
                self.fee_e.textChanged.connect(lambda: entry_changed(True) )
        
       -        self.run_hook('create_send_tab', (grid,))
       +        self.run_hook('create_send_tab', grid)
                return w2
        
        
       t@@ -764,8 +754,8 @@ class ElectrumWindow(QMainWindow):
                for addr,label in self.wallet.labels.items():
                    if addr in self.wallet.addressbook:
                        l.append( label + '  <' + addr + '>')
       -        l = l + self.wallet.aliases.keys()
        
       +        self.run_hook('update_completions', l)
                self.completions.setStringList(l)
        
        
       t@@ -780,19 +770,9 @@ class ElectrumWindow(QMainWindow):
                r = unicode( self.payto_e.text() )
                r = r.strip()
        
       -        # alias
       -        m1 = re.match(ALIAS_REGEXP, r)
                # label or alias, with address in brackets
       -        m2 = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
       -        
       -        if m1:
       -            to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
       -            if not to_address:
       -                return
       -        elif m2:
       -            to_address = m2.group(2)
       -        else:
       -            to_address = r
       +        m = re.match('(.*?)\s*\<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
       +        to_address = m.group(2) if m else r
        
                if not is_valid(to_address):
                    QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address') + ':\n' + to_address, _('OK'))
       t@@ -815,7 +795,7 @@ class ElectrumWindow(QMainWindow):
                    self.show_message(str(e))
                    return
        
       -        self.run_hook('send_tx', (tx,))
       +        self.run_hook('send_tx', tx)
        
                if label: 
                    self.set_label(tx.hash(), label)
       t@@ -844,10 +824,19 @@ class ElectrumWindow(QMainWindow):
        
        
            def set_url(self, url):
       -        payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
       +        address, amount, label, message, signature, identity, url = util.parse_url(url)
       +
       +        if label and self.wallet.labels.get(address) != label:
       +            if self.question('Give label "%s" to address %s ?'%(label,address)):
       +                if address not in self.wallet.addressbook and not self.wallet.is_mine(address):
       +                    self.wallet.addressbook.append(address)
       +                self.set_label(address, label)
       +
       +        self.run_hook('set_url', url, self.show_message, self.question)
       +
                self.tabs.setCurrentIndex(1)
       -        label = self.wallet.labels.get(payto)
       -        m_addr = label + '  <'+ payto+'>' if label else payto
       +        label = self.wallet.labels.get(address)
       +        m_addr = label + '  <'+ address +'>' if label else address
                self.payto_e.setText(m_addr)
        
                self.message_e.setText(message)
       t@@ -1011,54 +1000,45 @@ class ElectrumWindow(QMainWindow):
                    t = _("Unprioritize") if addr in self.wallet.prioritized_addresses else _("Prioritize")
                    menu.addAction(t, lambda: self.toggle_priority(addr))
                    
       -        self.run_hook('receive_menu', (menu,))
       +        self.run_hook('receive_menu', menu)
                menu.exec_(self.receive_list.viewport().mapToGlobal(position))
        
        
       -    def payto(self, x, is_alias):
       -        if not x: return
       -        if is_alias:
       -            label = x
       -            m_addr = label
       -        else:
       -            addr = x
       -            label = self.wallet.labels.get(addr)
       -            m_addr = label + '  <' + addr + '>' if label else addr
       +    def payto(self, addr):
       +        if not addr: return
       +        label = self.wallet.labels.get(addr)
       +        m_addr = label + '  <' + addr + '>' if label else addr
                self.tabs.setCurrentIndex(1)
                self.payto_e.setText(m_addr)
                self.amount_e.setFocus()
        
       -    def delete_contact(self, x, is_alias):
       +
       +    def delete_contact(self, x):
                if self.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
       -            if not is_alias and x in self.wallet.addressbook:
       +            if x in self.wallet.addressbook:
                        self.wallet.addressbook.remove(x)
                        self.set_label(x, None)
       -            elif is_alias and x in self.wallet.aliases:
       -                self.wallet.aliases.pop(x)
       -            self.update_history_tab()
       -            self.update_contacts_tab()
       -            self.update_completions()
       +                self.update_history_tab()
       +                self.update_contacts_tab()
       +                self.update_completions()
        
       -    def create_contact_menu(self, position):
       -        # fixme: this function apparently has a side effect.
       -        # if it is not called the menu pops up several times
       -        #self.contacts_list.selectedIndexes() 
        
       +    def create_contact_menu(self, position):
                item = self.contacts_list.itemAt(position)
                if not item: return
                addr = unicode(item.text(0))
                label = unicode(item.text(1))
       -        is_alias = label in self.wallet.aliases.keys()
       -        x = label if is_alias else addr
       +        is_editable = item.data(0,32).toBool()
       +        payto_addr = item.data(0,33).toString()
                menu = QMenu()
                menu.addAction(_("Copy to Clipboard"), lambda: self.app.clipboard().setText(addr))
       -        menu.addAction(_("Pay to"), lambda: self.payto(x, is_alias))
       +        menu.addAction(_("Pay to"), lambda: self.payto(payto_addr))
                menu.addAction(_("QR code"), lambda: self.show_qrcode("bitcoin:" + addr, _("Address")))
       -        if not is_alias:
       +        if is_editable:
                    menu.addAction(_("Edit label"), lambda: self.edit_label(False))
       -        else:
       -            menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
       -        menu.addAction(_("Delete"), lambda: self.delete_contact(x,is_alias))
       +            menu.addAction(_("Delete"), lambda: self.delete_contact(addr))
       +
       +        self.run_hook('create_contact_menu', menu, item)
                menu.exec_(self.contacts_list.viewport().mapToGlobal(position))
        
        
       t@@ -1068,7 +1048,7 @@ class ElectrumWindow(QMainWindow):
                label = self.wallet.labels.get(address,'')
                item.setData(1,0,label)
        
       -        self.run_hook('update_receive_item', (address, item))
       +        self.run_hook('update_receive_item', address, item)
                        
                c, u = self.wallet.get_addr_balance(address)
                balance = format_satoshis( c + u, False, self.wallet.num_zeros )
       t@@ -1114,13 +1094,12 @@ class ElectrumWindow(QMainWindow):
                        for address in account[is_change]:
                            h = self.wallet.history.get(address,[])
                    
       -                    if not is_change:
       -                        if h == []:
       -                            gap += 1
       -                            if gap > self.wallet.gap_limit:
       -                                is_red = True
       -                        else:
       -                            gap = 0
       +                    if h == []:
       +                        gap += 1
       +                        if gap > self.wallet.gap_limit:
       +                            is_red = True
       +                    else:
       +                        gap = 0
        
                            num_tx = '*' if h == ['*'] else "%d"%len(h)
                            item = QTreeWidgetItem( [ address, '', '', num_tx] )
       t@@ -1143,43 +1122,28 @@ class ElectrumWindow(QMainWindow):
                # we use column 1 because column 0 may be hidden
                l.setCurrentItem(l.topLevelItem(0),1)
        
       -    def show_contact_details(self, m):
       -        a = self.wallet.aliases.get(m)
       -        if a:
       -            if a[0] in self.wallet.authorities.keys():
       -                s = self.wallet.authorities.get(a[0])
       -            else:
       -                s = "self-signed"
       -            msg = _('Alias:')+' '+ m + '\n'+_('Target address:')+' '+ a[1] + '\n\n'+_('Signed by:')+' ' + s + '\n'+_('Signing address:')+' ' + a[0]
       -            QMessageBox.information(self, 'Alias', msg, 'OK')
        
            def update_contacts_tab(self):
        
                l = self.contacts_list
                l.clear()
        
       -        alias_targets = []
       -        for alias, v in self.wallet.aliases.items():
       -            s, target = v
       -            alias_targets.append(target)
       -            item = QTreeWidgetItem( [ target, alias, '-'] )
       -            item.setBackgroundColor(0, QColor('lightgray'))
       -            l.addTopLevelItem(item)
       -            
                for address in self.wallet.addressbook:
       -            if address in alias_targets: continue
                    label = self.wallet.labels.get(address,'')
       -            n = 0 
       -            for tx in self.wallet.transactions.values():
       -                if address in map(lambda x: x[0], tx.outputs): n += 1
       -            
       +            n = self.wallet.get_num_tx(address)
                    item = QTreeWidgetItem( [ address, label, "%d"%n] )
                    item.setFont(0, QFont(MONOSPACE_FONT))
       +            # 32 = label can be edited (bool)
       +            item.setData(0,32, True)
       +            # 33 = payto string
       +            item.setData(0,33, address)
                    l.addTopLevelItem(item)
        
       +        self.run_hook('update_contacts_tab', l)
                l.setCurrentItem(l.topLevelItem(0))
        
        
       +
            def create_console_tab(self):
                from qt_console import Console
                self.console = console = Console()
       t@@ -1288,7 +1252,7 @@ class ElectrumWindow(QMainWindow):
                try:
                    seed = self.wallet.decode_seed(password)
                except:
       -            QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
       +            QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
                    return
                self.show_seed(seed, self)
        
       t@@ -1518,8 +1482,9 @@ class ElectrumWindow(QMainWindow):
                vbox.addLayout(grid)
        
                vbox.addLayout(ok_cancel_buttons(d))
       -        d.setLayout(vbox) 
       +        d.setLayout(vbox)
        
       +        self.run_hook('password_dialog', pw, grid, 1)
                if not d.exec_(): return
                return unicode(pw.text())
        
       t@@ -1595,20 +1560,22 @@ class ElectrumWindow(QMainWindow):
                d.setModal(1)
        
                vbox = QVBoxLayout()
       -        msg = _("Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet.")
       +        msg = _("Please enter your wallet seed (or your master public key if you want to create a watching-only wallet)." + '\n')
                vbox.addWidget(QLabel(msg))
        
                grid = QGridLayout()
                grid.setSpacing(8)
        
                seed_e = QLineEdit()
       -        grid.addWidget(QLabel(_('Seed or mnemonic')), 1, 0)
       +        grid.addWidget(QLabel(_('Seed or master public key')), 1, 0)
                grid.addWidget(seed_e, 1, 1)
       +        grid.addWidget(HelpButton(_("Your seed can be entered as a mnemonic (sequence of words), or as a hexadecimal string.")), 1, 3)
        
                gap_e = QLineEdit()
                gap_e.setText("5")
                grid.addWidget(QLabel(_('Gap limit')), 2, 0)
                grid.addWidget(gap_e, 2, 1)
       +        grid.addWidget(HelpButton(_('Keep the default value unless you modified this parameter in your wallet.')), 2, 3)
                gap_e.textChanged.connect(lambda: numbify(gap_e,True))
                vbox.addLayout(grid)
        
       t@@ -1733,23 +1700,10 @@ class ElectrumWindow(QMainWindow):
                    self.show_message("There was a problem sending your transaction:\n %s" % (result_message))
        
            def do_process_from_text(self):
       -        dialog = QDialog(self)
       -        dialog.setMinimumWidth(500)
       -        dialog.setWindowTitle(_('Input raw transaction'))
       -        dialog.setModal(1)
       -        l = QVBoxLayout()
       -        dialog.setLayout(l)
       -        l.addWidget(QLabel(_("Transaction:")))
       -        txt = QTextEdit()
       -        l.addWidget(txt)
       -
       -        ok_button = QPushButton(_("Load transaction"))
       -        ok_button.setDefault(True)
       -        ok_button.clicked.connect(dialog.accept)
       -        l.addWidget(ok_button)
       -
       -        dialog.exec_()
       -        tx_dict = self.tx_dict_from_text(unicode(txt.toPlainText()))
       +        text = text_dialog(self, _('Input raw transaction'), _("Transaction:"), _("Load transaction"))
       +        if not text:
       +            return
       +        tx_dict = self.tx_dict_from_text(text)
                if tx_dict:
                    self.create_process_transaction_window(tx_dict)
        
       t@@ -1832,7 +1786,7 @@ class ElectrumWindow(QMainWindow):
                    for key, value in json.loads(data).items():
                        self.wallet.labels[key] = value
                    self.wallet.save()
       -            QMessageBox.information(None, _("Labels imported"), _("Your labels where imported from")+" '%s'" % str(labelsFile))
       +            QMessageBox.information(None, _("Labels imported"), _("Your labels were imported from")+" '%s'" % str(labelsFile))
                except (IOError, os.error), reason:
                    QMessageBox.critical(None, _("Unable to import labels"), _("Electrum was unable to import your labels.")+"\n" + str(reason))
                    
       t@@ -1857,24 +1811,34 @@ class ElectrumWindow(QMainWindow):
            @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' \
       +            r = QMessageBox.question(None, _('Warning'), '<b>'+_('Warning') +':\n</b><br/>'+ _('Imported keys are not recoverable from seed.') + ' ' \
       +                                         + _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '<p>' \
       +                                         + _('In addition, when you send bitcoins from one of your imported addresses, the "change" will be sent to an address derived from your seed, unless you disabled this option.') + '<p>' \
                                                 + _('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).strip()
       -        try:
       -            addr = self.wallet.import_key(sec, password)
       -            if not addr:
       -                QMessageBox.critical(None, _("Unable to import key"), "error")
       +        text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
       +        if not text: return
       +
       +        text = str(text).split()
       +        badkeys = []
       +        addrlist = []
       +        for key in text:
       +            try:
       +                addr = self.wallet.import_key(key, password)
       +            except BaseException as e:
       +                badkeys.append(key)
       +                continue
       +            if not addr: 
       +                badkeys.append(key)
                    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))
       +                addrlist.append(addr)
       +        if addrlist:
       +            QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
       +        if badkeys:
       +            QMessageBox.critical(self, _('Error'), _("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
       +        self.update_receive_tab()
       +        self.update_history_tab()
        
        
            def settings_dialog(self):
       t@@ -2036,7 +2000,7 @@ class ElectrumWindow(QMainWindow):
                    grid_plugins.setColumnStretch(0,1)
                    tabs.addTab(tab5, _('Plugins') )
                    def mk_toggle(cb, p):
       -                return lambda: cb.setChecked(p.toggle(self))
       +                return lambda: cb.setChecked(p.toggle())
                    for i, p in enumerate(self.plugins):
                        try:
                            name, description = p.get_info()
       t@@ -2222,7 +2186,7 @@ class ElectrumWindow(QMainWindow):
                    for p in protocol_letters:
                        i = protocol_letters.index(p)
                        j = server_protocol.model().index(i,0)
       -                if p not in pp.keys():
       +                if p not in pp.keys() and interface.is_connected:
                            server_protocol.model().setData(j, QtCore.QVariant(0), QtCore.Qt.UserRole-1)
                        else:
                            server_protocol.model().setData(j, QtCore.QVariant(0,False), QtCore.Qt.UserRole-1)
   DIR diff --git a/gui/gui_gtk.py b/gui/gui_gtk.py
       t@@ -868,14 +868,14 @@ class ElectrumWindow:
                        self.show_message(tx_details)
                    elif treeview == self.contacts_treeview:
                        m = self.addressbook_list.get_value( self.addressbook_list.get_iter(c), 0)
       -                a = self.wallet.aliases.get(m)
       -                if a:
       -                    if a[0] in self.wallet.authorities.keys():
       -                        s = self.wallet.authorities.get(a[0])
       -                    else:
       -                        s = "self-signed"
       -                    msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
       -                    self.show_message(msg)
       +                #a = self.wallet.aliases.get(m)
       +                #if a:
       +                #    if a[0] in self.wallet.authorities.keys():
       +                #        s = self.wallet.authorities.get(a[0])
       +                #    else:
       +                #        s = "self-signed"
       +                #    msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
       +                #    self.show_message(msg)
                    
        
            def treeview_key_press(self, treeview, event):
       t@@ -890,14 +890,14 @@ class ElectrumWindow:
                        self.show_message(tx_details)
                    elif treeview == self.contacts_treeview:
                        m = self.addressbook_list.get_value( self.addressbook_list.get_iter(c), 0)
       -                a = self.wallet.aliases.get(m)
       -                if a:
       -                    if a[0] in self.wallet.authorities.keys():
       -                        s = self.wallet.authorities.get(a[0])
       -                    else:
       -                        s = "self"
       -                    msg = 'Alias:'+ m + '\n\nTarget: '+ a[1] + '\nSigned by: ' + s + '\nSigning address:' + a[0]
       -                    self.show_message(msg)
       +                #a = self.wallet.aliases.get(m)
       +                #if a:
       +                #    if a[0] in self.wallet.authorities.keys():
       +                #        s = self.wallet.authorities.get(a[0])
       +                #    else:
       +                #        s = "self"
       +                #    msg = 'Alias:'+ m + '\n\nTarget: '+ a[1] + '\nSigned by: ' + s + '\nSigning address:' + a[0]
       +                #    self.show_message(msg)
        
                return False
        
       t@@ -1145,17 +1145,14 @@ class ElectrumWindow:
            def update_sending_tab(self):
                # detect addresses that are not mine in history, add them here...
                self.addressbook_list.clear()
       -        for alias, v in self.wallet.aliases.items():
       -            s, target = v
       -            label = self.wallet.labels.get(alias)
       -            self.addressbook_list.append((alias, label, '-'))
       +        #for alias, v in self.wallet.aliases.items():
       +        #    s, target = v
       +        #    label = self.wallet.labels.get(alias)
       +        #    self.addressbook_list.append((alias, label, '-'))
                    
                for address in self.wallet.addressbook:
                    label = self.wallet.labels.get(address)
       -            n = 0 
       -            for tx in self.wallet.transactions.values():
       -                if address in map(lambda x:x[0], tx.outputs): n += 1
       -
       +            n = self.wallet.get_num_tx(address)
                    self.addressbook_list.append((address, label, "%d"%n))
        
            def update_history_tab(self):
       t@@ -1176,7 +1173,7 @@ class ElectrumWindow:
        
                    label, is_default_label = self.wallet.get_label(tx_hash)
                    tooltip = tx_hash + "\n%d confirmations"%conf if tx_hash else ''
       -            details = self.wallet.get_tx_details(tx_hash)
       +            details = self.get_tx_details(tx_hash)
        
                    self.history_list.prepend( [tx_hash, conf_icon, time_str, label, is_default_label,
                                                format_satoshis(value,True,self.wallet.num_zeros),
       t@@ -1184,6 +1181,40 @@ class ElectrumWindow:
                if cursor: self.history_treeview.set_cursor( cursor )
        
        
       +    def get_tx_details(self, tx_hash):
       +        import datetime
       +        if not tx_hash: return ''
       +        tx = self.wallet.transactions.get(tx_hash)
       +        is_mine, v, fee = self.wallet.get_tx_value(tx)
       +        conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
       +
       +        if timestamp:
       +            time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
       +        else:
       +            time_str = 'pending'
       +
       +        inputs = map(lambda x: x.get('address'), tx.inputs)
       +        outputs = map(lambda x: x.get('address'), tx.d['outputs'])
       +        tx_details = "Transaction Details" +"\n\n" \
       +            + "Transaction ID:\n" + tx_hash + "\n\n" \
       +            + "Status: %d confirmations\n"%conf
       +        if is_mine:
       +            if fee: 
       +                tx_details += "Amount sent: %s\n"% format_satoshis(v-fee, False) \
       +                              + "Transaction fee: %s\n"% format_satoshis(fee, False)
       +            else:
       +                tx_details += "Amount sent: %s\n"% format_satoshis(v, False) \
       +                              + "Transaction fee: unknown\n"
       +        else:
       +            tx_details += "Amount received: %s\n"% format_satoshis(v, False) \
       +
       +        tx_details += "Date: %s\n\n"%time_str \
       +            + "Inputs:\n-"+ '\n-'.join(inputs) + "\n\n" \
       +            + "Outputs:\n-"+ '\n-'.join(outputs)
       +
       +        return tx_details
       +
       +
        
            def newaddress_dialog(self, w):
        
   DIR diff --git a/gui/plugins.py b/gui/plugins.py
       t@@ -0,0 +1,32 @@
       +
       +
       +class BasePlugin:
       +
       +    def get_info(self):
       +        return self.fullname, self.description
       +
       +    def __init__(self, gui, name, fullname, description):
       +        self.name = name
       +        self.fullname = fullname
       +        self.description = description
       +        self.gui = gui
       +        self.config = gui.config
       +
       +    def toggle(self):
       +        enabled = not self.is_enabled()
       +        self.set_enabled(enabled)
       +        self.init_gui()
       +        return enabled
       +    
       +    def init_gui(self):
       +        pass
       +
       +    def is_enabled(self):
       +        return self.is_available() and self.config.get('use_'+self.name) is True
       +
       +    def is_available(self):
       +        return True
       +
       +    def set_enabled(self, enabled):
       +        self.config.set_key('use_'+self.name, enabled, True)
       +
   DIR diff --git a/icons/electrum.ico b/icons/electrum.ico
       Binary files differ.
   DIR diff --git a/lib/bitcoin.py b/lib/bitcoin.py
       t@@ -364,10 +364,9 @@ random_seed = lambda n: "%032x"%ecdsa.util.randrange( pow(2,n) )
        
        def bip32_init(seed):
            import hmac
       -        
       +    seed = seed.decode('hex')        
            I = hmac.new("Bitcoin seed", seed, hashlib.sha512).digest()
        
       -    print "seed", seed.encode('hex')
            master_secret = I[0:32]
            master_chain = I[32:]
        
       t@@ -415,8 +414,8 @@ def CKD_prime(K, c, n):
        class ElectrumSequence:
            """  Privatekey(type,n) = Master_private_key + H(n|S|type)  """
        
       -    def __init__(self, master_public_key, mpk2 = None, mpk3 = None):
       -        self.master_public_key = master_public_key
       +    def __init__(self, mpk, mpk2 = None, mpk3 = None):
       +        self.mpk = mpk
                self.mpk2 = mpk2
                self.mpk3 = mpk3
        
       t@@ -445,7 +444,7 @@ class ElectrumSequence:
                    address = public_key_to_bc_address( pubkey.decode('hex') )
                elif not self.mpk3:
                    pubkey1 = self.get_pubkey(sequence)
       -            pubkey2 = self.get_pubkey(sequence, use_mpk2=True)
       +            pubkey2 = self.get_pubkey(sequence, mpk = self.mpk2)
                    address = Transaction.multisig_script([pubkey1, pubkey2], 2)["address"]
                else:
                    pubkey1 = self.get_pubkey(sequence)
       t@@ -456,7 +455,7 @@ class ElectrumSequence:
        
            def get_pubkey(self, sequence, mpk=None):
                curve = SECP256k1
       -        if mpk is None: mpk = self.master_public_key
       +        if mpk is None: mpk = self.mpk
                z = self.get_sequence(sequence, mpk)
                master_public_key = ecdsa.VerifyingKey.from_string( mpk.decode('hex'), curve = SECP256k1 )
                pubkey_point = master_public_key.pubkey.point + z*curve.generator
       t@@ -465,7 +464,7 @@ class ElectrumSequence:
        
            def get_private_key_from_stretched_exponent(self, sequence, secexp):
                order = generator_secp256k1.order()
       -        secexp = ( secexp + self.get_sequence(sequence, self.master_public_key) ) % order
       +        secexp = ( secexp + self.get_sequence(sequence, self.mpk) ) % order
                pk = number_to_string( secexp, generator_secp256k1.order() )
                compressed = False
                return SecretToASecret( pk, compressed )
       t@@ -483,7 +482,7 @@ class ElectrumSequence:
                secexp = self.stretch_key(seed)
                master_private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 )
                master_public_key = master_private_key.get_verifying_key().to_string().encode('hex')
       -        if master_public_key != self.master_public_key:
       +        if master_public_key != self.mpk:
                    print_error('invalid password (mpk)')
                    raise BaseException('Invalid password')
                return True
       t@@ -499,8 +498,8 @@ class ElectrumSequence:
                    redeemScript = Transaction.multisig_script([pubkey1, pubkey2], 2)['redeemScript']
                else:
                    pubkey1 = self.get_pubkey(sequence)
       -            pubkey2 = self.get_pubkey(sequence,mpk=self.mpk2)
       -            pubkey3 = self.get_pubkey(sequence,mpk=self.mpk3)
       +            pubkey2 = self.get_pubkey(sequence, mpk=self.mpk2)
       +            pubkey3 = self.get_pubkey(sequence, mpk=self.mpk3)
                    pk_addr = public_key_to_bc_address( pubkey1.decode('hex') ) # we need to return that address to get the right private key
                    redeemScript = Transaction.multisig_script([pubkey1, pubkey2, pubkey3], 2)['redeemScript']
                return pk_addr, redeemScript
       t@@ -510,43 +509,71 @@ class ElectrumSequence:
        
        class BIP32Sequence:
        
       -    def __init__(self, mpkc, mpkc2 = None):
       -        self.master_public_key, self.master_chain = mpkc
       -        if mpkc2:
       -            self.master_public_key2, self.master_chain2 = mpkc2
       -            self.is_p2sh = True
       -        else:
       -            self.is_p2sh = False
       +    def __init__(self, mpk, mpk2 = None, mpk3 = None):
       +        self.mpk = mpk
       +        self.mpk2 = mpk2
       +        self.mpk3 = mpk3
            
            @classmethod
            def mpk_from_seed(klass, seed):
                master_secret, master_chain, master_public_key, master_public_key_compressed = bip32_init(seed)
                return master_public_key.encode('hex'), master_chain.encode('hex')
        
       -    def get_pubkey(self, sequence, use_mpk2=False):
       -        if not use_mpl2:
       -            K = self.master_public_key.decode('hex')
       -            chain = self.master_chain.decode('hex')
       -        else:
       -            K = self.master_public_key_2.decode('hex')
       -            chain = self.master_chain_2.decode('hex')
       +    def get_pubkey(self, sequence, mpk = None):
       +        if not mpk: mpk = self.mpk
       +        master_public_key, master_chain = self.mpk
       +        K = master_public_key.decode('hex')
       +        chain = master_chain.decode('hex')
                for i in sequence:
                    K, K_compressed, chain = CKD_prime(K, chain, i)
       -        return K_compressed
       +        return K_compressed.encode('hex')
        
            def get_address(self, sequence):
       -        return hash_160_to_bc_address(hash_160(self.get_pubkey(sequence)))
       +        if not self.mpk2:
       +            pubkey = self.get_pubkey(sequence)
       +            address = public_key_to_bc_address( pubkey.decode('hex') )
       +        elif not self.mpk3:
       +            pubkey1 = self.get_pubkey(sequence)
       +            pubkey2 = self.get_pubkey(sequence, mpk = self.mpk2)
       +            address = Transaction.multisig_script([pubkey1, pubkey2], 2)["address"]
       +        else:
       +            pubkey1 = self.get_pubkey(sequence)
       +            pubkey2 = self.get_pubkey(sequence, mpk = self.mpk2)
       +            pubkey3 = self.get_pubkey(sequence, mpk = self.mpk3)
       +            address = Transaction.multisig_script([pubkey1, pubkey2, pubkey3], 2)["address"]
       +        return address
        
       -    def get_private_key(self, seed, sequence):
       -        k = self.master_secret
       -        chain = self.master_chain
       +    def get_private_key(self, sequence, seed):
       +        master_secret, master_chain, master_public_key, master_public_key_compressed = bip32_init(seed)
       +        chain = master_chain
       +        k = master_secret
                for i in sequence:
       -            k, k_compressed, chain = CKD(k, chain, i)
       -        return SecretToASecret(k0, True)
       +            k, chain = CKD(k, chain, i)
       +        return SecretToASecret(k, True)
       +
       +    def get_private_keys(self, sequence_list, seed):
       +        return [ self.get_private_key( sequence, seed) for sequence in sequence_list]
        
            def check_seed(self, seed):
                master_secret, master_chain, master_public_key, master_public_key_compressed = bip32_init(seed)
       -        assert self.master_public_key == master_public_key
       +        assert self.mpk == (master_public_key.encode('hex'), master_chain.encode('hex'))
       +
       +    def get_input_info(self, sequence):
       +        if not self.mpk2:
       +            pk_addr = self.get_address(sequence)
       +            redeemScript = None
       +        elif not self.mpk3:
       +            pubkey1 = self.get_pubkey(sequence)
       +            pubkey2 = self.get_pubkey(sequence, mpk=self.mpk2)
       +            pk_addr = public_key_to_bc_address( pubkey1.decode('hex') ) # we need to return that address to get the right private key
       +            redeemScript = Transaction.multisig_script([pubkey1, pubkey2], 2)['redeemScript']
       +        else:
       +            pubkey1 = self.get_pubkey(sequence)
       +            pubkey2 = self.get_pubkey(sequence, mpk=self.mpk2)
       +            pubkey3 = self.get_pubkey(sequence, mpk=self.mpk3)
       +            pk_addr = public_key_to_bc_address( pubkey1.decode('hex') ) # we need to return that address to get the right private key
       +            redeemScript = Transaction.multisig_script([pubkey1, pubkey2, pubkey3], 2)['redeemScript']
       +        return pk_addr, redeemScript
        
        ################################## transactions
        
       t@@ -842,7 +869,7 @@ class Transaction:
        
        
        def test_bip32():
       -    seed = "ff000000000000000000000000000000".decode('hex')
       +    seed = "ff000000000000000000000000000000"
            master_secret, master_chain, master_public_key, master_public_key_compressed = bip32_init(seed)
                
            print "secret key", master_secret.encode('hex')
   DIR diff --git a/lib/commands.py b/lib/commands.py
       t@@ -82,9 +82,10 @@ class Commands:
                self.wallet = wallet
                self.interface = interface
                self._callback = callback
       +        self.password = None
        
            def _run(self, method, args, password_getter):
       -        if method in protected_commands:
       +        if method in protected_commands and self.wallet.use_encryption:
                    self.password = apply(password_getter,())
                f = eval('self.'+method)
                result = apply(f,args)
   DIR diff --git a/lib/deserialize.py b/lib/deserialize.py
       t@@ -3,6 +3,7 @@
        #
        
        from bitcoin import public_key_to_bc_address, hash_160_to_bc_address, hash_encode, hash_160
       +from util import print_error
        #import socket
        import time
        import struct
       t@@ -356,7 +357,8 @@ def get_address_from_input_script(bytes):
                    pubkeys = [ dec2[1][1].encode('hex'), dec2[2][1].encode('hex'), dec2[3][1].encode('hex') ]
                    return pubkeys, signatures, hash_160_to_bc_address(hash_160(redeemScript), 5)
        
       -    raise BaseException("no match for scriptsig")
       +    print_error("cannot find address in input script", bytes.encode('hex'))
       +    return [], [], "(None)"
        
        
        
   DIR diff --git a/lib/interface.py b/lib/interface.py
       t@@ -595,8 +595,8 @@ class Interface(threading.Thread):
                    # wait until connection is established
                    self.connect_event.wait()
                    if not self.is_connected:
       -                print_msg("Not connected, aborting.")
       -                sys.exit(1)
       +                return False
       +        return True
        
            def run(self):
                while True:
   DIR diff --git a/lib/util.py b/lib/util.py
       t@@ -1,4 +1,4 @@
       -import os, sys
       +import os, sys, re
        import platform
        import shutil
        from datetime import datetime
       t@@ -147,3 +147,36 @@ def age(from_date, since_date = None, target_tz=None, include_seconds=False):
                return "about 1 year ago"
            else:
                return "over %d years ago" % (round(distance_in_minutes / 525600))
       +
       +
       +
       +
       +# URL decode
       +_ud = re.compile('%([0-9a-hA-H]{2})', re.MULTILINE)
       +urldecode = lambda x: _ud.sub(lambda m: chr(int(m.group(1), 16)), x)
       +
       +def parse_url(url):
       +    o = url[8:].split('?')
       +    address = o[0]
       +    if len(o)>1:
       +        params = o[1].split('&')
       +    else:
       +        params = []
       +
       +    amount = label = message = signature = identity = ''
       +    for p in params:
       +        k,v = p.split('=')
       +        uv = urldecode(v)
       +        if k == 'amount': amount = uv
       +        elif k == 'message': message = uv
       +        elif k == 'label': label = uv
       +        elif k == 'signature':
       +            identity, signature = uv.split(':')
       +            url = url.replace('&%s=%s'%(k,v),'')
       +        else: 
       +            print k,v
       +
       +    return address, amount, label, message, signature, identity, url
       +
       +
       +
   DIR diff --git a/lib/verifier.py b/lib/verifier.py
       t@@ -49,14 +49,10 @@ class WalletVerifier(threading.Thread):
            def get_confirmations(self, tx):
                """ return the number of confirmations of a monitored transaction. """
                with self.lock:
       -            if tx in self.transactions.keys():
       -                if tx in self.verified_tx:
       -                    height, timestamp = self.verified_tx[tx]
       -                    conf = (self.local_height - height + 1)
       -                else:
       -                    conf = -1
       +            if tx in self.verified_tx:
       +                height, timestamp = self.verified_tx[tx]
       +                conf = (self.local_height - height + 1)
                    else:
       -                #print "verifier: tx not in list", tx
                        conf = 0
        
                    if conf <= 0:
       t@@ -65,6 +61,13 @@ class WalletVerifier(threading.Thread):
                return conf, timestamp
        
        
       +    def get_height(self, tx_hash):
       +        with self.lock:
       +            v = self.verified_tx.get(tx_hash)
       +        height = v[0] if v else None
       +        return height
       +
       +
            def add(self, tx_hash, tx_height):
                """ add a transaction to the list of monitored transactions. """
                assert tx_height > 0
       t@@ -145,6 +148,10 @@ class WalletVerifier(threading.Thread):
                        continue
                    if not r: continue
        
       +            if r.get('error'):
       +                print_error('Verifier received an error:', r)
       +                continue
       +
                    # 3. handle response
                    method = r['method']
                    params = r['params']
       t@@ -183,7 +190,8 @@ class WalletVerifier(threading.Thread):
                # we passed all the tests
                header = self.read_header(tx_height)
                timestamp = header.get('timestamp')
       -        self.verified_tx[tx_hash] = (tx_height, timestamp)
       +        with self.lock:
       +            self.verified_tx[tx_hash] = (tx_height, timestamp)
                print_error("verified %s"%tx_hash)
                self.config.set_key('verified_tx2', self.verified_tx, True)
                self.interface.trigger_callback('updated')
       t@@ -241,12 +249,16 @@ class WalletVerifier(threading.Thread):
                    # this can be caused by a reorg.
                    print_error("verify header failed"+ repr(header))
                    # undo verifications
       -            for tx_hash, item in self.verified_tx.items():
       +            with self.lock:
       +                items = self.verified_tx.items()[:]
       +            for tx_hash, item in items:
                        tx_height, timestamp = item
                        if tx_height >= height:
                            print_error("redoing", tx_hash)
       -                    self.verified_tx.pop(tx_hash)
       -                    if tx_hash in self.merkle_roots: self.merkle_roots.pop(tx_hash)
       +                    with self.lock:
       +                        self.verified_tx.pop(tx_hash)
       +                        if tx_hash in self.merkle_roots:
       +                            self.merkle_roots.pop(tx_hash)
                    # return False to request previous header.
                    return False
        
   DIR diff --git a/lib/wallet.py b/lib/wallet.py
       t@@ -33,9 +33,6 @@ import time
        from util import print_msg, print_error, user_dir, format_satoshis
        from bitcoin import *
        
       -# URL decode
       -_ud = re.compile('%([0-9a-hA-H]{2})', re.MULTILINE)
       -urldecode = lambda x: _ud.sub(lambda m: chr(int(m.group(1), 16)), x)
        
        # AES encryption
        EncodeAES = lambda secret, s: base64.b64encode(aes.encryptData(secret,s))
       t@@ -82,19 +79,16 @@ class Wallet:
                self.use_encryption        = config.get('use_encryption', False)
                self.seed                  = config.get('seed', '')               # encrypted
                self.labels                = config.get('labels', {})
       -        self.aliases               = config.get('aliases', {})            # aliases for addresses
       -        self.authorities           = config.get('authorities', {})        # trusted addresses
                self.frozen_addresses      = config.get('frozen_addresses',[])
                self.prioritized_addresses = config.get('prioritized_addresses',[])
       -        self.receipts              = config.get('receipts',{})            # signed URIs
                self.addressbook           = config.get('contacts', [])
                self.imported_keys         = config.get('imported_keys',{})
                self.history               = config.get('addr_history',{})        # address -> list(txid, height)
       -        self.tx_height             = config.get('tx_height',{})
                self.accounts              = config.get('accounts', {})   # this should not include public keys
        
       +        self.SequenceClass = ElectrumSequence
                self.sequences = {}
       -        self.sequences[0] = ElectrumSequence(self.config.get('master_public_key'))
       +        self.sequences[0] = self.SequenceClass(self.config.get('master_public_key'))
        
                if self.accounts.get(0) is None:
                    self.accounts[0] = { 0:[], 1:[], 'name':'Main account' }
       t@@ -161,13 +155,13 @@ class Wallet:
                self.seed = seed 
                self.config.set_key('seed', self.seed, True)
                self.config.set_key('seed_version', self.seed_version, True)
       -        mpk = ElectrumSequence.mpk_from_seed(self.seed)
       +        mpk = self.SequenceClass.mpk_from_seed(self.seed)
                self.init_sequence(mpk)
        
        
            def init_sequence(self, mpk):
                self.config.set_key('master_public_key', mpk, True)
       -        self.sequences[0] = ElectrumSequence(mpk)
       +        self.sequences[0] = self.SequenceClass(mpk)
                self.accounts[0] = { 0:[], 1:[], 'name':'Main account' }
                self.config.set_key('accounts', self.accounts, True)
        
       t@@ -184,8 +178,8 @@ class Wallet:
                return address in self.addresses(True)
        
            def is_change(self, address):
       -        #return address in self.change_addresses
       -        return False
       +        acct, s = self.get_address_index(address)
       +        return s[0] == 1
        
            def get_master_public_key(self):
                return self.sequences[0].master_public_key
       t@@ -302,6 +296,7 @@ class Wallet:
                address = self.get_new_address( account, for_change, n)
                self.accounts[account][for_change].append(address)
                self.history[address] = []
       +        print_msg(address)
                return address
                
        
       t@@ -412,6 +407,12 @@ class Wallet:
                # redo labels
                # self.update_tx_labels()
        
       +    def get_num_tx(self, address):
       +        n = 0 
       +        for tx in self.transactions.values():
       +            if address in map(lambda x:x[0], tx.outputs): n += 1
       +        return n
       +
        
            def get_address_flags(self, addr):
                flags = "C" if self.is_change(addr) else "I" if addr in self.imported_keys.keys() else "-" 
       t@@ -424,46 +425,6 @@ class Wallet:
                return tx.get_value(addresses, self.prevout_values)
        
        
       -    def get_tx_details(self, tx_hash):
       -        import datetime
       -        if not tx_hash: return ''
       -        tx = self.transactions.get(tx_hash)
       -        is_mine, v, fee = self.get_tx_value(tx)
       -        conf, timestamp = self.verifier.get_confirmations(tx_hash)
       -
       -        if timestamp:
       -            time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
       -        else:
       -            time_str = 'pending'
       -
       -        inputs = map(lambda x: x.get('address'), tx.inputs)
       -        outputs = map(lambda x: x.get('address'), tx.d['outputs'])
       -        tx_details = "Transaction Details" +"\n\n" \
       -            + "Transaction ID:\n" + tx_hash + "\n\n" \
       -            + "Status: %d confirmations\n"%conf
       -        if is_mine:
       -            if fee: 
       -                tx_details += "Amount sent: %s\n"% format_satoshis(v-fee, False) \
       -                              + "Transaction fee: %s\n"% format_satoshis(fee, False)
       -            else:
       -                tx_details += "Amount sent: %s\n"% format_satoshis(v, False) \
       -                              + "Transaction fee: unknown\n"
       -        else:
       -            tx_details += "Amount received: %s\n"% format_satoshis(v, False) \
       -
       -        tx_details += "Date: %s\n\n"%time_str \
       -            + "Inputs:\n-"+ '\n-'.join(inputs) + "\n\n" \
       -            + "Outputs:\n-"+ '\n-'.join(outputs)
       -
       -        r = self.receipts.get(tx_hash)
       -        if r:
       -            tx_details += "\n_______________________________________" \
       -                + '\n\nSigned URI: ' + r[2] \
       -                + "\n\nSigned by: " + r[0] \
       -                + '\n\nSignature: ' + r[1]
       -
       -        return tx_details
       -
            
            def update_tx_outputs(self, tx_hash):
                tx = self.transactions.get(tx_hash)
       t@@ -566,6 +527,7 @@ class Wallet:
                    if h == ['*']: continue
                    for tx_hash, tx_height in h:
                        tx = self.transactions.get(tx_hash)
       +                if tx is None: raise BaseException("Wallet not synchronized")
                        for output in tx.d.get('outputs'):
                            if output.get('address') != addr: continue
                            key = tx_hash + ":%d" % output.get('index')
       t@@ -640,11 +602,12 @@ class Wallet:
            def receive_tx_callback(self, tx_hash, tx, tx_height):
        
                if not self.check_new_tx(tx_hash, tx):
       -            raise BaseException("error: received transaction is not consistent with history", tx_hash)
       +            # may happen due to pruning
       +            print_error("received transaction that is no longer referenced in history", tx_hash)
       +            return
        
                with self.lock:
                    self.transactions[tx_hash] = tx
       -            self.tx_height[tx_hash] = tx_height
        
                #tx_height = tx.get('height')
                if self.verifier and tx_height>0: 
       t@@ -669,17 +632,12 @@ class Wallet:
                        if tx_height>0:
                            # add it in case it was previously unconfirmed
                            if self.verifier: self.verifier.add(tx_hash, tx_height)
       -                    # set the height in case it changed
       -                    txh = self.tx_height.get(tx_hash)
       -                    if txh is not None and txh != tx_height:
       -                        print_error( "changing height for tx", tx_hash )
       -                        self.tx_height[tx_hash] = tx_height
        
        
            def get_tx_history(self):
                with self.lock:
                    history = self.transactions.items()
       -        history.sort(key = lambda x: self.tx_height.get(x[0]) if self.tx_height.get(x[0]) else 1e12)
       +        history.sort(key = lambda x: self.verifier.get_height(x[0]) if self.verifier.get_height(x[0]) else 1e12)
                result = []
            
                balance = 0
       t@@ -724,6 +682,9 @@ class Wallet:
                                    default_label = self.labels[o_addr]
                                except KeyError:
                                    default_label = o_addr
       +                        break
       +                else:
       +                    default_label = '(internal)'
                    else:
                        for o in tx.outputs:
                            o_addr, _ = o
       t@@ -812,48 +773,6 @@ class Wallet:
                return True, out
        
        
       -    def read_alias(self, alias):
       -        # this might not be the right place for this function.
       -        import urllib
       -
       -        m1 = re.match('([\w\-\.]+)@((\w[\w\-]+\.)+[\w\-]+)', alias)
       -        m2 = re.match('((\w[\w\-]+\.)+[\w\-]+)', alias)
       -        if m1:
       -            url = 'https://' + m1.group(2) + '/bitcoin.id/' + m1.group(1) 
       -        elif m2:
       -            url = 'https://' + alias + '/bitcoin.id'
       -        else:
       -            return ''
       -        try:
       -            lines = urllib.urlopen(url).readlines()
       -        except:
       -            return ''
       -
       -        # line 0
       -        line = lines[0].strip().split(':')
       -        if len(line) == 1:
       -            auth_name = None
       -            target = signing_addr = line[0]
       -        else:
       -            target, auth_name, signing_addr, signature = line
       -            msg = "alias:%s:%s:%s"%(alias,target,auth_name)
       -            print msg, signature
       -            EC_KEY.verify_message(signing_addr, signature, msg)
       -        
       -        # other lines are signed updates
       -        for line in lines[1:]:
       -            line = line.strip()
       -            if not line: continue
       -            line = line.split(':')
       -            previous = target
       -            print repr(line)
       -            target, signature = line
       -            EC_KEY.verify_message(previous, signature, "alias:%s:%s"%(alias,target))
       -
       -        if not is_valid(target):
       -            raise ValueError("Invalid bitcoin address")
       -
       -        return target, signing_addr, auth_name
        
            def update_password(self, seed, old_password, new_password):
                if new_password == '': new_password = None
       t@@ -867,96 +786,6 @@ class Wallet:
                    self.imported_keys[k] = c
                self.save()
        
       -    def get_alias(self, alias, interactive = False, show_message=None, question = None):
       -        try:
       -            target, signing_address, auth_name = self.read_alias(alias)
       -        except BaseException, e:
       -            # raise exception if verify fails (verify the chain)
       -            if interactive:
       -                show_message("Alias error: " + str(e))
       -            return
       -
       -        print target, signing_address, auth_name
       -
       -        if auth_name is None:
       -            a = self.aliases.get(alias)
       -            if not a:
       -                msg = "Warning: the alias '%s' is self-signed.\nThe signing address is %s.\n\nDo you want to add this alias to your list of contacts?"%(alias,signing_address)
       -                if interactive and question( msg ):
       -                    self.aliases[alias] = (signing_address, target)
       -                else:
       -                    target = None
       -            else:
       -                if signing_address != a[0]:
       -                    msg = "Warning: the key of alias '%s' has changed since your last visit! It is possible that someone is trying to do something nasty!!!\nDo you accept to change your trusted key?"%alias
       -                    if interactive and question( msg ):
       -                        self.aliases[alias] = (signing_address, target)
       -                    else:
       -                        target = None
       -        else:
       -            if signing_address not in self.authorities.keys():
       -                msg = "The alias: '%s' links to %s\n\nWarning: this alias was signed by an unknown key.\nSigning authority: %s\nSigning address: %s\n\nDo you want to add this key to your list of trusted keys?"%(alias,target,auth_name,signing_address)
       -                if interactive and question( msg ):
       -                    self.authorities[signing_address] = auth_name
       -                else:
       -                    target = None
       -
       -        if target:
       -            self.aliases[alias] = (signing_address, target)
       -            
       -        return target
       -
       -
       -    def parse_url(self, url, show_message, question):
       -        o = url[8:].split('?')
       -        address = o[0]
       -        if len(o)>1:
       -            params = o[1].split('&')
       -        else:
       -            params = []
       -
       -        amount = label = message = signature = identity = ''
       -        for p in params:
       -            k,v = p.split('=')
       -            uv = urldecode(v)
       -            if k == 'amount': amount = uv
       -            elif k == 'message': message = uv
       -            elif k == 'label': label = uv
       -            elif k == 'signature':
       -                identity, signature = uv.split(':')
       -                url = url.replace('&%s=%s'%(k,v),'')
       -            else: 
       -                print k,v
       -
       -        if label and self.labels.get(address) != label:
       -            if question('Give label "%s" to address %s ?'%(label,address)):
       -                if address not in self.addressbook and not self.is_mine(address):
       -                    self.addressbook.append(address)
       -                self.labels[address] = label
       -
       -        if signature:
       -            if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', identity):
       -                signing_address = self.get_alias(identity, True, show_message, question)
       -            elif is_valid(identity):
       -                signing_address = identity
       -            else:
       -                signing_address = None
       -            if not signing_address:
       -                return
       -            try:
       -                EC_KEY.verify_message(signing_address, signature, url )
       -                self.receipt = (signing_address, signature, url)
       -            except:
       -                show_message('Warning: the URI contains a bad signature.\nThe identity of the recipient cannot be verified.')
       -                address = amount = label = identity = message = ''
       -
       -        if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', address):
       -            payto_address = self.get_alias(address, True, show_message, question)
       -            if payto_address:
       -                address = address + ' <' + payto_address + '>'
       -
       -        return address, amount, label, message, signature, identity, url
       -
        
        
            def freeze(self,addr):
       t@@ -1007,15 +836,11 @@ class Wallet:
                    'labels': self.labels,
                    'contacts': self.addressbook,
                    'imported_keys': self.imported_keys,
       -            'aliases': self.aliases,
       -            'authorities': self.authorities,
       -            'receipts': self.receipts,
                    'num_zeros': self.num_zeros,
                    'frozen_addresses': self.frozen_addresses,
                    'prioritized_addresses': self.prioritized_addresses,
                    'gap_limit': self.gap_limit,
                    'transactions': tx,
       -            'tx_height': self.tx_height,
                }
                for k, v in s.items():
                    self.config.set_key(k,v)
       t@@ -1024,17 +849,6 @@ class Wallet:
            def set_verifier(self, verifier):
                self.verifier = verifier
        
       -        # review stored transactions and send them to the verifier
       -        # (they are not necessarily in the history, because history items might have have been pruned)
       -        for tx_hash, tx in self.transactions.items():
       -            tx_height = self.tx_height[tx_hash]
       -            if tx_height <1:
       -                print_error( "skipping", tx_hash, tx_height )
       -                continue
       -            
       -            if tx_height>0:
       -                self.verifier.add(tx_hash, tx_height)
       -
                # review transactions that are in the history
                for addr, hist in self.history.items():
                    if hist == ['*']: continue
       t@@ -1042,11 +856,6 @@ class Wallet:
                        if tx_height>0:
                            # add it in case it was previously unconfirmed
                            self.verifier.add(tx_hash, tx_height)
       -                    # set the height in case it changed
       -                    txh = self.tx_height.get(tx_hash)
       -                    if txh is not None and txh != tx_height:
       -                        print_error( "changing height for tx", tx_hash )
       -                        self.tx_height[tx_hash] = tx_height
        
        
        
       t@@ -1082,7 +891,7 @@ class Wallet:
                        if not tx: continue
                        
                        # already verified?
       -                if self.tx_height.get(tx_hash):
       +                if self.verifier.get_height(tx_hash):
                            continue
                        # unconfirmed tx
                        print_error("new history is orphaning transaction:", tx_hash)
       t@@ -1099,7 +908,6 @@ class Wallet:
                            for item in h:
                                if item.get('tx_hash') == tx_hash:
                                    height = item.get('height')
       -                            self.tx_height[tx_hash] = height
                        if height:
                            print_error("found height for", tx_hash, height)
                            self.verifier.add(tx_hash, height)
       t@@ -1155,22 +963,6 @@ class WalletSynchronizer(threading.Thread):
            def is_running(self):
                with self.lock: return self.running
        
       -    def synchronize_wallet(self):
       -        new_addresses = self.wallet.synchronize()
       -        if new_addresses:
       -            self.subscribe_to_addresses(new_addresses)
       -            self.wallet.up_to_date = False
       -            return
       -            
       -        if not self.interface.is_up_to_date('synchronizer'):
       -            if self.wallet.is_up_to_date():
       -                self.wallet.set_up_to_date(False)
       -                self.was_updated = True
       -            return
       -
       -        self.wallet.set_up_to_date(True)
       -        self.was_updated = True
       -
            
            def subscribe_to_addresses(self, addresses):
                messages = []
       t@@ -1202,15 +994,30 @@ class WalletSynchronizer(threading.Thread):
                self.subscribe_to_addresses(self.wallet.addresses(True))
        
                while self.is_running():
       -            # 1. send new requests
       -            self.synchronize_wallet()
       +            # 1. create new addresses
       +            new_addresses = self.wallet.synchronize()
        
       +            # request missing addresses
       +            if new_addresses:
       +                self.subscribe_to_addresses(new_addresses)
       +
       +            # request missing transactions
                    for tx_hash, tx_height in missing_tx:
                        if (tx_hash, tx_height) not in requested_tx:
                            self.interface.send([ ('blockchain.transaction.get',[tx_hash, tx_height]) ], 'synchronizer')
                            requested_tx.append( (tx_hash, tx_height) )
                    missing_tx = []
        
       +            # detect if situation has changed
       +            if not self.interface.is_up_to_date('synchronizer'):
       +                if self.wallet.is_up_to_date():
       +                    self.wallet.set_up_to_date(False)
       +                    self.was_updated = True
       +            else:
       +                if not self.wallet.is_up_to_date():
       +                    self.wallet.set_up_to_date(True)
       +                    self.was_updated = True
       +
                    if self.was_updated:
                        self.interface.trigger_callback('updated')
                        self.was_updated = False
   DIR diff --git a/make_packages b/make_packages
       t@@ -10,7 +10,7 @@ if __name__ == '__main__':
                sys.exit()
        
            os.system("python mki18n.py")
       -    os.system("pyrcc4 icons.qrc -o lib/icons_rc.py")
       +    os.system("pyrcc4 icons.qrc -o gui/icons_rc.py")
            os.system("python setup.py sdist --format=zip,gztar")
            
            _tgz="Electrum-%s.tar.gz"%version
   DIR diff --git a/plugins/aliases.py b/plugins/aliases.py
       t@@ -0,0 +1,199 @@
       +import re
       +import platform
       +from decimal import Decimal
       +
       +from PyQt4.QtGui import *
       +from PyQt4.QtCore import *
       +import PyQt4.QtCore as QtCore
       +import PyQt4.QtGui as QtGui
       +
       +from electrum_gui.qrcodewidget import QRCodeWidget
       +from electrum_gui import bmp, pyqrnative
       +from electrum_gui.i18n import _
       +
       +
       +ALIAS_REGEXP = '^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$'    
       +
       +
       +
       +from electrum_gui import BasePlugin
       +class Plugin(BasePlugin):
       +
       +    def __init__(self, gui):
       +        BasePlugin.__init__(self, gui, 'aliases', 'Aliases', _('Retrieve aliases using http.'))
       +        self.aliases      = self.config.get('aliases', {})            # aliases for addresses
       +        self.authorities  = self.config.get('authorities', {})        # trusted addresses
       +        self.receipts     = self.config.get('receipts',{})            # signed URIs
       +
       +
       +    def timer_actions(self):
       +        if self.gui.payto_e.hasFocus():
       +            return
       +        r = unicode( self.gui.payto_e.text() )
       +        if r != self.gui.previous_payto_e:
       +            self.gui.previous_payto_e = r
       +            r = r.strip()
       +            if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
       +                try:
       +                    to_address = self.get_alias(r, True, self.gui.show_message, self.gui.question)
       +                except:
       +                    return
       +                if to_address:
       +                    s = r + '  <' + to_address + '>'
       +                    self.gui.payto_e.setText(s)
       +
       +
       +    def get_alias(self, alias, interactive = False, show_message=None, question = None):
       +        try:
       +            target, signing_address, auth_name = read_alias(self, alias)
       +        except BaseException, e:
       +            # raise exception if verify fails (verify the chain)
       +            if interactive:
       +                show_message("Alias error: " + str(e))
       +            return
       +
       +        print target, signing_address, auth_name
       +
       +        if auth_name is None:
       +            a = self.aliases.get(alias)
       +            if not a:
       +                msg = "Warning: the alias '%s' is self-signed.\nThe signing address is %s.\n\nDo you want to add this alias to your list of contacts?"%(alias,signing_address)
       +                if interactive and question( msg ):
       +                    self.aliases[alias] = (signing_address, target)
       +                else:
       +                    target = None
       +            else:
       +                if signing_address != a[0]:
       +                    msg = "Warning: the key of alias '%s' has changed since your last visit! It is possible that someone is trying to do something nasty!!!\nDo you accept to change your trusted key?"%alias
       +                    if interactive and question( msg ):
       +                        self.aliases[alias] = (signing_address, target)
       +                    else:
       +                        target = None
       +        else:
       +            if signing_address not in self.authorities.keys():
       +                msg = "The alias: '%s' links to %s\n\nWarning: this alias was signed by an unknown key.\nSigning authority: %s\nSigning address: %s\n\nDo you want to add this key to your list of trusted keys?"%(alias,target,auth_name,signing_address)
       +                if interactive and question( msg ):
       +                    self.authorities[signing_address] = auth_name
       +                else:
       +                    target = None
       +
       +        if target:
       +            self.aliases[alias] = (signing_address, target)
       +            
       +        return target
       +
       +
       +
       +    def read_alias(self, alias):
       +        import urllib
       +
       +        m1 = re.match('([\w\-\.]+)@((\w[\w\-]+\.)+[\w\-]+)', alias)
       +        m2 = re.match('((\w[\w\-]+\.)+[\w\-]+)', alias)
       +        if m1:
       +            url = 'https://' + m1.group(2) + '/bitcoin.id/' + m1.group(1) 
       +        elif m2:
       +            url = 'https://' + alias + '/bitcoin.id'
       +        else:
       +            return ''
       +        try:
       +            lines = urllib.urlopen(url).readlines()
       +        except:
       +            return ''
       +
       +        # line 0
       +        line = lines[0].strip().split(':')
       +        if len(line) == 1:
       +            auth_name = None
       +            target = signing_addr = line[0]
       +        else:
       +            target, auth_name, signing_addr, signature = line
       +            msg = "alias:%s:%s:%s"%(alias,target,auth_name)
       +            print msg, signature
       +            EC_KEY.verify_message(signing_addr, signature, msg)
       +        
       +        # other lines are signed updates
       +        for line in lines[1:]:
       +            line = line.strip()
       +            if not line: continue
       +            line = line.split(':')
       +            previous = target
       +            print repr(line)
       +            target, signature = line
       +            EC_KEY.verify_message(previous, signature, "alias:%s:%s"%(alias,target))
       +
       +        if not is_valid(target):
       +            raise ValueError("Invalid bitcoin address")
       +
       +        return target, signing_addr, auth_name
       +
       +
       +    def set_url(self, url, show_message, question):
       +        payto, amount, label, message, signature, identity, url = util.parse_url(url)
       +        if signature:
       +            if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', identity):
       +                signing_address = get_alias(identity, True, show_message, question)
       +            elif is_valid(identity):
       +                signing_address = identity
       +            else:
       +                signing_address = None
       +            if not signing_address:
       +                return
       +            try:
       +                EC_KEY.verify_message(signing_address, signature, url )
       +                self.receipt = (signing_address, signature, url)
       +            except:
       +                show_message('Warning: the URI contains a bad signature.\nThe identity of the recipient cannot be verified.')
       +                address = amount = label = identity = message = ''
       +
       +        if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', address):
       +            payto_address = get_alias(address, True, show_message, question)
       +            if payto_address:
       +                address = address + ' <' + payto_address + '>'
       +
       +        return address, amount, label, message, signature, identity, url
       +
       +
       +
       +    def update_contacts_tab(self, l):
       +        alias_targets = []
       +        for alias, v in self.aliases.items():
       +            s, target = v
       +            alias_targets.append(target)
       +            item = QTreeWidgetItem( [ target, alias, '-'] )
       +            item.setBackgroundColor(0, QColor('lightgray'))
       +            item.setData(0,32,False)
       +            item.setData(0,33,alias + ' <' + target + '>')
       +            l.insertTopLevelItem(0,item)
       +
       +
       +    def update_completions(self, l):
       +        l[:] = l + self.aliases.keys()
       +
       +
       +    def create_contact_menu(self, menu, item):
       +        label = unicode(item.text(1))
       +        if label in self.aliases.keys():
       +            addr = unicode(item.text(0))
       +            label = unicode(item.text(1))
       +            menu.addAction(_("View alias details"), lambda: self.show_contact_details(label))
       +            menu.addAction(_("Delete alias"), lambda: delete_alias(self, label))
       +
       +
       +    def show_contact_details(self, m):
       +        a = self.aliases.get(m)
       +        if a:
       +            if a[0] in self.authorities.keys():
       +                s = self.authorities.get(a[0])
       +            else:
       +                s = "self-signed"
       +            msg = _('Alias:')+' '+ m + '\n'+_('Target address:')+' '+ a[1] + '\n\n'+_('Signed by:')+' ' + s + '\n'+_('Signing address:')+' ' + a[0]
       +            QMessageBox.information(self.gui, 'Alias', msg, 'OK')
       +
       +
       +    def delete_alias(self, x):
       +        if self.gui.question(_("Do you want to remove")+" %s "%x +_("from your list of contacts?")):
       +            if x in self.aliases:
       +                self.aliases.pop(x)
       +                self.update_history_tab()
       +                self.update_contacts_tab()
       +                self.update_completions()
   DIR diff --git a/plugins/pointofsale.py b/plugins/pointofsale.py
       t@@ -8,8 +8,7 @@ import PyQt4.QtCore as QtCore
        import PyQt4.QtGui as QtGui
        
        from electrum_gui.qrcodewidget import QRCodeWidget
       -from electrum_gui import bmp, pyqrnative
       -
       +from electrum_gui import bmp, pyqrnative, BasePlugin
        from electrum_gui.i18n import _
        
        
       t@@ -89,98 +88,95 @@ class QR_Window(QWidget):
                    
                self.qrw.set_addr( msg )
        
       -            
       -
        
       -config = {}
        
       -def get_info():
       -    return 'Point of Sale', _('Show QR code window and amounts requested for each address. Add menu item to request amount.')
        
       -def init(gui):
       -    global config
       -    config = gui.config
       -    gui.requested_amounts = config.get('requested_amounts',{}) 
       -    gui.merchant_name = config.get('merchant_name', 'Invoice')
       -    gui.qr_window = None
       -    do_enable(gui, is_enabled())
       +class Plugin(BasePlugin):
        
       -def is_enabled():
       -    return config.get('pointofsale') is True
       +    def __init__(self, gui):
       +        BasePlugin.__init__(self, gui, 'pointofsale', 'Point of Sale',
       +                            _('Show QR code window and amounts requested for each address. Add menu item to request amount.') )
       +        self.qr_window = None
       +        self.requested_amounts = self.config.get('requested_amounts',{}) 
       +        self.merchant_name = self.config.get('merchant_name', 'Invoice')
        
       -def is_available():
       -    return True
        
       +    def init_gui(self):
       +        enabled = self.is_enabled()
       +        if enabled:
       +            self.gui.expert_mode = True
       +            self.gui.receive_list.setHeaderLabels([ _('Address'), _('Label'), _('Balance'), _('Request')])
       +        else:
       +            self.gui.receive_list.setHeaderLabels([ _('Address'), _('Label'), _('Balance'), _('Tx')])
        
       -def toggle(gui):
       -    enabled = not is_enabled()
       -    config.set_key('pointofsale', enabled, True)
       -    do_enable(gui, enabled)
       -    update_gui(gui)
       -    return enabled
       +        self.toggle_QR_window(enabled)
       +    
        
       +    def close_main_window(self):
       +        if self.qr_window: 
       +            self.qr_window.close()
       +            self.qr_window = None
        
       -def do_enable(gui, enabled):
       -    if enabled:
       -        gui.expert_mode = True
       -        gui.set_hook('item_changed', item_changed)
       -        gui.set_hook('current_item_changed', recv_changed)
       -        gui.set_hook('receive_menu', receive_menu)
       -        gui.set_hook('update_receive_item', update_receive_item)
       -        gui.set_hook('timer_actions', timer_actions)
       -        gui.set_hook('close_main_window', close_main_window)
       -        gui.set_hook('init', update_gui)
       -    else:
       -        gui.unset_hook('item_changed', item_changed)
       -        gui.unset_hook('current_item_changed', recv_changed)
       -        gui.unset_hook('receive_menu', receive_menu)
       -        gui.unset_hook('update_receive_item', update_receive_item)
       -        gui.unset_hook('timer_actions', timer_actions)
       -        gui.unset_hook('close_main_window', close_main_window)
       -        gui.unset_hook('init', update_gui)
       +    
       +    def timer_actions(self):
       +        if self.qr_window:
       +            self.qr_window.qrw.update_qr()
       +
       +
       +    def toggle_QR_window(self, show):
       +        if show and not self.qr_window:
       +            self.qr_window = QR_Window(self.gui.exchanger)
       +            self.qr_window.setVisible(True)
       +            self.qr_window_geometry = self.qr_window.geometry()
       +            item = self.gui.receive_list.currentItem()
       +            if item:
       +                address = str(item.text(1))
       +                label = self.gui.wallet.labels.get(address)
       +                amount, currency = self.requested_amounts.get(address, (None, None))
       +                self.qr_window.set_content( address, label, amount, currency )
        
       +        elif show and self.qr_window and not self.qr_window.isVisible():
       +            self.qr_window.setVisible(True)
       +            self.qr_window.setGeometry(self.qr_window_geometry)
        
       +        elif not show and self.qr_window and self.qr_window.isVisible():
       +            self.qr_window_geometry = self.qr_window.geometry()
       +            self.qr_window.setVisible(False)
        
       -def update_gui(gui):
       -    enabled = is_enabled()
       -    if enabled:
       -        gui.receive_list.setHeaderLabels([ _('Address'), _('Label'), _('Balance'), _('Request')])
       -    else:
       -        gui.receive_list.setHeaderLabels([ _('Address'), _('Label'), _('Balance'), _('Tx')])
        
       -    toggle_QR_window(gui, enabled)
            
       -
       -
       -def toggle_QR_window(self, show):
       -    if show and not self.qr_window:
       -        self.qr_window = QR_Window(self.exchanger)
       -        self.qr_window.setVisible(True)
       -        self.qr_window_geometry = self.qr_window.geometry()
       -        item = self.receive_list.currentItem()
       -        if item:
       -            address = str(item.text(1))
       -            label = self.wallet.labels.get(address)
       +    def update_receive_item(self, address, item):
       +        try:
                    amount, currency = self.requested_amounts.get(address, (None, None))
       -            self.qr_window.set_content( address, label, amount, currency )
       -
       -    elif show and self.qr_window and not self.qr_window.isVisible():
       -        self.qr_window.setVisible(True)
       -        self.qr_window.setGeometry(self.qr_window_geometry)
       +        except:
       +            print "cannot get requested amount", address, self.requested_amounts.get(address)
       +            amount, currency = None, None
       +            self.requested_amounts.pop(address)
        
       -    elif not show and self.qr_window and self.qr_window.isVisible():
       -        self.qr_window_geometry = self.qr_window.geometry()
       -        self.qr_window.setVisible(False)
       +        amount_str = amount + (' ' + currency if currency else '') if amount is not None  else ''
       +        item.setData(column_index,0,amount_str)
        
        
       +    
       +    def current_item_changed(self, a):
       +        if a is not None and self.qr_window and self.qr_window.isVisible():
       +            address = str(a.text(0))
       +            label = self.gui.wallet.labels.get(address)
       +            try:
       +                amount, currency = self.requested_amounts.get(address, (None, None))
       +            except:
       +                amount, currency = None, None
       +            self.qr_window.set_content( address, label, amount, currency )
        
        
       -def item_changed(self, item, column):
       -    if column == column_index:
       +    
       +    def item_changed(self, item, column):
       +        if column != column_index:
       +            return
                address = str( item.text(0) )
                text = str( item.text(column) )
                try:
       -            seq = self.wallet.get_address_index(address)
       +            seq = self.gui.wallet.get_address_index(address)
                    index = seq[-1]
                except:
                    print "cannot get index"
       t@@ -198,9 +194,9 @@ def item_changed(self, item, column):
                        currency = currency.upper()
                            
                    self.requested_amounts[address] = (amount, currency)
       -            self.wallet.config.set_key('requested_amounts', self.requested_amounts, True)
       +            self.gui.wallet.config.set_key('requested_amounts', self.requested_amounts, True)
        
       -            label = self.wallet.labels.get(address)
       +            label = self.gui.wallet.labels.get(address)
                    if label is None:
                        label = self.merchant_name + ' - %04d'%(index+1)
                        self.wallet.labels[address] = label
       t@@ -213,50 +209,20 @@ def item_changed(self, item, column):
                    if address in self.requested_amounts:
                        self.requested_amounts.pop(address)
                    
       -        self.update_receive_item(self.receive_list.currentItem())
       -
       +        self.gui.update_receive_item(self.gui.receive_list.currentItem())
        
       -def recv_changed(self, a):
       -    if a is not None and self.qr_window and self.qr_window.isVisible():
       -        address = str(a.text(0))
       -        label = self.wallet.labels.get(address)
       -        try:
       -            amount, currency = self.requested_amounts.get(address, (None, None))
       -        except:
       -            amount, currency = None, None
       -        self.qr_window.set_content( address, label, amount, currency )
        
        
        
       -def edit_amount(self):
       -    l = self.receive_list
       -    item = l.currentItem()
       -    item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
       -    l.editItem( item, column_index )
       -    item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
       +    def edit_amount(self):
       +        l = self.gui.receive_list
       +        item = l.currentItem()
       +        item.setFlags(Qt.ItemIsEditable|Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
       +        l.editItem( item, column_index )
       +        item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsDragEnabled)
        
       -def receive_menu(self, menu):
       -    menu.addAction(_("Request amount"), lambda: edit_amount(self))
       -
       -
       -def update_receive_item(self, address, item):
       -    try:
       -        amount, currency = self.requested_amounts.get(address, (None, None))
       -    except:
       -        print "cannot get requested amount", address, self.requested_amounts.get(address)
       -        amount, currency = None, None
       -        self.requested_amounts.pop(address)
       -
       -    amount_str = amount + (' ' + currency if currency else '') if amount is not None  else ''
       -    item.setData(column_index,0,amount_str)
       -
       -
       -def close_main_window(self):
       -    if self.qr_window: 
       -        self.qr_window.close()
       -        self.qr_window = None
       +    
       +    def receive_menu(self, menu):
       +        menu.addAction(_("Request amount"), self.edit_amount)
        
        
       -def timer_actions(self):
       -    if self.qr_window:
       -        self.qr_window.qrw.update_qr()
   DIR diff --git a/plugins/qrscanner.py b/plugins/qrscanner.py
       t@@ -1,60 +1,69 @@
        from electrum.util import print_error
        from urlparse import urlparse, parse_qs
       +from PyQt4.QtGui import QPushButton
       +from electrum_gui.i18n import _
        
        try:
            import zbar
        except ImportError:
            zbar = None
        
       +from electrum_gui import BasePlugin
       +class Plugin(BasePlugin):
        
       +    def __init__(self, gui):
       +        BasePlugin.__init__(self, gui, 'qrscans', 'QR scans', "QR Scans.\nInstall the zbar package to enable this plugin")
       +        
       +    def is_available(self):
       +        if not zbar:
       +            return False
       +        try:
       +            proc = zbar.Processor()
       +            proc.init()
       +        except zbar.SystemError:
       +            # Cannot open video device
       +            return False
       +        return True
        
       -def init(gui):
       -    if is_enabled():
       -        gui.set_hook('create_send_tab', create_send_tab)
       -    else:
       -        gui.unset_hook('create_send_tab', create_send_tab)
       -
       -def get_info():
       -    return 'QR scans', "QR Scans.\nInstall the zbar package to enable this plugin"
       -
       -def is_enabled():
       -    return is_available()
       -
       -def toggle(gui):
       -    return is_enabled()
        
       +    def create_send_tab(self, grid):
       +        b = QPushButton(_("Scan QR code"))
       +        b.clicked.connect(self.fill_from_qr)
       +        grid.addWidget(b, 1, 5)
        
       -def is_available():
       -    if not zbar:
       -        return False
        
       -    try:
       +    def scan_qr(self):
                proc = zbar.Processor()
                proc.init()
       -    except zbar.SystemError:
       -        # Cannot open video device
       -        return False
       -
       -    return True
       +        proc.visible = True
       +
       +        while True:
       +            try:
       +                proc.process_one()
       +            except:
       +                # User closed the preview window
       +                return {}
       +
       +            for r in proc.results:
       +                if str(r.type) != 'QRCODE':
       +                    continue
       +                return parse_uri(r.data)
       +        
        
       -def scan_qr():
       -    proc = zbar.Processor()
       -    proc.init()
       -    proc.visible = True
       +    def fill_from_qr(self):
       +        qrcode = self.scan_qr()
       +        if 'address' in qrcode:
       +            self.gui.payto_e.setText(qrcode['address'])
       +        if 'amount' in qrcode:
       +            self.gui.amount_e.setText(str(qrcode['amount']))
       +        if 'label' in qrcode:
       +            self.gui.message_e.setText(qrcode['label'])
       +        if 'message' in qrcode:
       +            self.gui.message_e.setText("%s (%s)" % (self.gui.message_e.text(), qrcode['message']))
       +                
        
       -    while True:
       -        try:
       -            proc.process_one()
       -        except:
       -            # User closed the preview window
       -            return {}
        
       -        for r in proc.results:
       -            if str(r.type) != 'QRCODE':
       -                continue
        
       -            return parse_uri(r.data)
       -        
        def parse_uri(uri):
            if ':' not in uri:
                # It's just an address (not BIP21)
       t@@ -80,24 +89,6 @@ def parse_uri(uri):
        
        
        
       -def fill_from_qr(self):
       -    qrcode = qrscanner.scan_qr()
       -    if 'address' in qrcode:
       -        self.payto_e.setText(qrcode['address'])
       -    if 'amount' in qrcode:
       -        self.amount_e.setText(str(qrcode['amount']))
       -    if 'label' in qrcode:
       -        self.message_e.setText(qrcode['label'])
       -    if 'message' in qrcode:
       -        self.message_e.setText("%s (%s)" % (self.message_e.text(), qrcode['message']))
       -                
       -
       -def create_send_tab(gui, grid):
       -    if qrscanner.is_available():
       -        b = QPushButton(_("Scan QR code"))
       -        b.clicked.connect(lambda: fill_from_qr(gui))
       -        grid.addWidget(b, 1, 5)
       -
        
        
        if __name__ == '__main__':
   DIR diff --git a/plugins/virtualkeyboard.py b/plugins/virtualkeyboard.py
       t@@ -0,0 +1,65 @@
       +from PyQt4.QtGui import *
       +from electrum_gui import BasePlugin
       +from electrum_gui.i18n import _
       +
       +class Plugin(BasePlugin):
       +
       +
       +    def __init__(self, gui):
       +        BasePlugin.__init__(self, gui, 'virtualkeyboard', 'Virtual Keyboard',
       +                            _("Add an optional, mouse keyboard to the password dialog.\nWarning: do not use this if it makes you pick a weaker password."))
       +        self.vkb = None
       +        self.vkb_index = 0
       +
       +
       +    def password_dialog(self, pw, grid, pos):
       +        vkb_button = QPushButton(_("+"))
       +        vkb_button.setFixedWidth(20)
       +        vkb_button.clicked.connect(lambda: self.toggle_vkb(grid, pw))
       +        grid.addWidget(vkb_button, pos, 2)
       +        self.kb_pos = 2
       +
       +
       +    def toggle_vkb(self, grid, pw):
       +        if self.vkb: grid.removeItem(self.vkb)
       +        self.vkb = self.virtual_keyboard(self.vkb_index, pw)
       +        grid.addLayout(self.vkb, self.kb_pos, 0, 1, 3)
       +        self.vkb_index += 1
       +
       +
       +    def virtual_keyboard(self, i, pw):
       +        import random
       +        i = i%3
       +        if i == 0:
       +            chars = 'abcdefghijklmnopqrstuvwxyz '
       +        elif i == 1:
       +            chars = 'ABCDEFGHIJKLMNOPQRTSUVWXYZ '
       +        elif i == 2:
       +            chars = '1234567890!?.,;:/%&()[]{}+-'
       +            
       +        n = len(chars)
       +        s = []
       +        for i in xrange(n):
       +            while True:
       +                k = random.randint(0,n-1)
       +                if k not in s:
       +                    s.append(k)
       +                    break
       +
       +        def add_target(t):
       +            return lambda: pw.setText(str( pw.text() ) + t)
       +            
       +        vbox = QVBoxLayout()
       +        grid = QGridLayout()
       +        grid.setSpacing(2)
       +        for i in range(n):
       +            l_button = QPushButton(chars[s[i]])
       +            l_button.setFixedWidth(25)
       +            l_button.setFixedHeight(25)
       +            l_button.clicked.connect(add_target(chars[s[i]]) )
       +            grid.addWidget(l_button, i/6, i%6)
       +
       +        vbox.addLayout(grid)
       +
       +        return vbox
       +
   DIR diff --git a/setup-release.py b/setup-release.py
       t@@ -28,6 +28,8 @@ if sys.platform == 'darwin':
                setup_requires=['py2app'],
                app=[mainscript],
                options=dict(py2app=dict(argv_emulation=True,
       +                                 includes = ['PyQt4.QtCore','PyQt4.QtGui', 'sip'],
       +                                 packages = ['lib', 'gui', 'plugins'],
                                         iconfile='electrum.icns',
                                         resources=["data", "icons"])),
            )