diff options
Diffstat (limited to 'geneticone/gui/windows.py')
-rw-r--r-- | geneticone/gui/windows.py | 626 |
1 files changed, 626 insertions, 0 deletions
diff --git a/geneticone/gui/windows.py b/geneticone/gui/windows.py new file mode 100644 index 0000000..02613db --- /dev/null +++ b/geneticone/gui/windows.py @@ -0,0 +1,626 @@ +# +# File: geneticone/gui/windows.py +# This file is part of the Genetic/One-Project, a graphical portage-frontend. +# +# Copyright (C) 2006 Necoro d.M. +# This is free software. You may redistribute copies of it under the terms of +# the GNU General Public License version 2. +# There is NO WARRANTY, to the extent permitted by law. +# +# Written by Necoro d.M. <necoro@necoro.net> + +# our backend stuff + +VERSION = "0.3.1" +MENU_EMERGE = 1 +MENU_UNEMERGE = 2 + +# gtk stuff +import pygtk +pygtk.require("2.0") +import gtk +import gobject + +import geneticone +from geneticone import flags +from gui_helper import * + +# for the terminal +import pty +import vte + +# other +from portage_util import unique_array + +class AboutWindow: + + def __init__ (self, parent): + # window + self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) + self.window.set_title("About Genetic/One") + self.window.set_modal(True) + self.window.set_transient_for(parent) + self.window.set_destroy_with_parent(True) + self.window.set_resizable(False) + self.window.set_default_size(1,1) + + box = gtk.VBox(False) + self.window.add(box) + + label = gtk.Label() + label.set_justify(gtk.JUSTIFY_CENTER) + label.set_markup(""" +<big><b>Genetic/One v.%s</b></big> +A Portage-GUI + +This software is licensed under the terms of the GPLv2. +Copyright (C) 2006 Necoro d.M. <necoro@necoro.net> +""" % VERSION) + box.pack_start(label) + + okBtn = gtk.Button("OK") + okBtn.connect("clicked", lambda x: self.window.destroy()) + box.pack_start(okBtn) + + self.window.show_all() + +class PackageWindow: + """A window with data about a specfic package.""" + + def __init__ (self, parent, cp, queue = None, version = None, delOnClose = True, doEmerge = True): + """Build up window contents.""" + self.parent = parent # parent window + self.cp = cp # category/package + self.version = version # version - if not None this is used + self.queue = queue + self.delOnClose = delOnClose + self.doEmerge = doEmerge + self.flagChanged = False + + # window + self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) + self.window.set_title(cp) + self.window.set_modal(True) + self.window.set_transient_for(parent) + self.window.set_destroy_with_parent(True) + self.window.set_resizable(False) + self.window.set_default_size(1,1) # as small as possible + self.window.connect("delete-event", lambda a,b: False) + #self.window.connect("configure-event", self.cbSizeCheck) + + # packages and installed packages + self.packages = geneticone.sort_package_list(geneticone.find_packages(cp, masked=True)) + self.instPackages = geneticone.sort_package_list(geneticone.find_installed_packages(cp, masked=True)) + + # main structure - the table + self.table = gtk.Table(rows=4,columns=2) + self.window.add(self.table) + + # version-combo-box + self.vCombo = self.build_vers_combo() + self.table.attach(self.vCombo, 0, 1, 1, 2, yoptions = gtk.FILL) + + # the label (must be here, because it depends on the combo box) + desc = self.actual_package().get_env_var("DESCRIPTION") + use_markup = True + if not desc: + desc = "<no description>" + use_markup = False + else: + desc = "<b>"+desc+"</b>" + self.descLabel = gtk.Label(desc) + self.descLabel.set_line_wrap(True) + self.descLabel.set_justify(gtk.JUSTIFY_CENTER) + self.descLabel.set_use_markup(use_markup) + self.table.attach(self.descLabel, 0, 2, 0, 1, xoptions = gtk.FILL, ypadding = 10) + + # the check boxes + checkHB = gtk.HBox (True, 1) + self.table.attach(checkHB, 1, 2, 1, 2, yoptions = gtk.FILL) + + self.installedCheck = gtk.CheckButton() + self.installedCheck.connect("button-press-event", self.cb_button_pressed) + self.installedCheck.set_label("Installed") + checkHB.pack_start(self.installedCheck, True, False) + + self.maskedCheck = gtk.CheckButton() + self.maskedCheck.connect("button-press-event", self.cb_button_pressed) + self.maskedCheck.set_label("Masked") + checkHB.pack_start(self.maskedCheck, True, False) + + self.testingCheck = gtk.CheckButton() + self.testingCheck.connect("button-press-event", self.cb_button_pressed) + self.testingCheck.set_label("Testing") + checkHB.pack_start(self.testingCheck, True, False) + + # use list + self.useList = self.build_use_list() + self.useListScroll = gtk.ScrolledWindow() + self.useListScroll.add(self.useList) + self.useListScroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_NEVER) # XXX: make this work correctly + self.table.attach(self.useListScroll, 0, 2, 2, 3, ypadding = 10) + + # buttons + buttonHB = gtk.HButtonBox() + buttonHB.set_layout(gtk.BUTTONBOX_SPREAD) + self.table.attach(buttonHB, 0, 2, 3, 4) + + self.emergeBtn = gtk.Button("_Emerge") + self.unmergeBtn = gtk.Button("_Unmerge") + if not self.queue or not doEmerge: + self.emergeBtn.set_sensitive(False) + self.unmergeBtn.set_sensitive(False) + self.cancelBtn = gtk.Button("_Cancel") + if not self.delOnClose: + self.cancelBtn.set_label("_Close") + self.cancelBtn.connect("clicked", self.cb_cancel_clicked) + self.emergeBtn.connect("clicked", self.cb_emerge_clicked) + self.unmergeBtn.connect("clicked", self.cb_unmerge_clicked) + buttonHB.pack_start(self.emergeBtn) + buttonHB.pack_start(self.unmergeBtn) + buttonHB.pack_start(self.cancelBtn) + + # current status + self.cb_changed(self.vCombo) + + # show + self.window.show_all() + + def cb_changed (self, combo, data = None): + """Callback for the changed ComboBox. + It then rebuilds the useList and the checkboxes.""" + # remove old useList + self.useListScroll.remove(self.useList) + + # build new + self.useList = self.build_use_list() + self.useListScroll.add(self.useList) + self.update_checkboxes() + + self.useListScroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_NEVER) + + # set emerge-button-label + if not self.actual_package().is_installed(): + self.emergeBtn.set_label("_Emerge") + self.unmergeBtn.set_sensitive(False) + else: + self.emergeBtn.set_label("R_emerge") + self.unmergeBtn.set_sensitive(True) + + # refresh - make window as small as possible + self.table.show_all() + self.window.resize(1,1) + return True + + def build_vers_combo (self): + """Creates the combo box with the different versions.""" + combo = gtk.combo_box_new_text() + + # append versions + for s in [x.get_version() for x in self.packages]: + combo.append_text(s) + + # activate the first one + try: + best_version = "" + if self.version: + best_version = self.version + else: + best_version = geneticone.find_best_match(self.packages[0].get_cp(), (self.instPackages != [])).get_version() + for i in range(len(self.packages)): + if self.packages[i].get_version() == best_version: + combo.set_active(i) + break + except AttributeError: # no package found + combo.set_active(0) + + combo.connect("changed", self.cb_changed) + + return combo + + def actual_package (self): + """Returns the actual package (a geneticone.Package-object).""" + return self.packages[self.vCombo.get_active()] + + def cb_button_pressed (self, b, event, data = None): + """Callback for pressed checkboxes. Just quits the event-loop - no redrawing.""" + if not isinstance(b, gtk.CellRendererToggle): + b.emit_stop_by_name("button-press-event") + return True + + def cb_cancel_clicked (self, button, data = None): + if self.delOnClose: self.actual_package().remove_new_use_flags() + if self.flagChanged: + if self.queue: + self.queue.append(self.actual_package().get_cpv(), update = True) + self.window.destroy() + return True + + def cb_emerge_clicked (self, button, data = None): + """Adds the package to the EmergeQueue.""" + if not geneticone.am_i_root(): + errorMB = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, "You cannot (un)merge without being root.") + errorMB.run() + errorMB.destroy() + else: + self.queue.append(self.actual_package().get_cpv(), False) + self.window.destroy() + return True + + def cb_unmerge_clicked (self, button, data = None): + """Adds the package to the UnmergeQueue.""" + if not geneticone.am_i_root(): + errorMB = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, "You cannot (un)merge without being root.") + errorMB.run() + errorMB.destroy() + else: + self.queue.append(self.actual_package().get_cpv(), True) + self.window.destroy() + return True + + def cb_use_flag_toggled (self, cell, path, store, data = None): + store[path][0] = not store[path][0] + prefix = "" + if not store[path][0]: + prefix = "-" + self.actual_package().set_use_flag(prefix+store[path][1]) + self.flagChanged = True + return True + + def update_checkboxes (self): + """Updates the checkboxes.""" + self.installedCheck.set_active(self.actual_package().is_installed()) + self.maskedCheck.set_active(self.actual_package().is_masked()) + self.testingCheck.set_active((self.actual_package().get_mask_status() % 3) == 1) + + def build_use_list (self): + """Builds the useList.""" + store = gtk.ListStore(bool, str, str) + + pkg = self.actual_package() + for use in pkg.get_all_use_flags(): + if pkg.is_installed() and use in pkg.get_actual_use_flags(): # flags set during install + enabled = True + elif (not pkg.is_installed()) and use in pkg.get_settings("USE").split() and not flags.invert_use_flag(use) in pkg.get_new_use_flags(): # flags that would be set + enabled = True + elif use in pkg.get_new_use_flags(): + enabled = True + else: + enabled = False + store.append([enabled, use, geneticone.get_use_desc(use, self.cp)]) + + # build view + view = gtk.TreeView(store) + cell = gtk.CellRendererText() + tCell = gtk.CellRendererToggle() + tCell.set_property("activatable", True) + tCell.connect("toggled", self.cb_use_flag_toggled, store) + view.append_column(gtk.TreeViewColumn("Enabled", tCell, active = 0)) + view.append_column(gtk.TreeViewColumn("Flags", cell, text = 1)) + view.append_column(gtk.TreeViewColumn("Description", cell, text = 2)) + + if store.iter_n_children(None) == 0: + view.set_child_visible(False) + else: + view.set_child_visible(True) + return view + +class SearchWindow: + """A window showing the results of a search process.""" + + def __init__ (self, parent, list, jump_to): + # window + self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) + self.window.set_title("Search results") + self.window.set_modal(True) + self.window.set_transient_for(parent) + self.window.set_destroy_with_parent(True) + self.window.set_resizable(False) + self.window.set_default_size(1,1) + self.list = list + self.jump_to = jump_to + + box = gtk.HBox(False) + self.window.add(box) + + self.combo = gtk.combo_box_new_text() + for x in list: + self.combo.append_text(x) + self.combo.set_active(0) + + box.pack_start(self.combo) + + okBtn = gtk.Button("OK") + okBtn.connect("clicked", self.cb_ok_btn_clicked) + box.pack_start(okBtn) + + self.window.show_all() + + def cb_ok_btn_clicked (self, button, data = None): + self.window.destroy() + self.jump_to(self.list[self.combo.get_active()]) + +class MainWindow: + """Application main window.""" + + def __init__ (self): + """Build up window""" + + # window + self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) + self.window.set_title("Genetic/One") + self.window.connect("delete_event", self.cb_delete) + self.window.connect("destroy", self.cb_destroy) + self.window.set_border_width(2) + self.window.set_geometry_hints (self.window, min_width = 600, min_height = 800, max_height = gtk.gdk.screen_height(), max_width = gtk.gdk.screen_width()) + self.window.set_resizable(True) + + # main vb + vb = gtk.VBox(False, 1) + self.window.add(vb) + + # menubar + menubar = self.create_main_menu() + vb.pack_start(menubar, False) + + # search + self.searchEntry = gtk.Entry() + self.searchBtn = gtk.Button("_Search") + self.searchBtn.connect("clicked", self.cb_search_clicked) + hbSearch = gtk.HBox(False, 5) + hbSearch.pack_start(self.searchEntry, True, True) + hbSearch.pack_start(self.searchBtn, False, False) + vb.pack_start(hbSearch, False, False, 5) + + # VPaned holding the lists and the Terminal + vpaned = gtk.VPaned() + vpaned.set_position(400) + vb.pack_start(vpaned, True, True) + + # a HB holding the lists + hb = gtk.HBox(True, 5) + hbFrame = gtk.Frame() + hbFrame.add(hb) + hbFrame.set_shadow_type(gtk.SHADOW_IN) + vpaned.pack1(hbFrame, shrink = True, resize = True) + + self.scroll_1 = gtk.ScrolledWindow() + self.scroll_2 = gtk.ScrolledWindow() + self.scroll_1.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) + self.scroll_2.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) + hb.pack_start(self.scroll_1, True, True) + hb.pack_start(self.scroll_2, True, True) + + # create cat List + self.catList = self.create_cat_list() + self.scroll_1.add(self.catList) + + # create pkg list + self.pkgList = self.create_pkg_list() + self.scroll_2.add(self.pkgList) + # queue list + queueVB = gtk.VBox(False, 0) + hb.pack_start(queueVB, True, True) + + queueScroll = gtk.ScrolledWindow() + queueScroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + emergeStore = gtk.TreeStore(str) + self.emergeView = gtk.TreeView(emergeStore) + cell = gtk.CellRendererText() + col = gtk.TreeViewColumn("Queue", cell, text = 0) + self.emergeView.append_column(col) + self.emergeView.connect("row-activated", self.cb_row_activated, emergeStore) + queueScroll.add(self.emergeView) + queueVB.pack_start(queueScroll, True, True) + + # buttons right unter the queue list + buttonBox = gtk.HButtonBox() + queueVB.pack_start(buttonBox, False) + self.emergeBtn = gtk.Button("_Emerge") + self.emergeBtn.connect("clicked", self.cb_emerge_clicked) + self.unmergeBtn = gtk.Button("_Unmerge") + self.unmergeBtn.connect("clicked", self.cb_emerge_clicked) + self.removeBtn = gtk.Button("_Remove") + self.removeBtn.connect("clicked", self.cb_remove_clicked) + buttonBox.pack_start(self.emergeBtn) + buttonBox.pack_start(self.removeBtn) + buttonBox.pack_start(self.unmergeBtn) + + # the terminal + term = vte.Terminal() + term.set_scrollback_lines(1024) + term.set_scroll_on_output(True) + # XXX why is this not working with the colors + term.set_color_background(gtk.gdk.color_parse("white")) + term.set_color_foreground(gtk.gdk.color_parse("black")) + termBox = gtk.HBox(False, 0) + termScroll = gtk.VScrollbar(term.get_adjustment()) + termBox.pack_start(term, True, True) + termBox.pack_start(termScroll, False) + + termFrame = gtk.Frame("Console") + termFrame.set_shadow_type(gtk.SHADOW_IN) + termFrame.add(termBox) + vpaned.pack2(termFrame, shrink = True, resize = True) + + # the status line + self.statusLabel = gtk.Label("Genetic/One - <Statusline>") + self.statusLabel.set_alignment(0.0,0.7) + self.statusLabel.set_single_line_mode(True) + vb.pack_start(self.statusLabel, False, False) + + # show + self.window.show_all() + + # set emerge queue + self.queue = EmergeQueue(console=term, tree = emergeStore, packages = self.packages) + + def cb_delete (self, widget, data = None): + """Returns false -> window is deleted.""" + return False + + def cb_destroy (self, widget, data = None): + """Calls main_quit().""" + gtk.main_quit() + + def create_main_menu (self): + """Creates the main menu. XXX: Rebuild to use UIManager""" + # the menu-list + mainMenuDesc = [ + ( "/_File", None, None, 0, "<Branch>"), + ( "/File/_Close", None, self.cb_destroy, 0, ""), + ( "/_Emerge", None, None, 0, "<Branch>"), + ( "/Emerge/_Emerge", None, self.cb_emerge_clicked, MENU_EMERGE, ""), + ( "/Emerge/_Unmerge", None, self.cb_emerge_clicked, MENU_UNEMERGE, ""), + ( "/_?", None, None, 0, "<Branch>"), + ( "/?/_About", None, lambda x,y: AboutWindow(self.window), 0, "") + ] + self.itemFactory = gtk.ItemFactory(gtk.MenuBar, "<main>", None) + self.itemFactory.create_items(mainMenuDesc) + return self.itemFactory.get_widget("<main>") + + def cb_cat_list_selection (self, view, data = None, force = False): + """Callback for a category-list selection. Updates the package list with these packages in the category.""" + if view == self.catList: # be sure it is the catList + # get the selected category + sel = view.get_selection() + store, it = sel.get_selected() + + if it: + # remove old one + self.scroll_2.remove(self.pkgList) + # create new package list + self.pkgList = self.create_pkg_list(store.get_value(it,0), force) + self.scroll_2.add(self.pkgList) + self.scroll_2.show_all() + return False + + def cb_row_activated (self, view, path, col, store = None): + """Callback for an activated row in the pkgList. Opens a package window.""" + if view == self.pkgList: + package = store.get_value(store.get_iter(path), 0) + if package[-1] == '*': package = package[:-1] + PackageWindow(self.window, self.selCatName+"/"+package, self.queue) + elif view == self.emergeView: + if len(path) > 1: + package = store.get_value(store.get_iter(path), 0) + cat, name, vers, rev = geneticone.split_package_name(package) + if rev != "r0": vers = vers+"-"+rev + PackageWindow(self.window, cat+"/"+name, queue = self.queue, version = vers, delOnClose = False, doEmerge = False) + return True + + def create_cat_list (self): + """Creates the category list.""" + store = gtk.ListStore(str) + + # build categories + for p in geneticone.list_categories(): + store.append([p]) + # sort them alphabetically + store.set_sort_column_id(0, gtk.SORT_ASCENDING) + + view = gtk.TreeView(store) + cell = gtk.CellRendererText() + col = gtk.TreeViewColumn("Categories", cell, text = 0) + view.append_column(col) + view.connect("cursor-changed", self.cb_cat_list_selection) + view.connect("row-activated", lambda v,p,c : self.cb_cat_list_selection(v)) + view.set_search_column(0) + + return view + + packages = {} # directory category -> [packages] + def create_pkg_list (self, name = None, force = False): + """Creates the package list. Gets the name of the category.""" + self.selCatName = name # actual category + store = gtk.ListStore(str) + + # calculate packages + if name: + if name not in self.packages and not force: # only calc packages if not already done + self.packages[name] = [] + for p in unique_array([x.get_name() for x in geneticone.find_all_packages(name+"/")]): + if geneticone.find_installed_packages(name+"/"+p, masked=True) != []: + p += "*" # append a '*' if the package is installed + self.packages[name].append(p) + + for p in self.packages[name]: + store.append([p]) + + # sort alphabetically + store.set_sort_column_id(0, gtk.SORT_ASCENDING) + + # build view + pkgList = gtk.TreeView(store) + cell = gtk.CellRendererText() + col = gtk.TreeViewColumn("Packages", cell, text = 0) + pkgList.append_column(col) + pkgList.connect("row-activated", self.cb_row_activated, store) + + return pkgList + + def cb_remove_clicked (self, button, data = None): + """Removes a selected item in the (un)emerge-queue if possible.""" + selected = self.emergeView.get_selection() + + if selected: + model, iter = selected.get_selected() + + if not model.iter_parent(iter): # top-level + if model.iter_n_children(iter) > 0: # and has children which can be removed :) + askMB = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL, gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, "Do you really want to clear the whole queue?") + if askMB.run() == gtk.RESPONSE_YES : + self.queue.remove_children(iter) + askMB.destroy() + elif model.iter_parent(model.iter_parent(iter)): # this is in the 3rd level => dependency + infoMB = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO, gtk.BUTTONS_OK, "You cannot remove dependencies. :)") + infoMB.run() + infoMB.destroy() + else: + self.queue.remove(iter) + + return True + + def cb_emerge_clicked (self, button, data = None): + """Do emerge or unemerge.""" + print button + if button == self.emergeBtn or button == MENU_EMERGE: + if len(flags.newUseFlags) > 0: + hintMB = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO, gtk.BUTTONS_OK, + "You have changed use flags. Genetic/One will write these changes into the appropriate files. Please backup them if you think it is necessairy.") + hintMB.run() + hintMB.destroy() + flags.write_use_flags() + self.queue.emerge(force=True) + elif button == self.unmergeBtn or button == MENU_UNEMERGE: + self.queue.unmerge(force=True) + + return True + + def cb_search_clicked (self, button, data = None): + """Do a search.""" + if self.searchEntry.get_text() != "": + packages = geneticone.find_all_packages(self.searchEntry.get_text()) + + if packages == []: + dialog = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO, gtk.BUTTONS_OK, "Package not found!") + dialog.run() + dialog.destroy() + else: + packages = unique_array([p.get_cp() for p in packages]) + + if len(packages) == 1: + self.jump_to(packages[0]) + else: + SearchWindow(self.window, packages, self.jump_to) + + def jump_to (self, cp): + """Is called when we want to jump to a specific package.""" + PackageWindow(self.window, cp, self.queue) + + def main (self): + """Main.""" + gobject.threads_init() + # now subthreads can run normally, but are not allowed to touch the GUI. If threads should change sth there - use gobject.idle_add(). + # for more informations on threading and gtk: http://www.async.com.br/faq/pygtk/index.py?req=show&file=faq20.006.htp + gtk.main() |