Commits

Anonymous committed 6ce1577

Finalizing API changes in the support modules and stabilizing the gtk UI.

  • Participants
  • Parent commits 133dafb

Comments (0)

Files changed (12)

 
 To Do:
 ------
+* vkmap (keyboard mapping settings)
+  [ Mappings come from /usr/share/kbd/keymaps/i386/qwerty and /usr/share/kbd/keymaps/i386]
 * Boot manager setup (lilo and grub configure utility)
 * vnetconf equivalent (hostname)
     All other networking settings are handled by wicd, or some other networking daemon.
 *   repos.py    Manage slapt-getrc software sources.
 *   faveapps.py Manage user's preferred applications.
 *   disk_utility.py Launch the gnome disk utility (allows some operations as non-root user)
-*   filesystems.py  Launch gparted (only available as root)
+*   filesystems.py  Launch gparted (only available as root)

File modules/bootloaders.py

 
 VASM_CATEGORY = "%s/%s"% (_("System"), _("Startup Options"))
 VASM_LABEL=_("System Boot Menu")
-VASM_ICON=os.path.join(os.getcwd(), "modules/support/data/icons/bootmenu.png")
+VASM_ICON=os.path.join(os.getcwd(), "modules/support/data/icons/bootmenu.png")

File modules/dateset.py

         widgets.VasmModule.__init__(self, parent=parent,
                                     module_title = title,
                                     module_desc = description)
+        # The models
+        self.TZmodel = DATESET.TimeZone()
+        self.HWClockModel = DATESET.HWClock()
+        self.DateModel = DATESET.DateTime()
+        
         # add the calendar
         self.calendar = gtk.Calendar()
         calbox = gtk.HBox()
-        calbox.pack_start(self.calendar, True, True, 4)
-        self.pack_start(calbox, True,True, 4)
+        calbox.pack_start(self.calendar, False, True, 2)
+        self.pack_start(calbox, False, False, 4)
         
-        # the clock
-        self.clock = widgets.TimePicker()
+
+        # Add a clock below the calendar.
+        clockframe = gtk.Frame(_("System time"))
+        clockbox = gtk.VBox()
+        self.clock = widgets.WideTimePicker()
+        clockbox.pack_start(self.clock, False, True, 8)
+        clockframe.add(clockbox)
+        self.pack_start(clockframe, False, True, 4)
+
+        # hardware clock setting.
+        hwclockframe = gtk.Frame(_("Hardware Clock"))
+        hwbox = gtk.VBox()
+        hwclockframe.add(hwbox)
+        msg = (_("Linux uses the hardware clock setting and timezone to properly set the system time."),
+               " ",
+               _("To get the propper results, select the value that matches your hardware (BIOS) clock setting."),
+            _("If you are unsure about this, 'Localtime' is the common value for most PC's."))
+        lb = widgets.DLabel(' '.join(msg))
+        self.hwclockopts = widgets.HOptionsBox(
+            label = "<b>%s</b>"% _("Hardware Clock Setting:") + " ",
+            options = ['UTC', 'Localtime']
+            )
+        # Set the default value for this choicebox
+        self.hwclockopts.set_value(
+            self.HWClockModel.get_value()
+            )
+        hwbox.pack_start(lb, False, True, 4)
+        hwbox.pack_start(self.hwclockopts, False, True, 8)
+        # The timezone picker
+        zoneoptions = self.TZmodel.list_zones()
+        self.tzselector = widgets.DropdownBox(
+            label="<b>%s </b>"% _("Select Timezone:"),
+            choices = zoneoptions
+            )
+        # set the current value on the dropdown list
+        self.tzselector.set_value(
+            self.TZmodel.get_zone()
+            )
+        hwbox.pack_start(self.tzselector, False, True, 8)
+        # Add the content to the module body
+        self.pack_start(hwclockframe, False, True, 4)
         
-        timeframe = gtk.Frame(_("Set the system time."))
-        timebox = gtk.VBox()
-        empty = gtk.Label()
-        timebox.pack_start(empty, False, False, 0)
-        clockbox = gtk.HBox()
-        clockbox.pack_start(self.clock, False, False, 4)
-        timebox.pack_start(clockbox, False, False, 0)
-        timebox.pack_start(gtk.Label(), False, False, 0)
-        timeframe.add(timebox)
-        self.pack_start(timeframe, False, False, 4)
-
-        # buttons
+        # module action buttons
         btclose = gtk.Button(stock=gtk.STOCK_CANCEL)
         btclose.connect('clicked', self.close_module)
         self.add_button(btclose)
         bt_apply = gtk.Button(stock=gtk.STOCK_APPLY)
-        bt_apply.connect('clicked', self.set_date_time)
+        bt_apply.connect('clicked', self.apply_event)
         self.add_button(bt_apply)
