~ubuntu-branches/debian/stretch/electrum/stretch

« back to all changes in this revision

Viewing changes to gui/gui_text.py

  • Committer: Package Import Robot
  • Author(s): Vasudev Kamath
  • Date: 2013-06-19 21:44:47 UTC
  • Revision ID: package-import@ubuntu.com-20130619214447-8n0u7ryn1ftu25w8
Tags: upstream-1.8
ImportĀ upstreamĀ versionĀ 1.8

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
import curses, datetime
 
2
from decimal import Decimal
 
3
_ = lambda x:x
 
4
#from i18n import _
 
5
from electrum.util import format_satoshis, set_verbosity
 
6
from electrum.bitcoin import is_valid
 
7
 
 
8
import tty, sys
 
9
 
 
10
 
 
11
class ElectrumGui:
 
12
 
 
13
    def __init__(self, wallet, config, app=None):
 
14
        self.stdscr = curses.initscr()
 
15
        curses.noecho()
 
16
        curses.cbreak()
 
17
        curses.start_color()
 
18
        curses.use_default_colors()
 
19
        curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLUE)
 
20
        curses.init_pair(2, curses.COLOR_WHITE, curses.COLOR_CYAN)
 
21
        self.stdscr.keypad(1)
 
22
        self.stdscr.border(0)
 
23
        self.maxy, self.maxx = self.stdscr.getmaxyx()
 
24
        curses.curs_set(0)
 
25
        self.w = curses.newwin(10, 50, 5, 5)
 
26
 
 
27
        self.wallet = wallet
 
28
        self.config = config
 
29
        set_verbosity(False)
 
30
        self.tab = 0
 
31
        self.pos = 0
 
32
        self.popup_pos = 0
 
33
 
 
34
        self.str_recipient = ""
 
35
        self.str_description = ""
 
36
        self.str_amount = ""
 
37
        self.str_fee = ""
 
38
        
 
39
        self.wallet.interface.register_callback('updated', self.refresh)
 
40
        self.wallet.interface.register_callback('connected', self.refresh)
 
41
        self.wallet.interface.register_callback('disconnected', self.refresh)
 
42
        self.wallet.interface.register_callback('disconnecting', self.refresh)
 
43
        self.tab_names = [_("History"), _("Send"), _("Receive"), _("Contacts"), _("Wall")]
 
44
        self.num_tabs = len(self.tab_names)
 
45
        
 
46
 
 
47
    def restore_or_create(self):
 
48
        pass
 
49
 
 
50
    def verify_seed(self):
 
51
        pass
 
52
    
 
53
    def get_string(self, y, x):
 
54
        curses.curs_set(1)
 
55
        curses.echo()
 
56
        self.stdscr.addstr( y, x, " "*20, curses.A_REVERSE)
 
57
        s = self.stdscr.getstr(y,x)
 
58
        curses.noecho()
 
59
        curses.curs_set(0)
 
60
        return s
 
61
 
 
62
 
 
63
    def print_history(self):
 
64
        width = [20, 40, 14, 14]
 
65
        delta = (self.maxx - sum(width) - 4)/3
 
66
        format_str = "%"+"%d"%width[0]+"s"+"%"+"%d"%(width[1]+delta)+"s"+"%"+"%d"%(width[2]+delta)+"s"+"%"+"%d"%(width[3]+delta)+"s"
 
67
 
 
68
        b = 0 
 
69
        messages = []
 
70
 
 
71
        for item in self.wallet.get_tx_history():
 
72
            tx_hash, conf, is_mine, value, fee, balance, timestamp = item
 
73
            if conf:
 
74
                try:
 
75
                    time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3]
 
76
                except:
 
77
                    time_str = "------"
 
78
            else:
 
79
                time_str = 'pending'
 
80
 
 
81
            label, is_default_label = self.wallet.get_label(tx_hash)
 
82
            messages.append( format_str%( time_str, label, format_satoshis(value), format_satoshis(balance) ) )
 
83
 
 
84
        self.print_list(messages[::-1], format_str%( _("Date"), _("Description"), _("Amount"), _("Balance")))
 
