class ElectrumGui: def __init__(self, config, network): self.network = network self.config = config storage = WalletStorage(config.get_wallet_path()) if not storage.file_exists: print "Wallet not found. try 'electrum-pkb create'" exit() self.done = 0 self.last_balance = "" set_verbosity(False) self.str_recipient = "" self.str_description = "" self.str_amount = "" self.str_fee = "" self.wallet = Wallet(storage) self.wallet.start_threads(network) self.contacts = StoreDict(self.config, 'contacts') self.wallet.network.register_callback('updated', self.updated) self.wallet.network.register_callback('connected', self.connected) self.wallet.network.register_callback('disconnected', self.disconnected) self.wallet.network.register_callback('disconnecting', self.disconnecting) self.wallet.network.register_callback('peers', self.peers) self.wallet.network.register_callback('banner', self.print_banner) self.commands = [_("[h] - displays this help text"), \ _("[i] - display transaction history"), \ _("[o] - enter payment order"), \ _("[p] - print stored payment order"), \ _("[s] - send stored payment order"), \ _("[r] - show own receipt addresses"), \ _("[c] - display contacts"), \ _("[b] - print server banner"), \ _("[q] - quit") ] self.num_commands = len(self.commands) def main_command(self): self.print_balance() c = raw_input("enter command: ") if c == "h" : self.print_commands() elif c == "i" : self.print_history() elif c == "o" : self.enter_order() elif c == "p" : self.print_order() elif c == "s" : self.send_order() elif c == "r" : self.print_addresses() elif c == "c" : self.print_contacts() elif c == "b" : self.print_banner() elif c == "n" : self.network_dialog() elif c == "e" : self.settings_dialog() elif c == "q" : self.done = 1 else: self.print_commands() def peers(self): print("got peers list:") l = filter_protocol(self.wallet.network.get_servers(), 's') for s in l: print (s) def connected(self): print ("connected") def disconnected(self): print ("disconnected") def disconnecting(self): print ("disconnecting") def updated(self): s = self.get_balance() if s != self.last_balance: print(s) self.last_balance = s return True def print_commands(self): self.print_list(self.commands, "Available commands") def print_history(self): width = [20, 40, 14, 14] delta = (80 - sum(width) - 4)/3 format_str = "%"+"%d"%width[0]+"s"+"%"+"%d"%(width[1]+delta)+"s"+"%" \ + "%d"%(width[2]+delta)+"s"+"%"+"%d"%(width[3]+delta)+"s" b = 0 messages = [] for item in self.wallet.get_history(): tx_hash, confirmations, value, timestamp, balance = item if confirmations: try: time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3] except Exception: time_str = "unknown" else: time_str = 'pending' label, is_default_label = self.wallet.get_label(tx_hash) messages.append( format_str%( time_str, label, format_satoshis(value, whitespaces=True), format_satoshis(balance, whitespaces=True) ) ) self.print_list(messages[::-1], format_str%( _("Date"), _("Description"), _("Amount"), _("Balance"))) def print_balance(self): print(self.get_balance()) def get_balance(self): if self.wallet.network.is_connected(): if not self.wallet.up_to_date: msg = _( "Synchronizing..." ) else: c, u, x = self.wallet.get_balance() msg = _("Balance")+": %f "%(Decimal(c) / 100000000) if u: msg += " [%f unconfirmed]"%(Decimal(u) / 100000000) if x: msg += " [%f unmatured]"%(Decimal(x) / 100000000) else: msg = _( "Not connected" ) return(msg) def print_contacts(self): messages = map(lambda x: "%20s %45s "%(x[0], x[1][1]), self.contacts.items()) self.print_list(messages, "%19s %25s "%("Key", "Value")) def print_addresses(self): messages = map(lambda addr: "%30s %30s "%(addr, self.wallet.labels.get(addr,"")), self.wallet.addresses()) self.print_list(messages, "%19s %25s "%("Address", "Label")) def print_order(self): print("send order to " + self.str_recipient + ", amount: " + self.str_amount \ + "\nfee: " + self.str_fee + ", desc: " + self.str_description) def enter_order(self): self.str_recipient = raw_input("Pay to: ") self.str_description = raw_input("Description : ") self.str_amount = raw_input("Amount: ") self.str_fee = raw_input("Fee: ") def send_order(self): self.do_send() def print_banner(self): for i, x in enumerate( self.wallet.network.banner.split('\n') ): print( x ) def print_list(self, list, firstline): self.maxpos = len(list) if not self.maxpos: return print(firstline) for i in range(self.maxpos): msg = list[i] if i < len(list) else "" print(msg) def main(self,url): while self.done == 0: self.main_command() def do_send(self): if not is_valid(self.str_recipient): print(_('Invalid ParkByte address')) return try: amount = int( Decimal( self.str_amount) * 100000000 ) except Exception: print(_('Invalid Amount')) return try: fee = int( Decimal( self.str_fee) * 100000000 ) except Exception: print(_('Invalid Fee')) return if self.wallet.use_encryption: password = self.password_dialog() if not password: return else: password = None c = "" while c != "y": c = raw_input("ok to send (y/n)?") if c == "n": return try: tx = self.wallet.mktx( [(self.str_recipient, amount)], password, fee) except Exception as e: print(str(e)) return if self.str_description: self.wallet.labels[tx.hash()] = self.str_description h = self.wallet.send_tx(tx) print(_("Please wait...")) self.wallet.tx_event.wait() status, msg = self.wallet.receive_tx( h, tx ) if status: print(_('Payment sent.')) #self.do_clear() #self.update_contacts_tab() else: print(_('Error')) def network_dialog(self): print("use 'electrum-pkb setconfig server/proxy' to change your network settings") return True def settings_dialog(self): print("use 'electrum-pkb setconfig' to change your settings") return True def password_dialog(self): return getpass.getpass() # XXX unused def run_receive_tab(self, c): #if c == 10: # out = self.run_popup('Address', ["Edit label", "Freeze", "Prioritize"]) return def run_contacts_tab(self, c): pass
class ElectrumGui: def __init__(self, config, network): self.config = config self.network = network storage = WalletStorage(config.get_wallet_path()) if not storage.file_exists: print "Wallet not found. try 'electrum-pkb create'" exit() self.wallet = Wallet(storage) self.wallet.start_threads(self.network) self.contacts = StoreDict(self.config, 'contacts') locale.setlocale(locale.LC_ALL, '') self.encoding = locale.getpreferredencoding() self.stdscr = curses.initscr() curses.noecho() curses.cbreak() curses.start_color() curses.use_default_colors() curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLUE) curses.init_pair(2, curses.COLOR_WHITE, curses.COLOR_CYAN) self.stdscr.keypad(1) self.stdscr.border(0) self.maxy, self.maxx = self.stdscr.getmaxyx() self.set_cursor(0) self.w = curses.newwin(10, 50, 5, 5) set_verbosity(False) self.tab = 0 self.pos = 0 self.popup_pos = 0 self.str_recipient = "" self.str_description = "" self.str_amount = "" self.str_fee = "" self.history = None if self.network: self.network.register_callback('updated', self.update) self.network.register_callback('connected', self.refresh) self.network.register_callback('disconnected', self.refresh) self.network.register_callback('disconnecting', self.refresh) self.tab_names = [ _("History"), _("Send"), _("Receive"), _("Contacts"), _("Wall") ] self.num_tabs = len(self.tab_names) def set_cursor(self, x): try: curses.curs_set(x) except Exception: pass def restore_or_create(self): pass def verify_seed(self): pass def get_string(self, y, x): self.set_cursor(1) curses.echo() self.stdscr.addstr(y, x, " " * 20, curses.A_REVERSE) s = self.stdscr.getstr(y, x) curses.noecho() self.set_cursor(0) return s def update(self): self.update_history() if self.tab == 0: self.print_history() self.refresh() def print_history(self): width = [20, 40, 14, 14] delta = (self.maxx - sum(width) - 4) / 3 format_str = "%" + "%d" % width[0] + "s" + "%" + "%d" % ( width[1] + delta) + "s" + "%" + "%d" % ( width[2] + delta) + "s" + "%" + "%d" % (width[3] + delta) + "s" if self.history is None: self.update_history() self.print_list( self.history[::-1], format_str % (_("Date"), _("Description"), _("Amount"), _("Balance"))) def update_history(self): width = [20, 40, 14, 14] delta = (self.maxx - sum(width) - 4) / 3 format_str = "%" + "%d" % width[0] + "s" + "%" + "%d" % ( width[1] + delta) + "s" + "%" + "%d" % ( width[2] + delta) + "s" + "%" + "%d" % (width[3] + delta) + "s" b = 0 self.history = [] for item in self.wallet.get_history(): tx_hash, conf, value, timestamp, balance = item if conf: try: time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3] except Exception: time_str = "------" else: time_str = 'pending' label, is_default_label = self.wallet.get_label(tx_hash) if len(label) > 40: label = label[0:37] + '...' self.history.append( format_str % (time_str, label, format_satoshis(value, whitespaces=True), format_satoshis(balance, whitespaces=True))) def print_balance(self): if not self.network: msg = _("Offline") elif self.network.is_connected(): if not self.wallet.up_to_date: msg = _("Synchronizing...") else: c, u, x = self.wallet.get_balance() msg = _("Balance") + ": %f " % (Decimal(c) / 100000000) if u: msg += " [%f unconfirmed]" % (Decimal(u) / 100000000) if x: msg += " [%f unmatured]" % (Decimal(x) / 100000000) else: msg = _("Not connected") self.stdscr.addstr(self.maxy - 1, 3, msg) for i in range(self.num_tabs): 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) self.stdscr.addstr(self.maxy - 1, self.maxx - 30, ' '.join([_("Settings"), _("Network"), _("Quit")])) def print_contacts(self): messages = map(lambda x: "%20s %45s " % (x[0], x[1][1]), self.contacts.items()) self.print_list(messages, "%19s %15s " % ("Key", "Value")) def print_receive(self): fmt = "%-35s %-30s" messages = map( lambda addr: fmt % (addr, self.wallet.labels.get(addr, "")), self.wallet.addresses()) self.print_list(messages, fmt % ("Address", "Label")) def print_edit_line(self, y, label, text, index, size): text += " " * (size - len(text)) self.stdscr.addstr(y, 2, label) self.stdscr.addstr( y, 15, text, curses.A_REVERSE if self.pos % 6 == index else curses.color_pair(1)) def print_send_tab(self): self.stdscr.clear() self.print_edit_line(3, _("Pay to"), self.str_recipient, 0, 40) self.print_edit_line(5, _("Description"), self.str_description, 1, 40) self.print_edit_line(7, _("Amount"), self.str_amount, 2, 15) self.print_edit_line(9, _("Fee"), self.str_fee, 3, 15) self.stdscr.addstr( 12, 15, _("[Send]"), curses.A_REVERSE if self.pos % 6 == 4 else curses.color_pair(2)) self.stdscr.addstr( 12, 25, _("[Clear]"), curses.A_REVERSE if self.pos % 6 == 5 else curses.color_pair(2)) def print_banner(self): if self.network: self.print_list(self.network.banner.split('\n')) def print_list(self, list, firstline=None): self.maxpos = len(list) if not self.maxpos: return if firstline: firstline += " " * (self.maxx - 2 - len(firstline)) self.stdscr.addstr(1, 1, firstline) for i in range(self.maxy - 4): msg = list[i] if i < len(list) else "" msg += " " * (self.maxx - 2 - len(msg)) m = msg[0:self.maxx - 2] m = m.encode(self.encoding) self.stdscr.addstr( i + 2, 1, m, curses.A_REVERSE if i == (self.pos % self.maxpos) else 0) def refresh(self): if self.tab == -1: return self.stdscr.border(0) self.print_balance() self.stdscr.refresh() def main_command(self): c = self.stdscr.getch() print c if c == curses.KEY_RIGHT: self.tab = (self.tab + 1) % self.num_tabs elif c == curses.KEY_LEFT: self.tab = (self.tab - 1) % self.num_tabs elif c == curses.KEY_DOWN: self.pos += 1 elif c == curses.KEY_UP: self.pos -= 1 elif c == 9: self.pos += 1 # tab elif curses.unctrl(c) in ['^W', '^C', '^X', '^Q']: self.tab = -1 elif curses.unctrl(c) in ['^N']: self.network_dialog() elif curses.unctrl(c) == '^S': self.settings_dialog() else: return c if self.pos < 0: self.pos = 0 if self.pos >= self.maxpos: self.pos = self.maxpos - 1 def run_tab(self, i, print_func, exec_func): while self.tab == i: self.stdscr.clear() print_func() self.refresh() c = self.main_command() if c: exec_func(c) def run_history_tab(self, c): if c == 10: out = self.run_popup('', ["blah", "foo"]) def edit_str(self, target, c, is_num=False): # detect backspace if c in [8, 127, 263] and target: target = target[:-1] elif not is_num or curses.unctrl(c) in '0123456789.': target += curses.unctrl(c) return target def run_send_tab(self, c): if self.pos % 6 == 0: self.str_recipient = self.edit_str(self.str_recipient, c) if self.pos % 6 == 1: self.str_description = self.edit_str(self.str_description, c) if self.pos % 6 == 2: self.str_amount = self.edit_str(self.str_amount, c, True) elif self.pos % 6 == 3: self.str_fee = self.edit_str(self.str_fee, c, True) elif self.pos % 6 == 4: if c == 10: self.do_send() elif self.pos % 6 == 5: if c == 10: self.do_clear() def run_receive_tab(self, c): if c == 10: out = self.run_popup('Address', ["Edit label", "Freeze", "Prioritize"]) def run_contacts_tab(self, c): if c == 10 and self.contacts: out = self.run_popup( 'Adress', ["Copy", "Pay to", "Edit label", "Delete"]).get('button') key = self.contacts.keys()[self.pos % len(self.contacts.keys())] if out == "Pay to": self.tab = 1 self.str_recipient = key self.pos = 2 elif out == "Edit label": s = self.get_string(6 + self.pos, 18) if s: self.wallet.labels[address] = s def run_banner_tab(self, c): self.show_message(repr(c)) pass def main(self, url): tty.setraw(sys.stdin) while self.tab != -1: self.run_tab(0, self.print_history, self.run_history_tab) self.run_tab(1, self.print_send_tab, self.run_send_tab) self.run_tab(2, self.print_receive, self.run_receive_tab) self.run_tab(3, self.print_contacts, self.run_contacts_tab) self.run_tab(4, self.print_banner, self.run_banner_tab) tty.setcbreak(sys.stdin) curses.nocbreak() self.stdscr.keypad(0) curses.echo() curses.endwin() def do_clear(self): self.str_amount = '' self.str_recipient = '' self.str_fee = '' self.str_description = '' def do_send(self): if not is_valid(self.str_recipient): self.show_message(_('Invalid parkbytecoin address')) return try: amount = int(Decimal(self.str_amount) * 100000000) except Exception: self.show_message(_('Invalid Amount')) return try: fee = int(Decimal(self.str_fee) * 100000000) except Exception: self.show_message(_('Invalid Fee')) return if self.wallet.use_encryption: password = self.password_dialog() if not password: return else: password = None try: tx = self.wallet.mktx([(self.str_recipient, amount)], password, fee) except Exception as e: self.show_message(str(e)) return if self.str_description: self.wallet.labels[tx.hash()] = self.str_description h = self.wallet.send_tx(tx) self.show_message(_("Please wait..."), getchar=False) self.wallet.tx_event.wait() status, msg = self.wallet.receive_tx(h, tx) if status: self.show_message(_('Payment sent.')) self.do_clear() #self.update_contacts_tab() else: self.show_message(_('Error')) def show_message(self, message, getchar=True): w = self.w w.clear() w.border(0) for i, line in enumerate(message.split('\n')): w.addstr(2 + i, 2, line) w.refresh() if getchar: c = self.stdscr.getch() def run_popup(self, title, items): return self.run_dialog(title, map(lambda x: { 'type': 'button', 'label': x }, items), interval=1, y_pos=self.pos + 3) def network_dialog(self): if not self.network: return auto_connect = self.network.config.get('auto_cycle') host, port, protocol, proxy_config, auto_connect = self.network.get_parameters( ) srv = 'auto-connect' if auto_connect else self.network.default_server out = self.run_dialog('Network', [ { 'label': 'server', 'type': 'str', 'value': srv }, { 'label': 'proxy', 'type': 'str', 'value': self.config.get('proxy', '') }, ], buttons=1) if out: if out.get('server'): server = out.get('server') auto_connect = server == 'auto-connect' if not auto_connect: try: host, port, protocol = server.split(':') except Exception: self.show_message("Error:" + server + "\nIn doubt, type \"auto-connect\"") return False if out.get('proxy'): proxy = self.parse_proxy_options(out.get('proxy')) else: proxy = None self.network.set_parameters(host, port, protocol, proxy, auto_connect) def settings_dialog(self): out = self.run_dialog( 'Settings', [{ 'label': 'Default GUI', 'type': 'list', 'choices': ['classic', 'lite', 'gtk', 'text'], 'value': self.config.get('gui') }, { 'label': 'Default fee', 'type': 'satoshis', 'value': format_satoshis(self.wallet.fee_per_kb).strip() }], buttons=1) if out: if out.get('Default GUI'): self.config.set_key('gui', out['Default GUI'], True) if out.get('Default fee'): fee = int(Decimal(out['Default fee']) * 10000000) self.config.set_key('fee_per_kb', fee, True) def password_dialog(self): out = self.run_dialog('Password', [{ 'label': 'Password', 'type': 'password', 'value': '' }], buttons=1) return out.get('Password') def run_dialog(self, title, items, interval=2, buttons=None, y_pos=3): self.popup_pos = 0 self.w = curses.newwin( 5 + len(items) * interval + (2 if buttons else 0), 50, y_pos, 5) w = self.w out = {} while True: w.clear() w.border(0) w.addstr(0, 2, title) num = len(items) numpos = num if buttons: numpos += 2 for i in range(num): item = items[i] label = item.get('label') if item.get('type') == 'list': value = item.get('value', '') elif item.get('type') == 'satoshis': value = item.get('value', '') elif item.get('type') == 'str': value = item.get('value', '') elif item.get('type') == 'password': value = '*' * len(item.get('value', '')) else: value = '' if value is None: value = '' if len(value) < 20: value += ' ' * (20 - len(value)) if item.has_key('value'): w.addstr(2 + interval * i, 2, label) w.addstr( 2 + interval * i, 15, value, curses.A_REVERSE if self.popup_pos % numpos == i else curses.color_pair(1)) else: w.addstr( 2 + interval * i, 2, label, curses.A_REVERSE if self.popup_pos % numpos == i else 0) if buttons: w.addstr( 5 + interval * i, 10, "[ ok ]", curses.A_REVERSE if self.popup_pos % numpos == (numpos - 2) else curses.color_pair(2)) w.addstr( 5 + interval * i, 25, "[cancel]", curses.A_REVERSE if self.popup_pos % numpos == (numpos - 1) else curses.color_pair(2)) w.refresh() c = self.stdscr.getch() if c in [ord('q'), 27]: break elif c in [curses.KEY_LEFT, curses.KEY_UP]: self.popup_pos -= 1 elif c in [curses.KEY_RIGHT, curses.KEY_DOWN]: self.popup_pos += 1 else: i = self.popup_pos % numpos if buttons and c == 10: if i == numpos - 2: return out elif i == numpos - 1: return {} item = items[i] _type = item.get('type') if _type == 'str': item['value'] = self.edit_str(item['value'], c) out[item.get('label')] = item.get('value') elif _type == 'password': item['value'] = self.edit_str(item['value'], c) out[item.get('label')] = item['value'] elif _type == 'satoshis': item['value'] = self.edit_str(item['value'], c, True) out[item.get('label')] = item.get('value') elif _type == 'list': choices = item.get('choices') try: j = choices.index(item.get('value')) except Exception: j = 0 new_choice = choices[(j + 1) % len(choices)] item['value'] = new_choice out[item.get('label')] = item.get('value') elif _type == 'button': out['button'] = item.get('label') break return out
class ElectrumWindow: def show_message(self, msg): show_message(msg, self.window) def on_key(self, w, event): if Gdk.ModifierType.CONTROL_MASK & event.state and event.keyval in [113,119]: Gtk.main_quit() return True def __init__(self, wallet, config, network): self.config = config self.wallet = wallet self.network = network self.funds_error = False # True if not enough funds self.num_zeros = int(self.config.get('num_zeros',0)) self.window = Gtk.Window(Gtk.WindowType.TOPLEVEL) self.window.connect('key-press-event', self.on_key) title = 'Electrum-PKB ' + self.wallet.electrum_version + ' - ' + self.config.path if not self.wallet.seed: title += ' [seedless]' self.window.set_title(title) self.window.connect("destroy", Gtk.main_quit) self.window.set_border_width(0) #self.window.connect('mykeypress', Gtk.main_quit) self.window.set_default_size(720, 350) self.wallet_updated = False from electrum_pkb.util import StoreDict self.contacts = StoreDict(self.config, 'contacts') vbox = Gtk.VBox() self.notebook = Gtk.Notebook() self.create_history_tab() if self.wallet.seed: self.create_send_tab() self.create_recv_tab() self.create_book_tab() self.create_about_tab() self.notebook.show() vbox.pack_start(self.notebook, True, True, 2) self.status_bar = Gtk.Statusbar() vbox.pack_start(self.status_bar, False, False, 0) self.status_image = Gtk.Image() self.status_image.set_from_stock(Gtk.STOCK_NO, Gtk.IconSize.MENU) self.status_image.set_alignment(True, 0.5 ) self.status_image.show() self.network_button = Gtk.Button() self.network_button.connect("clicked", lambda x: run_network_dialog(self.network, self.window) ) self.network_button.add(self.status_image) self.network_button.set_relief(Gtk.ReliefStyle.NONE) self.network_button.show() self.status_bar.pack_end(self.network_button, False, False, 0) if self.wallet.seed: def seedb(w, wallet): if wallet.use_encryption: password = password_dialog(self.window) if not password: return else: password = None seed = wallet.get_mnemonic(password) show_seed_dialog(seed, self.window) button = Gtk.Button('S') button.connect("clicked", seedb, self.wallet ) button.set_relief(Gtk.ReliefStyle.NONE) button.show() self.status_bar.pack_end(button,False, False, 0) settings_icon = Gtk.Image() settings_icon.set_from_stock(Gtk.STOCK_PREFERENCES, Gtk.IconSize.MENU) settings_icon.set_alignment(0.5, 0.5) settings_icon.set_size_request(16,16 ) settings_icon.show() prefs_button = Gtk.Button() prefs_button.connect("clicked", lambda x: run_settings_dialog(self) ) prefs_button.add(settings_icon) prefs_button.set_tooltip_text("Settings") prefs_button.set_relief(Gtk.ReliefStyle.NONE) prefs_button.show() self.status_bar.pack_end(prefs_button,False,False, 0) self.pw_icon = Gtk.Image() self.pw_icon.set_from_stock(Gtk.STOCK_DIALOG_AUTHENTICATION, Gtk.IconSize.MENU) self.pw_icon.set_alignment(0.5, 0.5) self.pw_icon.set_size_request(16,16 ) self.pw_icon.show() if self.wallet.seed: if self.wallet.use_encryption: self.pw_icon.set_tooltip_text('Wallet is encrypted') else: self.pw_icon.set_tooltip_text('Wallet is unencrypted') password_button = Gtk.Button() password_button.connect("clicked", self.do_update_password, self.wallet) password_button.add(self.pw_icon) password_button.set_relief(Gtk.ReliefStyle.NONE) password_button.show() self.status_bar.pack_end(password_button,False,False, 0) self.window.add(vbox) self.window.show_all() #self.fee_box.hide() self.context_id = self.status_bar.get_context_id("statusbar") self.update_status_bar() self.network.register_callback('updated', self.update_callback) def update_status_bar_thread(): while True: GObject.idle_add( self.update_status_bar ) time.sleep(0.5) def check_recipient_thread(): old_r = '' while True: time.sleep(0.5) if self.payto_entry.is_focus(): continue r = self.payto_entry.get_text() if r != old_r: old_r = r r = r.strip() if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r): try: to_address = self.wallet.get_alias(r, interactive=False) except Exception: continue if to_address: s = r + ' <' + to_address + '>' GObject.idle_add( lambda: self.payto_entry.set_text(s) ) thread.start_new_thread(update_status_bar_thread, ()) if self.wallet.seed: thread.start_new_thread(check_recipient_thread, ()) self.notebook.set_current_page(0) def update_callback(self): self.wallet_updated = True def do_update_password(self, button, wallet): if not wallet.seed: show_message("No seed") return res = change_password_dialog(wallet.use_encryption, self.window) if res: _, password, new_password = res try: wallet.get_seed(password) except InvalidPassword: show_message("Incorrect password") return wallet.update_password(password, new_password) if wallet.use_encryption: self.pw_icon.set_tooltip_text('Wallet is encrypted') else: self.pw_icon.set_tooltip_text('Wallet is unencrypted') def add_tab(self, page, name): tab_label = Gtk.Label(label=name) tab_label.show() self.notebook.append_page(page, tab_label) def create_send_tab(self): page = vbox = Gtk.VBox() page.show() payto = Gtk.HBox() payto_label = Gtk.Label(label='Pay to:') payto_label.set_size_request(100,-1) payto.pack_start(payto_label, False, False, 0) payto_entry = Gtk.Entry() payto_entry.set_size_request(450, 26) payto.pack_start(payto_entry, False, False, 0) vbox.pack_start(payto, False, False, 5) message = Gtk.HBox() message_label = Gtk.Label(label='Description:') message_label.set_size_request(100,-1) message.pack_start(message_label, False, False, 0) message_entry = Gtk.Entry() message_entry.set_size_request(450, 26) message.pack_start(message_entry, False, False, 0) vbox.pack_start(message, False, False, 5) amount_box = Gtk.HBox() amount_label = Gtk.Label(label='Amount:') amount_label.set_size_request(100,-1) amount_box.pack_start(amount_label, False, False, 0) amount_entry = Gtk.Entry() amount_entry.set_size_request(120, -1) amount_box.pack_start(amount_entry, False, False, 0) vbox.pack_start(amount_box, False, False, 5) self.fee_box = fee_box = Gtk.HBox() fee_label = Gtk.Label(label='Fee:') fee_label.set_size_request(100,-1) fee_box.pack_start(fee_label, False, False, 0) fee_entry = Gtk.Entry() fee_entry.set_size_request(60, 26) fee_box.pack_start(fee_entry, False, False, 0) vbox.pack_start(fee_box, False, False, 5) end_box = Gtk.HBox() empty_label = Gtk.Label(label='') empty_label.set_size_request(100,-1) end_box.pack_start(empty_label, False, False, 0) send_button = Gtk.Button("Send") send_button.show() end_box.pack_start(send_button, False, False, 0) clear_button = Gtk.Button("Clear") clear_button.show() end_box.pack_start(clear_button, False, False, 15) send_button.connect("clicked", self.do_send, (payto_entry, message_entry, amount_entry, fee_entry)) clear_button.connect("clicked", self.do_clear, (payto_entry, message_entry, amount_entry, fee_entry)) vbox.pack_start(end_box, False, False, 5) # display this line only if there is a signature payto_sig = Gtk.HBox() payto_sig_id = Gtk.Label(label='') payto_sig.pack_start(payto_sig_id, False, False, 0) vbox.pack_start(payto_sig, True, True, 5) self.user_fee = False def entry_changed( entry, is_fee ): amount = numbify(amount_entry) fee = numbify(fee_entry) if not is_fee: fee = None if amount is None: return try: tx = self.wallet.make_unsigned_transaction([('op_return', 'dummy_tx', amount)], fee) self.funds_error = False except NotEnoughFunds: self.funds_error = True if not self.funds_error: if not is_fee: fee = tx.get_fee() fee_entry.set_text( str( Decimal( fee ) / 100000000 ) ) self.fee_box.show() amount_entry.modify_text(Gtk.StateType.NORMAL, Gdk.color_parse("#000000")) fee_entry.modify_text(Gtk.StateType.NORMAL, Gdk.color_parse("#000000")) send_button.set_sensitive(True) else: send_button.set_sensitive(False) amount_entry.modify_text(Gtk.StateType.NORMAL, Gdk.color_parse("#cc0000")) fee_entry.modify_text(Gtk.StateType.NORMAL, Gdk.color_parse("#cc0000")) amount_entry.connect('changed', entry_changed, False) fee_entry.connect('changed', entry_changed, True) self.payto_entry = payto_entry self.payto_fee_entry = fee_entry self.payto_sig_id = payto_sig_id self.payto_sig = payto_sig self.amount_entry = amount_entry self.message_entry = message_entry self.add_tab(page, 'Send') def set_frozen(self,entry,frozen): if frozen: entry.set_editable(False) entry.set_has_frame(False) entry.modify_base(Gtk.StateType.NORMAL, Gdk.color_parse("#eeeeee")) else: entry.set_editable(True) entry.set_has_frame(True) entry.modify_base(Gtk.StateType.NORMAL, Gdk.color_parse("#ffffff")) def set_url(self, url): payto, amount, label, message, payment_request = parse_URI(url) self.notebook.set_current_page(1) self.payto_entry.set_text(payto) self.message_entry.set_text(message) self.amount_entry.set_text(amount) self.payto_sig.set_visible(False) def create_about_tab(self): from gi.repository import Pango page = Gtk.VBox() page.show() tv = Gtk.TextView() tv.set_editable(False) tv.set_cursor_visible(False) tv.modify_font(Pango.FontDescription(MONOSPACE_FONT)) scroll = Gtk.ScrolledWindow() scroll.add(tv) page.pack_start(scroll, True, True, 0) self.info = tv.get_buffer() self.add_tab(page, 'Wall') def do_clear(self, w, data): self.payto_sig.set_visible(False) self.payto_fee_entry.set_text('') for entry in [self.payto_entry,self.amount_entry,self.message_entry]: self.set_frozen(entry,False) entry.set_text('') def question(self,msg): dialog = Gtk.MessageDialog( self.window, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.QUESTION, Gtk.ButtonsType.OK_CANCEL, msg) dialog.show() result = dialog.run() dialog.destroy() return result == Gtk.ResponseType.OK def do_send(self, w, data): payto_entry, label_entry, amount_entry, fee_entry = data label = label_entry.get_text() r = payto_entry.get_text() r = r.strip() m1 = re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r) m2 = re.match('(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+) \<([1-9A-HJ-NP-Za-km-z]{26,})\>', r) if m1: to_address = self.wallet.get_alias(r, True, self.show_message, self.question) if not to_address: return else: self.update_sending_tab() elif m2: to_address = m2.group(5) else: to_address = r if not is_valid(to_address): self.show_message( "invalid parkbyte address:\n"+to_address) return try: amount = int( Decimal(amount_entry.get_text()) * 100000000 ) except Exception: self.show_message( "invalid amount") return try: fee = int( Decimal(fee_entry.get_text()) * 100000000 ) except Exception: self.show_message( "invalid fee") return if self.wallet.use_encryption: password = password_dialog(self.window) if not password: return else: password = None try: tx = self.wallet.mktx( [(to_address, amount)], password, fee ) except Exception as e: self.show_message(str(e)) return if fee < tx.required_fee(self.wallet): self.show_message( "This transaction requires a higher fee, or it will not be propagated by the network." ) return if label: self.wallet.labels[tx.hash()] = label status, msg = self.wallet.sendtx( tx ) if status: self.show_message( "payment sent.\n" + msg ) payto_entry.set_text("") label_entry.set_text("") amount_entry.set_text("") fee_entry.set_text("") #self.fee_box.hide() self.update_sending_tab() else: self.show_message( msg ) def treeview_button_press(self, treeview, event): if event.type == Gdk.EventType.DOUBLE_BUTTON_PRESS: c = treeview.get_cursor()[0] if treeview == self.history_treeview: tx_details = self.history_list.get_value( self.history_list.get_iter(c), 8) self.show_message(tx_details) elif treeview == self.contacts_treeview: m = self.addressbook_list.get_value( self.addressbook_list.get_iter(c), 0) #a = self.wallet.aliases.get(m) #if a: # if a[0] in self.wallet.authorities.keys(): # s = self.wallet.authorities.get(a[0]) # else: # s = "self-signed" # msg = 'Alias: '+ m + '\nTarget address: '+ a[1] + '\n\nSigned by: ' + s + '\nSigning address:' + a[0] # self.show_message(msg) def treeview_key_press(self, treeview, event): c = treeview.get_cursor()[0] if event.keyval == Gdk.KEY_Up: if c and c[0] == 0: treeview.parent.grab_focus() treeview.set_cursor((0,)) elif event.keyval == Gdk.KEY_Return: if treeview == self.history_treeview: tx_details = self.history_list.get_value( self.history_list.get_iter(c), 8) self.show_message(tx_details) elif treeview == self.contacts_treeview: m = self.addressbook_list.get_value( self.addressbook_list.get_iter(c), 0) #a = self.wallet.aliases.get(m) #if a: # if a[0] in self.wallet.authorities.keys(): # s = self.wallet.authorities.get(a[0]) # else: # s = "self" # msg = 'Alias:'+ m + '\n\nTarget: '+ a[1] + '\nSigned by: ' + s + '\nSigning address:' + a[0] # self.show_message(msg) return False def create_history_tab(self): self.history_list = Gtk.ListStore(str, str, str, str, 'gboolean', str, str, str, str) treeview = Gtk.TreeView(model=self.history_list) self.history_treeview = treeview treeview.set_tooltip_column(7) treeview.show() treeview.connect('key-press-event', self.treeview_key_press) treeview.connect('button-press-event', self.treeview_button_press) tvcolumn = Gtk.TreeViewColumn('') treeview.append_column(tvcolumn) cell = Gtk.CellRendererPixbuf() tvcolumn.pack_start(cell, False) tvcolumn.set_attributes(cell, stock_id=1) tvcolumn = Gtk.TreeViewColumn('Date') treeview.append_column(tvcolumn) cell = Gtk.CellRendererText() tvcolumn.pack_start(cell, False) tvcolumn.add_attribute(cell, 'text', 2) tvcolumn = Gtk.TreeViewColumn('Description') treeview.append_column(tvcolumn) cell = Gtk.CellRendererText() cell.set_property('foreground', 'grey') cell.set_property('family', MONOSPACE_FONT) cell.set_property('editable', True) def edited_cb(cell, path, new_text, h_list): tx = h_list.get_value( h_list.get_iter(path), 0) self.wallet.set_label(tx,new_text) self.update_history_tab() cell.connect('edited', edited_cb, self.history_list) def editing_started(cell, entry, path, h_list): tx = h_list.get_value( h_list.get_iter(path), 0) if not self.wallet.labels.get(tx): entry.set_text('') cell.connect('editing-started', editing_started, self.history_list) tvcolumn.set_expand(True) tvcolumn.pack_start(cell, True) tvcolumn.set_attributes(cell, text=3, foreground_set = 4) tvcolumn = Gtk.TreeViewColumn('Amount') treeview.append_column(tvcolumn) cell = Gtk.CellRendererText() cell.set_alignment(1, 0.5) cell.set_property('family', MONOSPACE_FONT) tvcolumn.pack_start(cell, False) tvcolumn.add_attribute(cell, 'text', 5) tvcolumn = Gtk.TreeViewColumn('Balance') treeview.append_column(tvcolumn) cell = Gtk.CellRendererText() cell.set_alignment(1, 0.5) cell.set_property('family', MONOSPACE_FONT) tvcolumn.pack_start(cell, False) tvcolumn.add_attribute(cell, 'text', 6) tvcolumn = Gtk.TreeViewColumn('Tooltip') treeview.append_column(tvcolumn) cell = Gtk.CellRendererText() tvcolumn.pack_start(cell, False) tvcolumn.add_attribute(cell, 'text', 7) tvcolumn.set_visible(False) scroll = Gtk.ScrolledWindow() scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) scroll.add(treeview) self.add_tab(scroll, 'History') self.update_history_tab() def create_recv_tab(self): self.recv_list = Gtk.ListStore(str, str, str, str, str) self.add_tab( self.make_address_list(True), 'Receive') self.update_receiving_tab() def create_book_tab(self): self.addressbook_list = Gtk.ListStore(str, str, str) self.add_tab( self.make_address_list(False), 'Contacts') self.update_sending_tab() def make_address_list(self, is_recv): liststore = self.recv_list if is_recv else self.addressbook_list treeview = Gtk.TreeView(model= liststore) treeview.connect('key-press-event', self.treeview_key_press) treeview.connect('button-press-event', self.treeview_button_press) treeview.show() if not is_recv: self.contacts_treeview = treeview tvcolumn = Gtk.TreeViewColumn('Address') treeview.append_column(tvcolumn) cell = Gtk.CellRendererText() cell.set_property('family', MONOSPACE_FONT) tvcolumn.pack_start(cell, True) tvcolumn.add_attribute(cell, 'text', 0) tvcolumn = Gtk.TreeViewColumn('Label') tvcolumn.set_expand(True) treeview.append_column(tvcolumn) cell = Gtk.CellRendererText() cell.set_property('editable', True) def edited_cb2(cell, path, new_text, liststore): address = liststore.get_value( liststore.get_iter(path), 0) self.wallet.set_label(address, new_text) self.update_receiving_tab() self.update_sending_tab() self.update_history_tab() cell.connect('edited', edited_cb2, liststore) tvcolumn.pack_start(cell, True) tvcolumn.add_attribute(cell, 'text', 1) tvcolumn = Gtk.TreeViewColumn('Tx') treeview.append_column(tvcolumn) cell = Gtk.CellRendererText() tvcolumn.pack_start(cell, True) tvcolumn.add_attribute(cell, 'text', 2) if is_recv: tvcolumn = Gtk.TreeViewColumn('Balance') treeview.append_column(tvcolumn) cell = Gtk.CellRendererText() tvcolumn.pack_start(cell, True) tvcolumn.add_attribute(cell, 'text', 3) tvcolumn = Gtk.TreeViewColumn('Type') treeview.append_column(tvcolumn) cell = Gtk.CellRendererText() tvcolumn.pack_start(cell, True) tvcolumn.add_attribute(cell, 'text', 4) scroll = Gtk.ScrolledWindow() scroll.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) scroll.add(treeview) hbox = Gtk.HBox() if not is_recv: button = Gtk.Button("New") button.connect("clicked", self.newaddress_dialog) button.show() hbox.pack_start(button,False, False, 0) def showqrcode(w, treeview, liststore): import qrcode path, col = treeview.get_cursor() if not path: return address = liststore.get_value(liststore.get_iter(path), 0) qr = qrcode.QRCode() qr.add_data(address) boxsize = 7 matrix = qr.get_matrix() boxcount_row = len(matrix) size = (boxcount_row + 4) * boxsize def area_expose_cb(area, cr): style = area.get_style() Gdk.cairo_set_source_color(cr, style.white) cr.rectangle(0, 0, size, size) cr.fill() Gdk.cairo_set_source_color(cr, style.black) for r in range(boxcount_row): for c in range(boxcount_row): if matrix[r][c]: cr.rectangle((c + 2) * boxsize, (r + 2) * boxsize, boxsize, boxsize) cr.fill() area = Gtk.DrawingArea() area.set_size_request(size, size) area.connect("draw", area_expose_cb) area.show() dialog = Gtk.Dialog(address, parent=self.window, flags=Gtk.DialogFlags.MODAL, buttons = ("ok",1)) dialog.vbox.add(area) dialog.run() dialog.destroy() button = Gtk.Button("QR") button.connect("clicked", showqrcode, treeview, liststore) button.show() hbox.pack_start(button,False, False, 0) button = Gtk.Button("Copy to clipboard") def copy2clipboard(w, treeview, liststore): import platform path, col = treeview.get_cursor() if path: address = liststore.get_value( liststore.get_iter(path), 0) if platform.system() == 'Windows': from Tkinter import Tk r = Tk() r.withdraw() r.clipboard_clear() r.clipboard_append( address ) r.destroy() else: atom = Gdk.atom_intern('CLIPBOARD', True) c = Gtk.Clipboard.get(atom) c.set_text( address, len(address) ) button.connect("clicked", copy2clipboard, treeview, liststore) button.show() hbox.pack_start(button,False, False, 0) if is_recv: button = Gtk.Button("Freeze") def freeze_address(w, treeview, liststore, wallet): path, col = treeview.get_cursor() if path: address = liststore.get_value( liststore.get_iter(path), 0) if address in wallet.frozen_addresses: wallet.unfreeze(address) else: wallet.freeze(address) self.update_receiving_tab() button.connect("clicked", freeze_address, treeview, liststore, self.wallet) button.show() hbox.pack_start(button,False, False, 0) if not is_recv: button = Gtk.Button("Pay to") def payto(w, treeview, liststore): path, col = treeview.get_cursor() if path: address = liststore.get_value( liststore.get_iter(path), 0) self.payto_entry.set_text( address ) self.notebook.set_current_page(1) self.amount_entry.grab_focus() button.connect("clicked", payto, treeview, liststore) button.show() hbox.pack_start(button,False, False, 0) vbox = Gtk.VBox() vbox.pack_start(scroll,True, True, 0) vbox.pack_start(hbox, False, False, 0) return vbox def update_status_bar(self): if self.funds_error: text = "Not enough funds" elif self.network.is_connected(): host, port, _,_,_ = self.network.get_parameters() port = int(port) height = self.network.get_local_height() self.network_button.set_tooltip_text("Connected to %s:%d.\n%d blocks"%(host, port, height)) if not self.wallet.up_to_date: self.status_image.set_from_stock(Gtk.STOCK_REFRESH, Gtk.IconSize.MENU) text = "Synchronizing..." else: self.status_image.set_from_stock(Gtk.STOCK_YES, Gtk.IconSize.MENU) c, u, x = self.wallet.get_balance() text = "Balance: %s "%(format_satoshis(c, False, self.num_zeros)) if u: text += "[%s unconfirmed]"%(format_satoshis(u, True, self.num_zeros).strip()) if x: text += "[%s unmatured]"%(format_satoshis(x, True, self.num_zeros).strip()) else: self.status_image.set_from_stock(Gtk.STOCK_NO, Gtk.IconSize.MENU) self.network_button.set_tooltip_text("Not connected.") text = "Not connected" self.status_bar.pop(self.context_id) self.status_bar.push(self.context_id, text) if self.wallet.up_to_date and self.wallet_updated: self.update_history_tab() self.update_receiving_tab() # addressbook too... self.info.set_text( self.network.banner ) self.wallet_updated = False def update_receiving_tab(self): self.recv_list.clear() for address in self.wallet.addresses(True): Type = "R" c = u = 0 if self.wallet.is_change(address): Type = "C" if address in self.wallet.imported_keys.keys(): Type = "I" c, u, x = self.wallet.get_addr_balance(address) if address in self.wallet.frozen_addresses: Type = Type + "F" label = self.wallet.labels.get(address) h = self.wallet.history.get(address,[]) n = len(h) tx = "0" if n==0 else "%d"%n self.recv_list.append((address, label, tx, format_satoshis(c+u+x, False, self.num_zeros), Type )) def update_sending_tab(self): self.addressbook_list.clear() for k, v in self.contacts.items(): t, v = v self.addressbook_list.append((k, v, t)) def update_history_tab(self): cursor = self.history_treeview.get_cursor()[0] self.history_list.clear() for item in self.wallet.get_history(): tx_hash, conf, value, timestamp, balance = item if conf > 0: try: time_str = datetime.datetime.fromtimestamp( timestamp).isoformat(' ')[:-3] except Exception: time_str = "------" conf_icon = Gtk.STOCK_APPLY elif conf == -1: time_str = 'unverified' conf_icon = None else: time_str = 'pending' conf_icon = Gtk.STOCK_EXECUTE label, is_default_label = self.wallet.get_label(tx_hash) tooltip = tx_hash + "\n%d confirmations"%conf if tx_hash else '' details = self.get_tx_details(tx_hash) self.history_list.prepend( [tx_hash, conf_icon, time_str, label, is_default_label, format_satoshis(value,True,self.num_zeros, whitespaces=True), format_satoshis(balance,False,self.num_zeros, whitespaces=True), tooltip, details] ) if cursor: self.history_treeview.set_cursor( cursor ) def get_tx_details(self, tx_hash): import datetime if not tx_hash: return '' tx = self.wallet.transactions.get(tx_hash) tx.deserialize() is_relevant, is_mine, v, fee = self.wallet.get_wallet_delta(tx) conf, timestamp = self.wallet.get_confirmations(tx_hash) if timestamp: time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3] else: time_str = 'pending' inputs = map(lambda x: x.get('address'), tx.inputs) outputs = map(lambda x: x[0], tx.get_outputs()) tx_details = "Transaction Details" +"\n\n" \ + "Transaction ID:\n" + tx_hash + "\n\n" \ + "Status: %d confirmations\n"%conf if is_mine: if fee: tx_details += "Amount sent: %s\n"% format_satoshis(v-fee, False) \ + "Transaction fee: %s\n"% format_satoshis(fee, False) else: tx_details += "Amount sent: %s\n"% format_satoshis(v, False) \ + "Transaction fee: unknown\n" else: tx_details += "Amount received: %s\n"% format_satoshis(v, False) \ tx_details += "Date: %s\n\n"%time_str \ + "Inputs:\n-"+ '\n-'.join(inputs) + "\n\n" \ + "Outputs:\n-"+ '\n-'.join(outputs) return tx_details def newaddress_dialog(self, w): title = "New Contact" dialog = Gtk.Dialog(title, parent=self.window, flags=Gtk.DialogFlags.MODAL, buttons= ("cancel", 0, "ok",1) ) dialog.show() label = Gtk.HBox() label_label = Gtk.Label(label='Label:') label_label.set_size_request(120,10) label_label.show() label.pack_start(label_label, True, True, 0) label_entry = Gtk.Entry() label_entry.show() label.pack_start(label_entry, True, True, 0) label.show() dialog.vbox.pack_start(label, False, True, 5) address = Gtk.HBox() address_label = Gtk.Label(label='Address:') address_label.set_size_request(120,10) address_label.show() address.pack_start(address_label, True, True, 0) address_entry = Gtk.Entry() address_entry.show() address.pack_start(address_entry, True, True, 0) address.show() dialog.vbox.pack_start(address, False, True, 5) result = dialog.run() address = address_entry.get_text() label = label_entry.get_text() dialog.destroy() if result == 1: if is_valid(address): self.contacts[label] = address self.update_sending_tab() else: errorDialog = Gtk.MessageDialog( parent=self.window, flags=Gtk.DialogFlags.MODAL, buttons= Gtk.ButtonsType.CLOSE, message_format = "Invalid address") errorDialog.show() errorDialog.run() errorDialog.destroy()
class ElectrumGui: def __init__(self, config, network): self.config = config self.network = network storage = WalletStorage(config.get_wallet_path()) if not storage.file_exists: print "Wallet not found. try 'electrum-pkb create'" exit() self.wallet = Wallet(storage) self.wallet.start_threads(self.network) self.contacts = StoreDict(self.config, "contacts") locale.setlocale(locale.LC_ALL, "") self.encoding = locale.getpreferredencoding() self.stdscr = curses.initscr() curses.noecho() curses.cbreak() curses.start_color() curses.use_default_colors() curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLUE) curses.init_pair(2, curses.COLOR_WHITE, curses.COLOR_CYAN) self.stdscr.keypad(1) self.stdscr.border(0) self.maxy, self.maxx = self.stdscr.getmaxyx() self.set_cursor(0) self.w = curses.newwin(10, 50, 5, 5) set_verbosity(False) self.tab = 0 self.pos = 0 self.popup_pos = 0 self.str_recipient = "" self.str_description = "" self.str_amount = "" self.str_fee = "" self.history = None if self.network: self.network.register_callback("updated", self.update) self.network.register_callback("connected", self.refresh) self.network.register_callback("disconnected", self.refresh) self.network.register_callback("disconnecting", self.refresh) self.tab_names = [_("History"), _("Send"), _("Receive"), _("Contacts"), _("Wall")] self.num_tabs = len(self.tab_names) def set_cursor(self, x): try: curses.curs_set(x) except Exception: pass def restore_or_create(self): pass def verify_seed(self): pass def get_string(self, y, x): self.set_cursor(1) curses.echo() self.stdscr.addstr(y, x, " " * 20, curses.A_REVERSE) s = self.stdscr.getstr(y, x) curses.noecho() self.set_cursor(0) return s def update(self): self.update_history() if self.tab == 0: self.print_history() self.refresh() def print_history(self): width = [20, 40, 14, 14] delta = (self.maxx - sum(width) - 4) / 3 format_str = ( "%" + "%d" % width[0] + "s" + "%" + "%d" % (width[1] + delta) + "s" + "%" + "%d" % (width[2] + delta) + "s" + "%" + "%d" % (width[3] + delta) + "s" ) if self.history is None: self.update_history() self.print_list(self.history[::-1], format_str % (_("Date"), _("Description"), _("Amount"), _("Balance"))) def update_history(self): width = [20, 40, 14, 14] delta = (self.maxx - sum(width) - 4) / 3 format_str = ( "%" + "%d" % width[0] + "s" + "%" + "%d" % (width[1] + delta) + "s" + "%" + "%d" % (width[2] + delta) + "s" + "%" + "%d" % (width[3] + delta) + "s" ) b = 0 self.history = [] for item in self.wallet.get_history(): tx_hash, conf, value, timestamp, balance = item if conf: try: time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(" ")[:-3] except Exception: time_str = "------" else: time_str = "pending" label, is_default_label = self.wallet.get_label(tx_hash) if len(label) > 40: label = label[0:37] + "..." self.history.append( format_str % ( time_str, label, format_satoshis(value, whitespaces=True), format_satoshis(balance, whitespaces=True), ) ) def print_balance(self): if not self.network: msg = _("Offline") elif self.network.is_connected(): if not self.wallet.up_to_date: msg = _("Synchronizing...") else: c, u, x = self.wallet.get_balance() msg = _("Balance") + ": %f " % (Decimal(c) / 100000000) if u: msg += " [%f unconfirmed]" % (Decimal(u) / 100000000) if x: msg += " [%f unmatured]" % (Decimal(x) / 100000000) else: msg = _("Not connected") self.stdscr.addstr(self.maxy - 1, 3, msg) for i in range(self.num_tabs): 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, ) self.stdscr.addstr(self.maxy - 1, self.maxx - 30, " ".join([_("Settings"), _("Network"), _("Quit")])) def print_contacts(self): messages = map(lambda x: "%20s %45s " % (x[0], x[1][1]), self.contacts.items()) self.print_list(messages, "%19s %15s " % ("Key", "Value")) def print_receive(self): fmt = "%-35s %-30s" messages = map(lambda addr: fmt % (addr, self.wallet.labels.get(addr, "")), self.wallet.addresses()) self.print_list(messages, fmt % ("Address", "Label")) def print_edit_line(self, y, label, text, index, size): text += " " * (size - len(text)) self.stdscr.addstr(y, 2, label) self.stdscr.addstr(y, 15, text, curses.A_REVERSE if self.pos % 6 == index else curses.color_pair(1)) def print_send_tab(self): self.stdscr.clear() self.print_edit_line(3, _("Pay to"), self.str_recipient, 0, 40) self.print_edit_line(5, _("Description"), self.str_description, 1, 40) self.print_edit_line(7, _("Amount"), self.str_amount, 2, 15) self.print_edit_line(9, _("Fee"), self.str_fee, 3, 15) self.stdscr.addstr(12, 15, _("[Send]"), curses.A_REVERSE if self.pos % 6 == 4 else curses.color_pair(2)) self.stdscr.addstr(12, 25, _("[Clear]"), curses.A_REVERSE if self.pos % 6 == 5 else curses.color_pair(2)) def print_banner(self): if self.network: self.print_list(self.network.banner.split("\n")) def print_list(self, list, firstline=None): self.maxpos = len(list) if not self.maxpos: return if firstline: firstline += " " * (self.maxx - 2 - len(firstline)) self.stdscr.addstr(1, 1, firstline) for i in range(self.maxy - 4): msg = list[i] if i < len(list) else "" msg += " " * (self.maxx - 2 - len(msg)) m = msg[0 : self.maxx - 2] m = m.encode(self.encoding) self.stdscr.addstr(i + 2, 1, m, curses.A_REVERSE if i == (self.pos % self.maxpos) else 0) def refresh(self): if self.tab == -1: return self.stdscr.border(0) self.print_balance() self.stdscr.refresh() def main_command(self): c = self.stdscr.getch() print c if c == curses.KEY_RIGHT: self.tab = (self.tab + 1) % self.num_tabs elif c == curses.KEY_LEFT: self.tab = (self.tab - 1) % self.num_tabs elif c == curses.KEY_DOWN: self.pos += 1 elif c == curses.KEY_UP: self.pos -= 1 elif c == 9: self.pos += 1 # tab elif curses.unctrl(c) in ["^W", "^C", "^X", "^Q"]: self.tab = -1 elif curses.unctrl(c) in ["^N"]: self.network_dialog() elif curses.unctrl(c) == "^S": self.settings_dialog() else: return c if self.pos < 0: self.pos = 0 if self.pos >= self.maxpos: self.pos = self.maxpos - 1 def run_tab(self, i, print_func, exec_func): while self.tab == i: self.stdscr.clear() print_func() self.refresh() c = self.main_command() if c: exec_func(c) def run_history_tab(self, c): if c == 10: out = self.run_popup("", ["blah", "foo"]) def edit_str(self, target, c, is_num=False): # detect backspace if c in [8, 127, 263] and target: target = target[:-1] elif not is_num or curses.unctrl(c) in "0123456789.": target += curses.unctrl(c) return target def run_send_tab(self, c): if self.pos % 6 == 0: self.str_recipient = self.edit_str(self.str_recipient, c) if self.pos % 6 == 1: self.str_description = self.edit_str(self.str_description, c) if self.pos % 6 == 2: self.str_amount = self.edit_str(self.str_amount, c, True) elif self.pos % 6 == 3: self.str_fee = self.edit_str(self.str_fee, c, True) elif self.pos % 6 == 4: if c == 10: self.do_send() elif self.pos % 6 == 5: if c == 10: self.do_clear() def run_receive_tab(self, c): if c == 10: out = self.run_popup("Address", ["Edit label", "Freeze", "Prioritize"]) def run_contacts_tab(self, c): if c == 10 and self.contacts: out = self.run_popup("Adress", ["Copy", "Pay to", "Edit label", "Delete"]).get("button") key = self.contacts.keys()[self.pos % len(self.contacts.keys())] if out == "Pay to": self.tab = 1 self.str_recipient = key self.pos = 2 elif out == "Edit label": s = self.get_string(6 + self.pos, 18) if s: self.wallet.labels[address] = s def run_banner_tab(self, c): self.show_message(repr(c)) pass def main(self, url): tty.setraw(sys.stdin) while self.tab != -1: self.run_tab(0, self.print_history, self.run_history_tab) self.run_tab(1, self.print_send_tab, self.run_send_tab) self.run_tab(2, self.print_receive, self.run_receive_tab) self.run_tab(3, self.print_contacts, self.run_contacts_tab) self.run_tab(4, self.print_banner, self.run_banner_tab) tty.setcbreak(sys.stdin) curses.nocbreak() self.stdscr.keypad(0) curses.echo() curses.endwin() def do_clear(self): self.str_amount = "" self.str_recipient = "" self.str_fee = "" self.str_description = "" def do_send(self): if not is_valid(self.str_recipient): self.show_message(_("Invalid parkbytecoin address")) return try: amount = int(Decimal(self.str_amount) * 100000000) except Exception: self.show_message(_("Invalid Amount")) return try: fee = int(Decimal(self.str_fee) * 100000000) except Exception: self.show_message(_("Invalid Fee")) return if self.wallet.use_encryption: password = self.password_dialog() if not password: return else: password = None try: tx = self.wallet.mktx([(self.str_recipient, amount)], password, fee) except Exception as e: self.show_message(str(e)) return if self.str_description: self.wallet.labels[tx.hash()] = self.str_description h = self.wallet.send_tx(tx) self.show_message(_("Please wait..."), getchar=False) self.wallet.tx_event.wait() status, msg = self.wallet.receive_tx(h, tx) if status: self.show_message(_("Payment sent.")) self.do_clear() # self.update_contacts_tab() else: self.show_message(_("Error")) def show_message(self, message, getchar=True): w = self.w w.clear() w.border(0) for i, line in enumerate(message.split("\n")): w.addstr(2 + i, 2, line) w.refresh() if getchar: c = self.stdscr.getch() def run_popup(self, title, items): return self.run_dialog( title, map(lambda x: {"type": "button", "label": x}, items), interval=1, y_pos=self.pos + 3 ) def network_dialog(self): if not self.network: return auto_connect = self.network.config.get("auto_cycle") host, port, protocol, proxy_config, auto_connect = self.network.get_parameters() srv = "auto-connect" if auto_connect else self.network.default_server out = self.run_dialog( "Network", [ {"label": "server", "type": "str", "value": srv}, {"label": "proxy", "type": "str", "value": self.config.get("proxy", "")}, ], buttons=1, ) if out: if out.get("server"): server = out.get("server") auto_connect = server == "auto-connect" if not auto_connect: try: host, port, protocol = server.split(":") except Exception: self.show_message("Error:" + server + '\nIn doubt, type "auto-connect"') return False if out.get("proxy"): proxy = self.parse_proxy_options(out.get("proxy")) else: proxy = None self.network.set_parameters(host, port, protocol, proxy, auto_connect) def settings_dialog(self): out = self.run_dialog( "Settings", [ { "label": "Default GUI", "type": "list", "choices": ["classic", "lite", "gtk", "text"], "value": self.config.get("gui"), }, {"label": "Default fee", "type": "satoshis", "value": format_satoshis(self.wallet.fee_per_kb).strip()}, ], buttons=1, ) if out: if out.get("Default GUI"): self.config.set_key("gui", out["Default GUI"], True) if out.get("Default fee"): fee = int(Decimal(out["Default fee"]) * 10000000) self.config.set_key("fee_per_kb", fee, True) def password_dialog(self): out = self.run_dialog("Password", [{"label": "Password", "type": "password", "value": ""}], buttons=1) return out.get("Password") def run_dialog(self, title, items, interval=2, buttons=None, y_pos=3): self.popup_pos = 0 self.w = curses.newwin(5 + len(items) * interval + (2 if buttons else 0), 50, y_pos, 5) w = self.w out = {} while True: w.clear() w.border(0) w.addstr(0, 2, title) num = len(items) numpos = num if buttons: numpos += 2 for i in range(num): item = items[i] label = item.get("label") if item.get("type") == "list": value = item.get("value", "") elif item.get("type") == "satoshis": value = item.get("value", "") elif item.get("type") == "str": value = item.get("value", "") elif item.get("type") == "password": value = "*" * len(item.get("value", "")) else: value = "" if value is None: value = "" if len(value) < 20: value += " " * (20 - len(value)) if item.has_key("value"): w.addstr(2 + interval * i, 2, label) w.addstr( 2 + interval * i, 15, value, curses.A_REVERSE if self.popup_pos % numpos == i else curses.color_pair(1), ) else: w.addstr(2 + interval * i, 2, label, curses.A_REVERSE if self.popup_pos % numpos == i else 0) if buttons: w.addstr( 5 + interval * i, 10, "[ ok ]", curses.A_REVERSE if self.popup_pos % numpos == (numpos - 2) else curses.color_pair(2), ) w.addstr( 5 + interval * i, 25, "[cancel]", curses.A_REVERSE if self.popup_pos % numpos == (numpos - 1) else curses.color_pair(2), ) w.refresh() c = self.stdscr.getch() if c in [ord("q"), 27]: break elif c in [curses.KEY_LEFT, curses.KEY_UP]: self.popup_pos -= 1 elif c in [curses.KEY_RIGHT, curses.KEY_DOWN]: self.popup_pos += 1 else: i = self.popup_pos % numpos if buttons and c == 10: if i == numpos - 2: return out elif i == numpos - 1: return {} item = items[i] _type = item.get("type") if _type == "str": item["value"] = self.edit_str(item["value"], c) out[item.get("label")] = item.get("value") elif _type == "password": item["value"] = self.edit_str(item["value"], c) out[item.get("label")] = item["value"] elif _type == "satoshis": item["value"] = self.edit_str(item["value"], c, True) out[item.get("label")] = item.get("value") elif _type == "list": choices = item.get("choices") try: j = choices.index(item.get("value")) except Exception: j = 0 new_choice = choices[(j + 1) % len(choices)] item["value"] = new_choice out[item.get("label")] = item.get("value") elif _type == "button": out["button"] = item.get("label") break return out