~ubuntu-branches/ubuntu/utopic/electrum/utopic-proposed

« back to all changes in this revision

Viewing changes to gui/gtk.py

  • Committer: Package Import Robot
  • Author(s): Tristan Seligmann
  • Date: 2013-12-11 11:52:51 UTC
  • mfrom: (1.1.1)
  • Revision ID: package-import@ubuntu.com-20131211115251-4r3dhaxvmqlg2ilf
Tags: 1.9.5-1
* New upstream release (closes: #730353).
  - Contacts bugfix included in 1.8.1 (closes: #727232).
* Acknowledge NMU.
* Update watch file.
* Add myself to Uploaders.
* Update mk18n.py patch and ship new translations file.
* Bump dependency on python-ecdsa for secp256k1.
* Remove deprecated CDBS dependency management.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
#
 
3
# Electrum - lightweight Bitcoin client
 
4
# Copyright (C) 2011 thomasv@gitorious
 
5
#
 
6
# This program is free software: you can redistribute it and/or modify
 
7
# it under the terms of the GNU General Public License as published by
 
8
# the Free Software Foundation, either version 3 of the License, or
 
9
# (at your option) any later version.
 
10
#
 
11
# This program is distributed in the hope that it will be useful,
 
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 
14
# GNU General Public License for more details.
 
15
#
 
16
# You should have received a copy of the GNU General Public License
 
17
# along with this program. If not, see <http://www.gnu.org/licenses/>.
 
18
 
 
19
# use this because this file is named gtk.py
 
20
from __future__ import absolute_import
 
21
 
 
22
import datetime
 
23
import thread, time, ast, sys, re
 
24
import socket, traceback
 
25
import pygtk
 
26
pygtk.require('2.0')
 
27
import gtk, gobject
 
28
from decimal import Decimal
 
29
from electrum.util import print_error
 
30
from electrum.bitcoin import is_valid
 
31
from electrum import mnemonic, pyqrnative, WalletStorage, Wallet
 
32
 
 
33
gtk.gdk.threads_init()
 
34
APP_NAME = "Electrum"
 
35
import platform
 
36
MONOSPACE_FONT = 'Lucida Console' if platform.system() == 'Windows' else 'monospace'
 
37
 
 
38
from electrum.util import format_satoshis
 
39
from electrum.network import DEFAULT_SERVERS
 
40
from electrum.bitcoin import MIN_RELAY_TX_FEE
 
41
 
 
42
def numbify(entry, is_int = False):
 
43
    text = entry.get_text().strip()
 
44
    chars = '0123456789'
 
45
    if not is_int: chars +='.'
 
46
    s = ''.join([i for i in text if i in chars])
 
47
    if not is_int:
 
48
        if '.' in s:
 
49
            p = s.find('.')
 
50
            s = s.replace('.','')
 
51
            s = s[:p] + '.' + s[p:p+8]
 
52
        try:
 
53
            amount = int( Decimal(s) * 100000000 )
 
54
        except Exception:
 
55
            amount = None
 
56
    else:
 
57
        try:
 
58
            amount = int( s )
 
59
        except Exception:
 
60
            amount = None
 
61
    entry.set_text(s)
 
62
    return amount
 
63
 
 
64
 
 
65
 
 
66
 
 
67
def show_seed_dialog(wallet, password, parent):
 
68
    if not wallet.seed:
 
69
        show_message("No seed")
 
70
        return
 
71
    try:
 
72
        seed = wallet.get_seed(password)
 
73
    except Exception:
 
74
        show_message("Incorrect password")
 
75
        return
 
76
    dialog = gtk.MessageDialog(
 
77
        parent = parent,
 
78
        flags = gtk.DIALOG_MODAL, 
 
79
        buttons = gtk.BUTTONS_OK, 
 
80
        message_format = "Your wallet generation seed is:\n\n" + seed \
 
81
            + "\n\nPlease keep it in a safe place; if you lose it, you will not be able to restore your wallet.\n\n" \
 
82
            + "Equivalently, your wallet seed can be stored and recovered with the following mnemonic code:\n\n\"" + ' '.join(mnemonic.mn_encode(seed)) + "\"" )
 
83
    dialog.set_title("Seed")
 
84
    dialog.show()
 
85
    dialog.run()
 
86
    dialog.destroy()
 
87
 
 
88
def restore_create_dialog():
 
89
 
 
90
    # ask if the user wants to create a new wallet, or recover from a seed. 
 
91
    # if he wants to recover, and nothing is found, do not create wallet
 
92
    dialog = gtk.Dialog("electrum", parent=None, 
 
93
                        flags=gtk.DIALOG_MODAL|gtk.DIALOG_NO_SEPARATOR, 
 
94
                        buttons= ("create", 0, "restore",1, "cancel",2)  )
 
95
 
 
96
    label = gtk.Label("Wallet file not found.\nDo you want to create a new wallet,\n or to restore an existing one?"  )
 
97
    label.show()
 
98
    dialog.vbox.pack_start(label)
 
99
    dialog.show()
 
100
    r = dialog.run()
 
101
    dialog.destroy()
 
102
 
 
103
    if r==2: return False
 
104
    return 'restore' if r==1 else 'create'
 
105
 
 
106
 
 
107
 
 
108
def run_recovery_dialog():
 
109
    message = "Please enter your wallet seed or the corresponding mnemonic list of words, and the gap limit of your wallet."
 
110
    dialog = gtk.MessageDialog(
 
111
        parent = None,
 
112
        flags = gtk.DIALOG_MODAL, 
 
113
        buttons = gtk.BUTTONS_OK_CANCEL,
 
114
        message_format = message)
 
115
 
 
116
    vbox = dialog.vbox
 
117
    dialog.set_default_response(gtk.RESPONSE_OK)
 
118
 
 
119
    # ask seed, server and gap in the same dialog
 
120
    seed_box = gtk.HBox()
 
121
    seed_label = gtk.Label('Seed or mnemonic:')
 
122
    seed_label.set_size_request(150,-1)
 
123
    seed_box.pack_start(seed_label, False, False, 10)
 
124
    seed_label.show()
 
125
    seed_entry = gtk.Entry()
 
126
    seed_entry.show()
 
127
    seed_entry.set_size_request(450,-1)
 
128
    seed_box.pack_start(seed_entry, False, False, 10)
 
129
    add_help_button(seed_box, '.')
 
130
    seed_box.show()
 
131
    vbox.pack_start(seed_box, False, False, 5)    
 
132
 
 
133
    dialog.show()
 
134
    r = dialog.run()
 
135
    seed = seed_entry.get_text()
 
136
    dialog.destroy()
 
137
 
 
138
    if r==gtk.RESPONSE_CANCEL:
 
139
        return False
 
140
 
 
141
    try:
 
142
        seed.decode('hex')
 
143
    except Exception:
 
144
        print_error("Warning: Not hex, trying decode")
 
145
        seed = mnemonic.mn_decode( seed.split(' ') )
 
146
    if not seed:
 
147
        show_message("no seed")
 
148
        return False
 
149
        
 
150
    return seed
 
151
 
 
152
 
 
153
 
 
154
def run_settings_dialog(self):
 
155
 
 
156
    message = "Here are the settings of your wallet. For more explanations, click on the question mark buttons next to each input field."
 
157
        
 
158
    dialog = gtk.MessageDialog(
 
159
        parent = self.window,
 
160
        flags = gtk.DIALOG_MODAL, 
 
161
        buttons = gtk.BUTTONS_OK_CANCEL,
 
162
        message_format = message)
 
163
 
 
164
    image = gtk.Image()
 
165
    image.set_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_DIALOG)
 
166
    image.show()
 
167
    dialog.set_image(image)
 
168
    dialog.set_title("Settings")
 
169
 
 
170
    vbox = dialog.vbox
 
171
    dialog.set_default_response(gtk.RESPONSE_OK)
 
172
 
 
173
    fee = gtk.HBox()
 
174
    fee_entry = gtk.Entry()
 
175
    fee_label = gtk.Label('Transaction fee:')
 
176
    fee_label.set_size_request(150,10)
 
177
    fee_label.show()
 
178
    fee.pack_start(fee_label,False, False, 10)
 
179
    fee_entry.set_text( str( Decimal(self.wallet.fee) /100000000 ) )
 
180
    fee_entry.connect('changed', numbify, False)
 
181
    fee_entry.show()
 
182
    fee.pack_start(fee_entry,False,False, 10)
 
183
    add_help_button(fee, 'Fee per kilobyte of transaction. Recommended value:0.0001')
 
184
    fee.show()
 
185
    vbox.pack_start(fee, False,False, 5)
 
186
            
 
187
    nz = gtk.HBox()
 
188
    nz_entry = gtk.Entry()
 
189
    nz_label = gtk.Label('Display zeros:')
 
190
    nz_label.set_size_request(150,10)
 
191
    nz_label.show()
 
192
    nz.pack_start(nz_label,False, False, 10)
 
193
    nz_entry.set_text( str( self.num_zeros ))
 
194
    nz_entry.connect('changed', numbify, True)
 
195
    nz_entry.show()
 
196
    nz.pack_start(nz_entry,False,False, 10)
 
197
    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'")
 
198
    nz.show()
 
199
    vbox.pack_start(nz, False,False, 5)
 
200
            
 
201
    dialog.show()
 
202
    r = dialog.run()
 
203
    fee = fee_entry.get_text()
 
204
    nz = nz_entry.get_text()
 
205
        
 
206
    dialog.destroy()
 
207
    if r==gtk.RESPONSE_CANCEL:
 
208
        return
 
209
 
 
210
    try:
 
211
        fee = int( 100000000 * Decimal(fee) )
 
212
    except Exception:
 
213
        show_message("error")
 
214
        return
 
215
    self.wallet.set_fee(fee)
 
216
 
 
217
    try:
 
218
        nz = int( nz )
 
219
        if nz>8: nz = 8
 
220
    except Exception:
 
221
        show_message("error")
 
222
        return
 
223
 
 
224
    if self.num_zeros != nz:
 
225
        self.num_zeros = nz
 
226
        self.config.set_key('num_zeros',nz,True)
 
227
        self.update_history_tab()
 
228
 
 
229
 
 
230
 
 
231
 
 
232
def run_network_dialog( network, parent ):
 
233
    image = gtk.Image()
 
234
    image.set_from_stock(gtk.STOCK_NETWORK, gtk.ICON_SIZE_DIALOG)
 
235
    if parent:
 
236
        if network.is_connected():
 
237
            interface = network.interface
 
238
            status = "Connected to %s:%d\n%d blocks"%(interface.host, interface.port, network.blockchain.height())
 
239
        else:
 
240
            status = "Not connected"
 
241
    else:
 
242
        import random
 
243
        status = "Please choose a server.\nSelect cancel if you are offline."
 
244
 
 
245
    if network.is_connected():
 
246
        server = interface.server
 
247
        host, port, protocol = server.split(':')
 
248
 
 
249
    servers = network.get_servers()
 
250
 
 
251
    dialog = gtk.MessageDialog( parent, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
 
252
                                    gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, status)
 
253
    dialog.set_title("Server")
 
254
    dialog.set_image(image)
 
255
    image.show()
 
256
    
 
257
    vbox = dialog.vbox
 
258
    host_box = gtk.HBox()
 
259
    host_label = gtk.Label('Connect to:')
 
260
    host_label.set_size_request(100,-1)
 
261
    host_label.show()
 
262
    host_box.pack_start(host_label, False, False, 10)
 
263
    host_entry = gtk.Entry()
 
264
    host_entry.set_size_request(200,-1)
 
265
    if network.is_connected():
 
266
        host_entry.set_text(server)
 
267
    else:
 
268
        host_entry.set_text("Not Connected")
 
269
    host_entry.show()
 
270
    host_box.pack_start(host_entry, False, False, 10)
 
271
    add_help_button(host_box, 'The name, port number and protocol of your Electrum server, separated by a colon. Example: "ecdsa.org:50002:s". Some servers allow you to connect through http (port 80) or https (port 443)')
 
272
    host_box.show()
 
273
 
 
274
    p_box = gtk.HBox(False, 10)
 
275
    p_box.show()
 
276
 
 
277
    p_label = gtk.Label('Protocol:')
 
278
    p_label.set_size_request(100,-1)
 
279
    p_label.show()
 
280
    p_box.pack_start(p_label, False, False, 10)
 
281
 
 
282
    combobox = gtk.combo_box_new_text()
 
283
    combobox.show()
 
284
    combobox.append_text("TCP")
 
285
    combobox.append_text("SSL")
 
286
    combobox.append_text("HTTP")
 
287
    combobox.append_text("HTTPS")
 
288
 
 
289
    p_box.pack_start(combobox, True, True, 0)
 
290
 
 
291
    def current_line():
 
292
        return unicode(host_entry.get_text()).split(':')
 
293
 
 
294
    def set_combobox(protocol):
 
295
        combobox.set_active('tshg'.index(protocol))
 
296
 
 
297
    def set_protocol(protocol):
 
298
        host = current_line()[0]
 
299
        pp = servers[host]
 
300
        if protocol not in pp.keys():
 
301
            protocol = pp.keys()[0]
 
302
            set_combobox(protocol)
 
303
        port = pp[protocol]
 
304
        host_entry.set_text( host + ':' + port + ':' + protocol)
 
305
 
 
306
    combobox.connect("changed", lambda x:set_protocol('tshg'[combobox.get_active()]))
 
307
    if network.is_connected():
 
308
        set_combobox(protocol)
 
309
        
 
310
    server_list = gtk.ListStore(str)
 
311
    for host in servers.keys():
 
312
        server_list.append([host])
 
313
    
 
314
    treeview = gtk.TreeView(model=server_list)
 
315
    treeview.show()
 
316
 
 
317
    label = 'Active Servers' if network.irc_servers else 'Default Servers'
 
318
    tvcolumn = gtk.TreeViewColumn(label)
 
319
    treeview.append_column(tvcolumn)
 
320
    cell = gtk.CellRendererText()
 
321
    tvcolumn.pack_start(cell, False)
 
322
    tvcolumn.add_attribute(cell, 'text', 0)
 
323
 
 
324
    vbox.pack_start(host_box, False,False, 5)
 
325
    vbox.pack_start(p_box, True, True, 0)
 
326
 
 
327
    #scroll = gtk.ScrolledWindow()
 
328
    #scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
 
329
    #scroll.add_with_viewport(treeview)
 
330
    #scroll.show()
 
331
    #vbox.pack_start(scroll, True)
 
332
    vbox.pack_start(treeview, True)
 
333
 
 
334
    def my_treeview_cb(treeview):
 
335
        path, view_column = treeview.get_cursor()
 
336
        host = server_list.get_value( server_list.get_iter(path), 0)
 
337
 
 
338
        pp = servers[host]
 
339
        if 't' in pp.keys():
 
340
            protocol = 't'
 
341
        else:
 
342
            protocol = pp.keys()[0]
 
343
        port = pp[protocol]
 
344
        host_entry.set_text( host + ':' + port + ':' + protocol)
 
345
        set_combobox(protocol)
 
346
 
 
347
    treeview.connect('cursor-changed', my_treeview_cb)
 
348
 
 
349
    dialog.show_all()
 
350
    r = dialog.run()
 
351
    server = host_entry.get_text()
 
352
    dialog.destroy()
 
353
 
 
354
    if r==gtk.RESPONSE_CANCEL:
 
355
        return False
 
356
 
 
357
    try:
 
358
        host, port, protocol = server.split(':')
 
359
        proxy = network.config.get('proxy')
 
360
        auto_connect = network.config.get('auto_cycle')
 
361
        network.set_parameters(host, port, protocol, proxy, auto_connect)
 
362
    except Exception:
 
363
        show_message("error:" + server)
 
364
        return False
 
365
 
 
366
 
 
367
 
 
368
 
 
369
 
 
370
def show_message(message, parent=None):
 
371
    dialog = gtk.MessageDialog(
 
372
        parent = parent,
 
373
        flags = gtk.DIALOG_MODAL, 
 
374
        buttons = gtk.BUTTONS_CLOSE, 
 
375
        message_format = message )
 
376
    dialog.show()
 
377
    dialog.run()
 
378
    dialog.destroy()
 
379
 
 
380
def password_line(label):
 
381
    password = gtk.HBox()
 
382
    password_label = gtk.Label(label)
 
383
    password_label.set_size_request(120,10)
 
384
    password_label.show()
 
385
    password.pack_start(password_label,False, False, 10)
 
386
    password_entry = gtk.Entry()
 
387
    password_entry.set_size_request(300,-1)
 
388
    password_entry.set_visibility(False)
 
389
    password_entry.show()
 
390
    password.pack_start(password_entry,False,False, 10)
 
391
    password.show()
 
392
    return password, password_entry
 
393
 
 
394
def password_dialog(parent):
 
395
    dialog = gtk.MessageDialog( parent, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
 
396
                                gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL,  "Please enter your password.")
 
397
    dialog.get_image().set_visible(False)
 
398
    current_pw, current_pw_entry = password_line('Password:')
 
399
    current_pw_entry.connect("activate", lambda entry, dialog, response: dialog.response(response), dialog, gtk.RESPONSE_OK)
 
400
    dialog.vbox.pack_start(current_pw, False, True, 0)
 
401
    dialog.show()
 
402
    result = dialog.run()
 
403
    pw = current_pw_entry.get_text()
 
404
    dialog.destroy()
 
405
    if result != gtk.RESPONSE_CANCEL: return pw
 
406
 
 
407
def change_password_dialog(wallet, parent, icon):
 
408
    if not wallet.seed:
 
409
        show_message("No seed")
 
410
        return
 
411
 
 
412
    if parent:
 
413
        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'
 
414
    else:
 
415
        msg = "Please choose a password to encrypt your wallet keys"
 
416
 
 
417
    dialog = gtk.MessageDialog( parent, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, msg)
 
418
    dialog.set_title("Change password")
 
419
    image = gtk.Image()
 
420
    image.set_from_stock(gtk.STOCK_DIALOG_AUTHENTICATION, gtk.ICON_SIZE_DIALOG)
 
421
    image.show()
 
422
    dialog.set_image(image)
 
423
 
 
424
    if wallet.use_encryption:
 
425
        current_pw, current_pw_entry = password_line('Current password:')
 
426
        dialog.vbox.pack_start(current_pw, False, True, 0)
 
427
 
 
428
    password, password_entry = password_line('New password:')
 
429
    dialog.vbox.pack_start(password, False, True, 5)
 
430
    password2, password2_entry = password_line('Confirm password:')
 
431
    dialog.vbox.pack_start(password2, False, True, 5)
 
432
 
 
433
    dialog.show()
 
434
    result = dialog.run()
 
435
    password = current_pw_entry.get_text() if wallet.use_encryption else None
 
436
    new_password = password_entry.get_text()
 
437
    new_password2 = password2_entry.get_text()
 
438
    dialog.destroy()
 
439
    if result == gtk.RESPONSE_CANCEL: 
 
440
        return
 
441
 
 
442
    try:
 
443
        wallet.get_seed(password)
 
444
    except Exception:
 
445
        show_message("Incorrect password")
 
446
        return
 
447
 
 
448
    if new_password != new_password2:
 
449
        show_message("passwords do not match")
 
450
        return
 
451
 
 
452
    wallet.update_password(password, new_password)
 
453
 
 
454
    if icon:
 
455
        if wallet.use_encryption:
 
456
            icon.set_tooltip_text('wallet is encrypted')
 
457
        else:
 
458
            icon.set_tooltip_text('wallet is unencrypted')
 
459
 
 
460
 
 
461
def add_help_button(hbox, message):
 
462
    button = gtk.Button('?')
 
463
    button.connect("clicked", lambda x: show_message(message))
 
464
    button.show()
 
465
    hbox.pack_start(button,False, False)
 
466
 
 
467
 
 
468
class MyWindow(gtk.Window): __gsignals__ = dict( mykeypress = (gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION, None, (str,)) )
 
469
 
 
470
gobject.type_register(MyWindow)
 
471
gtk.binding_entry_add_signal(MyWindow, gtk.keysyms.W, gtk.gdk.CONTROL_MASK, 'mykeypress', str, 'ctrl+W')
 
472
gtk.binding_entry_add_signal(MyWindow, gtk.keysyms.Q, gtk.gdk.CONTROL_MASK, 'mykeypress', str, 'ctrl+Q')
 
473
 
 
474
 
 
475
class ElectrumWindow:
 
476
 
 
477
    def show_message(self, msg):
 
478
        show_message(msg, self.window)
 
479
 
 
480
    def __init__(self, wallet, config, network):
 
481
        self.config = config
 
482
        self.wallet = wallet
 
483
        self.network = network
 
484
        self.funds_error = False # True if not enough funds
 
485
        self.num_zeros = int(self.config.get('num_zeros',0))
 
486
 
 
487
        self.window = MyWindow(gtk.WINDOW_TOPLEVEL)
 
488
        title = 'Electrum ' + self.wallet.electrum_version + '  -  ' + self.config.path
 
489
        if not self.wallet.seed: title += ' [seedless]'
 
490
        self.window.set_title(title)
 
491
        self.window.connect("destroy", gtk.main_quit)
 
492
        self.window.set_border_width(0)
 
493
        self.window.connect('mykeypress', gtk.main_quit)
 
494
        self.window.set_default_size(720, 350)
 
495
        self.wallet_updated = False
 
496
 
 
497
        vbox = gtk.VBox()
 
498
 
 
499
        self.notebook = gtk.Notebook()
 
500
        self.create_history_tab()
 
501
        if self.wallet.seed:
 
502
            self.create_send_tab()
 
503
        self.create_recv_tab()
 
504
        self.create_book_tab()
 
505
        self.create_about_tab()
 
506
        self.notebook.show()
 
507
        vbox.pack_start(self.notebook, True, True, 2)
 
508
        
 
509
        self.status_bar = gtk.Statusbar()
 
510
        vbox.pack_start(self.status_bar, False, False, 0)
 
511
 
 
512
        self.status_image = gtk.Image()
 
513
        self.status_image.set_from_stock(gtk.STOCK_NO, gtk.ICON_SIZE_MENU)
 
514
        self.status_image.set_alignment(True, 0.5  )
 
515
        self.status_image.show()
 
516
 
 
517
        self.network_button = gtk.Button()
 
518
        self.network_button.connect("clicked", lambda x: run_network_dialog(self.network, self.window) )
 
519
        self.network_button.add(self.status_image)
 
520
        self.network_button.set_relief(gtk.RELIEF_NONE)
 
521
        self.network_button.show()
 
522
        self.status_bar.pack_end(self.network_button, False, False)
 
523
 
 
524
        if self.wallet.seed:
 
525
            def seedb(w, wallet):
 
526
                if wallet.use_encryption:
 
527
                    password = password_dialog(self.window)
 
528
                    if not password: return
 
529
                else: password = None
 
530
                show_seed_dialog(wallet, password, self.window)
 
531
            button = gtk.Button('S')
 
532
            button.connect("clicked", seedb, wallet )
 
533
            button.set_relief(gtk.RELIEF_NONE)
 
534
            button.show()
 
535
            self.status_bar.pack_end(button,False, False)
 
536
 
 
537
        settings_icon = gtk.Image()
 
538
        settings_icon.set_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_MENU)
 