85
 
 
86
 
 
87
    def print_balance(self):
 
88
        if self.wallet.interface and self.wallet.interface.is_connected:
 
89
            if not self.wallet.up_to_date:
 
90
                msg = _( "Synchronizing..." )
 
91
            else: 
 
92
                c, u =  self.wallet.get_balance()
 
93
                msg = _("Balance")+": %f  "%(Decimal( c ) / 100000000)
 
94
                if u: msg += "  [%f unconfirmed]"%(Decimal( u ) / 100000000)
 
95
        else:
 
96
                msg = _( "Not connected" )
 
97
            
 
98
        self.stdscr.addstr( self.maxy -1, 3, msg)
 
99
 
 
100
        for i in range(self.num_tabs):
 
101
            self.stdscr.addstr( 0, 2 + 2*i + len(''.join(self.tab_names[0:i])), ' '+self.tab_names[i]+' ', curses.A_BOLD if self.tab == i else 0)
 
102
            
 
103
        self.stdscr.addstr( self.maxy -1, self.maxx-30, ' '.join([_("Settings"), _("Network"), _("Quit")]))
 
104
 
 
105
 
 
106
    def print_contacts(self):
 
107
        messages = map(lambda addr: "%30s    %30s       "%(addr, self.wallet.labels.get(addr,"")), self.wallet.addressbook)
 
108
        self.print_list(messages, "%19s  %25s "%("Address", "Label"))
 
109
 
 
110
    def print_receive(self):
 
111
        messages = map(lambda addr: "%30s    %30s       "%(addr, self.wallet.labels.get(addr,"")), self.wallet.addresses())
 
112
        self.print_list(messages, "%19s  %25s "%("Address", "Label"))
 
113
 
 
114
    def print_edit_line(self, y, label, text, index, size):
 
115
        text += " "*(size - len(text) )
 
116
        self.stdscr.addstr( y, 2, label)
 
117
        self.stdscr.addstr( y, 15, text, curses.A_REVERSE if self.pos%6==index else curses.color_pair(1))
 
118
 
 
119
    def print_send_tab(self):
 
120
        self.stdscr.clear()
 
121
        self.print_edit_line(3, _("Pay to"), self.str_recipient, 0, 40)
 
122
        self.print_edit_line(5, _("Description"), self.str_description, 1, 40)
 
123
        self.print_edit_line(7, _("Amount"), self.str_amount, 2, 15)
 
124
        self.print_edit_line(9, _("Fee"), self.str_fee, 3, 15)
 
125
        self.stdscr.addstr( 12, 15, _("[Send]"), curses.A_REVERSE if self.pos%6==4 else curses.color_pair(2))
 
126
        self.stdscr.addstr( 12, 25, _("[Clear]"), curses.A_REVERSE if self.pos%6==5 else curses.color_pair(2))
 
127
 
 
128
    def print_banner(self):
 
129
        for i, x in enumerate( self.wallet.interface.banner.split('\n') ):
 
130
            self.stdscr.addstr( 1+i, 1, x )
 
131
 
 
132
    def print_list(self, list, firstline):
 
133
        self.maxpos = len(list)
 
134
        if not self.maxpos: return
 
135
        firstline += " "*(self.maxx -2 - len(firstline))
 
136
        self.stdscr.addstr( 1, 1, firstline )
 
137
        for i in range(self.maxy-4):
 
138
            msg = list[i] if i < len(list) else ""
 
139
            msg += " "*(self.maxx - 2 - len(msg))
 
140
            self.stdscr.addstr( i+2, 1, msg[0:self.maxx - 2], curses.A_REVERSE if i == (self.pos % self.maxpos) else 0)
 
141
 
 
142
    def refresh(self):
 
143
        if self.tab == -1: return
 
144
        self.stdscr.border(0)
 
145
        self.print_balance()
 
146
        self.stdscr.refresh()
 
147
 
 
148
    def main_command(self):
 
149
        c = self.stdscr.getch()
 
150
        print c
 
