This commit is contained in:
Anwesh 2015-12-24 06:59:16 +05:30
commit 93bcd98763
70 changed files with 2153 additions and 3432 deletions

View File

@ -2,6 +2,9 @@
* separation between plugins and GUIs
* the daemon supports jsonrpc commands
* new command: 'notify <address> <url>'
* improved coin selection to help preserve user privacy. This is an
experimental feature. Enable it by setting the Coin Selection
preference to Privacy.
# Release 2.5.4
* increase MIN_RELAY_TX_FEE to avoid dust transactions

View File

@ -1,5 +1,4 @@
#!/usr/bin/python
#!/usr/bin/python2
if __name__ == '__main__':
import sys, re, shutil, os, hashlib

View File

@ -1,4 +1,4 @@
#!/usr/bin/python
#!/usr/bin/python2
import sys
import re
import hashlib

View File

@ -1,4 +1,4 @@
#!/usr/bin/python
#!/usr/bin/python2
import sys, re, shutil, os, hashlib
import imp

View File

@ -1,4 +1,4 @@
#!/usr/bin/python
#!/usr/bin/python2
import sys, re, shutil, os, hashlib
import imp

303
electrum
View File

@ -107,9 +107,74 @@ def init_gui(config, network, plugins):
return gui
def run_non_RPC(config):
cmdname = config.get('cmd')
def init_cmdline(config):
storage = WalletStorage(config.get_wallet_path())
if storage.file_exists:
sys.exit("Error: Remove the existing wallet first!")
def password_dialog():
return prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
if cmdname == 'restore':
text = config.get('text')
password = password_dialog() if Wallet.is_seed(text) or Wallet.is_xprv(text) or Wallet.is_private_key(text) else None
try:
wallet = Wallet.from_text(text, password, storage)
except BaseException as e:
sys.exit(str(e))
if not config.get('offline'):
network = Network(config)
network.start()
wallet.start_threads(network)
print_msg("Recovering wallet...")
wallet.synchronize()
wallet.wait_until_synchronized()
msg = "Recovery successful" if wallet.is_found() else "Found no history for this wallet"
else:
msg = "This wallet was restored offline. It may contain more addresses than displayed."
print_msg(msg)
elif cmdname == 'create':
password = password_dialog()
wallet = Wallet(storage)
seed = wallet.make_seed()
wallet.add_seed(seed, password)
wallet.create_master_keys(password)
wallet.create_main_account(password)
wallet.synchronize()
print_msg("Your wallet generation seed is:\n\"%s\"" % seed)
print_msg("Please keep it in a safe place; if you lose it, you will not be able to restore your wallet.")
elif cmdname == 'deseed':
if not wallet.seed:
print_msg("Error: This wallet has no seed")
else:
ns = wallet.storage.path + '.seedless'
print_msg("Warning: you are going to create a seedless wallet'\nIt will be saved in '%s'" % ns)
if raw_input("Are you sure you want to continue? (y/n) ") in ['y', 'Y', 'yes']:
wallet.storage.path = ns
wallet.seed = ''
wallet.storage.put('seed', '')
wallet.use_encryption = False
wallet.storage.put('use_encryption', wallet.use_encryption)
for k in wallet.imported_keys.keys():
wallet.imported_keys[k] = ''
wallet.storage.put('imported_keys', wallet.imported_keys)
print_msg("Done.")
else:
print_msg("Action canceled.")
wallet.storage.write()
sys.exit(0)
wallet.storage.write()
print_msg("Wallet saved in '%s'" % wallet.storage.path)
sys.exit(0)
def init_cmdline(config_options):
config = SimpleConfig(config_options)
cmdname = config.get('cmd')
cmd = known_commands[cmdname]
@ -130,57 +195,11 @@ def init_cmdline(config):
# instanciate wallet for command-line
storage = WalletStorage(config.get_wallet_path())
if cmd.name in ['create', 'restore']:
if storage.file_exists:
sys.exit("Error: Remove the existing wallet first!")
def password_dialog():
return prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
if cmd.name == 'restore':
text = config.get('text')
password = password_dialog() if Wallet.is_seed(text) or Wallet.is_xprv(text) or Wallet.is_private_key(text) else None
try:
wallet = Wallet.from_text(text, password, storage)
except BaseException as e:
sys.exit(str(e))
if not config.get('offline'):
network = Network(config)
network.start()
wallet.start_threads(network)
print_msg("Recovering wallet...")
wallet.synchronize()
wallet.wait_until_synchronized()
msg = "Recovery successful" if wallet.is_found() else "Found no history for this wallet"
else:
msg = "This wallet was restored offline. It may contain more addresses than displayed."
print_msg(msg)
else:
password = password_dialog()
wallet = Wallet(storage)
seed = wallet.make_seed()
wallet.add_seed(seed, password)
wallet.create_master_keys(password)
wallet.create_main_account(password)
wallet.synchronize()
print_msg("Your wallet generation seed is:\n\"%s\"" % seed)
print_msg("Please keep it in a safe place; if you lose it, you will not be able to restore your wallet.")
print_msg("Wallet saved in '%s'" % wallet.storage.path)
sys.exit(0)
if cmd.requires_wallet and not storage.file_exists:
print_msg("Error: Wallet file not found.")
print_msg("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
sys.exit(0)
# create wallet instance
wallet = Wallet(storage) if cmd.requires_wallet else None
# notify plugins
always_hook('cmdline_load_wallet', wallet)
# important warning
if cmd.name in ['getprivatekeys']:
print_stderr("WARNING: ALL your private keys are secret.")
@ -188,7 +207,7 @@ def init_cmdline(config):
print_stderr("In particular, DO NOT use 'redeem private key' services proposed by third parties.")
# commands needing password
if cmd.requires_password and wallet.use_encryption:
if cmd.requires_password and storage.get('use_encryption'):
if config.get('password'):
password = config.get('password')
else:
@ -196,55 +215,49 @@ def init_cmdline(config):
if not password:
print_msg("Error: Password required")
sys.exit(1)
# check password
try:
seed = wallet.check_password(password)
except InvalidPassword:
print_msg("Error: This password does not decode this wallet.")
sys.exit(1)
else:
password = None
# run the command
if cmd.name == 'deseed':
if not wallet.seed:
print_msg("Error: This wallet has no seed")
else:
ns = wallet.storage.path + '.seedless'
print_msg("Warning: you are going to create a seedless wallet'\nIt will be saved in '%s'" % ns)
if raw_input("Are you sure you want to continue? (y/n) ") in ['y', 'Y', 'yes']:
wallet.storage.path = ns
wallet.seed = ''
wallet.storage.put('seed', '', True)
wallet.use_encryption = False
wallet.storage.put('use_encryption', wallet.use_encryption, True)
for k in wallet.imported_keys.keys():
wallet.imported_keys[k] = ''
wallet.storage.put('imported_keys', wallet.imported_keys, True)
print_msg("Done.")
else:
print_msg("Action canceled.")
sys.exit(0)
config_options['password'] = password
elif cmd.name == 'password':
if cmd.name == 'password':
new_password = prompt_password('New password:')
wallet.update_password(password, new_password)
sys.exit(0)
config_options['new_password'] = new_password
return cmd, password, wallet
return cmd, password
def run_offline_command(config, cmd, wallet, password):
def run_offline_command(config, config_options):
cmdname = config.get('cmd')
cmd = known_commands[cmdname]
storage = WalletStorage(config.get_wallet_path())
wallet = Wallet(storage) if cmd.requires_wallet else None
# check password
if cmd.requires_password and storage.get('use_encryption'):
password = config_options.get('password')
try:
seed = wallet.check_password(password)
except InvalidPassword:
print_msg("Error: This password does not decode this wallet.")
sys.exit(1)
if cmd.requires_network:
print_stderr("Warning: running command offline")
# notify plugins
always_hook('cmdline_load_wallet', wallet)
# arguments passed to function
args = map(lambda x: config.get(x), cmd.params)
# decode json arguments
args = map(json_decode, args)
# options
args += map(lambda x: config.get(x), cmd.options)
cmd_runner = Commands(config, wallet, None)
cmd_runner.password = password
cmd_runner = Commands(config, wallet, None,
password=config_options.get('password'),
new_password=config_options.get('new_password'))
func = getattr(cmd_runner, cmd.name)
result = func(*args)
# save wallet
if wallet:
wallet.storage.write()
return result
@ -308,80 +321,84 @@ if __name__ == '__main__':
config_options['url'] = uri
config = SimpleConfig(config_options)
cmd_name = config.get('cmd')
cmdname = config.get('cmd')
# initialize plugins.
gui_name = config.get('gui', 'qt') if cmd_name == 'gui' else 'cmdline'
gui_name = config.get('gui', 'qt') if cmdname == 'gui' else 'cmdline'
plugins = Plugins(config, is_bundle or is_local or is_android, gui_name)
# run command offline
if cmd_name not in ['gui', 'daemon']:
cmd, password, wallet = init_cmdline(config)
if not cmd.requires_network or config.get('offline'):
result = run_offline_command(config, cmd, wallet, password)
print_msg(json_encode(result))
sys.exit(0)
else:
config_options['password'] = password
# run non-RPC commands separately
if cmdname in ['create', 'restore', 'deseed']:
run_non_RPC(config)
sys.exit(0)
# check if a daemon is running
server = get_daemon(config)
# daemon is running
if server is not None:
cmdname = config_options.get('cmd')
if cmdname == 'daemon':
result = server.daemon(config_options)
elif cmdname == 'gui':
if cmdname == 'gui':
if server is not None:
result = server.gui(config_options)
else:
result = server.run_cmdline(config_options)
if type(result) in [str, unicode]:
print_msg(result)
elif type(result) is dict and result.get('error'):
print_stderr(result.get('error'))
elif result is not None:
print_msg(json_encode(result))
sys.exit(0)
# daemon is not running
if cmd_name == 'gui':
if not config.get('offline'):
network = Network(config)
network.start()
plugins.start()
else:
network = None
gui = init_gui(config, network, plugins)
daemon = Daemon(config, network, gui)
daemon.start()
gui.main()
sys.exit(0)
elif cmd_name == 'daemon':
subcommand = config.get('subcommand')
if subcommand in ['status', 'stop']:
print_msg("Daemon not running")
sys.exit(1)
elif subcommand == 'start':
p = os.fork()
if p == 0:
if not config.get('offline'):
network = Network(config)
network.start()
plugins.start()
daemon = Daemon(config, network)
if config.get('websocket_server'):
from electrum import websockets
websockets.WebSocketServer(config, network).start()
if config.get('requests_dir'):
util.check_www_dir(config.get('requests_dir'))
daemon.start()
daemon.join()
else:
print_stderr("starting daemon (PID %d)"%p)
sys.exit(0)
network = None
gui = init_gui(config, network, plugins)
daemon = Daemon(config, network, gui)
daemon.start()
gui.main()
sys.exit(0)
elif cmdname == 'daemon':
if server is not None:
result = server.daemon(config_options)
else:
print_msg("syntax: electrum daemon <start|status|stop>")
sys.exit(1)
subcommand = config.get('subcommand')
if subcommand in ['status', 'stop']:
print_msg("Daemon not running")
sys.exit(1)
elif subcommand == 'start':
p = os.fork()
if p == 0:
network = Network(config)
network.start()
plugins.start()
daemon = Daemon(config, network)
if config.get('websocket_server'):
from electrum import websockets
websockets.WebSocketServer(config, network).start()
if config.get('requests_dir'):
util.check_www_dir(config.get('requests_dir'))
daemon.start()
daemon.join()
sys.exit(0)
else:
print_stderr("starting daemon (PID %d)"%p)
sys.exit(0)
else:
print_msg("syntax: electrum daemon <start|status|stop>")
sys.exit(1)
else:
print_msg("Network daemon is not running. Try 'electrum daemon start'\nIf you want to run this command offline, use the -o flag.")
sys.exit(1)
# command line
init_cmdline(config_options)
if server is not None:
result = server.run_cmdline(config_options)
else:
cmd = known_commands[cmdname]
if cmd.requires_network:
print_msg("Network daemon is not running. Try 'electrum daemon start'")
sys.exit(1)
else:
result = run_offline_command(config, config_options)
# print result
if type(result) in [str, unicode]:
print_msg(result)
elif type(result) is dict and result.get('error'):
print_stderr(result.get('error'))
elif result is not None:
print_msg(json_encode(result))
sys.exit(0)

View File

@ -332,7 +332,7 @@ def get_history_values(n):
except Exception:
time_str = 'pending'
conf_str = 'v' if conf else 'o'
label, is_default_label = wallet.get_label(tx_hash)
label = wallet.get_label(tx_hash)
label = label.replace('<','').replace('>','')
values.append((conf_str, ' ' + time_str, ' ' + format_satoshis(value, True), ' ' + label))

View File

@ -1185,7 +1185,7 @@ class ElectrumWindow:
time_str = 'pending'
conf_icon = Gtk.STOCK_EXECUTE
label, is_default_label = self.wallet.get_label(tx_hash)
label = self.wallet.get_label(tx_hash)
tooltip = tx_hash + "\n%d confirmations"%conf if tx_hash else ''
details = self.get_tx_details(tx_hash)
@ -1300,7 +1300,7 @@ class ElectrumGui():
gap = self.config.get('gap_limit', 5)
if gap != 5:
wallet.gap_limit = gap
wallet.storage.put('gap_limit', gap, True)
wallet.storage.put('gap_limit', gap)
if action == 'create':
seed = wallet.make_seed()

View File

@ -29,6 +29,7 @@ except ImportError:
# minimum required version for kivy
kivy.require('1.8.0')
from electrum.i18n import set_language
from kivy.logger import Logger
from main_window import ElectrumWindow
@ -39,6 +40,7 @@ class ElectrumGui:
self.network = network
self.config = config
self.plugins = plugins
set_language(config.get('language'))
def main(self):
w = ElectrumWindow(config=self.config,

View File

@ -1,8 +1,11 @@
#:import Clock kivy.clock.Clock
#:import Window kivy.core.window.Window
#:import Factory kivy.factory.Factory
#:import _ electrum.i18n._
# Custom Global Widgets
<Button>
on_parent: self.MIN_STATE_TIME = 0.1
<VGridLayout@GridLayout>:
rows: 1
@ -187,22 +190,44 @@
size: self.size
pos: self.pos
<CardItem@ToggleButtonBehavior+BoxLayout>
size_hint: 1, None
height: '65dp'
group: 'requests'
padding: dp(12)
spacing: dp(5)
screen: None
on_release:
self.screen.show_menu(args[0]) if self.state == 'down' else self.screen.hide_menu()
canvas.before:
Color:
rgba: (0.192, .498, 0.745, 1) if self.state == 'down' else (0.3, 0.3, 0.3, 1)
Rectangle:
size: self.size
pos: self.pos
<AddressSelector@BlueSpinner>
icon: 'atlas://gui/kivy/theming/light/globe'
values: [] #app.wallet.addresses() if app.wallet else []
text: _("Select Your address")
<AmountButton@Button>:
<BlueButton@Button>:
background_color: .238, .585, .878, 0
halign: 'left'
text_size: (self.width-10, None)
size_hint: 0.5, None
default_text: 'Amount'
default_text: ''
text: self.default_text
padding: '5dp', '5db'
height: '40dp'
text_color: self.foreground_color
foreground_color: 1, 0, 0, 1
canvas.before:
Color:
rgba: (0.9, .498, 0.745, 1) if self.state == 'down' else self.background_color
Rectangle:
size: self.size
pos: self.pos
<TextInputBlue@TextInput>
@ -222,24 +247,26 @@
size_hint: 1, None
height: '48dp'
on_release:
self.parent.update_text(self.parent, self.text)
self.parent.update_amount(self.text)
<TabbedPanelStrip>:
on_parent:
if self.parent: self.parent.bar_width = 0
if self.parent: self.parent.scroll_x = 0.5
<TabbedCarousel>
carousel: carousel
do_default_tab: False
Carousel:
scroll_timeout: 190
scroll_timeout: 250
scroll_distance: '20dp'
anim_type: 'out_quart'
min_move: .05
anim_move_duration: .1
anim_cancel_duration: .54
scroll_distance: '10dp'
on_index: root.on_index(*args)
id: carousel
@ -281,65 +308,59 @@
TabbedCarousel:
id: panel
tab_height: '48dp'
#default_tab: send_tab
tab_width: panel.width/3
default_tab: history_tab
strip_border: 0, 0, 0, 0
HistoryScreen:
id: history_screen
tab: history_tab
InvoicesScreen:
id: invoices_screen
tab: invoices_tab
SendScreen:
id: send_screen
tab: send_tab
HistoryScreen:
id: history_screen
tab: history_tab
ReceiveScreen:
id: receive_screen
tab: receive_tab
ContactsScreen:
id: contacts_screen
tab: contacts_tab
RequestsScreen:
id: requests_screen
tab: requests_tab
#ContactsScreen:
# id: contacts_screen
# tab: contacts_tab
CleanHeader:
id: history_tab
text: _('History')
id: invoices_tab
text: _('Invoices')
slide: 0
CleanHeader:
id: send_tab
text: _('Send')
slide: 1
CleanHeader:
id: receive_tab
text: _('Receive')
id: history_tab
text: _('History')
slide: 2
CleanHeader:
id: contacts_tab
text: _('Contacts')
id: receive_tab
text: _('Receive')
slide: 3
CleanHeader:
id: requests_tab
text: _('Requests')
slide: 4
#CleanHeader:
# id: contacts_tab
# text: _('Contacts')
# slide: 4
<ActionOvrButton@ActionButton>
on_release:
if self.parent: self.parent.parent.dismiss()
app.popup_dialog(self.name)
<SettingsItem@ButtonBehavior+BoxLayout>
orientation: 'vertical'
title: ''
description: ''
size_hint: 1, 1
Label:
id: title
text: self.parent.title
size_hint: 1, 1
bold: True
text_size: self.size
halign: 'left'
Label:
text: self.parent.description
size_hint: 1, 1
text_size: self.width, None
color: 0.8, 0.8, 0.8, 1
halign: 'left'
Clock.schedule_once(lambda dt: self.parent.parent.dismiss() if self.parent else None, 0.05)
Clock.schedule_once(lambda dt: app.popup_dialog(self.name), 0.05)
BoxLayout:
orientation: 'vertical'
canvas.before:
@ -373,34 +394,26 @@ BoxLayout:
.format(app.status)
font_size: '22dp'
minimum_width: '1dp'
ActionButton:
id: context_button
text: app.context
width: 0
on_text:
self.width = 20 if self.text else 0
on_release: app.context_action()
on_release: app.popup_dialog('status')
ActionOverflow:
id: ao
ActionOvrButton:
text: _('Network')
name: 'about'
text: _('About')
ActionOvrButton:
name: 'network'
text: _('Network')
on_parent:
# when widget overflow drop down is shown, adjust the width
parent = args[1]
if parent: ao._dropdown.width = sp(200)
ActionOvrButton:
name: 'settings'
text: _('Settings')
ActionOvrButton:
name: 'wallets'
text: _('Wallets')
ActionOvrButton:
name: 'plugins'
text: _('Plugins')
name: 'settings'
text: _('Settings')
ScreenManager:
id: manager
ScreenTabs:

View File

@ -7,11 +7,13 @@ from decimal import Decimal
import electrum
from electrum import WalletStorage, Wallet
from electrum.i18n import _, set_language
from electrum.i18n import _
from electrum.contacts import Contacts
from electrum.paymentrequest import InvoiceStore
from electrum.util import profiler, InvalidPassword
from electrum.plugins import run_hook
from electrum.util import format_satoshis, format_satoshis_plain
from electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_UNKNOWN, PR_EXPIRED
from kivy.app import App
from kivy.core.window import Window
@ -31,6 +33,7 @@ Factory.register('InstallWizard',
Factory.register('InfoBubble', module='electrum_gui.kivy.uix.dialogs')
Factory.register('ELTextInput', module='electrum_gui.kivy.uix.screens')
#from kivy.core.window import Window
#Window.softinput_mode = 'below_target'
@ -54,8 +57,8 @@ from kivy.core.clipboard import Clipboard
Factory.register('TabbedCarousel', module='electrum_gui.kivy.uix.screens')
from electrum.util import base_units
base_units = {'BTC':8, 'mBTC':5, 'uBTC':2}
class ElectrumWindow(App):
@ -72,13 +75,6 @@ class ElectrumWindow(App):
self.history_screen.update()
base_unit = AliasProperty(_get_bu, _set_bu)
def _rotate_bu(self):
keys = sorted(base_units.keys())
self.base_unit = keys[ (keys.index(self.base_unit) + 1) % len(keys)]
context = StringProperty('')
context_action = lambda x: None
status = StringProperty('')
fiat_unit = StringProperty('')
@ -165,6 +161,9 @@ class ElectrumWindow(App):
self.nfcscanner = None
self.tabs = None
self.receive_address = None
self.current_invoice = None
super(ElectrumWindow, self).__init__(**kwargs)
title = _('Electrum App')
@ -176,6 +175,7 @@ class ElectrumWindow(App):
#self.config = self.gui_object.config
self.contacts = Contacts(self.electrum_config)
self.invoices = InvoiceStore(self.electrum_config)
self.bind(url=self.set_URI)
# were we sent a url?
@ -191,15 +191,60 @@ class ElectrumWindow(App):
self._trigger_notify_transactions = \
Clock.create_trigger(self.notify_transactions, 5)
def get_receive_address(self):
return self.receive_address if self.receive_address else self.wallet.get_unused_address(None)
def do_pay(self, obj):
pr = self.invoices.get(obj.key)
self.on_pr(pr)
def on_pr(self, pr):
if pr.verify(self.contacts):
key = self.invoices.add(pr)
self.invoices_screen.update()
status = self.invoices.get_status(key)
if status == PR_PAID:
self.show_error("invoice already paid")
self.send_screen.do_clear()
else:
if pr.has_expired():
self.show_error(_('Payment request has expired'))
else:
self.current_invoice = pr
self.update_tab('send')
self.switch_to('send')
else:
self.show_error("invoice error:" + pr.error)
self.send_screen.do_clear()
def set_URI(self, url):
try:
url = electrum.util.parse_URI(url)
url = electrum.util.parse_URI(url, self.on_pr)
except:
self.show_info("Invalid URI", url)
return
self.send_screen.set_URI(url)
def update_tab(self, name):
s = getattr(self, name + '_screen', None)
if s:
s.update()
@profiler
def update_tabs(self):
for tab in ['invoices', 'send', 'history', 'receive', 'requests']:
self.update_tab(tab)
def switch_to(self, name):
tab = self.tabs.ids[name + '_tab']
self.tabs.ids.panel.switch_to(tab)
def show_request(self, addr):
self.receive_address = addr
self.update_tab('receive')
self.switch_to('receive')
def scan_qr(self, on_complete):
from jnius import autoclass
from android import activity
@ -212,31 +257,10 @@ class ElectrumWindow(App):
if resultCode == -1: # RESULT_OK:
contents = intent.getStringExtra("SCAN_RESULT")
if intent.getStringExtra("SCAN_RESULT_FORMAT") == 'QR_CODE':
try:
uri = electrum.util.parse_URI(contents)
except:
self.show_info("Invalid URI", url)
return
on_complete(uri)
on_complete(contents)
activity.bind(on_activity_result=on_qr_result)
PythonActivity.mActivity.startActivityForResult(intent, 0)
def show_plugins(self, plugins_list):
def on_active(sw, value):
self.plugins.toggle_enabled(self.electrum_config, sw.name)
run_hook('init_kivy', self)
for item in self.plugins.descriptions:
if 'kivy' not in item.get('available_for', []):
continue
name = item.get('__name__')
label = Label(text=item.get('fullname'), height='48db', size_hint=(1, None))
plugins_list.add_widget(label)
sw = Switch()
sw.name = name
p = self.plugins.get(name)
sw.active = (p is not None) and p.is_enabled()
sw.bind(active=on_active)
plugins_list.add_widget(sw)
def build(self):
return Builder.load_file('gui/kivy/main.kv')
@ -273,30 +297,46 @@ class ElectrumWindow(App):
win.bind(keyboard_height=self.on_keyboard_height)
self.on_size(win, win.size)
self.init_ui()
self.load_wallet_by_name(self.electrum_config.get_wallet_path())
def load_wallet_by_name(self, wallet_path):
if not wallet_path:
return
self.stop_wallet()
config = self.electrum_config
storage = WalletStorage(config.get_wallet_path())
storage = WalletStorage(wallet_path)
Logger.info('Electrum: Check for existing wallet')
if storage.file_exists:
wallet = Wallet(storage)
action = wallet.get_action()
else:
action = 'new'
if action is not None:
# start installation wizard
Logger.debug('Electrum: Wallet not found. Launching install wizard')
wizard = Factory.InstallWizard(config, self.network, storage)
wizard.bind(on_wizard_complete=self.on_wizard_complete)
wizard.bind(on_wizard_complete=lambda instance, wallet: self.load_wallet(wallet))
wizard.run(action)
else:
wallet.start_threads(self.network)
self.on_wizard_complete(None, wallet)
self.load_wallet(wallet)
self.on_resume()
def create_wallet_dialog(self, l):
from uix.dialogs.label_dialog import LabelDialog
def f(text):
if text:
l.text = text
d = LabelDialog(_('Enter wallet name'), '', f)
d.open()
def on_stop(self):
self.stop_wallet()
def stop_wallet(self):
if self.wallet:
self.wallet.stop_threads()
@ -316,7 +356,6 @@ class ElectrumWindow(App):
active_widg = self.root.children[0]
except IndexError:
return
try:
fw = self._focused_widget
except AttributeError:
@ -350,21 +389,18 @@ class ElectrumWindow(App):
self.gui.main_gui.toggle_settings(self)
return True
def on_wizard_complete(self, instance, wallet):
if not wallet:
Logger.debug('Electrum: No Wallet set/found. Exiting...')
app = App.get_running_app()
app.show_error('Electrum: No Wallet set/found. Exiting...',
exit=True)
self.init_ui()
self.load_wallet(wallet)
def popup_dialog(self, name):
popup = Builder.load_file('gui/kivy/uix/ui_screens/'+name+'.kv')
popup.open()
if name == 'settings':
from uix.dialogs.settings import SettingsDialog
d = SettingsDialog(self)
d.open()
elif name == 'wallets':
from uix.dialogs.wallets import WalletDialog
d = WalletDialog()
d.open()
else:
popup = Builder.load_file('gui/kivy/uix/ui_screens/'+name+'.kv')
popup.open()
@profiler
def init_ui(self):
@ -372,7 +408,6 @@ class ElectrumWindow(App):
tasks of setting up the ui.
'''
from weakref import ref
set_language(self.electrum_config.get('language'))
self.funds_error = False
# setup UX
@ -401,7 +436,8 @@ class ElectrumWindow(App):
interests = ['updated', 'status', 'new_transaction']
self.network.register_callback(self.on_network, interests)
self.wallet = None
#self.wallet = None
self.tabs = self.root.ids['tabs']
def on_network(self, event, *args):
if event == 'updated':
@ -418,7 +454,7 @@ class ElectrumWindow(App):
self.update_wallet()
# Once GUI has been initialized check if we want to announce something
# since the callback has been called before the GUI was initialized
self.update_history_tab()
self.update_tabs()
self.notify_transactions()
run_hook('load_wallet', wallet, self)
@ -447,58 +483,17 @@ class ElectrumWindow(App):
amount, fee = self.wallet.get_max_amount(self.electrum_config, inputs, None)
return format_satoshis_plain(amount, self.decimal_point())
def update_password(self, label, c):
text = label.password
if c == '<':
text = text[:-1]
elif c == 'Clear':
text = ''
else:
text += c
label.password = text
def toggle_fiat(self, a):
a.is_fiat = not a.is_fiat
def update_amount(self, label, c):
amount = label.fiat_amount if label.is_fiat else label.amount
if c == '<':
amount = amount[:-1]
elif c == '.' and amount == '':
amount = '0.'
elif c == '0' and amount == '0':
amount = '0'
else:
try:
Decimal(amount+c)
amount += c
except:
pass
if label.is_fiat:
label.fiat_amount = amount
else:
label.amount = amount
def format_amount(self, x, is_diff=False, whitespaces=False):
return format_satoshis(x, is_diff, 0, self.decimal_point(), whitespaces)
def format_amount_and_units(self, x):
return format_satoshis_plain(x, self.decimal_point()) + ' ' + self.base_unit
@profiler
def update_wallet(self, *dt):
self._trigger_update_status()
if self.wallet.up_to_date or not self.network or not self.network.is_connected():
self.update_history_tab()
self.update_contacts_tab()
@profiler
def update_history_tab(self, see_all=False):
if self.history_screen:
self.history_screen.update(see_all)
def update_contacts_tab(self):
if self.contacts_screen:
self.contacts_screen.update()
#if self.wallet.up_to_date or not self.network or not self.network.is_connected():
self.update_tabs()
@profiler
@ -593,64 +588,10 @@ class ElectrumWindow(App):
else:
self.show_error(_('Invalid Address'))
def send_payment(self, address, amount=0, label='', message=''):
tabs = self.tabs
screen_send = tabs.ids.screen_send
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.wallet.set_label(address, label)
# switch_to the send screen
tabs.ids.panel.switch_to(tabs.ids.tab_send)
label = self.wallet.labels.get(address)
m_addr = label + ' <'+ address +'>' if label else address
# populate
def set_address(*l):
content = screen_send.ids
content.payto_e.text = m_addr
content.message_e.text = message
if amount:
content.amount_e.text = amount
# wait for screen to load
Clock.schedule_once(set_address, .5)
def set_send(self, address, amount, label, message):
self.send_payment(address, amount=amount, label=label, message=message)
def prepare_for_payment_request(self):
tabs = self.tabs
screen_send = tabs.ids.screen_send
# switch_to the send screen
tabs.ids.panel.switch_to(tabs.ids.tab_send)
content = screen_send.ids
if content:
self.set_frozen(content, False)
screen_send.screen_label.text = _("please wait...")
return True
def payment_request_ok(self):
tabs = self.tabs
screen_send = tabs.ids.screen_send
# switch_to the send screen
tabs.ids.panel.switch_to(tabs.ids.tab_send)
self.set_frozen(content, True)
screen_send.ids.payto_e.text = self.gui_object.payment_request.domain
screen_send.ids.amount_e.text = self.format_amount(self.gui_object.payment_request.get_amount())
screen_send.ids.message_e.text = self.gui_object.payment_request.memo
# wait for screen to load
Clock.schedule_once(set_address, .5)
def set_frozen(self, entry, frozen):
if frozen:
@ -660,15 +601,6 @@ class ElectrumWindow(App):
entry.disabled = False
Factory.Animation(opacity=1).start(content)
def payment_request_error(self):
tabs = self.tabs
screen_send = tabs.ids.screen_send
# switch_to the send screen
tabs.ids.panel.switch_to(tabs.ids.tab_send)
self.do_clear()
self.show_info(self.gui_object.payment_request.error)
def show_error(self, error, width='200dp', pos=None, arrow_pos=None,
exit=False, icon='atlas://gui/kivy/theming/light/error', duration=0,
@ -737,43 +669,32 @@ class ElectrumWindow(App):
pos = (win.center[0], win.center[1] - (info_bubble.height/2))
info_bubble.show(pos, duration, width, modal=modal, exit=exit)
def tx_dialog(self, tx_hash):
def tx_details_dialog(self, obj):
popup = Builder.load_file('gui/kivy/uix/ui_screens/transaction.kv')
popup.tx_hash = tx_hash
popup.tx_hash = obj.tx_hash
popup.open()
def tx_selected(self, txid, state):
if state == 'down':
self.context = 'tx'
self.context_action = lambda: self.tx_dialog(txid)
else:
self.reset_context()
def address_dialog(self, screen):
pass
def reset_context(self):
self.context = ''
self.context_action = lambda: None
def description_dialog(self, screen):
from uix.dialogs.label_dialog import LabelDialog
text = screen.message
def callback(text):
screen.message = text
d = LabelDialog(_('Enter description'), text, callback)
d.open()
@profiler
def amount_dialog(self, screen, show_max):
popup = Builder.load_file('gui/kivy/uix/ui_screens/amount.kv')
but_max = popup.ids.but_max
if not show_max:
but_max.disabled = True
but_max.opacity = 0
else:
but_max.disabled = False
but_max.opacity = 1
from uix.dialogs.amount_dialog import AmountDialog
amount = screen.amount
if amount:
a, u = str(amount).split()
amount, u = str(amount).split()
assert u == self.base_unit
popup.ids.kb.amount = a
def cb():
o = popup.ids.a.btc_text
screen.amount = o
popup.on_dismiss = cb
def cb(amount):
screen.amount = amount
popup = AmountDialog(show_max, amount, cb)
popup.open()
def protected(self, f, args):
@ -804,12 +725,9 @@ class ElectrumWindow(App):
self.show_error("PIN numbers do not match")
def password_dialog(self, title, f, args):
popup = Builder.load_file('gui/kivy/uix/ui_screens/password.kv')
popup.title = title
def callback():
pw = popup.ids.kb.password
from uix.dialogs.password_dialog import PasswordDialog
def callback(pw):
Clock.schedule_once(lambda x: apply(f, args + (pw,)), 0.1)
popup.on_dismiss = callback
popup = PasswordDialog(title, callback)
popup.open()

View File

@ -0,0 +1,52 @@
#!python
#!/usr/bin/env python
from kivy.app import App
from kivy.uix.bubble import Bubble
from kivy.animation import Animation
from kivy.uix.floatlayout import FloatLayout
from kivy.lang import Builder
from kivy.factory import Factory
from kivy.clock import Clock
Builder.load_string('''
<MenuItem@Button>
background_color: .2, .9, 1, 1
height: '48dp'
size_hint: 1, None
<ContextMenu>
size_hint: 1, None
height: '48dp'
pos: (0, 0)
show_arrow: False
arrow_pos: 'top_mid'
padding: 0
orientation: 'horizontal'
BoxLayout:
size_hint: 1, 1
height: '48dp'
orientation: 'horizontal'
id: buttons
''')
class MenuItem(Factory.Button):
pass
class ContextMenu(Bubble):
def __init__(self, obj, action_list):
Bubble.__init__(self)
self.obj = obj
for k, v in action_list:
l = MenuItem()
l.text = k
def func(f=v):
Clock.schedule_once(lambda dt: self.hide(), 0.1)
Clock.schedule_once(lambda dt: f(obj), 0.15)
l.on_release = func
self.ids.buttons.add_widget(l)
def hide(self):
if self.parent:
self.parent.hide_menu()

View File

@ -144,6 +144,7 @@ class InfoBubble(Factory.Bubble):
m.add_widget(self)
else:
Window.add_widget(self)
# wait for the bubble to adjust it's size according to text then animate
Clock.schedule_once(lambda dt: self._show(pos, duration))

View File

@ -1,13 +1,16 @@
#:import Decimal decimal.Decimal
from kivy.app import App
from kivy.factory import Factory
from kivy.properties import ObjectProperty
from kivy.lang import Builder
from decimal import Decimal
Builder.load_string('''
Popup:
<AmountDialog@Popup>
id: popup
title: _('Amount')
AnchorLayout:
anchor_x: 'center'
BoxLayout:
orientation: 'vertical'
size_hint: 0.8, 1
@ -18,7 +21,7 @@ Popup:
id: a
btc_text: (kb.amount + ' ' + app.base_unit) if kb.amount else ''
fiat_text: (kb.fiat_amount + ' ' + app.fiat_unit) if kb.fiat_amount else ''
text: (self.fiat_text + ' / ' + self.btc_text if kb.is_fiat else self.btc_text + ' / ' + self.fiat_text) if self.btc_text else ''
text: ((self.fiat_text + ' / ' + self.btc_text if kb.is_fiat else self.btc_text + ' / ' + self.fiat_text) if app.fiat_unit else self.btc_text) if self.btc_text else ''
size_hint: 1, 1
font_size: '22dp'
Widget:
@ -30,8 +33,8 @@ Popup:
is_fiat: False
on_fiat_amount: if self.is_fiat: self.amount = app.fiat_to_btc(self.fiat_amount)
on_amount: if not self.is_fiat: self.fiat_amount = app.btc_to_fiat(self.amount)
update_text: app.update_amount
size_hint: 1, None
update_amount: popup.update_amount
height: '300dp'
cols: 3
KButton:
@ -60,6 +63,8 @@ Popup:
text: '<'
Button:
id: but_max
opacity: 1 if root.show_max else 0
disabled: not root.show_max
size_hint: 1, None
height: '48dp'
text: 'Max'
@ -70,9 +75,9 @@ Popup:
id: button_fiat
size_hint: 1, None
height: '48dp'
text: app.fiat_unit if kb.is_fiat else app.base_unit
text: (app.fiat_unit if kb.is_fiat else app.base_unit) if app.fiat_unit else ''
on_release:
app.toggle_fiat(kb)
popup.toggle_fiat(kb)
Button:
size_hint: 1, None
height: '48dp'
@ -80,18 +85,53 @@ Popup:
on_release:
kb.amount = ''
kb.fiat_amount = ''
Widget:
size_hint: 1, None
BoxLayout:
size_hint: 1, None
height: '48dp'
Widget:
size_hint: 2, None
size_hint: 1, None
height: '48dp'
Button:
size_hint: 1, None
height: '48dp'
text: _('OK')
on_release: popup.dismiss()
on_release:
root.callback(a.btc_text)
popup.dismiss()
''')
from kivy.properties import BooleanProperty
class AmountDialog(Factory.Popup):
show_max = BooleanProperty(False)
def __init__(self, show_max, amount, cb):
Factory.Popup.__init__(self)
self.show_max = show_max
self.callback = cb
if amount:
self.ids.kb.amount = amount
def toggle_fiat(self, a):
a.is_fiat = not a.is_fiat
def update_amount(self, c):
kb = self.ids.kb
amount = kb.fiat_amount if kb.is_fiat else kb.amount
if c == '<':
amount = amount[:-1]
elif c == '.' and amount in ['0', '']:
amount = '0.'
elif amount == '0':
amount = c
else:
try:
Decimal(amount+c)
amount += c
except:
pass
if kb.is_fiat:
kb.fiat_amount = amount
else:
kb.amount = amount

View File

@ -0,0 +1,65 @@
from kivy.app import App
from kivy.factory import Factory
from kivy.properties import ObjectProperty
from kivy.lang import Builder
from kivy.uix.checkbox import CheckBox
from kivy.uix.label import Label
from kivy.uix.widget import Widget
Builder.load_string('''
<ChoiceDialog@Popup>
id: popup
title: ''
size_hint: 0.8, 0.8
pos_hint: {'top':0.9}
BoxLayout:
orientation: 'vertical'
Widget:
size_hint: 1, 0.1
ScrollView:
orientation: 'vertical'
size_hint: 1, 0.8
GridLayout:
row_default_height: '48dp'
orientation: 'vertical'
id: choices
cols: 2
size_hint: 1, 1
BoxLayout:
orientation: 'horizontal'
size_hint: 1, 0.2
Button:
text: 'Cancel'
size_hint: 0.5, None
height: '48dp'
on_release: popup.dismiss()
Button:
text: 'OK'
size_hint: 0.5, None
height: '48dp'
on_release:
root.callback(popup.value)
popup.dismiss()
''')
class ChoiceDialog(Factory.Popup):
def __init__(self, title, choices, key, callback):
Factory.Popup.__init__(self)
for k, v in choices.items():
l = Label(text=v)
l.height = '48dp'
cb = CheckBox(group='choices')
cb.value = k
cb.height = '48dp'
def f(cb, x):
if x: self.value = cb.value
cb.bind(active=f)
if k == key:
cb.active = True
self.ids.choices.add_widget(l)
self.ids.choices.add_widget(cb)
self.ids.choices.add_widget(Widget(size_hint_y=1))
self.callback = callback
self.title = title
self.value = key

View File

@ -43,7 +43,6 @@ Builder.load_string('''
on_release: if self.root: self.root.dispatch('on_release', self)
<-CreateAccountDialog>
text_color: .854, .925, .984, 1
auto_dismiss: False
@ -140,11 +139,11 @@ Builder.load_string('''
height: self.minimum_height
CreateAccountButton:
id: create
text: _('Create a Wallet')
text: _('Create a new seed')
root: root
CreateAccountButton:
id: restore
text: _('I already have a wallet')
text: _('I already have a seed')
root: root
@ -457,8 +456,7 @@ class RestoreSeedDialog(CreateAccountDialog):
self._trigger_check_seed = Clock.create_trigger(self.check_seed)
def check_seed(self, dt):
self.ids.next.disabled = not bool(self._wizard.is_any(
self.ids.text_input_seed))
self.ids.next.disabled = not bool(self._wizard.is_any(self.ids.text_input_seed))
def on_parent(self, instance, value):
if value:

View File

@ -73,7 +73,9 @@ class InstallWizard(Widget):
def is_any(self, seed_e):
text = self.get_seed_text(seed_e)
return (Wallet.is_seed(text) or
Wallet.is_mpk(text) or
Wallet.is_old_mpk(text) or
Wallet.is_xpub(text) or
Wallet.is_xprv(text) or
Wallet.is_address(text) or
Wallet.is_private_key(text))
@ -129,8 +131,8 @@ class InstallWizard(Widget):
if Wallet.is_seed(seed):
return self.password_dialog(wallet=wallet, mode='restore',
seed=seed)
elif Wallet.is_mpk(seed):
wallet = Wallet.from_mpk(seed, self.storage)
elif Wallet.is_xpub(seed):
wallet = Wallet.from_xpub(seed, self.storage)
elif Wallet.is_address(seed):
wallet = Wallet.from_address(seed, self.storage)
elif Wallet.is_private_key(seed):
@ -257,18 +259,19 @@ class InstallWizard(Widget):
new_password = None
if mode == 'restore':
wallet = Wallet.from_seed(seed, self.storage)
password = (unicode(ti_password.text)
if wallet and wallet.use_encryption else
None)
password = unicode(ti_password.text)
# if wallet and wallet.use_encryption else
# None)
if not password:
password = None
wallet = Wallet.from_text(seed, password, self.storage)
def on_complete(*l):
wallet.create_accounts(new_password)
self.load_network(wallet, mode='restore')
_dlg.close()
self.waiting_dialog(lambda: wallet.add_seed(seed, new_password),
msg=_("saving seed"),
self.waiting_dialog(wallet.synchronize,
msg=_("generating addresses"),
on_complete=on_complete)
return

View File

@ -0,0 +1,55 @@
from kivy.app import App
from kivy.factory import Factory
from kivy.properties import ObjectProperty
from kivy.lang import Builder
Builder.load_string('''
<LabelDialog@Popup>
id: popup
title: ''
size_hint: 0.8, 0.3
pos_hint: {'top':0.9}
BoxLayout:
orientation: 'vertical'
Widget:
size_hint: 1, 0.2
TextInput:
id:input
padding: '5dp'
size_hint: 1, None
height: '27dp'
pos_hint: {'center_y':.5}
text:''
multiline: False
background_normal: 'atlas://gui/kivy/theming/light/tab_btn'
background_active: 'atlas://gui/kivy/theming/light/textinput_active'
hint_text_color: self.foreground_color
foreground_color: 1, 1, 1, 1
font_size: '16dp'
focus: True
Widget:
size_hint: 1, 0.2
BoxLayout:
orientation: 'horizontal'
size_hint: 1, 0.5
Button:
text: 'Cancel'
size_hint: 0.5, None
height: '48dp'
on_release: popup.dismiss()
Button:
text: 'OK'
size_hint: 0.5, None
height: '48dp'
on_release:
root.callback(input.text)
popup.dismiss()
''')
class LabelDialog(Factory.Popup):
def __init__(self, title, text, callback):
Factory.Popup.__init__(self)
self.ids.input.text = text
self.callback = callback
self.title = title

View File

@ -1,4 +1,12 @@
Popup:
from kivy.app import App
from kivy.factory import Factory
from kivy.properties import ObjectProperty
from kivy.lang import Builder
from decimal import Decimal
Builder.load_string('''
<PasswordDialog@Popup>
id: popup
title: _('Enter PIN Code')
size_hint: 0.9, 0.9
@ -16,9 +24,9 @@ Popup:
GridLayout:
id: kb
update_text: app.update_password
update_amount: popup.update_password
password: ''
on_password: if len(self.password) == 6: popup.dismiss()
on_password: popup.on_password(self.password)
size_hint: 1, None
height: '300dp'
cols: 3
@ -49,3 +57,28 @@ Popup:
Widget:
size_hint: 1, 1
''')
class PasswordDialog(Factory.Popup):
def __init__(self, title, cb):
Factory.Popup.__init__(self)
self.title = title
self.callback = cb
def update_password(self, c):
kb = self.ids.kb
text = kb.password
if c == '<':
text = text[:-1]
elif c == 'Clear':
text = ''
else:
text += c
kb.password = text
def on_password(self, pw):
if len(pw) == 6:
self.dismiss()
self.callback(pw)

View File

@ -0,0 +1,171 @@
from kivy.app import App
from kivy.factory import Factory
from kivy.properties import ObjectProperty
from kivy.lang import Builder
from electrum.i18n import _
from electrum.util import base_units
from electrum.i18n import languages, set_language
Builder.load_string('''
<SettingsItem@ButtonBehavior+BoxLayout>
orientation: 'vertical'
title: ''
description: ''
size_hint: 1, 1
Label:
id: title
text: self.parent.title
size_hint: 1, 1
bold: True
text_size: self.size
halign: 'left'
Label:
text: self.parent.description
size_hint: 1, 1
text_size: self.width, None
color: 0.8, 0.8, 0.8, 1
halign: 'left'
<PluginItem@ButtonBehavior+BoxLayout>
orientation: 'vertical'
title: ''
description: ''
size_hint: 1, 1
BoxLayout:
orientation: 'horizontal'
Label:
id: title
text: self.parent.title
size_hint: 1, 1
bold: True
text_size: self.size
halign: 'left'
Switch:
id: sw
name: ''
Label:
text: self.parent.description
size_hint: 1, 1
text_size: self.width, None
color: 0.8, 0.8, 0.8, 1
halign: 'left'
<SettingsDialog@Popup>
id: settings
title: _('Settings')
BoxLayout:
orientation: 'vertical'
SettingsItem:
lang: settings.get_language_name()
title: _('Language') + ': %s'%self.lang
description: _("Language")
on_release:
settings.language_dialog(self)
CardSeparator
SettingsItem:
title: _('PIN Code') + ': %s'%('ON' if app.wallet.use_encryption else 'OFF')
description: _("Your PIN code will be required in order to spend bitcoins.")
on_release:
app.change_password()
self.title = _('PIN Code') + ' (%s)'%('ON' if app.wallet.use_encryption else 'OFF')
CardSeparator
SettingsItem:
bu: app.base_unit
title: _('Denomination') + ': ' + self.bu
description: _("Base unit for Bitcoin amounts.")
on_release:
settings.unit_dialog(self)
CardSeparator
SettingsItem:
title: _('Fiat Currency') + ': ' + app.fiat_unit
description: "Select the local fiat currency."
on_release:
settings.fiat_dialog(self)
CardSeparator
SettingsItem:
title: _('OpenAlias')
description: "Email-like address."
on_release:
settings.openalias_dialog()
Widget:
size_hint: 1, 1
BoxLayout:
Widget:
size_hint: 0.5, None
Button:
size_hint: 0.5, None
height: '48dp'
text: _('OK')
on_release:
settings.dismiss()
''')
class SettingsDialog(Factory.Popup):
def __init__(self, app):
self.app = app
Factory.Popup.__init__(self)
def get_language_name(self):
return languages.get(self.app.electrum_config.get('language', 'en_UK'), '')
def language_dialog(self, item):
from choice_dialog import ChoiceDialog
l = self.app.electrum_config.get('language', 'en_UK')
def cb(key):
self.app.electrum_config.set_key("language", key, True)
item.lang = self.get_language_name()
set_language(key)
d = ChoiceDialog(_('Language'), languages, l, cb)
d.open()
def unit_dialog(self, item):
from choice_dialog import ChoiceDialog
def cb(text):
self.app._set_bu(text)
item.bu = self.app.base_unit
d = ChoiceDialog(_('Denomination'), dict(map(lambda x: (x,x), base_units)), self.app.base_unit, cb)
d.open()
def fiat_dialog(self, item):
from choice_dialog import ChoiceDialog
def cb(text):
pass
d = ChoiceDialog(_('Fiat Currency'), {}, '', cb)
d.open()
def openalias_dialog(self):
from label_dialog import LabelDialog
def callback(text):
pass
d = LabelDialog(_('OpenAlias'), '', callback)
d.open()
def show_plugins(self, plugins_list):
def on_active(sw, value):
self.plugins.toggle_enabled(self.electrum_config, sw.name)
run_hook('init_kivy', self)
for item in self.plugins.descriptions:
if 'kivy' not in item.get('available_for', []):
continue
name = item.get('__name__')
label = Label(text=item.get('fullname'), height='48db', size_hint=(1, None))
plugins_list.add_widget(label)
sw = Switch()
sw.name = name
p = self.plugins.get(name)
sw.active = (p is not None) and p.is_enabled()
sw.bind(active=on_active)
plugins_list.add_widget(sw)
class PluginItem():
def __init__(self, name):
p = self.plugins.get(name)
sw.active = (p is not None) and p.is_enabled()
sw.bind(active=on_active)
plugins_list.add_widget(sw)

View File

@ -0,0 +1,79 @@
from kivy.app import App
from kivy.factory import Factory
from kivy.properties import ObjectProperty
from kivy.lang import Builder
from electrum.i18n import _
from electrum.util import base_units
import os
from label_dialog import LabelDialog
Builder.load_string('''
#:import os os
<WalletDialog@Popup>:
title: _('Wallets')
id: popup
path: app.wallet.storage.path
on_path:
button.text = _('Open') if os.path.exists(popup.path) else _('Create')
BoxLayout:
orientation: 'vertical'
BoxLayout:
height: '48dp'
size_hint_y: None
orientation: 'horizontal'
Label:
text: _('Wallet') + ': '
height: '48dp'
size_hint_y: None
Button:
id: wallet_name
height: '48dp'
size_hint_y: None
text: os.path.basename(app.wallet.storage.path)
on_release:
root.name_dialog()
on_text:
popup.path = os.path.join(wallet_selector.path, self.text)
Widget
size_hint_y: None
FileChooserListView:
id: wallet_selector
dirselect: False
filter_dirs: True
filter: '*.*'
path: os.path.dirname(app.wallet.storage.path)
on_selection:
wallet_name.text = os.path.basename(self.selection[0]) if self.selection else ''
size_hint_y: 0.4
Widget
size_hint_y: 0.1
GridLayout:
cols: 2
size_hint_y: None
Button:
size_hint: 0.5, None
height: '48dp'
text: _('Cancel')
on_release:
popup.dismiss()
Button:
id: button
size_hint: 0.5, None
height: '48dp'
text: _('Open') if os.path.exists(popup.path) else _('Create')
on_release:
popup.dismiss()
app.load_wallet_by_name(popup.path)
''')
class WalletDialog(Factory.Popup):
def name_dialog(self):
def cb(text):
if text:
self.ids.wallet_name.text = text
d = LabelDialog(_('Enter wallet name'), '', cb)
d.open()

View File

@ -42,14 +42,9 @@ Builder.load_string('''
class QRCodeWidget(FloatLayout):
data = StringProperty(None, allow_none=True)
background_color = ListProperty((1, 1, 1, 1))
foreground_color = ListProperty((0, 0, 0, 0))
#loading_image = StringProperty('gui/kivy/theming/loading.gif')
def __init__(self, **kwargs):
super(QRCodeWidget, self).__init__(**kwargs)
self.data = None
@ -57,21 +52,11 @@ class QRCodeWidget(FloatLayout):
self._qrtexture = None
def on_data(self, instance, value):
print "on data", value
if not (self.canvas or value):
return
img = self.ids.get('qrimage', None)
if not img:
# if texture hasn't yet been created delay the texture updation
Clock.schedule_once(lambda dt: self.on_data(instance, value))
return
#Thread(target=partial(self.update_qr, )).start()
self.update_qr()
def set_data(self, data):
print "set data", data
if self.data == data:
return
MinSize = 210 if len(data) < 128 else 500
@ -98,7 +83,7 @@ class QRCodeWidget(FloatLayout):
# currently unused, do we need this?
self._texture_size = size
def _create_texture(self, k, dt):
def _create_texture(self, k):
self._qrtexture = texture = Texture.create(size=(k,k), colorfmt='rgb')
# don't interpolate texture
texture.min_filter = 'nearest'
@ -107,32 +92,24 @@ class QRCodeWidget(FloatLayout):
def update_texture(self):
if not self.qr:
return
matrix = self.qr.get_matrix()
k = len(matrix)
# create the texture in main UI thread otherwise
# this will lead to memory corruption
Clock.schedule_once(partial(self._create_texture, k), -1)
# create the texture
self._create_texture(k)
buff = []
bext = buff.extend
cr, cg, cb, ca = self.background_color[:]
cr, cg, cb = cr*255, cg*255, cb*255
for r in range(k):
for c in range(k):
bext([0, 0, 0] if matrix[r][c] else [cr, cg, cb])
bext([0, 0, 0] if matrix[k-1-r][c] else [cr, cg, cb])
# then blit the buffer
buff = ''.join(map(chr, buff))
# update texture in UI thread.
Clock.schedule_once(lambda dt: self._upd_texture(buff), .1)
# update texture
self._upd_texture(buff)
def _upd_texture(self, buff):
texture = self._qrtexture
if not texture:
# if texture hasn't yet been created delay the texture updation
Clock.schedule_once(lambda dt: self._upd_texture(buff), .1)
return
texture.blit_buffer(buff, colorfmt='rgb', bufferfmt='ubyte')
img =self.ids.qrimage
img.anim_delay = -1

View File

@ -17,17 +17,24 @@ from kivy.lang import Builder
from kivy.factory import Factory
from electrum.i18n import _
from electrum.util import profiler, parse_URI
from electrum.util import profiler, parse_URI, format_time
from electrum import bitcoin
from electrum.util import timestamp_to_datetime
from electrum.plugins import run_hook
from context_menu import ContextMenu
from electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_UNKNOWN, PR_EXPIRED
class CScreen(Factory.Screen):
__events__ = ('on_activate', 'on_deactivate', 'on_enter', 'on_leave')
action_view = ObjectProperty(None)
loaded = False
kvname = None
context_menu = None
menu_actions = []
app = App.get_running_app()
def _change_action_view(self):
@ -65,8 +72,17 @@ class CScreen(Factory.Screen):
self.dispatch('on_deactivate')
def on_deactivate(self):
pass
#Clock.schedule_once(lambda dt: self._change_action_view())
self.hide_menu()
def hide_menu(self):
if self.context_menu is not None:
self.remove_widget(self.context_menu)
self.context_menu = None
def show_menu(self, obj):
self.hide_menu()
self.context_menu = ContextMenu(obj, self.menu_actions)
self.add_widget(self.context_menu)
class HistoryScreen(CScreen):
@ -77,10 +93,18 @@ class HistoryScreen(CScreen):
def __init__(self, **kwargs):
self.ra_dialog = None
super(HistoryScreen, self).__init__(**kwargs)
self.menu_actions = [ (_('Label'), self.label_dialog), (_('Details'), self.app.tx_details_dialog)]
def label_dialog(self, obj):
from dialogs.label_dialog import LabelDialog
key = obj.tx_hash
text = self.app.wallet.get_label(key)
def callback(text):
self.app.wallet.set_label(key, text)
self.update()
d = LabelDialog(_('Enter Transaction Label'), text, callback)
d.open()
def get_history_rate(self, btc_balance, timestamp):
date = timestamp_to_datetime(timestamp)
return run_hook('historical_value_str', btc_balance, date)
def parse_history(self, items):
for item in items:
@ -98,49 +122,43 @@ class HistoryScreen(CScreen):
time_str = _('pending')
icon = "atlas://gui/kivy/theming/light/unconfirmed"
elif conf < 6:
time_str = '' # add new to fix error when conf < 0
conf = max(1, conf)
icon = "atlas://gui/kivy/theming/light/clock{}".format(conf)
else:
icon = "atlas://gui/kivy/theming/light/confirmed"
if tx_hash:
label, is_default_label = self.app.wallet.get_label(tx_hash)
label = self.app.wallet.get_label(tx_hash) if tx_hash else _('Pruned transaction outputs')
date = timestamp_to_datetime(timestamp)
rate = run_hook('history_rate', date)
if self.app.fiat_unit:
quote_text = "..." if rate is None else "{0:.3} {1}".format(Decimal(value) / 100000000 * Decimal(rate), self.app.fiat_unit)
else:
label = _('Pruned transaction outputs')
is_default_label = False
quote_currency = 'USD'
rate = self.get_history_rate(value, timestamp)
quote_text = "..." if rate is None else "{0:.3} {1}".format(rate, quote_currency)
quote_text = ''
yield (conf, icon, time_str, label, value, tx_hash, quote_text)
def update(self, see_all=False):
if self.app.wallet is None:
return
history_card = self.screen.ids.recent_activity_card
history_card = self.screen.ids.history_container
history = self.parse_history(reversed(
self.app.wallet.get_history(self.app.current_account)))
# repopulate History Card
last_widget = history_card.ids.content.children[-1]
history_card.ids.content.clear_widgets()
history_add = history_card.ids.content.add_widget
history_add(last_widget)
RecentActivityItem = Factory.RecentActivityItem
history_card.clear_widgets()
history_add = history_card.add_widget
count = 0
for item in history:
count += 1
conf, icon, date_time, address, value, tx, quote_text = item
ri = RecentActivityItem()
conf, icon, date_time, message, value, tx, quote_text = item
ri = Factory.HistoryItem()
ri.icon = icon
ri.date = date_time
ri.address = address
ri.message = message
ri.value = value
ri.quote_text = quote_text
ri.confirmations = conf
ri.tx_hash = tx
ri.screen = self
history_add(ri)
if count == 8 and not see_all:
break
@ -181,20 +199,35 @@ class ScreenPassword(Factory.Screen):
class SendScreen(CScreen):
kvname = 'send'
payment_request = None
def set_URI(self, uri):
print "set uri", uri
self.screen.address = uri.get('address', '')
self.screen.message = uri.get('message', '')
amount = uri.get('amount')
if amount:
amount_str = str( Decimal(amount) / pow(10, self.app.decimal_point()))
self.screen.amount = amount_str + ' ' + self.app.base_unit
self.screen.amount = self.app.format_amount_and_units(amount)
def update(self):
if self.app.current_invoice:
self.set_request(self.app.current_invoice)
def do_clear(self):
self.screen.amount = ''
self.screen.message = ''
self.screen.address = ''
self.payment_request = None
def amount_dialog(self):
Clock.schedule_once(lambda dt: self.app.amount_dialog(self, True), .25)
def set_request(self, pr):
self.payment_request = pr
self.screen.address = pr.get_requestor()
amount = pr.get_amount()
if amount:
self.screen.amount = self.app.format_amount_and_units(amount)
self.screen.message = pr.get_memo()
def do_paste(self):
contents = unicode(self.app._clipboard.get())
@ -206,18 +239,24 @@ class SendScreen(CScreen):
self.set_URI(uri)
def do_send(self):
address = str(self.screen.address)
if not bitcoin.is_address(address):
self.app.show_error(_('Invalid Bitcoin Address') + ':\n' + address)
return
try:
amount = self.app.get_amount(self.screen.amount)
except:
self.app.show_error(_('Invalid amount') + ':\n' + self.screen.amount)
return
if self.payment_request:
if self.payment_request.has_expired():
self.app.show_error(_('Payment request has expired'))
return
outputs = self.payment_request.get_outputs()
else:
address = str(self.screen.address)
if not bitcoin.is_address(address):
self.app.show_error(_('Invalid Bitcoin Address') + ':\n' + address)
return
try:
amount = self.app.get_amount(self.screen.amount)
except:
self.app.show_error(_('Invalid amount') + ':\n' + self.screen.amount)
return
outputs = [('address', address, amount)]
message = unicode(self.screen.message)
fee = None
outputs = [('address', address, amount)]
self.app.protected(self.send_tx, (outputs, fee, message))
def send_tx(self, *args):
@ -250,7 +289,14 @@ class ReceiveScreen(CScreen):
kvname = 'receive'
def update(self):
self.screen.address = self.app.wallet.get_unused_address(None)
addr = self.app.get_receive_address()
self.screen.address = addr
req = self.app.wallet.receive_requests.get(addr)
if req:
self.screen.message = unicode(req.get('memo', ''))
amount = req.get('amount')
if amount:
self.screen.amount = self.app.format_amount_and_units(amount)
def amount_callback(self, popup):
amount_label = self.screen.ids.get('amount')
@ -269,17 +315,35 @@ class ReceiveScreen(CScreen):
@profiler
def update_qr(self):
uri = self.get_URI()
qr = self.screen.ids.get('qr')
qr = self.screen.ids.qr
qr.set_data(uri)
def do_copy(self):
uri = self.get_URI()
self.app._clipboard.put(uri, 'text/plain')
def do_clear(self):
def do_save(self):
addr = str(self.screen.address)
amount = str(self.screen.amount)
message = str(self.screen.message) #.ids.message_input.text)
if not message and not amount:
self.app.show_error(_('No message or amount'))
return
if amount:
amount = self.app.get_amount(amount)
else:
amount = 0
print "saving", amount, message
req = self.app.wallet.make_payment_request(addr, amount, message, None)
self.app.wallet.add_payment_request(req, self.app.electrum_config)
self.app.show_error(_('Request saved'))
self.app.update_tab('requests')
def do_new(self):
self.app.receive_address = None
self.screen.amount = ''
self.screen.message = ''
self.update()
class ContactsScreen(CScreen):
@ -311,6 +375,76 @@ class ContactsScreen(CScreen):
contact_list.add_widget(ci)
class InvoicesScreen(CScreen):
kvname = 'invoices'
def update(self):
self.menu_actions = [(_('Pay'), self.do_pay), (_('Delete'), self.do_delete)]
invoices_list = self.screen.ids.invoices_container
invoices_list.clear_widgets()
for pr in self.app.invoices.sorted_list():
ci = Factory.InvoiceItem()
ci.key = pr.get_id()
ci.requestor = pr.get_requestor()
ci.memo = pr.memo
ci.amount = self.app.format_amount_and_units(pr.get_amount())
status = self.app.invoices.get_status(ci.key)
if status == PR_PAID:
ci.icon = "atlas://gui/kivy/theming/light/confirmed"
elif status == PR_EXPIRED:
ci.icon = "atlas://gui/kivy/theming/light/important"
else:
ci.icon = "atlas://gui/kivy/theming/light/important"
exp = pr.get_expiration_date()
ci.date = format_time(exp) if exp else _('Never')
ci.screen = self
invoices_list.add_widget(ci)
def do_pay(self, obj):
self.app.do_pay(obj)
def do_delete(self, obj):
self.app.invoices.remove(obj.key)
self.app.update_tab('invoices')
class RequestsScreen(CScreen):
kvname = 'requests'
def update(self):
self.menu_actions = [(_('Show'), self.do_show), (_('Delete'), self.do_delete)]
requests_list = self.screen.ids.requests_container
requests_list.clear_widgets()
for req in self.app.wallet.get_sorted_requests(self.app.electrum_config):
address = req['address']
timestamp = req.get('time', 0)
amount = req.get('amount')
expiration = req.get('exp', None)
status = req.get('status')
signature = req.get('sig')
ci = Factory.RequestItem()
ci.address = req['address']
ci.memo = self.app.wallet.get_label(address)
status = req.get('status')
if status == PR_PAID:
ci.icon = "atlas://gui/kivy/theming/light/confirmed"
elif status == PR_EXPIRED:
ci.icon = "atlas://gui/kivy/theming/light/important"
else:
ci.icon = "atlas://gui/kivy/theming/light/important"
ci.amount = self.app.format_amount_and_units(amount) if amount else ''
ci.date = format_time(timestamp)
ci.screen = self
requests_list.add_widget(ci)
def do_show(self, obj):
self.app.show_request(obj.address)
def do_delete(self, obj):
self.app.wallet.remove_payment_request(obj.address, self.app.electrum_config)
self.update()
class CSpinner(Factory.Spinner):
'''CustomDropDown that allows fading out the dropdown
@ -340,27 +474,20 @@ class TabbedCarousel(Factory.TabbedPanel):
scrlv = self._tab_strip.parent
if not scrlv:
return
idx = self.tab_list.index(value)
if idx == 0:
n = len(self.tab_list)
if idx in [0, 1]:
scroll_x = 1
elif idx == len(self.tab_list) - 1:
elif idx in [n-1, n-2]:
scroll_x = 0
else:
self_center_x = scrlv.center_x
vcenter_x = value.center_x
diff_x = (self_center_x - vcenter_x)
try:
scroll_x = scrlv.scroll_x - (diff_x / scrlv.width)
except ZeroDivisionError:
pass
scroll_x = 1. * (n - idx - 1) / (n - 1)
mation = Factory.Animation(scroll_x=scroll_x, d=.25)
mation.cancel_all(scrlv)
mation.start(scrlv)
def on_current_tab(self, instance, value):
if value.text == 'default_tab':
return
self.animate_tab_to_center(value)
def on_index(self, instance, value):

View File

@ -0,0 +1,12 @@
Popup:
title: "About Electrum"
BoxLayout:
orientation: 'vertical'
spacing: '1dp'
Label:
text: "Lightweight Bitcoin Wallet"
Label:
text: "Author: Thomas Voegtlin"
Label:
text: "https://electrum.org"
Widget

View File

@ -5,23 +5,9 @@
#:set mbtc_symbol unichr(187)
<Card@GridLayout>
cols: 1
padding: '12dp' , '22dp', '12dp' , '12dp'
spacing: '12dp'
size_hint: 1, None
height: max(100, self.minimum_height)
canvas.before:
Color:
rgba: 1, 1, 1, 1
BorderImage:
border: 18, 18, 18, 18
source: 'atlas://gui/kivy/theming/light/card'
size: self.size
pos: self.pos
<CardLabel@Label>
color: 0.45, 0.45, 0.45, 1
color: 0.95, 0.95, 0.95, 1
size_hint: 1, None
text: ''
text_size: self.width, None
@ -29,87 +15,48 @@
halign: 'left'
valign: 'top'
<CardButton@Button>
background_normal: 'atlas://gui/kivy/theming/light/card_btn'
bold: True
font_size: '10sp'
color: 0.699, 0.699, 0.699, 1
size_hint: None, None
size: self.texture_size[0] + dp(32), self.texture_size[1] + dp(7)
<CardItem@ToggleButtonBehavior+GridLayout>
canvas.before:
Color:
rgba: 0.192, .498, 0.745, 1 if self.state == 'down' else 0
Rectangle
size: self.size
pos: self.x, self.y + dp(5)
cols: 1
padding: '2dp', '2dp'
spacing: '2dp'
size_hint: 1, None
height: self.minimum_height
group: 'history'
<RecentActivityItem@CardItem>
<HistoryItem@CardItem>
icon: 'atlas://gui/kivy/theming/light/important'
address: 'no address set'
message: ''
value: 0
amount: app.format_amount(self.value, True) if self.value is not None else '--'
amount_color: '#DB3627' if self.value < 0 else '#2EA442'
amount_color: '#FF6657' if self.value < 0 else '#2EA442'
confirmations: 0
date: '0/0/0'
quote_text: '.'
date: ''
quote_text: ''
spacing: '9dp'
on_release:
app.tx_selected(root.tx_hash, self.state)
Image:
id: icon
source: root.icon
size_hint: None, 1
width: self.height *.54
mipmap: True
BoxLayout:
size_hint: 1, None
spacing: '8dp'
height: '32dp'
Image:
id: icon
source: root.icon
size_hint: None, 1
width: self.height *.54
mipmap: True
BoxLayout:
orientation: 'vertical'
Widget
CardLabel:
shorten: True
text: root.address
markup: False
text_size: self.size
CardLabel:
color: .699, .699, .699, 1
text: root.date
font_size: '12sp'
Widget
orientation: 'vertical'
Widget
CardLabel:
halign: 'right'
text: root.date
font_size: '14sp'
CardLabel:
color: .699, .699, .699, 1
font_size: '13sp'
size_hint: None, 1
width: '110sp'
markup: True
font_name: font_light
text:
u'[color={amount_color}]{sign}{amount} {unit}[/color]\n'\
u'[color=#B2B3B3][size=12sp]{qt}[/size]'\
u'[/color]'.format(amount_color=root.amount_color,\
amount=root.amount[1:], qt=root.quote_text, sign=root.amount[0],\
unit=app.base_unit)
CardSeparator
<CardRecentActivity@Card>
GridLayout:
id: content
spacing: '7dp'
cols: 1
size_hint: 1, None
height: self.minimum_height
CardSeparator
shorten: True
text: root.message
Widget
CardLabel:
halign: 'right'
font_size: '15sp'
size_hint: None, 1
width: '110sp'
markup: True
font_name: font_light
text:
u'[color={amount_color}]{sign}{amount} {unit}[/color]\n'\
u'[color=#B2B3B3][size=13sp]{qt}[/size]'\
u'[/color]'.format(amount_color=root.amount_color,\
amount=root.amount[1:], qt=root.quote_text, sign=root.amount[0],\
unit=app.base_unit)
HistoryScreen:
@ -119,12 +66,9 @@ HistoryScreen:
id: content
do_scroll_x: False
GridLayout
id: grid
cols: 1 #if root.width < root.height else 2
id: history_container
cols: 1
size_hint: 1, None
height: self.minimum_height
padding: '12dp'
spacing: '12dp'
CardRecentActivity:
id: recent_activity_card
spacing: '2dp'

View File

@ -0,0 +1,59 @@
<InvoicesLabel@Label>
#color: .305, .309, .309, 1
text_size: self.width, None
halign: 'left'
valign: 'top'
<InvoiceItem@CardItem>
requestor: ''
memo: ''
amount: ''
status: ''
date: ''
icon: 'atlas://gui/kivy/theming/light/important'
Image:
id: icon
source: root.icon
size_hint: None, 1
width: self.height *.54
mipmap: True
BoxLayout:
spacing: '8dp'
height: '32dp'
orientation: 'vertical'
Widget
InvoicesLabel:
text: root.requestor
shorten: True
InvoicesLabel:
text: root.memo
color: .699, .699, .699, 1
font_size: '13sp'
shorten: True
Widget
InvoicesLabel:
halign: 'right'
font_size: '15sp'
size_hint: None, 1
width: '110sp'
text: root.amount
InvoicesScreen:
name: 'invoices'
BoxLayout:
orientation: 'vertical'
spacing: '1dp'
ScrollView:
canvas.before:
Color:
rgba: .8901, .8901, .8901, 0
Rectangle:
size: self.size
pos: self.pos
GridLayout:
cols: 1
id: invoices_container
size_hint: 1, None
height: self.minimum_height
spacing: '2dp'
padding: '12dp'

File diff suppressed because it is too large Load Diff

View File

@ -1,27 +0,0 @@
Popup:
title: _('Plugins')
id: popup
BoxLayout:
orientation: 'vertical'
GridLayout:
cols: 2
size_hint: 1, None
height: '100dp'
id: plugins_list
on_parent:
app.show_plugins(plugins_list)
Widget:
size_hint: 1, 1
BoxLayout:
Widget:
size_hint: 0.5, None
Button:
size_hint: 0.5, None
height: '48dp'
text: _('OK')
on_release:
popup.dismiss()

View File

@ -42,7 +42,8 @@ ReceiveScreen:
size_hint: 1, None
height: '48dp'
opacity: 0.5 if qr.shaded else 1
text: s.address
text: _('Bitcoin Address') + ': ' + s.address
text_size: self.width, None
SendReceiveBlueBottom:
id: blue_bottom
@ -51,13 +52,15 @@ ReceiveScreen:
BoxLayout:
size_hint: 1, None
height: blue_bottom.item_height
spacing: '5dp'
Image:
source: 'atlas://gui/kivy/theming/light/globe'
size_hint: None, None
size: '22dp', '22dp'
pos_hint: {'center_y': .5}
AmountButton:
BlueButton:
id: amount_label
default_text: 'Amount'
text: s.amount if s.amount else 'Amount'
on_release: app.amount_dialog(s, False)
CardSeparator:
@ -74,12 +77,10 @@ ReceiveScreen:
size_hint: None, None
size: '22dp', '22dp'
pos_hint: {'center_y': .5}
TextInputBlue:
id: message_input
hint_text: 'Description'
text: s.message
on_text_validate: s.message = self.text
BlueButton:
id: description
text: s.message if s.message else _('Description')
on_release: app.description_dialog(s)
BoxLayout:
size_hint: 1, None
height: '48dp'
@ -89,9 +90,14 @@ ReceiveScreen:
height: '48dp'
on_release: s.parent.do_copy()
Button:
text: _('Clear')
text: _('Save')
size_hint: 1, None
height: '48dp'
on_release: s.parent.do_clear()
Widget:
size_hint: 1, 0.3
on_release: s.parent.do_save()
Button:
text: _('New')
size_hint: 1, None
height: '48dp'
on_release: s.parent.do_new()
#Widget:
# size_hint: 1, 0.3

View File

@ -0,0 +1,60 @@
<RequestLabel@Label>
#color: .305, .309, .309, 1
text_size: self.width, None
halign: 'left'
valign: 'top'
<RequestItem@CardItem>
address: ''
memo: ''
amount: ''
status: ''
date: ''
icon: 'atlas://gui/kivy/theming/light/important'
Image:
id: icon
source: root.icon
size_hint: None, 1
width: self.height *.54
mipmap: True
BoxLayout:
spacing: '8dp'
height: '32dp'
orientation: 'vertical'
Widget
RequestLabel:
text: root.address
shorten: True
RequestLabel:
text: root.memo
color: .699, .699, .699, 1
font_size: '13sp'
shorten: True
Widget
RequestLabel:
halign: 'right'
font_size: '15sp'
size_hint: None, 1
width: '110sp'
text: root.amount
RequestsScreen:
name: 'requests'
BoxLayout:
orientation: 'vertical'
spacing: '1dp'
ScrollView:
canvas.before:
Color:
rgba: .8901, .8901, .8901, 0
Rectangle:
size: self.size
pos: self.pos
GridLayout:
cols: 1
id: requests_container
size_hint_y: None
height: self.minimum_height
spacing: '2dp'
padding: '12dp'

View File

@ -23,6 +23,7 @@ SendScreen:
id: blue_bottom
size_hint: 1, None
height: self.minimum_height
spacing: '5dp'
BoxLayout:
size_hint: 1, None
height: blue_bottom.item_height
@ -32,32 +33,32 @@ SendScreen:
size_hint: None, None
size: '22dp', '22dp'
pos_hint: {'center_y': .5}
TextInputBlue:
BlueButton:
id: payto_e
text: s.address
hint_text: "Recipient"
text: s.address if s.address else _('Recipient')
on_release: app.address_dialog(s)
CardSeparator:
opacity: message_selection.opacity
color: blue_bottom.foreground_color
BoxLayout:
size_hint: 1, None
height: blue_bottom.item_height
spacing: '5dp'
Image:
source: 'atlas://gui/kivy/theming/light/globe'
size_hint: None, None
size: '22dp', '22dp'
pos_hint: {'center_y': .5}
AmountButton:
BlueButton:
id: amount_e
text: s.amount if s.amount else 'Amount'
on_release: app.amount_dialog(s, True)
default_text: _('Amount')
text: s.amount if s.amount else _('Amount')
on_release: s.amount_dialog()
CardSeparator:
opacity: message_selection.opacity
color: blue_bottom.foreground_color
BoxLayout:
id: message_selection
opacity: 1
size_hint: 1, None
height: blue_bottom.item_height
spacing: '5dp'
@ -66,11 +67,10 @@ SendScreen:
size_hint: None, None
size: '22dp', '22dp'
pos_hint: {'center_y': .5}
TextInputBlue:
id: message_e
hint_text: 'Description'
text: s.message
on_text_validate: s.message = self.text
BlueButton:
id: description
text: s.message if s.message else _('Description')
on_release: app.description_dialog(s)
BoxLayout:
size_hint: 1, None
height: '48dp'
@ -78,7 +78,7 @@ SendScreen:
id: qr
text: _('QR Code')
on_release:
app.scan_qr(on_complete=s.parent.set_URI)
app.scan_qr(on_complete=app.set_URI)
Button:
id: paste_button
text: _('Paste')

View File

@ -1,37 +0,0 @@
Popup:
id: settings
title: _('Settings')
BoxLayout:
orientation: 'vertical'
SettingsItem:
title: _('PIN Code') + ' (%s)'%('ON' if app.wallet.use_encryption else 'OFF')
description: _("Your PIN code will be required in order to spend bitcoins.")
on_release:
app.change_password()
self.title = _('PIN Code') + ' (%s)'%('ON' if app.wallet.use_encryption else 'OFF')
CardSeparator
SettingsItem:
title: _('Denomination') + ' (' + app.base_unit + ')'
description: _("Base unit for Bitcoin amounts.")
on_release:
app._rotate_bu()
self.title = _('Denomination') + ' (' + app.base_unit + ')'
CardSeparator
SettingsItem:
title: _('OpenAlias')
description: "Email-like address."
Widget:
size_hint: 1, 1
BoxLayout:
Widget:
size_hint: 0.5, None
Button:
size_hint: 0.5, None
height: '48dp'
text: _('OK')
on_release:
settings.dismiss()

View File

@ -0,0 +1,31 @@
#:import os os
Popup:
title: "Balance"
confirmed: 0
unconfirmed: 0
unmatured: 0
on_parent:
self.confirmed, self.unconfirmed, self.x = app.wallet.get_balance()
BoxLayout:
orientation: 'vertical'
spacing: '1dp'
GridLayout:
cols:2
Label:
text: _("Wallet:")
Label:
text: os.path.basename(app.wallet.storage.path)
Label:
text: _("Confirmed:")
Label:
text: app.format_amount_and_units(root.confirmed)
Label:
text: _("Unconfirmed:")
Label:
text: app.format_amount_and_units(root.unconfirmed)
Label:
text: "Unmatured:"
Label:
text: app.format_amount_and_units(root.unmatured)
Widget

View File

@ -1,35 +0,0 @@
#:import os os
Popup:
title: _('Wallets')
id: popup
BoxLayout:
orientation: 'vertical'
GridLayout:
size_hint_y: None
cols: 2
Label:
height: '32dp'
size_hint_y: None
text: _('Wallet file') + ':'
TextInput:
id: text_input
height: '32dp'
size_hint_y: None
text: os.path.basename(app.wallet.storage.path)
FileChooserIconView:
id: wallet_selector
path: os.path.dirname(app.wallet.storage.path)
on_selection: text_input.text = os.path.basename(self.selection[0]) if self.selection else ''
size_hint: 1, 1
BoxLayout:
Widget:
size_hint: 0.5, None
Button:
size_hint: 0.5, None
height: '48dp'
text: _('OK')
on_release:
popup.dismiss()

View File

@ -64,7 +64,7 @@ class OpenFileEventFilter(QObject):
class ElectrumGui:
class ElectrumGui(MessageBoxMixin):
def __init__(self, config, network, plugins):
set_language(config.get('language'))
@ -134,7 +134,7 @@ class ElectrumGui:
try:
storage = WalletStorage(filename)
except Exception as e:
QMessageBox.information(None, _('Error'), str(e), _('OK'))
self.show_error(str(e))
return
if not storage.file_exists:
recent = self.config.get('recently_open', [])
@ -147,7 +147,7 @@ class ElectrumGui:
wallet = Wallet(storage)
except BaseException as e:
traceback.print_exc(file=sys.stdout)
QMessageBox.warning(None, _('Warning'), str(e), _('OK'))
self.show_warning(str(e))
return
action = wallet.get_action()
# run wizard
@ -162,32 +162,6 @@ class ElectrumGui:
return wallet
def get_wallet_folder(self):
#return os.path.dirname(os.path.abspath(self.wallet.storage.path if self.wallet else self.wallet.storage.path))
return os.path.dirname(os.path.abspath(self.config.get_wallet_path()))
def new_wallet(self):
wallet_folder = self.get_wallet_folder()
i = 1
while True:
filename = "wallet_%d"%i
if filename in os.listdir(wallet_folder):
i += 1
else:
break
filename = line_dialog(None, _('New Wallet'), _('Enter file name') + ':', _('OK'), filename)
if not filename:
return
full_path = os.path.join(wallet_folder, filename)
storage = WalletStorage(full_path)
if storage.file_exists:
QMessageBox.critical(None, "Error", _("File exists"))
return
wizard = InstallWizard(self.app, self.config, self.network, storage)
wallet = wizard.run('new')
if wallet:
self.new_window(full_path)
def new_window(self, path, uri=None):
# Use a signal as can be called from daemon thread
self.app.emit(SIGNAL('new_window'), path, uri)
@ -245,6 +219,9 @@ class ElectrumGui:
# main loop
self.app.exec_()
# Shut down the timer cleanly
self.timer.stop()
# clipboard persistence. see http://www.mail-archive.com/pyqt@riverbankcomputing.com/msg17328.html
event = QtCore.QEvent(QtCore.QEvent.Clipboard)
self.app.sendEvent(self.app.clipboard(), event)

View File

@ -25,9 +25,10 @@ from PyQt4.QtCore import *
from util import *
from history_widget import HistoryWidget
class AddressDialog(QDialog):
class AddressDialog(WindowModalDialog):
def __init__(self, address, parent):
def __init__(self, parent, address):
WindowModalDialog.__init__(self, parent, _("Address"))
self.address = address
self.parent = parent
self.config = parent.config
@ -35,10 +36,7 @@ class AddressDialog(QDialog):
self.app = parent.app
self.saved = True
QDialog.__init__(self)
self.setMinimumWidth(700)
self.setWindowTitle(_("Address"))
self.setModal(1)
vbox = QVBoxLayout()
self.setLayout(vbox)

View File

@ -74,7 +74,7 @@ class HistoryWidget(MyTreeWidget):
icon, time_str = self.get_icon(conf, timestamp)
v_str = self.parent.format_amount(value, True, whitespaces=True)
balance_str = self.parent.format_amount(balance, whitespaces=True)
label, is_default_label = self.wallet.get_label(tx_hash)
label = self.wallet.get_label(tx_hash)
entry = ['', tx_hash, time_str, label, v_str, balance_str]
run_hook('history_tab_update', tx, entry)
item = QTreeWidgetItem(entry)
@ -88,8 +88,6 @@ class HistoryWidget(MyTreeWidget):
item.setForeground(4, QBrush(QColor("#BC1E1E")))
if tx_hash:
item.setData(0, Qt.UserRole, tx_hash)
if is_default_label:
item.setForeground(3, QBrush(QColor('grey')))
self.insertTopLevelItem(0, item)
if current_tx == tx_hash:
self.setCurrentItem(item)

View File

@ -62,17 +62,17 @@ class CosignWidget(QWidget):
class InstallWizard(QDialog):
class InstallWizard(WindowModalDialog, MessageBoxMixin):
def __init__(self, app, config, network, storage):
QDialog.__init__(self)
title = 'Electrum' + ' - ' + _('Install Wizard')
WindowModalDialog.__init__(self, None, title=title)
self.app = app
self.config = config
self.network = network
self.storage = storage
self.setMinimumSize(575, 400)
self.setMaximumSize(575, 400)
self.setWindowTitle('Electrum' + ' - ' + _('Install Wizard'))
self.connect(self, QtCore.SIGNAL('accept'), self.accept)
self.stack = QStackedLayout()
self.setLayout(self.stack)
@ -139,8 +139,8 @@ class InstallWizard(QDialog):
button.setChecked(True)
vbox.addStretch(1)
self.set_layout(vbox)
vbox.addLayout(Buttons(CancelButton(self), OkButton(self, _('Next'))))
self.set_layout(vbox)
self.show()
self.raise_()
@ -157,7 +157,7 @@ class InstallWizard(QDialog):
if not r:
return
if prepare_seed(r) != prepare_seed(seed):
QMessageBox.warning(None, _('Error'), _('Incorrect seed'), _('OK'))
self.show_error(_('Incorrect seed'))
return False
else:
return True
@ -384,22 +384,6 @@ class InstallWizard(QDialog):
wallet_type = '%dof%d'%(m,n)
return wallet_type
def question(self, msg, yes_label=_('OK'), no_label=_('Cancel'), icon=None):
vbox = QVBoxLayout()
self.set_layout(vbox)
if icon:
logo = QLabel()
logo.setPixmap(icon)
vbox.addWidget(logo)
label = QLabel(msg)
label.setWordWrap(True)
vbox.addWidget(label)
vbox.addStretch(1)
vbox.addLayout(Buttons(CancelButton(self, no_label), OkButton(self, yes_label)))
if not self.exec_():
return None
return True
def show_seed(self, seed, sid):
vbox = seed_dialog.show_seed_box_msg(seed, sid)
vbox.addLayout(Buttons(CancelButton(self), OkButton(self, _("Next"))))
@ -407,22 +391,21 @@ class InstallWizard(QDialog):
return self.exec_()
def password_dialog(self):
msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
+_("Leave these fields empty if you want to disable encryption.")
from password_dialog import make_password_dialog, run_password_dialog
self.set_layout( make_password_dialog(self, None, msg) )
return run_password_dialog(self, None, self)[2]
from password_dialog import PasswordDialog
msg = _("Please choose a password to encrypt your wallet keys.\n"
"Leave these fields empty if you want to disable encryption.")
dialog = PasswordDialog(self, None, _("Choose a password"), msg, True)
return dialog.run()[2]
def run(self, action):
if self.storage.file_exists and action != 'new':
path = self.storage.path
msg = _("The file '%s' contains an incompletely created wallet.\n"
"Do you want to complete its creation now?") % path
if not question(msg):
if question(_("Do you want to delete '%s'?") % path):
if not self.question(msg):
if self.question(_("Do you want to delete '%s'?") % path):
os.remove(path)
QMessageBox.information(self, _('Warning'),
_('The file was removed'), _('OK'))
self.show_warning(_('The file was removed'))
return
return
self.show()
@ -434,7 +417,7 @@ class InstallWizard(QDialog):
wallet = self.run_wallet_type(action, wallet_type)
except BaseException as e:
traceback.print_exc(file=sys.stdout)
QMessageBox.information(None, _('Error'), str(e), _('OK'))
self.show_error(str(e))
return
return wallet
@ -463,7 +446,7 @@ class InstallWizard(QDialog):
elif wallet_type == 'twofactor':
wallet_type = '2fa'
if action == 'create':
self.storage.put('wallet_type', wallet_type, False)
self.storage.put('wallet_type', wallet_type)
if action is None:
return
@ -527,7 +510,7 @@ class InstallWizard(QDialog):
if self.config.get('server') is None:
self.network_dialog()
else:
QMessageBox.information(None, _('Warning'), _('You are offline'), _('OK'))
self.show_warning(_('You are offline'))
# start wallet threads
@ -539,7 +522,7 @@ class InstallWizard(QDialog):
msg = _("Recovery successful") if wallet.is_found() else _("No transactions found for this seed")
else:
msg = _("This wallet was restored offline. It may contain more addresses than displayed.")
QMessageBox.information(None, _('Information'), msg, _('OK'))
self.show_message(msg)
return wallet
@ -560,7 +543,7 @@ class InstallWizard(QDialog):
password = self.password_dialog() if any(map(lambda x: Wallet.is_seed(x) or Wallet.is_xprv(x), key_list)) else None
wallet = Wallet.from_multisig(key_list, password, self.storage, t)
else:
self.storage.put('wallet_type', t, False)
self.storage.put('wallet_type', t)
# call the constructor to load the plugin (side effect)
Wallet(self.storage)
wallet = always_hook('installwizard_restore', self, self.storage)

View File

@ -17,7 +17,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys, time, threading
import os.path, json, traceback
import os, json, traceback
import shutil
import socket
import weakref
@ -40,20 +40,17 @@ from electrum.i18n import _
from electrum.util import block_explorer, block_explorer_info, block_explorer_URL
from electrum.util import format_satoshis, format_satoshis_plain, format_time
from electrum.util import PrintError, NotEnoughFunds, StoreDict
from electrum import Transaction
from electrum import mnemonic
from electrum import Transaction, mnemonic
from electrum import util, bitcoin, commands, Wallet
from electrum import SimpleConfig, COIN_CHOOSERS, WalletStorage
from electrum import Imported_Wallet
from electrum import paymentrequest
from electrum import Imported_Wallet, paymentrequest
from amountedit import BTCAmountEdit, MyLineEdit, BTCkBEdit
from network_dialog import NetworkDialog
from qrcodewidget import QRCodeWidget, QRDialog
from qrtextedit import ScanQRTextEdit, ShowQRTextEdit
from qrtextedit import ShowQRTextEdit
from transaction_dialog import show_transaction
from installwizard import InstallWizard
@ -83,7 +80,6 @@ class StatusBarButton(QPushButton):
from electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_UNKNOWN, PR_EXPIRED
from electrum.paymentrequest import PaymentRequest, get_payment_request
pr_icons = {
PR_UNPAID:":icons/unpaid.png",
@ -106,7 +102,7 @@ expiration_values = [
class ElectrumWindow(QMainWindow, PrintError):
class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
def __init__(self, gui_object, wallet):
QMainWindow.__init__(self)
@ -270,7 +266,7 @@ class ElectrumWindow(QMainWindow, PrintError):
self.update_account_selector()
# update menus
self.new_account_menu.setVisible(self.wallet.can_create_accounts())
self.private_keys_menu.setEnabled(not self.wallet.is_watching_only())
self.export_menu.setEnabled(not self.wallet.is_watching_only())
self.password_menu.setEnabled(self.wallet.can_change_password())
self.seed_menu.setEnabled(self.wallet.has_seed())
self.mpk_menu.setEnabled(self.wallet.is_deterministic())
@ -288,14 +284,17 @@ class ElectrumWindow(QMainWindow, PrintError):
except:
self.setGeometry(100, 100, 840, 400)
self.show()
self.warn_if_watching_only()
run_hook('load_wallet', wallet, self)
def warn_if_watching_only(self):
if self.wallet.is_watching_only():
msg = ' '.join([
_("This wallet is watching-only."),
_("This means you will not be able to spend Bitcoins with it."),
_("Make sure you own the seed phrase or the private keys, before you request Bitcoins to be sent to this wallet.")
])
QMessageBox.warning(self, _('Information'), msg, _('OK'))
run_hook('load_wallet', wallet, self)
self.show_warning(msg, title=_('Information'))
def import_old_contacts(self):
# backward compatibility: import contacts
@ -321,7 +320,7 @@ class ElectrumWindow(QMainWindow, PrintError):
self.wallet.synchronize()
def open_wallet(self):
wallet_folder = self.gui_object.get_wallet_folder()
wallet_folder = self.get_wallet_folder()
filename = unicode(QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder))
if not filename:
return
@ -339,11 +338,9 @@ class ElectrumWindow(QMainWindow, PrintError):
if new_path != path:
try:
shutil.copy2(path, new_path)
QMessageBox.information(None,"Wallet backup created", _("A copy of your wallet file was created in")+" '%s'" % str(new_path))
self.show_message(_("A copy of your wallet file was created in")+" '%s'" % str(new_path), title=_("Wallet backup created"))
except (IOError, os.error), reason:
QMessageBox.critical(None,"Unable to create backup", _("Electrum was unable to copy your wallet file to the specified location.")+"\n" + str(reason))
self.show_critical(_("Electrum was unable to copy your wallet file to the specified location.") + "\n" + str(reason), title=_("Unable to create backup"))
def update_recently_visited(self, filename=None):
recent = self.config.get('recently_open', [])
@ -361,13 +358,39 @@ class ElectrumWindow(QMainWindow, PrintError):
self.recently_visited_menu.addAction(b, loader(k)).setShortcut(QKeySequence("Ctrl+%d"%(i+1)))
self.recently_visited_menu.setEnabled(len(recent))
def get_wallet_folder(self):
return os.path.dirname(os.path.abspath(self.config.get_wallet_path()))
def new_wallet(self):
wallet_folder = self.get_wallet_folder()
i = 1
while True:
filename = "wallet_%d" % i
if filename in os.listdir(wallet_folder):
i += 1
else:
break
filename = line_dialog(self, _('New Wallet'), _('Enter file name')
+ ':', _('OK'), filename)
if not filename:
return
full_path = os.path.join(wallet_folder, filename)
storage = WalletStorage(full_path)
if storage.file_exists:
self.show_critical(_("File exists"))
return
wizard = InstallWizard(self.app, self.config, self.network, storage)
wallet = wizard.run('new')
if wallet:
self.new_window(full_path)
def init_menubar(self):
menubar = QMenuBar()
file_menu = menubar.addMenu(_("&File"))
self.recently_visited_menu = file_menu.addMenu(_("&Recently open"))
file_menu.addAction(_("&Open"), self.open_wallet).setShortcut(QKeySequence.Open)
file_menu.addAction(_("&New/Restore"), self.gui_object.new_wallet).setShortcut(QKeySequence.New)
file_menu.addAction(_("&New/Restore"), self.new_wallet).setShortcut(QKeySequence.New)
file_menu.addAction(_("&Save Copy"), self.backup_wallet).setShortcut(QKeySequence.SaveAs)
file_menu.addSeparator()
file_menu.addAction(_("&Quit"), self.close)
@ -435,7 +458,7 @@ class ElectrumWindow(QMainWindow, PrintError):
_("Before reporting a bug, upgrade to the most recent version of Electrum (latest release or git HEAD), and include the version number in your report."),
_("Try to explain not only what the bug is, but how it occurs.")
])
QMessageBox.information(self, "Electrum - " + _("Reporting Bugs"), msg)
self.show_message(msg, title="Electrum - " + _("Reporting Bugs"))
def notify_transactions(self):
if not self.network or not self.network.is_connected():
@ -582,7 +605,7 @@ class ElectrumWindow(QMainWindow, PrintError):
def show_address(self, addr):
import address_dialog
d = address_dialog.AddressDialog(addr, self)
d = address_dialog.AddressDialog(self, addr)
d.exec_()
def show_transaction(self, tx, tx_desc = None):
@ -744,7 +767,7 @@ class ElectrumWindow(QMainWindow, PrintError):
try:
self.wallet.sign_payment_request(addr, alias, alias_addr, password)
except Exception as e:
QMessageBox.warning(self, _('Error'), str(e), _('OK'))
self.show_error(str(e))
return
else:
return
@ -757,7 +780,7 @@ class ElectrumWindow(QMainWindow, PrintError):
amount = self.receive_amount_e.get_amount()
message = unicode(self.receive_message_e.text())
if not message and not amount:
QMessageBox.warning(self, _('Error'), _('No message or amount'), _('OK'))
self.show_error(_('No message or amount'))
return False
i = self.expires_combo.currentIndex()
expiration = map(lambda x: x[1], expiration_values)[i]
@ -769,8 +792,7 @@ class ElectrumWindow(QMainWindow, PrintError):
self.save_request_button.setEnabled(False)
def view_and_paste(self, title, msg, data):
dialog = QDialog(self)
dialog.setWindowTitle(title)
dialog = WindowModalDialog(self, title)
vbox = QVBoxLayout()
label = QLabel(msg)
label.setWordWrap(True)
@ -1129,7 +1151,7 @@ class ElectrumWindow(QMainWindow, PrintError):
self.wallet.check_password(password)
break
except Exception as e:
QMessageBox.warning(parent, _('Error'), str(e), _('OK'))
self.show_error(str(e), parent=parent)
continue
else:
password = None
@ -1140,7 +1162,7 @@ class ElectrumWindow(QMainWindow, PrintError):
def read_send_tab(self):
if self.payment_request and self.payment_request.has_expired():
QMessageBox.warning(self, _('Error'), _('Payment request has expired'), _('OK'))
self.show_error(_('Payment request has expired'))
return
label = unicode( self.message_e.text() )
@ -1161,23 +1183,23 @@ class ElectrumWindow(QMainWindow, PrintError):
return
if not outputs:
QMessageBox.warning(self, _('Error'), _('No outputs'), _('OK'))
self.show_error(_('No outputs'))
return
for _type, addr, amount in outputs:
if addr is None:
QMessageBox.warning(self, _('Error'), _('Bitcoin Address is None'), _('OK'))
self.show_error(_('Bitcoin Address is None'))
return
if _type == 'address' and not bitcoin.is_address(addr):
QMessageBox.warning(self, _('Error'), _('Invalid Bitcoin Address'), _('OK'))
self.show_error(_('Invalid Bitcoin Address'))
return
if amount is None:
QMessageBox.warning(self, _('Error'), _('Invalid Amount'), _('OK'))
self.show_error(_('Invalid Amount'))
return
fee = self.fee_e.get_amount()
if fee is None:
QMessageBox.warning(self, _('Error'), _('Invalid Fee'), _('OK'))
self.show_error(_('Invalid Fee'))
return
coins = self.get_coins()
@ -1203,7 +1225,7 @@ class ElectrumWindow(QMainWindow, PrintError):
return
if tx.get_fee() < MIN_RELAY_TX_FEE and tx.requires_fee(self.wallet):
QMessageBox.warning(self, _('Error'), _("This transaction requires a higher fee, or it will not be propagated by the network."), _('OK'))
self.show_error(_("This transaction requires a higher fee, or it will not be propagated by the network"))
return
if self.show_before_broadcast():
@ -1258,16 +1280,14 @@ class ElectrumWindow(QMainWindow, PrintError):
def sign_thread():
if not self.wallet.is_watching_only():
self.wallet.sign_transaction(tx, password)
def on_sign_successful(ret):
def on_signed(ret):
success[0] = True
def on_dialog_close():
def on_finished():
self.send_button.setDisabled(False)
callback(success[0])
# keep a reference to WaitingDialog or the gui might crash
self.waiting_dialog = WaitingDialog(parent, 'Signing transaction...', sign_thread, on_sign_successful, on_dialog_close)
self.waiting_dialog.start()
WaitingDialog(parent, _('Signing transaction...'), sign_thread,
on_success=on_signed, on_finished=on_finished)
def broadcast_transaction(self, tx, tx_desc, parent=None):
@ -1296,19 +1316,16 @@ class ElectrumWindow(QMainWindow, PrintError):
if status:
if tx_desc is not None and tx.is_complete():
self.wallet.set_label(tx.hash(), tx_desc)
QMessageBox.information(parent, '', _('Payment sent.') + '\n' + msg, _('OK'))
self.show_message(_('Payment sent.') + '\n' + msg, parent=parent)
self.invoices_list.update()
self.do_clear()
else:
QMessageBox.warning(parent, _('Error'), msg, _('OK'))
self.show_error(msg, parent=parent)
self.send_button.setDisabled(False)
if parent == None:
parent = self
self.waiting_dialog = WaitingDialog(parent, 'Broadcasting transaction...', broadcast_thread, broadcast_done)
self.waiting_dialog.start()
parent = parent or self
WaitingDialog(parent, _('Broadcasting transaction...'),
broadcast_thread, broadcast_done)
def prepare_for_payment_request(self):
self.tabs.setCurrentIndex(1)
@ -1346,37 +1363,28 @@ class ElectrumWindow(QMainWindow, PrintError):
self.payment_request = None
self.do_clear()
def on_pr(self, request):
self.payment_request = request
if self.payment_request.verify(self.contacts):
self.emit(SIGNAL('payment_request_ok'))
else:
self.emit(SIGNAL('payment_request_error'))
def pay_to_URI(self, URI):
if not URI:
return
try:
out = util.parse_URI(unicode(URI))
except Exception as e:
QMessageBox.warning(self, _('Error'), _('Invalid bitcoin URI:') + '\n' + str(e), _('OK'))
out = util.parse_URI(unicode(URI), self.on_pr)
except BaseException as e:
self.show_error(_('Invalid bitcoin URI:') + '\n' + str(e))
return
self.tabs.setCurrentIndex(1)
r = out.get('r')
sig = out.get('sig')
name = out.get('name')
if r or (name and sig):
def get_payment_request_thread():
if name and sig:
from electrum import paymentrequest
pr = paymentrequest.serialize_request(out).SerializeToString()
self.payment_request = paymentrequest.PaymentRequest(pr)
else:
self.payment_request = get_payment_request(r)
if self.payment_request.verify(self.contacts):
self.emit(SIGNAL('payment_request_ok'))
else:
self.emit(SIGNAL('payment_request_error'))
t = threading.Thread(target=get_payment_request_thread)
t.setDaemon(True)
t.start()
self.prepare_for_payment_request()
return
address = out.get('address')
amount = out.get('amount')
label = out.get('label')
@ -1555,6 +1563,13 @@ class ElectrumWindow(QMainWindow, PrintError):
def paytomany(self):
self.tabs.setCurrentIndex(1)
self.payto_e.paytomany()
msg = '\n'.join([
_('Enter a list of outputs in the \'Pay to\' field.'),
_('One output per line.'),
_('Format: address, amount'),
_('You may load a CSV file using the file icon.')
])
self.show_warning(msg, title=_('Pay to many'))
def payto_contacts(self, labels):
paytos = [self.get_contact_payto(label) for label in labels]
@ -1578,7 +1593,7 @@ class ElectrumWindow(QMainWindow, PrintError):
def set_contact(self, label, address):
if not is_valid(address):
QMessageBox.warning(self, _('Error'), _('Invalid Address'), _('OK'))
self.show_error(_('Invalid Address'))
self.contacts_list.update() # Displays original unchanged value
return False
self.contacts[label] = ('address', address)
@ -1628,8 +1643,7 @@ class ElectrumWindow(QMainWindow, PrintError):
self.show_pr_details(pr)
def show_pr_details(self, pr):
d = QDialog(self)
d.setWindowTitle(_("Invoice"))
d = WindowModalDialog(self, _("Invoice"))
vbox = QVBoxLayout(d)
grid = QGridLayout()
grid.addWidget(QLabel(_("Requestor") + ':'), 0, 0)
@ -1840,8 +1854,39 @@ class ElectrumWindow(QMainWindow, PrintError):
def change_password_dialog(self):
from password_dialog import PasswordDialog
d = PasswordDialog(self.wallet, self)
d.run()
if self.wallet and self.wallet.is_watching_only():
self.show_error(_('This is a watching-only wallet'))
return
msg = (_('Your wallet is encrypted. Use this dialog to change your '
'password. To disable wallet encryption, enter an empty new '
'password.') if self.wallet.use_encryption
else _('Your wallet keys are not encrypted'))
d = PasswordDialog(self, self.wallet, _("Set Password"), msg, True)
ok, password, new_password = d.run()
if not ok:
return
try:
self.wallet.check_password(password)
except BaseException as e:
self.show_error(str(e))
return
try:
self.wallet.update_password(password, new_password)
except:
traceback.print_exc(file=sys.stdout)
self.show_error(_('Failed to update password'))
return
if new_password:
msg = _('Password was updated successfully')
else:
msg = _('This wallet is not encrypted')
self.show_message(msg, title=_("Success"))
self.update_lock_icon()
def toggle_search(self):
@ -1866,8 +1911,7 @@ class ElectrumWindow(QMainWindow, PrintError):
def new_contact_dialog(self):
d = QDialog(self)
d.setWindowTitle(_("New Contact"))
d = WindowModalDialog(self, _("New Contact"))
vbox = QVBoxLayout(d)
vbox.addWidget(QLabel(_('New Contact') + ':'))
grid = QGridLayout()
@ -1892,9 +1936,7 @@ class ElectrumWindow(QMainWindow, PrintError):
@protected
def new_account_dialog(self, password):
dialog = QDialog(self)
dialog.setModal(1)
dialog.setWindowTitle(_("New Account"))
dialog = WindowModalDialog(self, _("New Account"))
vbox = QVBoxLayout()
vbox.addWidget(QLabel(_('Account name')+':'))
e = QLineEdit()
@ -1917,11 +1959,7 @@ class ElectrumWindow(QMainWindow, PrintError):
def show_master_public_keys(self):
dialog = QDialog(self)
dialog.setModal(1)
dialog.setWindowTitle(_("Master Public Keys"))
dialog = WindowModalDialog(self, "Master Public Keys")
mpk_dict = self.wallet.get_master_public_keys()
vbox = QVBoxLayout()
# only show the combobox in case multiple accounts are available
@ -1966,13 +2004,13 @@ class ElectrumWindow(QMainWindow, PrintError):
@protected
def show_seed_dialog(self, password):
if not self.wallet.has_seed():
QMessageBox.information(self, _('Message'), _('This wallet has no seed'), _('OK'))
self.show_message(_('This wallet has no seed'))
return
try:
mnemonic = self.wallet.get_mnemonic(password)
except BaseException as e:
QMessageBox.warning(self, _('Error'), str(e), _('OK'))
self.show_error(str(e))
return
from seed_dialog import SeedDialog
d = SeedDialog(self, mnemonic, self.wallet.has_imported_keys())
@ -1980,10 +2018,10 @@ class ElectrumWindow(QMainWindow, PrintError):
def show_qrcode(self, data, title = _("QR code")):
def show_qrcode(self, data, title = _("QR code"), parent=None):
if not data:
return
d = QRDialog(data, self, title)
d = QRDialog(data, parent or self, title)
d.exec_()
def show_public_keys(self, address):
@ -1995,10 +2033,8 @@ class ElectrumWindow(QMainWindow, PrintError):
self.show_message(str(e))
return
d = QDialog(self)
d = WindowModalDialog(self, _("Public key"))
d.setMinimumSize(600, 200)
d.setModal(1)
d.setWindowTitle(_("Public key"))
vbox = QVBoxLayout()
vbox.addWidget( QLabel(_("Address") + ': ' + address))
vbox.addWidget( QLabel(_("Public key") + ':'))
@ -2019,10 +2055,8 @@ class ElectrumWindow(QMainWindow, PrintError):
self.show_message(str(e))
return
d = QDialog(self)
d = WindowModalDialog(self, _("Private key"))
d.setMinimumSize(600, 200)
d.setModal(1)
d.setWindowTitle(_("Private key"))
vbox = QVBoxLayout()
vbox.addWidget( QLabel(_("Address") + ': ' + address))
vbox.addWidget( QLabel(_("Private key") + ':'))
@ -2056,9 +2090,7 @@ class ElectrumWindow(QMainWindow, PrintError):
def sign_verify_message(self, address=''):
d = QDialog(self)
d.setModal(1)
d.setWindowTitle(_('Sign/verify Message'))
d = WindowModalDialog(self, _('Sign/verify Message'))
d.setMinimumSize(410, 290)
layout = QGridLayout(d)
@ -2117,9 +2149,7 @@ class ElectrumWindow(QMainWindow, PrintError):
def encrypt_message(self, address = ''):
d = QDialog(self)
d.setModal(1)
d.setWindowTitle(_('Encrypt/decrypt Message'))
d = WindowModalDialog(self, _('Encrypt/decrypt Message'))
d.setMinimumSize(610, 490)
layout = QGridLayout(d)
@ -2157,22 +2187,9 @@ class ElectrumWindow(QMainWindow, PrintError):
layout.addLayout(hbox, 4, 1)
d.exec_()
def question(self, msg):
return QMessageBox.question(self, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
def show_message(self, msg):
QMessageBox.information(self, _('Message'), msg, _('OK'))
def show_warning(self, msg):
QMessageBox.warning(self, _('Warning'), msg, _('OK'))
def password_dialog(self, msg=None, parent=None):
if parent == None:
parent = self
d = QDialog(parent)
d.setModal(1)
d.setWindowTitle(_("Enter Password"))
parent = parent or self
d = WindowModalDialog(parent, _("Enter Password"))
pw = QLineEdit()
pw.setEchoMode(2)
vbox = QVBoxLayout()
@ -2200,33 +2217,24 @@ class ElectrumWindow(QMainWindow, PrintError):
except:
is_hex = False
if is_hex:
try:
return Transaction(txt)
except:
traceback.print_exc(file=sys.stdout)
QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
return
try:
if is_hex:
return Transaction(txt)
tx_dict = json.loads(str(txt))
assert "hex" in tx_dict.keys()
tx = Transaction(tx_dict["hex"])
#if tx_dict.has_key("input_info"):
# input_info = json.loads(tx_dict['input_info'])
# tx.add_input_info(input_info)
return tx
except Exception:
except:
traceback.print_exc(file=sys.stdout)
QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction"))
self.show_critical(_("Electrum was unable to parse your transaction"))
return
def read_tx_from_qrcode(self):
from electrum import qrscanner
try:
data = qrscanner.scan_qr(self.config)
except BaseException, e:
QMessageBox.warning(self, _('Error'), _(e), _('OK'))
except BaseException as e:
self.show_error(str(e))
return
if not data:
return
@ -2252,8 +2260,8 @@ class ElectrumWindow(QMainWindow, PrintError):
try:
with open(fileName, "r") as f:
file_content = f.read()
except (ValueError, IOError, os.error), reason:
QMessageBox.critical(None, _("Unable to read file or no transaction found"), _("Electrum was unable to open your transaction file") + "\n" + str(reason))
except (ValueError, IOError, os.error) as reason:
self.show_critical(_("Electrum was unable to open your transaction file") + "\n" + str(reason), title=_("Unable to read file or no transaction found"))
return self.tx_from_text(file_content)
def do_process_from_text(self):
@ -2292,11 +2300,10 @@ class ElectrumWindow(QMainWindow, PrintError):
try:
self.wallet.check_password(password)
except Exception as e:
QMessageBox.warning(self, _('Error'), str(e), _('OK'))
self.show_error(str(e))
return
d = QDialog(self)
d.setWindowTitle(_('Private keys'))
d = WindowModalDialog(self, _('Private keys'))
d.setMinimumSize(850, 300)
vbox = QVBoxLayout(d)
@ -2349,9 +2356,12 @@ class ElectrumWindow(QMainWindow, PrintError):
try:
self.do_export_privkeys(filename, private_keys, csv_button.isChecked())
except (IOError, os.error), reason:
export_error_label = _("Electrum was unable to produce a private key-export.")
QMessageBox.critical(None, _("Unable to create csv"), export_error_label + "\n" + str(reason))
except (IOError, os.error) as reason:
txt = "\n".join([
_("Electrum was unable to produce a private key-export."),
str(reason)
])
self.show_critical(txt, title=_("Unable to create csv"))
except Exception as e:
self.show_message(str(e))
@ -2381,9 +2391,9 @@ class ElectrumWindow(QMainWindow, PrintError):
f.close()
for key, value in json.loads(data).items():
self.wallet.set_label(key, value)
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))
self.show_message(_("Your labels were imported from") + " '%s'" % str(labelsFile))
except (IOError, os.error) as reason:
self.show_critical(_("Electrum was unable to import your labels.") + "\n" + str(reason))
def do_export_labels(self):
@ -2393,14 +2403,13 @@ class ElectrumWindow(QMainWindow, PrintError):
if fileName:
with open(fileName, 'w+') as f:
json.dump(labels, f)
QMessageBox.information(None, _("Labels exported"), _("Your labels where exported to")+" '%s'" % str(fileName))
self.show_message(_("Your labels where exported to") + " '%s'" % str(fileName))
except (IOError, os.error), reason:
QMessageBox.critical(None, _("Unable to export labels"), _("Electrum was unable to export your labels.")+"\n" + str(reason))
self.show_critical(_("Electrum was unable to export your labels.") + "\n" + str(reason))
def export_history_dialog(self):
d = QDialog(self)
d.setWindowTitle(_('Export History'))
d = WindowModalDialog(self, _('Export History'))
d.setMinimumSize(400, 200)
vbox = QVBoxLayout(d)
defaultname = os.path.expanduser('~/electrum-history.csv')
@ -2421,9 +2430,9 @@ class ElectrumWindow(QMainWindow, PrintError):
self.do_export_history(self.wallet, filename, csv_button.isChecked())
except (IOError, os.error), reason:
export_error_label = _("Electrum was unable to produce a transaction export.")
QMessageBox.critical(self, _("Unable to export history"), export_error_label + "\n" + str(reason))
self.show_critical(export_error_label + "\n" + str(reason), title=_("Unable to export history"))
return
QMessageBox.information(self,_("History exported"), _("Your wallet history has been successfully exported."))
self.show_message(_("Your wallet history has been successfully exported."))
def do_export_history(self, wallet, fileName, is_csv):
@ -2445,7 +2454,7 @@ class ElectrumWindow(QMainWindow, PrintError):
value_string = '--'
if tx_hash:
label, is_default_label = wallet.get_label(tx_hash)
label = wallet.get_label(tx_hash)
label = label.encode('utf-8')
else:
label = ""
@ -2467,12 +2476,11 @@ class ElectrumWindow(QMainWindow, PrintError):
def sweep_key_dialog(self):
d = QDialog(self)
d.setWindowTitle(_('Sweep private keys'))
d = WindowModalDialog(self, title=_('Sweep private keys'))
d.setMinimumSize(600, 300)
vbox = QVBoxLayout(d)
vbox.addWidget(QLabel(_("Enter private keys")))
vbox.addWidget(QLabel(_("Enter private keys:")))
keys_e = QTextEdit()
keys_e.setTabChangesFocus(True)
@ -2508,16 +2516,17 @@ class ElectrumWindow(QMainWindow, PrintError):
if not tx:
self.show_message(_('No inputs found. (Note that inputs need to be confirmed)'))
return
self.warn_if_watching_only()
self.show_transaction(tx)
@protected
def do_import_privkey(self, password):
if not self.wallet.has_imported_keys():
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>' \
+ _('Are you sure you understand what you are doing?'), 3, 4)
if r == 4: return
if not self.question('<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>' \
+ _('Are you sure you understand what you are doing?'), title=_('Warning')):
return
text = text_dialog(self, _('Import private keys'), _("Enter private keys")+':', _("Import"))
if not text: return
@ -2536,18 +2545,16 @@ class ElectrumWindow(QMainWindow, PrintError):
else:
addrlist.append(addr)
if addrlist:
QMessageBox.information(self, _('Information'), _("The following addresses were added") + ':\n' + '\n'.join(addrlist))
self.show_message(_("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.show_critical(_("The following inputs could not be imported") + ':\n'+ '\n'.join(badkeys))
self.address_list.update()
self.history_list.update()
def settings_dialog(self):
self.need_restart = False
d = QDialog(self)
d.setWindowTitle(_('Preferences'))
d.setModal(1)
d = WindowModalDialog(self, _('Preferences'))
vbox = QVBoxLayout()
tabs = QTabWidget()
gui_widgets = []
@ -2593,21 +2600,6 @@ class ElectrumWindow(QMainWindow, PrintError):
nz.valueChanged.connect(on_nz)
gui_widgets.append((nz_label, nz))
choosers = sorted(COIN_CHOOSERS.keys())
chooser_name = self.wallet.coin_chooser_name(self.config)
msg = _('Choose coin (UTXO) selection method. The following are available:\n\n')
msg += '\n\n'.join(key + ": " + klass.__doc__
for key, klass in COIN_CHOOSERS.items())
chooser_label = HelpLabel(_('Coin selection') + ':', msg)
chooser_combo = QComboBox()
chooser_combo.addItems(choosers)
chooser_combo.setCurrentIndex(choosers.index(chooser_name))
def on_chooser(x):
chooser_name = choosers[chooser_combo.currentIndex()]
self.config.set_key('coin_chooser', chooser_name)
chooser_combo.currentIndexChanged.connect(on_chooser)
tx_widgets.append((chooser_label, chooser_combo))
msg = _('Fee per kilobyte of transaction.') + '\n' \
+ _('If you enable dynamic fees, this parameter will be used as upper bound.')
fee_label = HelpLabel(_('Transaction fee per kb') + ':', msg)
@ -2784,6 +2776,24 @@ class ElectrumWindow(QMainWindow, PrintError):
can_edit_fees_cb.setToolTip(_('This option lets you edit fees in the send tab.'))
tx_widgets.append((can_edit_fees_cb, None))
def fmt_docs(key, klass):
lines = [ln.lstrip(" ") for ln in klass.__doc__.split("\n")]
return '\n'.join([key, "", " ".join(lines)])
choosers = sorted(COIN_CHOOSERS.keys())
chooser_name = self.wallet.coin_chooser_name(self.config)
msg = _('Choose coin (UTXO) selection method. The following are available:\n\n')
msg += '\n\n'.join(fmt_docs(*item) for item in COIN_CHOOSERS.items())
chooser_label = HelpLabel(_('Coin selection') + ':', msg)
chooser_combo = QComboBox()
chooser_combo.addItems(choosers)
chooser_combo.setCurrentIndex(choosers.index(chooser_name))
def on_chooser(x):
chooser_name = choosers[chooser_combo.currentIndex()]
self.config.set_key('coin_chooser', chooser_name)
chooser_combo.currentIndexChanged.connect(on_chooser)
tx_widgets.append((chooser_label, chooser_combo))
tabs_info = [
(tx_widgets, _('Transactions')),
(gui_widgets, _('Appearance')),
@ -2813,13 +2823,11 @@ class ElectrumWindow(QMainWindow, PrintError):
run_hook('close_settings_dialog')
if self.need_restart:
QMessageBox.warning(self, _('Success'), _('Please restart Electrum to activate the new GUI settings'), _('OK'))
self.show_warning(_('Please restart Electrum to activate the new GUI settings'), title=_('Success'))
def run_network_dialog(self):
if not self.network:
QMessageBox.warning(self, _('Offline'), _('You are using Electrum in offline mode.\nRestart Electrum if you want to get connected.'), _('OK'))
self.show_warning(_('You are using Electrum in offline mode; restart Electrum if you want to get connected'), title=_('Offline'))
return
NetworkDialog(self.wallet.network, self.config, self).do_exec()
@ -2839,9 +2847,7 @@ class ElectrumWindow(QMainWindow, PrintError):
def plugins_dialog(self):
self.pluginsdialog = d = QDialog(self)
d.setWindowTitle(_('Electrum Plugins'))
d.setModal(1)
self.pluginsdialog = d = WindowModalDialog(self, _('Electrum Plugins'))
plugins = self.gui_object.plugins
@ -2867,7 +2873,7 @@ class ElectrumWindow(QMainWindow, PrintError):
def enable_settings_widget(p, name, i):
widget = settings_widgets.get(name)
if not widget and p and p.requires_settings():
widget = settings_widgets[name] = p.settings_widget(self)
widget = settings_widgets[name] = p.settings_widget(d)
grid.addWidget(widget, i, 1)
if widget:
widget.setEnabled(bool(p and p.is_enabled()))
@ -2904,9 +2910,7 @@ class ElectrumWindow(QMainWindow, PrintError):
def show_account_details(self, k):
account = self.wallet.accounts[k]
d = QDialog(self)
d.setWindowTitle(_('Account Details'))
d.setModal(1)
d = WindowModalDialog(self, _('Account Details'))
vbox = QVBoxLayout(d)
name = self.wallet.get_account_name(k)

View File

@ -16,30 +16,21 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys, time, datetime, re, threading
import os.path, json, ast, traceback
from PyQt4.QtGui import *
from PyQt4.QtCore import *
from electrum.i18n import _
from electrum.util import print_error, print_msg
from electrum import DEFAULT_PORTS
from electrum.network import serialize_server, deserialize_server
from util import *
#protocol_names = ['TCP', 'HTTP', 'SSL', 'HTTPS']
#protocol_letters = 'thsg'
protocol_names = ['TCP', 'SSL']
protocol_letters = 'ts'
class NetworkDialog(QDialog):
class NetworkDialog(WindowModalDialog):
def __init__(self, network, config, parent):
QDialog.__init__(self,parent)
self.setModal(1)
self.setWindowTitle(_('Network'))
WindowModalDialog.__init__(self, parent, _('Network'))
self.setMinimumSize(375, 20)
self.network = network

View File

@ -23,84 +23,6 @@ from util import *
import re
import math
def make_password_dialog(self, wallet, msg, new_pass=True):
self.pw = QLineEdit()
self.pw.setEchoMode(2)
self.new_pw = QLineEdit()
self.new_pw.setEchoMode(2)
self.conf_pw = QLineEdit()
self.conf_pw.setEchoMode(2)
vbox = QVBoxLayout()
label = QLabel(msg)
label.setWordWrap(True)
grid = QGridLayout()
grid.setSpacing(8)
grid.setColumnMinimumWidth(0, 70)
grid.setColumnStretch(1,1)
logo = QLabel()
lockfile = ":icons/lock.png" if wallet and wallet.use_encryption else ":icons/unlock.png"
logo.setPixmap(QPixmap(lockfile).scaledToWidth(36))
logo.setAlignment(Qt.AlignCenter)
grid.addWidget(logo, 0, 0)
grid.addWidget(label, 0, 1, 1, 2)
vbox.addLayout(grid)
grid = QGridLayout()
grid.setSpacing(8)
grid.setColumnMinimumWidth(0, 250)
grid.setColumnStretch(1,1)
if wallet and wallet.use_encryption:
grid.addWidget(QLabel(_('Password')), 0, 0)
grid.addWidget(self.pw, 0, 1)
grid.addWidget(QLabel(_('New Password') if new_pass else _('Password')), 1, 0)
grid.addWidget(self.new_pw, 1, 1)
grid.addWidget(QLabel(_('Confirm Password')), 2, 0)
grid.addWidget(self.conf_pw, 2, 1)
vbox.addLayout(grid)
#Password Strength Label
self.pw_strength = QLabel()
grid.addWidget(self.pw_strength, 3, 0, 1, 2)
self.new_pw.textChanged.connect(lambda: update_password_strength(self.pw_strength, self.new_pw.text()))
vbox.addStretch(1)
vbox.addLayout(Buttons(CancelButton(self), OkButton(self)))
return vbox
def run_password_dialog(self, wallet, parent):
if wallet and wallet.is_watching_only():
QMessageBox.information(parent, _('Error'), _('This is a watching-only wallet'), _('OK'))
return False, None, None
if not self.exec_():
return False, None, None
password = unicode(self.pw.text()) if wallet and wallet.use_encryption else None
new_password = unicode(self.new_pw.text())
new_password2 = unicode(self.conf_pw.text())
if new_password != new_password2:
QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
# Retry
return run_password_dialog(self, wallet, parent)
if not new_password:
new_password = None
return True, password, new_password
def check_password_strength(password):
'''
@ -117,59 +39,89 @@ def check_password_strength(password):
password_strength = {0:"Weak",1:"Medium",2:"Strong",3:"Very Strong"}
return password_strength[min(3, int(score))]
class PasswordDialog(WindowModalDialog):
def update_password_strength(pw_strength_label,password):
'''
call the function check_password_strength and update the label pw_strength interactively as the user is typing the password
:param pw_strength_label: the label pw_strength
:param password: password entered in New Password text box
:return: None
'''
if password:
colors = {"Weak":"Red","Medium":"Blue","Strong":"Green", "Very Strong":"Green"}
strength = check_password_strength(password)
label = _("Password Strength")+ ": "+"<font color=" + colors[strength] + ">" + strength + "</font>"
else:
label = ""
pw_strength_label.setText(label)
class PasswordDialog(QDialog):
def __init__(self, wallet, parent):
QDialog.__init__(self, parent)
self.setModal(1)
def __init__(self, parent, wallet, title, msg, new_pass):
WindowModalDialog.__init__(self, parent, title)
self.wallet = wallet
self.parent = parent
self.setWindowTitle(_("Set Password"))
msg = (_('Your wallet is encrypted. Use this dialog to change your password.') + ' '\
+_('To disable wallet encryption, enter an empty new password.')) \
if wallet.use_encryption else _('Your wallet keys are not encrypted')
self.setLayout(make_password_dialog(self, wallet, msg))
self.pw = QLineEdit()
self.pw.setEchoMode(2)
self.new_pw = QLineEdit()
self.new_pw.setEchoMode(2)
self.conf_pw = QLineEdit()
self.conf_pw.setEchoMode(2)
vbox = QVBoxLayout()
label = QLabel(msg)
label.setWordWrap(True)
grid = QGridLayout()
grid.setSpacing(8)
grid.setColumnMinimumWidth(0, 70)
grid.setColumnStretch(1,1)
logo = QLabel()
logo.setAlignment(Qt.AlignCenter)
grid.addWidget(logo, 0, 0)
grid.addWidget(label, 0, 1, 1, 2)
vbox.addLayout(grid)
grid = QGridLayout()
grid.setSpacing(8)
grid.setColumnMinimumWidth(0, 250)
grid.setColumnStretch(1,1)
if wallet and wallet.use_encryption:
grid.addWidget(QLabel(_('Password')), 0, 0)
grid.addWidget(self.pw, 0, 1)
lockfile = ":icons/lock.png"
else:
self.pw = None
lockfile = ":icons/unlock.png"
logo.setPixmap(QPixmap(lockfile).scaledToWidth(36))
grid.addWidget(QLabel(_('New Password') if new_pass else _('Password')), 1, 0)
grid.addWidget(self.new_pw, 1, 1)
grid.addWidget(QLabel(_('Confirm Password')), 2, 0)
grid.addWidget(self.conf_pw, 2, 1)
vbox.addLayout(grid)
# Password Strength Label
self.pw_strength = QLabel()
grid.addWidget(self.pw_strength, 3, 0, 1, 2)
self.new_pw.textChanged.connect(self.pw_changed)
self.conf_pw.textChanged.connect(self.check_OKButton)
self.OKButton = OkButton(self)
vbox.addStretch(1)
vbox.addLayout(Buttons(CancelButton(self), self.OKButton))
self.setLayout(vbox)
def pw_changed(self):
password = self.new_pw.text()
if password:
colors = {"Weak":"Red", "Medium":"Blue", "Strong":"Green",
"Very Strong":"Green"}
strength = check_password_strength(password)
label = (_("Password Strength") + ": " + "<font color="
+ colors[strength] + ">" + strength + "</font>")
else:
label = ""
self.pw_strength.setText(label)
self.check_OKButton()
def check_OKButton(self):
self.OKButton.setEnabled(self.new_pw.text() == self.conf_pw.text())
def run(self):
ok, password, new_password = run_password_dialog(self, self.wallet, self.parent)
if not ok:
return
try:
self.wallet.check_password(password)
except BaseException as e:
QMessageBox.warning(self.parent, _('Error'), str(e), _('OK'))
if not self.exec_():
return False, None, None
try:
self.wallet.update_password(password, new_password)
except:
import traceback, sys
traceback.print_exc(file=sys.stdout)
QMessageBox.warning(self.parent, _('Error'), _('Failed to update password'), _('OK'))
return
password = unicode(self.pw.text()) if self.pw else None
new_password = unicode(self.new_pw.text())
new_password2 = unicode(self.conf_pw.text())
if new_password:
QMessageBox.information(self.parent, _('Success'), _('Password was updated successfully'), _('OK'))
else:
QMessageBox.information(self.parent, _('Success'), _('This wallet is not encrypted'), _('OK'))
return True, password or None, new_password or None

View File

@ -160,16 +160,8 @@ class PayToEdit(ScanQRTextEdit):
return len(self.lines()) > 1
def paytomany(self):
from electrum.i18n import _
self.setText("\n\n\n")
self.update_size()
msg = '\n'.join([
_('Enter a list of outputs in the \'Pay to\' field.'),
_('One output per line.'),
_('Format: address, amount.'),
_('You may load a CSV file using the file icon.')
])
QMessageBox.warning(self, _('Pay to many'), msg, _('OK'))
def update_size(self):
docHeight = self.document().size().height()

View File

@ -1,6 +1,5 @@
from PyQt4.QtGui import *
from PyQt4.QtCore import *
import PyQt4.QtCore as QtCore
import PyQt4.QtGui as QtGui
import os
@ -9,6 +8,7 @@ import qrcode
import electrum
from electrum import bmp
from electrum.i18n import _
from util import WindowModalDialog, MessageBoxMixin
class QRCodeWidget(QWidget):
@ -83,13 +83,11 @@ class QRCodeWidget(QWidget):
class QRDialog(QDialog):
class QRDialog(WindowModalDialog, MessageBoxMixin):
def __init__(self, data, parent=None, title = "", show_text=False):
QDialog.__init__(self, parent)
WindowModalDialog.__init__(self, parent, title)
d = self
d.setWindowTitle(title)
vbox = QVBoxLayout()
qrw = QRCodeWidget(data)
vbox.addWidget(qrw, 1)
@ -107,12 +105,12 @@ class QRDialog(QDialog):
def print_qr():
bmp.save_qrcode(qrw.qr, filename)
QMessageBox.information(None, _('Message'), _("QR code saved to file") + " " + filename, _('OK'))
self.show_message(_("QR code saved to file") + " " + filename)
def copy_to_clipboard():
bmp.save_qrcode(qrw.qr, filename)
QApplication.clipboard().setImage(QImage(filename))
QMessageBox.information(None, _('Message'), _("QR code saved to clipboard"), _('OK'))
self.show_message(_("QR code copied to clipboard"))
b = QPushButton(_("Copy"))
hbox.addWidget(b)
@ -124,8 +122,8 @@ class QRDialog(QDialog):
b = QPushButton(_("Close"))
hbox.addWidget(b)
b.clicked.connect(d.accept)
b.clicked.connect(self.accept)
b.setDefault(True)
vbox.addLayout(hbox)
d.setLayout(vbox)
self.setLayout(vbox)

View File

@ -3,7 +3,7 @@ from electrum.plugins import run_hook
from PyQt4.QtGui import *
from PyQt4.QtCore import *
from util import ButtonsTextEdit
from util import ButtonsTextEdit, MessageBoxMixin
class ShowQRTextEdit(ButtonsTextEdit):
@ -29,7 +29,7 @@ class ShowQRTextEdit(ButtonsTextEdit):
m.exec_(e.globalPos())
class ScanQRTextEdit(ButtonsTextEdit):
class ScanQRTextEdit(ButtonsTextEdit, MessageBoxMixin):
def __init__(self, text=""):
ButtonsTextEdit.__init__(self, text)
@ -50,8 +50,8 @@ class ScanQRTextEdit(ButtonsTextEdit):
from electrum import qrscanner, get_config
try:
data = qrscanner.scan_qr(get_config())
except BaseException, e:
QMessageBox.warning(self, _('Error'), _(e), _('OK'))
except BaseException as e:
self.show_error(str(e))
return ""
if type(data) != str:
return

View File

@ -20,17 +20,14 @@ from PyQt4.QtGui import *
from PyQt4.QtCore import *
import PyQt4.QtCore as QtCore
from electrum.i18n import _
from electrum import mnemonic
from util import *
from qrtextedit import ShowQRTextEdit, ScanQRTextEdit
class SeedDialog(QDialog):
class SeedDialog(WindowModalDialog):
def __init__(self, parent, seed, imported_keys):
QDialog.__init__(self, parent)
self.setModal(1)
WindowModalDialog.__init__(self, parent, ('Electrum - ' + _('Seed')))
self.setMinimumWidth(400)
self.setWindowTitle('Electrum' + ' - ' + _('Seed'))
vbox = show_seed_box_msg(seed)
if imported_keys:
vbox.addWidget(QLabel("<b>"+_("WARNING")+":</b> " + _("Your wallet contains imported keys. These keys cannot be recovered from seed.") + "</b><p>"))

View File

@ -38,7 +38,7 @@ def show_transaction(tx, parent, desc=None, prompt_if_unsaved=False):
dialogs.append(d)
d.show()
class TxDialog(QDialog):
class TxDialog(QDialog, MessageBoxMixin):
def __init__(self, tx, parent, desc, prompt_if_unsaved):
'''Transactions in the wallet will show their description.
@ -54,7 +54,7 @@ class TxDialog(QDialog):
self.desc = desc
QDialog.__init__(self)
self.setMinimumWidth(600)
self.setMinimumWidth(660)
self.setWindowTitle(_("Transaction"))
vbox = QVBoxLayout()
@ -62,7 +62,7 @@ class TxDialog(QDialog):
vbox.addWidget(QLabel(_("Transaction ID:")))
self.tx_hash_e = ButtonsLineEdit()
qr_show = lambda: self.parent.show_qrcode(str(self.tx_hash_e.text()), 'Transaction ID')
qr_show = lambda: self.parent.show_qrcode(str(self.tx_hash_e.text()), 'Transaction ID', parent=self)
self.tx_hash_e.addButton(":icons/qrcode.png", qr_show, _("Show as QR code"))
self.tx_hash_e.setReadOnly(True)
vbox.addWidget(self.tx_hash_e)
@ -122,10 +122,7 @@ class TxDialog(QDialog):
def closeEvent(self, event):
if (self.prompt_if_unsaved and not self.saved and not self.broadcast
and QMessageBox.question(
self, _('Warning'),
_('This transaction is not saved. Close anyway?'),
QMessageBox.Yes | QMessageBox.No) == QMessageBox.No):
and not self.question(_('This transaction is not saved. Close anyway?'), title=_("Warning"))):
event.ignore()
else:
event.accept()
@ -135,7 +132,7 @@ class TxDialog(QDialog):
text = self.tx.raw.decode('hex')
text = base_encode(text, base=43)
try:
self.parent.show_qrcode(text, 'Transaction')
self.parent.show_qrcode(text, 'Transaction', parent=self)
except Exception as e:
self.show_message(str(e))
@ -173,7 +170,7 @@ class TxDialog(QDialog):
status = _("Signed")
if tx_hash in self.wallet.transactions.keys():
desc, is_default = self.wallet.get_label(tx_hash)
desc = self.wallet.get_label(tx_hash)
conf, timestamp = self.wallet.get_confirmations(tx_hash)
if timestamp:
time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
@ -249,6 +246,9 @@ class TxDialog(QDialog):
return chg if self.wallet.is_change(addr) else rec
return ext
def format_amount(amt):
return self.parent.format_amount(amt, whitespaces = True)
i_text = QTextEdit()
i_text.setFont(QFont(MONOSPACE_FONT))
i_text.setReadOnly(True)
@ -270,6 +270,8 @@ class TxDialog(QDialog):
if addr is None:
addr = _('unknown')
cursor.insertText(addr, text_format(addr))
if x.get('value'):
cursor.insertText(format_amount(x['value']), ext)
cursor.insertBlock()
vbox.addWidget(i_text)
@ -283,11 +285,6 @@ class TxDialog(QDialog):
cursor.insertText(addr, text_format(addr))
if v is not None:
cursor.insertText('\t', ext)
cursor.insertText(self.parent.format_amount(v, whitespaces = True), ext)
cursor.insertText(format_amount(v), ext)
cursor.insertBlock()
vbox.addWidget(o_text)
def show_message(self, msg):
QMessageBox.information(self, _('Message'), msg, _('OK'))

View File

@ -21,51 +21,19 @@ RED_FG = "QWidget {color:red;}"
BLUE_FG = "QWidget {color:blue;}"
BLACK_FG = "QWidget {color:black;}"
class WaitingDialog(QThread):
def __init__(self, parent, message, run_task, on_success=None, on_complete=None):
QThread.__init__(self)
self.parent = parent
self.d = QDialog(parent)
self.d.setWindowTitle('Please wait')
l = QLabel(message)
vbox = QVBoxLayout(self.d)
vbox.addWidget(l)
self.run_task = run_task
self.on_success = on_success
self.on_complete = on_complete
self.d.connect(self.d, SIGNAL('done'), self.close)
self.d.show()
def run(self):
self.error = None
try:
self.result = self.run_task()
except BaseException as e:
traceback.print_exc(file=sys.stdout)
self.error = str(e)
self.d.emit(SIGNAL('done'))
def close(self):
self.d.accept()
if self.error:
QMessageBox.warning(self.parent, _('Error'), self.error, _('OK'))
else:
if self.on_success:
if type(self.result) is not tuple:
self.result = (self.result,)
self.on_success(*self.result)
if self.on_complete:
self.on_complete()
dialogs = []
class Timer(QThread):
stopped = False
def run(self):
while True:
while not self.stopped:
self.emit(SIGNAL('timersignal'))
time.sleep(0.5)
def stop(self):
self.stopped = True
self.wait()
class EnterButton(QPushButton):
def __init__(self, text, func):
@ -187,12 +155,91 @@ class CancelButton(QPushButton):
QPushButton.__init__(self, label or _("Cancel"))
self.clicked.connect(dialog.reject)
class MessageBoxMixin:
def question(self, msg, parent=None, title=None):
Yes, No = QMessageBox.Yes, QMessageBox.No
return self.msg_box(QMessageBox.Question, parent or self, title or '',
msg, buttons=Yes|No, defaultButton=No) == Yes
def show_warning(self, msg, parent=None, title=None):
return self.msg_box(QMessageBox.Warning, parent or self,
title or _('Warning'), msg)
def show_error(self, msg, parent=None):
return self.msg_box(QMessageBox.Warning, parent or self,
_('Error'), msg)
def show_critical(self, msg, parent=None, title=None):
return self.msg_box(QMessageBox.Critical, parent or self,
title or _('Critical Error'), msg)
def show_message(self, msg, parent=None, title=None):
return self.msg_box(QMessageBox.Information, parent or self,
title or _('Information'), msg)
@staticmethod
def msg_box(icon, parent, title, text, buttons=QMessageBox.Ok,
defaultButton=QMessageBox.NoButton):
# handle e.g. ElectrumGui
if not isinstance(parent, QWidget):
parent = None
d = QMessageBox(icon, title, text, buttons, parent)
d.setWindowModality(Qt.WindowModal)
d.setDefaultButton(defaultButton)
return d.exec_()
class WindowModalDialog(QDialog):
'''Handy wrapper; window modal dialogs are better for our multi-window
daemon model as other wallet windows can still be accessed.'''
def __init__(self, parent, title=None):
QDialog.__init__(self, parent)
self.setWindowModality(Qt.WindowModal)
if title:
self.setWindowTitle(title)
class WaitingDialog(QThread, MessageBoxMixin):
'''Shows a please wait dialog whilst runnning a task. It is not
necessary to maintain a reference to this dialog.'''
def __init__(self, parent, message, task, on_success=None,
on_finished=None):
global dialogs
dialogs.append(self) # Prevent GC
QThread.__init__(self)
self.task = task
self.on_success = on_success
self.on_finished = on_finished
self.dialog = WindowModalDialog(parent, _("Please wait"))
vbox = QVBoxLayout(self.dialog)
vbox.addWidget(QLabel(message))
self.dialog.show()
self.dialog.connect(self, SIGNAL("finished()"), self.finished)
self.start()
def run(self):
self.error = None
try:
self.result = self.task()
except BaseException as e:
traceback.print_exc(file=sys.stdout)
self.error = str(e)
def finished(self):
global dialogs
dialogs.remove(self)
if self.error:
self.show_error(self.error, parent=self.dialog.parent())
elif self.on_success:
result = self.result
if type(result) is not tuple:
result = (result,)
self.on_success(*result)
if self.on_finished:
self.on_finished()
self.dialog.accept()
def line_dialog(parent, title, label, ok_label, default=None):
dialog = QDialog(parent)
dialog = WindowModalDialog(parent, title)
dialog.setMinimumWidth(500)
dialog.setWindowTitle(title)
dialog.setModal(1)
l = QVBoxLayout()
dialog.setLayout(l)
l.addWidget(QLabel(label))
@ -206,10 +253,8 @@ def line_dialog(parent, title, label, ok_label, default=None):
def text_dialog(parent, title, label, ok_label, default=None):
from qrtextedit import ScanQRTextEdit
dialog = QDialog(parent)
dialog = WindowModalDialog(parent, title)
dialog.setMinimumWidth(500)
dialog.setWindowTitle(title)
dialog.setModal(1)
l = QVBoxLayout()
dialog.setLayout(l)
l.addWidget(QLabel(label))
@ -221,9 +266,6 @@ def text_dialog(parent, title, label, ok_label, default=None):
if dialog.exec_():
return unicode(txt.toPlainText())
def question(msg):
return QMessageBox.question(None, _('Message'), msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
def address_field(addresses):
hbox = QHBoxLayout()
address_e = QLineEdit()
@ -383,12 +425,6 @@ class MyTreeWidget(QTreeWidget):
key = str(item.data(0, Qt.UserRole).toString())
text = unicode(item.text(column))
self.parent.wallet.set_label(key, text)
if text:
item.setForeground(column, QBrush(QColor('black')))
else:
text = self.parent.wallet.get_default_label(key)
item.setText(column, text)
item.setForeground(column, QBrush(QColor('gray')))
self.parent.history_list.update()
self.parent.update_completions()

View File

@ -96,7 +96,7 @@ class ElectrumGui:
else:
time_str = 'pending'
label, is_default_label = self.wallet.get_label(tx_hash)
label = self.wallet.get_label(tx_hash)
messages.append( format_str%( time_str, label, format_satoshis(value, whitespaces=True), format_satoshis(balance, whitespaces=True) ) )
self.print_list(messages[::-1], format_str%( _("Date"), _("Description"), _("Amount"), _("Balance")))

View File

@ -116,7 +116,7 @@ class ElectrumGui:
else:
time_str = 'pending'
label, is_default_label = self.wallet.get_label(tx_hash)
label = self.wallet.get_label(tx_hash)
if len(label) > 40:
label = label[0:37] + '...'
self.history.append( format_str%( time_str, label, format_satoshis(value, whitespaces=True), format_satoshis(balance, whitespaces=True) ) )

View File

@ -21,13 +21,14 @@ import os
import util
from bitcoin import *
MAX_TARGET = 0x00000000FFFF0000000000000000000000000000000000000000000000000000
class Blockchain(util.PrintError):
'''Manages blockchain headers and their verification'''
def __init__(self, config, network):
self.config = config
self.network = network
self.headers_url = 'https://headers.electrum.org/blockchain_headers'
self.headers_url = "https://headers.electrum.org/blockchain_headers"
self.local_height = 0
self.set_local_height()
@ -39,76 +40,44 @@ class Blockchain(util.PrintError):
self.set_local_height()
self.print_error("%d blocks" % self.local_height)
def verify_header(self, header, prev_header, bits, target):
prev_hash = self.hash_header(prev_header)
assert prev_hash == header.get('prev_block_hash'), "prev hash mismatch: %s vs %s" % (prev_hash, header.get('prev_block_hash'))
assert bits == header.get('bits'), "bits mismatch: %s vs %s" % (bits, header.get('bits'))
_hash = self.hash_header(header)
assert int('0x' + _hash, 16) <= target, "insufficient proof of work: %s vs target %s" % (int('0x' + _hash, 16), target)
def verify_chain(self, chain):
first_header = chain[0]
prev_header = self.read_header(first_header.get('block_height') -1)
prev_header = self.read_header(first_header.get('block_height') - 1)
for header in chain:
height = header.get('block_height')
prev_hash = self.hash_header(prev_header)
if prev_hash != header.get('prev_block_hash'):
self.print_error("prev hash mismatch: %s vs %s"
% (prev_hash, header.get('prev_block_hash')))
return False
bits, target = self.get_target(height/2016, chain)
if bits != header.get('bits'):
self.print_error("bits mismatch: %s vs %s"
% (bits, header.get('bits')))
return False
_hash = self.hash_header(header)
if int('0x'+_hash, 16) > target:
self.print_error("insufficient proof of work: %s vs target %s"
% (int('0x'+_hash, 16), target))
return False
bits, target = self.get_target(height / 2016, chain)
self.verify_header(header, prev_header, bits, target)
prev_header = header
return True
def verify_chunk(self, index, hexdata):
data = hexdata.decode('hex')
height = index*2016
num = len(data)/80
if index == 0:
previous_hash = ("0"*64)
else:
prev_header = self.read_header(index*2016-1)
if prev_header is None: raise
previous_hash = self.hash_header(prev_header)
def verify_chunk(self, index, data):
num = len(data) / 80
prev_header = None
if index != 0:
prev_header = self.read_header(index*2016 - 1)
bits, target = self.get_target(index)
for i in range(num):
height = index*2016 + i
raw_header = data[i*80:(i+1)*80]
header = self.header_from_string(raw_header)
_hash = self.hash_header(header)
assert previous_hash == header.get('prev_block_hash')
assert bits == header.get('bits')
assert int('0x'+_hash,16) < target
raw_header = data[i*80:(i+1) * 80]
header = self.deserialize_header(raw_header)
self.verify_header(header, prev_header, bits, target)
prev_header = header
previous_header = header
previous_hash = _hash
self.save_chunk(index, data)
self.print_error("validated chunk %d to height %d" % (index, height))
def header_to_string(self, res):
s = int_to_hex(res.get('version'),4) \
def serialize_header(self, res):
s = int_to_hex(res.get('version'), 4) \
+ rev_hex(res.get('prev_block_hash')) \
+ rev_hex(res.get('merkle_root')) \
+ int_to_hex(int(res.get('timestamp')),4) \
+ int_to_hex(int(res.get('bits')),4) \
+ int_to_hex(int(res.get('nonce')),4)
+ int_to_hex(int(res.get('timestamp')), 4) \
+ int_to_hex(int(res.get('bits')), 4) \
+ int_to_hex(int(res.get('nonce')), 4)
return s
def header_from_string(self, s):
def deserialize_header(self, s):
hex_to_int = lambda s: int('0x' + s[::-1].encode('hex'), 16)
h = {}
h['version'] = hex_to_int(s[0:4])
@ -120,7 +89,9 @@ class Blockchain(util.PrintError):
return h
def hash_header(self, header):
return rev_hex(Hash(self.header_to_string(header).decode('hex')).encode('hex'))
if header is None:
return '0' * 64
return hash_encode(Hash(self.serialize_header(header).decode('hex')))
def path(self):
return os.path.join(self.config.path, 'blockchain_headers')
@ -132,28 +103,28 @@ class Blockchain(util.PrintError):
try:
import urllib, socket
socket.setdefaulttimeout(30)
self.print_error("downloading ", self.headers_url )
self.print_error("downloading ", self.headers_url)
urllib.urlretrieve(self.headers_url, filename)
self.print_error("done.")
except Exception:
self.print_error( "download failed. creating file", filename )
open(filename,'wb+').close()
self.print_error("download failed. creating file", filename)
open(filename, 'wb+').close()
def save_chunk(self, index, chunk):
filename = self.path()
f = open(filename,'rb+')
f.seek(index*2016*80)
f = open(filename, 'rb+')
f.seek(index * 2016 * 80)
h = f.write(chunk)
f.close()
self.set_local_height()
def save_header(self, header):
data = self.header_to_string(header).decode('hex')
data = self.serialize_header(header).decode('hex')
assert len(data) == 80
height = header.get('block_height')
filename = self.path()
f = open(filename,'rb+')
f.seek(height*80)
f = open(filename, 'rb+')
f.seek(height * 80)
h = f.write(data)
f.close()
self.set_local_height()
@ -168,58 +139,47 @@ class Blockchain(util.PrintError):
def read_header(self, block_height):
name = self.path()
if os.path.exists(name):
f = open(name,'rb')
f.seek(block_height*80)
f = open(name, 'rb')
f.seek(block_height * 80)
h = f.read(80)
f.close()
if len(h) == 80:
h = self.header_from_string(h)
h = self.deserialize_header(h)
return h
def get_target(self, index, chain=None):
if chain is None:
chain = [] # Do not use mutables as default values!
max_target = 0x00000000FFFF0000000000000000000000000000000000000000000000000000
if index == 0: return 0x1d00ffff, max_target
first = self.read_header((index-1)*2016)
last = self.read_header(index*2016-1)
if index == 0:
return 0x1d00ffff, MAX_TARGET
first = self.read_header((index-1) * 2016)
last = self.read_header(index*2016 - 1)
if last is None:
for h in chain:
if h.get('block_height') == index*2016-1:
if h.get('block_height') == index*2016 - 1:
last = h
nActualTimespan = last.get('timestamp') - first.get('timestamp')
nTargetTimespan = 14*24*60*60
nActualTimespan = max(nActualTimespan, nTargetTimespan/4)
nActualTimespan = min(nActualTimespan, nTargetTimespan*4)
assert last is not None
# bits to target
bits = last.get('bits')
# convert to bignum
MM = 256*256*256
a = bits%MM
if a < 0x8000:
a *= 256
target = (a) * pow(2, 8 * (bits/MM - 3))
bitsN = (bits >> 24) & 0xff
assert bitsN >= 0x03 and bitsN <= 0x1d, "First part of bits should be in [0x03, 0x1d]"
bitsBase = bits & 0xffffff
assert bitsBase >= 0x8000 and bitsBase <= 0x7fffff, "Second part of bits should be in [0x8000, 0x7fffff]"
target = bitsBase << (8 * (bitsN-3))
# new target
new_target = min( max_target, (target * nActualTimespan)/nTargetTimespan )
# convert it to bits
c = ("%064X"%new_target)[2:]
i = 31
while c[0:2]=="00":
nActualTimespan = last.get('timestamp') - first.get('timestamp')
nTargetTimespan = 14 * 24 * 60 * 60
nActualTimespan = max(nActualTimespan, nTargetTimespan / 4)
nActualTimespan = min(nActualTimespan, nTargetTimespan * 4)
new_target = min(MAX_TARGET, (target*nActualTimespan) / nTargetTimespan)
# convert new target to bits
c = ("%064x" % new_target)[2:]
while c[:2] == '00' and len(c) > 6:
c = c[2:]
i -= 1
c = int('0x'+c[0:6],16)
if c >= 0x800000:
c /= 256
i += 1
new_bits = c + MM * i
return new_bits, new_target
bitsN, bitsBase = len(c) / 2, int('0x' + c[:6], 16)
if bitsBase >= 0x800000:
bitsN += 1
bitsBase >>= 8
new_bits = bitsN << 24 | bitsBase
return new_bits, bitsBase << (8 * (bitsN-3))
def connect_header(self, chain, header):
'''Builds a header chain until it connects. Returns True if it has
@ -241,18 +201,23 @@ class Blockchain(util.PrintError):
# The chain is complete. Reverse to order by increasing height
chain.reverse()
if self.verify_chain(chain):
try:
self.verify_chain(chain)
self.print_error("connected at height:", previous_height)
for header in chain:
self.save_header(header)
return True
except BaseException as e:
self.print_error(str(e))
return False
return False
def connect_chunk(self, idx, chunk):
def connect_chunk(self, idx, hexdata):
try:
self.verify_chunk(idx, chunk)
data = hexdata.decode('hex')
self.verify_chunk(idx, data)
self.print_error("validated chunk %d" % idx)
self.save_chunk(idx, data)
return idx + 1
except Exception:
self.print_error('verify_chunk failed')
except BaseException as e:
self.print_error('verify_chunk failed', str(e))
return idx - 1

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python
#
# Electrum - lightweight Bitcoin client
# Copyright (C) 2011 thomasv@gitorious
# Copyright (C) 2015 kyuupichan@gmail
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -17,7 +17,8 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from collections import defaultdict, namedtuple
from random import shuffle
from random import choice, randint, shuffle
from math import floor, log10
from bitcoin import COIN
from transaction import Transaction
@ -25,6 +26,15 @@ from util import NotEnoughFunds, PrintError, profiler
Bucket = namedtuple('Bucket', ['desc', 'size', 'value', 'coins'])
def strip_unneeded(bkts, sufficient_funds):
'''Remove buckets that are unnecessary in achieving the spend amount'''
bkts = sorted(bkts, key = lambda bkt: bkt.value)
for i in range(len(bkts)):
if not sufficient_funds(bkts[i + 1:]):
return bkts[i:]
# Shouldn't get here
return bkts
class CoinChooserBase(PrintError):
def keys(self, coins):
@ -49,17 +59,25 @@ class CoinChooserBase(PrintError):
return 0
return penalty
def add_change(self, tx, change_addrs, fee_estimator, dust_threshold):
# How much is left if we add 1 change output?
change_amount = tx.get_fee() - fee_estimator(1)
def change_amounts(self, tx, count, fee_estimator, dust_threshold):
# The amount left after adding 1 change output
return [max(0, tx.get_fee() - fee_estimator(1))]
def change_outputs(self, tx, change_addrs, fee_estimator, dust_threshold):
amounts = self.change_amounts(tx, len(change_addrs), fee_estimator,
dust_threshold)
assert min(amounts) >= 0
assert len(change_addrs) >= len(amounts)
# If change is above dust threshold after accounting for the
# size of the change output, add it to the transaction.
if change_amount > dust_threshold:
tx.outputs.append(('address', change_addrs[0], change_amount))
self.print_error('change', change_amount)
elif change_amount:
self.print_error('not keeping dust', change_amount)
dust = sum(amount for amount in amounts if amount < dust_threshold)
amounts = [amount for amount in amounts if amount >= dust_threshold]
change = [('address', addr, amount)
for addr, amount in zip(change_addrs, amounts)]
self.print_error('change:', change)
if dust:
self.print_error('not keeping dust', dust)
return change
def make_tx(self, coins, outputs, change_addrs, fee_estimator,
dust_threshold):
@ -72,12 +90,18 @@ class CoinChooserBase(PrintError):
tx = Transaction.from_io([], outputs[:])
# Size of the transaction with no inputs and no change
base_size = tx.estimated_size()
# Returns fee given input size
fee = lambda input_size: fee_estimator(base_size + input_size)
spent_amount = tx.output_value()
def sufficient_funds(buckets):
'''Given a list of buckets, return True if it has enough
value to pay for the transaction'''
total_input = sum(bucket.value for bucket in buckets)
total_size = sum(bucket.size for bucket in buckets) + base_size
return total_input >= spent_amount + fee_estimator(total_size)
# Collect the coins into buckets, choose a subset of the buckets
buckets = self.bucketize_coins(coins)
buckets = self.choose_buckets(buckets, tx.output_value(), fee,
buckets = self.choose_buckets(buckets, sufficient_funds,
self.penalty_func(tx))
tx.inputs = [coin for b in buckets for coin in b.coins]
@ -86,50 +110,37 @@ class CoinChooserBase(PrintError):
# This takes a count of change outputs and returns a tx fee;
# each pay-to-bitcoin-address output serializes as 34 bytes
fee = lambda count: fee_estimator(tx_size + count * 34)
self.add_change(tx, change_addrs, fee, dust_threshold)
change = self.change_outputs(tx, change_addrs, fee, dust_threshold)
tx.outputs.extend(change)
self.print_error("using %d inputs" % len(tx.inputs))
self.print_error("using buckets:", [bucket.desc for bucket in buckets])
return tx
class CoinChooserClassic(CoinChooserBase):
'''
The classic electrum algorithm. Chooses coins starting with
the oldest that are sufficient to cover the spent amount, and
then removes any unneeded starting with the smallest in value.'''
class CoinChooserOldestFirst(CoinChooserBase):
'''The classic electrum algorithm. Chooses coins starting with the
oldest that are sufficient to cover the spent amount, and then
removes any unneeded starting with the smallest in value.'''
def keys(self, coins):
return [coin['prevout_hash'] + ':' + str(coin['prevout_n'])
for coin in coins]
def choose_buckets(self, buckets, spent_amount, fee, penalty_func):
def choose_buckets(self, buckets, sufficient_funds, penalty_func):
'''Spend the oldest buckets first.'''
# Unconfirmed coins are young, not old
adj_height = lambda height: 99999999 if height == 0 else height
buckets.sort(key = lambda b: max(adj_height(coin['height'])
for coin in b.coins))
selected, value, size = [], 0, 0
selected = []
for bucket in buckets:
selected.append(bucket)
value += bucket.value
size += bucket.size
if value >= spent_amount + fee(size):
break
if sufficient_funds(selected):
return strip_unneeded(selected, sufficient_funds)
else:
raise NotEnoughFunds()
# Remove unneeded inputs starting with the smallest.
selected.sort(key = lambda b: b.value)
dropped = []
for bucket in selected:
if value - bucket.value >= spent_amount + fee(size - bucket.size):
value -= bucket.value
size -= bucket.size
dropped.append(bucket)
return [bucket for bucket in selected if bucket not in dropped]
class CoinChooserRandom(CoinChooserBase):
def bucket_candidates(self, buckets, sufficient_funds):
@ -157,18 +168,11 @@ class CoinChooserRandom(CoinChooserBase):
else:
raise NotEnoughFunds()
return [[buckets[n] for n in candidate] for candidate in candidates]
candidates = [[buckets[n] for n in c] for c in candidates]
return [strip_unneeded(c, sufficient_funds) for c in candidates]
def choose_buckets(self, buckets, spent_amount, fee, penalty_func):
def sufficient(buckets):
'''Given a set of buckets, return True if it has enough
value to pay for the transaction'''
total_input = sum(bucket.value for bucket in buckets)
total_size = sum(bucket.size for bucket in buckets)
return total_input >= spent_amount + fee(total_size)
candidates = self.bucket_candidates(buckets, sufficient)
def choose_buckets(self, buckets, sufficient_funds, penalty_func):
candidates = self.bucket_candidates(buckets, sufficient_funds)
penalties = [penalty_func(cand) for cand in candidates]
winner = candidates[penalties.index(min(penalties))]
self.print_error("Bucket sets:", len(buckets))
@ -176,15 +180,19 @@ class CoinChooserRandom(CoinChooserBase):
return winner
class CoinChooserPrivacy(CoinChooserRandom):
'''
Attempts to better preserve user privacy. First, if any coin is
'''Attempts to better preserve user privacy. First, if any coin is
spent from a user address, all coins are. Compared to spending
from other addresses to make up an amount, this reduces
information leakage about sender holdings. It also helps to
reduce blockchain UTXO bloat, and reduce future privacy loss
that would come from reusing that address' remaining UTXOs.
Second, it penalizes change that is quite different to the sent
amount. Third, it penalizes change that is too big.'''
reduce blockchain UTXO bloat, and reduce future privacy loss that
would come from reusing that address' remaining UTXOs. Second, it
penalizes change that is quite different to the sent amount.
Third, it penalizes change that is too big. Fourth, it breaks
large change up into amounts comparable to the spent amount.
Finally, change is rounded to similar precision to sent amounts.
Extra change outputs and rounding might raise the transaction fee
slightly. Transaction priority might be less than if older coins
were chosen.'''
def keys(self, coins):
return [coin['address'] for coin in coins]
@ -213,5 +221,52 @@ class CoinChooserPrivacy(CoinChooserRandom):
return penalty
COIN_CHOOSERS = {'Classic': CoinChooserClassic,
def change_amounts(self, tx, count, fee_estimator, dust_threshold):
# Break change up if bigger than max_change
output_amounts = [o[2] for o in tx.outputs]
max_change = max(max(output_amounts) * 1.25, dust_threshold * 10)
# Use N change outputs
for n in range(1, count + 1):
# How much is left if we add this many change outputs?
change_amount = max(0, tx.get_fee() - fee_estimator(n))
if change_amount // n <= max_change:
break
# Get a handle on the precision of the output amounts; round our
# change to look similar
def trailing_zeroes(val):
s = str(val)
return len(s) - len(s.rstrip('0'))
zeroes = map(trailing_zeroes, output_amounts)
min_zeroes = min(zeroes)
max_zeroes = max(zeroes)
zeroes = range(max(0, min_zeroes - 1), (max_zeroes + 1) + 1)
# Calculate change; randomize it a bit if using more than 1 output
remaining = change_amount
amounts = []
while n > 1:
average = remaining // n
amount = randint(int(average * 0.7), int(average * 1.3))
precision = min(choice(zeroes), int(floor(log10(amount))))
amount = int(round(amount, -precision))
amounts.append(amount)
remaining -= amount
n -= 1
# Last change output. Round down to maximum precision but lose
# no more than 100 satoshis to fees (2dp)
N = pow(10, min(2, zeroes[0]))
amount = (remaining // N) * N
amounts.append(amount)
assert sum(amounts) <= change_amount
return amounts
COIN_CHOOSERS = {'Oldest First': CoinChooserOldestFirst,
'Privacy': CoinChooserPrivacy}

View File

@ -74,21 +74,22 @@ def command(s):
class Commands:
def __init__(self, config, wallet, network, callback = None):
def __init__(self, config, wallet, network, callback = None, password=None, new_password=None):
self.config = config
self.wallet = wallet
self.network = network
self._callback = callback
self.password = None
self._password = password
self.new_password = new_password
self.contacts = contacts.Contacts(self.config)
def _run(self, method, args, password_getter):
cmd = known_commands[method]
if cmd.requires_password and self.wallet.use_encryption:
self.password = apply(password_getter,())
self._password = apply(password_getter,())
f = getattr(self, method)
result = f(*args)
self.password = None
self._password = None
if self._callback:
apply(self._callback, ())
return result
@ -120,7 +121,9 @@ class Commands:
@command('wp')
def password(self):
"""Change wallet password. """
raise BaseException('Not a JSON-RPC command')
self.wallet.update_password(self._password, self.new_password)
self.wallet.storage.write()
return {'password':self.wallet.use_encryption}
@command('')
def getconfig(self, key):
@ -157,7 +160,7 @@ class Commands:
"""
return self.network.synchronous_get(('blockchain.address.get_history', [address]))
@command('nw')
@command('w')
def listunspent(self):
"""List unspent outputs. Returns the list of unspent transaction
outputs in your wallet."""
@ -200,7 +203,7 @@ class Commands:
outputs = map(lambda x: ('address', x[0], int(COIN*x[1])), outputs.items())
tx = Transaction.from_io(tx_inputs, outputs)
if not unsigned:
self.wallet.sign_transaction(tx, self.password)
self.wallet.sign_transaction(tx, self._password)
return tx.as_dict()
@command('wp')
@ -212,7 +215,7 @@ class Commands:
pubkey = bitcoin.public_key_from_private_key(privkey)
t.sign({pubkey:privkey})
else:
self.wallet.sign_transaction(t, self.password)
self.wallet.sign_transaction(t, self._password)
return t.as_dict()
@command('')
@ -250,7 +253,7 @@ class Commands:
"""Get private keys of addresses. You may pass a single wallet address, or a list of wallet addresses."""
is_list = type(address) is list
domain = address if is_list else [address]
out = [self.wallet.get_private_key(address, self.password) for address in domain]
out = [self.wallet.get_private_key(address, self._password) for address in domain]
return out if is_list else out[0]
@command('w')
@ -273,10 +276,9 @@ class Commands:
"""Return the public keys for a wallet address. """
return self.wallet.get_public_keys(address)
@command('nw')
@command('w')
def getbalance(self, account=None):
"""Return the balance of your wallet. If run with the --offline flag,
returns the last known balance."""
"""Return the balance of your wallet. """
if account is None:
c, u, x = self.wallet.get_balance()
else:
@ -334,19 +336,19 @@ class Commands:
@command('wp')
def getmasterprivate(self):
"""Get master private key. Return your wallet\'s master private key"""
return str(self.wallet.get_master_private_key(self.wallet.root_name, self.password))
return str(self.wallet.get_master_private_key(self.wallet.root_name, self._password))
@command('wp')
def getseed(self):
"""Get seed phrase. Print the generation seed of your wallet."""
s = self.wallet.get_mnemonic(self.password)
s = self.wallet.get_mnemonic(self._password)
return s.encode('utf8')
@command('wp')
def importprivkey(self, privkey):
"""Import a private key. """
try:
addr = self.wallet.import_key(privkey, self.password)
addr = self.wallet.import_key(privkey, self._password)
out = "Keypair imported: " + addr
except Exception as e:
out = "Error: " + str(e)
@ -377,7 +379,7 @@ class Commands:
def signmessage(self, address, message):
"""Sign a message with a key. Use quotes if your message contains
whitespaces"""
sig = self.wallet.sign_message(address, message, self.password)
sig = self.wallet.sign_message(address, message, self._password)
return base64.b64encode(sig)
@command('')
@ -415,24 +417,24 @@ class Commands:
tx = self.wallet.make_unsigned_transaction(coins, final_outputs, self.config, fee, change_addr)
str(tx) #this serializes
if not unsigned:
self.wallet.sign_transaction(tx, self.password)
self.wallet.sign_transaction(tx, self._password)
return tx
@command('wpn')
@command('wp')
def payto(self, destination, amount, tx_fee=None, from_addr=None, change_addr=None, nocheck=False, unsigned=False):
"""Create a transaction. """
domain = [from_addr] if from_addr else None
tx = self._mktx([(destination, amount)], tx_fee, change_addr, domain, nocheck, unsigned)
return tx.as_dict()
@command('wpn')
@command('wp')
def paytomany(self, outputs, tx_fee=None, from_addr=None, change_addr=None, nocheck=False, unsigned=False):
"""Create a multi-output transaction. """
domain = [from_addr] if from_addr else None
tx = self._mktx(outputs, tx_fee, change_addr, domain, nocheck, unsigned)
return tx.as_dict()
@command('wn')
@command('w')
def history(self):
"""Wallet history. Returns the transaction history of your wallet."""
balance = 0
@ -443,7 +445,7 @@ class Commands:
time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
except Exception:
time_str = "----"
label, is_default_label = self.wallet.get_label(tx_hash)
label = self.wallet.get_label(tx_hash)
out.append({
'txid':tx_hash,
'timestamp':timestamp,
@ -502,7 +504,7 @@ class Commands:
out.append(item)
return out
@command('nw')
@command('w')
def gettransaction(self, txid):
"""Retrieve a transaction. """
tx = self.wallet.transactions.get(txid) if self.wallet else None
@ -522,7 +524,7 @@ class Commands:
@command('wp')
def decrypt(self, pubkey, encrypted):
"""Decrypt a message encrypted with a public key."""
return self.wallet.decrypt_message(pubkey, encrypted, self.password)
return self.wallet.decrypt_message(pubkey, encrypted, self._password)
def _format_request(self, out):
pr_str = {
@ -535,7 +537,7 @@ class Commands:
out['status'] = pr_str[out.get('status', PR_UNKNOWN)]
return out
@command('wn')
@command('w')
def getrequest(self, key):
"""Return a payment request"""
r = self.wallet.get_payment_request(key, self.config)
@ -548,7 +550,7 @@ class Commands:
# """<Not implemented>"""
# pass
@command('wn')
@command('w')
def listrequests(self, pending=False, expired=False, paid=False):
"""List the payment requests you made."""
out = self.wallet.get_sorted_requests(self.config)
@ -565,7 +567,7 @@ class Commands:
return map(self._format_request, out)
@command('w')
def addrequest(self, requested_amount, memo='', expiration=60*60, force=False):
def addrequest(self, amount, memo='', expiration=60*60, force=False):
"""Create a payment request."""
addr = self.wallet.get_unused_address(None)
if addr is None:
@ -573,7 +575,7 @@ class Commands:
addr = self.wallet.create_new_address(None, False)
else:
return False
amount = int(Decimal(requested_amount)*COIN)
amount = int(COIN*Decimal(amount))
expiration = int(expiration)
req = self.wallet.make_payment_request(addr, amount, memo, expiration)
self.wallet.add_payment_request(req, self.config)
@ -587,7 +589,7 @@ class Commands:
if not alias:
raise BaseException('No alias in your configuration')
alias_addr = self.contacts.resolve(alias)['address']
self.wallet.sign_payment_request(address, alias, alias_addr, self.password)
self.wallet.sign_payment_request(address, alias, alias_addr, self._password)
@command('w')
def rmrequest(self, address):
@ -664,15 +666,18 @@ command_options = {
}
# don't use floats because of rounding errors
json_loads = lambda x: json.loads(x, parse_float=lambda x: str(Decimal(x)))
arg_types = {
'num':int,
'nbits':int,
'entropy':long,
'pubkeys': json.loads,
'inputs': json.loads,
'outputs': json.loads,
'tx_fee': lambda x: float(x) if x is not None else None,
'amount': lambda x: float(x) if x!='!' else '!',
'num': int,
'nbits': int,
'entropy': long,
'tx': json_loads,
'pubkeys': json_loads,
'inputs': json_loads,
'outputs': json_loads,
'tx_fee': lambda x: str(Decimal(x)) if x is not None else None,
'amount': lambda x: str(Decimal(x)) if x!='!' else '!',
}
config_variables = {
@ -728,7 +733,6 @@ def get_parser():
group.add_argument("-v", "--verbose", action="store_true", dest="verbose", default=False, help="Show debugging information")
group.add_argument("-P", "--portable", action="store_true", dest="portable", default=False, help="Use local 'electrum_data' directory")
group.add_argument("-w", "--wallet", dest="wallet_path", help="wallet path")
group.add_argument("-o", "--offline", action="store_true", dest="offline", default=False, help="Run offline")
# create main parser
parser = argparse.ArgumentParser(
parents=[parent_parser],
@ -739,6 +743,7 @@ def get_parser():
parser_gui.add_argument("url", nargs='?', default=None, help="bitcoin URI (or bip70 file)")
#parser_gui.set_defaults(func=run_gui)
parser_gui.add_argument("-g", "--gui", dest="gui", help="select graphical user interface", choices=['qt', 'lite', 'gtk', 'kivy', 'text', 'stdio', 'jsonrpc'])
parser_gui.add_argument("-o", "--offline", action="store_true", dest="offline", default=False, help="Run offline")
parser_gui.add_argument("-m", action="store_true", dest="hide_gui", default=False, help="hide GUI on startup")
parser_gui.add_argument("-L", "--lang", dest="language", default=None, help="default language used in GUI")
add_network_options(parser_gui)

View File

@ -103,7 +103,7 @@ class Daemon(DaemonThread):
'nodes': self.network.get_interfaces(),
'connected': self.network.is_connected(),
'auto_connect': p[4],
'wallets': self.wallets.keys(),
'wallets': dict([ (k, w.is_up_to_date()) for k, w in self.wallets.items()]),
}
elif sub == 'stop':
self.stop()
@ -135,21 +135,19 @@ class Daemon(DaemonThread):
return wallet
def run_cmdline(self, config_options):
password = config_options.get('password')
config = SimpleConfig(config_options)
cmdname = config.get('cmd')
cmd = known_commands[cmdname]
wallet = self.load_wallet(config) if cmd.requires_wallet else None
if wallet:
wallet.wait_until_synchronized()
# arguments passed to function
args = map(lambda x: config.get(x), cmd.params)
# decode json arguments
args = map(json_decode, args)
# options
args += map(lambda x: config.get(x), cmd.options)
cmd_runner = Commands(config, wallet, self.network)
cmd_runner.password = password
cmd_runner = Commands(config, wallet, self.network,
password=config_options.get('password'),
new_password=config_options.get('new_password'))
func = getattr(cmd_runner, cmd.name)
result = func(*args)
return result

View File

@ -12,7 +12,7 @@ proc = None
def scan_qr(config):
global proc
if not zbar:
raise BaseException("\n".join([_("Cannot start QR scanner."),_("The zbar package is not available."),_("On Linux, try 'sudo pip install zbar'")]))
raise RuntimeError("\n".join([_("Cannot start QR scanner."),_("The zbar package is not available."),_("On Linux, try 'sudo pip install zbar'")]))
if proc is None:
device = config.get("video_device", "default")
if device == 'default':

View File

@ -179,5 +179,5 @@ class Synchronizer(ThreadJob):
if up_to_date != self.wallet.is_up_to_date():
self.wallet.set_up_to_date(up_to_date)
if up_to_date:
self.wallet.save_transactions()
self.wallet.save_transactions(write=True)
self.network.trigger_callback('updated')

View File

@ -57,7 +57,7 @@ class TestWalletStorage(WalletTestCase):
some_dict = {"a":"b", "c":"d"}
for key, value in some_dict.items():
storage.put(key, value, False)
storage.put(key, value)
storage.write()
contents = ""

View File

@ -10,6 +10,8 @@ import urllib
import threading
from i18n import _
base_units = {'BTC':8, 'mBTC':5, 'uBTC':2}
def normalize_version(v):
return [int(x) for x in re.sub(r'(\.0+)*$','', v).split(".")]
@ -147,20 +149,20 @@ def json_encode(obj):
def json_decode(x):
try:
return json.loads(x)
return json.loads(x, parse_float=decimal.Decimal)
except:
return x
# decorator that prints execution time
def profiler(func):
def do_profile(func, args):
def do_profile(func, args, kw_args):
n = func.func_name
t0 = time.time()
o = apply(func, args)
o = func(*args, **kw_args)
t = time.time() - t0
print_error("[profiler]", n, "%.4f"%t)
return o
return lambda *args: do_profile(func, args)
return lambda *args, **kw_args: do_profile(func, args, kw_args)
@ -317,7 +319,7 @@ def block_explorer_URL(config, kind, item):
#_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_URI(uri):
def parse_URI(uri, on_pr=None):
import bitcoin
from bitcoin import COIN
@ -364,6 +366,22 @@ def parse_URI(uri):
if 'sig' in out:
out['sig'] = bitcoin.base_decode(out['sig'], None, base=58).encode('hex')
r = out.get('r')
sig = out.get('sig')
name = out.get('name')
if r or (name and sig):
def get_payment_request_thread():
import paymentrequest as pr
if name and sig:
s = pr.serialize_request(out).SerializeToString()
request = pr.PaymentRequest(s)
else:
request = pr.get_payment_request(r)
on_pr(request)
t = threading.Thread(target=get_payment_request_thread)
t.setDaemon(True)
t.start()
return out
@ -561,13 +579,14 @@ class StoreDict(dict):
def check_www_dir(rdir):
# rewrite index.html every time
import urllib, urlparse, shutil, os
if not os.path.exists(rdir):
os.mkdir(rdir)
index = os.path.join(rdir, 'index.html')
src = os.path.join(os.path.dirname(__file__), 'www', 'index.html')
shutil.copy(src, index)
if not os.path.exists(index):
print_error("copying index.html")
src = os.path.join(os.path.dirname(__file__), 'www', 'index.html')
shutil.copy(src, index)
files = [
"https://code.jquery.com/jquery-1.9.1.min.js",
"https://raw.githubusercontent.com/davidshimjs/qrcodejs/master/qrcode.js",

View File

@ -26,7 +26,7 @@ import json
import copy
from functools import partial
from util import PrintError, profiler
from util import NotEnoughFunds, PrintError, profiler
from bitcoin import *
from account import *
@ -69,11 +69,11 @@ class WalletStorage(PrintError):
except:
try:
d = ast.literal_eval(data) #parse raw data from reading wallet file
labels = d.get('labels', {})
except Exception as e:
raise IOError("Cannot read wallet file '%s'" % self.path)
self.data = {}
# In old versions of Electrum labels were latin1 encoded, this fixes breakage.
labels = d.get('labels', {})
for i, label in labels.items():
try:
unicode(label)
@ -98,7 +98,7 @@ class WalletStorage(PrintError):
v = copy.deepcopy(v)
return v
def put(self, key, value, save = True):
def put(self, key, value):
try:
json.dumps(key)
json.dumps(value)
@ -113,8 +113,6 @@ class WalletStorage(PrintError):
elif key in self.data:
self.modified = True
self.data.pop(key)
if save:
self.write()
def write(self):
if threading.currentThread().isDaemon():
@ -142,7 +140,7 @@ class WalletStorage(PrintError):
import stat
os.chmod(self.path, mode)
self.print_error("saved", self.path)
self.modified = False
class Abstract_Wallet(PrintError):
@ -199,7 +197,7 @@ class Abstract_Wallet(PrintError):
# save wallet type the first time
if self.storage.get('wallet_type') is None:
self.storage.put('wallet_type', self.wallet_type, True)
self.storage.put('wallet_type', self.wallet_type)
def diagnostic_name(self):
return self.basename()
@ -220,17 +218,18 @@ class Abstract_Wallet(PrintError):
self.transactions.pop(tx_hash)
@profiler
def save_transactions(self):
def save_transactions(self, write=False):
with self.transaction_lock:
tx = {}
for k,v in self.transactions.items():
tx[k] = str(v)
# Flush storage only with the last put
self.storage.put('transactions', tx, False)
self.storage.put('txi', self.txi, False)
self.storage.put('txo', self.txo, False)
self.storage.put('pruned_txo', self.pruned_txo, False)
self.storage.put('addr_history', self.history, True)
self.storage.put('transactions', tx)
self.storage.put('txi', self.txi)
self.storage.put('txo', self.txo)
self.storage.put('pruned_txo', self.pruned_txo)
self.storage.put('addr_history', self.history)
if write:
self.storage.write()
def clear_history(self):
with self.transaction_lock:
@ -376,7 +375,7 @@ class Abstract_Wallet(PrintError):
if changed:
run_hook('set_label', self, name, text)
self.storage.put('labels', self.labels, True)
self.storage.put('labels', self.labels)
return changed
@ -436,7 +435,7 @@ class Abstract_Wallet(PrintError):
self.unverified_tx.pop(tx_hash, None)
with self.lock:
self.verified_tx[tx_hash] = info # (tx_height, timestamp, pos)
self.storage.put('verified_tx3', self.verified_tx, True)
self.storage.put('verified_tx3', self.verified_tx)
conf, timestamp = self.get_confirmations(tx_hash)
self.network.trigger_callback('verified', tx_hash, conf, timestamp)
@ -867,24 +866,9 @@ class Abstract_Wallet(PrintError):
return h2
def get_label(self, tx_hash):
label = self.labels.get(tx_hash)
is_default = (label == '') or (label is None)
if is_default:
label = self.get_default_label(tx_hash)
return label, is_default
def get_default_label(self, tx_hash):
if self.txi.get(tx_hash) == {}:
d = self.txo.get(tx_hash, {})
labels = []
for addr in d.keys():
label = self.labels.get(addr)
if label:
labels.append(label)
return ', '.join(labels)
return ''
label = self.labels.get(tx_hash, '')
return label
def fee_per_kb(self, config):
b = config.get('dynamic_fees')
@ -899,7 +883,7 @@ class Abstract_Wallet(PrintError):
def coin_chooser_name(self, config):
kind = config.get('coin_chooser')
if not kind in COIN_CHOOSERS:
kind = 'Classic'
kind = 'Oldest First'
return kind
def coin_chooser(self, config):
@ -912,6 +896,10 @@ class Abstract_Wallet(PrintError):
if type == 'address':
assert is_address(data), "Address " + data + " is invalid!"
# Avoid index-out-of-range with coins[0] below
if not coins:
raise NotEnoughFunds()
for item in coins:
self.add_input_info(item)
@ -1044,7 +1032,7 @@ class Abstract_Wallet(PrintError):
if self.has_seed():
decoded = self.get_seed(old_password)
self.seed = pw_encode( decoded, new_password)
self.storage.put('seed', self.seed, True)
self.storage.put('seed', self.seed)
imported_account = self.accounts.get(IMPORTED_ACCOUNT)
if imported_account:
@ -1056,10 +1044,10 @@ class Abstract_Wallet(PrintError):
b = pw_decode(v, old_password)
c = pw_encode(b, new_password)
self.master_private_keys[k] = c
self.storage.put('master_private_keys', self.master_private_keys, True)
self.storage.put('master_private_keys', self.master_private_keys)
self.use_encryption = (new_password != None)
self.storage.put('use_encryption', self.use_encryption,True)
self.storage.put('use_encryption', self.use_encryption)
def is_frozen(self, addr):
return addr in self.frozen_addresses
@ -1071,7 +1059,7 @@ class Abstract_Wallet(PrintError):
self.frozen_addresses |= set(addrs)
else:
self.frozen_addresses -= set(addrs)
self.storage.put('frozen_addresses', list(self.frozen_addresses), True)
self.storage.put('frozen_addresses', list(self.frozen_addresses))
return True
return False
@ -1109,7 +1097,8 @@ class Abstract_Wallet(PrintError):
self.verifier = None
# Now no references to the syncronizer or verifier
# remain so they will be GC-ed
self.storage.put('stored_height', self.get_local_height(), True)
self.storage.put('stored_height', self.get_local_height())
self.storage.write()
def wait_until_synchronized(self, callback=None):
from i18n import _
@ -1147,7 +1136,7 @@ class Abstract_Wallet(PrintError):
d = {}
for k, v in self.accounts.items():
d[k] = v.dump()
self.storage.put('accounts', d, True)
self.storage.put('accounts', d)
def can_import(self):
return not self.is_watching_only()
@ -1431,9 +1420,9 @@ class Deterministic_Wallet(Abstract_Wallet):
else:
self.use_encryption = False
self.storage.put('seed', self.seed, False)
self.storage.put('seed_version', self.seed_version, False)
self.storage.put('use_encryption', self.use_encryption,True)
self.storage.put('seed', self.seed)
self.storage.put('seed_version', self.seed_version)
self.storage.put('use_encryption', self.use_encryption)
def get_seed(self, password):
return pw_decode(self.seed, password)
@ -1445,7 +1434,7 @@ class Deterministic_Wallet(Abstract_Wallet):
assert isinstance(value, int), 'gap limit must be of type int, not of %s'%type(value)
if value >= self.gap_limit:
self.gap_limit = value
self.storage.put('gap_limit', self.gap_limit, True)
self.storage.put('gap_limit', self.gap_limit)
return True
elif value >= self.min_acceptable_gap():
@ -1456,7 +1445,7 @@ class Deterministic_Wallet(Abstract_Wallet):
account.receiving_pubkeys = account.receiving_pubkeys[0:n]
account.receiving_addresses = account.receiving_addresses[0:n]
self.gap_limit = value
self.storage.put('gap_limit', self.gap_limit, True)
self.storage.put('gap_limit', self.gap_limit)
self.save_accounts()
return True
else:
@ -1579,11 +1568,11 @@ class BIP32_Wallet(Deterministic_Wallet):
if xpub in self.master_public_keys.values():
raise BaseException('Duplicate master public key')
self.master_public_keys[name] = xpub
self.storage.put('master_public_keys', self.master_public_keys, True)
self.storage.put('master_public_keys', self.master_public_keys)
def add_master_private_key(self, name, xpriv, password):
self.master_private_keys[name] = pw_encode(xpriv, password)
self.storage.put('master_private_keys', self.master_private_keys, True)
self.storage.put('master_private_keys', self.master_private_keys)
def derive_xkeys(self, root, derivation, password):
x = self.master_private_keys[root]
@ -1626,16 +1615,16 @@ class BIP32_Simple_Wallet(BIP32_Wallet):
def create_xprv_wallet(self, xprv, password):
xpub = bitcoin.xpub_from_xprv(xprv)
account = BIP32_Account({'xpub':xpub})
self.storage.put('seed_version', self.seed_version, True)
self.storage.put('seed_version', self.seed_version)
self.add_master_private_key(self.root_name, xprv, password)
self.add_master_public_key(self.root_name, xpub)
self.add_account('0', account)
self.use_encryption = (password != None)
self.storage.put('use_encryption', self.use_encryption,True)
self.storage.put('use_encryption', self.use_encryption)
def create_xpub_wallet(self, xpub):
account = BIP32_Account({'xpub':xpub})
self.storage.put('seed_version', self.seed_version, True)
self.storage.put('seed_version', self.seed_version)
self.add_master_public_key(self.root_name, xpub)
self.add_account('0', account)
@ -1834,7 +1823,7 @@ class OldWallet(Deterministic_Wallet):
def create_master_keys(self, password):
seed = self.get_seed(password)
mpk = OldAccount.mpk_from_seed(seed)
self.storage.put('master_public_key', mpk, True)
self.storage.put('master_public_key', mpk)
def get_master_public_key(self):
return self.storage.get("master_public_key")
@ -1852,8 +1841,8 @@ class OldWallet(Deterministic_Wallet):
def create_watching_only_wallet(self, mpk):
self.seed_version = OLD_SEED_VERSION
self.storage.put('seed_version', self.seed_version, False)
self.storage.put('master_public_key', mpk, True)
self.storage.put('seed_version', self.seed_version)
self.storage.put('master_public_key', mpk)
self.create_account(mpk)
def get_seed(self, password):
@ -2037,7 +2026,7 @@ class Wallet(object):
@classmethod
def from_multisig(klass, key_list, password, storage, wallet_type):
storage.put('wallet_type', wallet_type, True)
storage.put('wallet_type', wallet_type)
self = Multisig_Wallet(storage)
key_list = sorted(key_list, key = lambda x: klass.is_xpub(x))
for i, text in enumerate(key_list):
@ -2056,7 +2045,7 @@ class Wallet(object):
else:
self.add_cosigner_seed(text, name, password)
self.use_encryption = (password != None)
self.storage.put('use_encryption', self.use_encryption, True)
self.storage.put('use_encryption', self.use_encryption)
self.create_main_account(password)
return self

View File

@ -1,18 +1,18 @@
from electrum.plugins import BasePlugin, hook
from electrum_gui.qt.util import WaitingDialog, EnterButton
from electrum.util import print_msg, print_error
from electrum.i18n import _
from PyQt4.QtGui import *
from PyQt4.QtCore import *
import traceback
from functools import partial
import zlib
import json
from io import BytesIO
import sys
import platform
from electrum.plugins import BasePlugin, hook
from electrum_gui.qt.util import WaitingDialog, EnterButton, WindowModalDialog
from electrum.util import print_msg, print_error
from electrum.i18n import _
from PyQt4.QtGui import *
from PyQt4.QtCore import *
try:
import amodem.audio
import amodem.main
@ -42,11 +42,10 @@ class Plugin(BasePlugin):
return True
def settings_widget(self, window):
return EnterButton(_('Settings'), self.settings_dialog)
return EnterButton(_('Settings'), partial(self.settings_dialog, window))
def settings_dialog(self):
d = QDialog()
d.setWindowTitle("Settings")
def settings_dialog(self, window):
d = WindowModalDialog(window, _("Audio Modem Settings"))
layout = QGridLayout(d)
layout.addWidget(QLabel(_('Bit rate [kbps]: ')), 0, 0)
@ -75,24 +74,20 @@ class Plugin(BasePlugin):
def handler():
blob = json.dumps(dialog.tx.as_dict())
self.sender = self._send(parent=dialog, blob=blob)
self.sender.start()
self._send(parent=dialog, blob=blob)
b.clicked.connect(handler)
dialog.sharing_buttons.insert(-1, b)
@hook
def scan_text_edit(self, parent):
def handler():
self.receiver = self._recv(parent=parent)
self.receiver.start()
parent.addButton(':icons/microphone.png', handler, _("Read from microphone"))
parent.addButton(':icons/microphone.png', partial(self._recv, parent),
_("Read from microphone"))
@hook
def show_text_edit(self, parent):
def handler():
blob = str(parent.toPlainText())
self.sender = self._send(parent=parent, blob=blob)
self.sender.start()
self._send(parent=parent, blob=blob)
parent.addButton(':icons/speaker.png', handler, _("Send to speaker"))
def _audio_interface(self):
@ -101,31 +96,25 @@ class Plugin(BasePlugin):
def _send(self, parent, blob):
def sender_thread():
try:
with self._audio_interface() as interface:
src = BytesIO(blob)
dst = interface.player()
amodem.main.send(config=self.modem_config, src=src, dst=dst)
except Exception:
traceback.print_exc()
with self._audio_interface() as interface:
src = BytesIO(blob)
dst = interface.player()
amodem.main.send(config=self.modem_config, src=src, dst=dst)
print_msg('Sending:', repr(blob))
blob = zlib.compress(blob)
kbps = self.modem_config.modem_bps / 1e3
msg = 'Sending to Audio MODEM ({0:.1f} kbps)...'.format(kbps)
return WaitingDialog(parent=parent, message=msg, run_task=sender_thread)
WaitingDialog(parent, msg, sender_thread)
def _recv(self, parent):
def receiver_thread():
try:
with self._audio_interface() as interface:
src = interface.recorder()
dst = BytesIO()
amodem.main.recv(config=self.modem_config, src=src, dst=dst)
return dst.getvalue()
except Exception:
traceback.print_exc()
with self._audio_interface() as interface:
src = interface.recorder()
dst = BytesIO()
amodem.main.recv(config=self.modem_config, src=src, dst=dst)
return dst.getvalue()
def on_success(blob):
if blob:
@ -135,5 +124,4 @@ class Plugin(BasePlugin):
kbps = self.modem_config.modem_bps / 1e3
msg = 'Receiving from Audio MODEM ({0:.1f} kbps)...'.format(kbps)
return WaitingDialog(parent=parent, message=msg,
run_task=receiver_thread, on_success=on_success)
WaitingDialog(parent, msg, receiver_thread, on_success=on_success)

View File

@ -18,12 +18,10 @@
from __future__ import absolute_import
import socket
import time
import threading
import base64
from decimal import Decimal
from Queue import Queue
from functools import partial
import smtplib
import imaplib
@ -37,12 +35,11 @@ from PyQt4.QtCore import *
import PyQt4.QtCore as QtCore
import PyQt4.QtGui as QtGui
from electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_EXPIRED
from electrum.plugins import BasePlugin, hook
from electrum import util
from electrum.paymentrequest import PaymentRequest
from electrum.i18n import _
from electrum_gui.qt.util import text_dialog, EnterButton
from electrum_gui.qt.util import EnterButton, Buttons, CloseButton
from electrum_gui.qt.util import OkButton, WindowModalDialog
@ -166,14 +163,10 @@ class Plugin(BasePlugin):
return True
def settings_widget(self, window):
self.settings_window = window
return EnterButton(_('Settings'), self.settings_dialog)
return EnterButton(_('Settings'), partial(self.settings_dialog, window))
def settings_dialog(self, x):
from electrum_gui.qt.util import Buttons, CloseButton, OkButton
d = QDialog(self.settings_window)
d.setWindowTitle("Email settings")
def settings_dialog(self, window):
d = WindowModalDialog(window, _("Email settings"))
d.setMinimumSize(500, 200)
vbox = QVBoxLayout(d)

View File

@ -346,13 +346,18 @@ class FxPlugin(BasePlugin, ThreadJob):
return _("No data")
@hook
def historical_value_str(self, satoshis, d_t):
def history_rate(self, d_t):
rate = self.exchange.historical_rate(self.ccy, d_t)
# Frequently there is no rate for today, until tomorrow :)
# Use spot quotes in that case
if rate is None and (datetime.today().date() - d_t.date()).days <= 2:
rate = self.exchange.quotes.get(self.ccy)
self.history_used_spot = True
return rate
@hook
def historical_value_str(self, satoshis, d_t):
rate = self.history_rate(d_t)
return self.value_str(satoshis, rate)
@hook

View File

@ -128,11 +128,10 @@ class Plugin(FxPlugin):
window.connect(window.app, SIGNAL('refresh_headers'), window.history_list.refresh_headers)
def settings_widget(self, window):
return EnterButton(_('Settings'), self.settings_dialog)
return EnterButton(_('Settings'), partial(self.settings_dialog, window))
def settings_dialog(self):
d = QDialog()
d.setWindowTitle("Settings")
def settings_dialog(self, window):
d = WindowModalDialog(window, _("Exchange Rate Settings"))
layout = QGridLayout(d)
layout.addWidget(QLabel(_('Exchange rate API: ')), 0, 0)
layout.addWidget(QLabel(_('Currency: ')), 1, 0)

View File

@ -21,7 +21,7 @@ import urllib
import sys
import requests
from PyQt4.QtGui import QMessageBox, QApplication, QPushButton
from PyQt4.QtGui import QApplication, QPushButton
from electrum.plugins import BasePlugin, hook
from electrum.i18n import _
@ -65,7 +65,7 @@ class Plugin(BasePlugin):
'to verify that transaction is instant.\n'
'Please enter your password to sign a\n'
'verification request.')
password = window.password_dialog(msg)
password = window.password_dialog(msg, parent=d)
if not password:
return
try:
@ -84,14 +84,12 @@ class Plugin(BasePlugin):
# 3. display the result
if response.get('verified'):
QMessageBox.information(None, _('Verification successful!'),
_('%s is covered by GreenAddress instant confirmation') % (tx.hash()), _('OK'))
d.show_message(_('%s is covered by GreenAddress instant confirmation') % (tx.hash()), title=_('Verification successful!'))
else:
QMessageBox.critical(None, _('Verification failed!'),
_('%s is not covered by GreenAddress instant confirmation') % (tx.hash()), _('OK'))
d.show_critical(_('%s is not covered by GreenAddress instant confirmation') % (tx.hash()), title=_('Verification failed!'))
except BaseException as e:
import traceback
traceback.print_exc(file=sys.stdout)
QMessageBox.information(None, _('Error'), str(e), _('OK'))
d.show_error(str(e))
finally:
d.verify_button.setText(self.button_label)

View File

@ -1,4 +1,4 @@
from PyQt4.Qt import QMessageBox, QDialog, QVBoxLayout, QLabel, QThread, SIGNAL, QGridLayout, QInputDialog, QPushButton
from PyQt4.Qt import QVBoxLayout, QLabel, SIGNAL, QGridLayout, QInputDialog, QPushButton
import PyQt4.QtCore as QtCore
from electrum_gui.qt.util import *
from electrum_gui.qt.main_window import StatusBarButton, ElectrumWindow
@ -26,11 +26,11 @@ class Plugin(KeepKeyPlugin):
try:
self.get_client().ping('t')
except BaseException as e:
QMessageBox.information(window, _('Error'), _("KeepKey device not detected.\nContinuing in watching-only mode." + '\n\nReason:\n' + str(e)), _('OK'))
window.show_error(_('KeepKey device not detected.\nContinuing in watching-only mode.\nReason:\n' + str(e)))
self.wallet.force_watching_only = True
return
if self.wallet.addresses() and not self.wallet.check_proper_device():
QMessageBox.information(window, _('Error'), _("This wallet does not match your KeepKey device"), _('OK'))
window.show_error(_("This wallet does not match your KeepKey device"))
self.wallet.force_watching_only = True
@hook
@ -73,7 +73,7 @@ class Plugin(KeepKeyPlugin):
return
get_label = lambda: self.get_client().features.label
update_label = lambda: current_label_label.setText("Label: %s" % get_label())
d = QDialog()
d = WindowModalDialog(window, _("KeepKey Settings"))
layout = QGridLayout(d)
layout.addWidget(QLabel("KeepKey Options"),0,0)
layout.addWidget(QLabel("ID:"),1,0)
@ -132,10 +132,7 @@ class KeepKeyQtHandler:
return self.passphrase
def pin_dialog(self):
d = QDialog(None)
d.setModal(1)
d.setWindowTitle(_("Enter PIN"))
d.setWindowFlags(d.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
d = WindowModalDialog(self.win, _("Enter PIN"))
matrix = PinMatrixWidget()
vbox = QVBoxLayout()
vbox.addWidget(QLabel(self.message))
@ -153,23 +150,18 @@ class KeepKeyQtHandler:
self.passphrase = unicodedata.normalize('NFKD', unicode(passphrase)) if passphrase else ''
else:
assert type(self.win) is InstallWizard
from electrum_gui.qt.password_dialog import make_password_dialog, run_password_dialog
d = QDialog()
d.setModal(1)
d.setLayout(make_password_dialog(d, None, self.message, False))
confirmed, p, passphrase = run_password_dialog(d, None, None)
from electrum_gui.qt.password_dialog import PasswordDialog
d = PasswordDialog(self.win, None, None, self.message, False)
confirmed, p, passphrase = d.run()
if not confirmed:
QMessageBox.critical(None, _('Error'), _("Password request canceled"), _('OK'))
self.win.show_critical(_("Password request canceled"))
self.passphrase = None
else:
self.passphrase = unicodedata.normalize('NFKD', unicode(passphrase)) if passphrase else ''
self.done.set()
def message_dialog(self):
self.d = QDialog()
self.d.setModal(1)
self.d.setWindowTitle('Please Check KeepKey Device')
self.d.setWindowFlags(self.d.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
self.d = WindowModalDialog(self.win, _('Please Check KeepKey Device'))
l = QLabel(self.message)
vbox = QVBoxLayout(self.d)
vbox.addWidget(l)
@ -182,5 +174,3 @@ class KeepKeyQtHandler:
def dialog_stop(self):
self.d.hide()

View File

@ -42,9 +42,9 @@ class LabelsPlugin(BasePlugin):
self.set_nonce(wallet, nonce)
return nonce
def set_nonce(self, wallet, nonce, force_write=True):
def set_nonce(self, wallet, nonce):
self.print_error("set", wallet.basename(), "nonce to", nonce)
wallet.storage.put("wallet_nonce", nonce, force_write)
wallet.storage.put("wallet_nonce", nonce)
@hook
def set_label(self, wallet, item, label):
@ -61,7 +61,7 @@ class LabelsPlugin(BasePlugin):
t.setDaemon(True)
t.start()
# Caller will write the wallet
self.set_nonce(wallet, nonce + 1, force_write=False)
self.set_nonce(wallet, nonce + 1)
def do_request(self, method, url = "/labels", is_batch=False, data=None):
url = 'https://' + self.target_host + url
@ -125,8 +125,8 @@ class LabelsPlugin(BasePlugin):
self.print_error("received %d labels" % len(response))
# do not write to disk because we're in a daemon thread
wallet.storage.put('labels', wallet.labels, False)
self.set_nonce(wallet, response["nonce"] + 1, False)
wallet.storage.put('labels', wallet.labels)
self.set_nonce(wallet, response["nonce"] + 1)
self.on_pulled(wallet)
except Exception as e:

View File

@ -6,7 +6,8 @@ from PyQt4.QtCore import *
from electrum.plugins import hook
from electrum.i18n import _
from electrum_gui.qt import EnterButton
from electrum_gui.qt.util import ThreadedButton, Buttons, CancelButton, OkButton
from electrum_gui.qt.util import ThreadedButton, Buttons, CancelButton
from electrum_gui.qt.util import WindowModalDialog, OkButton
from labels import LabelsPlugin
@ -25,25 +26,23 @@ class Plugin(LabelsPlugin):
partial(self.settings_dialog, window))
def settings_dialog(self, window):
d = QDialog(window)
wallet = window.parent().wallet
d = WindowModalDialog(window, _("Label Settings"))
vbox = QVBoxLayout(d)
layout = QGridLayout()
vbox.addLayout(layout)
layout.addWidget(QLabel("Label sync options: "), 2, 0)
self.upload = ThreadedButton("Force upload",
partial(self.push_thread, window.wallet),
partial(self.push_thread, wallet),
self.done_processing)
layout.addWidget(self.upload, 2, 1)
self.download = ThreadedButton("Force download",
partial(self.pull_thread, window.wallet, True),
partial(self.pull_thread, wallet, True),
self.done_processing)
layout.addWidget(self.download, 2, 2)
self.accept = OkButton(d, _("Done"))
vbox.addLayout(Buttons(CancelButton(d), self.accept))
if d.exec_():
return True
else:
return False
return bool(d.exec_())
def on_pulled(self, wallet):
self.obj.emit(SIGNAL('labels_changed'), wallet)

View File

@ -1,7 +1,7 @@
from PyQt4.Qt import QApplication, QMessageBox, QDialog, QInputDialog, QLineEdit, QVBoxLayout, QLabel, QThread, SIGNAL
from PyQt4.Qt import QDialog, QInputDialog, QLineEdit, QVBoxLayout, QLabel, SIGNAL
import PyQt4.QtCore as QtCore
import threading
from electrum_gui.qt.password_dialog import make_password_dialog, run_password_dialog
from electrum.plugins import BasePlugin, hook
from ledger import LedgerPlugin
@ -16,10 +16,10 @@ class Plugin(LedgerPlugin):
self.handler = BTChipQTHandler(window)
if self.btchip_is_connected():
if not self.wallet.check_proper_device():
QMessageBox.information(window, _('Error'), _("This wallet does not match your Ledger device"), _('OK'))
window.show_error(_("This wallet does not match your Ledger device"))
self.wallet.force_watching_only = True
else:
QMessageBox.information(window, _('Error'), _("Ledger device not detected.\nContinuing in watching-only mode."), _('OK'))
window.show_error(_("Ledger device not detected.\nContinuing in watching-only mode."))
self.wallet.force_watching_only = True

View File

@ -1,4 +1,4 @@
from PyQt4.Qt import QMessageBox, QDialog, QVBoxLayout, QLabel, QThread, SIGNAL, QGridLayout, QInputDialog, QPushButton
from PyQt4.Qt import QVBoxLayout, QLabel, SIGNAL, QGridLayout, QInputDialog, QPushButton
import PyQt4.QtCore as QtCore
from electrum_gui.qt.util import *
from electrum_gui.qt.main_window import StatusBarButton, ElectrumWindow
@ -46,10 +46,7 @@ class TrezorQtHandler:
return self.passphrase
def pin_dialog(self):
d = QDialog(None)
d.setModal(1)
d.setWindowTitle(_("Enter PIN"))
d.setWindowFlags(d.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
d = WindowModalDialog(self.win, _("Enter PIN"))
matrix = PinMatrixWidget()
vbox = QVBoxLayout()
vbox.addWidget(QLabel(self.message))
@ -67,23 +64,18 @@ class TrezorQtHandler:
self.passphrase = unicodedata.normalize('NFKD', unicode(passphrase)) if passphrase else ''
else:
assert type(self.win) is InstallWizard
from electrum_gui.qt.password_dialog import make_password_dialog, run_password_dialog
d = QDialog()
d.setModal(1)
d.setLayout(make_password_dialog(d, None, self.message, False))
confirmed, p, passphrase = run_password_dialog(d, None, None)
from electrum_gui.qt.password_dialog import PasswordDialog
d = PasswordDialog(self.win, None, None, self.message, False)
confirmed, p, passphrase = d.run()
if not confirmed:
QMessageBox.critical(None, _('Error'), _("Password request canceled"), _('OK'))
self.win.show_critical(_("Password request canceled"))
self.passphrase = None
else:
self.passphrase = unicodedata.normalize('NFKD', unicode(passphrase)) if passphrase else ''
self.done.set()
def message_dialog(self):
self.d = QDialog()
self.d.setModal(1)
self.d.setWindowTitle('Please Check Trezor Device')
self.d.setWindowFlags(self.d.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
self.d = WindowModalDialog(self.win, _('Please Check Trezor Device'))
l = QLabel(self.message)
vbox = QVBoxLayout(self.d)
vbox.addWidget(l)
@ -108,11 +100,11 @@ class Plugin(TrezorPlugin):
try:
self.get_client().ping('t')
except BaseException as e:
QMessageBox.information(window, _('Error'), _("Trezor device not detected.\nContinuing in watching-only mode." + '\n\nReason:\n' + str(e)), _('OK'))
window.show_error(_('Trezor device not detected.\nContinuing in watching-only mode.\nReason:\n' + str(e)))
self.wallet.force_watching_only = True
return
if self.wallet.addresses() and not self.wallet.check_proper_device():
QMessageBox.information(window, _('Error'), _("This wallet does not match your Trezor device"), _('OK'))
window.show_error(_("This wallet does not match your Trezor device"))
self.wallet.force_watching_only = True
@hook
@ -171,7 +163,7 @@ class Plugin(TrezorPlugin):
return
get_label = lambda: self.get_client().features.label
update_label = lambda: current_label_label.setText("Label: %s" % get_label())
d = QDialog()
d = WindowModalDialog(window, _("Trezor Settings"))
layout = QGridLayout(d)
layout.addWidget(QLabel("Trezor Options"),0,0)
layout.addWidget(QLabel("ID:"),1,0)
@ -194,7 +186,3 @@ class Plugin(TrezorPlugin):
layout.addWidget(current_label_label,3,0)
layout.addWidget(change_label_button,3,1)
d.exec_()

View File

@ -1,4 +1,23 @@
#!/usr/bin/env python
#
# Electrum - Lightweight Bitcoin Client
# Copyright (C) 2015 Thomas Voegtlin
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from functools import partial
from threading import Thread
from PyQt4.QtGui import *
from PyQt4.QtCore import *
@ -10,7 +29,19 @@ from electrum_gui.qt.main_window import StatusBarButton
from electrum.i18n import _
from electrum.plugins import hook
from trustedcoin import TrustedCoinPlugin
from trustedcoin import TrustedCoinPlugin, Wallet_2fa
def need_server(wallet, tx):
from electrum.account import BIP32_Account
# Detect if the server is needed
long_id, short_id = wallet.get_user_id()
xpub3 = wallet.master_public_keys['x3/']
for x in tx.inputs_to_sign():
if x[0:2] == 'ff':
xpub, sequence = BIP32_Account.parse_xpubkey(x)
if xpub == xpub3:
return True
return False
class Plugin(TrustedCoinPlugin):
@ -27,8 +58,7 @@ class Plugin(TrustedCoinPlugin):
t.start()
def auth_dialog(self, window):
d = QDialog(window)
d.setModal(1)
d = WindowModalDialog(window, _("Authorization"))
vbox = QVBoxLayout(d)
pw = AmountEdit(None, is_int = True)
msg = _('Please enter your Google Authenticator code')
@ -55,16 +85,18 @@ class Plugin(TrustedCoinPlugin):
self.print_error("twofactor: xpub3 not needed")
window.wallet.auth_code = auth_code
def waiting_dialog(self, window, on_success=None):
task = partial(self.request_billing_info, window.wallet)
return WaitingDialog(window, 'Getting billing information...', task,
on_success=on_success)
@hook
def abort_send(self, window):
wallet = window.wallet
if type(wallet) is Wallet_2fa and not wallet.can_sign_without_server():
if wallet.billing_info is None:
# request billing info before forming the transaction
task = partial(self.request_billing_info, wallet)
waiting_dialog = WaitingDialog(window, 'please wait...', task)
waiting_dialog.start()
waiting_dialog.wait()
waiting_dialog(self, window).wait()
if wallet.billing_info is None:
window.show_message('Could not contact server')
return True
@ -72,9 +104,8 @@ class Plugin(TrustedCoinPlugin):
def settings_dialog(self, window):
task = partial(self.request_billing_info, window.wallet)
self.waiting_dialog = WaitingDialog(window, 'please wait...', task, partial(self.show_settings_dialog, window))
self.waiting_dialog.start()
on_success = partial(self.show_settings_dialog, window)
self.waiting_dialog(window, on_success)
def show_settings_dialog(self, window, success):
if not success:
@ -82,8 +113,7 @@ class Plugin(TrustedCoinPlugin):
return
wallet = window.wallet
d = QDialog(window)
d.setWindowTitle("TrustedCoin Information")
d = WindowModalDialog(window, _("TrustedCoin Information"))
d.setMinimumSize(500, 200)
vbox = QVBoxLayout(d)
hbox = QHBoxLayout()
@ -238,7 +268,5 @@ class Plugin(TrustedCoinPlugin):
server.auth(_id, otp)
return True
except:
QMessageBox.information(window, _('Message'), _('Incorrect password'), _('OK'))
window.show_message(_('Incorrect password'))
pw.setText('')

View File

@ -16,7 +16,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from threading import Thread
import socket
import os
import re
@ -270,18 +269,6 @@ def make_billing_address(wallet, num):
address = public_key_to_bc_address( cK )
return address
def need_server(wallet, tx):
from electrum.account import BIP32_Account
# Detect if the server is needed
long_id, short_id = wallet.get_user_id()
xpub3 = wallet.master_public_keys['x3/']
for x in tx.inputs_to_sign():
if x[0:2] == 'ff':
xpub, sequence = BIP32_Account.parse_xpubkey(x)
if xpub == xpub3:
return True
return False
class TrustedCoinPlugin(BasePlugin):
@ -318,8 +305,8 @@ class TrustedCoinPlugin(BasePlugin):
return
password = window.password_dialog()
wallet.storage.put('seed_version', wallet.seed_version, True)
wallet.storage.put('use_encryption', password is not None, True)
wallet.storage.put('seed_version', wallet.seed_version)
wallet.storage.put('use_encryption', password is not None)
words = seed.split()
n = len(words)/2