539
        settings_icon.set_alignment(0.5, 0.5)
 
540
        settings_icon.set_size_request(16,16 )
 
541
        settings_icon.show()
 
542
 
 
543
        prefs_button = gtk.Button()
 
544
        prefs_button.connect("clicked", lambda x: run_settings_dialog(self) )
 
545
        prefs_button.add(settings_icon)
 
546
        prefs_button.set_tooltip_text("Settings")
 
547
        prefs_button.set_relief(gtk.RELIEF_NONE)
 
548
        prefs_button.show()
 
549
        self.status_bar.pack_end(prefs_button,False,False)
 
550
 
 
551
        pw_icon = gtk.Image()
 
552
        pw_icon.set_from_stock(gtk.STOCK_DIALOG_AUTHENTICATION, gtk.ICON_SIZE_MENU)
 
553
        pw_icon.set_alignment(0.5, 0.5)
 
554
        pw_icon.set_size_request(16,16 )
 
555
        pw_icon.show()
 
556
 
 
557
        if self.wallet.seed:
 
558
            password_button = gtk.Button()
 
559
            password_button.connect("clicked", lambda x: change_password_dialog(self.wallet, self.window, pw_icon))
 
560
            password_button.add(pw_icon)
 
561
            password_button.set_relief(gtk.RELIEF_NONE)
 