151
        if   c == curses.KEY_RIGHT: self.tab = (self.tab + 1)%self.num_tabs
 
152
        elif c == curses.KEY_LEFT: self.tab = (self.tab - 1)%self.num_tabs
 
153
        elif c == curses.KEY_DOWN: self.pos +=1
 
154
        elif c == curses.KEY_UP: self.pos -= 1
 
155
        elif c == 9: self.pos +=1 # tab
 
156
        elif curses.unctrl(c) in ['^W', '^C', '^X', '^Q']: self.tab = -1
 
157
        elif curses.unctrl(c) in ['^N']: self.network_dialog()
 
158
        elif curses.unctrl(c) == '^S': self.settings_dialog()
 
159
        else: return c
 
160
        if self.pos<0: self.pos=0
 
161
        if self.pos>=self.maxpos: self.pos=self.maxpos - 1
 
162
 
 
163
    def run_tab(self, i, print_func, exec_func):
 
164
        while self.tab == i:
 
165
            self.stdscr.clear()
 
166
            print_func()
 
167
            self.refresh()
 
168
            c = self.main_command()
 
169
            if c: exec_func(c)
 
170
 
 
171
 
 
172
    def run_history_tab(self, c):
 
173
        if c == 10:
 
174
            out = self.run_popup('',["blah","foo"])
 
175
            
 
176
 
 
177
    def edit_str(self, target, c, is_num=False):
 
178
        if c==263 and target:
 
179
            target = target[:-1]
 
180
        elif not is_num or curses.unctrl(c) in '0123456789.':
 
181
            target += curses.unctrl(c)
 
182
        return target
 
183
 
 
184
 
 
185
    def run_send_tab(self, c):
 
186
        if self.pos%6 == 0:
 
187
            self.str_recipient = self.edit_str(self.str_recipient, c)
 
188
        if self.pos%6 == 1:
 
189
            self.str_description = self.edit_str(self.str_description, c)
 
190
        if self.pos%6 == 2:
 
191
            self.str_amount = self.edit_str(self.str_amount, c, True)
 
192
        elif self.pos%6 == 3:
 
193
            self.str_fee = self.edit_str(self.str_fee, c, True)
 
194
        elif self.pos%6==4:
 
195
            if c == 10: self.do_send()
 
196
        elif self.pos%6==5:
 
197
            if c == 10: self.do_clear()
 
198
 
 
199
            
 
200
    def run_receive_tab(self, c):
 
201
        if c == 10:
 
202
            out = self.run_popup('Address', ["Edit label", "Freeze", "Prioritize"])
 
203
            
 
204
    def run_contacts_tab(self, c):
 
205
        if c == 10:
 
206
            out = self.run_popup('Adress', ["Copy", "Pay to", "Edit label", "Delete"]).get('button')
 
207
            address = self.wallet.addressbook[self.pos%len(self.wallet.addressbook)]
 
208
            if out == "Pay to":
 
209
                self.tab = 1
 
210
                self.str_recipient = address 
 
211
                self.pos = 2
 
212
            elif out == "Edit label":
 
213
                s = self.get_string(6 + self.pos, 18)
 
214
                if s:
 
215
                    self.wallet.labels[address] = s
 
216
            
 
217
    def run_banner_tab(self, c):
 
218
        self.show_message(repr(c))
 
219
        pass
 
220
 
 
221
    def main(self,url):
 
222
 
 
223
        tty.setraw(sys.stdin)
 
224
        while self.tab != -1:
 
225
            self.run_tab(0, self.print_history, self.run_history_tab)
 
226
            self.run_tab(1, self.print_send_tab, self.run_send_tab)
 
227
            self.run_tab(2, self.print_receive, self.run_receive_tab)
 
228
            self.run_tab(3, self.print_contacts, self.run_contacts_tab)
 
229
            self.run_tab(4, self.print_banner, self.run_banner_tab)
 
230
 
 
231
        tty.setcbreak(sys.stdin)
 
232
        curses.nocbreak()
 
233
        self.stdscr.keypad(0)
 
