7e286810b1d22261e2c99c4ab72395a3a5addc86
[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 = self.wallet.labels.get(tx_hash)
78             is_default_label = (label == '') or (label is None)
79             if is_default_label: label = tx['default_label']
80
81             #label += ' '*(40 - len(label) )
82             messages.append( format_str%( time_str, label, format_satoshis(v), format_satoshis(b) ) )
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%5==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%5==4 else 0)
126
127     def getstr_send(self):
128         curses.curs_set(1)
129         curses.echo()
130         if self.pos%5==0:
131             s = self.stdscr.getstr(3, 15)
132             if s: self.str_recipient = s
133         elif self.pos%5==1:
134             s = self.stdscr.getstr(5, 15)
135             if s: self.str_description = s
136         elif self.pos%5==2:
137             s = self.stdscr.getstr(7, 15)
138             if s: self.str_amount = s
139         elif self.pos%5==3:
140             s = self.stdscr.getstr(9, 15)
141             if s: self.str_fee = s
142         else:
143             pass
144         curses.noecho()
145         curses.curs_set(0)
146         self.print_send_tab()
147
148     def print_banner(self):
149         self.stdscr.addstr( 1, 1, self.wallet.banner )
150
151     def print_list(self, list, firstline):
152         firstline += " "*(self.maxx -2 - len(firstline))
153         self.stdscr.addstr( 1, 1, firstline )
154         for i in range(self.maxy-4):
155             msg = list[i] if i < len(list) else ""
156             msg += " "*(self.maxx - 2 - len(msg))
157             self.stdscr.addstr( i+2, 1, msg[0:self.maxx - 2], curses.A_REVERSE if i == (self.pos % len(list)) else 0)
158
159     def refresh(self):
160         self.stdscr.border(0)
161         self.print_balance()
162         self.stdscr.refresh()
163
164     def main_command(self):
165         c = self.stdscr.getch()
166         if   c == curses.KEY_RIGHT: self.tab = (self.tab + 1)%self.num_tabs
167         elif c == curses.KEY_LEFT: self.tab = (self.tab - 1)%self.num_tabs
168         elif c == curses.KEY_DOWN: self.pos +=1
169         elif c == curses.KEY_UP: self.pos -= 1
170         elif c == 9: self.pos +=1 # tab
171         elif c in [27, ord('q')]: self.tab = -1
172         elif c == ord('n'): self.network_dialog()
173         elif c == ord('s'): self.settings_dialog()
174         else: return c
175
176     def run_tab(self, i, print_func, exec_func):
177         while self.tab == i:
178             self.stdscr.clear()
179             print_func()
180             self.refresh()
181             c = self.main_command()
182             if c: exec_func(c)
183
184
185     def run_history_tab(self, c):
186         if c == 10:
187             out = self.run_popup('',["blah","foo"])
188             
189     
190     def run_send_tab(self, c):
191         if c == 10:
192             if self.pos%5==4:
193                 self.do_send()
194             else:
195                 self.getstr_send()
196                 self.print_send_tab()
197             
198     def run_receive_tab(self, c):
199         if c == 10:
200             out = self.run_popup('Address', ["Edit label", "Freeze", "Prioritize"])
201             
202     def run_contacts_tab(self, c):
203         if c == 10:
204             out = self.run_popup('Adress', ["Copy", "Pay to", "Edit label", "Delete"]).get('button')
205             address = self.wallet.addressbook[self.pos%len(self.wallet.addressbook)]
206             if out == "Pay to":
207                 self.tab = 1
208                 self.str_recipient = address 
209                 self.pos = 2
210             elif out == "Edit label":
211                 s = self.get_string(6 + self.pos, 18)
212                 if s:
213                     self.wallet.labels[address] = s
214             
215     def run_banner_tab(self, c):
216         pass
217
218     def main(self,url):
219
220         while self.tab != -1:
221             self.run_tab(0, self.print_history, self.run_history_tab)
222             self.run_tab(1, self.print_send_tab, self.run_send_tab)
223             self.run_tab(2, self.print_receive, self.run_receive_tab)
224             self.run_tab(3, self.print_contacts, self.run_contacts_tab)
225             self.run_tab(4, self.print_banner, self.run_banner_tab)
226
227         curses.nocbreak();
228         self.stdscr.keypad(0);
229         curses.echo()            
230         curses.endwin()
231
232
233
234     def do_send(self):
235         if not self.wallet.is_valid(self.str_recipient):
236             self.show_message(_('Invalid Bitcoin address'))
237             return
238         try:
239             amount = int( Decimal( self.str_amount) * 100000000 )
240         except:
241             self.show_message(_('Invalid Amount'))
242             return
243         try:
244             fee = int( Decimal( self.str_fee) * 100000000 )
245         except:
246             self.show_message(_('Invalid Fee'))
247             return
248
249         if self.wallet.use_encryption:
250             password = self.password_dialog()
251             if not password:
252                 return
253         else:
254             password = None
255
256
257     def show_message(self, message):
258         w = self.w
259         w.clear()
260         w.border(0)
261         w.addstr(2,2,message)
262         w.refresh()
263         c = self.stdscr.getch()
264
265
266     def run_popup(self, title, items):
267         return self.run_dialog(title, map(lambda x: {'type':'button','label':x}, items), interval=1, y_pos = self.pos+3)
268
269
270     def network_dialog(self):
271         out = self.run_dialog('Network', [
272             {'label':'server', 'type':'str', 'value':self.wallet.interface.server},
273             {'label':'proxy', 'type':'str', 'value':self.config.get('proxy')},
274             ], buttons = 1)
275         if out:
276             if out.get('server'):  self.wallet.interface.set_server(out.get('server'))
277
278
279     def settings_dialog(self):
280         out = self.run_dialog('Settings', [
281             {'label':'Default GUI', 'type':'list', 'choices':['classic','lite','gtk','text'], 'value':self.config.get('gui')},
282             {'label':'Default fee', 'type':'satoshis', 'value':self.config.get('fee')}
283             ], buttons = 1)
284         if out:
285             if out.get('Default GUI'): self.config.set_key('gui', out['Default GUI'], True)
286
287
288     def password_dialog(self):
289         out = self.run_dialog('Password', [
290             {'label':'Password', 'type':'str'}
291             ], buttons = 1)
292         return out
293         
294
295     def run_dialog(self, title, items, interval=2, buttons=None, y_pos=3):
296         self.w = curses.newwin( 2+len(items)*interval, 50, y_pos, 5)
297         w = self.w
298         #items.append({'label':'cancel','type':'button'})
299         if buttons: items.append({'label':' ok ','type':'button'})
300         out = {}
301         while True:
302             w.clear()
303             w.border(0)
304             w.addstr( 0, 2, title)
305
306             num = len(items)
307             for i in range(num):
308                 item = items[i]
309                 label = item.get('label')
310                 if item.get('type') == 'list':
311                     value = item.get('value','')
312                 elif item.get('type') == 'satoshis':
313                     value = format_satoshis(item.get('value'))
314                 elif item.get('type') == 'str':
315                     value = item.get('value','')
316                 else:
317                     value = None
318                 if value:
319                     w.addstr( 1+interval*i, 2, label)
320                     w.addstr( 1+interval*i, 15, value, curses.A_REVERSE if self.popup_pos%num==i else curses.color_pair(1) )
321                 else:
322                     w.addstr( 1+interval*i, 2, label, curses.A_REVERSE if self.popup_pos%num==i else 0)
323                 
324             w.refresh()
325
326             c = self.stdscr.getch()
327             if c in [ord('q'), 27]: break
328             elif c == curses.KEY_UP: self.popup_pos -= 1
329             elif c == curses.KEY_DOWN: self.popup_pos +=1
330             elif c == 10:
331                 i = self.popup_pos%num
332                 item = items[i]
333                 _type = item.get('type')
334                 if _type == 'str':
335                     item['value'] = self.get_string(2+2*i, 15)
336                     out[item.get('label')] = item.get('value')
337
338                 elif _type == 'satoshis':
339                     curses.curs_set(1)
340                     curses.echo()
341                     s = w.getstr(2+2*i, 15)
342                     curses.noecho()
343                     curses.curs_set(0)
344                     try:
345                         s = int( Decimal(s)*100000000 )
346                         item['value'] = s
347                         out[item.get('label')] = item.get('value')
348                     except:
349                         pass
350                 elif _type == 'list':
351                     choices = item.get('choices')
352                     try:
353                         j = choices.index(item.get('value'))
354                     except:
355                         j = 0
356                     new_choice = choices[(j + 1)% len(choices)]
357                     item['value'] = new_choice
358                     out[item.get('label')] = item.get('value')
359                     
360
361                 elif _type == 'button':
362                     out['button'] = item.get('label')
363                     break
364
365         
366         return out
367