562
            password_button.show()
 
563
            self.status_bar.pack_end(password_button,False,False)
 
564
 
 
565
        self.window.add(vbox)
 
566
        self.window.show_all()
 
567
        #self.fee_box.hide()
 
568
 
 
569
        self.context_id = self.status_bar.get_context_id("statusbar")
 
570
        self.update_status_bar()
 
571
 
 
572
        self.network.register_callback('updated', self.update_callback)
 
573
 
 
574
 
 
575
        def update_status_bar_thread():
 
576
            while True:
 
577
                gobject.idle_add( self.update_status_bar )
 
578
                time.sleep(0.5)
 
579
 
 
580
 
 
581
        def check_recipient_thread():
 
582
            old_r = ''
 
583
            while True:
 
584
                time.sleep(0.5)
 
585
                if self.payto_entry.is_focus():
 
586
                    continue
 
587
                r = self.payto_entry.get_text()
 
588
                if r != old_r:
 
589
                    old_r = r
 
590
                    r = r.strip()
 
591
                    if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
 
592
                        try:
 
593
                            to_address = self.wallet.get_alias(r, interactive=False)
 
594
                        except Exception:
 
595
                            continue
 
596
                        if to_address:
 
597
                            s = r + ' <' + to_address + '>'
 
598
                            gobject.idle_add( lambda: self.payto_entry.set_text(s) )
 
