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