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