599
                
 
600
 
 
601
        thread.start_new_thread(update_status_bar_thread, ())
 
602
        if self.wallet.seed:
 
603
            thread.start_new_thread(check_recipient_thread, ())
 
604
        self.notebook.set_current_page(0)
 
605
 
 
606
    def update_callback(self):
 
607
        self.wallet_updated = True
 
608
 
 
609
 
 
610
    def add_tab(self, page, name):
 
611
        tab_label = gtk.Label(name)
 
612
        tab_label.show()
 
613
        self.notebook.append_page(page, tab_label)
 
614
 
 
615
 
 
616
    def create_send_tab(self):
 
617
        
 
618
        page = vbox = gtk.VBox()
 
619
        page.show()
 
620
 
 
621
        payto = gtk.HBox()
 
622
        payto_label = gtk.Label('Pay to:')
 
623
        payto_label.set_size_request(100,-1)
 
624
        payto.pack_start(payto_label, False)
 
625
        payto_entry = gtk.Entry()
 
626
        payto_entry.set_size_request(450, 26)
 
627
        payto.pack_start(payto_entry, False)
 
628
        vbox.pack_start(payto, False, False, 5)
 
629
 
 
630
        message = gtk.HBox()
 
631
        message_label = gtk.Label('Description:')
 
632
        message_label.set_size_request(100,-1)
 
633
        message.pack_start(message_label, False)
 
634
        message_entry = gtk.Entry()
 
635
        message_entry.set_size_request(450, 26)
 
636
        message.pack_start(message_entry, False)
 
637
        vbox.pack_start(message, False, False, 5)
 
638
 
 
639
        amount_box = gtk.HBox()
 
640
        amount_label = gtk.Label('Amount:')
 
641
        amount_label.set_size_request(100,-1)
 
642
        amount_box.pack_start(amount_label, False)
 
643
        amount_entry = gtk.Entry()
 
644
        amount_entry.set_size_request(120, -1)
 
645
        amount_box.pack_start(amount_entry, False)
 
646
        vbox.pack_start(amount_box, False, False, 5)
 
647
 
 
648
        self.fee_box = fee_box = gtk.HBox()
 
649
        fee_label = gtk.Label('Fee:')
 
650
        fee_label.set_size_request(100,-1)
 
651
        fee_box.pack_start(fee_label, False)
 
652
        fee_entry = gtk.Entry()
 
653
        fee_entry.set_size_request(60, 26)
 
654
        fee_box.pack_start(fee_entry, False)
 
655
        vbox.pack_start(fee_box, False, False, 5)
 
656
 
 
657
        end_box = gtk.HBox()
 
658
        empty_label = gtk.Label('')
 
659
        empty_label.set_size_request(100,-1)
 
660
        end_box.pack_start(empty_label, False)
 
661
        send_button = gtk.Button("Send")
 
662
        send_button.show()
 
663
        end_box.pack_start(send_button, False, False, 0)
 
664
        clear_button = gtk.Button("Clear")
 
665
        clear_button.show()
 
666
        end_box.pack_start(clear_button, False, False, 15)
 
667
        send_button.connect("clicked", self.do_send, (payto_entry, message_entry, amount_entry, fee_entry))
 
668
        clear_button.connect("clicked", self.do_clear, (payto_entry, message_entry, amount_entry, fee_entry))
 
669
 
 
670
        vbox.pack_start(end_box, False, False, 5)
 
671
 
 
672
        # display this line only if there is a signature
 
673
        payto_sig = gtk.HBox()
 
674
        payto_sig_id = gtk.Label('')
 
675
        payto_sig.pack_start(payto_sig_id, False)
 
676
        vbox.pack_start(payto_sig, True, True, 5)
 
677
        
 
678
 
 
679
        self.user_fee = False
 
680
 
 
681
        def entry_changed( entry, is_fee ):
 
682
            self.funds_error = False
 
683
            amount = numbify(amount_entry)
 
684
            fee = numbify(fee_entry)
 
685
            if not is_fee: fee = None
 
686
            if amount is None:
 
687
                return
 
688
            inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
 
689
            if not is_fee:
 
690
                fee_entry.set_text( str( Decimal( fee ) / 100000000 ) )
 
691
                self.fee_box.show()
 
692
            if inputs:
 
693
                amount_entry.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("#000000"))
 
694
                fee_entry.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("#000000"))
 
695
                send_button.set_sensitive(True)
 
696
            else:
 
697
                send_button.set_sensitive(False)
 
698
                amount_entry.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("#cc0000"))
 
699
                fee_entry.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("#cc0000"))
 
700
                self.funds_error = True
 