234
        curses.echo()
 
235
        curses.endwin()
 
236
 
 
237
 
 
238
    def do_clear(self):
 
239
        self.str_amount = ''
 
240
        self.str_recipient = ''
 
241
        self.str_fee = ''
 
242
        self.str_description = ''
 
243
 
 
244
    def do_send(self):
 
245
        if not is_valid(self.str_recipient):
 
246
            self.show_message(_('Invalid Bitcoin address'))
 
247
            return
 
248
        try:
 
249
            amount = int( Decimal( self.str_amount) * 100000000 )
 
250
        except:
 
251
            self.show_message(_('Invalid Amount'))
 
252
            return
 
253
        try:
 
254
            fee = int( Decimal( self.str_fee) * 100000000 )
 
255
        except:
 
256
            self.show_message(_('Invalid Fee'))
 
257
            return
 
258
 
 
259
        if self.wallet.use_encryption:
 
260
            password = self.password_dialog()
 
261
            if not password:
 
262
                return
 
263
        else:
 
264
            password = None
 
265
 
 
266
        try:
 
267
            tx = self.wallet.mktx( [(self.str_recipient, amount)], password, fee)
 
268
        except BaseException, e:
 
269
            self.show_message(str(e))
 
270
            return
 
271
            
 
272
        if self.str_description: 
 
273
            self.wallet.labels[tx.hash()] = self.str_description
 
274
 
 
275
        h = self.wallet.send_tx(tx)
 
276
        self.show_message(_("Please wait..."), getchar=False)
 
277
        self.wallet.tx_event.wait()
 
278
        status, msg = self.wallet.receive_tx( h )
 
279
 
 
280
        if status:
 
281
            self.show_message(_('Payment sent.'))
 
282
            self.do_clear()
 
283
            #self.update_contacts_tab()
 
284
        else:
 
285
            self.show_message(_('Error'))
 
286
 
 
287
 
 
288
    def show_message(self, message, getchar = True):
 
289
        w = self.w
 
290
        w.clear()
 
291
        w.border(0)
 
292
        w.addstr(2,2,message)
 
293
        w.refresh()
 
294
        if getchar: c = self.stdscr.getch()
 
295
 
 
296
 
 
297
    def run_popup(self, title, items):
 
298
        return self.run_dialog(title, map(lambda x: {'type':'button','label':x}, items), interval=1, y_pos = self.pos+3)
 
299
 
 
300
 
 
301
    def network_dialog(self):
 
302
        out = self.run_dialog('Network', [
 
303
            {'label':'server', 'type':'str', 'value':self.wallet.interface.server},
 
304
            {'label':'proxy', 'type':'str', 'value':self.config.get('proxy', '')},
 
305
            ], buttons = 1)
 
306
        if out:
 
307
            if out.get('server'):
 
308
                server = out.get('server')
 
309
                if out.get('proxy'):
 
310
                    proxy = self.parse_proxy_options(out.get('proxy'))
 
311
                else:
 
312
                    proxy = None
 
313
 
 
314
                self.wallet.config.set_key("proxy", proxy, True)
 
315
                self.wallet.config.set_key("server", server, True)
 
316
                self.wallet.interface.set_server(server, proxy)
 
317
                
 
318
 
 
319
 
 
320
    def settings_dialog(self):
 
321
        out = self.run_dialog('Settings', [
 
322
            {'label':'Default GUI', 'type':'list', 'choices':['classic','lite','gtk','text'], 'value':self.config.get('gui')},
 
323
            {'label':'Default fee', 'type':'satoshis', 'value': format_satoshis(self.config.get('fee_per_kb')).strip() }
 
324
            ], buttons = 1)
 
325
        if out:
 
326
            if out.get('Default GUI'):
 
327
                self.config.set_key('gui', out['Default GUI'], True)
 
328
            if out.get('Default fee'):
 
329
                fee = int ( Decimal( out['Default fee']) *10000000 )
 
330
                self.config.set_key('fee_per_kb', fee, True)
 
331
 
 
332
 
 
333
    def password_dialog(self):
 