-    
-    def set_date_time(self, widget=None):
-        # Set the system date first
-        code, output = DATESET.set_date(self.calendar.get_date())
-        if code > 0:
-            dialogs.error("%s\n\n%s"% (
-                _("There was an error while setting the system date.  Here is the output"),
-                output))
-            return
-        code, output = DATESET.set_time(self.clock.get_time())
-        if code > 0:
-            dialogs.error("%s\n\n%s"% (
-                _("There was an error while setting the system time.  Here is the error."),
-                output))
-            return
+
+    def _set_date(self, widget=None):
+        """ Save the date settings to the system."""
+        _year, _month, _day = self.calendar.get_date()
+        # gtk.Calendar's moth array starts with 0, so we need to add 1 to the returned value
+        self.DateModel.year = _year
+        self.DateModel.month = _month + 1
+        self.DateModel.day = _day
+        return self.DateModel.set_date()
+
+    def _set_time(self, widget=None):
+        """ Save the time settings to the system. """
+        _hr, _min, _sec = self.clock.get_value()
+        self.DateModel.hour = _hr
+        self.DateModel.minute = _min
+        self.DateModel.second = _sec
+        return self.DateModel.set_time()
+
+    def _set_hwclock_setting(self, widget=None):
+        """ Save the hardware clock setting to the system. """
+        hwc = self.hwclockopts.get_value()
+        return self.HWClockModel.set_value(hwc)
+
+    def _set_timezone(self, widget=None):
+        """ Save the timezone setting to the system."""
+        zone = self.tzselector.get_value()
+        return self.TZmodel.set_zone(zone)
+
+    def apply_event(self, widget=None):
+        """ Save all settings. """
+        try:
+            self._set_date()
+        except DATESET.DateTimeError as e:
+            return dialogs.error(
+                "%s \n\n%s"% (
+                    _("Failed to set the sytem date."),
+                    e
+                    )
+                )
+        try:
+            self._set_time()
+        except DATESET.DateTimeError as e:
+            return dialods.error(
+                "%s \n\n%s"% (
+                    _("Failed to set the system time."), e
+                    )
+                )
+
+        try:
+            self._set_hwclock_setting()
+        except IOError as e:
+            # unable to lock the file
+            return dialogs.error(
+                "%s \n\n%s"%
+                (_("Unable to save hardware clock setting."), e)
+                )
+        except AssertionError as e:
+            return dialogs.error(
+                _("Unable to save the hardware clock setting") + \
+                "\n\n" % e
+                )
+        try:
+            self._set_timezone()
+        except IOError as e:
+            return dialogs.error(
+                "%s \n\n %s"% (
+                    _("Unable to save timezone setting."), e
+                    )
+                )
+
+        dialogs.info(
+            _("Settings successfully saved.")
+            )
+        return self.close_module()
         
-        dialogs.info(
-            _("The system date and time settings have been successfully adjusted."))
-        self.close_module()
+
 
 def __vasm_test__():
     """ Make this available to root only """

File modules/pyfstab.py

     def __init__(self, parent):
         self._parent = parent
         tx = _("Setup the partitions and devices that should be automatically mounted when the system starts up.")
