#
# osbootwidget.py: gui bootloader list of operating systems to boot
#
# Jeremy Katz <katzj@redhat.com>
#
# Copyright 2001-2002 Red Hat, Inc.
#
# This software may be freely redistributed under the terms of the GNU
# library public license.
#
# You should have received a copy of the GNU Library Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#

import gtk
import gobject
import iutil
import partedUtils
import gui
from rhpl.translate import _, N_


class OSBootWidget:
    """Widget to display OSes to boot and allow adding new ones."""
    
    def __init__(self, bl, fsset, diskset, parent, intf, blname):
        self.bl = bl
        self.fsset = fsset
        self.diskset = diskset
        self.parent = parent
        self.intf = intf
        if blname is not None:
            self.blname = blname
        else:
            self.blname = "GRUB"

        self.setIllegalChars()
        
        self.vbox = gtk.VBox(gtk.FALSE, 5)
        label = gui.WrappingLabel(_("You can configure the boot loader to boot other operating systems. "
				    "It will allow you to select an operating system to boot from the list. "
				    "To add additional operating systems, which are not automatically "
				    "detected, click 'Add.' To change the operating system booted by default, "
				    "select 'Default' by the desired operating system."))
	label.set_alignment(0.0, 0.0)
	label.set_size_request(350, -1)
        self.vbox.pack_start(label, gtk.FALSE)

        spacer = gtk.Label("")
        spacer.set_size_request(10, 1)
        self.vbox.pack_start(spacer, gtk.FALSE)

        box = gtk.HBox (gtk.FALSE, 5)
        sw = gtk.ScrolledWindow()
        sw.set_shadow_type(gtk.SHADOW_ETCHED_IN)
        sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
        sw.set_size_request(300, 100)
        box.pack_start(sw, gtk.TRUE)


        self.osStore = gtk.ListStore(gobject.TYPE_BOOLEAN, gobject.TYPE_STRING,
                                     gobject.TYPE_STRING, gobject.TYPE_BOOLEAN)
        self.osTreeView = gtk.TreeView(self.osStore)
        theColumns = [ _("Default"), _("Label"), _("Device") ]

        self.checkboxrenderer = gtk.CellRendererToggle()
        column = gtk.TreeViewColumn(theColumns[0], self.checkboxrenderer,
                                    active = 0)
        column.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
        self.checkboxrenderer.connect("toggled", self.toggledDefault)
        self.osTreeView.append_column(column)

        for columnTitle in theColumns[1:]:
            renderer = gtk.CellRendererText()
            column = gtk.TreeViewColumn(columnTitle, renderer,
                                        text = theColumns.index(columnTitle))
            column.set_clickable(gtk.FALSE)
            self.osTreeView.append_column(column)

        self.osTreeView.set_headers_visible(gtk.TRUE)
        self.osTreeView.columns_autosize()
        self.osTreeView.set_size_request(100, 100)
        sw.add(self.osTreeView)
        self.osTreeView.connect('row-activated', self.osTreeActivateCb)

        self.imagelist = self.bl.images.getImages()
        self.defaultDev = self.bl.images.getDefault()
        self.fillOSList()

        buttonbar = gtk.VButtonBox()
        buttonbar.set_layout(gtk.BUTTONBOX_START)
        buttonbar.set_border_width(5)
        add = gtk.Button(_("_Add"))
        buttonbar.pack_start(add, gtk.FALSE)
        add.connect("clicked", self.addEntry)

        edit = gtk.Button(_("_Edit"))
        buttonbar.pack_start(edit, gtk.FALSE)
        edit.connect("clicked", self.editEntry)

        delete = gtk.Button(_("_Delete"))
        buttonbar.pack_start(delete, gtk.FALSE)
        delete.connect("clicked", self.deleteEntry)
        box.pack_start(buttonbar, gtk.FALSE)

        self.vbox.pack_start(box, gtk.FALSE)

        alignment = gtk.Alignment()
        alignment.set(0.1, 0, 0, 0)
        alignment.add(self.vbox)
        self.widget = alignment

    def setIllegalChars(self):
        # illegal characters for boot loader labels
        if self.blname == "GRUB":
            self.illegalChars = [ "$", "=" ]
        else:
            self.illegalChars = [ "$", "=", " " ]

    def changeBootLoader(self, blname):
        if blname is not None:
            self.blname = blname
        else:
            self.blname = "GRUB"
        self.setIllegalChars()
        self.fillOSList()

    # adds/edits a new "other" os to the boot loader config
    def editOther(self, oldDevice, oldLabel, isDefault, isRoot = 0):
        dialog = gtk.Dialog(_("Image"), self.parent)
        dialog.add_button('gtk-cancel', 2)
        dialog.add_button('gtk-ok', 1)
        dialog.set_position(gtk.WIN_POS_CENTER)
        gui.addFrame(dialog)

        dialog.vbox.pack_start(gui.WrappingLabel(
            _("Enter a label to be displayed in the boot loader menu. The "
	      "device (or hard drive and partition number) is the device "
	      "from which it boots.")))

        spacer = gtk.Label("")
        spacer.set_size_request(10, 1)
        dialog.vbox.pack_start(spacer, gtk.FALSE)

        table = gtk.Table(2, 5)
        table.set_row_spacings(5)
        table.set_col_spacings(5)

        label = gui.MnemonicLabel(_("_Label"))
        table.attach(label, 0, 1, 1, 2, gtk.FILL, 0, 10)
        labelEntry = gtk.Entry(32)
        label.set_mnemonic_widget(labelEntry)
        table.attach(labelEntry, 1, 2, 1, 2, gtk.FILL, 0, 10)
        if oldLabel:
            labelEntry.set_text(oldLabel)

        label = gui.MnemonicLabel(_("_Device"))
        table.attach(label, 0, 1, 2, 3, gtk.FILL, 0, 10)
        if not isRoot:
            # XXX should potentially abstract this out into a function
            pedparts = []
            parts = []
            disks = self.diskset.disks
            for drive in disks.keys():
                pedparts.extend(partedUtils.get_all_partitions(disks[drive]))
            for part in pedparts:
                parts.append(partedUtils.get_partition_name(part))
            del pedparts
            parts.sort()
            
            deviceOption = gtk.OptionMenu()
            deviceMenu = gtk.Menu()
            defindex = None
            i = 0
            for part in  parts:
                item = gtk.MenuItem("/dev/" + part)
                item.set_data("part", part)
                # XXX gtk bug -- have to show so that the menu is sized right
                item.show()
                deviceMenu.add(item)
                if oldDevice and oldDevice == part:
                    defindex = i
                i = i + 1
            deviceOption.set_menu(deviceMenu)
            if defindex:
                deviceOption.set_history(defindex)
            
            table.attach(deviceOption, 1, 2, 2, 3, gtk.FILL, 0, 10)
            label.set_mnemonic_widget(deviceOption)
        else:
            table.attach(gtk.Label(oldDevice), 1, 2, 2, 3, gtk.FILL, 0, 10)

        default = gtk.CheckButton(_("Default Boot _Target"))
        table.attach(default, 0, 2, 3, 4, gtk.FILL, 0, 10)
        if isDefault != 0:
            default.set_active(gtk.TRUE)

        if self.numentries == 1 and oldDevice != None:
            default.set_sensitive(gtk.FALSE)
        else:
            default.set_sensitive(gtk.TRUE)
        
        dialog.vbox.pack_start(table)
        dialog.show_all()

        while 1:
            rc = dialog.run()

            # cancel
            if rc == 2:
                break

            label = labelEntry.get_text()

            if not isRoot:
                dev = deviceMenu.get_active().get_data("part")
            else:
                dev = oldDevice

            if not label:
                self.intf.messageWindow(_("Error"),
                                        _("You must specify a label for the "
                                          "entry"),
                                        type="warning")
                continue

            foundBad = 0
            for char in self.illegalChars:
                if char in label:
                    self.intf.messageWindow(_("Error"),
                                            _("Boot label contains illegal "
                                              "characters"),
                                            type="warning")
                    foundBad = 1
                    break
            if foundBad:
                continue

            # verify that the label hasn't been used
            foundBad = 0
            for key in self.imagelist.keys():
                if dev == key:
                    continue
                if self.blname == "GRUB":
                    thisLabel = self.imagelist[key][1]
                else:
                    thisLabel = self.imagelist[key][0]

                # if the label is the same as it used to be, they must
                # have changed the device which is fine
                if thisLabel == oldLabel:
                    continue

                if thisLabel == label:
                    self.intf.messageWindow(_("Duplicate Label"),
                                            _("This label is already in "
                                              "use for another boot entry."),
                                            type="warning")
                    foundBad = 1
                    break
            if foundBad:
                continue

            # XXX need to do some sort of validation of the device?

            # they could be duplicating a device, which we don't handle
            if dev in self.imagelist.keys() and (not oldDevice or
                                                 dev != oldDevice):
                self.intf.messageWindow(_("Duplicate Device"),
                                        _("This device is already being "
                                          "used for another boot entry."),
                                        type="warning")
                continue

            # if we're editing a previous, get what the old info was for
            # labels.  otherwise, make it something safe for grub and the
            # device name for lilo for lack of any better ideas
            if oldDevice:
                (oldshort, oldlong, oldisroot) = self.imagelist[oldDevice]
            else:
                (oldshort, oldlong, oldisroot) = (dev, label, None)
                
            # if we're editing and the device has changed, delete the old
            if oldDevice and dev != oldDevice:
                del self.imagelist[oldDevice]
                
            # go ahead and add it
            if self.blname == "GRUB":
                self.imagelist[dev] = (oldshort, label, isRoot)
            else:
                self.imagelist[dev] = (label, oldlong, isRoot)

            if default.get_active():
                self.defaultDev = dev

            # refill the os list store
            self.fillOSList()
            break
        
        dialog.destroy()

    def getSelected(self):
        selection = self.osTreeView.get_selection()
        (model, iter) = selection.get_selected()
        if not iter:
            return None

        dev = model.get_value(iter, 2)
        theDev = dev[5:] # strip /dev/
        
        label = model.get_value(iter, 1)
        isRoot = model.get_value(iter, 3)
        isDefault = model.get_value(iter, 0)
        return (theDev, label, isDefault, isRoot)


    def addEntry(self, widget, *args):
        self.editOther(None, None, 0)

    def deleteEntry(self, widget, *args):
        rc = self.getSelected()
        if not rc:
            return
        (dev, label, isDefault, isRoot) = rc
        if not isRoot:
            del self.imagelist[dev]
            if isDefault:
                keys = self.imagelist.keys()
                keys.sort()
                self.defaultDev = keys[0]
                
            self.fillOSList()
        else:
            self.intf.messageWindow(_("Cannot Delete"),
                                    _("This boot target cannot be deleted "
#CJS TJD getting the red out
#				      "because it is for the Red Hat Linux "
				      "because it is for the Fermi Linux "
				      "system you are about to install."),
                                      type="warning")

    def editEntry(self, widget, *args):
        rc = self.getSelected()
        if not rc:
            return
        (dev, label, isDefault, isRoot) = rc
        self.editOther(dev, label, isDefault, isRoot)

    # the default os was changed in the treeview
    def toggledDefault(self, data, row):
        iter = self.osStore.get_iter((int(row),))
        dev = self.osStore.get_value(iter, 2)
        self.defaultDev = dev[5:]
        self.fillOSList()

    # fill in the os list tree view
    def fillOSList(self):
        self.osStore.clear()
        
        keys = self.imagelist.keys()
        keys.sort()

        for dev in keys:
            (label, longlabel, fstype) = self.imagelist[dev]
            if self.blname == "GRUB":
                theLabel = longlabel
            else:
                theLabel = label

            # if the label is empty, remove from the image list and don't
            # worry about it
            if not theLabel:
                del self.imagelist[dev]
                continue

	    isRoot = 0
	    fsentry = self.fsset.getEntryByDeviceName(dev)
	    if fsentry and fsentry.getMountPoint() == '/':
		isRoot = 1

            iter = self.osStore.append()
            self.osStore.set_value(iter, 1, theLabel)
            self.osStore.set_value(iter, 2, "/dev/%s" % (dev,))
            self.osStore.set_value(iter, 3, isRoot)
            if self.defaultDev == dev:
                self.osStore.set_value(iter, 0, gtk.TRUE)
            else:
                self.osStore.set_value(iter, 0, gtk.FALSE)

        self.numentries = len(keys)

    def osTreeActivateCb(self, view, path, col):
        self.editEntry(view)
        
        
    def getWidget(self):
        return self.widget

    # FIXME: I really shouldn't have such intimate knowledge of
    # the bootloader object
    def setBootloaderImages(self):
        "Apply the changes from our list into the self.bl object"
        # make a copy of our image list to shove into the bl struct
        self.bl.images.images = {}
        for key in self.imagelist.keys():
            self.bl.images.images[key] = self.imagelist[key]
        self.bl.images.setDefault(self.defaultDev)
        