334
        out = self.run_dialog('Password', [
 
335
            {'label':'Password', 'type':'password', 'value':''}
 
336
            ], buttons = 1)
 
337
        return out.get('Password')
 
338
        
 
339
 
 
340
    def run_dialog(self, title, items, interval=2, buttons=None, y_pos=3):
 
341
        self.popup_pos = 0
 
342
        
 
343
        self.w = curses.newwin( 5 + len(items)*interval + (2 if buttons else 0), 50, y_pos, 5)
 
344
        w = self.w
 
345
        out = {}
 
346
        while True:
 
347
            w.clear()
 
348
            w.border(0)
 
349
            w.addstr( 0, 2, title)
 
350
 
 
351
            num = len(items)
 
352
 
 
353
            numpos = num
 
354
            if buttons: numpos += 2
 
355
 
 
356
            for i in range(num):
 
357
                item = items[i]
 
358
                label = item.get('label')
 
359
                if item.get('type') == 'list':
 
360
                    value = item.get('value','')
 
361
                elif item.get('type') == 'satoshis':
 
362
                    value = item.get('value','')
 
363
                elif item.get('type') == 'str':
 
364
                    value = item.get('value','')
 
365
                elif item.get('type') == 'password':
 
366
                    value = '*'*len(item.get('value',''))
 
367
                else:
 
368
                    value = ''
 
369
 
 
370
                if len(value)<20: value += ' '*(20-len(value))
 
371
 
 
372
                if item.has_key('value'):
 
373
                    w.addstr( 2+interval*i, 2, label)
 
374
                    w.addstr( 2+interval*i, 15, value, curses.A_REVERSE if self.popup_pos%numpos==i else curses.color_pair(1) )
 
375
                else:
 
376
                    w.addstr( 2+interval*i, 2, label, curses.A_REVERSE if self.popup_pos%numpos==i else 0)
 
377
 
 
378
            if buttons:
 
379
                w.addstr( 5+interval*i, 10, "[  ok  ]",     curses.A_REVERSE if self.popup_pos%numpos==(numpos-2) else curses.color_pair(2))
 
380
                w.addstr( 5+interval*i, 25, "[cancel]", curses.A_REVERSE if self.popup_pos%numpos==(numpos-1) else curses.color_pair(2))
 
381
                
 
382
            w.refresh()
 
383
 
 
384
            c = self.stdscr.getch()
 
385
            if c in [ord('q'), 27]: break
 
386
            elif c == curses.KEY_UP: self.popup_pos -= 1
 
387
            elif c == curses.KEY_DOWN: self.popup_pos +=1
 
388
            else:
 
389
                i = self.popup_pos%numpos
 
390
                if buttons and c==10:
 
391
                    if i == numpos-2:
 
392
                        return out
 
393
                    elif i == numpos -1:
 
394
                        return {}
 
395
 
 
396
                item = items[i]
 
397
                _type = item.get('type')
 
398
 
 
399
                if _type == 'str':
 
400
                    item['value'] = self.edit_str(item['value'], c)
 
401
                    out[item.get('label')] = item.get('value')
 
402
 
 
403
                elif _type == 'password':
 
404
                    item['value'] = self.edit_str(item['value'], c)
 
405
                    out[item.get('label')] = item ['value']
 
406
 
 
407
                elif _type == 'satoshis':
 
408
                    item['value'] = self.edit_str(item['value'], c, True)
 
409
                    out[item.get('label')] = item.get('value')
 
410
 
 
411
                elif _type == 'list':
 
412
                    choices = item.get('choices')
 
413
                    try:
 
414
                        j = choices.index(item.get('value'))
 
415
                    except:
 
416
                        j = 0
 
417
                    new_choice = choices[(j + 1)% len(choices)]
 
418
                    item['value'] = new_choice
 
419
                    out[item.get('label')] = item.get('value')
 
420
                    
 
421
                elif _type == 'button':
 
422
                    out['button'] = item.get('label')
 
423
                    break
 
424
 
 
425
        return out
 
426