701
 
 
702
        amount_entry.connect('changed', entry_changed, False)
 
703
        fee_entry.connect('changed', entry_changed, True)        
 
704
 
 
705
        self.payto_entry = payto_entry
 
706
        self.payto_fee_entry = fee_entry
 
707
        self.payto_sig_id = payto_sig_id
 
708
        self.payto_sig = payto_sig
 
709
        self.amount_entry = amount_entry
 
710
        self.message_entry = message_entry
 
711
        self.add_tab(page, 'Send')
 
712
 
 
713
    def set_frozen(self,entry,frozen):
 
714
        if frozen:
 
715
            entry.set_editable(False)
 
716
            entry.set_has_frame(False)
 
717
            entry.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse("#eeeeee"))
 
718
        else:
 
719
            entry.set_editable(True)
 
720
            entry.set_has_frame(True)
 
721
            entry.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse("#ffffff"))
 
722
 
 
723
    def set_url(self, url):
 
724
        payto, amount, label, message, signature, identity, url = self.wallet.parse_url(url, self.show_message, self.question)
 
725
        self.notebook.set_current_page(1)
 
726
        self.payto_entry.set_text(payto)
 
727
        self.message_entry.set_text(message)
 
728
        self.amount_entry.set_text(amount)
 
729
        if identity:
 
730
            self.set_frozen(self.payto_entry,True)
 
731
            self.set_frozen(self.amount_entry,True)
 
732
            self.set_frozen(self.message_entry,True)
 
733
            self.payto_sig_id.set_text( '      The bitcoin URI was signed by ' + identity )
 
734
        else:
 
735
            self.payto_sig.set_visible(False)
 
736
 
 
737
    def create_about_tab(self):
 
738
        import pango
 
739
        page = gtk.VBox()
 
740
        page.show()
 
741
        tv = gtk.TextView()
 
742
        tv.set_editable(False)
 
743
        tv.set_cursor_visible(False)
 
744
        tv.modify_font(pango.FontDescription(MONOSPACE_FONT))
 
745
        scroll = gtk.ScrolledWindow()
 
746
        scroll.add(tv)
 
747
        page.pack_start(scroll)
 
748
        self.info = tv.get_buffer()
 
749
        self.add_tab(page, 'Wall')
 
750
 
 
751
    def do_clear(self, w, data):
 
752
        self.payto_sig.set_visible(False)
 
753
        self.payto_fee_entry.set_text('')
 
754
        for entry in [self.payto_entry,self.amount_entry,self.message_entry]:
 
755
            self.set_frozen(entry,False)
 
756
            entry.set_text('')
 
757
 
 
758
    def question(self,msg):
 
759
        dialog = gtk.MessageDialog( self.window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, msg)
 
760
        dialog.show()
 
761
        result = dialog.run()
 
762
        dialog.destroy()
 
763
        return result == gtk.RESPONSE_OK
 
764
 
 
765
    def do_send(self, w, data):
 
766
        payto_entry, label_entry, amount_entry, fee_entry = data
 
767
        label = label_entry.get_text()
 
768
        r = payto_entry.get_text()
 
769
        r = r.strip()
 
770
 
 
771
        m1 = re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r)
 
