You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

1095 lines
41 KiB

from functools import partial
import os, datetime, json, csv
from kivy.app import App
from kivy.animation import Animation
from kivy.core.clipboard import Clipboard
from kivy.clock import Clock
from kivy.factory import Factory
from kivy.metrics import dp
from kivy.properties import (ObjectProperty, StringProperty, ListProperty,
DictProperty)
from kivy.uix.button import Button
from kivy.uix.bubble import Bubble, BubbleButton
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.uix.screenmanager import Screen as Screen, ScreenManager
from kivy.uix.tabbedpanel import TabbedPanel
from electrum_gui.kivy.dialog import (NewContactDialog, TakeInputDialog,
PrivateKeyDialog, SignVerifyDialog, MessageBox, MessageBoxError,
SaveDialog, LoadDialog, InfoDialog, ImportPrivateKeysDialog, Dialog,
EditLabelDialog, EditDescriptionDialog, ShowMasterPublicKeyDialog,
RecentActivityDialog)
from electrum_gui.i18n import _, languages
from electrum_gui.kivy.menus import ContextMenu
from electrum.interface import DEFAULT_PORTS
from electrum.verifier import WalletVerifier
from electrum.wallet import Wallet, WalletSynchronizer
from electrum.bitcoin import is_valid
DEFAULT_PATH = '/tmp/'
# Delayed imports
encode_uri = None
class CScreen(Screen):
__events__ = ('on_activate', 'on_deactivate')
action_view = ObjectProperty(None)
def _change_action_view(self):
app = App.get_running_app()
action_bar = app.root.main_screen.ids.action_bar
_action_view = self.action_view
if (not _action_view) or _action_view.parent:
return
action_bar.clear_widgets()
action_bar.add_widget(_action_view)
def on_activate(self):
Clock.schedule_once(lambda dt: self._change_action_view())
def on_deactivate(self):
Clock.schedule_once(lambda dt: self._change_action_view())
class RootManager(ScreenManager):
'''Main Root Widget of the app'''
# initialize properties that will be updted in kv
main_screen = ObjectProperty(None)
'''Object holding the reference to main screen'''
screen_preferences = ObjectProperty(None)
'''Object holding the reference to preferences screen'''
screen_seed = ObjectProperty(None)
''''''
screen_network = ObjectProperty(None)
'''Object holding the Network screen'''
class MainScreen(Screen):
pass
class ScreenSend(CScreen):
pass
class ScreenDashboard(CScreen):
tab = ObjectProperty(None)
def show_tx_details(
self, date, address, amount, amount_color, balance,
tx_hash, conf, quote_text):
ra_dialog = RecentActivityDialog()
ra_dialog.address = address
ra_dialog.amount = amount
ra_dialog.amount_color = amount_color
ra_dialog.confirmations = conf
ra_dialog.quote_text = quote_text
date_time = date.split()
if len(date_time) == 2:
ra_dialog.date = date_time[0]
ra_dialog.time = date_time[1]
ra_dialog.status = 'Validated'
else:
ra_dialog.date = date_time
ra_dialog.status = 'Pending'
ra_dialog.tx_hash = tx_hash
app = App.get_running_app()
main_gui = app.gui.main_gui
tx_hash = tx_hash
tx = app.wallet.transactions.get(tx_hash)
if tx_hash in app.wallet.transactions.keys():
is_relevant, is_mine, v, fee = app.wallet.get_tx_value(tx)
conf, timestamp = app.wallet.verifier.get_confirmations(tx_hash)
#if timestamp:
# time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
#else:
# time_str = 'pending'
else:
is_mine = False
ra_dialog.is_mine = is_mine
if is_mine:
if fee is not None:
ra_dialog.fee = main_gui.format_amount(fee)
else:
ra_dialog.fee = 'unknown'
ra_dialog.open()
class ScreenPassword(Screen):
__events__ = ('on_release', 'on_deactivate', 'on_activate')
def on_activate(self):
app = App.get_running_app()
action_bar = app.root.main_screen.ids.action_bar
action_bar.add_widget(self._action_view)
def on_deactivate(self):
self.ids.password.text = ''
def on_release(self, *args):
pass
class SettingsScreen(Screen):
def __init__(self, **kwargs):
super(SettingsScreen, self).__init__(**kwargs)
Clock.schedule_once(self.delayed_init)
self.app = App.get_running_app()
def on_enter(self, *args):
self.delayed_init()
def delayed_init(self, *dt):
app = self.app
try:
main_gui = app.gui.main_gui
except AttributeError:
# wait for main gui to start
Clock.schedule_once(self.delayed_init, 1)
return
ids = self.ids
ids.st_unit_combo.key = main_gui.base_unit()
ids.st_fee_e.text = main_gui.format_amount(app.wallet.fee).strip()
ids.st_expert_cb.active = main_gui.expert_mode
currencies = main_gui.exchanger.get_currencies()
currencies.insert(0, "None")
currencies = zip(currencies, currencies)
key = app.conf.get('currency', 'None')
ids.st_cur_combo.text = ids.st_cur_combo.key = key
ids.st_cur_combo.items = currencies
ids.st_lang_combo.key = key = app.conf.get("language", '')
ids.st_lang_combo.items = languages.items()
x, y = zip(*ids.st_lang_combo.items)
ids.st_lang_combo.text = y[x.index(key)]
def do_callback(self, instance):
ids = self.ids
app = self.app
wallet = app.wallet
main_gui = app.gui.main_gui
if instance == ids.export_labels:
title = _("Select file to save your labels")
path = DEFAULT_PATH
filename = "electrum_labels.dat"
filters = ["*.dat"]
def save(instance):
path = dialog.file_chooser.path
filename = dialog.text_input.text.strip()
labels = wallet.labels
try:
with open(os.path.join(path, filename), 'w+') as stream:
json.dump(labels, stream)
MessageBox(title="Labels exported",
message=_("Your labels were exported to")\
+ " '%s'" % str(filename),
size=('320dp', '320dp')).open()
except (IOError, os.error), reason:
MessageBoxError(
title="Unable to export labels",
message=_("Electrum was unable to export your labels.")+
"\n" + str(reason), size=('320dp', '320dp')).open()
dialog.close()
dialog = SaveDialog(title=title,
path=path,
filename=filename,
filters=filters)
dialog.save_button.bind(on_release=save)
dialog.open()
elif instance == ids.import_labels:
title = _("Open labels file")
path = DEFAULT_PATH
filename = ""
filters = ["*.dat"]
def load(instance):
path = dialog.file_chooser.path
filename = dialog.text_input.text.strip()
labels = wallet.labels
try:
with open(os.path.join(path, filename), 'r') as stream:
for key, value in json.loads(stream.read()).items():
wallet.labels[key] = value
wallet.save()
MessageBox(title="Labels imported",
message=_("Your labels were imported from") + " '%s'" % str(filename),
size=('320dp', '320dp')).open()
except (IOError, os.error), reason:
MessageBoxError(title="Unable to import labels",
message=_("Electrum was unable to import your labels.") + "\n" + str(reason),
size=('320dp', '320dp')).open()
dialog.close()
dialog = LoadDialog(title=title, path=path, filename=filename, filters=filters)
dialog.load_button.bind(on_press=load)
dialog.open()
elif instance == ids.export_history:
title = _("Select file to export your wallet transactions to")
path = os.path.expanduser('~')
filename = "electrum-history.csv"
filters = ["*.csv"]
def save(instance):
path = dialog.file_chooser.path
filename = dialog.text_input.text.strip()
# extracted from gui_lite.csv_transaction
wallet = wallet
try:
with open(os.path.join(path, filename), "w+") as stream:
transaction = csv.writer(stream)
transaction.writerow(["transaction_hash", "label", "confirmations", "value", "fee", "balance", "timestamp"])
for item in wallet.get_tx_history():
tx_hash, confirmations, is_mine, value, fee, balance, timestamp = item
if confirmations:
if timestamp is not None:
try:
time_string = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
except [RuntimeError, TypeError, NameError] as reason:
time_string = "unknown"
pass
else:
time_string = "unknown"
else:
time_string = "pending"
if value is not None:
value_string = format_satoshis(value, True, wallet.num_zeros)
else:
value_string = '--'
if fee is not None:
fee_string = format_satoshis(fee, True, wallet.num_zeros)
else:
fee_string = '0'
if tx_hash:
label, is_default_label = wallet.get_label(tx_hash)
else:
label = ""
balance_string = format_satoshis(balance, False, wallet.num_zeros)
transaction.writerow([tx_hash, label, confirmations, value_string, fee_string, balance_string, time_string])
MessageBox(title="CSV Export created",
message="Your CSV export has been successfully created.",
size=('320dp', '320dp')).open()
except (IOError, os.error), reason:
export_error_label = _("Electrum was unable to produce a transaction export.")
MessageBoxError(title="Unable to create csv",
message=export_error_label + "\n" + str(reason),
size=('320dp', '320dp')).open()
dialog.close()
dialog = SaveDialog(title=title, path=path, filename=filename, filters=filters)
dialog.save_button.bind(on_press=save)
dialog.open()
elif instance == ids.export_privkey:
# NOTE: equivalent to @protected
def protected_save_dialog(instance=None, password=None):
def show_save_dialog(_dlg, instance):
_dlg.close()
title = _("Select file to export your private keys to")
path = DEFAULT_PATH
filename = "electrum-private-keys.csv"
filters = ["*.csv"]
def save(instance):
path = dialog.file_chooser.path
filename = dialog.text_input.text.strip()
try:
with open(os.path.join(path, filename), "w+") as csvfile:
transaction = csv.writer(csvfile)
transaction.writerow(["address", "private_key"])
for addr, pk in wallet.get_private_keys(wallet.addresses(True), password).items():
transaction.writerow(["%34s" % addr, pk])
MesageBox(message=_("Private keys exported."),
size=('320dp', '320dp')).open()
except (IOError, os.error), reason:
export_error_label = _("Electrum was unable to produce a private key-export.")
return MessageBoxError(message="Unable to create csv", content_text=export_error_label + "\n" + str(reason),
size=('320dp', '320dp')).open()
except BaseException, e:
return app.show_info_bubble(text=str(e))
dialog.close()
dialog = SaveDialog(title=title, path=path, filename=filename, filters=filters)
dialog.save_button.bind(on_press=save)
dialog.open()
mb = MessageBox(message="%s\n%s\n%s" % (
_("[color=ff0000ff][b]WARNING[/b][/color]: ALL your private keys are secret."),
_("Exposing a single private key can compromise your entire wallet!") + '\n\n',
_("In particular, [color=ff0000ff]DO NOT[/color] use 'redeem private key' services proposed by third parties.")),
on_release=show_save_dialog,
size = ('350dp', '320dp')).open()
if wallet.use_encryption:
return main_gui.password_required_dialog(post_ok=protected_save_dialog)
return protected_save_dialog()
elif instance == ids.import_privkey:
# NOTE: equivalent to @protected
def protected_load_dialog(_instance=None, password=None):
def show_privkey_dialog(__instance=None):
def on_release(_dlg, _btn):
if _btn.text == _('Cancel'):
_dlg.close()
confirm_dialog.close()
return
text = _dlg.ids.ti.text.split()
badkeys = []
addrlist = []
for key in text:
try:
addr = wallet.import_key(key, password)
except BaseException as e:
badkeys.append(key)
continue
if not addr:
badkeys.append(key)
else:
addrlist.append(addr)
if addrlist:
MessageBox(title=_('Information'),
message=_("The following addresses were added") + ':\n' + '\n'.join(addrlist),
size=('320dp', '320dp')).open()
if badkeys:
MessageBoxError(title=_('Error'),
message=_("The following inputs could not be imported") + ':\n' + '\n'.join(badkeys),
size=('320dp', '320dp')).open()
main_gui.update_receive_tab()
main_gui.update_history_tab()
if _instance is not None: # called via callback
_dlg.close()
ImportPrivateKeysDialog(on_release=on_release).open()
if not wallet.imported_keys:
def on_release(_dlg, _btn):
_dlg.close
if _btn.text == _('No'):
return
show_privkey_dialog()
confirm_dialog = MessageBoxError(title=_('Warning'),
message=_('Imported keys are not recoverable from seed.') + ' ' \
+ _('If you ever need to restore your wallet from its seed, these keys will be lost.') + '\n\n' \
+ _('Are you sure you understand what you are doing?'),
size=('320dp', '320dp'),
on_release=on_release)
confirm_dialog.buttons = [_('No'), _('Yes')]
confirm_dialog.open()
else:
show_privkey_dialog()
if wallet.use_encryption:
return main_gui.password_required_dialog(
post_ok=protected_load_dialog)
return protected_load_dialog()
elif instance == ids.show_pubkey:
# NOTE: Kivy TextInput doesn't wrap long text. So must handle it manually
pub_key = wallet.get_master_public_key()
pub_key = '%s\n%s\n%s\n%s' % (pub_key[0:31], pub_key[32:63], pub_key[64:95], pub_key[96:127])
ShowMasterPublicKeyDialog(text=pub_key).open()
elif instance == ids.from_file:
title = _("Select your transaction file")
path = DEFAULT_PATH
filename = ""
filters = ["*.txn"]
def load(instance):
path = dialog.file_chooser.path
filename = dialog.text_input.text.strip()
if not filename:
return
try:
with open(os.path.join(path, filename), "r") as f:
file_content = f.read()
except (ValueError, IOError, os.error), reason:
MessageBoxError(title="Unable to read file or no transaction found",
message=_("Electrum was unable to open your transaction file") + "\n" + str(reason),
size=('320dp', '320dp')).open()
tx_dict = main_gui.tx_dict_from_text(file_content)
if tx_dict:
main_gui.create_process_transaction_window(tx_dict)
dialog.close()
dialog = LoadDialog(title=title, path=path, filename=filename, filters=filters)
dialog.load_button.bind(on_press=load)
dialog.open()
elif instance == ids.from_text:
def load_transaction(_dlg, _btn):
if _btn.text == _('Cancel'):
_dlg.close
return
text = _dlg.ids.ti.text
if not text:
return
tx_dict = main_gui.tx_dict_from_text(text)
if tx_dict:
main_gui.create_process_transaction_window(tx_dict)
_dlg.close()
dialog = TakeInputDialog(on_release=load_transaction)
dialog.title = title=_("Input raw transaction")
dialog.open()
# End of do_callback() #
def on_ok(self, instance):
##########
app = self.app
main_gui = app.gui.main_gui
fee = unicode(self.ids.st_fee_e.text)
try:
fee = main_gui.read_amount(fee)
except:
return MessageBoxError(message=_('Invalid value') + ': %s' % fee).open()
app.wallet.set_fee(fee)
##########
nz = unicode(self.ids.st_nz_e.text)
try:
nz = int(nz)
if nz > 8: nz = 8
except:
return MessageBoxError(message=_('Invalid value') + ':%s' % nz).open()
if app.wallet.num_zeros != nz:
app.wallet.num_zeros = nz
app.conf.set_key('num_zeros', nz, True)
main_gui.update_history_tab()
main_gui.update_receive_tab()
usechange_result = self.ids.st_usechange_cb.active
if app.wallet.use_change != usechange_result:
app.wallet.use_change = usechange_result
app.conf.set_key('use_change', app.wallet.use_change, True)
unit_result = self.ids.st_unit_combo.text
if main_gui.base_unit() != unit_result:
main_gui.decimal_point = 8 if unit_result == 'BTC' else 5
app.conf.set_key('decimal_point', main_gui.decimal_point, True)
main_gui.update_history_tab()
main_gui.update_status()
try:
n = int(self.ids.st_gap_e.text)
except:
return MessageBoxError(message=_('Invalid value')).open()
if app.wallet.gap_limit != n:
if app.wallet.change_gap_limit(n):
main_gui.update_receive_tab()
app.conf.set_key('gap_limit', app.wallet.gap_limit, True)
else:
MessageBoxError(Message=_('Invalid value')).open()
# TODO: no return???
need_restart = False
lang_request = str(self.ids.st_lang_combo.key)
if lang_request != app.conf.get('language'):
app.conf.set_key("language", lang_request, True) # TODO: why can't save unicode
need_restart = True
cur_request = str(self.ids.st_cur_combo.text)
if cur_request != app.conf.get('currency', "None"):
app.conf.set_key('currency', cur_request, True) # TODO: why can't save unicode
main_gui.update_wallet()
main_gui.run_hook('close_settings_dialog')
if need_restart:
MessageBox(message=_('Please restart Electrum to activate the new GUI settings')).open()
# from receive_tab_set_mode()
main_gui.save_column_widths()
main_gui.expert_mode = self.ids.st_expert_cb.active
app.conf.set_key('classic_expert_mode', main_gui.expert_mode, True)
main_gui.update_receive_tab()
# close
app.root.current = 'main_screen'
class NetworkScreen(Screen):
status = StringProperty(_('Uninitialized'))
'''status message displayed on top of screen'''
server = StringProperty('')
#servers = ListProperty([])
servers_view = ObjectProperty(None)
server_host = ObjectProperty(None)
server_port = ObjectProperty(None)
server_protocol = ObjectProperty(None)
proxy_host = ObjectProperty(None)
proxy_port = ObjectProperty(None)
proxy_mode = ObjectProperty(None)
protocol_names = ListProperty(['TCP', 'HTTP', 'SSL', 'HTTPS'])
protocol_letters = StringProperty('thsg')
proxy_names = ListProperty(['NONE', 'SOCKS4', 'SOCKS5', 'HTTP'])
proxy_keys = ListProperty(['none', 'socks4', 'socks5', 'http'])
autocycle_cb = ObjectProperty(None)
interface = ObjectProperty(None)
def __init__(self, **kwargs):
self.initialized = True
super(NetworkScreen, self).__init__(**kwargs)
Clock.schedule_once(self._delayed_init)
def _delayed_init(self, dt):
self.protocol = None
self.app = app = App.get_running_app()
self.conf = conf = app.conf
self.wallet = wallet = app.wallet
self.interface = interface = wallet.interface
if not self.initialized:
if interface.is_connected:
self.status = _("Connected to") + " %s" % (interface.host) + "\n%d " % (wallet.verifier.height) + _("blocks")
else:
self.status = _("Not connected")
else:
self.status = _("Please choose a server.") + "\n" + _("Select 'Cancel' if you are offline.")
self.server = server = interface.server
self.servers = interface.get_servers()
self.servers_view.content_adapter.bind(on_selection_change=self.server_changed)
########################
if server:
host, port, protocol = server.split(':')
self.set_protocol(protocol)
self.change_server(host, protocol)
else:
self.set_protocol('s')
########################
# TODO: review it
# if not config.is_modifiable('server'):
# for w in [self.server_host, self.server_port, self.server_protocol, self.servers_list_widget]: w.setEnabled(False)
self.check_for_disable(None, 'none')
# if not wallet.config.is_modifiable('proxy'):
# for w in [proxy_host, proxy_port, proxy_mode]: w.setEnabled(False)
proxy_config = interface.proxy\
if interface.proxy else\
{ "mode":"none", "host":"localhost", "port":"8080"}
self.proxy_mode.key = proxy_config.get("mode")
self.proxy_host.text = proxy_config.get("host")
self.proxy_port.text = proxy_config.get("port")
# server = unicode( server_host.text ) + ':' + unicode( server_port.text ) + ':' + (protocol_letters[server_protocol.currentIndex()])
# if proxy_mode.currentText() != 'NONE':
# proxy = { u'mode':unicode(proxy_mode.currenttext).lower(), u'host':unicode(proxy_host.text), u'port':unicode(proxy_port.text) }
# else:
# proxy = None
self.autocycle_cb.active = conf.get('auto_cycle', True)
if not conf.is_modifiable('auto_cycle'):
self.autocycle_cb.active = False
def check_for_disable(self, instance, proxy_mode_key):
if proxy_mode_key != 'none':
self.proxy_host.disabled = False
self.proxy_port.disabled = False
else:
self.proxy_host.disabled = True
self.proxy_port.disabled = True
def on_cancel(self, *args):
self.manager.current = 'main_screen'
# TODO: RuntimeError: threads can only be started once
# interface.start(wait=False)
# interface.send([('server.peers.subscribe', [])])
# generate the first addresses, in case we are offline
self.wallet.synchronize()
verifier = WalletVerifier(self.interface, self.conf)
verifier.start()
self.wallet.set_verifier(verifier)
synchronizer = WalletSynchronizer(self.wallet, self.conf)
synchronizer.start()
if not self.initialized:
self.app.gui.main_gui.change_password_dialog()
def on_ok(self, *args):
self.manager.current = 'main_screen'
################
server = ':'.join([str(self.server_host.text),
str(self.server_port.text),
str(self.server_protocol.key)])
if self.proxy_mode.key != 'none':
proxy = { 'mode':str(self.proxy_mode.key),
'host':str(self.proxy_host.text),
'port':str(self.proxy_port.text) }
else:
proxy = None
app = self.app
conf = self.conf
wallet = self.wallet
interface = self.interface
conf.set_key("proxy", proxy, True)
conf.set_key("server", server, True)
interface.set_server(server, proxy)
conf.set_key('auto_cycle', self.autocycle_cb.active, True)
# generate the first addresses, in case we are offline
if app.gui.action == 'create':
app.wallet.synchronize()
app.gui.change_password_dialog()
verifier = WalletVerifier(interface, conf)
verifier.start()
wallet.set_verifier(verifier)
synchronizer = WalletSynchronizer(wallet, conf)
synchronizer.start()
if app.gui.action == 'restore':
initialized = self.initialized
try:
def on_complete(keep_it=False):
wallet.fill_addressbook()
#if not keep_it:
# app.stop()
# return
if not initialized:
app.gui.change_password_dialog()
app.gui.restore_wallet(on_complete=on_complete)
except:
import traceback, sys
traceback.print_exc(file=sys.stdout)
app.stop()
if not interface.isAlive():
interface.start(wait=False)
interface.send([('server.peers.subscribe', [])])
def init_servers_list(self):
data = []
for _host, d in self.servers.items():
if d.get(self.protocol):
pruning_level = d.get('pruning', '')
data.append((_host, pruning_level))
self.servers_view.content_adapter.data = data
def set_protocol(self, protocol):
if protocol != self.protocol:
self.protocol = protocol
self.init_servers_list()
def on_change_protocol(self, instance, protocol_key):
p = protocol_key
host = unicode(self.server_host.text)
pp = self.servers.get(host)
if not pp:
return
if p not in pp.keys():
p = pp.keys()[0]
port = pp[p]
self.server_host.text = host
self.server_port.text = port
self.set_protocol(p)
def server_changed(self, instance):
try:
index = instance.selection[0].index
except (AttributeError, IndexError):
return
item = instance.get_data_item(index)
self.change_server(item[0], self.protocol)
def change_server(self, host, protocol):
pp = self.servers.get(host, DEFAULT_PORTS)
if protocol:
port = pp.get(protocol)
if not port: protocol = None
if not protocol:
if 's' in pp.keys():
protocol = 's'
port = pp.get(protocol)
else:
protocol = pp.keys()[0]
port = pp.get(protocol)
self.server_host.text = host
self.server_port.text = port
self.server_protocol.text = self.protocol_names[self.protocol_letters.index(protocol)]
if not self.servers: return
# TODO: what's this?
# for p in protocol_letters:
# i = protocol_letters.index(p)
# j = self.server_protocol.model().index(i,0)
# #if p not in pp.keys(): # and self.interface.is_connected:
# # self.server_protocol.model().setData(j, QVariant(0), Qt.UserRole-1)
# #else:
# # self.server_protocol.model().setData(j, QVariant(33), Qt.UserRole-1)
class ScreenAddress(CScreen):
labels = DictProperty({})
'''
'''
tab = ObjectProperty(None)
''' The tab associated With this Carousel
'''
class ScreenConsole(CScreen):
pass
class ScreenReceive(CScreen):
pass
#TODO: move to wallet management
class ScreenReceive2(CScreen):
receive_view = ObjectProperty(None)
def __init__(self, **kwargs):
self.context_menu = None
super(ScreenReceive, self).__init__(**kwargs)
self.app = App.get_running_app()
def on_receive_view(self, instance, value):
if not value:
return
value.on_context_menu = self.on_context_menu
def on_menu_item_selected(self, instance, _menu, _btn):
'''Called when any one of the bubble menu items is selected
'''
app = self.app
main_gui = app.gui.main_gui
def delete_imported_key():
def on_release(_dlg, _dlg_btn):
if _dlg_btn.text == _('Cancel'):
_dlg.close()
return
app.wallet.delete_imported_key(address)
main_gui.update_receive_tab()
main_gui.update_history_tab()
MessageBox(title=_('Delete imported key'),
message=_("Do you want to remove")
+" %s "%addr +_("from your wallet?"),
buttons=[_('Cancel'), _('OK')],
on_release=on_release).open()
def edit_label_dialog():
# Show dialog to edit the label
def save_label(_dlg, _dlg_btn):
if _dlg_btn.text != _('Ok'):
return
txt = _dlg.ids.ti.text
if txt:
instance.parent.children[2].text = txt
_dlg.close()
text = instance.parent.children[2].text
dialog = EditLabelDialog(text=text,
on_release=save_label).open()
def show_private_key_dialog():
# NOTE: equivalent to @protected
def protected_show_private_key(_instance=None, password=None):
try:
pk = app.wallet.get_private_key(address, password)
except BaseException, e:
app.show_info_bubble(text=str(e))
return
PrivateKeyDialog(address=address,
private_key=pk).open()
if app.wallet.use_encryption:
return main_gui.password_required_dialog(
post_ok=protected_show_private_key)
protected_show_private_key()
def show_sign_verify_dialog():
def on_release(_dlg, _dlg_btn):
if _dlg_btn.text != _('Ok'):
return
if _dlg.ids.tabs.current_tab.text == _('Sign'):
# NOTE: equivalent to @protected
def protected_do_sign_message(instance=None, password=None):
try:
sig = app.wallet.sign_message(
_dlg.ids.sign_address.text,
_dlg.ids.sign_message.text,
password)
_dlg.ids.sign_signature.text = sig
except BaseException, e:
app.show_info_bubble(text=str(e.message))
if app.wallet.use_encryption:
return main_gui.password_required_dialog(
post_ok=protected_do_sign_message)
return protected_do_sign_message()
else: # _('Verify')
if app.wallet.verify_message(
_dlg.ids.verify_address.text,
_dlg.ids.verify_signature.text,
_dlg.ids.verify_message.text):
app.show_info_bubble(text=_("Signature verified"))
else:
app.show_info_bubble(
text=_("Error: wrong signature"))
SignVerifyDialog(on_release=on_release, address=address).open()
def toggle_freeze():
if address in app.wallet.frozen_addresses:
app.wallet.unfreeze(address)
else:
app.wallet.freeze(address)
main_gui.update_receive_tab()
def toggle_priority(_dlg, _dlg_btn):
if address in app.wallet.prioritized_addresses:
app.wallet.unprioritize(address)
else:
app.wallet.prioritize(address)
main_gui.update_receive_tab()
_menu.hide()
address = instance.parent.children[3].text
if _btn.text == _('Copy to clipboard'):
# copy data to clipboard
Clipboard.put(instance.parent.children[3].text, 'UTF8_STRING')
elif _btn.text == _('Edit label'):
edit_label_dialog()
elif _btn.text == _('Private key'):
show_private_key_dialog()
elif _btn.text == _('Sign message'):
# sign message
show_sign_verify_dialog()
elif _btn.text == _('Remove_from_wallet'):
delete_imported_key()
elif _btn.text in (_('Freeze'), _('Unfreeze')):
toggle_freeze()
elif _btn.text in (_('Prioritize'), _('Unprioritize')):
toggle_priority(_menu, _btn)
def on_context_menu(self, instance):
'''Called when list item is clicked.
Objective: show bubble menu
'''
app = self.app
address = instance.parent.children[3].text
if not address or not is_valid(address): return
context_menu = ContextMenu(size_hint=(None, None),
size=('160dp', '160dp'),
orientation='vertical',
arrow_pos='left_mid',
buttons=[_('Copy to clipboard'),
_('Edit label'),
_('Private key'),
_('Sign message')],
on_release=partial(self.on_menu_item_selected,
instance))
if address in app.wallet.imported_keys:
context_menu.buttons = context_menu.buttons +\
[_('Remove from wallet')]
# TODO: test more this feature
if app.gui.main_gui.expert_mode:
# TODO: show frozen, prioritized rows in different color
# as original code
t = _("Unfreeze")\
if address in app.wallet.frozen_addresses else\
_("Freeze")
context_menu.buttons = context_menu.buttons + [t]
t = _("Unprioritize")\
if address in app.wallet.prioritized_addresses else\
_("Prioritize")
context_menu.buttons = context_menu.buttons + [t]
context_menu.show(pos=(instance.right, instance.top))
class ScreenContacts(CScreen):
def add_new_contact(self):
NewContactDialog().open()
class TabbedCarousel(TabbedPanel):
carousel = ObjectProperty(None)
def animate_tab_to_center(self, value):
scrlv = self._tab_strip.parent
if not scrlv:
return
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
mation = Animation(scroll_x=max(0, min(scroll_x, 1)), 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):
current_slide = instance.current_slide
if not hasattr(current_slide, 'tab'):
return
tab = current_slide.tab
ct = self.current_tab
try:
if ct.text != tab.text:
carousel = self.carousel
carousel.slides[ct.slide].dispatch('on_deactivate')
self.switch_to(tab)
carousel.slides[tab.slide].dispatch('on_activate')
except AttributeError:
current_slide.dispatch('on_activate')
def switch_to(self, header):
# we have to replace the functionality of the original switch_to
if not header:
return
if not hasattr(header, 'slide'):
header.content = self.carousel
super(TabbedCarousel, self).switch_to(header)
tab = self.tab_list[-1]
self._current_tab = tab
tab.state = 'down'
return
carousel = self.carousel
self.current_tab.state = "normal"
header.state = 'down'
self._current_tab = header
# set the carousel to load the appropriate slide
# saved in the screen attribute of the tab head
slide = carousel.slides[header.slide]
if carousel.current_slide != slide:
carousel.current_slide.dispatch('on_deactivate')
carousel.load_slide(slide)
slide.dispatch('on_activate')
def add_widget(self, widget, index=0):
if isinstance(widget, Screen):
self.carousel.add_widget(widget)
return
super(TabbedCarousel, self).add_widget(widget, index=index)
class TabbedScreens(TabbedPanel):
manager = ObjectProperty(None)
'''Linked to the screen manager in kv'''
def switch_to(self, header):
# we don't use default tab so skip
if not hasattr(header, 'screen'):
header.content = self.manager
super(TabbedScreens, self).switch_to(header)
return
if not header.screen:
return
panel = self
panel.current_tab.state = "normal"
header.state = 'down'
panel._current_tab = header
self.manager.current = header.screen
def add_widget(self, widget, index=0):
if isinstance(widget, Screen):
self.manager.add_widget(widget)
return
super(TabbedScreens, self).add_widget(widget, index=index)