# # partition_gui.py: allows the user to choose how to partition their disks # # Matt Wilson # # Copyright 2001 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. # from iw_gui import * from gtk import * from GDK import * from gnome.ui import * from gnome.util import * from translate import _, N_ from constants import * from partitioning import * from fsset import * from autopart import doPartitioning, queryAutoPartitionOK from autopart import CLEARPART_TYPE_LINUX_DESCR_TEXT, CLEARPART_TYPE_ALL_DESCR_TEXT, CLEARPART_TYPE_NONE_DESCR_TEXT from autopart import AUTOPART_DISK_CHOICE_DESCR_TEXT import gui import parted import string import copy STRIPE_HEIGHT = 32.0 LOGICAL_INSET = 3.0 CANVAS_WIDTH_800 = 500 CANVAS_WIDTH_640 = 400 CANVAS_HEIGHT = 200 TREE_SPACING = 2 MODE_ADD = 1 MODE_EDIT = 2 # XXX this is made up and used by the size spinner; should just be set with # a callback MAX_PART_SIZE = 1024*1024*1024 class DiskStripeSlice: def eventHandler(self, widget, event): if event.type == GDK.BUTTON_PRESS: if event.button == 1: self.parent.selectSlice(self.partition, 1) elif event.type == GDK._2BUTTON_PRESS: self.editCb(self.ctree) return TRUE def shutDown(self): self.parent = None if self.group: self.group.destroy() self.group = None del self.partition def select(self): if self.partition.type != parted.PARTITION_EXTENDED: self.group.raise_to_top() self.box.set(outline_color="red") self.box.set(fill_color=self.selectColor()) def deselect(self): self.box.set(outline_color="black", fill_color=self.fillColor()) def getPartition(self): return self.partition def fillColor(self): if self.partition.type & parted.PARTITION_FREESPACE: return "grey88" return "white" def selectColor(self): if self.partition.type & parted.PARTITION_FREESPACE: return "cornsilk2" return "cornsilk1" def hideOrShowText(self): if self.box.get_bounds()[2] < self.text.get_bounds()[2]: self.text.hide() else: self.text.show() def sliceText(self): if self.partition.type & parted.PARTITION_EXTENDED: return "" if self.partition.type & parted.PARTITION_FREESPACE: rc = "Free\n" else: rc = "%s\n" % (get_partition_name(self.partition),) rc = rc + "%d MB" % (getPartSizeMB(self.partition),) return rc def getDeviceName(self): return get_partition_name(self.partition) def update(self): disk = self.parent.getDisk() totalSectors = float(disk.dev.heads * disk.dev.sectors * disk.dev.cylinders) # XXX hack but will work for now if screen_width() > 640: width = CANVAS_WIDTH_800 else: width = CANVAS_WIDTH_640 xoffset = self.partition.geom.start / totalSectors * width xlength = self.partition.geom.length / totalSectors * width if self.partition.type & parted.PARTITION_LOGICAL: yoffset = 0.0 + LOGICAL_INSET yheight = STRIPE_HEIGHT - (LOGICAL_INSET * 2) texty = 0.0 else: yoffset = 0.0 yheight = STRIPE_HEIGHT texty = LOGICAL_INSET self.group.set(x=xoffset, y=yoffset) self.box.set(x1=0.0, y1=0.0, x2=xlength, y2=yheight, fill_color=self.fillColor(), outline_color='black', width_units=1.0) self.text.set(x=2.0, y=texty + 2.0, text=self.sliceText(), fill_color='black', anchor=ANCHOR_NW, clip=TRUE, clip_width=xlength-1, clip_height=yheight-1) self.hideOrShowText() def __init__(self, parent, partition, ctree, editCb): self.text = None self.partition = partition self.parent = parent self.ctree = ctree self.editCb = editCb pgroup = parent.getGroup() self.group = pgroup.add("group") self.box = self.group.add ("rect") self.group.connect("event", self.eventHandler) self.text = self.group.add ("text", fontset="-*-helvetica-medium-r-*-*-8-*") self.update() class DiskStripe: def __init__(self, drive, disk, group, ctree, canvas, editCb): self.disk = disk self.group = group self.tree = ctree self.drive = drive self.canvas = canvas self.slices = [] self.hash = {} self.editCb = editCb self.selected = None # XXX hack but will work for now if screen_width() > 640: width = CANVAS_WIDTH_800 else: width = CANVAS_WIDTH_640 group.add ("rect", x1=0.0, y1=10.0, x2=width, y2=STRIPE_HEIGHT, fill_color='green', outline_color='grey71', width_units=1.0) group.lower_to_bottom() def shutDown(self): while self.slices: slice = self.slices.pop() slice.shutDown() if self.group: self.group.destroy() self.group = None del self.disk def holds(self, partition): return self.hash.has_key (partition) def getSlice(self, partition): return self.hash[partition] def getDisk(self): return self.disk def getDrive(self): return self.drive def getGroup (self): return self.group def getCanvas (self): return self.canvas def selectSlice(self, partition, updateTree=0): self.deselect() slice = self.hash[partition] slice.select() # update selection of the tree if updateTree: self.tree.unselect(self.tree.selection[0]) nodes = self.tree.base_nodes() for node in nodes: row = self.tree.find_by_row_data (node, partition) self.tree.select(row) break self.selected = slice def deselect(self): if self.selected: self.selected.deselect () self.selected = None def add (self, partition): stripe = DiskStripeSlice(self, partition, self.tree, self.editCb) self.slices.append(stripe) self.hash[partition] = stripe class DiskStripeGraph: def __init__(self, ctree, editCb): self.canvas = GnomeCanvas() self.diskStripes = [] self.textlabels = [] self.ctree = ctree self.editCb = editCb self.next_ypos = 0.0 def __del__(self): self.shutDown() def shutDown(self): # remove any circular references so we can clean up while self.diskStripes: stripe = self.diskStripes.pop() stripe.shutDown() while self.textlabels: lab = self.textlabels.pop() lab.destroy() self.next_ypos = 0.0 def getCanvas(self): return self.canvas def selectSlice(self, partition): for stripe in self.diskStripes: stripe.deselect() if stripe.holds(partition): stripe.selectSlice(partition) def getSlice(self, partition): for stripe in self.diskStripes: if stripe.holds(partition): return stripe.getSlice(partition) def getDisk(self, partition): for stripe in self.diskStripes: if stripe.holds(partition): return stripe.getDisk() def add (self, drive, disk): # yoff = len(self.diskStripes) * (STRIPE_HEIGHT + 5) yoff = self.next_ypos text = self.canvas.root().add ("text", x=0.0, y=yoff, fontset="-*-helvetica-bold-r-normal-*-*-120-*-*-p-*-iso8859-1") drivetext = "Drive %s (Geom: %s/%s/%s) (Model: %s)" % ('/dev/' + drive, disk.dev.cylinders, disk.dev.heads, disk.dev.sectors, disk.dev.model) text.set(text=drivetext, fill_color='black', anchor=ANCHOR_NW) (xxx1, yyy1, xxx2, yyy2) = text.get_bounds() textheight = yyy2 - yyy1 self.textlabels.append(text) group = self.canvas.root().add("group", x=0, y=yoff+textheight) stripe = DiskStripe (drive, disk, group, self.ctree, self.canvas, self.editCb) self.diskStripes.append(stripe) self.next_ypos = self.next_ypos + STRIPE_HEIGHT+textheight+10 return stripe # this should probably go into a class # some helper functions for build UI components def createAlignedLabel(text): label = GtkLabel(text) label.set_alignment(0.0, 0.0) return label def createMountPointCombo(request): mountCombo = GtkCombo() mntptlist = [] if request.type != REQUEST_NEW and request.fslabel: mntptlist.append(request.fslabel) for p in defaultMountPoints: if not p in mntptlist and (p[0] == "/"): mntptlist.append(p) mountCombo.set_popdown_strings (mntptlist) mountpoint = request.mountpoint if request.fstype and request.fstype.isMountable(): mountCombo.set_sensitive(1) if mountpoint: mountCombo.entry.set_text(mountpoint) else: mountCombo.entry.set_text("") else: mountCombo.entry.set_text(_("")) mountCombo.set_sensitive(0) mountCombo.set_data("saved_mntpt", None) return mountCombo def setMntPtComboStateFromType(fstype, mountCombo): prevmountable = mountCombo.get_data("prevmountable") mountpoint = mountCombo.get_data("saved_mntpt") if prevmountable and fstype.isMountable(): return if fstype.isMountable(): mountCombo.set_sensitive(1) if mountpoint != None: mountCombo.entry.set_text(mountpoint) else: mountCombo.entry.set_text("") else: if mountCombo.entry.get_text() != _(""): mountCombo.set_data("saved_mntpt", mountCombo.entry.get_text()) mountCombo.entry.set_text(_("")) mountCombo.set_sensitive(0) mountCombo.set_data("prevmountable", fstype.isMountable()) def fstypechangeCB(widget, mountCombo): fstype = widget.get_data("type") setMntPtComboStateFromType(fstype, mountCombo) def createAllowedDrivesClist(disks, reqdrives): driveclist = GtkCList() driveclist.set_selection_mode (SELECTION_MULTIPLE) driveclist.set_usize(-1, 75) driverow = 0 drives = disks.keys() drives.sort() for drive in drives: size = getDeviceSizeMB(disks[drive].dev) str = "%s: %s - %0.0f MB" % (drive, disks[drive].dev.model, size) row = driveclist.append((str,)) driveclist.set_row_data(row, drive) if reqdrives: if drive in reqdrives: driveclist.select_row(driverow, 0) else: driveclist.select_row(driverow, 0) driverow = driverow + 1 return driveclist def createAllowedRaidPartitionsClist(allraidparts, reqraidpart): partclist = GtkCList() partclist.set_selection_mode (SELECTION_MULTIPLE) partclist.set_usize(-1, 95) sw = GtkScrolledWindow() sw.add(partclist) sw.set_policy(POLICY_NEVER, POLICY_AUTOMATIC) partrow = 0 for (part, size, used) in allraidparts: partname = "%s: %8.0f MB" % (part, size) partclist.append((partname,)) if used or not reqraidpart: partclist.select_row(partrow, 0) partrow = partrow + 1 return (partclist, sw) def createRaidLevelMenu(levels, reqlevel, raidlevelchangeCB, sparesb): leveloption = GtkOptionMenu() leveloptionmenu = GtkMenu() defindex = None i = 0 for lev in levels: item = GtkMenuItem(lev) item.set_data ("level", lev) leveloptionmenu.add(item) if reqlevel and lev == reqlevel: defindex = i if raidlevelchangeCB and sparesb: item.connect("activate", raidlevelchangeCB, sparesb) i = i + 1 leveloption.set_menu (leveloptionmenu) if defindex: leveloption.set_history(defindex) if reqlevel and reqlevel == "RAID0": sparesb.set_sensitive(0) return (leveloption, leveloptionmenu) # pass in callback for when fs changes because of python scope issues def createFSTypeMenu(fstype, fstypechangeCB, mountCombo, availablefstypes = None, ignorefs = None): fstypeoption = GtkOptionMenu () fstypeoptionMenu = GtkMenu () types = fileSystemTypeGetTypes() if availablefstypes: names = availablefstypes else: names = types.keys() if fstype and fstype.isSupported() and fstype.isFormattable(): default = fstype else: default = fileSystemTypeGetDefault() names.sort() defindex = None i = 0 for name in names: if not fileSystemTypeGet(name).isSupported(): continue if ignorefs and name in ignorefs: continue if fileSystemTypeGet(name).isFormattable(): item = GtkMenuItem(name) item.set_data ("type", types[name]) fstypeoptionMenu.add(item) if default and default.getName() == name: defindex = i defismountable = types[name].isMountable() if fstypechangeCB and mountCombo: item.connect("activate", fstypechangeCB, mountCombo) i = i + 1 fstypeoption.set_menu (fstypeoptionMenu) if defindex: fstypeoption.set_history(defindex) if mountCombo: mountCombo.set_data("prevmountable", fstypeoptionMenu.get_active().get_data("type").isMountable()) return (fstypeoption, fstypeoptionMenu) def raidlevelchangeCB(widget, sparesb): raidlevel = widget.get_data("level") numparts = sparesb.get_data("numparts") maxspares = get_raid_max_spares(raidlevel, numparts) if maxspares > 0 and raidlevel != "RAID0": sparesb.set_sensitive(1) adj = sparesb.get_adjustment() value = adj.value if adj.value > maxspares: value = maxspares adj.set_all(value, 0, maxspares, adj.step_increment, adj.page_increment, adj.page_size) sparesb.set_adjustment(adj) sparesb.set_value(value) else: sparesb.set_value(0) sparesb.set_sensitive(0) class PartitionWindow(InstallWindow): def __init__(self, ics): InstallWindow.__init__(self, ics) ics.setTitle (_("Disk Setup")) ics.setNextEnabled (TRUE) ics.readHTML("partition") self.parent = ics.getICW().window def quit(self): pass def presentPartitioningComments(self, type, title, labelstr1, labelstr2, comments): win = GnomeDialog(title) if type == "ok": win.append_button(_("OK")) elif type == "yesno": win.append_button(_("Yes")) win.append_button(_("No")) hbox = GtkHBox(FALSE) file = pixmap_file('gnome-warning.png') if file: hbox.pack_start(GnomePixmap(file), FALSE) win.connect ("clicked", self.quit) textbox = GtkText() textbox.insert_defaults (comments) textbox.set_word_wrap(1) textbox.set_editable(0) sw = GtkScrolledWindow() sw.add(textbox) sw.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC) info1 = GtkLabel(labelstr1) info1.set_line_wrap(TRUE) info1.set_usize(300, -1) info2 = GtkLabel(labelstr2) info2.set_line_wrap(TRUE) info2.set_usize(300, -1) vbox = GtkVBox(FALSE) vbox.pack_start(info1, FALSE) vbox.pack_start(sw, TRUE) vbox.pack_start(info2, FALSE) hbox.pack_start(vbox, FALSE) win.vbox.pack_start(hbox) win.set_usize(400,300) win.set_position(WIN_POS_CENTER) win.show_all() rc = win.run() win.close() return not rc def getNext(self): (errors, warnings) = sanityCheckAllRequests(self.partitions, self.diskset) if errors: labelstr1 = _("The following critical errors exist " "with your requested partitioning " "scheme.") labelstr2 = _("These errors must be corrected prior " "to continuing with your install of " "%s." % (productName,)) commentstr = string.join(errors, "\n\n") rc = self.presentPartitioningComments("ok", _("Partitioning Errors"), labelstr1, labelstr2, commentstr) raise gui.StayOnScreen if warnings: labelstr1 = _("The following warnings exist with " "your requested partition scheme.") labelstr2 = _("Would you like to continue with " "your requested partitioning " "scheme?") commentstr = string.join(warnings, "\n\n") rc = self.presentPartitioningComments("yesno", _("Partitioning Warnings"), labelstr1, labelstr2, commentstr) if rc != 1: raise gui.StayOnScreen formatWarnings = getPreExistFormatWarnings(self.partitions, self.diskset) if formatWarnings: labelstr1 = _("The following pre-existing partitions have been " "selected to be formatted, destroying all data.") labelstr2 = _("Select 'Yes' to continue and format these " "partitions, or 'No' to go back and change these " "settings.") commentstr = "" for (dev, type, mntpt) in formatWarnings: commentstr = commentstr + \ "/dev/%s %s %s\n" % (dev,type,mntpt) rc = self.presentPartitioningComments("yesno", _("Format Warnings"), labelstr1, labelstr2, commentstr) if rc != 1: raise gui.StayOnScreen self.diskStripeGraph.shutDown() self.tree.freeze() self.clearTree() del self.parent return None def getPrev(self): self.diskStripeGraph.shutDown() self.tree.freeze() self.clearTree() del self.parent return None def populate (self, initial = 0): drives = self.diskset.disks.keys() drives.sort() for drive in drives: text = [""] * self.numCols text[self.titleSlot["Device"]] = '/dev/' + drive disk = self.diskset.disks[drive] sectorsPerCyl = disk.dev.heads * disk.dev.sectors # add a disk stripe to the graph stripe = self.diskStripeGraph.add (drive, disk) # add a parent node to the tree parent = self.tree.insert_node (None, None, text, is_leaf = FALSE, expanded = TRUE, spacing = TREE_SPACING) extendedParent = None part = disk.next_partition () while part: if part.type & parted.PARTITION_METADATA: part = disk.next_partition (part) continue stripe.add (part) text = [""] * self.numCols device = get_partition_name(part) request = self.partitions.getRequestByDeviceName(device) if request and request.mountpoint: text[self.titleSlot["Mount Point"]] = request.mountpoint if part.type & parted.PARTITION_FREESPACE: ptype = _("Free space") elif part.type == parted.PARTITION_EXTENDED: ptype = _("Extended") elif part.get_flag(parted.PARTITION_RAID) == 1: ptype = _("software RAID") elif part.fs_type: if request and request.fstype != None: ptype = request.fstype.getName() if ptype == "foreign": ptype = map_foreign_to_fsname(part.native_type) else: ptype = part.fs_type.name if request.format: text[self.titleSlot["Format"]] = _("Yes") else: text[self.titleSlot["Format"]] = _("No") else: if request and request.fstype != None: ptype = request.fstype.getName() if ptype == "foreign": ptype = map_foreign_to_fsname(part.native_type) else: ptype = _("None") if part.type & parted.PARTITION_FREESPACE: text[self.titleSlot["Device"]] = _("Free") else: text[self.titleSlot["Device"]] = '/dev/' + device text[self.titleSlot["Type"]] = ptype text[self.titleSlot["Start"]] = "%d" % \ (start_sector_to_cyl(disk.dev, part.geom.start),) text[self.titleSlot["End"]] = "%d" % \ (end_sector_to_cyl(disk.dev, part.geom.end),) size = getPartSizeMB(part) if size < 1.0: sizestr = "< 1" else: sizestr = "%8.0f" % (size) text[self.titleSlot["Size (MB)"]] = sizestr if part.type == parted.PARTITION_EXTENDED: if extendedParent: raise RuntimeError, ("can't handle more than " "one extended partition per disk") extendedParent = \ self.tree.insert_node (parent, None, text, is_leaf=FALSE, expanded=TRUE, spacing=TREE_SPACING) node = extendedParent elif part.type & parted.PARTITION_LOGICAL: if not extendedParent: raise RuntimeError, ("crossed logical partition " "before extended") node = self.tree.insert_node (extendedParent, None, text, spacing = TREE_SPACING) else: node = self.tree.insert_node (parent, None, text, spacing = TREE_SPACING) self.tree.node_set_row_data (node, part) part = disk.next_partition (part) # handle RAID next raidcounter = 0 raidrequests = self.partitions.getRaidRequests() if raidrequests: for request in raidrequests: text = [""] * self.numCols if request and request.mountpoint: text[self.titleSlot["Mount Point"]] = request.mountpoint if request.fstype: ptype = request.fstype.getName() if request.format: text[self.titleSlot["Format"]] = _("Yes") else: text[self.titleSlot["Format"]] = _("No") else: ptype = _("None") device = _("RAID Device %s" % (str(raidcounter))) text[self.titleSlot["Device"]] = device text[self.titleSlot["Type"]] = ptype text[self.titleSlot["Start"]] = "" text[self.titleSlot["End"]] = "" text[self.titleSlot["Size (MB)"]] = \ "%g" % (request.size) # add a parent node to the tree parent = self.tree.insert_node (None, None, text, is_leaf = FALSE, expanded = TRUE, spacing = TREE_SPACING) self.tree.node_set_row_data (parent, request.device) raidcounter = raidcounter + 1 canvas = self.diskStripeGraph.getCanvas() apply(canvas.set_scroll_region, canvas.root().get_bounds()) self.tree.columns_autosize() def treeSelectClistRowCb(self, list, row, column, event, tree): if event: if event.type == GDK._2BUTTON_PRESS: self.editCb(tree) def treeSelectCb(self, tree, node, column): partition = tree.node_get_row_data (node) if partition: self.diskStripeGraph.selectSlice(partition) def newCB(self, widget): # create new request of size 1M request = PartitionSpec(fileSystemTypeGetDefault(), REQUEST_NEW, 1) self.editPartitionRequest(request, isNew = 1) # edit a partition request # isNew implies that this request has never been successfully used before def editPartitionRequest(self, origrequest, isNew = 0): def formatOptionCB(widget, data): (menuwidget, menu, mntptcombo, ofstype) = data menuwidget.set_sensitive(widget.get_active()) # inject event for fstype menu if widget.get_active(): fstype = menu.get_active().get_data("type") setMntPtComboStateFromType(fstype, mntptcombo) else: setMntPtComboStateFromType(ofstype, mntptcombo) def noformatCB(widget, badblocks): badblocks.set_sensitive(widget.get_active()) def sizespinchangedCB(widget, fillmaxszsb): size = widget.get_value_as_int() maxsize = fillmaxszsb.get_value_as_int() if size > maxsize: fillmaxszsb.set_value(size) # ugly got to be better way adj = fillmaxszsb.get_adjustment() adj.set_all(adj.value, size, adj.upper, adj.step_increment, adj.page_increment, adj.page_size) fillmaxszsb.set_adjustment(adj) def cylspinchangedCB(widget, data): (dev, startcylspin, endcylspin, bycyl_sizelabel) = data startsec = start_cyl_to_sector(dev, startcylspin.get_value_as_int()) endsec = end_cyl_to_sector(dev, endcylspin.get_value_as_int()) cursize = (endsec - startsec)/2048 bycyl_sizelabel.set_text("%s" % (int(cursize))) def fillmaxszCB(widget, spin): spin.set_sensitive(widget.get_active()) # pass in CB defined above because of two scope limitation of python! def createSizeOptionsFrame(request, fillmaxszCB): frame = GtkFrame (_("Additional Size Options")) sizeoptiontable = GtkTable() sizeoptiontable.set_row_spacings(5) sizeoptiontable.set_border_width(4) fixedrb = GtkRadioButton(label=_("Fixed size")) fillmaxszrb = GtkRadioButton(group=fixedrb, label=_("Fill all space up to (MB):")) maxsizeAdj = GtkAdjustment (value = 1, lower = 1, upper = MAX_PART_SIZE, step_incr = 1) fillmaxszsb = GtkSpinButton(maxsizeAdj, digits = 0) fillmaxszhbox = GtkHBox() fillmaxszhbox.pack_start(fillmaxszrb) fillmaxszhbox.pack_start(fillmaxszsb) fillunlimrb = GtkRadioButton(group=fixedrb, label=_("Fill to maximum allowable size")) fillmaxszrb.connect("toggled", fillmaxszCB, fillmaxszsb) # default to fixed, turn off max size spinbutton fillmaxszsb.set_sensitive(0) if request.grow: if request.maxSize != None: fillmaxszrb.set_active(1) fillmaxszsb.set_sensitive(1) fillmaxszsb.set_value(request.maxSize) else: fillunlimrb.set_active(1) else: fixedrb.set_active(1) sizeoptiontable.attach(fixedrb, 0, 1, 0, 1) sizeoptiontable.attach(fillmaxszhbox, 0, 1, 1, 2) sizeoptiontable.attach(fillunlimrb, 0, 1, 2, 3) frame.add(sizeoptiontable) return (frame, fixedrb, fillmaxszrb, fillmaxszsb) # # start of editPartitionRequest # dialog = GnomeDialog(_("Add Partition")) dialog.set_parent(self.parent) dialog.append_button (_("OK")) dialog.append_button (_("Cancel")) dialog.set_position(WIN_POS_CENTER) dialog.close_hides(TRUE) maintable = GtkTable() maintable.set_row_spacings (5) maintable.set_col_spacings (5) row = 0 # see if we are creating a floating request or by cylinder if origrequest.type == REQUEST_NEW: newbycyl = origrequest.start != None # Mount Point entry maintable.attach(createAlignedLabel(_("Mount Point:")), 0, 1, row, row + 1) mountCombo = createMountPointCombo(origrequest) maintable.attach(mountCombo, 1, 2, row, row + 1) row = row + 1 # Partition Type if origrequest.type == REQUEST_NEW: maintable.attach(createAlignedLabel(_("Filesystem Type:")), 0, 1, row, row + 1) (newfstype, newfstypeMenu) = createFSTypeMenu(origrequest.fstype, fstypechangeCB, mountCombo) maintable.attach(newfstype, 1, 2, row, row + 1) else: maintable.attach(createAlignedLabel(_("Original Filesystem Type:")), 0, 1, row, row + 1) if origrequest.origfstype: typestr = origrequest.origfstype.getName() if origrequest.origfstype.getName() == "foreign": part = get_partition_by_name(self.diskset.disks, origrequest.device) typestr = map_foreign_to_fsname(part.native_type) else: typestr = _("Unknown") fstypelabel = GtkLabel(typestr) maintable.attach(fstypelabel, 1, 2, row, row + 1) newfstype = None newfstypeMenu = None row = row + 1 # allowable drives if origrequest.type == REQUEST_NEW: if not newbycyl: maintable.attach(createAlignedLabel(_("Allowable Drives:")), 0, 1, row, row + 1) driveclist = createAllowedDrivesClist(self.diskset.disks, origrequest.drive) sw = GtkScrolledWindow() sw.add(driveclist) sw.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC) maintable.attach(sw, 1, 2, row, row + 1) else: maintable.attach(createAlignedLabel(_("Drive:")), 0, 1, row, row + 1) maintable.attach(createAlignedLabel(origrequest.drive[0]), 1, 2, row, row + 1) row = row + 1 # original fs label if origrequest.type != REQUEST_NEW and origrequest.fslabel: maintable.attach(createAlignedLabel(_("Original Filesystem Label:")), 0, 1, row, row + 1) fslabel = GtkLabel(origrequest.fslabel) maintable.attach(fslabel, 1, 2, row, row + 1) row = row + 1 # size if origrequest.type == REQUEST_NEW: if not newbycyl: # Size specification maintable.attach(createAlignedLabel(_("Size (MB):")), 0, 1, row, row + 1) sizeAdj = GtkAdjustment (value = 1, lower = 1, upper = MAX_PART_SIZE, step_incr = 1) sizespin = GtkSpinButton(sizeAdj, digits = 0) if origrequest.size: sizespin.set_value(origrequest.size) maintable.attach(sizespin, 1, 2, row, row + 1) bycyl_sizelabel = None else: # XXX need to add partition by size and # wire in limits between start and end dev = self.diskset.disks[origrequest.drive[0]].dev maintable.attach(createAlignedLabel(_("Size (MB):")), 0, 1, row, row + 1) bycyl_sizelabel = createAlignedLabel("") maintable.attach(bycyl_sizelabel, 1, 2, row, row + 1) row = row + 1 maintable.attach(createAlignedLabel(_("Start Cylinder:")), 0, 1, row, row + 1) maxcyl = self.diskset.disks[origrequest.drive[0]].dev.cylinders cylAdj = GtkAdjustment (value = origrequest.start, lower = origrequest.start, upper = maxcyl, step_incr = 1) startcylspin = GtkSpinButton(cylAdj, digits = 0) maintable.attach(startcylspin, 1, 2, row, row + 1) row = row + 1 endcylAdj = GtkAdjustment (value = origrequest.end, lower = origrequest.start, upper = maxcyl, step_incr = 1) maintable.attach(createAlignedLabel(_("End Cylinder:")), 0, 1, row, row + 1) endcylspin = GtkSpinButton(endcylAdj, digits = 0) maintable.attach(endcylspin, 1, 2, row, row + 1) startcylspin.connect("changed", cylspinchangedCB, (dev, startcylspin, endcylspin, bycyl_sizelabel)) endcylspin.connect("changed", cylspinchangedCB, (dev, startcylspin, endcylspin, bycyl_sizelabel)) startsec = start_cyl_to_sector(dev, origrequest.start) endsec = end_cyl_to_sector(dev, origrequest.end) cursize = (endsec - startsec)/2048 bycyl_sizelabel.set_text("%s" % (int(cursize))) else: maintable.attach(createAlignedLabel(_("Size (MB):")), 0, 1, row, row + 1) sizelabel = GtkLabel("%d" % (origrequest.size)) maintable.attach(sizelabel, 1, 2, row, row + 1) sizespin = None row = row + 1 # format/migrate options for pre-existing partitions if origrequest.type == REQUEST_PREEXIST and origrequest.fstype: ofstype = origrequest.fstype maintable.attach(GtkHSeparator(), 0, 2, row, row + 1) row = row + 1 label = GtkLabel(_("How would you like to prepare the filesystem " "on this partition?")) label.set_line_wrap(1) label.set_alignment(0.0, 0.0) label.set_usize(400, -1) maintable.attach(label, 0, 2, row, row + 1) row = row + 1 noformatrb = GtkRadioButton (label=_("Leave unchanged (preserve data)")) noformatrb.set_active(1) maintable.attach(noformatrb, 0, 2, row, row + 1) row = row + 1 formatrb = GtkRadioButton (label=_("Format partition as:"), group = noformatrb) formatrb.set_active(0) if origrequest.format: formatrb.set_active(1) maintable.attach(formatrb, 0, 1, row, row + 1) (fstype, fstypeMenu) = createFSTypeMenu(ofstype,fstypechangeCB, mountCombo) fstype.set_sensitive(formatrb.get_active()) maintable.attach(fstype, 1, 2, row, row + 1) row = row + 1 if not formatrb.get_active() and not origrequest.migrate: mountCombo.set_data("prevmountable", ofstype.isMountable()) formatrb.connect("toggled", formatOptionCB, (fstype, fstypeMenu, mountCombo, ofstype)) if origrequest.origfstype.isMigratable(): migraterb = GtkRadioButton (label=_("Migrate partition to:"), group=noformatrb) migraterb.set_active(0) if origrequest.migrate: migraterb.set_active(1) migtypes = origrequest.origfstype.getMigratableFSTargets() maintable.attach(migraterb, 0, 1, row, row + 1) (migfstype, migfstypeMenu)=createFSTypeMenu(ofstype, None, None, availablefstypes = migtypes) migfstype.set_sensitive(migraterb.get_active()) maintable.attach(migfstype, 1, 2, row, row + 1) row = row + 1 migraterb.connect("toggled", formatOptionCB, (migfstype, migfstypeMenu, mountCombo, ofstype)) else: migraterb = None badblocks = GtkCheckButton(_("Check for bad blocks?")) badblocks.set_active(0) maintable.attach(badblocks, 0, 1, row, row + 1) formatrb.connect("toggled", noformatCB, badblocks) if not origrequest.format: badblocks.set_sensitive(0) if origrequest.badblocks: badblocks.set_active(1) row = row + 1 else: noformatrb = None formatrb = None migraterb = None # size options if origrequest.type == REQUEST_NEW: if not newbycyl: (sizeframe, fixedrb, fillmaxszrb, fillmaxszsb) = createSizeOptionsFrame(origrequest, fillmaxszCB) sizespin.connect("changed", sizespinchangedCB, fillmaxszsb) maintable.attach(sizeframe, 0, 2, row, row + 1) else: # XXX need new by cyl options (if any) pass row = row + 1 else: sizeoptiontable = None # create only as primary if origrequest.type == REQUEST_NEW: primonlycheckbutton = GtkCheckButton(_("Force to be a primary partition")) primonlycheckbutton.set_active(0) if origrequest.primary: primonlycheckbutton.set_active(1) maintable.attach(primonlycheckbutton, 0, 2, row, row+1) row = row + 1 badblocks = GtkCheckButton(_("Check for bad blocks")) badblocks.set_active(0) maintable.attach(badblocks, 0, 1, row, row + 1) row = row + 1 if origrequest.badblocks: badblocks.set_active(1) # put main table into dialog dialog.vbox.pack_start(maintable) dialog.show_all() while 1: rc = dialog.run() # user hit cancel, do nothing if rc == 1: dialog.close() return if origrequest.type == REQUEST_NEW: # read out UI into a partition specification filesystem = newfstypeMenu.get_active().get_data("type") request = copy.copy(origrequest) request.fstype = filesystem request.format = TRUE if request.fstype.isMountable(): request.mountpoint = mountCombo.entry.get_text() else: request.mountpoint = None if primonlycheckbutton.get_active(): primonly = TRUE else: primonly = None if badblocks and badblocks.get_active(): request.badblocks = TRUE else: request.badblocks = None if not newbycyl: if fixedrb.get_active(): grow = None else: grow = TRUE if fillmaxszrb.get_active(): maxsize = fillmaxszsb.get_value_as_int() else: maxsize = None if len(driveclist.selection) == len(self.diskset.disks.keys()): allowdrives = None else: allowdrives = [] for i in driveclist.selection: allowdrives.append(driveclist.get_row_data(i)) request.size = sizespin.get_value_as_int() request.drive = allowdrives request.grow = grow request.primary = primonly request.maxSize = maxsize else: request.start = startcylspin.get_value_as_int() request.end = endcylspin.get_value_as_int() if request.end <= request.start: self.intf.messageWindow(_("Error With Request"), "The end cylinder must be " "greater than the start " "cylinder.") continue err = sanityCheckPartitionRequest(self.partitions, request) if err: self.intf.messageWindow(_("Error With Request"), "%s" % (err)) continue else: # preexisting partition, just set mount point and format flag request = copy.copy(origrequest) if formatrb: request.format = formatrb.get_active() if request.format: request.fstype = fstypeMenu.get_active().get_data("type") if badblocks and badblocks.get_active(): request.badblocks = TRUE else: request.badblocks = None else: request.format = 0 request.badblocks = None if migraterb: request.migrate = migraterb.get_active() if request.migrate: request.fstype =migfstypeMenu.get_active().get_data("type") else: request.migrate = 0 # set back if we are not formatting or migrating if not request.format and not request.migrate: request.fstype = origrequest.origfstype if request.fstype.isMountable(): request.mountpoint = mountCombo.entry.get_text() else: request.mountpoint = None err = sanityCheckPartitionRequest(self.partitions, request) if err: self.intf.messageWindow(_("Error With Request"), "%s" % (err)) continue # if not origrequest.format and request.format: # if not queryFormatPreExisting(self.intf): # continue if not request.format and request.mountpoint and isFormatOnByDefault(request): if not queryNoFormatPreExisting(self.intf): continue if not isNew: self.partitions.removeRequest(origrequest) self.partitions.addRequest(request) if self.refresh(): # the add failed; remove what we just added and put # back what was there if we removed it self.partitions.removeRequest(request) if not isNew: self.partitions.addRequest(origrequest) if self.refresh(): # this worked before and doesn't now... raise RuntimeError, "Returning partitions to state prior to edit failed" else: break dialog.close() def deleteCb(self, widget): node = self.tree.selection[0] partition = self.tree.node_get_row_data (node) if doDeletePartitionByRequest(self.intf, self.partitions, partition): self.refresh() def clearTree(self): node = self.tree.node_nth(0) while node: self.tree.remove_node(node) node = self.tree.node_nth(0) self.tree.set_selection_mode (SELECTION_SINGLE) self.tree.set_selection_mode (SELECTION_BROWSE) def resetCb(self, *args): if not confirmResetPartitionState(self.intf): return self.diskStripeGraph.shutDown() self.newFsset = self.fsset.copy() self.tree.freeze() self.clearTree() self.diskset.refreshDevices() self.partitions.setFromDisk(self.diskset) self.populate() self.tree.thaw() def refresh(self): self.diskStripeGraph.shutDown() self.tree.freeze() self.clearTree() try: doPartitioning(self.diskset, self.partitions) rc = 0 except PartitioningError, msg: self.intf.messageWindow(_("Error Partitioning"), _("Could not allocate requested partitions: %s.") % (msg)) rc = -1 except PartitioningWarning, msg: # XXX somebody other than me should make this look better dialog = GnomeDialog(_("Warning")) dialog.set_parent(self.parent) dialog.append_button(_("Modify Partition")) dialog.append_button(_("Add anyway")) dialog.set_position(WIN_POS_CENTER) dialog.close_hides(TRUE) label = GtkLabel(_("Warning: %s.") %(msg)) label.set_line_wrap(TRUE) dialog.vbox.pack_start(label) dialog.show_all() rc = dialog.run() dialog.close() if rc != 1: rc = -1 else: rc = 0 req = self.partitions.getBootableRequest() if req: req.ignoreBootConstraints = 1 self.populate() self.tree.thaw() return rc def editCb(self, widget): node = self.tree.selection[0] part = self.tree.node_get_row_data (node) (type, request) = doEditPartitionByRequest(self.intf, self.partitions, part) if request: if type == "RAID": self.editRaidRequest(request) elif type == "NEW": self.editPartitionRequest(request, isNew = 1) else: self.editPartitionRequest(request) # isNew implies that this request has never been successfully used before def editRaidRequest(self, raidrequest, isNew = 0): # # start of editRaidRuquest # dialog = GnomeDialog(_("Make RAID Device")) dialog.set_parent(self.parent) dialog.append_button (_("OK")) dialog.append_button (_("Cancel")) dialog.set_position(WIN_POS_CENTER) dialog.close_hides(TRUE) maintable = GtkTable() maintable.set_row_spacings (5) maintable.set_col_spacings (5) row = 0 availraidparts = get_available_raid_partitions(self.diskset, self.partitions, raidrequest) # if no raid partitions exist, raise an error message and return if len(availraidparts) < 2: dlg = GnomeMessageBox(_("At least two software RAID partitions are needed."), MESSAGE_BOX_ERROR,STOCK_BUTTON_OK) dlg.set_position(WIN_POS_CENTER) dlg.show() dlg.run_and_close() return # Mount Point entry maintable.attach(createAlignedLabel(_("Mount Point:")), 0, 1, row, row + 1) mountCombo = createMountPointCombo(raidrequest) maintable.attach(mountCombo, 1, 2, row, row + 1) row = row + 1 # Filesystem Type maintable.attach(createAlignedLabel(_("Filesystem type:")), 0, 1, row, row + 1) (fstypeoption, fstypeoptionMenu) = createFSTypeMenu(raidrequest.fstype, fstypechangeCB, mountCombo, ignorefs = ["software RAID"]) maintable.attach(fstypeoption, 1, 2, row, row + 1) row = row + 1 # raid level maintable.attach(createAlignedLabel(_("RAID Level:")), 0, 1, row, row + 1) # Create here, pack below numparts = len(availraidparts) if raidrequest.raidspares: nspares = raidrequest.raidspares else: nspares = 0 if raidrequest.raidlevel: maxspares = get_raid_max_spares(raidrequest.raidlevel, numparts) else: maxspares = 0 spareAdj = GtkAdjustment (value = nspares, lower = 0, upper = maxspares, step_incr = 1) sparesb = GtkSpinButton(spareAdj, digits = 0) sparesb.set_data("numparts", numparts) if maxspares > 0: sparesb.set_sensitive(1) else: sparesb.set_value(0) sparesb.set_sensitive(0) (leveloption, leveloptionmenu) = createRaidLevelMenu(availRaidLevels, raidrequest.raidlevel, raidlevelchangeCB, sparesb) maintable.attach(leveloption, 1, 2, row, row + 1) row = row + 1 # raid members maintable.attach(createAlignedLabel(_("RAID Members:")), 0, 1, row, row + 1) # XXX need to pass in currently used partitions for this device (raidclist, sw) = createAllowedRaidPartitionsClist(availraidparts, raidrequest.raidmembers) maintable.attach(sw, 1, 2, row, row + 1) row = row + 1 # number of spares - created widget above maintable.attach(createAlignedLabel(_("Number of spares:")), 0, 1, row, row + 1) maintable.attach(sparesb, 1, 2, row, row + 1) row = row + 1 # format or not? if raidrequest.fstype and raidrequest.fstype.isFormattable(): formatButton = GtkCheckButton (_("Format partition?")) # XXX this probably needs more logic once we detect existing raid if raidrequest.format == None or raidrequest.format != 0: formatButton.set_active(1) else: formatButton.set_active(0) maintable.attach(formatButton, 0, 2, row, row + 1) row = row + 1 else: formatButton = None # put main table into dialog dialog.vbox.pack_start(maintable) dialog.show_all() while 1: rc = dialog.run() # user hit cancel, do nothing if rc == 1: dialog.close() return elif rc == -1: # something died in dialog raise ValueError, "Died inside of raid edit dialog!" # read out UI into a partition specification request = copy.copy(raidrequest) filesystem = fstypeoptionMenu.get_active().get_data("type") request.fstype = filesystem if request.fstype.isMountable(): request.mountpoint = mountCombo.entry.get_text() else: request.mountpoint = None raidmembers = [] for i in raidclist.selection: id = self.partitions.getRequestByDeviceName(availraidparts[i][0]).uniqueID raidmembers.append(id) request.raidmembers = raidmembers request.raidlevel = leveloptionmenu.get_active().get_data("level") if request.raidlevel != "RAID0": request.raidspares = sparesb.get_value_as_int() else: request.raidspares = 0 if formatButton: request.format = formatButton.get_active() else: request.format = 0 err = sanityCheckRaidRequest(self.partitions, request) if err: self.intf.messageWindow(_("Error With Request"), "%s" % (err)) continue if not isNew: self.partitions.removeRequest(raidrequest) self.partitions.addRequest(request) if self.refresh(): # how can this fail? well, if it does, do the remove new, # add old back in dance self.partitions.removeRequest(request) if not isNew: self.partitions.addRequest(raidrequest) if self.refresh(): raise RuntimeError, "Returning partitions to state prior to RAID edit failed" else: break dialog.close() def makeraidCB(self, widget): request = PartitionSpec(fileSystemTypeGetDefault(), REQUEST_RAID, 1) self.editRaidRequest(request, isNew = 1) def getScreen (self, fsset, diskset, partitions, intf): self.fsset = fsset self.diskset = diskset self.intf = intf self.diskset.openDevices() self.partitions = partitions checkForSwapNoMatch(self.intf, self.diskset, self.partitions) # XXX PartitionRequests() should already exist and # if upgrade or going back, have info filled in # self.newFsset = self.fsset.copy() # operational buttons buttonBox = GtkHButtonBox() buttonBox.set_layout (BUTTONBOX_SPREAD) self.accelgroup = GtkAccelGroup() ops = ((_("_New"), self.newCB), (_("_Edit"), self.editCb), (_("_Delete"), self.deleteCb), (_("_Reset"), self.resetCb), (_("Make _RAID"), self.makeraidCB)) for label, cb in ops: labelwid = GtkLabel(label) key = labelwid.parse_uline(label) button = GtkButton () button.add(labelwid) button.add_accelerator("clicked", self.accelgroup, key, GDK.MOD1_MASK, 0) buttonBox.add (button) button.connect ("clicked", cb) self.ics.getICW().window.add_accel_group (self.accelgroup) # set up the tree titles = [N_("Device"), N_("Start"), N_("End"), N_("Size (MB)"), N_("Type"), N_("Mount Point"), N_("Format")] # do two things: enumerate the location of each field and translate self.titleSlot = {} i = 0 for title in titles: self.titleSlot[title] = i titles[i] = _(title) i = i + 1 self.numCols = len(titles) self.tree = GtkCTree (self.numCols, 0, titles) self.tree.set_selection_mode (SELECTION_BROWSE) self.tree.column_titles_passive() for i in range(self.numCols): self.tree.set_column_resizeable(i, 0) self.tree.set_column_justification(1, JUSTIFY_RIGHT) self.tree.set_column_justification(2, JUSTIFY_RIGHT) self.tree.set_column_justification(3, JUSTIFY_RIGHT) self.tree.connect ("select_row", self.treeSelectClistRowCb, self.tree) self.tree.connect ("tree_select_row", self.treeSelectCb) # set up the canvas self.diskStripeGraph = DiskStripeGraph(self.tree, self.editCb) # do the initial population of the tree and the graph self.populate (initial = 1) box = GtkVBox(FALSE, 5) sw = GtkScrolledWindow() sw.add (self.diskStripeGraph.getCanvas()) sw.set_policy (POLICY_NEVER, POLICY_AUTOMATIC) box.pack_start (sw, TRUE) box.pack_start (buttonBox, FALSE) sw = GtkScrolledWindow() sw.add (self.tree) sw.set_policy (POLICY_NEVER, POLICY_AUTOMATIC) box.pack_start (sw, TRUE) return box class AutoPartitionWindow(InstallWindow): def __init__(self, ics): InstallWindow.__init__(self, ics) ics.setTitle (_("Automatic Partitioning")) ics.setNextEnabled (TRUE) ics.readHTML("autopart") self.parent = ics.getICW().window def getNext(self): if self.clearLinuxRB.get_active(): self.partitions.autoClearPartType = CLEARPART_TYPE_LINUX elif self.clearAllRB.get_active(): self.partitions.autoClearPartType = CLEARPART_TYPE_ALL else: self.partitions.autoClearPartType = CLEARPART_TYPE_NONE allowdrives = [] for i in self.driveclist.selection: allowdrives.append(self.driveclist.get_row_data(i)) if len(allowdrives) < 1: self.intf.messageWindow(_("Warning"), _("You need to select at least one drive to have " "%s installed onto.") % (productName,), type = "ok") raise gui.StayOnScreen self.partitions.autoClearPartDrives = allowdrives if not queryAutoPartitionOK(self.intf, self.diskset, self.partitions): raise gui.StayOnScreen if self.inspect.get_active(): self.dispatch.skipStep("partition", skip = 0) else: self.dispatch.skipStep("partition") return None def getScreen(self, diskset, partitions, intf, dispatch): self.diskset = diskset self.partitions = partitions self.intf = intf self.dispatch = dispatch type = partitions.autoClearPartType cleardrives = partitions.autoClearPartDrives box = GtkVBox (FALSE) box.set_border_width (5) label = GtkLabel(_(AUTOPART_DISK_CHOICE_DESCR_TEXT)) label.set_line_wrap(1) label.set_alignment(0.0, 0.0) label.set_usize(400, -1) box.pack_start(label, FALSE, FALSE) # what partition types to remove clearbox = GtkVBox(FALSE) label = GtkLabel(_("I want to have automatic partitioning:")) label.set_alignment(0.0, 0.0) clearbox.pack_start(label, FALSE, FALSE, 10) radioBox = GtkVBox (FALSE) self.clearLinuxRB = GtkRadioButton( None, _(CLEARPART_TYPE_LINUX_DESCR_TEXT)) radioBox.pack_start(self.clearLinuxRB, FALSE, FALSE) self.clearAllRB = GtkRadioButton( self.clearLinuxRB, _(CLEARPART_TYPE_ALL_DESCR_TEXT)) radioBox.pack_start(self.clearAllRB, FALSE, FALSE) self.clearNoneRB = GtkRadioButton( self.clearLinuxRB, _(CLEARPART_TYPE_NONE_DESCR_TEXT)) radioBox.pack_start(self.clearNoneRB, FALSE, FALSE) if type == CLEARPART_TYPE_LINUX: self.clearLinuxRB.set_active(1) elif type == CLEARPART_TYPE_ALL: self.clearAllRB.set_active(1) else: self.clearNoneRB.set_active(1) align = GtkAlignment() align.add(radioBox) align.set(0.5, 0.5, 0.0, 0.0) clearbox.pack_start(align, FALSE, FALSE) box.pack_start(clearbox, FALSE, FALSE, 10) # which drives to use? drivesbox = GtkVBox(FALSE) label = GtkLabel(_("Which drive(s) do you want to use for this " "installation?")) label.set_alignment(0.0, 0.0) drivesbox.pack_start(label, FALSE, FALSE, 10) self.driveclist = createAllowedDrivesClist(diskset.disks, cleardrives) # XXX bad use of usize self.driveclist.set_usize(300, 80) sw = GtkScrolledWindow() sw.add(self.driveclist) sw.set_policy(POLICY_NEVER, POLICY_AUTOMATIC) align = GtkAlignment() align.add(sw) align.set(0.5, 0.5, 0.0, 0.0) drivesbox.pack_start(align, FALSE, FALSE) box.pack_start(drivesbox, FALSE, FALSE) self.inspect = GtkCheckButton() label = GtkLabel(_("Review (allows you to see and " "change the automatic partitioning results)")) label.set_alignment(0.0, 1.0) label.set_line_wrap(TRUE) label.set_usize(400, -1) self.inspect.add(label) self.inspect.set_active(not dispatch.stepInSkipList("partition")) box.pack_start(self.inspect, FALSE, FALSE, 10) self.ics.setNextEnabled (TRUE) align = GtkAlignment() align.add(box) align.set(0.5, 0.5, 0.0, 0.0) return align