772
        m2 = re.match('(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+) \<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
 
773
        
 
774
        if m1:
 
775
            to_address = self.wallet.get_alias(r, True, self.show_message, self.question)
 
776
            if not to_address:
 
777
                return
 
778
            else:
 
779
                self.update_sending_tab()
 
780
 
 
781
        elif m2:
 
782
            to_address = m2.group(5)
 
783
        else:
 
784
            to_address = r
 
785
 
 
786
        if not is_valid(to_address):
 
787
            self.show_message( "invalid bitcoin address:\n"+to_address)
 
788
            return
 
789
 
 
790
        try:
 
791
            amount = int( Decimal(amount_entry.get_text()) * 100000000 )
 
792
        except Exception:
 
793
            self.show_message( "invalid amount")
 
794
            return
 
795
        try:
 
796
            fee = int( Decimal(fee_entry.get_text()) * 100000000 )
 
797
        except Exception:
 
798
            self.show_message( "invalid fee")
 
799
            return
 
800
 
 
801
        if self.wallet.use_encryption:
 
802
            password = password_dialog(self.window)
 
803
            if not password:
 
804
                return
 
805
        else:
 
806
            password = None
 
807
 
 
808
        try:
 
809
            tx = self.wallet.mktx( [(to_address, amount)], password, fee )
 
810
        except Exception as e:
 
811
            self.show_message(str(e))
 
812
            return
 
813
 
 
814
        if tx.requires_fee(self.wallet.verifier) and fee < MIN_RELAY_TX_FEE:
 
815
            self.show_message( "This transaction requires a higher fee, or it will not be propagated by the network." )
 
816
            return
 
817
 
 
818
            
 
819
        if label: 
 
820
            self.wallet.labels[tx.hash()] = label
 
821
 
 
822
        status, msg = self.wallet.sendtx( tx )
 
823
        if status:
 
824
            self.show_message( "payment sent.\n" + msg )
 
825
            payto_entry.set_text("")
 
826
            label_entry.set_text("")
 
827
            amount_entry.set_text("")
 
828
            fee_entry.set_text("")
 
829
            #self.fee_box.hide()
 
830
            self.update_sending_tab()
 
831
        else:
 
832
            self.show_message( msg )
 
833
 
 
834
 
 
835
    def treeview_button_press(self, treeview, event):
 
836
        if event.type == gtk.gdk._2BUTTON_PRESS:
 
837
            c = treeview.get_cursor()[0]
 
838
            if treeview == self.history_treeview:
 
839
                tx_details = self.history_list.get_value( self.history_list.get_iter(c), 8)
 
840
                self.show_message(tx_details)
 
841
            elif treeview == self.contacts_treeview:
 
842
                m = self.addressbook_list.get_value( self.addressbook_list.get_iter(c), 0)
 
843
                #a = self.wallet.aliases.get(m)
 
844
                #if a:
 
845
                #    if a[0] in self.wallet.authorities.keys():
 
846
                #        s = self.wallet.authorities.get(a[0])
 
847
                #    else:
 
848
                #        s = "self-signed"
 
849
                #    msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0]
 
850
                #    self.show_message(msg)
 
851
            
 
852
 
 
853
    def treeview_key_press(self, treeview, event):
 
854
        c = treeview.get_cursor()[0]
 
855
        if event.keyval == gtk.keysyms.Up:
 
856
            if c and c[0] == 0:
 
857
                treeview.parent.grab_focus()
 
858
                treeview.set_cursor((0,))
 
859
        elif event.keyval == gtk.keysyms.Return:
 
860
            if treeview == self.history_treeview:
 
861
                tx_details = self.history_list.get_value( self.history_list.get_iter(c), 8)
 
862
                self.show_message(tx_details)
 
863
            elif treeview == self.contacts_treeview:
 
864
                m = self.addressbook_list.get_value( self.addressbook_list.get_iter(c), 0)
 
865
                #a = self.wallet.aliases.get(m)
 
866
                #if a:
 
867
                #    if a[0] in self.wallet.authorities.keys():
 
868
                #        s = self.wallet.authorities.get(a[0])
 
869
                #    else:
 
870
                #        s = "self"
 
871
                #    msg = 'Alias:'+ m + '\n\nTarget: '+ a[1] + '\nSigned by: ' + s + '\nSigning address:' + a[0]
 
872
                #    self.show_message(msg)
 
873
 
 
874
        return False
 
875
 
 
876
    def create_history_tab(self):
 
877
 
 
878
        self.history_list = gtk.ListStore(str, str, str, str, 'gboolean',  str, str, str, str)
 
879
        treeview = gtk.TreeView(model=self.history_list)
 
880
        self.history_treeview = treeview
 
881
        treeview.set_tooltip_column(7)
 
882
        treeview.show()
 
883
        treeview.connect('key-press-event', self.treeview_key_press)
 
884
        treeview.connect('button-press-event', self.treeview_button_press)
 
885
 
 
886
        tvcolumn = gtk.TreeViewColumn('')
 
887
        treeview.append_column(tvcolumn)
 
888
        cell = gtk.CellRendererPixbuf()
 
889
        tvcolumn.pack_start(cell, False)
 
890
        tvcolumn.set_attributes(cell, stock_id=1)
 
891
 
 
892
        tvcolumn = gtk.TreeViewColumn('Date')
 
893
        treeview.append_column(tvcolumn)
 
894
        cell = gtk.CellRendererText()
 
895
        tvcolumn.pack_start(cell, False)
 
896
        tvcolumn.add_attribute(cell, 'text', 2)
 
897
 
 
898
        tvcolumn = gtk.TreeViewColumn('Description')
 
899
        treeview.append_column(tvcolumn)
 
900
        cell = gtk.CellRendererText()
 
901
        cell.set_property('foreground', 'grey')
 
902
        cell.set_property('family', MONOSPACE_FONT)
 
903
        cell.set_property('editable', True)
 
904
        def edited_cb(cell, path, new_text, h_list):
 
905
            tx = h_list.get_value( h_list.get_iter(path), 0)
 
906
            self.wallet.set_label(tx,new_text)
 
907
            self.update_history_tab()
 
908
        cell.connect('edited', edited_cb, self.history_list)
 
909
        def editing_started(cell, entry, path, h_list):
 
910
            tx = h_list.get_value( h_list.get_iter(path), 0)
 
911
            if not self.wallet.labels.get(tx): entry.set_text('')
 
912
        cell.connect('editing-started', editing_started, self.history_list)
 
913
        tvcolumn.set_expand(True)
 
914
        tvcolumn.pack_start(cell, True)
 
915
        tvcolumn.set_attributes(cell, text=3, foreground_set = 4)
 
916
 
 
917
        tvcolumn = gtk.TreeViewColumn('Amount')
 
918
        treeview.append_column(tvcolumn)
 
919
        cell = gtk.CellRendererText()
 
920
        cell.set_alignment(1, 0.5)
 
921
        cell.set_property('family', MONOSPACE_FONT)
 
922
        tvcolumn.pack_start(cell, False)
 
923
        tvcolumn.add_attribute(cell, 'text', 5)
 
924
 
 
925
        tvcolumn = gtk.TreeViewColumn('Balance')
 
926
        treeview.append_column(tvcolumn)
 
927
        cell = gtk.CellRendererText()
 
928
        cell.set_alignment(1, 0.5)
 
929
        cell.set_property('family', MONOSPACE_FONT)
 
930
        tvcolumn.pack_start(cell, False)
 
931
        tvcolumn.add_attribute(cell, 'text', 6)
 
932
 
 
933
        tvcolumn = gtk.TreeViewColumn('Tooltip')
 
934
        treeview.append_column(tvcolumn)
 
935
        cell = gtk.CellRendererText()
 
936
        tvcolumn.pack_start(cell, False)
 
937
        tvcolumn.add_attribute(cell, 'text', 7)
 
938
        tvcolumn.set_visible(False)
 
939
 
 
940
        scroll = gtk.ScrolledWindow()
 
941
        scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
 
942
        scroll.add(treeview)
 
943
 
 
944
        self.add_tab(scroll, 'History')
 
945
        self.update_history_tab()
 
946
 
 
947
 
 
948
    def create_recv_tab(self):
 
949
        self.recv_list = gtk.ListStore(str, str, str, str, str)
 
950
        self.add_tab( self.make_address_list(True), 'Receive')
 
951
        self.update_receiving_tab()
 
952
 
 
953
    def create_book_tab(self):
 
954
        self.addressbook_list = gtk.ListStore(str, str, str)
 
955
        self.add_tab( self.make_address_list(False), 'Contacts')
 
956
        self.update_sending_tab()
 
957
 
 
958
    def make_address_list(self, is_recv):
 
959
        liststore = self.recv_list if is_recv else self.addressbook_list
 
960
        treeview = gtk.TreeView(model= liststore)
 
961
        treeview.connect('key-press-event', self.treeview_key_press)
 
962
        treeview.connect('button-press-event', self.treeview_button_press)
 
963
        treeview.show()
 
964
        if not is_recv:
 
965
            self.contacts_treeview = treeview
 
966
 
 
967
        tvcolumn = gtk.TreeViewColumn('Address')
 
968
        treeview.append_column(tvcolumn)
 
969
        cell = gtk.CellRendererText()
 
970
        cell.set_property('family', MONOSPACE_FONT)
 
971
        tvcolumn.pack_start(cell, True)
 
972
        tvcolumn.add_attribute(cell, 'text', 0)
 
973
 
 
974
        tvcolumn = gtk.TreeViewColumn('Label')
 
975
        tvcolumn.set_expand(True)
 
976
        treeview.append_column(tvcolumn)
 
977
        cell = gtk.CellRendererText()
 
978
        cell.set_property('editable', True)
 
979
        def edited_cb2(cell, path, new_text, liststore):
 
980
            address = liststore.get_value( liststore.get_iter(path), 0)
 
981
            self.wallet.set_label(address, new_text)
 
982
            self.update_receiving_tab()
 
983
            self.update_sending_tab()
 
984
            self.update_history_tab()
 
985
        cell.connect('edited', edited_cb2, liststore)
 
986
        tvcolumn.pack_start(cell, True)
 
987
        tvcolumn.add_attribute(cell, 'text', 1)
 
988
 
 
989
        tvcolumn = gtk.TreeViewColumn('Tx')
 
990
        treeview.append_column(tvcolumn)
 
991
        cell = gtk.CellRendererText()
 
992
        tvcolumn.pack_start(cell, True)
 
993
        tvcolumn.add_attribute(cell, 'text', 2)
 
994
 
 
995
        if is_recv:
 
996
            tvcolumn = gtk.TreeViewColumn('Balance')
 
997
            treeview.append_column(tvcolumn)
 
998
            cell = gtk.CellRendererText()
 
999
            tvcolumn.pack_start(cell, True)
 
1000
            tvcolumn.add_attribute(cell, 'text', 3)
 
1001
            tvcolumn = gtk.TreeViewColumn('Type')
 
1002
            treeview.append_column(tvcolumn)
 
1003
            cell = gtk.CellRendererText()
 
1004
            tvcolumn.pack_start(cell, True)
 
1005
            tvcolumn.add_attribute(cell, 'text', 4)
 
1006
 
 
1007
        scroll = gtk.ScrolledWindow()
 
1008
        scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
 
1009
        scroll.add(treeview)
 
1010
 
 
1011
        hbox = gtk.HBox()
 
1012
        if not is_recv:
 
1013
            button = gtk.Button("New")
 
1014
            button.connect("clicked", self.newaddress_dialog)
 
1015
            button.show()
 
1016
            hbox.pack_start(button,False)
 
1017
 
 
1018
        def showqrcode(w, treeview, liststore):
 
1019
            path, col = treeview.get_cursor()
 
1020
            if not path: return
 
1021
            address = liststore.get_value(liststore.get_iter(path), 0)
 
1022
            qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.H)
 
1023
            qr.addData(address)
 
1024
            qr.make()
 
1025
            boxsize = 7
 
1026
            size = qr.getModuleCount()*boxsize
 
1027
            def area_expose_cb(area, event):
 
1028
                style = area.get_style()
 
1029
                k = qr.getModuleCount()
 
1030
                for r in range(k):
 
1031
                    for c in range(k):
 
