diff --git a/curses/README b/curses/README index d4d1444..8ee7f80 100644 --- a/curses/README +++ b/curses/README @@ -7,7 +7,7 @@ work on it more and figure out how to use distutils. Right now, it lists current available networks, and whether you are connected to anything or not, all of which is updated in real time. It will actually -connect you to networks now. Configuring them is something soon to come soon. +connect you to networks now. Configuring them is a work in progress. Controls: @@ -23,4 +23,4 @@ ESC or Q: Quit dialog without saving information (if present) -~nacl +~NaCl diff --git a/curses/TODO b/curses/TODO index b306e15..6b3e9fb 100644 --- a/curses/TODO +++ b/curses/TODO @@ -1,7 +1,7 @@ Things to do (in no particular order): -* Make a settings dialog - * Implement something that resembles a combo box in urwid +* Make a settings dialog -- Finish the backend. The frontend is pretty much + done. * Make a network config dialog * Make an about dialog * Implement a keyhandler function for the overall frame @@ -9,6 +9,7 @@ Things to do (in no particular order): * Make color schemes customizable * Integrate this with the my local copy of the experimental branch * Give some indication of activity during the connection process +* Make a man page for the UI. Oh, and most importantly: diff --git a/curses/curses_misc.py b/curses/curses_misc.py new file mode 100644 index 0000000..d34fedb --- /dev/null +++ b/curses/curses_misc.py @@ -0,0 +1,176 @@ +#!/usr/bin/env python +# -* coding: utf-8 -*- + +""" curses_misc.py: Module for various widgets that are used throughout +wicd-curses. +""" + +# Copyright (C) 2008 Andrew Psaltis + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. + +import urwid + +# My savior. :-) +# Although I could have made this myself pretty easily, just want to give credit where +# its due. +# http://excess.org/urwid/browser/contrib/trunk/rbreu_filechooser.py +class SelText(urwid.Text): + """A selectable text widget. See urwid.Text.""" + + def selectable(self): + """Make widget selectable.""" + return True + + + def keypress(self, size, key): + """Don't handle any keys.""" + return key + +class ToggleEdit(urwid.WidgetWrap): + """A edit that can be rendered unselectable by somethhing like a checkbox""" + def __init__(self, caption='', state=True, + attr=('editbx','editfc'),attrnsens='body'): + """ + caption : the Edit's caption + state : the Edit's current sensitivity + attr : tuple of (attr_no_focus, attr_focus) + attrnsens: attr to use when not sensitive + """ + edit = urwid.Edit(caption) + curattr = attr[0] if state == True else attrnsens + w = urwid.AttrWrap(edit,curattr,attr[1]) + self.sensitive=state + self.__super.__init__(w) + + # Kinda like the thing in PyGTK + def set_sensitive(self,state): + self.sensitive=state + if state: + self._w.set_attr('editbx') + else: + self._w.set_attr('body') + + # If we aren't sensitive, don't be selectab;e + def selectable(self): + return self.sensitive + + # Do what an edit does with keys + def keypress(self,size,key): + return self._w.keypress(size,key) + +# Would seem to complicate things a little bit, but could be very useful. ^_^ +# Not used yet. Will be used very shortly, as a superclass of some future +# overlays +class TabColumns(urwid.WidgetWrap): + def __init__(self): + pass + def selectable(self): + return True + def keypress(self,size,key): + pass + +# A "combo box" of SelTexts +# I based this off of the code found here: +# http://excess.org/urwid/browser/contrib/trunk/rbreu_menus.py +class ComboText(urwid.WidgetWrap): + """A ComboBox of text objects""" + class ComboSpace(urwid.WidgetWrap): + """The actual menu-like space that comes down from the ComboText""" + def __init__(self,list,body,ui,show_first=0,pos=(0,0),attr=('body','focus')): + """ + body : parent widget + list : stuff to include in the combobox + ui : the screen + show_first: index of the element in the list to pick first + pos : a tuple of (row,col) where to put the list + attr : a tuple of (attr_no_focus,attr_focus) + """ + + #Calculate width and height of the menu widget: + height = len(list) + width = 0 + for entry in list: + if len(entry) > width: + width = len(entry) + content = [urwid.AttrWrap(SelText(" " + w), attr[0], attr[1]) + for w in list] + self._listbox = urwid.ListBox(content) + + overlay = urwid.Overlay(self._listbox, body, ('fixed left', pos[0]), + width + 2, ('fixed top', pos[1]), height) + self.__super.__init__(overlay) + + def show(self,ui,display): + + dim = ui.get_cols_rows() + keys = True + + #Event loop: + while True: + if keys: + ui.draw_screen(dim, self.render(dim, True)) + + keys = ui.get_input() + + if "window resize" in keys: + dim = ui.get_cols_rows() + if "esc" in keys: + return None + if "enter" in keys: + (wid,pos) = self._listbox.get_focus() + (text,attr) = wid.get_text() + return text + + for k in keys: + #Send key to underlying widget: + self._w.keypress(dim, k) + + #def get_size(self): + + def __init__(self,label,list,body,ui,row = 0,show_first=0,attr=('body','focus')): + """ + label : bit of text that preceeds the combobox + list : stuff to include in the combobox + body : parent widget + ui : the screen + row : where this object is to be found onscreen + show_first: index of the element in the list to pick first + """ + + self.label = urwid.Text(label) + str,trash = self.label.get_text() + + self.cbox = urwid.AttrWrap(SelText(list[show_first]),attr[0],attr[1]) + self.overlay = self.ComboSpace(list,body,ui,show_first,pos=(len(str)+1,row)) + # Unicode will kill me sooner or later. ^_^ + w = urwid.Columns([('fixed',len(str),self.label),self.cbox,('fixed',3,urwid.Text("vvv"))],dividechars=1) + self.__super.__init__(w) + + # We need this to control the keypress + self.body = body + self.ui = ui + # If we press space or enter, be a combo box! + def keypress(self,size,key): + if key == ' ' or key == 'enter': + retval = self.overlay.show(self.ui,self.body) + if retval != None: + self.cbox.set_w(SelText(retval)) + return self._w.keypress(size,key) + + # Most obvious thing ever. :-) + def selectable(self): + return True diff --git a/curses/prefs_curses.py b/curses/prefs_curses.py index a7cc9db..1fb36be 100644 --- a/curses/prefs_curses.py +++ b/curses/prefs_curses.py @@ -1,4 +1,5 @@ #!/usr/bin/env python + # Copyright (C) 2008 Andrew Psaltis # This program is free software; you can redistribute it and/or modify @@ -19,71 +20,14 @@ import urwid from wicd import misc +from curses_misc import SelText,ToggleEdit,ComboText # Will work for now, I guess. language = misc.get_language_list_gui() -class SelText(urwid.Text): - """A selectable text widget. See urwid.Text.""" - - def selectable(self): - """Make widget selectable.""" - return True - - - def keypress(self, size, key): - """Don't handle any keys.""" - return key - -class ToggleEdit(urwid.WidgetWrap): - """A edit that can be rendered unselectable by somethhing like a checkbox""" - def __init__(self, caption='', state=True,attr=('editbx','editfc'),attrnfoc='body'): - edit = urwid.Edit(caption) - curattr = attr[0] if state == True else attrnfoc - w = urwid.AttrWrap(edit,curattr,attr[1]) - self.sensitive=state - self.__super.__init__(w) - def set_sensitive(self,state): - self.sensitive=state - if state: - self._w.set_attr('editbx') - else: - self._w.set_attr('body') - def selectable(self): - return self.sensitive - def keypress(self,size,key): - return self._w.keypress(size,key) - -# Would seem to complicate things a little bit... -class TabColumns(urwid.WidgetWrap): - def __init__(self): - pass - def selectable(self): - return True - def keypress(self,size,key): - pass - -# A "combo box" of SelTexts -class ComboText(urwid.WidgetWrap): - class ComboSpace(urwid.WidgetWrap): - def init(self,body,list,show_first=0,pos=(0,0)): - - #Calculate width and height of the menu widget: - height = len(list) - width = 0 - for entry in list: - if len(entry) > width: - width = len(entry) - self._listbox = urwid.ListBox(list) - - overlay = urwid.Overlay(self._listbox, body, ('fixed left', pos[0]), - width + 2, ('fixed top', pos[1]), height) - - def init(self,list,show_first=0): - pass - class PrefOverlay(urwid.WidgetWrap): - def __init__(self,body,pos): + def __init__(self,body,pos,ui): + self.ui = ui # We are on a VT100, I presume. width = 80 height = 20 @@ -91,7 +35,7 @@ class PrefOverlay(urwid.WidgetWrap): header0_t = language["gen_settings"] header1_t = language["ext_programs"] header2_t = language["advanced_settings"] - self.header0 = urwid.AttrWrap(SelText(header0_t),'body','focus') + self.header0 = urwid.AttrWrap(SelText(header0_t),'tab active','focus') self.header1 = urwid.AttrWrap(SelText(header1_t),'body','focus') self.header2 = urwid.AttrWrap(SelText(header2_t),'body','focus') title = language['preferences'] @@ -104,18 +48,20 @@ class PrefOverlay(urwid.WidgetWrap): #### # General Settings - wired_t=('editcp',language['wired_interface']+':') - wless_t=('editcp',language['wireless_interface']+':') - global_dns_t=(language['use_global_dns']) - search_dom_t= ('editcp','Search domain:') - dns1_t = ('editcp','DNS server 1:') - dns2_t = ('editcp','DNS server 2:') - dns3_t = ('editcp','DNS server 3:') + net_cat_t = ('header','Network Interfaces') + wired_t = ('editcp',language['wired_interface']+': ') + wless_t = ('editcp',language['wireless_interface']+':') + always_show_wired_t = 'Always show wired interface' - always_show_wired_t = 'wired always on' #language['wired always on'] - auto_reconnect_t = language['auto_reconnect'] + global_dns_cat_t = ('header','Global DNS Servers') + global_dns_t = ('editcp',language['use_global_dns']) + search_dom_t = ('editcp',' Search domain:') + dns1_t = ('editcp',' DNS server 1: ') + dns2_t = ('editcp',' DNS server 2: ') + dns3_t = ('editcp',' DNS server 3: ') - #wired_autoconnect_header = 'Wired Autoconnect Setting' + + wired_auto_cat_t= ('header','Wired Autoconnect Settings') wired_auto_1_t = language['use_default_profile'] wired_auto_2_t = language['show_wired_list'] wired_auto_3_t = language['use_last_used_profile'] @@ -123,34 +69,51 @@ class PrefOverlay(urwid.WidgetWrap): #### External Programs automatic_t = language['wicd_auto_config'] - dhcp_header = language["dhcp_client"] + dhcp_header_t = ('header',language["dhcp_client"]) # Automatic dhcp1_t = 'dhclient' dhcp2_t = 'dhcpcd' dhcp3_t = 'pump' - wired_detect_header = language["wired_detect"] - wired1_t = 'ethtool' - wired2_t = 'mii-tool' + wired_detect_header_t = ('header',language["wired_detect"]) + wired1_t = 'ethtool' + wired2_t = 'mii-tool' - route_table_header = language["route_flush"] + route_table_header_t = ('header',language["route_flush"]) route1_t = 'ip' route2_t = 'route' - # Advanced Settings - wpa_t=('editcp',language['wpa_supplicant_driver']+':') + #### Advanced Settings + #wpa_t=('editcp',language['wpa_supplicant_driver']+':') + wpa_cat_t=('header','WPA_Supplicant') + wpa_t=('editcp','Driver:') + wpa_list = ['spam','double spam','triple spam','quadruple spam'] + wpa_warn_t = ('important','You should almost always use wext as the WPA Supplicant Driver') + + backend_cat_t = ('header',language['backend']) + backend_t = language['backend']+':' + backend_list = ['spam','double spam','triple spam','quadruple spam'] + + debug_cat_t = ('header','Debugging') debug_mode_t = language['use_debug_mode'] + + wless_cat_t = ('header','Wireless Interface') use_dbm_t = language['display_type_dialog'] - # backend_sel_t = + + auto_reconn_cat_t = ('header','Automatic Reconnect') + auto_reconn_t = 'Automatically reconnect on connection loss' + #### #### UI Widgets #### # General Settings - self.wpa_edit = urwid.AttrWrap(urwid.Edit(wpa_t),'editbx','editfc') + self.net_cat = urwid.Text(net_cat_t) self.wired_iface = urwid.AttrWrap(urwid.Edit(wired_t),'editbx','editfc') self.wless_iface = urwid.AttrWrap(urwid.Edit(wless_t),'editbx','editfc') + + self.global_dns_cat = urwid.Text(global_dns_cat_t) global_dns_state = False self.global_dns = urwid.CheckBox(global_dns_t,global_dns_state, on_state_change=self.global_dns_trigger) @@ -160,58 +123,109 @@ class PrefOverlay(urwid.WidgetWrap): self.dns3 = ToggleEdit(dns3_t,global_dns_state) self.always_show_wired = urwid.CheckBox(always_show_wired_t) - self.auto_reconnect = urwid.CheckBox(auto_reconnect_t) - self.debug_mode = urwid.CheckBox(debug_mode_t) - self.use_dbm = urwid.CheckBox(use_dbm_t) wired_auto_l = [] - self.wired_auto_1_r = urwid.RadioButton(wired_auto_l,wired_auto_1_t) - self.wired_auto_2_r = urwid.RadioButton(wired_auto_l,wired_auto_2_t) - self.wired_auto_3_r = urwid.RadioButton(wired_auto_l,wired_auto_3_t) - generalPile = urwid.Pile([ - self.wired_iface,#self._blank, - self.wless_iface,self._blank, + self.wired_auto_cat = urwid.Text(wired_auto_cat_t) + self.wired_auto_1 = urwid.RadioButton(wired_auto_l,wired_auto_1_t) + self.wired_auto_2 = urwid.RadioButton(wired_auto_l,wired_auto_2_t) + self.wired_auto_3 = urwid.RadioButton(wired_auto_l,wired_auto_3_t) + generalPile = urwid.Pile([self.net_cat, + self.wless_iface,#self._blank, + self.wired_iface, + self.always_show_wired,self._blank, + self.global_dns_cat, self.global_dns,#self._blank, self.search_dom, self.dns1,self.dns2,self.dns3,self._blank, - self.always_show_wired, - self.auto_reconnect, - self.debug_mode, - self.use_dbm,self._blank, - self.wired_auto_1_r, - self.wired_auto_2_r, - self.wired_auto_3_r + self.wired_auto_cat, + self.wired_auto_1, + self.wired_auto_2, + self.wired_auto_3 ]) - #externalPile = urwid.Pile() + #### External Programs tab + automatic_t = language['wicd_auto_config'] + + self.dhcp_header = urwid.Text(dhcp_header_t) + dhcp_l = [] + # Automatic + self.dhcp0 = urwid.RadioButton(dhcp_l,automatic_t) + self.dhcp1 = urwid.RadioButton(dhcp_l,dhcp1_t) + self.dhcp2 = urwid.RadioButton(dhcp_l,dhcp2_t) + self.dhcp3 = urwid.RadioButton(dhcp_l,dhcp3_t) + + wired_l = [] + self.wired_detect_header = urwid.Text(wired_detect_header_t) + self.wired0 = urwid.RadioButton(wired_l,automatic_t) + self.wired1 = urwid.RadioButton(wired_l,wired1_t) + self.wired2 = urwid.RadioButton(wired_l,wired2_t) + + route_l = [] + self.route_table_header = urwid.Text(route_table_header_t) + self.route0 = urwid.RadioButton(route_l,automatic_t) + self.route1 = urwid.RadioButton(route_l,route1_t) + self.route2 = urwid.RadioButton(route_l,route2_t) + + externalPile = urwid.Pile([self.dhcp_header, + self.dhcp0,self.dhcp1,self.dhcp2,self.dhcp3, + self._blank, + self.wired_detect_header, + self.wired0,self.wired1,self.wired2, + self._blank, + self.route_table_header, + self.route0,self.route1,self.route2 + ]) - - # Advanced Settings - # WPA Supplicant: Combo Box - # Backend: Combo box - # Debugging - # Enable debug mode - # Wireless Interface - # Use DBM to measure signal strength + #### Advanced settings + self.wpa_cat = urwid.Text(wpa_cat_t) + self.wpa_cbox = ComboText(wpa_t,wpa_list,self,ui,4) + self.wpa_warn = urwid.Text(wpa_warn_t) - advancedPile = urwid.Pile([self.wpa_edit,self._blank]) - - self.columns = urwid.Columns([('fixed',len(header0_t),self.header0),('fixed',len(header1_t),self.header1),urwid.Text(('header',title),align='right')],dividechars=1) + self.backend_cat = urwid.Text(backend_cat_t) + self.backend_cbox = ComboText(backend_t,backend_list,self,ui,8) + self.debug_cat = urwid.Text(debug_cat_t) + self.debug_mode = urwid.CheckBox(debug_mode_t) + + self.wless_cat = urwid.Text(wless_cat_t) + self.use_dbm = urwid.CheckBox(use_dbm_t) + + self.auto_reconn_cat = urwid.Text(auto_reconn_cat_t) + self.auto_reconn = urwid.CheckBox(auto_reconn_t) + + advancedPile = urwid.Pile([self.wpa_cat, + self.wpa_cbox,self.wpa_warn,self._blank, + self.backend_cat, + self.backend_cbox,self._blank, + self.debug_cat, + self.debug_mode, self._blank, + self.wless_cat, + self.use_dbm, self._blank, + self.auto_reconn_cat, + self.auto_reconn]) + self.tab_map = {self.header0 : generalPile, - self.header1 : advancedPile, + self.header1 : externalPile, self.header2 : advancedPile} + self.active_tab = self.header0 + self.columns = urwid.Columns([('fixed',len(header0_t),self.header0), + ('fixed',len(header1_t),self.header1), + ('fixed',len(header2_t),self.header2), + urwid.Text(('header',title),align='right')], + dividechars=1) + content = [self.columns,generalPile] #self._label = urwid.AttrWrap(SelText(titles),attr[0],attr[1]) self.walker = urwid.SimpleListWalker(content) self._listbox = urwid.ListBox(self.walker) - self._boxadap = urwid.BoxAdapter #self._linebox = urwid.LineBox(self._listbox) overlay = urwid.Overlay(self._listbox, body, ('fixed left', pos[0]), width + 2, ('fixed top', pos[1]), height) self.__super.__init__(overlay) + + def global_dns_trigger(self,check_box,new_state,user_data=None): for w in self.search_dom,self.dns1,self.dns2,self.dns3: @@ -223,6 +237,9 @@ class PrefOverlay(urwid.WidgetWrap): if wid is self.columns: lw = self._listbox.body lw.pop(1) + self.active_tab.set_attr('body') + self.columns.get_focus().set_attr('tab active') + self.active_tab = self.columns.get_focus() lw.append(self.tab_map[self.columns.get_focus()]) self._listbox.body = lw diff --git a/curses/wicd-curses.py b/curses/wicd-curses.py index e07e9bb..b605256 100644 --- a/curses/wicd-curses.py +++ b/curses/wicd-curses.py @@ -55,6 +55,7 @@ from wicd import dbusmanager import sys # Curses UIs for other stuff +from curses_misc import SelText import prefs_curses from prefs_curses import PrefOverlay @@ -109,22 +110,6 @@ class wrap_exceptions: return wrap_exceptions -# My savior. :-) -# Although I could have made this myself pretty easily, just want to give credit where -# its due. -# http://excess.org/urwid/browser/contrib/trunk/rbreu_filechooser.py -class SelText(urwid.Text): - """A selectable text widget. See urwid.Text.""" - - def selectable(self): - """Make widget selectable.""" - return True - - - def keypress(self, size, key): - """Don't handle any keys.""" - return key - ######################################## ##### SUPPORT FUNCTIONS ######################################## @@ -430,7 +415,7 @@ class appGUI(): # Prevents automatic reconnecting if that option is enabled daemon.SetForcedDisconnect(True) if "P" in keys: - dialog = PrefOverlay(self.frame,(0,1)) + dialog = PrefOverlay(self.frame,(0,1),ui) dialog.run(ui,self.size,self.frame) for k in keys: if k == "window resize": @@ -486,17 +471,20 @@ def main(): # Color scheme. # Other potential color schemes can be found at: # http://excess.org/urwid/wiki/RecommendedPalette + # Note: the current palette below is optimized for the linux console. + # For example, this will look like crap on a default-colored XTerm. + # NB: To find current terminal background use variable COLORFGBG ui.register_palette([ - ('body','light gray','black'), + ('body','light gray','default'), ('focus','dark magenta','light gray'), - ('header','light blue','black'), - ('important','light red','black'), - ('connected','dark green','black'), - ('connected focus','black','dark green'), - # I'll be needing these soon, so I'll leave them here for now. - ('editcp', 'light gray', 'black', 'standout'), + ('header','light blue','default'), + ('important','light red','default'), + ('connected','dark green','default'), + ('connected focus','default','dark green'), + ('editcp', 'default', 'default', 'standout'), ('editbx', 'light gray', 'dark blue'), - ('editfc', 'white','dark blue', 'bold') ]) + ('editfc', 'white','dark blue', 'bold'), + ('tab active','dark green','light gray')]) # This is a wrapper around a function that calls another a function that is a # wrapper around a infinite loop. Fun. ui.run_wrapper(run)