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.
1298 lines
46 KiB
1298 lines
46 KiB
13 years ago
|
#!/usr/bin/env python
|
||
|
#
|
||
|
# Electrum - lightweight Bitcoin client
|
||
|
# Copyright (C) 2011 thomasv@gitorious
|
||
|
#
|
||
|
# 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/>.
|
||
|
|
||
|
import datetime
|
||
13 years ago
|
import thread, time, ast, sys, re
|
||
13 years ago
|
import socket, traceback
|
||
13 years ago
|
import pygtk
|
||
|
pygtk.require('2.0')
|
||
|
import gtk, gobject
|
||
13 years ago
|
from electrum import pyqrnative
|
||
13 years ago
|
from decimal import Decimal
|
||
13 years ago
|
|
||
|
gtk.gdk.threads_init()
|
||
|
APP_NAME = "Electrum"
|
||
13 years ago
|
import platform
|
||
|
MONOSPACE_FONT = 'Lucida Console' if platform.system() == 'Windows' else 'monospace'
|
||
13 years ago
|
|
||
13 years ago
|
from wallet import format_satoshis
|
||
13 years ago
|
from interface import DEFAULT_SERVERS
|
||
13 years ago
|
|
||
|
def numbify(entry, is_int = False):
|
||
|
text = entry.get_text().strip()
|
||
13 years ago
|
chars = '0123456789'
|
||
|
if not is_int: chars +='.'
|
||
|
s = ''.join([i for i in text if i in chars])
|
||
13 years ago
|
if not is_int:
|
||
13 years ago
|
if '.' in s:
|
||
|
p = s.find('.')
|
||
|
s = s.replace('.','')
|
||
|
s = s[:p] + '.' + s[p:p+8]
|
||
13 years ago
|
try:
|
||
13 years ago
|
amount = int( Decimal(s) * 100000000 )
|
||
13 years ago
|
except:
|
||
13 years ago
|
amount = None
|
||
13 years ago
|
else:
|
||
|
try:
|
||
|
amount = int( s )
|
||
|
except:
|
||
|
amount = None
|
||
13 years ago
|
entry.set_text(s)
|
||
13 years ago
|
return amount
|
||
|
|
||
|
|
||
|
|
||
13 years ago
|
|
||
13 years ago
|
def show_seed_dialog(wallet, password, parent):
|
||
13 years ago
|
from electrum import mnemonic
|
||
13 years ago
|
try:
|
||
|
seed = wallet.pw_decode( wallet.seed, password)
|
||
|
except:
|
||
|
show_message("Incorrect password")
|
||
|
return
|
||
|
dialog = gtk.MessageDialog(
|
||
13 years ago
|
parent = parent,
|
||
13 years ago
|
flags = gtk.DIALOG_MODAL,
|
||
|
buttons = gtk.BUTTONS_OK,
|
||
|
message_format = "Your wallet generation seed is:\n\n" + seed \
|
||
|
+ "\n\nPlease keep it in a safe place; if you lose it, you will not be able to restore your wallet.\n\n" \
|
||
13 years ago
|
+ "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:\n\n\"" + ' '.join(mnemonic.mn_encode(seed)) + "\"" )
|
||
13 years ago
|
dialog.set_title("Seed")
|
||
13 years ago
|
dialog.show()
|
||
|
dialog.run()
|
||
|
dialog.destroy()
|
||
|
|
||
13 years ago
|
def restore_create_dialog(wallet):
|
||
13 years ago
|
|
||
13 years ago
|
# ask if the user wants to create a new wallet, or recover from a seed.
|
||
|
# if he wants to recover, and nothing is found, do not create wallet
|
||
|
dialog = gtk.Dialog("electrum", parent=None,
|
||
|
flags=gtk.DIALOG_MODAL|gtk.DIALOG_NO_SEPARATOR,
|
||
|
buttons= ("create", 0, "restore",1, "cancel",2) )
|
||
|
|
||
|
label = gtk.Label("Wallet file not found.\nDo you want to create a new wallet,\n or to restore an existing one?" )
|
||
|
label.show()
|
||
|
dialog.vbox.pack_start(label)
|
||
|
dialog.show()
|
||
|
r = dialog.run()
|
||
|
dialog.destroy()
|
||
13 years ago
|
|
||
|
if r==2: return False
|
||
13 years ago
|
|
||
|
is_recovery = (r==1)
|
||
|
|
||
13 years ago
|
# ask for the server.
|
||
|
if not run_network_dialog( wallet, parent=None ): return False
|
||
|
|
||
13 years ago
|
if not is_recovery:
|
||
|
|
||
|
wallet.new_seed(None)
|
||
|
# generate first key
|
||
13 years ago
|
wallet.init_mpk( wallet.seed )
|
||
|
wallet.up_to_date_event.clear()
|
||
|
wallet.update()
|
||
13 years ago
|
|
||
13 years ago
|
# run a dialog indicating the seed, ask the user to remember it
|
||
|
show_seed_dialog(wallet, None, None)
|
||
|
|
||
|
#ask for password
|
||
|
change_password_dialog(wallet, None, None)
|
||
|
else:
|
||
|
# ask for seed and gap.
|
||
|
run_recovery_dialog( wallet )
|
||
|
|
||
|
dialog = gtk.MessageDialog(
|
||
|
parent = None,
|
||
|
flags = gtk.DIALOG_MODAL,
|
||
|
buttons = gtk.BUTTONS_CANCEL,
|
||
|
message_format = "Please wait..." )
|
||
|
dialog.show()
|
||
13 years ago
|
|
||
13 years ago
|
def recover_thread( wallet, dialog ):
|
||
|
wallet.init_mpk( wallet.seed ) # not encrypted at this point
|
||
13 years ago
|
wallet.up_to_date_event.clear()
|
||
|
wallet.update()
|
||
13 years ago
|
|
||
13 years ago
|
if wallet.is_found():
|
||
|
# history and addressbook
|
||
|
wallet.update_tx_history()
|
||
|
wallet.fill_addressbook()
|
||
|
print "recovery successful"
|
||
|
|
||
|
gobject.idle_add( dialog.destroy )
|
||
|
|
||
|
thread.start_new_thread( recover_thread, ( wallet, dialog ) )
|
||
|
r = dialog.run()
|
||
|
dialog.destroy()
|
||
13 years ago
|
if r==gtk.RESPONSE_CANCEL: return False
|
||
13 years ago
|
if not wallet.is_found:
|
||
|
show_message("No transactions found for this seed")
|
||
13 years ago
|
|
||
13 years ago
|
wallet.save()
|
||
|
return True
|
||
|
|
||
13 years ago
|
|
||
13 years ago
|
def run_recovery_dialog(wallet):
|
||
|
message = "Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet."
|
||
|
dialog = gtk.MessageDialog(
|
||
|
parent = None,
|
||
|
flags = gtk.DIALOG_MODAL,
|
||
|
buttons = gtk.BUTTONS_OK_CANCEL,
|
||
|
message_format = message)
|
||
|
|
||
|
vbox = dialog.vbox
|
||
|
dialog.set_default_response(gtk.RESPONSE_OK)
|
||
|
|
||
|
# ask seed, server and gap in the same dialog
|
||
|
seed_box = gtk.HBox()
|
||
|
seed_label = gtk.Label('Seed or mnemonic:')
|
||
|
seed_label.set_size_request(150,-1)
|
||
|
seed_box.pack_start(seed_label, False, False, 10)
|
||
|
seed_label.show()
|
||
|
seed_entry = gtk.Entry()
|
||
|
seed_entry.show()
|
||
|
seed_entry.set_size_request(450,-1)
|
||
|
seed_box.pack_start(seed_entry, False, False, 10)
|
||
|
add_help_button(seed_box, '.')
|
||
|
seed_box.show()
|
||
|
vbox.pack_start(seed_box, False, False, 5)
|
||
|
|
||
|
gap = gtk.HBox()
|
||
|
gap_label = gtk.Label('Gap limit:')
|
||
|
gap_label.set_size_request(150,10)
|
||
|
gap_label.show()
|
||
|
gap.pack_start(gap_label,False, False, 10)
|
||
|
gap_entry = gtk.Entry()
|
||
|
gap_entry.set_text("%d"%wallet.gap_limit)
|
||
|
gap_entry.connect('changed', numbify, True)
|
||
|
gap_entry.show()
|
||
|
gap.pack_start(gap_entry,False,False, 10)
|
||
|
add_help_button(gap, 'The maximum gap that is allowed between unused addresses in your wallet. During wallet recovery, this parameter is used to decide when to stop the recovery process. If you increase this value, you will need to remember it in order to be able to recover your wallet from seed.')
|
||
|
gap.show()
|
||
|
vbox.pack_start(gap, False,False, 5)
|
||
13 years ago
|
|
||
13 years ago
|
dialog.show()
|
||
|
r = dialog.run()
|
||
|
gap = gap_entry.get_text()
|
||
13 years ago
|
seed = seed_entry.get_text()
|
||
13 years ago
|
dialog.destroy()
|
||
|
|
||
|
if r==gtk.RESPONSE_CANCEL:
|
||
|
sys.exit(1)
|
||
|
try:
|
||
|
gap = int(gap)
|
||
|
except:
|
||
|
show_message("error")
|
||
|
sys.exit(1)
|
||
|
|
||
|
try:
|
||
|
seed.decode('hex')
|
||
|
except:
|
||
13 years ago
|
from electrum import mnemonic
|
||
13 years ago
|
print "not hex, trying decode"
|
||
|
seed = mnemonic.mn_decode( seed.split(' ') )
|
||
13 years ago
|
if not seed:
|
||
|
show_message("no seed")
|
||
|
sys.exit(1)
|
||
13 years ago
|
|
||
|
wallet.seed = seed
|
||
|
wallet.gap_limit = gap
|
||
|
wallet.save()
|
||
|
|
||
|
|
||
|
|
||
|
def run_settings_dialog(wallet, parent):
|
||
|
|
||
13 years ago
|
message = "Here are the settings of your wallet. For more explanations, click on the question mark buttons next to each input field."
|
||
13 years ago
|
|
||
|
dialog = gtk.MessageDialog(
|
||
|
parent = parent,
|
||
|
flags = gtk.DIALOG_MODAL,
|
||
|
buttons = gtk.BUTTONS_OK_CANCEL,
|
||
|
message_format = message)
|
||
13 years ago
|
|
||
13 years ago
|
image = gtk.Image()
|
||
|
image.set_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_DIALOG)
|
||
|
image.show()
|
||
|
dialog.set_image(image)
|
||
13 years ago
|
dialog.set_title("Settings")
|
||
13 years ago
|
|
||
13 years ago
|
vbox = dialog.vbox
|
||
13 years ago
|
dialog.set_default_response(gtk.RESPONSE_OK)
|
||
13 years ago
|
|
||
13 years ago
|
fee = gtk.HBox()
|
||
|
fee_entry = gtk.Entry()
|
||
|
fee_label = gtk.Label('Transaction fee:')
|
||
|
fee_label.set_size_request(150,10)
|
||
|
fee_label.show()
|
||
|
fee.pack_start(fee_label,False, False, 10)
|
||
|
fee_entry.set_text( str( Decimal(wallet.fee) /100000000 ) )
|
||
|
fee_entry.connect('changed', numbify, False)
|
||
|
fee_entry.show()
|
||
|
fee.pack_start(fee_entry,False,False, 10)
|
||
|
add_help_button(fee, 'Fee per transaction input. Transactions involving multiple inputs tend to have a higher fee. Recommended value:0.0005')
|
||
|
fee.show()
|
||
|
vbox.pack_start(fee, False,False, 5)
|
||
13 years ago
|
|
||
13 years ago
|
nz = gtk.HBox()
|
||
|
nz_entry = gtk.Entry()
|
||
|
nz_label = gtk.Label('Display zeros:')
|
||
|
nz_label.set_size_request(150,10)
|
||
|
nz_label.show()
|
||
|
nz.pack_start(nz_label,False, False, 10)
|
||
|
nz_entry.set_text( str( wallet.num_zeros ))
|
||
|
nz_entry.connect('changed', numbify, True)
|
||
|
nz_entry.show()
|
||
|
nz.pack_start(nz_entry,False,False, 10)
|
||
|
add_help_button(nz, "Number of zeros displayed after the decimal point.\nFor example, if this number is 2, then '5.' is displayed as '5.00'")
|
||
|
nz.show()
|
||
|
vbox.pack_start(nz, False,False, 5)
|
||
|
|
||
13 years ago
|
dialog.show()
|
||
|
r = dialog.run()
|
||
13 years ago
|
fee = fee_entry.get_text()
|
||
13 years ago
|
nz = nz_entry.get_text()
|
||
13 years ago
|
|
||
13 years ago
|
dialog.destroy()
|
||
13 years ago
|
if r==gtk.RESPONSE_CANCEL:
|
||
13 years ago
|
return
|
||
13 years ago
|
|
||
13 years ago
|
try:
|
||
13 years ago
|
fee = int( 100000000 * Decimal(fee) )
|
||
13 years ago
|
except:
|
||
|
show_message("error")
|
||
|
return
|
||
13 years ago
|
if wallet.fee != fee:
|
||
|
wallet.fee = fee
|
||
|
wallet.save()
|
||
13 years ago
|
|
||
13 years ago
|
try:
|
||
|
nz = int( nz )
|
||
|
if nz>8: nz = 8
|
||
|
except:
|
||
|
show_message("error")
|
||
|
return
|
||
|
if wallet.num_zeros != nz:
|
||
|
wallet.num_zeros = nz
|
||
|
wallet.save()
|
||
13 years ago
|
|
||
|
|
||
13 years ago
|
|
||
13 years ago
|
|
||
13 years ago
|
def run_network_dialog( wallet, parent ):
|
||
|
image = gtk.Image()
|
||
|
image.set_from_stock(gtk.STOCK_NETWORK, gtk.ICON_SIZE_DIALOG)
|
||
13 years ago
|
interface = wallet.interface
|
||
13 years ago
|
if parent:
|
||
13 years ago
|
if interface.is_connected:
|
||
13 years ago
|
status = "Connected to %s:%d\n%d blocks"%(interface.host, interface.port, wallet.blocks)
|
||
13 years ago
|
else:
|
||
|
status = "Not connected"
|
||
13 years ago
|
server = wallet.server
|
||
13 years ago
|
else:
|
||
13 years ago
|
import random
|
||
13 years ago
|
status = "Please choose a server."
|
||
13 years ago
|
server = random.choice( DEFAULT_SERVERS )
|
||
13 years ago
|
|
||
13 years ago
|
if not wallet.interface.servers:
|
||
|
servers_list = []
|
||
|
for x in DEFAULT_SERVERS:
|
||
|
h,port,protocol = x.split(':')
|
||
|
servers_list.append( (h,[(protocol,port)] ) )
|
||
|
else:
|
||
|
servers_list = wallet.interface.servers
|
||
|
|
||
13 years ago
|
plist = {}
|
||
13 years ago
|
for item in servers_list:
|
||
13 years ago
|
host, pp = item
|
||
|
z = {}
|
||
|
for item2 in pp:
|
||
|
protocol, port = item2
|
||
|
z[protocol] = port
|
||
|
plist[host] = z
|
||
|
|
||
13 years ago
|
dialog = gtk.MessageDialog( parent, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
|
||
|
gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, status)
|
||
|
dialog.set_title("Server")
|
||
|
dialog.set_image(image)
|
||
|
image.show()
|
||
|
|
||
|
vbox = dialog.vbox
|
||
13 years ago
|
host_box = gtk.HBox()
|
||
13 years ago
|
host_label = gtk.Label('Connect to:')
|
||
|
host_label.set_size_request(100,-1)
|
||
|
host_label.show()
|
||
13 years ago
|
host_box.pack_start(host_label, False, False, 10)
|
||
13 years ago
|
host_entry = gtk.Entry()
|
||
|
host_entry.set_size_request(200,-1)
|
||
13 years ago
|
host_entry.set_text(server)
|
||
13 years ago
|
host_entry.show()
|
||
13 years ago
|
host_box.pack_start(host_entry, False, False, 10)
|
||
|
add_help_button(host_box, 'The name and port number of your Electrum server, separated by a colon. Example: "ecdsa.org:50000". If no port number is provided, port 50000 will be tried. Some servers allow you to connect through http (port 80) or https (port 443)')
|
||
|
host_box.show()
|
||
13 years ago
|
|
||
|
|
||
|
p_box = gtk.HBox(False, 10)
|
||
|
p_box.show()
|
||
|
|
||
|
p_label = gtk.Label('Protocol:')
|
||
|
p_label.set_size_request(100,-1)
|
||
|
p_label.show()
|
||
|
p_box.pack_start(p_label, False, False, 10)
|
||
|
|
||
|
radio1 = gtk.RadioButton(None, "tcp")
|
||
|
p_box.pack_start(radio1, True, True, 0)
|
||
|
radio1.show()
|
||
|
radio2 = gtk.RadioButton(radio1, "http")
|
||
|
p_box.pack_start(radio2, True, True, 0)
|
||
|
radio2.show()
|
||
|
|
||
|
def current_line():
|
||
|
return unicode(host_entry.get_text()).split(':')
|
||
|
|
||
|
def set_button(protocol):
|
||
|
if protocol == 't':
|
||
|
radio1.set_active(1)
|
||
|
elif protocol == 'h':
|
||
|
radio2.set_active(1)
|
||
|
|
||
|
def set_protocol(protocol):
|
||
|
host = current_line()[0]
|
||
|
pp = plist[host]
|
||
|
if protocol not in pp.keys():
|
||
|
protocol = pp.keys()[0]
|
||
|
set_button(protocol)
|
||
|
port = pp[protocol]
|
||
|
host_entry.set_text( host + ':' + port + ':' + protocol)
|
||
|
|
||
|
radio1.connect("toggled", lambda x,y:set_protocol('t'), "radio button 1")
|
||
|
radio2.connect("toggled", lambda x,y:set_protocol('h'), "radio button 1")
|
||
13 years ago
|
|
||
|
server_list = gtk.ListStore(str)
|
||
13 years ago
|
for host in plist.keys():
|
||
|
server_list.append([host])
|
||
13 years ago
|
|
||
|
treeview = gtk.TreeView(model=server_list)
|
||
|
treeview.show()
|
||
|
|
||
13 years ago
|
if wallet.interface.servers:
|
||
|
label = 'Active Servers'
|
||
|
else:
|
||
|
label = 'Default Servers'
|
||
|
|
||
|
tvcolumn = gtk.TreeViewColumn(label)
|
||
13 years ago
|
treeview.append_column(tvcolumn)
|
||
|
cell = gtk.CellRendererText()
|
||
|
tvcolumn.pack_start(cell, False)
|
||
|
tvcolumn.add_attribute(cell, 'text', 0)
|
||
|
|
||
|
scroll = gtk.ScrolledWindow()
|
||
|
scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
|
||
|
scroll.add(treeview)
|
||
|
scroll.show()
|
||
|
|
||
13 years ago
|
vbox.pack_start(host_box, False,False, 5)
|
||
13 years ago
|
vbox.pack_start(p_box, True, True, 0)
|
||
13 years ago
|
vbox.pack_start(scroll)
|
||
|
|
||
|
def my_treeview_cb(treeview):
|
||
|
path, view_column = treeview.get_cursor()
|
||
13 years ago
|
host = server_list.get_value( server_list.get_iter(path), 0)
|
||
|
|
||
|
pp = plist[host]
|
||
|
if 't' in pp.keys():
|
||
|
protocol = 't'
|
||
|
else:
|
||
|
protocol = pp.keys()[0]
|
||
|
port = pp[protocol]
|
||
|
host_entry.set_text( host + ':' + port + ':' + protocol)
|
||
|
set_button(protocol)
|
||
|
|
||
13 years ago
|
treeview.connect('cursor-changed', my_treeview_cb)
|
||
|
|
||
|
dialog.show()
|
||
|
r = dialog.run()
|
||
13 years ago
|
server = host_entry.get_text()
|
||
13 years ago
|
dialog.destroy()
|
||
13 years ago
|
|
||
13 years ago
|
if r==gtk.RESPONSE_CANCEL:
|
||
13 years ago
|
return False
|
||
13 years ago
|
|
||
13 years ago
|
try:
|
||
13 years ago
|
wallet.set_server(server)
|
||
13 years ago
|
except:
|
||
13 years ago
|
show_message("error:" + server)
|
||
13 years ago
|
return False
|
||
13 years ago
|
|
||
13 years ago
|
if parent:
|
||
|
wallet.save()
|
||
13 years ago
|
return True
|
||
13 years ago
|
|
||
|
|
||
13 years ago
|
|
||
13 years ago
|
def show_message(message, parent=None):
|
||
13 years ago
|
dialog = gtk.MessageDialog(
|
||
13 years ago
|
parent = parent,
|
||
13 years ago
|
flags = gtk.DIALOG_MODAL,
|
||
|
buttons = gtk.BUTTONS_CLOSE,
|
||
|
message_format = message )
|
||
|
dialog.show()
|
||
|
dialog.run()
|
||
|
dialog.destroy()
|
||
|
|
||
|
def password_line(label):
|
||
|
password = gtk.HBox()
|
||
|
password_label = gtk.Label(label)
|
||
|
password_label.set_size_request(120,10)
|
||
|
password_label.show()
|
||
|
password.pack_start(password_label,False, False, 10)
|
||
|
password_entry = gtk.Entry()
|
||
13 years ago
|
password_entry.set_size_request(300,-1)
|
||
13 years ago
|
password_entry.set_visibility(False)
|
||
|
password_entry.show()
|
||
|
password.pack_start(password_entry,False,False, 10)
|
||
|
password.show()
|
||
|
return password, password_entry
|
||
|
|
||
13 years ago
|
def password_dialog(parent):
|
||
|
dialog = gtk.MessageDialog( parent, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
|
||
13 years ago
|
gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, "Please enter your password.")
|
||
13 years ago
|
dialog.get_image().set_visible(False)
|
||
|
current_pw, current_pw_entry = password_line('Password:')
|
||
|
current_pw_entry.connect("activate", lambda entry, dialog, response: dialog.response(response), dialog, gtk.RESPONSE_OK)
|
||
|
dialog.vbox.pack_start(current_pw, False, True, 0)
|
||
|
dialog.show()
|
||
|
result = dialog.run()
|
||
|
pw = current_pw_entry.get_text()
|
||
|
dialog.destroy()
|
||
13 years ago
|
if result != gtk.RESPONSE_CANCEL: return pw
|
||
13 years ago
|
|
||
13 years ago
|
def change_password_dialog(wallet, parent, icon):
|
||
|
if parent:
|
||
13 years ago
|
msg = 'Your wallet is encrypted. Use this dialog to change the password. To disable wallet encryption, enter an empty new password.' if wallet.use_encryption else 'Your wallet keys are not encrypted'
|
||
13 years ago
|
else:
|
||
|
msg = "Please choose a password to encrypt your wallet keys"
|
||
|
|
||
13 years ago
|
dialog = gtk.MessageDialog( parent, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, msg)
|
||
13 years ago
|
dialog.set_title("Change password")
|
||
13 years ago
|
image = gtk.Image()
|
||
|
image.set_from_stock(gtk.STOCK_DIALOG_AUTHENTICATION, gtk.ICON_SIZE_DIALOG)
|
||
|
image.show()
|
||
|
dialog.set_image(image)
|
||
13 years ago
|
|
||
13 years ago
|
if wallet.use_encryption:
|
||
13 years ago
|
current_pw, current_pw_entry = password_line('Current password:')
|
||
13 years ago
|
dialog.vbox.pack_start(current_pw, False, True, 0)
|
||
|
|
||
|
password, password_entry = password_line('New password:')
|
||
|
dialog.vbox.pack_start(password, False, True, 5)
|
||
|
password2, password2_entry = password_line('Confirm password:')
|
||
|
dialog.vbox.pack_start(password2, False, True, 5)
|
||
|
|
||
|
dialog.show()
|
||
|
result = dialog.run()
|
||
|
password = current_pw_entry.get_text() if wallet.use_encryption else None
|
||
|
new_password = password_entry.get_text()
|
||
|
new_password2 = password2_entry.get_text()
|
||
|
dialog.destroy()
|
||
13 years ago
|
if result == gtk.RESPONSE_CANCEL:
|
||
13 years ago
|
return
|
||
|
|
||
|
try:
|
||
13 years ago
|
seed = wallet.pw_decode( wallet.seed, password)
|
||
13 years ago
|
except:
|
||
13 years ago
|
show_message("Incorrect password")
|
||
13 years ago
|
return
|
||
|
|
||
|
if new_password != new_password2:
|
||
|
show_message("passwords do not match")
|
||
|
return
|
||
|
|
||
13 years ago
|
wallet.update_password(seed, new_password)
|
||
13 years ago
|
|
||
|
if icon:
|
||
|
if wallet.use_encryption:
|
||
|
icon.set_tooltip_text('wallet is encrypted')
|
||
|
else:
|
||
|
icon.set_tooltip_text('wallet is unencrypted')
|
||
13 years ago
|
|
||
|
|
||
|
def add_help_button(hbox, message):
|
||
|
button = gtk.Button('?')
|
||
|
button.connect("clicked", lambda x: show_message(message))
|
||
|
button.show()
|
||
|
hbox.pack_start(button,False, False)
|
||
|
|
||
|
|
||
|
class MyWindow(gtk.Window): __gsignals__ = dict( mykeypress = (gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION, None, (str,)) )
|
||
|
|
||
|
gobject.type_register(MyWindow)
|
||
|
gtk.binding_entry_add_signal(MyWindow, gtk.keysyms.W, gtk.gdk.CONTROL_MASK, 'mykeypress', str, 'ctrl+W')
|
||
|
gtk.binding_entry_add_signal(MyWindow, gtk.keysyms.Q, gtk.gdk.CONTROL_MASK, 'mykeypress', str, 'ctrl+Q')
|
||
|
|
||
|
|
||
13 years ago
|
class ElectrumWindow:
|
||
13 years ago
|
|
||
13 years ago
|
def show_message(self, msg):
|
||
|
show_message(msg, self.window)
|
||
|
|
||
13 years ago
|
def __init__(self, wallet):
|
||
|
self.wallet = wallet
|
||
13 years ago
|
self.funds_error = False # True if not enough funds
|
||
13 years ago
|
|
||
|
self.window = MyWindow(gtk.WINDOW_TOPLEVEL)
|
||
13 years ago
|
self.window.set_title(APP_NAME + " " + self.wallet.electrum_version)
|
||
13 years ago
|
self.window.connect("destroy", gtk.main_quit)
|
||
|
self.window.set_border_width(0)
|
||
|
self.window.connect('mykeypress', gtk.main_quit)
|
||
13 years ago
|
self.window.set_default_size(720, 350)
|
||
13 years ago
|
|
||
|
vbox = gtk.VBox()
|
||
|
|
||
|
self.notebook = gtk.Notebook()
|
||
|
self.create_history_tab()
|
||
|
self.create_send_tab()
|
||
|
self.create_recv_tab()
|
||
|
self.create_book_tab()
|
||
|
self.create_about_tab()
|
||
|
self.notebook.show()
|
||
|
vbox.pack_start(self.notebook, True, True, 2)
|
||
|
|
||
|
self.status_bar = gtk.Statusbar()
|
||
|
vbox.pack_start(self.status_bar, False, False, 0)
|
||
|
|
||
|
self.status_image = gtk.Image()
|
||
13 years ago
|
self.status_image.set_from_stock(gtk.STOCK_NO, gtk.ICON_SIZE_MENU)
|
||
13 years ago
|
self.status_image.set_alignment(True, 0.5 )
|
||
|
self.status_image.show()
|
||
13 years ago
|
|
||
13 years ago
|
self.network_button = gtk.Button()
|
||
13 years ago
|
self.network_button.connect("clicked", lambda x: run_network_dialog(self.wallet, self.window) )
|
||
13 years ago
|
self.network_button.add(self.status_image)
|
||
|
self.network_button.set_relief(gtk.RELIEF_NONE)
|
||
|
self.network_button.show()
|
||
|
self.status_bar.pack_end(self.network_button, False, False)
|
||
13 years ago
|
|
||
|
def seedb(w, wallet):
|
||
|
if wallet.use_encryption:
|
||
13 years ago
|
password = password_dialog(self.window)
|
||
13 years ago
|
if not password: return
|
||
|
else: password = None
|
||
13 years ago
|
show_seed_dialog(wallet, password, self.window)
|
||
13 years ago
|
button = gtk.Button('S')
|
||
|
button.connect("clicked", seedb, wallet )
|
||
|
button.set_relief(gtk.RELIEF_NONE)
|
||
|
button.show()
|
||
|
self.status_bar.pack_end(button,False, False)
|
||
|
|
||
13 years ago
|
settings_icon = gtk.Image()
|
||
|
settings_icon.set_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_MENU)
|
||
13 years ago
|
settings_icon.set_alignment(0.5, 0.5)
|
||
|
settings_icon.set_size_request(16,16 )
|
||
13 years ago
|
settings_icon.show()
|
||
|
|
||
|
prefs_button = gtk.Button()
|
||
13 years ago
|
prefs_button.connect("clicked", lambda x: run_settings_dialog(self.wallet, self.window) )
|
||
13 years ago
|
prefs_button.add(settings_icon)
|
||
|
prefs_button.set_tooltip_text("Settings")
|
||
13 years ago
|
prefs_button.set_relief(gtk.RELIEF_NONE)
|
||
13 years ago
|
prefs_button.show()
|
||
|
self.status_bar.pack_end(prefs_button,False,False)
|
||
|
|
||
13 years ago
|
pw_icon = gtk.Image()
|
||
|
pw_icon.set_from_stock(gtk.STOCK_DIALOG_AUTHENTICATION, gtk.ICON_SIZE_MENU)
|
||
|
pw_icon.set_alignment(0.5, 0.5)
|
||
|
pw_icon.set_size_request(16,16 )
|
||
|
pw_icon.show()
|
||
|
|
||
|
password_button = gtk.Button()
|
||
13 years ago
|
password_button.connect("clicked", lambda x: change_password_dialog(self.wallet, self.window, pw_icon))
|
||
13 years ago
|
password_button.add(pw_icon)
|
||
|
password_button.set_relief(gtk.RELIEF_NONE)
|
||
|
password_button.show()
|
||
|
self.status_bar.pack_end(password_button,False,False)
|
||
|
|
||
13 years ago
|
self.window.add(vbox)
|
||
|
self.window.show_all()
|
||
13 years ago
|
#self.fee_box.hide()
|
||
13 years ago
|
|
||
|
self.context_id = self.status_bar.get_context_id("statusbar")
|
||
|
self.update_status_bar()
|
||
|
|
||
|
def update_status_bar_thread():
|
||
|
while True:
|
||
|
gobject.idle_add( self.update_status_bar )
|
||
|
time.sleep(0.5)
|
||
|
|
||
13 years ago
|
|
||
|
def check_recipient_thread():
|
||
|
old_r = ''
|
||
|
while True:
|
||
|
time.sleep(0.5)
|
||
13 years ago
|
if self.payto_entry.is_focus():
|
||
|
continue
|
||
13 years ago
|
r = self.payto_entry.get_text()
|
||
|
if r != old_r:
|
||
|
old_r = r
|
||
13 years ago
|
r = r.strip()
|
||
13 years ago
|
if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
|
||
13 years ago
|
try:
|
||
13 years ago
|
to_address = self.wallet.get_alias(r, interactive=False)
|
||
13 years ago
|
except:
|
||
|
continue
|
||
13 years ago
|
if to_address:
|
||
13 years ago
|
s = r + ' <' + to_address + '>'
|
||
13 years ago
|
gobject.idle_add( lambda: self.payto_entry.set_text(s) )
|
||
|
|
||
|
|
||
13 years ago
|
thread.start_new_thread(update_status_bar_thread, ())
|
||
13 years ago
|
thread.start_new_thread(check_recipient_thread, ())
|
||
13 years ago
|
self.notebook.set_current_page(0)
|
||
|
|
||
|
|
||
|
def add_tab(self, page, name):
|
||
|
tab_label = gtk.Label(name)
|
||
|
tab_label.show()
|
||
|
self.notebook.append_page(page, tab_label)
|
||
|
|
||
|
|
||
|
def create_send_tab(self):
|
||
13 years ago
|
|
||
13 years ago
|
page = vbox = gtk.VBox()
|
||
|
page.show()
|
||
|
|
||
|
payto = gtk.HBox()
|
||
|
payto_label = gtk.Label('Pay to:')
|
||
13 years ago
|
payto_label.set_size_request(100,-1)
|
||
13 years ago
|
payto.pack_start(payto_label, False)
|
||
|
payto_entry = gtk.Entry()
|
||
13 years ago
|
payto_entry.set_size_request(450, 26)
|
||
13 years ago
|
payto.pack_start(payto_entry, False)
|
||
|
vbox.pack_start(payto, False, False, 5)
|
||
13 years ago
|
|
||
13 years ago
|
message = gtk.HBox()
|
||
13 years ago
|
message_label = gtk.Label('Description:')
|
||
13 years ago
|
message_label.set_size_request(100,-1)
|
||
|
message.pack_start(message_label, False)
|
||
|
message_entry = gtk.Entry()
|
||
|
message_entry.set_size_request(450, 26)
|
||
|
message.pack_start(message_entry, False)
|
||
|
vbox.pack_start(message, False, False, 5)
|
||
13 years ago
|
|
||
13 years ago
|
amount_box = gtk.HBox()
|
||
13 years ago
|
amount_label = gtk.Label('Amount:')
|
||
13 years ago
|
amount_label.set_size_request(100,-1)
|
||
|
amount_box.pack_start(amount_label, False)
|
||
13 years ago
|
amount_entry = gtk.Entry()
|
||
13 years ago
|
amount_entry.set_size_request(120, -1)
|
||
|
amount_box.pack_start(amount_entry, False)
|
||
|
vbox.pack_start(amount_box, False, False, 5)
|
||
13 years ago
|
|
||
13 years ago
|
self.fee_box = fee_box = gtk.HBox()
|
||
|
fee_label = gtk.Label('Fee:')
|
||
13 years ago
|
fee_label.set_size_request(100,-1)
|
||
|
fee_box.pack_start(fee_label, False)
|
||
13 years ago
|
fee_entry = gtk.Entry()
|
||
13 years ago
|
fee_entry.set_size_request(60, 26)
|
||
13 years ago
|
fee_box.pack_start(fee_entry, False)
|
||
13 years ago
|
vbox.pack_start(fee_box, False, False, 5)
|
||
13 years ago
|
|
||
|
end_box = gtk.HBox()
|
||
|
empty_label = gtk.Label('')
|
||
13 years ago
|
empty_label.set_size_request(100,-1)
|
||
13 years ago
|
end_box.pack_start(empty_label, False)
|
||
|
send_button = gtk.Button("Send")
|
||
|
send_button.show()
|
||
13 years ago
|
end_box.pack_start(send_button, False, False, 0)
|
||
13 years ago
|
clear_button = gtk.Button("Clear")
|
||
|
clear_button.show()
|
||
13 years ago
|
end_box.pack_start(clear_button, False, False, 15)
|
||
|
send_button.connect("clicked", self.do_send, (payto_entry, message_entry, amount_entry, fee_entry))
|
||
|
clear_button.connect("clicked", self.do_clear, (payto_entry, message_entry, amount_entry, fee_entry))
|
||
13 years ago
|
|
||
13 years ago
|
vbox.pack_start(end_box, False, False, 5)
|
||
13 years ago
|
|
||
13 years ago
|
# display this line only if there is a signature
|
||
|
payto_sig = gtk.HBox()
|
||
|
payto_sig_id = gtk.Label('')
|
||
|
payto_sig.pack_start(payto_sig_id, False)
|
||
|
vbox.pack_start(payto_sig, True, True, 5)
|
||
|
|
||
|
|
||
13 years ago
|
self.user_fee = False
|
||
|
|
||
|
def entry_changed( entry, is_fee ):
|
||
13 years ago
|
self.funds_error = False
|
||
13 years ago
|
amount = numbify(amount_entry)
|
||
|
fee = numbify(fee_entry)
|
||
|
if not is_fee: fee = None
|
||
13 years ago
|
if amount is None:
|
||
13 years ago
|
return
|
||
13 years ago
|
inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
|
||
|
if not is_fee:
|
||
|
fee_entry.set_text( str( Decimal( fee ) / 100000000 ) )
|
||
|
self.fee_box.show()
|
||
|
if inputs:
|
||
|
amount_entry.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("#000000"))
|
||
|
fee_entry.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("#000000"))
|
||
|
send_button.set_sensitive(True)
|
||
|
else:
|
||
|
send_button.set_sensitive(False)
|
||
|
amount_entry.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("#cc0000"))
|
||
|
fee_entry.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("#cc0000"))
|
||
13 years ago
|
self.funds_error = True
|
||
13 years ago
|
|
||
|
amount_entry.connect('changed', entry_changed, False)
|
||
13 years ago
|
fee_entry.connect('changed', entry_changed, True)
|
||
13 years ago
|
|
||
|
self.payto_entry = payto_entry
|
||
13 years ago
|
self.payto_fee_entry = fee_entry
|
||
13 years ago
|
self.payto_sig_id = payto_sig_id
|
||
|
self.payto_sig = payto_sig
|
||
13 years ago
|
self.amount_entry = amount_entry
|
||
|
self.message_entry = message_entry
|
||
13 years ago
|
self.add_tab(page, 'Send')
|
||
|
|
||
13 years ago
|
def set_frozen(self,entry,frozen):
|
||
|
if frozen:
|
||
|
entry.set_editable(False)
|
||
|
entry.set_has_frame(False)
|
||
|
entry.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse("#eeeeee"))
|
||
|
else:
|
||
|
entry.set_editable(True)
|
||
|
entry.set_has_frame(True)
|
||
|
entry.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse("#ffffff"))
|
||
|
|
||
13 years ago
|
def set_url(self, url):
|
||
13 years ago
|
payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
|
||
13 years ago
|
self.notebook.set_current_page(1)
|
||
13 years ago
|
self.payto_entry.set_text(payto)
|
||
13 years ago
|
self.message_entry.set_text(message)
|
||
|
self.amount_entry.set_text(amount)
|
||
13 years ago
|
if identity:
|
||
|
self.set_frozen(self.payto_entry,True)
|
||
13 years ago
|
self.set_frozen(self.amount_entry,True)
|
||
|
self.set_frozen(self.message_entry,True)
|
||
13 years ago
|
self.payto_sig_id.set_text( ' The bitcoin URI was signed by ' + identity )
|
||
13 years ago
|
else:
|
||
|
self.payto_sig.set_visible(False)
|
||
13 years ago
|
|
||
13 years ago
|
def create_about_tab(self):
|
||
13 years ago
|
import pango
|
||
13 years ago
|
page = gtk.VBox()
|
||
|
page.show()
|
||
13 years ago
|
tv = gtk.TextView()
|
||
|
tv.set_editable(False)
|
||
|
tv.set_cursor_visible(False)
|
||
13 years ago
|
tv.modify_font(pango.FontDescription(MONOSPACE_FONT))
|
||
13 years ago
|
page.pack_start(tv)
|
||
|
self.info = tv.get_buffer()
|
||
13 years ago
|
self.add_tab(page, 'Wall')
|
||
13 years ago
|
|
||
13 years ago
|
def do_clear(self, w, data):
|
||
13 years ago
|
self.payto_sig.set_visible(False)
|
||
13 years ago
|
self.payto_fee_entry.set_text('')
|
||
13 years ago
|
for entry in [self.payto_entry,self.amount_entry,self.message_entry]:
|
||
13 years ago
|
self.set_frozen(entry,False)
|
||
|
entry.set_text('')
|
||
13 years ago
|
|
||
13 years ago
|
def question(self,msg):
|
||
|
dialog = gtk.MessageDialog( self.window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, msg)
|
||
|
dialog.show()
|
||
|
result = dialog.run()
|
||
|
dialog.destroy()
|
||
|
return result == gtk.RESPONSE_OK
|
||
|
|
||
13 years ago
|
def do_send(self, w, data):
|
||
13 years ago
|
payto_entry, label_entry, amount_entry, fee_entry = data
|
||
13 years ago
|
label = label_entry.get_text()
|
||
13 years ago
|
r = payto_entry.get_text()
|
||
13 years ago
|
r = r.strip()
|
||
|
|
||
13 years ago
|
m1 = re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r)
|
||
|
m2 = re.match('(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+) \<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
|
||
|
|
||
|
if m1:
|
||
13 years ago
|
to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
|
||
13 years ago
|
if not to_address:
|
||
|
return
|
||
13 years ago
|
else:
|
||
|
self.update_sending_tab()
|
||
|
|
||
13 years ago
|
elif m2:
|
||
|
to_address = m2.group(5)
|
||
13 years ago
|
else:
|
||
|
to_address = r
|
||
13 years ago
|
|
||
13 years ago
|
if not self.wallet.is_valid(to_address):
|
||
13 years ago
|
self.show_message( "invalid bitcoin address:\n"+to_address)
|
||
13 years ago
|
return
|
||
|
|
||
|
try:
|
||
13 years ago
|
amount = int( Decimal(amount_entry.get_text()) * 100000000 )
|
||
13 years ago
|
except:
|
||
13 years ago
|
self.show_message( "invalid amount")
|
||
|
return
|
||
|
try:
|
||
|
fee = int( Decimal(fee_entry.get_text()) * 100000000 )
|
||
|
except:
|
||
|
self.show_message( "invalid fee")
|
||
13 years ago
|
return
|
||
|
|
||
13 years ago
|
if self.wallet.use_encryption:
|
||
13 years ago
|
password = password_dialog(self.window)
|
||
13 years ago
|
if not password:
|
||
|
return
|
||
|
else:
|
||
|
password = None
|
||
13 years ago
|
|
||
13 years ago
|
try:
|
||
|
tx = self.wallet.mktx( to_address, amount, label, password, fee )
|
||
|
except BaseException, e:
|
||
|
self.show_message(e.message)
|
||
13 years ago
|
return
|
||
13 years ago
|
|
||
13 years ago
|
status, msg = self.wallet.sendtx( tx )
|
||
13 years ago
|
if status:
|
||
13 years ago
|
self.show_message( "payment sent.\n" + msg )
|
||
13 years ago
|
payto_entry.set_text("")
|
||
|
label_entry.set_text("")
|
||
|
amount_entry.set_text("")
|
||
13 years ago
|
fee_entry.set_text("")
|
||
13 years ago
|
#self.fee_box.hide()
|
||
13 years ago
|
self.update_sending_tab()
|
||
13 years ago
|
else:
|
||
13 years ago
|
self.show_message( msg )
|
||
13 years ago
|
|
||
|
|
||
13 years ago
|
def treeview_button_press(self, treeview, event):
|
||
|
if event.type == gtk.gdk._2BUTTON_PRESS:
|
||
|
c = treeview.get_cursor()[0]
|
||
13 years ago
|
if treeview == self.history_treeview:
|
||
|
tx_details = self.history_list.get_value( self.history_list.get_iter(c), 8)
|
||
|
self.show_message(tx_details)
|
||
|
elif treeview == self.contacts_treeview:
|
||
|
m = self.addressbook_list.get_value( self.addressbook_list.get_iter(c), 0)
|
||
|
a = self.wallet.aliases.get(m)
|
||
13 years ago
|
if a:
|
||
|
if a[0] in self.wallet.authorities.keys():
|
||
|
s = self.wallet.authorities.get(a[0])
|
||
|
else:
|
||
13 years ago
|
s = "self-signed"
|
||
|
msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
|
||
13 years ago
|
self.show_message(msg)
|
||
13 years ago
|
|
||
13 years ago
|
|
||
13 years ago
|
def treeview_key_press(self, treeview, event):
|
||
|
c = treeview.get_cursor()[0]
|
||
|
if event.keyval == gtk.keysyms.Up:
|
||
|
if c and c[0] == 0:
|
||
|
treeview.parent.grab_focus()
|
||
|
treeview.set_cursor((0,))
|
||
13 years ago
|
elif event.keyval == gtk.keysyms.Return:
|
||
|
if treeview == self.history_treeview:
|
||
|
tx_details = self.history_list.get_value( self.history_list.get_iter(c), 8)
|
||
|
self.show_message(tx_details)
|
||
|
elif treeview == self.contacts_treeview:
|
||
|
m = self.addressbook_list.get_value( self.addressbook_list.get_iter(c), 0)
|
||
|
a = self.wallet.aliases.get(m)
|
||
13 years ago
|
if a:
|
||
|
if a[0] in self.wallet.authorities.keys():
|
||
|
s = self.wallet.authorities.get(a[0])
|
||
|
else:
|
||
|
s = "self"
|
||
|
msg = 'Alias:'+ m + '\n\nTarget: '+ a[1] + '\nSigned by: ' + s + '\nSigning address:' + a[0]
|
||
|
self.show_message(msg)
|
||
13 years ago
|
|
||
13 years ago
|
return False
|
||
|
|
||
|
def create_history_tab(self):
|
||
|
|
||
13 years ago
|
self.history_list = gtk.ListStore(str, str, str, str, 'gboolean', str, str, str, str)
|
||
13 years ago
|
treeview = gtk.TreeView(model=self.history_list)
|
||
|
self.history_treeview = treeview
|
||
|
treeview.set_tooltip_column(7)
|
||
|
treeview.show()
|
||
|
treeview.connect('key-press-event', self.treeview_key_press)
|
||
13 years ago
|
treeview.connect('button-press-event', self.treeview_button_press)
|
||
13 years ago
|
|
||
|
tvcolumn = gtk.TreeViewColumn('')
|
||
|
treeview.append_column(tvcolumn)
|
||
|
cell = gtk.CellRendererPixbuf()
|
||
|
tvcolumn.pack_start(cell, False)
|
||
|
tvcolumn.set_attributes(cell, stock_id=1)
|
||
|
|
||
|
tvcolumn = gtk.TreeViewColumn('Date')
|
||
|
treeview.append_column(tvcolumn)
|
||
|
cell = gtk.CellRendererText()
|
||
|
tvcolumn.pack_start(cell, False)
|
||
|
tvcolumn.add_attribute(cell, 'text', 2)
|
||
|
|
||
13 years ago
|
tvcolumn = gtk.TreeViewColumn('Description')
|
||
13 years ago
|
treeview.append_column(tvcolumn)
|
||
|
cell = gtk.CellRendererText()
|
||
|
cell.set_property('foreground', 'grey')
|
||
13 years ago
|
cell.set_property('family', MONOSPACE_FONT)
|
||
13 years ago
|
cell.set_property('editable', True)
|
||
|
def edited_cb(cell, path, new_text, h_list):
|
||
|
tx = h_list.get_value( h_list.get_iter(path), 0)
|
||
|
self.wallet.labels[tx] = new_text
|
||
|
self.wallet.save()
|
||
|
self.update_history_tab()
|
||
|
cell.connect('edited', edited_cb, self.history_list)
|
||
|
def editing_started(cell, entry, path, h_list):
|
||
|
tx = h_list.get_value( h_list.get_iter(path), 0)
|
||
|
if not self.wallet.labels.get(tx): entry.set_text('')
|
||
|
cell.connect('editing-started', editing_started, self.history_list)
|
||
|
tvcolumn.set_expand(True)
|
||
|
tvcolumn.pack_start(cell, True)
|
||
|
tvcolumn.set_attributes(cell, text=3, foreground_set = 4)
|
||
|
|
||
|
tvcolumn = gtk.TreeViewColumn('Amount')
|
||
|
treeview.append_column(tvcolumn)
|
||
|
cell = gtk.CellRendererText()
|
||
|
cell.set_alignment(1, 0.5)
|
||
13 years ago
|
cell.set_property('family', MONOSPACE_FONT)
|
||
13 years ago
|
tvcolumn.pack_start(cell, False)
|
||
|
tvcolumn.add_attribute(cell, 'text', 5)
|
||
|
|
||
|
tvcolumn = gtk.TreeViewColumn('Balance')
|
||
|
treeview.append_column(tvcolumn)
|
||
|
cell = gtk.CellRendererText()
|
||
|
cell.set_alignment(1, 0.5)
|
||
13 years ago
|
cell.set_property('family', MONOSPACE_FONT)
|
||
13 years ago
|
tvcolumn.pack_start(cell, False)
|
||
|
tvcolumn.add_attribute(cell, 'text', 6)
|
||
|
|
||
|
tvcolumn = gtk.TreeViewColumn('Tooltip')
|
||
|
treeview.append_column(tvcolumn)
|
||
|
cell = gtk.CellRendererText()
|
||
|
tvcolumn.pack_start(cell, False)
|
||
|
tvcolumn.add_attribute(cell, 'text', 7)
|
||
|
tvcolumn.set_visible(False)
|
||
|
|
||
|
scroll = gtk.ScrolledWindow()
|
||
13 years ago
|
scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
|
||
13 years ago
|
scroll.add(treeview)
|
||
|
|
||
|
self.add_tab(scroll, 'History')
|
||
|
self.update_history_tab()
|
||
|
|
||
|
|
||
|
def create_recv_tab(self):
|
||
|
self.recv_list = gtk.ListStore(str, str, str)
|
||
|
self.add_tab( self.make_address_list(True), 'Receive')
|
||
|
self.update_receiving_tab()
|
||
|
|
||
|
def create_book_tab(self):
|
||
|
self.addressbook_list = gtk.ListStore(str, str, str)
|
||
|
self.add_tab( self.make_address_list(False), 'Contacts')
|
||
|
self.update_sending_tab()
|
||
|
|
||
|
def make_address_list(self, is_recv):
|
||
|
liststore = self.recv_list if is_recv else self.addressbook_list
|
||
|
treeview = gtk.TreeView(model= liststore)
|
||
|
treeview.connect('key-press-event', self.treeview_key_press)
|
||
13 years ago
|
treeview.connect('button-press-event', self.treeview_button_press)
|
||
13 years ago
|
treeview.show()
|
||
13 years ago
|
if not is_recv:
|
||
|
self.contacts_treeview = treeview
|
||
13 years ago
|
|
||
|
tvcolumn = gtk.TreeViewColumn('Address')
|
||
|
treeview.append_column(tvcolumn)
|
||
|
cell = gtk.CellRendererText()
|
||
13 years ago
|
cell.set_property('family', MONOSPACE_FONT)
|
||
13 years ago
|
tvcolumn.pack_start(cell, True)
|
||
|
tvcolumn.add_attribute(cell, 'text', 0)
|
||
|
|
||
|
tvcolumn = gtk.TreeViewColumn('Label')
|
||
|
tvcolumn.set_expand(True)
|
||
|
treeview.append_column(tvcolumn)
|
||
|
cell = gtk.CellRendererText()
|
||
|
cell.set_property('editable', True)
|
||
|
def edited_cb2(cell, path, new_text, liststore):
|
||
|
address = liststore.get_value( liststore.get_iter(path), 0)
|
||
|
self.wallet.labels[address] = new_text
|
||
|
self.wallet.save()
|
||
|
self.wallet.update_tx_labels()
|
||
|
self.update_receiving_tab()
|
||
|
self.update_sending_tab()
|
||
|
self.update_history_tab()
|
||
|
cell.connect('edited', edited_cb2, liststore)
|
||
|
tvcolumn.pack_start(cell, True)
|
||
|
tvcolumn.add_attribute(cell, 'text', 1)
|
||
|
|
||
|
tvcolumn = gtk.TreeViewColumn('Tx')
|
||
|
treeview.append_column(tvcolumn)
|
||
|
cell = gtk.CellRendererText()
|
||
|
tvcolumn.pack_start(cell, True)
|
||
|
tvcolumn.add_attribute(cell, 'text', 2)
|
||
|
|
||
|
scroll = gtk.ScrolledWindow()
|
||
|
scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
|
||
|
scroll.add(treeview)
|
||
|
|
||
|
hbox = gtk.HBox()
|
||
13 years ago
|
if not is_recv:
|
||
13 years ago
|
button = gtk.Button("New")
|
||
|
button.connect("clicked", self.newaddress_dialog)
|
||
13 years ago
|
button.show()
|
||
|
hbox.pack_start(button,False)
|
||
13 years ago
|
|
||
13 years ago
|
def showqrcode(w, treeview, liststore):
|
||
|
path, col = treeview.get_cursor()
|
||
|
if not path: return
|
||
|
address = liststore.get_value(liststore.get_iter(path), 0)
|
||
|
qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.H)
|
||
|
qr.addData(address)
|
||
|
qr.make()
|
||
|
boxsize = 7
|
||
|
size = qr.getModuleCount()*boxsize
|
||
|
def area_expose_cb(area, event):
|
||
|
style = area.get_style()
|
||
|
k = qr.getModuleCount()
|
||
|
for r in range(k):
|
||
|
for c in range(k):
|
||
|
gc = style.black_gc if qr.isDark(r, c) else style.white_gc
|
||
|
area.window.draw_rectangle(gc, True, c*boxsize, r*boxsize, boxsize, boxsize)
|
||
|
area = gtk.DrawingArea()
|
||
|
area.set_size_request(size, size)
|
||
|
area.connect("expose-event", area_expose_cb)
|
||
|
area.show()
|
||
13 years ago
|
dialog = gtk.Dialog(address, parent=self.window, flags=gtk.DIALOG_MODAL|gtk.DIALOG_NO_SEPARATOR, buttons = ("ok",1))
|
||
13 years ago
|
dialog.vbox.add(area)
|
||
|
dialog.run()
|
||
|
dialog.destroy()
|
||
|
|
||
|
button = gtk.Button("QR")
|
||
|
button.connect("clicked", showqrcode, treeview, liststore)
|
||
|
button.show()
|
||
|
hbox.pack_start(button,False)
|
||
|
|
||
13 years ago
|
button = gtk.Button("Copy to clipboard")
|
||
|
def copy2clipboard(w, treeview, liststore):
|
||
13 years ago
|
import platform
|
||
13 years ago
|
path, col = treeview.get_cursor()
|
||
|
if path:
|
||
|
address = liststore.get_value( liststore.get_iter(path), 0)
|
||
13 years ago
|
if platform.system() == 'Windows':
|
||
|
from Tkinter import Tk
|
||
|
r = Tk()
|
||
|
r.withdraw()
|
||
|
r.clipboard_clear()
|
||
|
r.clipboard_append( address )
|
||
|
r.destroy()
|
||
|
else:
|
||
|
c = gtk.clipboard_get()
|
||
|
c.set_text( address )
|
||
13 years ago
|
button.connect("clicked", copy2clipboard, treeview, liststore)
|
||
|
button.show()
|
||
|
hbox.pack_start(button,False)
|
||
|
|
||
|
if not is_recv:
|
||
|
button = gtk.Button("Pay to")
|
||
|
def payto(w, treeview, liststore):
|
||
|
path, col = treeview.get_cursor()
|
||
|
if path:
|
||
|
address = liststore.get_value( liststore.get_iter(path), 0)
|
||
|
self.payto_entry.set_text( address )
|
||
|
self.notebook.set_current_page(1)
|
||
13 years ago
|
self.amount_entry.grab_focus()
|
||
13 years ago
|
|
||
|
button.connect("clicked", payto, treeview, liststore)
|
||
|
button.show()
|
||
|
hbox.pack_start(button,False)
|
||
|
|
||
|
vbox = gtk.VBox()
|
||
|
vbox.pack_start(scroll,True)
|
||
|
vbox.pack_start(hbox, False)
|
||
|
return vbox
|
||
|
|
||
|
def update_status_bar(self):
|
||
13 years ago
|
interface = self.wallet.interface
|
||
13 years ago
|
if self.funds_error:
|
||
|
text = "Not enough funds"
|
||
13 years ago
|
elif interface.is_connected:
|
||
13 years ago
|
self.network_button.set_tooltip_text("Connected to %s:%d.\n%d blocks"%(interface.host, interface.port, self.wallet.blocks))
|
||
13 years ago
|
if self.wallet.blocks == -1:
|
||
|
self.status_image.set_from_stock(gtk.STOCK_NO, gtk.ICON_SIZE_MENU)
|
||
|
text = "Connecting..."
|
||
|
elif self.wallet.blocks == 0:
|
||
13 years ago
|
self.status_image.set_from_stock(gtk.STOCK_NO, gtk.ICON_SIZE_MENU)
|
||
13 years ago
|
text = "Server not ready"
|
||
13 years ago
|
elif not self.wallet.up_to_date:
|
||
13 years ago
|
self.status_image.set_from_stock(gtk.STOCK_REFRESH, gtk.ICON_SIZE_MENU)
|
||
13 years ago
|
text = "Synchronizing..."
|
||
|
else:
|
||
13 years ago
|
self.status_image.set_from_stock(gtk.STOCK_YES, gtk.ICON_SIZE_MENU)
|
||
13 years ago
|
self.network_button.set_tooltip_text("Connected to %s:%d.\n%d blocks"%(interface.host, interface.port, self.wallet.blocks))
|
||
13 years ago
|
c, u = self.wallet.get_balance()
|
||
13 years ago
|
text = "Balance: %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
|
||
|
if u: text += "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
|
||
13 years ago
|
else:
|
||
|
self.status_image.set_from_stock(gtk.STOCK_NO, gtk.ICON_SIZE_MENU)
|
||
13 years ago
|
self.network_button.set_tooltip_text("Trying to contact %s.\n%d blocks"%(interface.host, self.wallet.blocks))
|
||
13 years ago
|
text = "Not connected"
|
||
|
|
||
13 years ago
|
self.status_bar.pop(self.context_id)
|
||
13 years ago
|
self.status_bar.push(self.context_id, text)
|
||
|
|
||
13 years ago
|
if self.wallet.was_updated and self.wallet.up_to_date:
|
||
13 years ago
|
self.update_history_tab()
|
||
|
self.update_receiving_tab()
|
||
|
# addressbook too...
|
||
13 years ago
|
self.info.set_text( self.wallet.banner )
|
||
|
self.wallet.was_updated = False
|
||
13 years ago
|
|
||
13 years ago
|
|
||
|
def update_receiving_tab(self):
|
||
|
self.recv_list.clear()
|
||
13 years ago
|
for address in self.wallet.all_addresses():
|
||
13 years ago
|
if self.wallet.is_change(address):continue
|
||
13 years ago
|
label = self.wallet.labels.get(address)
|
||
|
n = 0
|
||
13 years ago
|
h = self.wallet.history.get(address,[])
|
||
|
for item in h:
|
||
13 years ago
|
if not item['is_input'] : n=n+1
|
||
13 years ago
|
tx = "None" if n==0 else "%d"%n
|
||
13 years ago
|
self.recv_list.append((address, label, tx ))
|
||
13 years ago
|
|
||
|
def update_sending_tab(self):
|
||
|
# detect addresses that are not mine in history, add them here...
|
||
|
self.addressbook_list.clear()
|
||
13 years ago
|
for alias, v in self.wallet.aliases.items():
|
||
|
s, target = v
|
||
|
label = self.wallet.labels.get(alias)
|
||
|
self.addressbook_list.append((alias, label, '-'))
|
||
|
|
||
13 years ago
|
for address in self.wallet.addressbook:
|
||
|
label = self.wallet.labels.get(address)
|
||
|
n = 0
|
||
|
for item in self.wallet.tx_history.values():
|
||
|
if address in item['outputs'] : n=n+1
|
||
|
tx = "None" if n==0 else "%d"%n
|
||
|
self.addressbook_list.append((address, label, tx))
|
||
|
|
||
|
def update_history_tab(self):
|
||
|
cursor = self.history_treeview.get_cursor()[0]
|
||
|
self.history_list.clear()
|
||
|
balance = 0
|
||
|
for tx in self.wallet.get_tx_history():
|
||
|
tx_hash = tx['tx_hash']
|
||
|
if tx['height']:
|
||
13 years ago
|
conf = self.wallet.blocks - tx['height'] + 1
|
||
13 years ago
|
time_str = datetime.datetime.fromtimestamp( tx['timestamp']).isoformat(' ')[:-3]
|
||
13 years ago
|
conf_icon = gtk.STOCK_APPLY
|
||
|
else:
|
||
|
conf = 0
|
||
|
time_str = 'pending'
|
||
|
conf_icon = gtk.STOCK_EXECUTE
|
||
|
v = tx['value']
|
||
|
balance += v
|
||
|
label = self.wallet.labels.get(tx_hash)
|
||
|
is_default_label = (label == '') or (label is None)
|
||
|
if is_default_label: label = tx['default_label']
|
||
|
tooltip = tx_hash + "\n%d confirmations"%conf
|
||
13 years ago
|
|
||
13 years ago
|
# tx = self.wallet.tx_history.get(tx_hash)
|
||
|
details = "Transaction Details:\n\n" \
|
||
|
+ "Transaction ID:\n" + tx_hash + "\n\n" \
|
||
|
+ "Status: %d confirmations\n\n"%conf \
|
||
|
+ "Date: %s\n\n"%time_str \
|
||
|
+ "Inputs:\n-"+ '\n-'.join(tx['inputs']) + "\n\n" \
|
||
|
+ "Outputs:\n-"+ '\n-'.join(tx['outputs'])
|
||
|
r = self.wallet.receipts.get(tx_hash)
|
||
|
if r:
|
||
|
details += "\n_______________________________________" \
|
||
13 years ago
|
+ '\n\nSigned URI: ' + r[2] \
|
||
|
+ "\n\nSigned by: " + r[0] \
|
||
13 years ago
|
+ '\n\nSignature: ' + r[1]
|
||
|
|
||
13 years ago
|
|
||
|
self.history_list.prepend( [tx_hash, conf_icon, time_str, label, is_default_label,
|
||
13 years ago
|
format_satoshis(v,True,self.wallet.num_zeros), format_satoshis(balance,False,self.wallet.num_zeros), tooltip, details] )
|
||
13 years ago
|
if cursor: self.history_treeview.set_cursor( cursor )
|
||
|
|
||
|
|
||
|
|
||
13 years ago
|
def newaddress_dialog(self, w):
|
||
13 years ago
|
|
||
13 years ago
|
title = "New Contact"
|
||
13 years ago
|
dialog = gtk.Dialog(title, parent=self.window,
|
||
|
flags=gtk.DIALOG_MODAL|gtk.DIALOG_NO_SEPARATOR,
|
||
|
buttons= ("cancel", 0, "ok",1) )
|
||
|
dialog.show()
|
||
13 years ago
|
|
||
13 years ago
|
label = gtk.HBox()
|
||
|
label_label = gtk.Label('Label:')
|
||
|
label_label.set_size_request(120,10)
|
||
|
label_label.show()
|
||
|
label.pack_start(label_label)
|
||
|
label_entry = gtk.Entry()
|
||
|
label_entry.show()
|
||
|
label.pack_start(label_entry)
|
||
|
label.show()
|
||
|
dialog.vbox.pack_start(label, False, True, 5)
|
||
|
|
||
|
address = gtk.HBox()
|
||
|
address_label = gtk.Label('Address:')
|
||
|
address_label.set_size_request(120,10)
|
||
|
address_label.show()
|
||
|
address.pack_start(address_label)
|
||
|
address_entry = gtk.Entry()
|
||
|
address_entry.show()
|
||
|
address.pack_start(address_entry)
|
||
|
address.show()
|
||
|
dialog.vbox.pack_start(address, False, True, 5)
|
||
|
|
||
|
result = dialog.run()
|
||
|
address = address_entry.get_text()
|
||
|
label = label_entry.get_text()
|
||
|
dialog.destroy()
|
||
13 years ago
|
|
||
13 years ago
|
if result == 1:
|
||
|
if self.wallet.is_valid(address):
|
||
|
self.wallet.addressbook.append(address)
|
||
|
if label: self.wallet.labels[address] = label
|
||
|
self.wallet.save()
|
||
|
self.update_sending_tab()
|
||
|
else:
|
||
|
errorDialog = gtk.MessageDialog(
|
||
|
parent=self.window,
|
||
|
flags=gtk.DIALOG_MODAL,
|
||
|
buttons= gtk.BUTTONS_CLOSE,
|
||
|
message_format = "Invalid address")
|
||
|
errorDialog.show()
|
||
|
errorDialog.run()
|
||
|
errorDialog.destroy()
|
||
13 years ago
|
|
||
13 years ago
|
|
||
13 years ago
|
|
||
|
class ElectrumGui():
|
||
|
|
||
|
def __init__(self, wallet):
|
||
|
self.wallet = wallet
|
||
|
|
||
13 years ago
|
def main(self, url=None):
|
||
|
ew = ElectrumWindow(self.wallet)
|
||
|
if url: ew.set_url(url)
|
||
13 years ago
|
gtk.main()
|
||
|
|
||
13 years ago
|
def restore_or_create(self):
|
||
|
return restore_create_dialog(self.wallet)
|