1032
                        gc = style.black_gc if qr.isDark(r, c) else style.white_gc
 
1033
                        area.window.draw_rectangle(gc, True, c*boxsize, r*boxsize, boxsize, boxsize)
 
1034
            area = gtk.DrawingArea()
 
1035
            area.set_size_request(size, size)
 
1036
            area.connect("expose-event", area_expose_cb)
 
1037
            area.show()
 
1038
            dialog = gtk.Dialog(address, parent=self.window, flags=gtk.DIALOG_MODAL|gtk.DIALOG_NO_SEPARATOR, buttons = ("ok",1))
 
1039
            dialog.vbox.add(area)
 
1040
            dialog.run()
 
1041
            dialog.destroy()
 
1042
 
 
1043
        button = gtk.Button("QR")
 
1044
        button.connect("clicked", showqrcode, treeview, liststore)
 
1045
        button.show()
 
1046
        hbox.pack_start(button,False)
 
1047
 
 
1048
        button = gtk.Button("Copy to clipboard")
 
1049
        def copy2clipboard(w, treeview, liststore):
 
1050
            import platform
 
1051
            path, col =  treeview.get_cursor()
 
1052
            if path:
 
1053
                address =  liststore.get_value( liststore.get_iter(path), 0)
 
1054
                if platform.system() == 'Windows':
 
1055
                    from Tkinter import Tk
 
1056
                    r = Tk()
 
1057
                    r.withdraw()
 
1058
                    r.clipboard_clear()
 
1059
                    r.clipboard_append( address )
 
1060
                    r.destroy()
 
1061
                else:
 
1062
                    c = gtk.clipboard_get()
 
1063
                    c.set_text( address )
 
1064
        button.connect("clicked", copy2clipboard, treeview, liststore)
 
1065
        button.show()
 
1066
        hbox.pack_start(button,False)
 
1067
 
 
1068
        if is_recv:
 
1069
            button = gtk.Button("Freeze")
 
1070
            def freeze_address(w, treeview, liststore, wallet):
 
1071
                path, col = treeview.get_cursor()
 
1072
                if path:
 
1073
                    address = liststore.get_value( liststore.get_iter(path), 0)
 
1074
                    if address in wallet.frozen_addresses:
 
1075
                        wallet.unfreeze(address)
 
1076
                    else:
 
1077
                        wallet.freeze(address)
 
1078
                    self.update_receiving_tab()
 
1079
            button.connect("clicked", freeze_address, treeview, liststore, self.wallet)
 
1080
            button.show()
 
1081
            hbox.pack_start(button,False)
 
1082
 
 
1083
        if not is_recv:
 
1084
            button = gtk.Button("Pay to")
 
1085
            def payto(w, treeview, liststore):
 
1086
                path, col =  treeview.get_cursor()
 
1087
                if path:
 
1088
                    address =  liststore.get_value( liststore.get_iter(path), 0)
 
1089
                    self.payto_entry.set_text( address )
 
1090
                    self.notebook.set_current_page(1)
 
1091
                    self.amount_entry.grab_focus()
 
1092
 
 
1093
            button.connect("clicked", payto, treeview, liststore)
 
1094
            button.show()
 
1095
            hbox.pack_start(button,False)
 
1096
 
 
1097
        vbox = gtk.VBox()
 
1098
        vbox.pack_start(scroll,True)
 
1099
        vbox.pack_start(hbox, False)
 
1100
        return vbox
 
1101
 
 
1102
    def update_status_bar(self):
 
1103
        interface = self.network.interface
 
1104
        if self.funds_error:
 
1105
            text = "Not enough funds"
 
1106
        elif interface and interface.is_connected:
 
1107
            self.network_button.set_tooltip_text("Connected to %s:%d.\n%d blocks"%(interface.host, interface.port, self.network.blockchain.height()))
 
1108
            if not self.wallet.up_to_date:
 
1109
                self.status_image.set_from_stock(gtk.STOCK_REFRESH, gtk.ICON_SIZE_MENU)
 
1110
                text = "Synchronizing..."
 
1111
            else:
 
1112
                self.status_image.set_from_stock(gtk.STOCK_YES, gtk.ICON_SIZE_MENU)
 
1113
                self.network_button.set_tooltip_text("Connected to %s:%d.\n%d blocks"%(interface.host, interface.port, self.network.blockchain.height()))
 
1114
                c, u = self.wallet.get_balance()
 
1115
                text =  "Balance: %s "%( format_satoshis(c,False,self.num_zeros) )
 
1116
                if u: text +=  "[%s unconfirmed]"%( format_satoshis(u,True,self.num_zeros).strip() )
 
1117
        else:
 
1118
            self.status_image.set_from_stock(gtk.STOCK_NO, gtk.ICON_SIZE_MENU)
 
1119
            self.network_button.set_tooltip_text("Not connected.")
 
1120
            text = "Not connected"
 
1121
 
 
1122
        self.status_bar.pop(self.context_id) 
 
1123
        self.status_bar.push(self.context_id, text)
 
1124
 
 
1125
        if self.wallet.up_to_date and self.wallet_updated:
 
1126
            self.update_history_tab()
 
1127
            self.update_receiving_tab()
 
1128
            # addressbook too...
 
1129
            self.info.set_text( self.network.banner )
 
1130
            self.wallet_updated = False
 
1131
 
 
1132
    def update_receiving_tab(self):
 
1133
        self.recv_list.clear()
 
1134
        for address in self.wallet.addresses(True):
 
1135
            Type = "R"
 
1136
            c = u = 0
 
1137
            if self.wallet.is_change(address): Type = "C"
 
1138
            if address in self.wallet.imported_keys.keys():
 
1139
                Type = "I"
 
1140
            c, u = self.wallet.get_addr_balance(address)
 
1141
            if address in self.wallet.frozen_addresses: Type = Type + "F"
 
1142
            label = self.wallet.labels.get(address)
 
1143
            h = self.wallet.history.get(address,[])
 
1144
            n = len(h)
 
1145
            tx = "0" if n==0 else "%d"%n
 
1146
            self.recv_list.append((address, label, tx, format_satoshis(c,False,self.num_zeros), Type ))
 
1147
 
 
1148
    def update_sending_tab(self):
 
1149
        # detect addresses that are not mine in history, add them here...
 
1150
        self.addressbook_list.clear()
 
1151
        #for alias, v in self.wallet.aliases.items():
 
1152
        #    s, target = v
 
1153
        #    label = self.wallet.labels.get(alias)
 
1154
        #    self.addressbook_list.append((alias, label, '-'))
 
1155
            
 
1156
        for address in self.wallet.addressbook:
 
1157
            label = self.wallet.labels.get(address)
 
1158
            n = self.wallet.get_num_tx(address)
 
1159
            self.addressbook_list.append((address, label, "%d"%n))
 
1160
 
 
1161
    def update_history_tab(self):
 
1162
        cursor = self.history_treeview.get_cursor()[0]
 
1163
        self.history_list.clear()
 
1164
 
 
1165
        for item in self.wallet.get_tx_history():
 
1166
            tx_hash, conf, is_mine, value, fee, balance, timestamp = item
 
1167
            if conf > 0:
 
1168
                try:
 
1169
                    time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
 
1170
                except Exception:
 
1171
                    time_str = "------"
 
1172
                conf_icon = gtk.STOCK_APPLY
 
1173
            elif conf == -1:
 
1174
                time_str = 'unverified'
 
1175
                conf_icon = None
 
1176
            else:
 
1177
                time_str = 'pending'
 
1178
                conf_icon = gtk.STOCK_EXECUTE
 
1179
 
 
1180
            label, is_default_label = self.wallet.get_label(tx_hash)
 
