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
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)
|
|
|