# # osbootwidget.py: gui bootloader list of operating systems to boot # # Copyright (C) 2001, 2002 Red Hat, Inc. All rights reserved. # # 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, see . # # Author(s): Jeremy Katz # import gtk import gobject import iutil import parted import gui import datacombo from constants import * from storage.devices import devicePathToName import gettext _ = lambda x: gettext.ldgettext("anaconda", x) class OSBootWidget: """Widget to display OSes to boot and allow adding new ones.""" def __init__(self, anaconda, parent, blname = None): self.bl = anaconda.bootloader self.storage = anaconda.storage self.parent = parent self.intf = anaconda.intf if blname is not None: self.blname = blname else: self.blname = "GRUB" self.setIllegalChars() self.vbox = gtk.VBox(False, 5) label = gtk.Label("" + _("Boot loader operating system list") + "") label.set_alignment(0.0, 0.0) label.set_property("use-markup", True) self.vbox.pack_start(label, False) box = gtk.HBox (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, 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.checkboxrenderer.set_radio(True) 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(False) self.osTreeView.append_column(column) self.osTreeView.set_headers_visible(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, False) add.connect("clicked", self.addEntry) edit = gtk.Button(_("_Edit")) buttonbar.pack_start(edit, False) edit.connect("clicked", self.editEntry) delete = gtk.Button(_("_Delete")) buttonbar.pack_start(delete, False) delete.connect("clicked", self.deleteEntry) box.pack_start(buttonbar, False) self.vbox.pack_start(box, False) self.widget = self.vbox 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 for the boot loader menu to display. The " "device (or hard drive and partition number) is the device " "from which it boots."))) 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: parts = [] for part in self.storage.partitions: if part.partedPartition.getFlag(parted.PARTITION_LVM) or \ part.partedPartition.getFlag(parted.PARTITION_RAID) or \ not part.partedPartition.active: continue parts.append(part) deviceCombo = datacombo.DataComboBox() defindex = 0 i = 0 for part in parts: deviceCombo.append(part.path, part.name) if oldDevice and oldDevice == part.name: defindex = i i = i + 1 deviceCombo.set_active(defindex) table.attach(deviceCombo, 1, 2, 2, 3, gtk.FILL, 0, 10) label.set_mnemonic_widget(deviceCombo) 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(True) if self.numentries == 1 and oldDevice != None: default.set_sensitive(False) else: default.set_sensitive(True) dialog.vbox.pack_start(table) dialog.show_all() while 1: rc = dialog.run() # cancel if rc in [2, gtk.RESPONSE_DELETE_EVENT]: break label = labelEntry.get_text() if not isRoot: dev = deviceCombo.get_active_value() 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 = devicePathToName(model.get_value(iter, 2)) label = model.get_value(iter, 1) isRoot = model.get_value(iter, 3) isDefault = model.get_value(iter, 0) return (dev, 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 " "because it is for the %s " "system you are about to install.") %(productName,), 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 = devicePathToName(dev) 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] device = self.storage.devicetree.getDeviceByName(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 rootDev = self.storage.rootDevice if rootDev and rootDev.name == dev: isRoot = 1 devPath = getattr(device, "path", "/dev/%s" % dev) iter = self.osStore.append() self.osStore.set_value(iter, 1, theLabel) self.osStore.set_value(iter, 2, devPath) self.osStore.set_value(iter, 3, isRoot) if self.defaultDev == dev: self.osStore.set_value(iter, 0, True) else: self.osStore.set_value(iter, 0, 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)