1181
            tooltip = tx_hash + "\n%d confirmations"%conf if tx_hash else ''
 
1182
            details = self.get_tx_details(tx_hash)
 
1183
 
 
1184
            self.history_list.prepend( [tx_hash, conf_icon, time_str, label, is_default_label,
 
1185
                                        format_satoshis(value,True,self.num_zeros, whitespaces=True),
 
1186
                                        format_satoshis(balance,False,self.num_zeros, whitespaces=True), tooltip, details] )
 
1187
        if cursor: self.history_treeview.set_cursor( cursor )
 
1188
 
 
1189
 
 
1190
    def get_tx_details(self, tx_hash):
 
1191
        import datetime
 
1192
        if not tx_hash: return ''
 
1193
        tx = self.wallet.transactions.get(tx_hash)
 
1194
        is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx)
 
1195
        conf, timestamp = self.wallet.verifier.get_confirmations(tx_hash)
 
1196
 
 
1197
        if timestamp:
 
1198
            time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3]
 
1199
        else:
 
1200
            time_str = 'pending'
 
1201
 
 
1202
        inputs = map(lambda x: x.get('address'), tx.inputs)
 
1203
        outputs = map(lambda x: x.get('address'), tx.d['outputs'])
 
1204
        tx_details = "Transaction Details" +"\n\n" \
 
1205
            + "Transaction ID:\n" + tx_hash + "\n\n" \
 
1206
            + "Status: %d confirmations\n"%conf
 
1207
        if is_mine:
 
1208
            if fee: 
 
1209
                tx_details += "Amount sent: %s\n"% format_satoshis(v-fee, False) \
 
1210
                              + "Transaction fee: %s\n"% format_satoshis(fee, False)
 
1211
            else:
 
1212
                tx_details += "Amount sent: %s\n"% format_satoshis(v, False) \
 
1213
                              + "Transaction fee: unknown\n"
 
1214
        else:
 
1215
            tx_details += "Amount received: %s\n"% format_satoshis(v, False) \
 
1216
 
 
1217
        tx_details += "Date: %s\n\n"%time_str \
 
1218
            + "Inputs:\n-"+ '\n-'.join(inputs) + "\n\n" \
 
1219
            + "Outputs:\n-"+ '\n-'.join(outputs)
 
1220
 
 
1221
        return tx_details
 
1222
 
 
1223
 
 
1224
 
 
1225
    def newaddress_dialog(self, w):
 
1226
 
 
1227
        title = "New Contact" 
 
1228
        dialog = gtk.Dialog(title, parent=self.window, 
 
1229
                            flags=gtk.DIALOG_MODAL|gtk.DIALOG_NO_SEPARATOR, 
 
1230
                            buttons= ("cancel", 0, "ok",1)  )
 
1231
        dialog.show()
 
1232
 
 
1233
        label = gtk.HBox()
 
1234
        label_label = gtk.Label('Label:')
 
1235
        label_label.set_size_request(120,10)
 
1236
        label_label.show()
 
1237
        label.pack_start(label_label)
 
1238
        label_entry = gtk.Entry()
 
1239
        label_entry.show()
 
1240
        label.pack_start(label_entry)
 
1241
        label.show()
 
1242
        dialog.vbox.pack_start(label, False, True, 5)
 
1243
 
 
1244
        address = gtk.HBox()
 
1245
        address_label = gtk.Label('Address:')
 
1246
        address_label.set_size_request(120,10)
 
1247
        address_label.show()
 
1248
        address.pack_start(address_label)
 
1249
        address_entry = gtk.Entry()
 
1250
        address_entry.show()
 
1251
        address.pack_start(address_entry)
 
1252
        address.show()
 
1253
        dialog.vbox.pack_start(address, False, True, 5)
 
1254
        
 
1255
        result = dialog.run()
 
1256
        address = address_entry.get_text()
 
1257
        label = label_entry.get_text()
 
1258
        dialog.destroy()
 
1259
 
 
1260
        if result == 1:
 
1261
            if is_valid(address):
 
1262
                self.wallet.add_contact(address,label)
 
1263
                self.update_sending_tab()
 
1264
            else:
 
1265
                errorDialog = gtk.MessageDialog(
 
1266
                    parent=self.window,
 
1267
                    flags=gtk.DIALOG_MODAL, 
 
1268
                    buttons= gtk.BUTTONS_CLOSE, 
 
1269
                    message_format = "Invalid address")
 
1270
                errorDialog.show()
 
1271
                errorDialog.run()
 
1272
                errorDialog.destroy()
 
1273
 
 
1274
    
 
1275
 
 
1276
class ElectrumGui():
 
1277
 
 
1278
    def __init__(self, config, network):
 
1279
        self.network = network
 
1280
        self.config = config
 
1281
 
 
1282
 
 
1283
    def main(self, url=None):
 
1284
 
 
1285
        storage = WalletStorage(self.config)
 
1286
        if not storage.file_exists:
 
1287
            action = self.restore_or_create()
 
1288
            if not action:
 
1289
                exit()
 
1290
            self.wallet = wallet = Wallet(storage)
 
1291
            gap = self.config.get('gap_limit', 5)
 
1292
            if gap != 5:
 
1293
                wallet.gap_limit = gap
 
1294
                wallet.storage.put('gap_limit', gap, True)
 
1295
 
 
1296
            self.wallet.start_threads(self.network)
 
1297
 
 
1298
            if action == 'create':
 
1299
                wallet.init_seed(None)
 
1300
                wallet.save_seed()
 
1301
                wallet.synchronize()  # generate first addresses offline
 
1302
            elif action == 'restore':
 
1303
                seed = self.seed_dialog()
 
1304
                wallet.init_seed(seed)
 
1305
                wallet.save_seed()
 
1306
                self.restore_wallet(wallet)
 
1307
                
 
1308
            else:
 
1309
                exit()
 
1310
        else:
 
1311
            self.wallet = Wallet(storage)
 
1312
            self.wallet.start_threads(self.network)
 
1313
 
 
1314
        w = ElectrumWindow(self.wallet, self.config, self.network)
 
1315
        if url: w.set_url(url)
 
1316
        gtk.main()
 
1317
 
 
1318
    def restore_or_create(self):
 
1319
        return restore_create_dialog()
 
1320
 
 
1321
    def seed_dialog(self):
 
1322
        return run_recovery_dialog()
 
1323
 
 
1324
    def verify_seed(self):
 
1325
        self.wallet.save_seed()
 
1326
        return True
 
1327
 
 
1328
    def network_dialog(self):
 
1329
        return run_network_dialog( self.network, parent=None )
 
1330
 
 
1331
    def show_seed(self):
 
1332
        show_seed_dialog(self.wallet, None, None)
 
1333
 
 
1334
    def password_dialog(self):
 
1335
        change_password_dialog(self.wallet, None, None)
 
1336
 
 
1337
    def restore_wallet(self, wallet):
 
1338
 
 
1339
        dialog = gtk.MessageDialog(
 
1340
            parent = None,
 
1341
            flags = gtk.DIALOG_MODAL, 
 
1342
            buttons = gtk.BUTTONS_CANCEL, 
 
1343
            message_format = "Please wait..."  )
 
1344
        dialog.show()
 
1345
 
 
1346
        def recover_thread( wallet, dialog ):
 
1347
            wallet.restore(lambda x:x)
 
1348
            gobject.idle_add( dialog.destroy )
 
1349
 
 
1350
        thread.start_new_thread( recover_thread, ( wallet, dialog ) )
 
1351
        r = dialog.run()
 
1352
        dialog.destroy()
 
1353
        if r==gtk.RESPONSE_CANCEL: return False
 
1354
        if not wallet.is_found():
 
1355
            show_message("No transactions found for this seed")
 
1356
 
 
1357
        return True