-        widgets.VasmModule.__init__(self, parent=parent,
+        widgets.VasmModule.__init__(self, parent=self._parent,
                                     module_title=VASM_LABEL, module_desc=tx)
         
         self.datamodel = FSTAB.FstabModel()
         ret = dialogs.question(
             _("Are you sure you want to remove this entry?"), parent=self._parent)
         if ret == gtk.RESPONSE_YES:
-            return self.datamodel.delete_entry(selected)
-        # user cancelled
+            try:
+                self.datamodel.removeEntry(selected)
+            except AssertionError as e:
+                return dialogs.error(
+                    _("Unable to delete this entry") + "  \n\n" + e)
+        # Simply return if the user backs out
         return
     
     def add_event(self, widget=None):
                 if w.targetbox.get_text() == "":
                     errtxt = _(("You did not specify a mount point for your entry.  "
                                 "Incomplete entries cannot be processed"))
-                    dialogs.error(txt=errtxt, title="Incomplete Entry", parent=self)
+                    dialogs.error(txt=errtxt, title="Incomplete Entry", parent=self._parent)
                     #dialogs.error("%s")% errtxt
                     w.destroy()
                     return
     def refresh_fstab_list(self):
         store = self.fstree.get_model()
         store.clear()
-        for entry in self.datamodel.get_active_listing():
-            store.append(row=entry)
+        for entry in self.datamodel.listEntries():
+            uid = entry.uuid or "N/A"
+            store.append([
+                entry.device, entry.filesystem, entry.mountpoint, entry.options, uid
+                ])
         
 
 def __run__(parent):
 class FstabEntryDialog(gtk.Dialog):
     """ Dialog to collect information for a new entry to add to /etc/fstab """
     def __init__(self, parent=None, fstabmodel=None):
-        gtk.Dialog.__init__(self)
+        gtk.Dialog.__init__(self, parent=parent)
         self.set_has_separator(True)
         self.add_button(gtk.STOCK_SAVE, gtk.RESPONSE_OK)
         self.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
     
     def load_available_partitions(self, widget):
         """ Add the available partitions to widget.  Widget is a combobox"""
-        currentlist = self.fstabmodel.get_active_listing()
         allpartitions = FSTAB.list_all_system_partitions()
-        existing = []
-        for item in currentlist:
-            existing.append(item[0])
-        for part in allpartitions:
-            if not part in existing:
-                widget.append_text(part)
+        for item in allpartitions:
+            if not self.fstabmodel.hasEntry(item):
+                widget.append_text(item)
     
     def select_target_event(self, widget=None):
         """ Browse to select the target directory """
         dia.hide()
         if res == gtk.RESPONSE_OK:
             self.targetbox.set_text(dia.get_filename())
-            self.target = dia.get_filename()
+            self.entryobject.mountpoint = dia.get_filename()
         dia.destroy()
 
     def combobox_change_event(self, combobox):
         sel = combobox.get_active_text()
-        fs = FSTAB.get_partition_filesystem(sel)
-        options = FSTAB.get_default_fstab_options(fs)
+        self.entryobject = FSTAB.FstabEntry(sel)
+        self.entryobject.filesystem = self.entryobject.find_filesystem()
+        self.entryobject.options = self.entryobject.find_default_options()
+        #        fs = self.entryobject.find_filesystem()
+        #        options = self.entryobject.find_default_options()
         # set the filesystem text on a label for informational puposes.
-        self.fslabel.set_label(fs)
+        self.fslabel.set_label(self.entryobject.filesystem)
         # set the default options
-        self.optionsbox.set_text(options)
+        self.optionsbox.set_text(self.entryobject.options)
         self.device = sel
-        if "swap" in fs:
+        if "swap" in self.entryobject.filesystem:
             self.targetbox.set_text("none")
             self.targetbox.set_property("sensitive", False)
             self.btSelectTarget.set_property("sensitive", False)
     def do_add_entry(self):
         """ Add an entry to fstab.  This method needs to check to make sure
         all parameters are right to call add_entry on the FSTAB.FstabModel"""
-        self.target = self.targetbox.get_text()
-        fs = self.fslabel.get_label()
-        options = self.optionsbox.get_text()
-        
-        if self.target == "":
-            # we cannot add this entry because it is missing the mountpoint
-            return True
-        
-        # if the mountpoint does not exist, create it.
-        if self.target is not "none" and not os.path.isdir(self.target):
-            os.makedirs(self.target)
-        # create the entry on fstab.
-        return self.fstabmodel.add_entry(self.device,
-                                         self.target,
-                                         fs,
-                                         options)
-
+        if self.fstabmodel.hasEntry(self.entryobject.device):
+            return dialogs.error(
+                _("An entry for this device already exists.  Please delete it and add it again.")
+                )
+        try:
+            self.fstabmodel.addEntry(self.entryobject)
+        except AssertionError as e:
+            return dialogs.error(
+                _("Unable to add entry.") + " \n\n" + e)
+        # Create the mountpoint if it does not exist
+        if not os.path.exists(self.entryobject.mountpoint):
+            os.makedirs(self.entryobject.mountpoint)
+        return

File modules/support/vectorlinux/BOOTLOADERS.py

                 ret.append("%s label = %s"% (spacer, entry.label))
             else:
                 ret.append("%s label = VectorLinux"% spacer)
-            if entry.initrd is not "":
+            if entry.initrd not in ("",None):
                 ret.append("%s initrd = %s"% (spacer, entry.initrd))
             if entry.appendline is not "":
                 if "splash" not in entry.appendline:
                     elif _line.startswith("}"):
                         break
                 res.append(entry)
-        return res
+        return res

File modules/support/vectorlinux/DATESET.py

     def _line_sets_value(self, line):
         """ Returns True if line is "localtime" or "UTC" """
         return line.startswith("localtime") or line.startswith("UTC") \
-            or line.startswith('LocalTime') or line.startswith('LOCALTIME')
+            or line.startswith('LocalTime') or line.startswith('LOCALTIME') \
+            or line.startswith("Localtime")
 
     def get_value(self):
         """ Read self.descriptor and return the current setting """
                     ret = line.strip()
         # make sure we have a valid setting in there, otherwise, we wont be able
         # to set the value later
-        assert ret != ""
+        assert ret != "", "Unable to get current hardware clock setting."
         return ret
 
     def set_value(self, value=""):
     def __init__(self, zone=None):
         self.zone = zone
         self.tzlink = "/etc/localtime-copied-from"
-        self.timezones_base_dir = "/usr/share/zoneinfo"
+        self.timezones_base_dir = "/usr/share/zoneinfo/"
 
     def get_zone(self):
         """ Return a string representing the current timezone """
         os.symlink(src, dest)
         return self._activate_zone()
 
+    def list_zones(self):
+        """ Return a list of time zones to be presented in a UI """
+        lst = []
+
+        for zone in os.listdir(self.timezones_base_dir): # level 1
+            zonepath = os.path.join(self.timezones_base_dir, zone)
+            if os.path.isdir(zonepath): # level 2
+                for subzone in os.listdir(zonepath):
+                    subzonepath = os.path.join(zonepath, subzone)
+                    if os.path.isdir(subzonepath): # level 3
+                        for last in os.listdir(subzonepath):
+                            lastpath = os.path.join(subzonepath, last)
+                            if os.path.isdir(lastpath):
+                                for lastzone in os.listdir(lastpath):
+                                    lst.append("%s/%s/%s/%s"% ( zone, subzone, last, lastzone))
+                            else:
+                                lst.append("%s/%s/%s"% (zone,subzone,last))
+                    else:
+                        lst.append("%s/%s"% (zone,subzone))
+            else:
+                if not os.path.islink(zone) and '.' not in zone:
+                    lst.append(zone)
+        lst.sort()
+        return lst
+        
+
 
 class DateTime:
     """Manage the system date and time.
         datestr = "%s%s%s"% (self.year, month, day)
         cmd = ["/bin/date", "+%Y%m%d", "-s", datestr]
         proc = utils.get_popen(cmd)
-        output = proc.communicate()[1]
+        out, err = proc.communicate()
         rcode = proc.returncode
 
         if rcode > 0:
-            _log = ("Unable to set the system date.", " ",
-                "Command: %s said \"%s\""% (" ".join(cmd),
-                    "".join(output)))
-            log.fatal(" ".join(_log))
-            raise DateTimeError(_log)
+            _log = ("Command: %s said \"%s\""% (" ".join(cmd),
+                    err))
+            log.fatal("".join(_log))
+            raise DateTimeError(''.join(_log))
         else:
             log.debug("System date has been set to %s"% datestr)        
 
         timestr = "%s:%s:%s" % (self.hour, self.minute, self.second)
         cmd = ["/bin/date", "+%T", "-s", timestr]
         proc = utils.get_popen(cmd)
-        output = proc.communicate()[1]
+        out, err = proc.communicate()
         rcode = proc.returncode
 
         if rcode > 0:
-            _log = ("Unable to set the system time.", " ",
-            "Command: %s said \"%s\""% (" ".join(cmd), "".join(output)))
+            _log = ("Command: %s said \"%s\""% (" ".join(cmd), err))
             log.fatal(_log)
-            raise DateTimeError(_log)
+            raise DateTimeError(''.join(_log))
         else:
             log.debug("System time has been set to %s"% timestr)
 

File modules/support/vectorlinux/FSTAB.py

     """ return a list of all partition paths"""
     partitions = []
     for dev in parted.getAllDevices():
+        if "-ROM" in dev.model:
+            continue
         if dev.type == 1:
             dsk = parted.Disk(dev)
             for part in dsk.partitions:
             options = entry.find_default_options()
         else:
             options = entry.options
-
+        data = []
+        with open(self.dataobject) as f:
+            for line in f:
+                data.append(line)
         line = sep.join((
             part,
             entry.mountpoint,
             entry.filesystem,
             options
             ))
-        data = [line]
+        data.append(line + "\n")
         f = open(self.dataobject, 'w')
         f.writelines(''.join(data))
         f.close()

File modules/support/vectorlinux/PASSWORDS.py

     pass
 class BadNewPasswordException(Exception):
     pass
+class UserModError(Exception):
+    pass
 
 def test_passwords_match(pass1, pass2):
     """ Check if both passwords are the same """
 def change_password(user, oldpassword, newpassword):
     """Proxy to decide how to change the user password.
     If root is changing someone's password, tehre is no need to user pexpect"""
-    if os.geteuid() == 0 and user != "root":
-        # root is changing somebody's password. root is not allowed to do this
-        # for the root account. (it's clumpsy)
+    if os.geteuid() == 0:
+        # root can do anything !
+        if user == "own":
+            user = "root"
         return change_password_usermod(user, newpassword)
     else:
-        # We do not want root changing his/her password using usermod.
-        return change_password_pexpect(user, oldpassword, newpassword)
-    
+        # Non-privileged accounts are not allowed to use usermod.
+        return change_password_pexpect(user, oldpassword, newpassword)    
 
 def change_password_usermod(login, password):
     """ change the user password using usermod"""
     salt = password[0] + password[-1]
     epass = crypt.crypt(password, salt)
-    cmd = ["/usr/bin/usermod", "-p", epass, login]
-    proc = get_popen(cmd)
-    #cmd = "/usr/sbin/usermod -p %s %s"% (epass, login)
-    #proc = sp.Popen(cmd.split(), stdout=sp.PIPE, stderr=sp.STDOUT)
-    stderr = proc.communicate()[1]
-    code = proc.returncode
-    if code > 0:
-        msg = ("Unable to change password for user %s"% login, " ",
-            "Command %s said \" %s \""% (cmd[0], " ".join(stderr)))
-        log.fatal(" ".join(msg))
-    else:
-        log.debug("Successfully changed password for user %s"% login)
-    return code
+    proc = get_popen(
+        ['/usr/sbin/usermod','-p', epass, login]
+        )
+    out, err = proc.communicate()
+    retv = proc.returncode
+    if retv > 0:
+        raise UserModError(err)
+    return
     
 def change_password_pexpect(user, oldpassword, newpassword):
     """ Change user password using pexpect

File modules/support/vectorlinux/USERADMIN.py

 from utils import get_popen
 
 class EmptyAttributeError(Exception):
-    def __init__(self, value):
-        self.value = value
-
-    def __str__(self):
-        return repr(self.value)
-        
+    def __init__(self, message):
+        Exception.__init__(self)
+        self.message = message
 
 # __distro_groups__ is a dictionary of the default vectorlinux
 # user groups and their default value (True or False) for new user accounts.
     "wheel": ("Elite user", False)
 }
 
+class AccountError(Exception):
+    pass
+
 class User(object):
     """ convinience methods for working with a single user account """
     def __init__(self, login=None, new=False):
         self.login = login
         uinfo = None
+        self.password = None # The password is always none, on new accounts, the password is stored here
         if not new:
             uinfo = [p for p in pwd.getpwall() if p.pw_name == login][0]
             self.homedir = uinfo.pw_dir
             self.uid = uinfo.pw_uid
             self.fullname = uinfo.pw_gecos
+            self.shell = uinfo.pw_shell
             # get the group listing
             self.groups = []
             for line in grp.getgrall():
             self.fullname = None
             self.groups = None
             self.faceicon = None
+            self.shell = None
+            self.password = None
 
+    def save_groups_changes(self):
+        """ Save the group membership changes for this account """
+        # After self.groups has been set, call this method to save the changes.
+        assert self.groups is not None, "Empty groups attribute.  Please set the groups value first."
+        assert self.login is not None, "User account does not exist in this system yet.  New account?"
+        proc = get_popen([
+            "/usr/sbin/usermod","-G",",".join(self.groups), self.login
+            ])
+        out, err = proc.communicate()
+        retv = proc.returncode
+        if retv > 0:
+            raise AccountError(err)
+        return
 
 class UserModel(object):
     """ Model to be used for creating and managing user accounts """
     def notify(self):
         """ Notify any observers of the changes in the current user setup """
         for f in self.watchers:
-            f(self.list_all())
+            f(self)
         return
 
     def _check_user_object(self, usuario):
         if not hasattr(usuario, 'password') or usuario.password is None:
             raise EmptyAttributeError('Password attribute is empty.  Expected a string.')
         if not hasattr(usuario, 'groups') or usuario.groups is None or type(usuario.groups) is not list:
-            raise EmptyAttributeError('Empty groups attribute.  Expected a list.')
+            raise EmptyAttributeErro('Empty groups attribute.  Expected a list.')
         if not hasattr(usuario, 'password') or usuario.password is None:
             raise EmptyAttributeError('Empty password attribute.  Expected a string.')
         assert len(usuario.password) >= 5, "Provided password for new user is too short."

File modules/support/widgets.py

 vlbluecolor = gtk.gdk.color_parse("#003063")
 vllightbluecolor = gtk.gdk.color_parse("#6d87a3")
 
+horizontal = gtk.HBox
+vertical = gtk.VBox
 
 class VasmEmbedder(object):
     def __init__(self, parent=None, command="", window_title=""):
             time.sleep(1)
         return wid
 
+class HOptionsBox(gtk.HBox):
+    def __init__(self, label=None, options=[]):
+        gtk.HBox.__init__(self)
+        self._options = []
+        if label is not None:
+            self.label = gtk.Label()
+            self.label.set_use_markup(True)
+            self.label.set_markup(label)
+            self.label.set_property('xalign', 0.0)
+            self.pack_start(self.label, False, False, 4)
+        for choice in options:
+            grp = self._get_rb_group()
+            wid = gtk.RadioButton(grp, choice)
+            self._options.append(wid)
+            self.pack_start(wid, False, False, 4)
+
+    def set_label(self, txt):
+        return self.label.set_markup(txt)
+
+    def set_value(self, value):
+            for wid in self._options:
+                if wid.get_label() == value or wid.get_label().lower() == value.lower():
+                    wid.set_active(True)
+                    return
+            return
+
+    def get_value(self):
+        for wid in self._options:
+            if wid.get_active():
+                return wid.get_label()
+        return None
+
+    def _get_rb_group(self):
+        if len(self._options) > 0:
+            return self._options[0]
+        return None
+            
 
 class VasmSubModule(gtk.VBox):
     """ Submodule widget to be used as a sub-category display """
         closebtn = gtk.Button(stock=gtk.STOCK_GO_BACK)
         closebtn.connect('clicked', self.close_module)
         self.action_area.pack_end(closebtn, False, False, 4)
-        self.pack_end(self.action_area, False, True, 4)
+        # Leave the action area unpacked because the main UI will put it in a different spot.
+        # self.pack_end(self.action_area, False, True, 4)
         
         # Add a separator
         sep = gtk.HSeparator()
         self.pack_start(titlebox, False, True, 4)
         self.pack_start(descbox, False, True, 8)
         self.action_area = gtk.HBox()
-        self.pack_end(self.action_area, False, False, 2)
-                # the separator
+        #self.pack_end(self.action_area, False, False, 2)
+        # the separator
         sep = gtk.HSeparator()
         self.pack_end(sep, False, False, 2)
     
     
     def add_button(self, button):
         """ Add a button to the action area """
-        if type(button) == type(("foo",'bar')):
+        if type(button) is tuple:
         #if type(button) is not gtk.Button:
             bt, cb = button
             bt.connect('clicked', cb)
     def set_label(self, markup):
         return self.label.set_markup(markup)
 
-class TimePicker(gtk.HBox):
-    """ Returns a gtk.HBox holding 3 spinboxes to select the time """
+class HLabeledSpinner(gtk.HBox):
+    def __init__(self, label="", spinner_range=59.0):
+        gtk.HBox.__init__(self)
+        self.label = gtk.Label(label)
+        self._adj = gtk.Adjustment(0.0, 0.0, spinner_range, 1.0, 5.0, 0.0)
+        self._spinner = gtk.SpinButton(self._adj)
+        self._spinner.set_property('numeric', True)
+        self.pack_start(self.label, False, True, 2)
+        self.pack_start(self._spinner, False, True, 2)
+
+    def get_value(self):
+        return self._spinner.get_value()
+
+    def set_value(self, value):
+        return self._spinner.set_value(value)
+
+    def set_label(self, label):
+        return self.label.set_property('label', label)
+
+class VLabeledSpinner(gtk.VBox):
+    def __init__(self, label="", spinner_range=59.0):
+        gtk.VBox.__init__(self)
+        self.label = gtk.Label(label)
+        self.label.set_property('xalign', 0.0)
+        self._adj = gtk.Adjustment(0.0, 0.0, spinner_range, 1.0, 5.0, 0.0)
+        self._spinner = gtk.SpinButton(self._adj)
+        self._spinner.set_property('numeric', True)
+        self.pack_start(self.label, False, True, 2)
+        self.pack_start(self._spinner, False, True, 2)
+
+    def get_value(self):
+        return self._spinner.get_value()
+
+    def set_value(self, value):
+        return self._spinner.set_value(value)
+
+    def set_label(self, txt):
+        return self.label.set_property('label', txt)
+
+class StackedTimePicker(gtk.HBox):
     def __init__(self):
         gtk.HBox.__init__(self)
-        hradj = gtk.Adjustment(0.0,0.0,23.0,1.0,5.0,0.0)
-        minadj = gtk.Adjustment(0.0,0.0,59.0, 1.5, 10.0, 0.0)
-        secadj = gtk.Adjustment(0.0, 0.0, 59.0, 1.5, 10.0, 0.0)
-        self._hrspin = gtk.SpinButton(hradj)
-        #self._hrspin.set_property('numeric', True)
-        self._minspin = gtk.SpinButton(minadj)
-        self._secspin = gtk.SpinButton(secadj)
-        for spinner, label in ((self._hrspin, _("Hour:")),
-            (self._minspin, _("Minutes:")),
-            (self._secspin, _("Seconds:"))):
-            spinner.set_property('numeric', True)
-            self.pack_start(gtk.Label(label), False, False, 2)
+        # we'll get 3 boxes and pack them
+        self._hrspin = VLabeledSpinner("Hours", spinner_range=23.0)
+        self._minspin = VLabeledSpinner("Minutes")
+        self._secspin = VLabeledSpinner("Seconds")
+        for spinner in (self._hrspin, self._minspin):
+            sep = gtk.VSeparator()
             self.pack_start(spinner, False, False, 2)
-        self._hrspin.set_value(datetime.datetime.now().hour)
-        self._minspin.set_value(datetime.datetime.now().minute)
-        self._secspin.set_value(datetime.datetime.now().second)
+            self.pack_start(sep, False, False, 0)
+        self.pack_start(self._secspin, False, False, 2)
+        now = datetime.datetime.now()
+        self._hrspin.set_value(now.hour)
+        self._minspin.set_value(now.minute)
+        self._secspin.set_value(now.second)
+
+class WideTimePicker(gtk.HBox):
+    def __init__(self):
+        gtk.HBox.__init__(self)
+        self._hrspin = HLabeledSpinner("Hours:", spinner_range=23.0)
+        self._minspin = HLabeledSpinner("Minutes:")
+        self._secspin = HLabeledSpinner("Seconds:")
+        for spinner in (self._hrspin, self._minspin, self._secspin):
+            self.pack_start(spinner, False, False, 2)
+        now = datetime.datetime.now()
+        self._hrspin.set_value(now.hour)
+        self._minspin.set_value(now.minute)
+        self._secspin.set_value(now.second)
+
+    def get_value(self):
+        """ Return a tuple with the selected (hour, minute, second) values """
+        _hr = int(self._hrspin.get_value())
+        _min = int(self._minspin.get_value())
+        _sec = int(self._secspin.get_value())
+        return (_hr, _min, _sec)
+
+    def set_value(self, value_tuple):
+        """ Set the value to the time picker using value_tuple (hr, min, sec)"""
+        _hr, _min, _sec = value_tuple
+        self._hrspin.set_value(_hr)
+        self._minspin.set_value(_min)
+        self._secspin.set_value(_sec)
+        return
+        
+
+class TimePicker(gtk.VBox):
+    """ Returns a gtk.HBox holding 3 spinboxes to select the time """
+    def __init__(self, orientation = horizontal):
+        gtk.VBox.__init__(self)
+        box = orientation()
+        # create the components.
+        box1 = box2 = box3 = gtk.VBox()
+        self._hrspin = LabeledSpinner(_("H:"), spinner_range = 23.0)
+        self._minspin = LabeledSpinner(_("M:"))
+        self._secspin = LabeledSpinner(_("S:"))
+        for spinner in (self._hrspin, self._minspin, self._secspin):
+           box.pack_start(spinner, False, False, 2)
+        # pre-set the values
+        now = datetime.datetime.now()
+        self._hrspin.set_value(now.hour)
+        self._minspin.set_value(now.minute)
+        self._secspin.set_value(now.second)
+        self.pack_start(box, True, True, 0)
     
     def get_time(self):
         """ Returns a tuple with the selected values for time in (hr, min, sec)"""
                 int(self._minspin.get_value()),
                 int(self._secspin.get_value()))
 
+class DropdownBox(gtk.HBox):
+    """ Labeled dropdown box."""
+    def __init__(self, label, choices):
+        gtk.HBox.__init__(self)
+        if label:
+            self.label = gtk.Label()
+            self.label.set_use_markup(True)
+            self.label.set_markup(label)
+            self.label.set_property('xalign', 0.0)
+            self.pack_start(self.label, False, False, 2)
+        self.selector = gtk.combo_box_new_text()
+        for i in choices:
+            self.selector.append_text(i)
+        self.selector.set_active(0)
+        self.pack_start(self.selector, False, True, 2)
+        self.selector.set_title("foo")
+
+    def get_value(self):
+        return self.selector.get_active_text()
+
+    def get_model(self):
+        return self.selector.get_model()
+
+    def set_value(self, value):
+        model = self.selector.get_model()
+        i = 0
+        for item in model:
+            if item[0] == value:
+                self.selector.set_active(i)
+                return
+            i += 1
+        return
+
 class SettingsHolder(gtk.VBox):
     def __init__(self, label, icon, parent=None):
         gtk.VBox.__init__(self)

File modules/usermanage.py

         self._parent = parent
         self.title = user
         widgets.VasmModule.__init__(self, parent, self.title, "")
-        self.initial_group_membership = self.user.get_groups()
+        self.initial_group_membership = self.user.groups
         self.newgrouplist = []
         
         # create the tabstrip
         box.pack_start(gtk.Label(), False, False, 2)
         unamebox = widgets.TextEntry(_("User Login"), label_width_chars=15,
                                      entry_fill_box = False)
-        unamebox.entry.set_text(self.user.get_login_name())
+        unamebox.entry.set_text(self.user.login)
         unamebox.entry.set_property('sensitive', False)
         box.pack_start(unamebox, False, True, 2)
         
         fullnamebox = widgets.TextEntry(_("Full Name"),entry_fill_box=False,
                                         label_width_chars=15)
-        fullnamebox.entry.set_text(self.user.get_fullname())
+        fullnamebox.entry.set_text(self.user.fullname)
         fullnamebox.entry.set_property('sensitive', False)
         box.pack_start(fullnamebox, False, True, 2)
         homebox = widgets.TextEntry(_("Home Directory"), entry_fill_box=False,
                                     label_width_chars=15)
-        homebox.entry.set_text(self.user.get_home_dir())
+        homebox.entry.set_text(self.user.homedir)
         homebox.entry.set_property('sensitive', False)
         box.pack_start(homebox, False, True, 2)
         unamebox = widgets.TextEntry(_("User ID"), entry_fill_box = False,
                                      label_width_chars=15)
-        unamebox.entry.set_text(str(self.user.get_uid()))
+        unamebox.entry.set_text(str(self.user.uid))
         unamebox.entry.set_property('sensitive', False)
         box.pack_start(unamebox, False, True, 2)
         shellbox = widgets.TextEntry(_("Shell"), entry_fill_box=False,
                                      label_width_chars=15)
-        shellbox.entry.set_text(str(self.user.get_shell()))
+        shellbox.entry.set_text(str(self.user.shell))
         shellbox.entry.set_property('sensitive', False)
         box.pack_start(shellbox, False, True, 2)
 
         pass2 = self.passbox1.entry.get_text()
         user = self.user.login
         # passwords  have already been tested as they are entered.
-        ret = PASSWORDS.change_password(user,"foo",pass2)
         # clear the text boxes
         self.passbox1.entry.set_text("")
         self.passbox2.entry.set_text("")
-        if ret > 0:
-            return dialogs.error(
-                _("Unable to change password for this user.  The usermod command returned > 0")
-            , parent=self._parent)
-        return dialogs.info(_("Successfully changed the password for") + " " + self.user.login)
+	try:
+	    PASSWORDS.change_password(user, "foo", pass2)
+	except PASSWORDS.UserModError as e:
+	    return dialogs.error(
+		_("Unable to change the user password.") + "  \n\n" + e
+		    )
+        return dialogs.info(
+	    _("Password has been successfully changed for this user.")
+		)
         
     def apply_groups_event(self, widget):
         # Defines what happens when the apply button is pressed.
         # page index
         # 0 = User Info
         # 1 = User group membership
-        if page is 1: # User
-            status, stdout =self.user.set_groups(self.newgrouplist)
-            if status > 0:
-                dialogs.error(
-                    _("There was an error trying to setup group memberships.") + \
-                    "  " + _("See the returned error below.") + "\n\n" + stdout.strip())
-            else:
-                dialogs.info(_("Group membership changes applied successfully."))
-                    
-            self.initial_group_membership = self.newgrouplist
-        widget.set_property('sensitive', False)
+	widget.set_property('sensitive', False)
+	if page is 1:
+	    self.user.groups = self.newgrouplist
+	    try:
+		self.user.save_groups_changes()
+	    except AssertionError as e:
+		return dialogs.error(
+		    _("Unable to save changes.") + "  \n\n" + e
+			)
+	    except AccountError as e:
+		msg = (_("You may be trying to save changes to a new account.")," ",
+		       _("Make sure the account is created before trying to modify it."))
+		return dialogs.error(" ".join(msg))
+	return dialogs.info(
+		_("Group membership changes were applied successfully.")
+		)
 
 class UserAdd(gtk.Dialog):
     """Dialog to add a new user account"""
                 self.ok_button = bt
 
         self.usermodel = usermodel
-        self.user_groups = []
-        self.uname = ""
-        self.passw = ""
-        self.comment = ""
+	self.account = USERADMIN.User(new=True)
         self.set_size_request(540,460)
         topbox = gtk.HBox()
         banner = widgets.DLabel()
         picimg = gtk.Image()
         # set default icon
         iconpath = "/usr/share/apps/kdm/pics/users/default2.png"
+	if not os.path.exists(iconpath):
+	    iconpath = os.path.join(
+		    os.getcwd(), "modules/support/data/icons/category_personal.png")
         self.facepath = iconpath
         picimg.set_from_pixbuf(gtk.gdk.pixbuf_new_from_file_at_size(iconpath, 96, 96))
         picbt.set_image(picimg)
         sw.add(self.treeview)
         
         # Add the default group setup
+	self.account.groups = []
         for grp in USERADMIN.__distro_groups__:
             default=USERADMIN.__distro_groups__[grp][1]
             desc = USERADMIN.__distro_groups__[grp][0]
             store.append([default, True, grp, desc])
             # Add the default list
             if default:
-                self.user_groups.append(grp)
+		self.account.groups.append(grp)
+
         
         self.body.pack_start(widgets.DLabel(
             _("User group membership")), False, False, 2)
                 dia.destroy()
                 return
             # update the image using the filename selected
-            self.facepath = fpath
+	    self.account.faceicon = fpath
             img = widget.get_image()
             img.set_from_pixbuf(
                 gtk.gdk.pixbuf_new_from_file_at_size(self.facepath, 96, 96)
         dia.destroy()
     
     def update_entered_login(self, widget):
-        self.uname = widget.get_text()
+	self.account.login = widget.get_text()
         return self.ok_button.set_property('sensitive',
                                            self.check_passwords())
     
     def update_entered_comment(self, widget):
-        self.comment = widget.get_text()
+	self.account.fullname = widget.get_text()
     
     def check_passwords(self):
         """ Check if the entered passwords are suitable to continue.
     def update_entered_password(self, widget):
         # check the passwords to see if they match before enabling the OK button
         if self.check_passwords():
-            self.passw = widget.get_text()
+		#    self.passw = widget.get_text()
+	    self.account.password = widget.get_text()
             # check if the username is already entered
-            if not self.uname:
-                return self.ok_button.set_property('sensitive', False)
+	    if not self.account.login:
+		return self.ok_button.set_property('sensitive', False)
             return self.ok_button.set_property("sensitive", True)
-        self.passw = ""
+        self.account.password = None
         return self.ok_button.set_property("sensitive", False)
 
     def toggle_user_group(self, cell, path, model):
         """ Update the self.user_groups propety so that we can access it later
          """
-        self.user_groups = []
+	self.account.groups = []
         item = model.get_iter(path)
         model.set_value(item,0,not cell.get_active())
         for grp in model:
             enabled = grp[0]
             grpnam = grp[2]
             if enabled:
-                self.user_groups.append(grpnam)
+		self.account.groups.append(grpnam)
 
 class FacePreview(gtk.Image):
     """Preview widget for the face picker """
         # create the users model we will be working with
         self.usermodel = USERADMIN.UserModel()
         # Add the observable to automatically refresh the user list upon any changes
-        self.usermodel.add_observable(self.refresh_user_list)
+        self.usermodel.add_observer(self.refresh_user_list)
         
         # content box contains the user list (treeview) on the left
         # and the action buttons on the right
         "  " + "<b>%s</b>"%(_("This cannot  be undone."))
         response = dialogs.question(ask)
         if response == gtk.RESPONSE_YES:
-            code, output = self.usermodel.delete_user(self.selected)
-            if code > 0 :
-                dialogs.error(_("Error deleting the account.") + "\n " + output.strip())
-            else:
-                dialogs.info(_("User account has been deleted."))
+	    try:
+		self.usermodel.deleteUser(self.selected)
+	    except AssertionError as e:
+		return dialogs.error(
+			_("Unable to delete user.") + "\n\n" + e
+			)
+	return dialogs.info(
+		_("User account has been deleted."))
 
     def launch_add_user(self, widget=None):
         box = UserAdd(parent=self._parent, usermodel=self.usermodel)
         box.show_all()
         ret = box.run()
-        if ret == gtk.RESPONSE_OK:
-            # create the account
-            res, output = self.usermodel.create_new_account(
-                box.uname,
-                box.comment,
-                box.passw,
-                box.user_groups,
-                box.facepath
-            )
-            if res > 0:
-                dialogs.error(_("There was an error creating the user account.") +\
-                             "\n" + output.strip())
-            else:
-                msg = _("User account was successfully created for") + " " + box.uname + \
-                ".  " + _("The account is ready for immediate use.")
-                dialogs.info(msg)
-            
-        box.destroy()
+	box.hide()
+	if ret == gtk.RESPONSE_OK:
+	    acct = box.account
+	    try:
+		self.usermodel.addUser(acct)
+	    except AssertionError as e:
+		box.destroy()
+		return dialogs.error(
+			_("Unable to add new user account.") + " \n\n" + e)
+	    except USERADMIN.EmptyAttributeError as e:
+		box.destroy()
+		return dialogs.error(
+		    _("Account information is incomplete.") + " \n\n" + e.message
+			)
+	msg = _("User account was successfully created and is ready for immediate use")
+	box.destroy()
+	return dialogs.info(msg)
 
-    def refresh_user_list(self, data):
+    def refresh_user_list(self, datamodel=None):
         model = self.tree.get_model()
         # clear the model
         model.clear()
-        for line in data:
-            uname, uid, uhome = line
-            model.append([self.get_user_face(uname), uname, uid, uhome])
-        return
+	# get the user list
+	users = datamodel.listUsers()
+	for account in users:
+		model.append([self.get_user_face(account.login),
+			      account.login,
+			account.uid,
+			account.homedir])
+	return
 
     def update_tree_selection(self, treeview=None):
         """ Update the global selected value """
         self.pane = pane
         #pane.pack2(escroll)
 
+        # Add a place holder to embed the module's toolbar at the bottom of the scrollview
+        self._active_toolbar = gtk.HBox()
+        rightbody.pack_start(self._active_toolbar, False, True, 2)
+        # display the default exit button when we start up
+        self._show_home_toolbar()
+
         self.add_leftpane_category_shortcuts(leftbody)
         self.set_default_size(640, 480)
         self.set_position(gtk.WIN_POS_CENTER)
         self.body.pack_start(pane, True, True, 0)
         self.add(self.body)
         self._module_stack = [self._home_,]
-    
-    def do_exit(self, window, event):
+
+    def do_exit(self, widget=None, window=None, event=None):
         # check for child processes.
         allpids = psutil.get_process_list()
         mainpid = os.getpid()
             viewport.remove(viewport.get_children()[0])
         part2 = pathbar.PathPart(self.fancytoolbar, label=body.title, callback=self.jump_to_module)
         self.fancytoolbar.append_no_callback(part2)
+        # embed the modules toolbar to the bottom of the right pane
+        self._display_module_actionbuttons(body)
         
         # disable events for left pane
         self._disable_left_pane()
         return viewport.add(body)
+
+    def _display_module_actionbuttons(self, moduleobject):
+        """ Embeds the module's action area into the main screen so that the content
+        can scroll freely, while the action buttons are always visible """
+        bar = moduleobject.action_area
+        holder = self._active_toolbar
+        if holder.get_children():
+            holder.remove(holder.get_children()[0])
+        holder.pack_start(bar, True, True, 4)
+        holder.show_all()
+        return
+
+    def _show_home_toolbar(self):
+        """ Display the default toolbar (just an exit button )"""
+        holder = self._active_toolbar
+        if holder.get_children():
+            holder.remove(holder.get_children()[0])
+        bar = gtk.HBox()
+        bt = gtk.Button(stock=gtk.STOCK_QUIT)
+        bar.pack_end(bt, False, False, 4)
+        bt.connect('clicked', gtk.main_quit)
+        holder.pack_start(bar, True, True, 4)
+        holder.show_all()
     
     def jump_to_module(self, widget=None, data=None):
         parts = self.fancytoolbar.get_parts()
         id = parts.index(data) + 1
         for mod in self._module_stack[id::][::-1]:
             mod.close_module()
-        return
+        lastmod = self._module_stack[-1]
+        return self._display_module_actionbuttons(lastmod)
     
     def close_module(self, body):
         """ close a module"""
         # see if we are home.
         if len(self._module_stack) == 1:
             self._enable_left_pane()
+            # Display the home toolbar
+        self._show_home_toolbar()
         return viewport.add(prev)
     
     def return_home(self, widget=None, data=None):
         self._enable_left_pane()
         # clear out the module stack
         self._module_stack = [self._home_,]
+        # show the home toolbar
+        self._show_home_toolbar()
         return viewport.add(self._home_)
     
     def _disable_left_pane(self):
     gobject.threads_init()
     w = VASM()
     w.show_all()
-    gtk.main()
+    gtk.main()