diff options
Diffstat (limited to 'portato')
-rw-r--r-- | portato/__init__.py | 11 | ||||
-rw-r--r-- | portato/backend/__init__.py | 33 | ||||
-rw-r--r-- | portato/backend/exceptions.py | 23 | ||||
-rw-r--r-- | portato/backend/flags.py | 617 | ||||
-rw-r--r-- | portato/backend/package.py | 324 | ||||
-rw-r--r-- | portato/backend/portage_helper.py | 374 | ||||
-rw-r--r-- | portato/config_parser.py | 274 | ||||
-rw-r--r-- | portato/constants.py | 17 | ||||
-rw-r--r-- | portato/gui/__init__.py | 14 | ||||
-rw-r--r-- | portato/gui/gtk/__init__.py | 13 | ||||
-rw-r--r-- | portato/gui/gtk/dialogs.py | 66 | ||||
-rw-r--r-- | portato/gui/gtk/glade/geneticone.glade | 1028 | ||||
-rw-r--r-- | portato/gui/gtk/windows.py | 892 | ||||
-rw-r--r-- | portato/gui/gtk/wrapper.py | 105 | ||||
-rw-r--r-- | portato/gui/gui_helper.py | 608 | ||||
-rw-r--r-- | portato/gui/wrapper.py | 184 | ||||
-rw-r--r-- | portato/helper.py | 60 |
17 files changed, 4643 insertions, 0 deletions
diff --git a/portato/__init__.py b/portato/__init__.py new file mode 100644 index 0000000..4b1f42f --- /dev/null +++ b/portato/__init__.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# +# File: portato/__init__.py +# This file is part of the Portato-Project, a graphical portage-frontend. +# +# Copyright (C) 2006 René 'Necoro' Neumann +# 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 René 'Necoro' Neumann <necoro@necoro.net> diff --git a/portato/backend/__init__.py b/portato/backend/__init__.py new file mode 100644 index 0000000..55cb10b --- /dev/null +++ b/portato/backend/__init__.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# +# File: portato/backend/__init__.py +# This file is part of the Portato-Project, a graphical portage-frontend. +# +# Copyright (C) 2006 René 'Necoro' Neumann +# 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 René 'Necoro' Neumann <necoro@necoro.net> + +import sys + +# insert the gentoolkit-location into syspath +sys.path.insert(0, "/usr/lib/gentoolkit/pym") + +# import gentoolkit and portage +import gentoolkit +import portage + +# this is set to "var/lib/portage/world" by default - so we add the leading / +portage.WORLD_FILE = portage.settings["ROOT"]+portage.WORLD_FILE +portage.settings = None # we use our own one ... + +# portage tree vars +porttree = gentoolkit.porttree +vartree = gentoolkit.vartree + +# import our packages +from exceptions import * +from package import * +from portage_helper import * diff --git a/portato/backend/exceptions.py b/portato/backend/exceptions.py new file mode 100644 index 0000000..a5cb2fb --- /dev/null +++ b/portato/backend/exceptions.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# +# File: portato/backend/exceptions.py +# This file is part of the Portato-Project, a graphical portage-frontend. +# +# Copyright (C) 2006 René 'Necoro' Neumann +# 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 René 'Necoro' Neumann <necoro@necoro.net> + +class BlockedException (Exception): + """An exception marking, that some package is blocking another one.""" + pass + +class PackageNotFoundException (Exception): + """An exception marking that a package could not be found.""" + pass + +class DependencyCalcError (Exception): + """An error occured during dependency calculation.""" + pass diff --git a/portato/backend/flags.py b/portato/backend/flags.py new file mode 100644 index 0000000..d1187c9 --- /dev/null +++ b/portato/backend/flags.py @@ -0,0 +1,617 @@ +# -*- coding: utf-8 -*- +# +# File: portato/backend/flags.py +# This file is part of the Portato-Project, a graphical portage-frontend. +# +# Copyright (C) 2006 René 'Necoro' Neumann +# 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 René 'Necoro' Neumann <necoro@necoro.net> + +import os +import os.path +from subprocess import Popen, PIPE # needed for grep + +from portato.helper import * +from portage_helper import split_package_name +import package + +import portage +from portage_util import unique_array + +CONFIG = { + "usefile" : "portato", + "maskfile" : "portato", + "testingfile" : "portato", + "usePerVersion" : True, + "maskPerVersion" : True, + "testingPerVersion" : True + } + +### GENERAL PART ### + +def grep (pkg, path): + """Grep runs "egrep" on a given path and looks for occurences of a given package. + @param pkg: the package + @type pkg: string (cpv) or L{backend.Package}-object + @param path: path to look in + @type path: string + + @returns: occurences of pkg in the format: "file:line-no:complete_line_found" + @rtype: string""" + + if not isinstance(pkg, package.Package): + pkg = package.Package(pkg) # assume it is a cpv or a gentoolkit.Package + + command = "egrep -x -n -r -H '^[<>!=~]{0,2}%s(-[0-9].*)?[[:space:]]?.*$' %s" # %s is replaced in the next line ;) + return Popen((command % (pkg.get_cp(), path)), shell = True, stdout = PIPE).communicate()[0].splitlines() + +def get_data(pkg, path): + """This splits up the data of L{grep} and builds tuples in the format (file,line,criterion,list_of_flags). + @param pkg: package to find + @type pkg: string (cpv) or L{backend.Package}-object + @param path: path to look in + @type path: string + + @returns: a list of tuples in the form (file,line,criterion,list_of_flags) + @rtype: (string,string,string,string[])[]""" + + flags = [] + + # do grep + list = grep(pkg, path) + + for i in range(len(list)): + file, line, fl = tuple(list[i].split(":")) # get file, line and flag-list + fl = fl.split() + crit = fl[0] + fl = fl[1:] + # stop after first comment + for j in range(len(fl)): + if fl[j][0] == "#": # comment - stop here + fl = fl[:j] + break + flags.append((file,line,crit,fl)) + + return flags + +def set_config (cfg): + """This function sets the CONFIG-variable for the whole module. Use this instead of modifying L{CONFIG} directly. + @param cfg: a dictionary with at least all the keys of the CONFIG-var + @type cfg: dict + @raises KeyError: if a keyword is missing in the new cfg""" + + for i in CONFIG.keys(): + if not i in cfg: + raise KeyError, "Missing keyword in config: "+i + + for i in CONFIG: + CONFIG[i] = cfg[i] + +def generate_path (cpv, exp): + """Generates the correct path out of given wildcards. + These wildcards can be: + - $(cat) : category + - $(cat-1): first part of the category (e.g. "app") + - $(cat-2): second part of the category + - $(pkg) : name of the package + + @param cpv: the cpv of the current package + @type cpv: string (cat/pkg-ver) + @param exp: the expression to render the path from + @type exp: string + @returns: rendered path + @rtype string""" + + cat, pkg, ver, rev = split_package_name(cpv) + + if exp.find("$(") != -1: + exp = exp.replace("$(cat)",cat).\ + replace("$(pkg)",pkg).\ + replace("$(cat-1)",cat.split("-")[0]).\ + replace("$(cat-2)",cat.split("-")[1]) + return exp + +### USE FLAG PART ### +USE_PATH = os.path.join(portage.USER_CONFIG_PATH,"package.use") +USE_PATH_IS_DIR = os.path.isdir(USE_PATH) +useFlags = {} # useFlags in the file +newUseFlags = {} # useFlags as we want them to be: format: cpv -> [(file, line, useflag, (true if removed from list / false if added))] + +def invert_use_flag (flag): + """Invertes a flag. + + >>> invert_use_flag("foo") + -foo + >>> invert_use_flag("-bar") + bar + + @param flag: the flag + @type flag: string + @returns: inverted flag + @rtype: string + """ + + if flag[0] == "-": + return flag[1:] + else: + return "-"+flag + +def set_use_flag (pkg, flag): + """Sets the useflag for a given package. + + @param pkg: the package + @type pkg: string (cpv) or L{backend.Package}-object + @param flag: the flag to set + @type flag: string""" + + global useFlags, newUseFlags + + if not isinstance(pkg, package.Package): + pkg = package.Package(pkg) # assume cpv or gentoolkit.Package + + cpv = pkg.get_cpv() + invFlag = invert_use_flag(flag) + + # if not saved in useFlags, get it by calling get_data() which calls grep() + data = None + if not cpv in useFlags: + data = get_data(pkg, USE_PATH) + useFlags[cpv] = data + else: + data = useFlags[cpv] + + if not cpv in newUseFlags: + newUseFlags[cpv] = [] + + debug("data: "+str(data)) + # add a useflag / delete one + added = False + for file, line, crit, flags in data: + if pkg.matches(crit): + # we have the inverted flag in the uselist/newuselist --> delete it + if invFlag in flags or (file, line, invFlag, False) in newUseFlags[cpv] or (file, line, flag, True) in newUseFlags[cpv]: + if added: del newUseFlags[-1] # we currently added it as an extra option - delete it + added = True + jumpOut = False + for t in [(file, line, invFlag, False),(file, line, flag, True)]: + if t in newUseFlags[cpv]: + newUseFlags[cpv].remove(t) + jumpOut = True + break + if not jumpOut: newUseFlags[cpv].append((file, line, invFlag, True)) + break + + # we want to duplicate the flag --> ignore + elif flag in flags: + added = True # emulate adding + break + + # add as an extra flag + else: + if not added: newUseFlags[cpv].append((file, line, flag, False)) + added = True + + # create a new line + if not added: + path = USE_PATH + if USE_PATH_IS_DIR: + path = os.path.join(USE_PATH, generate_path(cpv, CONFIG["usefile"])) + try: + newUseFlags[cpv].remove((path, -1, invFlag, False)) + except ValueError: # not in UseFlags + newUseFlags[cpv].append((path, -1, flag, False)) + + newUseFlags[cpv] = unique_array(newUseFlags[cpv]) + debug("newUseFlags: "+str(newUseFlags)) + +def remove_new_use_flags (cpv): + """Removes all new use-flags for a specific package. + + @param cpv: the package for which to remove the flags + @type cpv: string (cpv) or L{backend.Package}-object""" + + if isinstance(cpv, package.Package): + cpv = cpv.get_cpv() + + try: + del newUseFlags[cpv] + except KeyError: + pass + +def get_new_use_flags (cpv): + """Gets all the new use-flags for a specific package. + + @param cpv: the package from which to get the flags + @type cpv: string (cpv) or L{backend.Package}-object + @returns: list of flags + @rtype: string[]""" + + if isinstance(cpv, package.Package): + cpv = cpv.get_cpv() + + list2return = [] + try: + for file, line, flag, remove in newUseFlags[cpv]: + if remove: + list2return.append(invert_use_flag(flag)) + else: + list2return.append(flag) + except KeyError: + pass + + return list2return + +def write_use_flags (): + """This writes our changed useflags into the file.""" + global newUseFlags, useFlags + + def insert (flag, list): + """Shortcut for inserting a new flag right after the package-name.""" + list.insert(1,flag) + + def remove (flag, list): + """Removes a flag.""" + try: + list.remove(flag) + except ValueError: # flag is given as flag\n + list.remove(flag+"\n") + list.append("\n") #re-insert the newline + + # no more flags there - comment it out + if len(list) == 1 or list[1][0] in ("#","\n"): + list[0] = "#"+list[0] + insert("#removed by portato#",list) + + file_cache = {} # cache for having to read the file only once: name->[lines] + for cpv in newUseFlags: + flagsToAdd = [] # this is used for collecting the flags to be inserted in a _new_ line + for file, line, flag, delete in newUseFlags[cpv]: + line = int(line) # it is saved as a string so far! + + # add new line + if line == -1: + flagsToAdd.append(flag) + # change a line + else: + if not file in file_cache: + # read file + f = open(file, "r") + lines = [] + i = 1 + while i < line: # stop at the given line + lines.append(f.readline()) + i += 1 + l = f.readline().split(" ") + + # delete or insert + if delete: + remove(flag,l) + else: + insert(flag,l) + lines.append(" ".join(l)) + + # read the rest + lines.extend(f.readlines()) + + file_cache[file] = lines + f.close() + + else: # in cache + l = file_cache[file][line-1].split(" ") + if delete: + remove(flag,l) + else: + insert(flag,l) + file_cache[file][line-1] = " ".join(l) + + if flagsToAdd: + # write new lines + msg = "\n#portato update#\n" + if CONFIG["usePerVersion"]: # add on a per-version-base + msg += "=%s %s\n" % (cpv, ' '.join(flagsToAdd)) + else: # add on a per-package-base + list = split_package_name(cpv) + msg += "%s/%s %s\n" % (list[0], list[1], ' '.join(flagsToAdd)) + if not file in file_cache: + f = open(file, "a") + f.write(msg) + f.close() + else: + file_cache[file].append(msg) + + # write to disk + for file in file_cache.keys(): + f = open(file, "w") + f.writelines(file_cache[file]) + f.close() + # reset + useFlags = {} + newUseFlags = {} + +### MASKING PART ### +MASK_PATH = os.path.join(portage.USER_CONFIG_PATH,"package.mask") +UNMASK_PATH = os.path.join(portage.USER_CONFIG_PATH,"package.unmask") +MASK_PATH_IS_DIR = os.path.isdir(MASK_PATH) +UNMASK_PATH_IS_DIR = os.path.isdir(UNMASK_PATH) + +new_masked = {} +new_unmasked = {} + +def set_masked (pkg, masked = True): + """Sets the masking status of the package. + + @param pkg: the package from which to get the flags + @type pkg: string (cpv) or L{backend.Package}-object + @param masked: if True: mask it; if False: unmask it + @type masked: boolean""" + + global new_masked, newunmasked + + if not isinstance(pkg, package.Package): + pkg = package.Package(pkg) + + cpv = pkg.get_cpv() + + if not cpv in new_unmasked: + new_unmasked[cpv] = [] + if not cpv in new_masked: + new_masked[cpv] = [] + + if masked: + link_neq = new_masked + link_eq = new_unmasked + path = UNMASK_PATH + else: + link_neq = new_unmasked + link_eq = new_masked + path = MASK_PATH + + copy = link_eq[cpv] + for file, line in copy: + if line == "-1": + link_eq[cpv].remove((file, line)) + + copy = link_neq[cpv][:] + for file, line in copy: + if line != "-1": + link_neq[cpv].remove(file, line) + + if masked == pkg.is_masked(): + return + + data = get_data(pkg, path) + debug("data: "+str(link_eq)) + done = False + for file, line, crit, flags in data: + if pkg.matches(crit): + link_eq[cpv].append((file, line)) + done = True + + if done: return + + if masked: + is_dir = MASK_PATH_IS_DIR + path = MASK_PATH + else: + is_dir = UNMASK_PATH_IS_DIR + path = UNMASK_PATH + + if is_dir: + file = os.path.join(path, generate_path(cpv, CONFIG["usefile"])) + else: + file = path + + link_neq[cpv].append((file, "-1")) + link_neq[cpv] = unique_array(link_neq[cpv]) + debug("new_(un)masked: "+str(link_neq)) + +def remove_new_masked (cpv): + if isinstance(cpv, package.Package): + cpv = cpv.get_cpv() + + try: + del new_masked[cpv] + except KeyError: + pass + + try: + del new_unmasked[cpv] + except KeyError: + pass + +def new_masking_status (cpv): + if isinstance(cpv, package.Package): + cpv = cpv.get_cpv() + + if cpv in new_masked and new_masked[cpv]: + return "masked" + elif cpv in new_unmasked and new_unmasked[cpv]: + return "unmasked" + else: return None + +def write_masked (): + global new_unmasked, new_masked + file_cache = {} + + def write(cpv, file, line): + line = int(line) + # add new line + if line == -1: + msg = "\n#portato update#\n" + if CONFIG["maskPerVersion"]: + msg += "=%s\n" % cpv + else: + list = split_package_name(cpv) + msg += "%s/%s\n" % (list[0],list[1]) + if not file in file_cache: + f = open(file, "a") + f.write(msg) + f.close() + else: + file_cache[file].append(msg) + # change a line + else: + if not file in file_cache: + # read file + f = open(file, "r") + lines = [] + i = 1 + while i < line: # stop at the given line + lines.append(f.readline()) + i = i+1 + # delete + l = f.readline() + l = "#"+l[:-1]+" # removed by portato\n" + lines.append(l) + + # read the rest + lines.extend(f.readlines()) + + file_cache[file] = lines + f.close() + else: # in cache + l = file_cache[file][line-1] + # delete: + l = "#"+l[:-1]+" # removed by portato\n" + file_cache[file][line-1] = l + + + for cpv in new_masked: + for file, line in new_masked[cpv]: + write(cpv, file, line) + + for cpv in new_unmasked: + for file, line in new_unmasked[cpv]: + write(cpv, file, line) + + # write to disk + for file in file_cache.keys(): + f = open(file, "w") + f.writelines(file_cache[file]) + f.close() + # reset + new_masked = {} + new_unmasked = {} + +### TESTING PART ### +TESTING_PATH = os.path.join(portage.USER_CONFIG_PATH, "package.keywords") +TESTING_PATH_IS_DIR = os.path.isdir(TESTING_PATH) +newTesting = {} +arch = "" + +def remove_new_testing (cpv): + if isinstance(cpv, package.Package): + cpv = cpv.get_cpv() + + try: + del newTesting[cpv] + except KeyError: + pass + +def new_testing_status (cpv): + if isinstance(cpv, package.Package): + cpv = cpv.get_cpv() + + if cpv in newTesting: + for file, line in newTesting[cpv]: + if line == "-1": return False + else: return True + + return None + +def set_testing (pkg, enable): + """Enables the package for installing when it is marked as testing (~ARCH). + @param pkg: the package + @type pkg: string (cpv) or L{backend.Package}-object + @param enable: controls whether to enable (True) or disable (False) for test-installing + @type enable: boolean""" + + global arch, newTesting + if not isinstance(pkg, package.Package): + pkg = package.Package(pkg) + + arch = pkg.get_settings("ARCH") + cpv = pkg.get_cpv() + if not cpv in newTesting: + newTesting[cpv] = [] + + for file, line in newTesting[cpv]: + if (enable and line != "-1") or (not enable and line == "-1"): + newTesting[cpv].remove((file, line)) + + if (enable and not pkg.is_testing(allowed=True)) or (not enable and pkg.is_testing(allowed=True)): + return + + if not enable: + test = get_data(pkg, TESTING_PATH) + debug("data (test): "+str(test)) + for file, line, crit, flags in test: + if pkg.matches(crit) and flags[0] == "~"+arch: + newTesting[cpv].append((file, line)) + else: + if TESTING_PATH_IS_DIR: + file = os.path.join(TESTING_PATH, CONFIG["testingfile"]) + else: + file = TESTING_PATH + newTesting[cpv].append((file, "-1")) + + newTesting[cpv] = unique_array(newTesting[cpv]) + debug("newTesting: "+str(newTesting)) + +def write_testing (): + global arch, newTesting + file_cache = {} + + for cpv in newTesting: + for file, line in newTesting[cpv]: + line = int(line) + # add new line + if line == -1: + msg = "\n#portato update#\n" + if CONFIG["testingPerVersion"]: + msg += "=%s ~%s\n" % (cpv, arch) + else: + list = split_package_name(cpv) + msg += "%s/%s ~%s\n" % (list[0],list[1],arch) + if not file in file_cache: + f = open(file, "a") + f.write(msg) + f.close() + else: + file_cache[file].append(msg) + # change a line + else: + if not file in file_cache: + # read file + f = open(file, "r") + lines = [] + i = 1 + while i < line: # stop at the given line + lines.append(f.readline()) + i = i+1 + # delete + l = f.readline() + l = "#"+l[:-1]+" # removed by portato\n" + lines.append(l) + + # read the rest + lines.extend(f.readlines()) + + file_cache[file] = lines + f.close() + else: # in cache + l = file_cache[file][line-1] + # delete: + l = "#"+l[:-1]+" # removed by portato\n" + file_cache[file][line-1] = l + + # write to disk + for file in file_cache.keys(): + f = open(file, "w") + f.writelines(file_cache[file]) + f.close() + # reset + newTesting = {} diff --git a/portato/backend/package.py b/portato/backend/package.py new file mode 100644 index 0000000..8b56eb5 --- /dev/null +++ b/portato/backend/package.py @@ -0,0 +1,324 @@ +# -*- coding: utf-8 -*- +# +# File: portato/backend/package.py +# This file is part of the Portato-Project, a graphical portage-frontend. +# +# Copyright (C) 2006 René 'Necoro' Neumann +# 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 René 'Necoro' Neumann <necoro@necoro.net> + +from portato.backend import * +from portato.helper import * +from portage_helper import * +import flags + +import portage, portage_dep, gentoolkit +from portage_util import unique_array + +import types + +class Package (gentoolkit.Package): + """This is a subclass of the gentoolkit.Package-class which a lot of additional functionality we need in Portato.""" + + def __init__ (self, cpv): + """Constructor. + + @param cpv: The cpv or gentoolkit.Package which describes the package to create. + @type cpv: string (cat/pkg-ver) or gentoolkit.Package-object.""" + + if isinstance(cpv, gentoolkit.Package): + cpv = cpv.get_cpv() + gentoolkit.Package.__init__(self, cpv) + try: + self._status = portage.getmaskingstatus(self.get_cpv(), settings = gentoolkit.settings) + except KeyError: # package is not located in the system + self._status = None + + def is_in_system (self): + """Returns False if the package could not be found in the portage system. + + @return: True if in portage system; else False + @rtype: boolean""" + + return (self._status != None) + + def is_missing_keyword(self): + """Returns True if the package is missing the needed keyword. + + @return: True if keyword is missing; else False + @rtype: boolean""" + + if self._status and "missing keyword" in self._status: + return True + return False + + def is_testing(self, allowed = False): + """Checks whether a package is marked as testing. + + @param allowed: Controls whether possible keywords are taken into account or not. + @type allowed: boolean + @returns: True if the package is marked as testing; else False. + @rtype: boolean""" + + testArch = "~" + self.get_settings("ARCH") + if not allowed: # keywords are NOT taken into account + if testArch in self.get_env_var("KEYWORDS").split(): + return True + return False + + else: # keywords are taken into account + status = flags.new_testing_status(self.get_cpv()) + if status == None: # we haven't changed it in any way + if self._status and testArch+" keyword" in self._status: + return True + return False + else: + return status + + def set_testing(self, enable = True): + """Sets the actual testing status of the package. + + @param enable: if True it is masked as stable; if False it is marked as testing + @type enable: boolean""" + + flags.set_testing(self, enable) + + def remove_new_testing(self): + """Removes possible changed testing status.""" + + flags.remove_new_testing(self.get_cpv()) + + def is_masked (self): + """Returns True if either masked by package.mask or by profile. + + @returns: True if masked / False otherwise + @rtype: boolean""" + + status = flags.new_masking_status(self.get_cpv()) + if status != None: # we have locally changed it + if status == "masked": return True + elif status == "unmasked": return False + else: + debug("BUG in flags.new_masking_status. It returns",status) + else: # we have not touched the status + if self._status and ("profile" in self._status or "package.mask" in self._status): + return True + return False + + def set_masked (self, masking = False): + """Sets the masking status of the package. + + @param masking: if True: mask it; if False: unmask it + @type masking: boolean""" + + flags.set_masked(self, masked = masking) + + def remove_new_masked (self): + """Removes possible changed masking status.""" + + flags.remove_new_masked(self.get_cpv()) + + def get_all_use_flags (self): + """Returns a list of _all_ useflags for this package, i.e. all useflags you can set for this package. + + @returns: list of use-flags + @rtype: string[]""" + + return unique_array(self.get_env_var("IUSE").split()) + + def get_installed_use_flags (self): + """Returns a list of the useflags enabled at installation time. If package is not installed, it returns an empty list. + + @returns: list of useflags enabled at installation time or an empty list + @rtype: string[]""" + + if self.is_installed(): + uses = self.get_use_flags().split() # all set at installation time + iuses = self.get_all_use_flags() # all you can set for the package + set = [] + for u in iuses: + if u in uses: + set.append(u) + return set + else: + return [] + + def get_new_use_flags (self): + """Returns a list of the new useflags, i.e. these flags which are not written to the portage-system yet. + + @returns: list of flags or [] + @rtype: string[]""" + + return flags.get_new_use_flags(self) + + def get_actual_use_flags (self): + """This returns the result of installed_use_flags + new_use_flags. If the package is not installed, it returns only the new flags. + + @return: list of flags + @rtype: string[]""" + + if self.is_installed(): + i_flags = self.get_installed_use_flags() + for f in self.get_new_use_flags(): + + if flags.invert_flag(f) in i_flags: + i_flags.remove(flags.invert_flag(f)) + + elif f not in i_flags: + i_flags.append(f) + return i_flags + else: + return self.get_new_flags() + + def set_use_flag (self, flag): + """Set a use-flag. + + @param flag: the flag to set + @type flag: string""" + + flags.set_use_flag(self, flag) + + def remove_new_use_flags (self): + """Remove all the new use-flags.""" + + flags.remove_new_use_flags(self) + + def get_matched_dep_packages (self): + """This function looks for all dependencies which are resolved. In normal case it makes only sense for installed packages, but should work for uninstalled ones too. + + @returns: unique list of dependencies resolved (with elements like "<=net-im/foobar-1.2.3") + @rtype: string[]""" + + # change the useflags, because we have internally changed some, but not made them visible for portage + newUseFlags = self.get_new_use_flags() + actual = self.get_settings("USE").split() + if newUseFlags: + for u in newUseFlags: + if u[0] == "-" and flags.invert_use_flag(u) in actual: + actual.remove(flags.invert_use_flag(u)) + elif u not in actual: + actual.append(u) + + # + # the following stuff is mostly adapted from portage.dep_check() + # + + depstring = self.get_env_var("RDEPEND")+" "+self.get_env_var("DEPEND")+" "+self.get_env_var("PDEPEND") + + # change the parentheses into lists + mysplit = portage_dep.paren_reduce(depstring) + + # strip off these deps we don't have a flag for + mysplit = portage_dep.use_reduce(mysplit, uselist = actual, masklist = [], matchall = False, excludeall = self.get_settings("ARCH")) + + # move the || (or) into the lists + mysplit = portage_dep.dep_opconvert(mysplit) + + # turn virtuals into real packages + mysplit = portage.dep_virtual(mysplit, self._settings) + + mysplit_reduced= portage.dep_wordreduce(mysplit, self._settings, vartree.dbapi, mode = None) + + retlist = [] + def add (list, red_list): + """Adds the packages to retlist.""" + for i in range(len(list)): + if type(list[i]) == types.ListType: + add(list[i], red_list[i]) + elif list[i] == "||": + continue + else: + if red_list[i]: + retlist.append(list[i]) + + add(mysplit, mysplit_reduced) + + return unique_array(retlist) + + def get_dep_packages (self): + """Returns a cpv-list of packages on which this package depends and which have not been installed yet. This does not check the dependencies in a recursive manner. + + @returns: list of cpvs on which the package depend + @rtype: string[] + + @raises portato.BlockedException: when a package in the dependency-list is blocked by an installed one + @raises portato.PackageNotFoundException: when a package in the dependency list could not be found in the system + @raises portato.DependencyCalcError: when an error occured during executing portage.dep_check()""" + + dep_pkgs = [] # the package list + + # change the useflags, because we have internally changed some, but not made them visible for portage + newUseFlags = self.get_new_use_flags() + actual = self.get_settings("USE").split() + if newUseFlags: + for u in newUseFlags: + if u[0] == "-" and flags.invert_use_flag(u) in actual: + actual.remove(flags.invert_use_flag(u)) + elif u not in actual: + actual.append(u) + + # let portage do the main stuff ;) + # pay attention to any changes here + deps = portage.dep_check (self.get_env_var("RDEPEND")+" "+self.get_env_var("DEPEND")+" "+self.get_env_var("PDEPEND"), vartree.dbapi, self._settings, myuse = actual) + + if not deps: # FIXME: what is the difference to [1, []] ? + return [] + + if deps[0] == 0: # error + raise DependencyCalcError, deps[1] + + deps = deps[1] + + for dep in deps: + if dep[0] == '!': # blocking sth + dep = dep[1:] + if dep != self.get_cp(): # not cpv, because a version might explicitly block another one + blocked = find_installed_packages(dep) + if blocked != []: + raise BlockedException, (self.get_cpv(), blocked[0].get_cpv()) + continue # finished with the blocking one -> next + + pkg = find_best_match(dep) + if not pkg: # try to find masked ones + list = find_packages(dep, masked = True) + if not list: + raise PackageNotFoundException, dep + + list = sort_package_list(list) + done = False + for i in range(len(list)-1,0,-1): + p = list[i] + if not p.is_masked(): + dep_pkgs.append(p.get_cpv()) + done = True + break + if not done: + dep_pkgs.append(list[-1].get_cpv()) + else: + dep_pkgs.append(pkg.get_cpv()) + + return dep_pkgs + + def get_cp (self): + """Returns the cp-string. + + @returns: category/package. + @rtype: string""" + + return self.get_category()+"/"+self.get_name() + + def matches (self, criterion): + """This checks, whether this package matches a specific verisioning criterion - e.g.: "<=net-im/foobar-1.2". + + @param criterion: the criterion to match against + @type criterion: string + @returns: True if matches; False if not + @rtype: boolean""" + + if portage.match_from_list(criterion, [self.get_cpv()]) == []: + return False + else: + return True diff --git a/portato/backend/portage_helper.py b/portato/backend/portage_helper.py new file mode 100644 index 0000000..6e8fc84 --- /dev/null +++ b/portato/backend/portage_helper.py @@ -0,0 +1,374 @@ +# -*- coding: utf-8 -*- +# +# File: portato/backend/portage_helper.py +# This file is part of the Portato-Project, a graphical portage-frontend. +# +# Copyright (C) 2006 René 'Necoro' Neumann +# 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 René 'Necoro' Neumann <necoro@necoro.net> + +import re, os, copy + +import portage, gentoolkit +from portage_util import unique_array + +from portato.backend import * +import package + +from portato.helper import debug + +def find_lambda (name): + """Returns the function needed by all the find_all_*-functions. Returns None if no name is given. + + @param name: name to build the function of + @type name: string + @returns: + 1. None if no name is given + 2. a lambda function + @rtype: function""" + + if name != None: + return lambda x: re.match(".*"+name+".*",x) + else: + return lambda x: True + +def geneticize_list (list_of_packages): + """Convertes a list of gentoolkit.Packages into L{backend.Package}s. + + @param list_of_packages: the list of packages + @type list_of_packages: list of gentoolkit.Packages + @returns: converted list + @rtype: backend.Package[]""" + + return [package.Package(x) for x in list_of_packages] + +def find_best_match (search_key, only_installed = False): + """Finds the best match in the portage tree. It does not find masked packages! + + @param search_key: the key to find in the portage tree + @type search_key: string + @param only_installed: if True, only installed packages are searched + @type only_installed: boolean + + @returns: the package found or None + @rtype: backend.Package""" + + t = None + if not only_installed: + t = porttree.dep_bestmatch(search_key) + else: + t = vartree.dep_bestmatch(search_key) + if t: + return package.Package(t) + return None + +def find_packages (search_key, masked=False): + """This returns a list of packages which have to fit exactly. Additionally ranges like '<,>,=,~,!' et. al. are possible. + + @param search_key: the key to look for + @type search_key: string + @param masked: if True, also look for masked packages + @type masked: boolean + + @returns: list of found packages + @rtype: backend.Package[]""" + + return geneticize_list(gentoolkit.find_packages(search_key, masked)) + +def find_installed_packages (search_key, masked=False): + """This returns a list of packages which have to fit exactly. Additionally ranges like '<,>,=,~,!' et. al. are possible. + + @param search_key: the key to look for + @type search_key: string + @param masked: if True, also look for masked packages + @type masked: boolean + + @returns: list of found packages + @rtype: backend.Package[]""" + + return geneticize_list(gentoolkit.find_installed_packages(search_key, masked)) + +def find_system_packages (): + """Looks for all packages saved as "system-packages". + + @returns: a tuple of (resolved_packages, unresolved_packages). + @rtype: (backend.Package[], backend.Package[])""" + + list = gentoolkit.find_system_packages() + return (geneticize_list(list[0]), geneticize_list(list[1])) + +def find_world_packages (): + """Looks for all packages saved in the world-file. + + @returns: a tuple of (resolved_packages, unresolved_packages). + @rtype: (backend.Package[], backend.Package[])""" + + list = gentoolkit.find_world_packages() + return geneticize_list(list[0]),geneticize_list(list[1]) + +def find_all_installed_packages (name=None, withVersion=True): + """Finds all installed packages matching a name or all if no name is specified. + + @param name: the name to look for - it is expanded to .*name.* ; if None, all packages are returned + @type name: string or None + @param withVersion: if True version-specific packages are returned; else only the cat/package-strings a delivered + @type withVersion: boolean + + @returns: all packages/cp-strings found + @rtype: backend.Package[] or cp-string[]""" + + if withVersion: + return geneticize_list(gentoolkit.find_all_installed_packages(find_lambda(name))) + else: + t = vartree.dbapi.cp_all() + if name: + t = filter(find_lambda(name),t) + return t + +def find_all_uninstalled_packages (name=None): + """Finds all uninstalled packages matching a name or all if no name is specified. + + @param name: the name to look for - it is expanded to .*name.* ; if None, all packages are returned + @type name: string or None + @returns: all packages found + @rtype: backend.Package[]""" + + return geneticize_list(gentoolkit.find_all_uninstalled_packages(find_lambda(name))) + +def find_all_packages (name=None, withVersion=True): + """Finds all packages matching a name or all if no name is specified. + + @param name: the name to look for - it is expanded to .*name.* ; if None, all packages are returned + @type name: string or None + @param withVersion: if True version-specific packages are returned; else only the cat/package-strings a delivered + @type withVersion: boolean + + @returns: all packages/cp-strings found + @rtype: backend.Package[] or cp-string[]""" + + if (withVersion): + return geneticize_list(gentoolkit.find_all_packages(find_lambda(name))) + else: + t = porttree.dbapi.cp_all() + t += vartree.dbapi.cp_all() + t = unique_array(t) + if name: + t = filter(find_lambda(name),t) + return t + +def find_all_world_packages (name=None): + """Finds all world packages matching a name or all if no name is specified. + + @param name: the name to look for - it is expanded to .*name.* ; if None, all packages are returned + @type name: string or None + @returns: all packages found + @rtype: backend.Package[]""" + + world = filter(find_lambda(name), [x.get_cpv() for x in find_world_packages()[0]]) + world = unique_array(world) + return geneticize_list(world) + +def find_all_system_packages (name=None): + """Finds all system packages matching a name or all if no name is specified. + + @param name: the name to look for - it is expanded to .*name.* ; if None, all packages are returned + @type name: string or None + @returns: all packages found + @rtype: backend.Package[]""" + + sys = filter(find_lambda(name), [x.get_cpv() for x in find_system_packages()[0]]) + sys = unique_array(sys) + return geneticize_list(sys) + +def get_all_versions (cp): + """Returns all versions of a certain package. + + @param cp: the package + @type cp: string (cat/pkg) + @returns: the list of found packages + @rtype: backend.Package[]""" + + t = porttree.dbapi.cp_list(cp) + t += vartree.dbapi.cp_list(cp) + t = unique_array(t) + return geneticize_list(t) + +def get_all_installed_versions (cp): + """Returns all installed versions of a certain package. + + @param cp: the package + @type cp: string (cat/pkg) + @returns: the list of found packages + @rtype: backend.Package[]""" + + return geneticize_list(vartree.dbapi.cp_list(cp)) + +def list_categories (name=None): + """Finds all categories matching a name or all if no name is specified. + + @param name: the name to look for - it is expanded to .*name.* ; if None, all categories are returned + @type name: string or None + @returns: all categories found + @rtype: string[]""" + + categories = gentoolkit.settings.categories + return filter(find_lambda(name), categories) + +def split_package_name (name): + """Splits a package name in its elements. + + @param name: name to split + @type name: string + @returns: list: [category, name, version, rev] whereby rev is "r0" if not specified in the name + @rtype: string[]""" + + return gentoolkit.split_package_name(name) + +def sort_package_list(pkglist): + """Sorts a package list in the same manner portage does. + + @param pkglist: list to sort + @type pkglist: Packages[]""" + + return gentoolkit.sort_package_list(pkglist) + +def reload_settings (): + """Reloads portage.""" + gentoolkit.settings = portage.config(config_incrementals = copy.deepcopy(gentoolkit.settings.incrementals)) + +def update_world (newuse = False, deep = False): + """Calculates the packages to get updated in an update world. + + @param newuse: Checks if a use-flag has a different state then to install time. + @type newuse: boolean + @param deep: Not only check world packages but also there dependencies. + @type deep: boolean + @returns: a list containing of the tuple (new_package, old_package) + @rtype: (backend.Package, backend.Package)[]""" + + # read world file + world = open(portage.WORLD_FILE) + packages = [] + for line in world: + line = line.strip() + if not len(line): continue # empty line + if line[0] == "#": continue + packages.append(line) + world.close() + + sys = gentoolkit.settings.packages + for x in sys: + if x[0] == "*": + x = x[1:] + packages.append(x.strip()) + + # Remove everything that is package.provided from our list + # This is copied from emerge.getlist() + for atom in packages[:]: + for expanded_atom in portage.flatten(portage.dep_virtual([atom], gentoolkit.settings)): + mykey = portage.dep_getkey(expanded_atom) + if mykey in gentoolkit.settings.pprovideddict and portage.match_from_list(expanded_atom, settings.pprovideddict[mykey]): + packages.remove(atom) + break + + packages = [find_best_match(x) for x in packages] + + checked = [] + updating = [] + raw_checked = [] + def check (p): + """Checks whether a package is updated or not.""" + if p.get_cp() in checked: return + else: checked.append(p.get_cp()) + + appended = False + tempDeep = False + + if not p.is_installed(): + oldList = find_installed_packages(p.get_cp()) + if oldList: + old = oldList[0] # assume we have only one there; FIXME: slotted packages + else: + debug("Bug? Not found installed one:",p.get_cp()) + return + updating.append((p, old)) + appended = True + p = old + + if newuse: + old = p.get_installed_use_flags() + new = p.get_settings("USE").split() + + for u in p.get_all_use_flags(): + if (u in new) != (u in old): + if not appended: + updating.append((p,p)) + tempDeep = True + + if deep or tempDeep: + for i in p.get_matched_dep_packages(): + if i not in raw_checked: + raw_checked.append(i) + bm = find_best_match(i) + if not bm: + debug("Bug? No best match could be found:",i) + else: + check(bm) + + for p in packages: + if not p: continue # if a masked package is installed we have "None" here + check(p) + + return updating + +use_descs = {} +local_use_descs = {} +def get_use_desc (flag, package = None): + """Returns the description of a specific useflag or None if no desc was found. + If a package is given (in the <cat>/<name> format) the local use descriptions are searched too. + + @param flag: flag to get the description for + @type flag: string + @param package: name of a package: if given local use descriptions are searched too + @type package: cp-string + @returns: found description + @rtype: string""" + + # In the first run the dictionaries 'use_descs' and 'local_use_descs' are filled. + + # fill cache if needed + if use_descs == {} or local_use_descs == {}: + # read use.desc + fd = open(gentoolkit.settings["PORTDIR"]+"/profiles/use.desc") + for line in fd.readlines(): + line = line.strip() + if line != "" and line[0] != '#': + fields = [x.strip() for x in line.split(" - ",1)] + if len(fields) == 2: + use_descs[fields[0]] = fields[1] + + # read use.local.desc + fd = open(gentoolkit.settings["PORTDIR"]+"/profiles/use.local.desc") + for line in fd.readlines(): + line = line.strip() + if line != "" and line[0] != '#': + fields = [x.strip() for x in line.split(":",1)] + if len(fields) == 2: + if not fields[0] in local_use_descs: # create + local_use_descs[fields[0]] = {} + subfields = [x.strip() for x in fields[1].split(" - ",1)] + if len(subfields) == 2: + local_use_descs[fields[0]][subfields[0]] = subfields[1] + + # start + desc = None + if flag in use_descs: + desc = use_descs[flag] + if package != None: + if package in local_use_descs: + if flag in local_use_descs[package]: + desc = local_use_descs[package][flag] + return desc diff --git a/portato/config_parser.py b/portato/config_parser.py new file mode 100644 index 0000000..8418a6a --- /dev/null +++ b/portato/config_parser.py @@ -0,0 +1,274 @@ +# -*- coding: utf-8 -*- +# +# File: portato/config_parser.py +# This file is part of the Portato-Project, a graphical portage-frontend. +# +# Copyright (C) 2006 René 'Necoro' Neumann +# 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 René 'Necoro' Neumann <necoro@necoro.net> + +from helper import debug + +import re +import types + +DELIMITER = ["=", ":"] +COMMENT = [";","#"] + +# precompiled expressions +TRUE = re.compile("((true)|(1)|(on)|(wahr)|(ja)|(yes))", re.I) +FALSE = re.compile("((false)|(0)|(off)|(falsch)|(nein)|(no))", re.I) +SECTION = re.compile("\s*\[(\w+)\]\s*") +EXPRESSION = re.compile(r"\s*(\w+)\s*[:=]\s*(.*)\s*") + +class Value (object): + """Class defining a value of a key.""" + + def __init__ (self, value, line, bool = None): + """Constructor. + + @param value: the value + @type value: string + @param line: the line in the config file + @type line: int + @param bool: boolean meaning of the value + @type bool: boolean""" + + self.__value = value + self.line = line + self.boolean = bool + + self.changed = False # true if we changed it + self.old = value # keep the original one ... so if we change it back to this one, we do not have to write + + def set (self, value): + """Sets the value to a new one. + + @param value: new value + @type value: string""" + + self.__value = value + + if value != self.old: + self.changed = True + else: + self.changed = False + + def get (self): + """Returns the actual value. + + @returns: the actual value + @rtype: string""" + + return self.__value + + def is_bool (self): + """Returns whether the actual value has a boolean meaning. + + @returns: True if the actual value can be interpreted as a boolean + @rtype: boolean""" + + return (self.boolean != None) + + def __str__ (self): + return str(self.__value) + + def __repr__ (self): + return self.__str__() + + value = property(get,set) + +class ConfigParser: + """The newly implemented Config-Parser.""" + + # generates the complementary true-false-pairs + true_false = { + "true" : "false", + "1" : "0", + "on" : "off", + "yes" : "no", + "ja" : "nein", + "wahr" : "falsch"} + true_false.update(zip(true_false.values(), true_false.keys())) + + def __init__ (self, file): + """Constructor. + + @param file: the configuration file to open + @type file: string""" + + self.file = file + self.__initialize() + + def __initialize (self): + """Private method which initializes our dictionaries.""" + + self.vars = {"MAIN": {}} + self.cache = None # file cache + self.pos = {} # stores the positions of the matches + + def _invert (self, val): + """Invertes a given boolean. + + @param val: value to invert + @type val: string + @returns: inverted value + @rtype: string""" + + return self.true_false[val.lower()] + + def parse (self): + """Parses the file.""" + + # read into cache + file = open(self.file, "r") + self.cache = file.readlines() + file.close() + + # implicit first section is main + section = "MAIN" + count = -1 + for line in self.cache: + count += 1 + + ls = line.strip() + if not ls: continue # empty + if ls[0] in COMMENT: continue # comment + + # look for a section + match = SECTION.search(line) + if match: + sec = match.group(1).upper() + if sec != section: + self.vars[sec] = {} + section = sec + continue + + # look for an expression + match = EXPRESSION.search(line) + if match: + val = match.group(2) + + # find the boolean value + bool = None + if TRUE.match(val): + bool = True + elif FALSE.match(val): + bool = False + + # insert + key = match.group(1).lower() + self.vars[section][key] = Value(val, count, bool = bool) + self.pos[count] = match.span(2) + else: # neither comment nor empty nor expression nor section => error + debug("Unrecognized line:",line) + + def get (self, key, section = "MAIN"): + """Returns the value of a given key in a section. + + @param key: the key + @type key: string + @param section: the section + @type section: string + + @returns: value + @rtype: string + + @raises KeyError: if section or key could not be found""" + + section = section.upper() + key = key.lower() + return self.vars[section][key].value + + def get_boolean (self, key, section = "MAIN"): + """Returns the boolean value of a given key in a section. + + @param key: the key + @type key: string + @param section: the section + @type section: string + + @returns: value + @rtype: boolean + + @raises KeyError: if section or key could not be found + @raises ValueError: if key does not have a boolean value""" + + section = section.upper() + key = key.lower() + + val = self.vars[section][key] + + if val.is_bool(): + return val.boolean + + raise ValueError, "\"%s\" is not a boolean." % key + + def set (self, key, value = "", section = "MAIN"): + """Sets a new value of a given key in a section. + + @param key: the key + @type key: string + @param value: the new value + @type value: string + @param section: the section + @type section: string + + @raises KeyError: if section or key could not be found""" + + section = section.upper() + key = key.lower() + + self.vars[section][key].value = value + + def set_boolean (self, key, value, section = "MAIN"): + """Sets a new boolean value of a given key in a section. + Therefore it invertes the string representation of the boolean (in lowercase). + + @param key: the key + @type key: string + @param value: the new value + @type value: boolean + @param section: the section + @type section: string + + @raises KeyError: if section or key could not be found + @raises ValueError: if the old/new value is not a boolean""" + + section = section.upper() + key = key.lower() + + if not isinstance(value, types.BooleanType): + raise ValueError, "Passed value must be a boolean." + + val = self.vars[section][key] + if val.is_bool(): + if value is not val.boolean: + val.boolean = value + val.value = self._invert(val.value) + else: + raise ValueError, "\"%s\" is not a boolean." % key + + def write (self): + """Writes file.""" + + for sec in self.vars: + for key in self.vars[sec]: + val = self.vars[sec][key] + if val.changed: + part1 = self.cache[val.line][:self.pos[val.line][0]] # key+DELIMITER + part2 = val.value # value + part3 = self.cache[val.line][self.pos[val.line][1]:] # everything behind the vale (\n in normal cases) + self.cache[val.line] = part1 + part2 + part3 + + # write + f = open(self.file, "w") + f.writelines(self.cache) + f.close() + + # reload + self.__initialize() + self.parse() diff --git a/portato/constants.py b/portato/constants.py new file mode 100644 index 0000000..9424036 --- /dev/null +++ b/portato/constants.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# +# File: portato/constants.py +# This file is part of the Portato-Project, a graphical portage-frontend. +# +# Copyright (C) 2006 René 'Necoro' Neumann +# 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 René 'Necoro' Neumann <necoro@necoro.net> + +CONFIG_DIR = "/etc/portato/" +CONFIG_LOCATION = CONFIG_DIR+"portato.cfg" + +DATA_DIR = "portato/gui/gtk/glade/" +VERSION = 9999 diff --git a/portato/gui/__init__.py b/portato/gui/__init__.py new file mode 100644 index 0000000..6248ece --- /dev/null +++ b/portato/gui/__init__.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +# +# File: portato/gui/__init__.py +# This file is part of the Portato-Project, a graphical portage-frontend. +# +# Copyright (C) 2006 René 'Necoro' Neumann +# 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 René 'Necoro' Neumann <necoro@necoro.net> + +import gtk +from gtk import * diff --git a/portato/gui/gtk/__init__.py b/portato/gui/gtk/__init__.py new file mode 100644 index 0000000..85938b4 --- /dev/null +++ b/portato/gui/gtk/__init__.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# +# File: portato/gui/gtk/__init__.py +# This file is part of the Portato-Project, a graphical portage-frontend. +# +# Copyright (C) 2006 René 'Necoro' Neumann +# 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 René 'Necoro' Neumann <necoro@necoro.net> + +from windows import MainWindow diff --git a/portato/gui/gtk/dialogs.py b/portato/gui/gtk/dialogs.py new file mode 100644 index 0000000..68cd629 --- /dev/null +++ b/portato/gui/gtk/dialogs.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +# +# File: portato/gui/gtk/dialogs.py +# This file is part of the Portato-Project, a graphical portage-frontend. +# +# Copyright (C) 2006 René 'Necoro' Neumann +# 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 René 'Necoro' Neumann <necoro@necoro.net> + +import gtk + +def io_ex_dialog (io_ex): + string = io_ex.strerror + if io_ex.filename: + string = string+": "+io_ex.filename + + dialog = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, string) + ret = dialog.run() + dialog.destroy() + return ret + +def blocked_dialog (blocked, blocks): + dialog = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, blocked+" is blocked by "+blocks+".\nPlease unmerge the blocking package.") + ret = dialog.run() + dialog.destroy() + return ret + +def not_root_dialog (): + errorMB = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, "You are not root.") + ret = errorMB.run() + errorMB.destroy() + return ret + +def unmask_dialog (cpv): + dialog = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, cpv+" seems to be masked.\nDo you want to unmask it and its dependencies?.\n") + ret = dialog.run() + dialog.destroy() + return ret + +def nothing_found_dialog (): + dialog = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO, gtk.BUTTONS_OK, "Package not found!") + ret = dialog.run() + dialog.destroy() + return ret + +def changed_flags_dialog (what = "flags"): + hintMB = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO, gtk.BUTTONS_OK, + "You have changed %s. Portato will write these changes into the appropriate files. Please backup them if you think it is necessairy." % what) + ret = hintMB.run() + hintMB.destroy() + return ret + +def remove_deps_dialog (): + infoMB = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO, gtk.BUTTONS_OK, "You cannot remove dependencies. :)") + ret = infoMB.run() + infoMB.destroy() + return ret + +def remove_queue_dialog (): + askMB = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, "Do you really want to clear the whole queue?") + ret = askMB.run() + askMB.destroy() + return ret diff --git a/portato/gui/gtk/glade/geneticone.glade b/portato/gui/gtk/glade/geneticone.glade new file mode 100644 index 0000000..cf187c7 --- /dev/null +++ b/portato/gui/gtk/glade/geneticone.glade @@ -0,0 +1,1028 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd"> +<!--*- mode: xml -*--> +<glade-interface> + <widget class="GtkWindow" id="MainWindow"> + <property name="border_width">2</property> + <property name="window_position">GTK_WIN_POS_CENTER</property> + <signal name="destroy" handler="cb_destroy"/> + <child> + <widget class="GtkVBox" id="mainVB"> + <property name="visible">True</property> + <child> + <widget class="GtkMenuBar" id="menubar"> + <property name="visible">True</property> + <child> + <widget class="GtkMenuItem" id="fileMenu"> + <property name="visible">True</property> + <property name="label" translatable="yes">_File</property> + <property name="use_underline">True</property> + <child> + <widget class="GtkMenu" id="menu1"> + <property name="visible">True</property> + <child> + <widget class="GtkMenuItem" id="prefItem"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Preferences</property> + <property name="use_underline">True</property> + <signal name="activate" handler="cb_preferences_clicked"/> + </widget> + </child> + <child> + <widget class="GtkMenuItem" id="reloadItem"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Reload Portage</property> + <property name="use_underline">True</property> + <signal name="activate" handler="cb_reload_clicked"/> + </widget> + </child> + <child> + <widget class="GtkSeparatorMenuItem" id="separatormenuitem1"> + <property name="visible">True</property> + </widget> + </child> + <child> + <widget class="GtkMenuItem" id="closeItem"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Close</property> + <property name="use_underline">True</property> + <signal name="activate" handler="cb_destroy"/> + </widget> + </child> + </widget> + </child> + </widget> + </child> + <child> + <widget class="GtkMenuItem" id="emergeMenu"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Emerge</property> + <property name="use_underline">True</property> + <child> + <widget class="GtkMenu" id="menu2"> + <property name="visible">True</property> + <child> + <widget class="GtkMenuItem" id="emergeItem"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Emerge</property> + <property name="use_underline">True</property> + <signal name="activate" handler="cb_emerge_clicked"/> + </widget> + </child> + <child> + <widget class="GtkMenuItem" id="unmergeItem"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Unmerge</property> + <property name="use_underline">True</property> + <signal name="activate" handler="cb_unmerge_clicked"/> + </widget> + </child> + <child> + <widget class="GtkMenuItem" id="updateItem"> + <property name="visible">True</property> + <property name="label" translatable="yes">Update _World</property> + <property name="use_underline">True</property> + <signal name="activate" handler="cb_update_clicked"/> + </widget> + </child> + <child> + <widget class="GtkMenuItem" id="syncItem"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Sync</property> + <property name="use_underline">True</property> + <signal name="activate" handler="cb_sync_clicked"/> + </widget> + </child> + <child> + <widget class="GtkMenuItem" id="saveFlagsItem"> + <property name="visible">True</property> + <property name="label" translatable="yes">Save _Flags</property> + <property name="use_underline">True</property> + <signal name="activate" handler="cb_save_flags_clicked"/> + </widget> + </child> + </widget> + </child> + </widget> + </child> + <child> + <widget class="GtkMenuItem" id="helpMenu"> + <property name="visible">True</property> + <property name="label" translatable="yes">_?</property> + <property name="use_underline">True</property> + <child> + <widget class="GtkMenu" id="menu3"> + <property name="visible">True</property> + <child> + <widget class="GtkMenuItem" id="aboutItem"> + <property name="visible">True</property> + <property name="label" translatable="yes">_About</property> + <property name="use_underline">True</property> + <signal name="activate" handler="cb_about_clicked"/> + </widget> + </child> + </widget> + </child> + </widget> + </child> + </widget> + <packing> + <property name="expand">False</property> + </packing> + </child> + <child> + <widget class="GtkHBox" id="searchHB"> + <property name="visible">True</property> + <property name="border_width">3</property> + <child> + <widget class="GtkEntry" id="searchEntry"> + <property name="visible">True</property> + <signal name="activate" handler="cb_search_clicked" object="searchEntry"/> + </widget> + <packing> + <property name="padding">5</property> + </packing> + </child> + <child> + <widget class="GtkButton" id="searchButton"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Search</property> + <property name="use_underline">True</property> + <signal name="clicked" handler="cb_search_clicked" object="searchEntry"/> + </widget> + <packing> + <property name="expand">False</property> + <property name="position">1</property> + </packing> + </child> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + <child> + <widget class="GtkVPaned" id="vpaned"> + <property name="visible">True</property> + <property name="position">300</property> + <child> + <widget class="GtkFrame" id="listFrame"> + <property name="visible">True</property> + <property name="label_xalign">0</property> + <property name="label_yalign">0</property> + <property name="shadow_type">GTK_SHADOW_IN</property> + <child> + <widget class="GtkHBox" id="listHB"> + <property name="visible">True</property> + <property name="spacing">5</property> + <property name="homogeneous">True</property> + <child> + <widget class="GtkScrolledWindow" id="catScroll"> + <property name="visible">True</property> + <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property> + <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property> + <child> + <widget class="GtkTreeView" id="catList"> + <property name="visible">True</property> + <property name="search_column">0</property> + <signal name="cursor_changed" handler="cb_cat_list_selection"/> + </widget> + </child> + </widget> + </child> + <child> + <widget class="GtkScrolledWindow" id="pkgScroll"> + <property name="visible">True</property> + <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property> + <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property> + <child> + <widget class="GtkTreeView" id="pkgList"> + <property name="visible">True</property> + <property name="search_column">0</property> + <signal name="cursor_changed" handler="cb_pkg_list_selection"/> + </widget> + </child> + </widget> + <packing> + <property name="position">1</property> + </packing> + </child> + </widget> + </child> + <child> + <placeholder/> + <packing> + <property name="type">label_item</property> + </packing> + </child> + </widget> + <packing> + <property name="resize">False</property> + </packing> + </child> + <child> + <widget class="GtkNotebook" id="notebook"> + <property name="visible">True</property> + <property name="homogeneous">True</property> + <child> + <widget class="GtkTable" id="PackageTable"> + <property name="visible">True</property> + <property name="n_rows">4</property> + <property name="n_columns">2</property> + <child> + <widget class="GtkHBox" id="checkHB"> + <property name="visible">True</property> + <property name="spacing">1</property> + <property name="homogeneous">True</property> + <child> + <widget class="GtkCheckButton" id="installedCheck"> + <property name="visible">True</property> + <property name="no_show_all">True</property> + <property name="label" translatable="yes">Installed</property> + <property name="draw_indicator">True</property> + <signal name="button_press_event" handler="cb_button_pressed"/> + </widget> + <packing> + <property name="fill">False</property> + </packing> + </child> + <child> + <widget class="GtkCheckButton" id="maskedCheck"> + <property name="visible">True</property> + <property name="no_show_all">True</property> + <property name="label" translatable="yes">Masked</property> + <property name="draw_indicator">True</property> + <signal name="toggled" handler="cb_masked_toggled"/> + </widget> + <packing> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + <child> + <widget class="GtkCheckButton" id="testingCheck"> + <property name="visible">True</property> + <property name="no_show_all">True</property> + <property name="label" translatable="yes">Testing</property> + <property name="draw_indicator">True</property> + <signal name="toggled" handler="cb_testing_toggled"/> + </widget> + <packing> + <property name="fill">False</property> + <property name="position">2</property> + </packing> + </child> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="y_options">GTK_FILL</property> + </packing> + </child> + <child> + <widget class="GtkHButtonBox" id="buttonBB"> + <property name="visible">True</property> + <property name="border_width">5</property> + <property name="layout_style">GTK_BUTTONBOX_SPREAD</property> + <child> + <widget class="GtkButton" id="pkgEmergeBtn"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Emerge</property> + <property name="use_underline">True</property> + <signal name="clicked" handler="cb_package_emerge_clicked"/> + </widget> + </child> + <child> + <widget class="GtkButton" id="pkgUnmergeBtn"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Unmerge</property> + <property name="use_underline">True</property> + <signal name="clicked" handler="cb_package_unmerge_clicked"/> + </widget> + <packing> + <property name="position">1</property> + </packing> + </child> + <child> + <widget class="GtkButton" id="pkgRevertBtn"> + <property name="visible">True</property> + <property name="label" translatable="yes">Re_vert</property> + <property name="use_underline">True</property> + <signal name="clicked" handler="cb_package_revert_clicked"/> + </widget> + <packing> + <property name="position">2</property> + </packing> + </child> + </widget> + <packing> + <property name="right_attach">2</property> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> + <property name="y_options"></property> + </packing> + </child> + <child> + <widget class="GtkLabel" id="notInSysLabel"> + <property name="visible">True</property> + <property name="no_show_all">True</property> + <property name="label" translatable="yes"><b>Installed, but not in portage anymore</b></property> + <property name="use_markup">True</property> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="y_options">GTK_FILL</property> + </packing> + </child> + <child> + <widget class="GtkLabel" id="missingLabel"> + <property name="visible">True</property> + <property name="no_show_all">True</property> + <property name="label" translatable="yes"><span foreground='red'><b>MISSING KEYWORD</b></span></property> + <property name="use_markup">True</property> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="y_options">GTK_FILL</property> + </packing> + </child> + <child> + <widget class="GtkLabel" id="descLabel"> + <property name="visible">True</property> + <property name="justify">GTK_JUSTIFY_CENTER</property> + <property name="wrap">True</property> + </widget> + <packing> + <property name="right_attach">2</property> + <property name="x_options">GTK_FILL</property> + <property name="y_options"></property> + <property name="y_padding">10</property> + </packing> + </child> + <child> + <widget class="GtkVBox" id="comboVB"> + <property name="visible">True</property> + <child> + <placeholder/> + </child> + </widget> + <packing> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="y_options">GTK_FILL</property> + <property name="x_padding">5</property> + </packing> + </child> + <child> + <widget class="GtkScrolledWindow" id="useListScroll"> + <property name="visible">True</property> + <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property> + <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property> + <child> + <widget class="GtkTreeView" id="useList"> + <property name="visible">True</property> + </widget> + </child> + </widget> + <packing> + <property name="right_attach">2</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + <property name="x_padding">5</property> + <property name="y_padding">5</property> + </packing> + </child> + </widget> + <packing> + <property name="tab_expand">False</property> + <property name="tab_fill">False</property> + </packing> + </child> + <child> + <widget class="GtkLabel" id="label"> + <property name="visible">True</property> + <property name="label" translatable="yes">Package</property> + </widget> + <packing> + <property name="type">tab</property> + <property name="tab_expand">False</property> + <property name="tab_fill">False</property> + </packing> + </child> + <child> + <widget class="GtkHBox" id="queueHB"> + <property name="visible">True</property> + <child> + <widget class="GtkScrolledWindow" id="queueScroll"> + <property name="visible">True</property> + <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property> + <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property> + <child> + <widget class="GtkTreeView" id="queueList"> + <property name="visible">True</property> + <property name="headers_visible">False</property> + <property name="enable_search">False</property> + <signal name="button_press_event" handler="cb_queue_right_click"/> + <signal name="row_activated" handler="cb_row_activated"/> + </widget> + </child> + </widget> + </child> + <child> + <widget class="GtkVButtonBox" id="queueBB"> + <property name="visible">True</property> + <property name="homogeneous">True</property> + <property name="layout_style">GTK_BUTTONBOX_SPREAD</property> + <child> + <widget class="GtkButton" id="emergeBtn"> + <property name="visible">True</property> + <property name="label" translatable="yes">E_merge</property> + <property name="use_underline">True</property> + <signal name="clicked" handler="cb_emerge_clicked"/> + </widget> + </child> + <child> + <widget class="GtkButton" id="unmergeBtn"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Unmerge</property> + <property name="use_underline">True</property> + <signal name="clicked" handler="cb_unmerge_clicked"/> + </widget> + <packing> + <property name="position">1</property> + </packing> + </child> + <child> + <widget class="GtkButton" id="updateBtn"> + <property name="visible">True</property> + <property name="label" translatable="yes">Update _World</property> + <property name="use_underline">True</property> + <signal name="clicked" handler="cb_update_clicked"/> + </widget> + <packing> + <property name="position">2</property> + </packing> + </child> + <child> + <widget class="GtkButton" id="removeBtn"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Remove</property> + <property name="use_underline">True</property> + <signal name="clicked" handler="cb_remove_clicked"/> + </widget> + <packing> + <property name="position">3</property> + </packing> + </child> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="padding">5</property> + <property name="position">1</property> + </packing> + </child> + </widget> + <packing> + <property name="position">1</property> + <property name="tab_expand">False</property> + </packing> + </child> + <child> + <widget class="GtkLabel" id="label2"> + <property name="visible">True</property> + <property name="label" translatable="yes">Queue</property> + <property name="single_line_mode">True</property> + </widget> + <packing> + <property name="type">tab</property> + <property name="position">1</property> + <property name="tab_expand">False</property> + <property name="tab_fill">False</property> + </packing> + </child> + <child> + <widget class="GtkHBox" id="termHB"> + <property name="visible">True</property> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + </widget> + <packing> + <property name="position">2</property> + <property name="tab_expand">False</property> + </packing> + </child> + <child> + <widget class="GtkLabel" id="label3"> + <property name="visible">True</property> + <property name="label" translatable="yes">Console</property> + <property name="single_line_mode">True</property> + </widget> + <packing> + <property name="type">tab</property> + <property name="position">2</property> + <property name="tab_expand">False</property> + <property name="tab_fill">False</property> + </packing> + </child> + </widget> + </child> + </widget> + <packing> + <property name="position">2</property> + </packing> + </child> + <child> + <widget class="GtkAlignment" id="alignment4"> + <property name="visible">True</property> + <property name="top_padding">5</property> + <child> + <widget class="GtkLabel" id="statusLabel"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="yalign">0</property> + <property name="label" translatable="yes">Portato - A Portage GUI</property> + <property name="single_line_mode">True</property> + </widget> + </child> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">3</property> + </packing> + </child> + </widget> + </child> + </widget> + <widget class="GtkWindow" id="AboutWindow"> + <property name="title" translatable="yes">About</property> + <property name="resizable">False</property> + <property name="modal">True</property> + <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property> + <property name="default_width">1</property> + <property name="default_height">1</property> + <property name="destroy_with_parent">True</property> + <property name="skip_taskbar_hint">True</property> + <child> + <widget class="GtkVBox" id="vbox1"> + <property name="visible">True</property> + <child> + <widget class="GtkLabel" id="aboutLabel"> + <property name="visible">True</property> + <property name="use_markup">True</property> + <property name="justify">GTK_JUSTIFY_CENTER</property> + <property name="wrap">True</property> + </widget> + </child> + <child> + <widget class="GtkButton" id="okBtn"> + <property name="visible">True</property> + <property name="label" translatable="yes">_OK</property> + <property name="use_underline">True</property> + <signal name="clicked" handler="close"/> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + </widget> + </child> + </widget> + <widget class="GtkWindow" id="SearchWindow"> + <property name="title" translatable="yes">Search</property> + <property name="resizable">False</property> + <property name="modal">True</property> + <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property> + <property name="default_width">1</property> + <property name="default_height">1</property> + <property name="destroy_with_parent">True</property> + <property name="skip_taskbar_hint">True</property> + <child> + <placeholder/> + </child> + </widget> + <widget class="GtkWindow" id="PreferenceWindow"> + <property name="border_width">5</property> + <property name="title" translatable="yes">Preferences</property> + <property name="modal">True</property> + <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property> + <property name="destroy_with_parent">True</property> + <property name="skip_taskbar_hint">True</property> + <child> + <widget class="GtkVBox" id="mainVB"> + <property name="visible">True</property> + <property name="spacing">5</property> + <child> + <widget class="GtkFrame" id="generalFrame"> + <property name="visible">True</property> + <property name="label_xalign">0</property> + <child> + <widget class="GtkAlignment" id="alignment1"> + <property name="visible">True</property> + <property name="left_padding">12</property> + <child> + <widget class="GtkCheckButton" id="debugCheck"> + <property name="visible">True</property> + <property name="label" translatable="yes">Debug</property> + <property name="draw_indicator">True</property> + </widget> + </child> + </widget> + </child> + <child> + <widget class="GtkLabel" id="label1"> + <property name="visible">True</property> + <property name="label" translatable="yes"><b>General Options</b></property> + <property name="use_markup">True</property> + </widget> + <packing> + <property name="type">label_item</property> + </packing> + </child> + </widget> + </child> + <child> + <widget class="GtkFrame" id="syncFrame"> + <property name="visible">True</property> + <property name="label_xalign">0</property> + <child> + <widget class="GtkAlignment" id="alignment5"> + <property name="visible">True</property> + <property name="bottom_padding">5</property> + <property name="left_padding">12</property> + <property name="right_padding">5</property> + <child> + <widget class="GtkHBox" id="syncHB"> + <property name="visible">True</property> + <child> + <widget class="GtkLabel" id="syncLabel"> + <property name="visible">True</property> + <property name="label" translatable="yes">Sync command: </property> + </widget> + <packing> + <property name="expand">False</property> + </packing> + </child> + <child> + <widget class="GtkEntry" id="syncCommandEdit"> + <property name="visible">True</property> + </widget> + <packing> + <property name="position">1</property> + </packing> + </child> + </widget> + </child> + </widget> + </child> + <child> + <widget class="GtkLabel" id="label6"> + <property name="visible">True</property> + <property name="label" translatable="yes"><b>Sync Options</b></property> + <property name="use_markup">True</property> + </widget> + <packing> + <property name="type">label_item</property> + </packing> + </child> + </widget> + <packing> + <property name="position">1</property> + </packing> + </child> + <child> + <widget class="GtkFrame" id="updateFrame"> + <property name="visible">True</property> + <property name="label_xalign">0</property> + <child> + <widget class="GtkAlignment" id="alignment2"> + <property name="visible">True</property> + <property name="left_padding">12</property> + <child> + <widget class="GtkVBox" id="updateVB"> + <property name="visible">True</property> + <child> + <widget class="GtkCheckButton" id="deepCheck"> + <property name="visible">True</property> + <property name="label" translatable="yes">--deep</property> + <property name="draw_indicator">True</property> + </widget> + <packing> + <property name="fill">False</property> + </packing> + </child> + <child> + <widget class="GtkCheckButton" id="newUseCheck"> + <property name="visible">True</property> + <property name="label" translatable="yes">--newuse</property> + <property name="draw_indicator">True</property> + </widget> + <packing> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + </widget> + </child> + </widget> + </child> + <child> + <widget class="GtkLabel" id="label4"> + <property name="visible">True</property> + <property name="label" translatable="yes"><b>Update World Options</b></property> + <property name="use_markup">True</property> + </widget> + <packing> + <property name="type">label_item</property> + </packing> + </child> + </widget> + <packing> + <property name="position">2</property> + </packing> + </child> + <child> + <widget class="GtkFrame" id="keywordFrame"> + <property name="visible">True</property> + <property name="label_xalign">0</property> + <child> + <widget class="GtkAlignment" id="alignment3"> + <property name="visible">True</property> + <property name="bottom_padding">5</property> + <property name="left_padding">12</property> + <property name="right_padding">5</property> + <child> + <widget class="GtkTable" id="keywordTable"> + <property name="visible">True</property> + <property name="n_rows">10</property> + <property name="n_columns">2</property> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <widget class="GtkEntry" id="useFileEdit"> + <property name="visible">True</property> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> + </packing> + </child> + <child> + <widget class="GtkLabel" id="useEditLabel"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">File name to use, if package.use is a directory: </property> + <property name="single_line_mode">True</property> + </widget> + <packing> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> + </packing> + </child> + <child> + <widget class="GtkCheckButton" id="usePerVersionCheck"> + <property name="visible">True</property> + <property name="label" translatable="yes">Add only exact version to package.use</property> + <property name="draw_indicator">True</property> + </widget> + <packing> + <property name="right_attach">2</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + </packing> + </child> + <child> + <widget class="GtkCheckButton" id="testPerVersionCheck"> + <property name="visible">True</property> + <property name="label" translatable="yes">Add only exact version to package.keywords</property> + <property name="draw_indicator">True</property> + </widget> + <packing> + <property name="right_attach">2</property> + <property name="top_attach">5</property> + <property name="bottom_attach">6</property> + </packing> + </child> + <child> + <widget class="GtkLabel" id="testEditLabel"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">File name to use, if package.keywords is a directory: </property> + <property name="single_line_mode">True</property> + </widget> + <packing> + <property name="top_attach">6</property> + <property name="bottom_attach">7</property> + </packing> + </child> + <child> + <widget class="GtkEntry" id="testFileEdit"> + <property name="visible">True</property> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">6</property> + <property name="bottom_attach">7</property> + </packing> + </child> + <child> + <widget class="GtkEntry" id="maskFileEdit"> + <property name="visible">True</property> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">9</property> + <property name="bottom_attach">10</property> + </packing> + </child> + <child> + <widget class="GtkLabel" id="maskEditLabel"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">File name to use, if package.mask/package.unmask is a directory: </property> + <property name="single_line_mode">True</property> + </widget> + <packing> + <property name="top_attach">9</property> + <property name="bottom_attach">10</property> + </packing> + </child> + <child> + <widget class="GtkCheckButton" id="maskPerVersionCheck"> + <property name="visible">True</property> + <property name="label" translatable="yes">Add only exact version to package.mask/package.unmask</property> + <property name="draw_indicator">True</property> + </widget> + <packing> + <property name="right_attach">2</property> + <property name="top_attach">8</property> + <property name="bottom_attach">9</property> + </packing> + </child> + <child> + <widget class="GtkEventBox" id="hintEB"> + <property name="visible">True</property> + <child> + <widget class="GtkFrame" id="hintFrame"> + <property name="visible">True</property> + <property name="label_xalign">0</property> + <property name="shadow_type">GTK_SHADOW_OUT</property> + <child> + <widget class="GtkLabel" id="hintLabel"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes"><u>You may use the following placeholders:</u> + +<i>$(cat)</i>: category +<i>$(pkg)</i>: package name +<i>$(cat-1)/$(cat-2)</i>: first/second part of the category</property> + <property name="use_markup">True</property> + </widget> + </child> + <child> + <placeholder/> + <packing> + <property name="type">label_item</property> + </packing> + </child> + </widget> + </child> + </widget> + <packing> + <property name="right_attach">2</property> + </packing> + </child> + <child> + <widget class="GtkLabel" id="useLabel"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="xpad">5</property> + <property name="label" translatable="yes"><u><i>Use-Flags</i></u></property> + <property name="use_markup">True</property> + <property name="single_line_mode">True</property> + </widget> + <packing> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="y_padding">6</property> + </packing> + </child> + <child> + <widget class="GtkLabel" id="testLabel"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="xpad">5</property> + <property name="label" translatable="yes"><u><i>Testing Keywords</i></u></property> + <property name="use_markup">True</property> + <property name="single_line_mode">True</property> + </widget> + <packing> + <property name="top_attach">4</property> + <property name="bottom_attach">5</property> + <property name="y_padding">5</property> + </packing> + </child> + <child> + <widget class="GtkLabel" id="maskLabel"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="xpad">5</property> + <property name="label" translatable="yes"><u><i>Masking Keywords</i></u></property> + <property name="use_markup">True</property> + <property name="single_line_mode">True</property> + </widget> + <packing> + <property name="top_attach">7</property> + <property name="bottom_attach">8</property> + <property name="y_padding">5</property> + </packing> + </child> + </widget> + </child> + </widget> + </child> + <child> + <widget class="GtkLabel" id="label5"> + <property name="visible">True</property> + <property name="label" translatable="yes"><b>Use Flag and Keyword Options</b></property> + <property name="use_markup">True</property> + </widget> + <packing> + <property name="type">label_item</property> + </packing> + </child> + </widget> + <packing> + <property name="position">3</property> + </packing> + </child> + <child> + <widget class="GtkHButtonBox" id="buttonBox"> + <property name="visible">True</property> + <property name="homogeneous">True</property> + <property name="layout_style">GTK_BUTTONBOX_SPREAD</property> + <child> + <widget class="GtkButton" id="okBtn"> + <property name="visible">True</property> + <property name="label" translatable="yes">_OK</property> + <property name="use_underline">True</property> + <signal name="clicked" handler="cb_ok_clicked"/> + </widget> + </child> + <child> + <widget class="GtkButton" id="cancelBtn"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Cancel</property> + <property name="use_underline">True</property> + <signal name="clicked" handler="cb_cancel_clicked"/> + </widget> + <packing> + <property name="position">1</property> + </packing> + </child> + </widget> + <packing> + <property name="position">4</property> + </packing> + </child> + </widget> + </child> + </widget> + <widget class="GtkMenu" id="queuePopup"> + <property name="visible">True</property> + <child> + <widget class="GtkMenuItem" id="oneShotItem"> + <property name="visible">True</property> + <property name="label" translatable="yes">oneshot</property> + <signal name="activate" handler="cb_oneshot_clicked"/> + </widget> + </child> + </widget> +</glade-interface> diff --git a/portato/gui/gtk/windows.py b/portato/gui/gtk/windows.py new file mode 100644 index 0000000..f4a37fe --- /dev/null +++ b/portato/gui/gtk/windows.py @@ -0,0 +1,892 @@ +# -*- coding: utf-8 -*- +# +# File: portato/gui/gtk/windows.py +# This file is part of the Portato-Project, a graphical portage-frontend. +# +# Copyright (C) 2006 René 'Necoro' Neumann +# 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 René 'Necoro' Neumann <necoro@necoro.net> + +# gtk stuff +import pygtk +pygtk.require("2.0") +import gtk +import gtk.glade +import gobject + +#our backend stuff +from portato.helper import * +from portato.constants import CONFIG_LOCATION, VERSION, DATA_DIR +from portato import backend +from portato.backend import flags +from portato.backend.exceptions import * + +# more GUI stuff +from portato.gui.gui_helper import Database, Config, EmergeQueue +from dialogs import * +from wrapper import GtkTree, GtkConsole + +# for the terminal +import vte + +# other +from portage_util import unique_array + +class Window: + def __init__ (self): + self.tree = gtk.glade.XML(DATA_DIR+"portato.glade", root = self.__class__.__name__) + self.tree.signal_autoconnect(self) + self.window = self.tree.get_widget(self.__class__.__name__) + + @staticmethod + def watch_cursor (func): + """This is a decorator for functions being so time consuming, that it is appropriate to show the watch-cursor. + @attention: this function relies on the gtk.Window-Object being stored as self.window""" + def wrapper (self, *args, **kwargs): + ret = None + def cb_idle(): + try: + ret = func(self, *args, **kwargs) + finally: + self.window.window.set_cursor(None) + return False + + watch = gtk.gdk.Cursor(gtk.gdk.WATCH) + self.window.window.set_cursor(watch) + gobject.idle_add(cb_idle) + return ret + return wrapper + +class AbstractDialog (Window): + """A class all our dialogs get derived from. It sets useful default vars and automatically handles the ESC-Button.""" + + def __init__ (self, parent): + """Constructor. + + @param parent: the parent window + @type parent: gtk.Window""" + + Window.__init__(self) + + # set parent + self.window.set_transient_for(parent) + + # catch the ESC-key + self.window.connect("key-press-event", self.cb_key_pressed) + + def cb_key_pressed (self, widget, event): + """Closes the window if ESC is pressed.""" + keyname = gtk.gdk.keyval_name(event.keyval) + if keyname == "Escape": + self.close() + return True + else: + return False + + def close (self, *args): + self.window.destroy() + +class AboutWindow (AbstractDialog): + """A window showing the "about"-informations.""" + + def __init__ (self, parent): + """Constructor. + + @param parent: the parent window + @type parent: gtk.Window""" + + AbstractDialog.__init__(self, parent) + + label = self.tree.get_widget("aboutLabel") + label.set_markup(""" +<big><b>Portato v.%s</b></big> +A Portage-GUI + +This software is licensed under the terms of the GPLv2. +Copyright (C) 2006 René 'Necoro' Neumann <necoro@necoro.net> + +<small>Thanks to Fred for support and ideas :P</small> +""" % VERSION) + + self.window.show_all() + +class SearchWindow (AbstractDialog): + """A window showing the results of a search process.""" + + def __init__ (self, parent, list, jump_to): + """Constructor. + + @param parent: parent-window + @type parent: gtk.Window + @param list: list of results to show + @type list: string[] + @param jump_to: function to call if "OK"-Button is hit + @type jump_to: function(string)""" + + AbstractDialog.__init__(self, parent) + + self.list = list # list to show + self.jump_to = jump_to # function to call for jumping + + # combo box + self.combo = gtk.combo_box_new_text() + for x in list: + self.combo.append_text(x) + self.combo.set_active(0) # first item + self.combo.connect("key-press-event", self.cb_key_pressed_combo) + + self.window.add(self.combo) + + # finished --> show + self.window.show_all() + + def cb_key_pressed_combo (self, widget, event): + """Emulates a ok-button-click.""" + keyname = gtk.gdk.keyval_name(event.keyval) + if keyname == "Return": # take it as an "OK" if Enter is pressed + self.window.destroy() + self.jump_to(self.list[self.combo.get_active()]) + return True + else: + return False + +class PreferenceWindow (AbstractDialog): + """Window displaying some preferences.""" + + # all checkboxes in the window + # widget name -> option name + checkboxes = { + "debugCheck" : "debug_opt", + "deepCheck" : "deep_opt", + "newUseCheck" : "newuse_opt", + "maskPerVersionCheck" : "maskPerVersion_opt", + "usePerVersionCheck" : "usePerVersion_opt", + "testPerVersionCheck" : "testingPerVersion_opt" + } + + # all edits in the window + # widget name -> option name + edits = { + "maskFileEdit" : "maskFile_opt", + "testFileEdit" : "testingFile_opt", + "useFileEdit" : "useFile_opt", + "syncCommandEdit" : "syncCmd_opt" + } + + def __init__ (self, parent, cfg): + """Constructor. + + @param parent: parent window + @type parent: gtk.Window + @param cfg: configuration object + @type cfg: gui_helper.Config""" + + AbstractDialog.__init__(self, parent) + + # our config + self.cfg = cfg + + # set the bg-color of the hint + hintEB = self.tree.get_widget("hintEB") + hintEB.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse("#f3f785")) + + for box in self.checkboxes: + self.tree.get_widget(box).\ + set_active(self.cfg.get_boolean(self.checkboxes[box])) + + for edit in self.edits: + self.tree.get_widget(edit).\ + set_text(self.cfg.get(self.edits[edit])) + + self.window.show_all() + + def _save(self): + """Sets all options in the Config-instance.""" + + for box in self.checkboxes: + self.cfg.set_boolean(self.checkboxes[box], self.tree.get_widget(box).get_active()) + + for edit in self.edits: + self.cfg.set(self.edits[edit],self.tree.get_widget(edit).get_text()) + + def cb_ok_clicked(self, button): + """Saves, writes to config-file and closes the window.""" + self._save() + try: + self.cfg.write() + except IOError, e: + io_ex_dialog(e) + + self.window.destroy() + + def cb_cancel_clicked (self, button): + """Just closes - w/o saving.""" + self.window.destroy() + +class PackageTable: + """A window with data about a specfic package.""" + + def __init__ (self, main): + """Build up window contents. + + @param main: the main window + @type main: MainWindow""" + + self.main = main + self.tree = main.tree + self.window = main.window + self.tree.signal_autoconnect(self) + + # the table + self.table = self.tree.get_widget("PackageTable") + + # chechboxes + self.installedCheck = self.tree.get_widget("installedCheck") + self.maskedCheck = self.tree.get_widget("maskedCheck") + self.testingCheck = self.tree.get_widget("testingCheck") + + # labels + self.notInSysLabel = self.tree.get_widget("notInSysLabel") + self.missingLabel = self.tree.get_widget("missingLabel") + + # buttons + self.emergeBtn = self.tree.get_widget("pkgEmergeBtn") + self.unmergeBtn = self.tree.get_widget("pkgUnmergeBtn") + self.cancelBtn = self.tree.get_widget("pkgCancelBtn") + + # useList + self.useListScroll = self.tree.get_widget("useListScroll") + self.useList = None + + def update (self, cp, queue = None, version = None, doEmerge = True, instantChange = False): + """Updates the table to show the contents for the package. + + @param cp: the selected package + @type cp: string (cp) + @param queue: emerge-queue (if None the emerge-buttons are disabled) + @type queue: EmergeQueue + @param version: if not None, specifies the version to select + @type version: string + @param doEmerge: if False, the emerge buttons are disabled + @type doEmerge: False + @param instantChange: if True the changed keywords are updated instantly + @type instantChange: boolean""" + + self.cp = cp # category/package + self.version = version # version - if not None this is used + self.queue = queue + self.doEmerge = doEmerge + self.instantChange = instantChange + + # packages and installed packages + self.packages = backend.sort_package_list(backend.get_all_versions(cp)) + self.instPackages = backend.sort_package_list(backend.get_all_installed_versions(cp)) + + # version-combo-box + self.vCombo = self.build_vers_combo() + if not self.doEmerge: self.vCombo.set_sensitive(False) + vb = self.tree.get_widget("comboVB") + children = vb.get_children() + if children: + for c in children: vb.remove(c) + vb.pack_start(self.vCombo) + + # the label (must be here, because it depends on the combo box) + desc = self.actual_package().get_env_var("DESCRIPTION").replace("&","&") + if not desc: + desc = "<no description>" + use_markup = False + else: + desc = "<b>"+desc+"</b>" + use_markup = True + desc = "<i><u>"+self.actual_package().get_cp()+"</u></i>\n\n"+desc + self.descLabel = self.tree.get_widget("descLabel") + self.descLabel.set_use_markup(use_markup) + self.descLabel.set_label(desc) + + if not self.queue or not self.doEmerge: + self.emergeBtn.set_sensitive(False) + self.unmergeBtn.set_sensitive(False) + + # current status + self.cb_combo_changed(self.vCombo) + self.table.show_all() + + def fill_use_list(self, store): + """Fills a given ListStore with the use-flag data. + + @param store: the store to fill + @type store: gtk.ListStore""" + + pkg = self.actual_package() + pkg_flags = pkg.get_all_use_flags() + pkg_flags.sort() + for use in pkg_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, backend.get_use_desc(use, self.cp)]) + + return store + + def build_use_list (self): + """Builds the useList.""" + store = gtk.ListStore(bool, str, str) + self.fill_use_list(store) + + # 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: # if there are no nodes in the list ... + view.set_child_visible(False) # ... do not show the list + else: + view.set_child_visible(True) + return view + + 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 = backend.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 + debug('catched AttributeError => no "best package" found. Selected first one.') + combo.set_active(0) + + combo.connect("changed", self.cb_combo_changed) + + return combo + + def actual_package (self): + """Returns the actual selected package. + + @returns: the actual selected package + @rtype: backend.Package""" + + return self.packages[self.vCombo.get_active()] + + def _update_keywords (self, emerge, update = False): + if emerge: + try: + try: + self.queue.append(self.actual_package().get_cpv(), unmerge = False, update = update) + except backend.PackageNotFoundException, e: + if unmask_dialog(e[0]) == gtk.RESPONSE_YES: + self.queue.append(self.actual_package().get_cpv(), unmerge = False, unmask = True, update = update) + except BlockedException, e: + blocked_dialog(e[0], e[1]) + else: + try: + self.queue.append(self.actual_package().get_cpv(), unmerge = True) + except backend.PackageNotFoundException, e: + masked_dialog(e[0]) + + def cb_combo_changed (self, combo): + """Callback for the changed ComboBox. + It then rebuilds the useList and the checkboxes.""" + + # remove old useList + w = self.useListScroll.get_child() + if w: + self.useListScroll.remove(w) + + # build new + self.useList = self.build_use_list() + self.useListScroll.add(self.useList) + pkg = self.actual_package() + + # + # rebuild the buttons and checkboxes in all the different manners which are possible + # + if (not pkg.is_in_system()) or pkg.is_missing_keyword(): + if not pkg.is_in_system(): + self.missingLabel.hide() + self.notInSysLabel.show() + else: # missing keyword + self.missingLabel.show() + self.notInSysLabel.hide() + + self.installedCheck.hide() + self.maskedCheck.hide() + self.testingCheck.hide() + self.emergeBtn.set_sensitive(False) + else: + self.missingLabel.hide() + self.notInSysLabel.hide() + self.installedCheck.show() + self.maskedCheck.show() + self.testingCheck.show() + if self.doEmerge: + self.emergeBtn.set_sensitive(True) + self.installedCheck.set_active(pkg.is_installed()) + self.maskedCheck.set_active(pkg.is_masked()) + if pkg.is_testing(allowed = False) and not pkg.is_testing(allowed=True): + self.testingCheck.set_label("<i>(Testing)</i>") + self.testingCheck.get_child().set_use_markup(True) + else: + self.testingCheck.set_label("Testing") + self.testingCheck.set_active(pkg.is_testing(allowed = False)) + + if self.doEmerge: + # 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) + + self.table.show_all() + + return True + + def cb_button_pressed (self, b, event): + """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_package_revert_clicked (self, button): + """Callback for pressed cancel-button. Closes the window.""" + self.actual_package().remove_new_use_flags() + self.actual_package().remove_new_masked() + self.actual_package().remove_new_testing() + self.cb_combo_changed(self.vCombo) + if self.instantChange: + self._update_keywords(True, update = True) + return True + + def cb_package_emerge_clicked (self, button): + """Callback for pressed emerge-button. Adds the package to the EmergeQueue.""" + if not am_i_root(): + not_root_dialog() + else: + self._update_keywords(True) + self.main.notebook.set_current_page(self.main.QUEUE_PAGE) + return True + + def cb_package_unmerge_clicked (self, button): + """Callback for pressed unmerge-button clicked. Adds the package to the UnmergeQueue.""" + if not am_i_root(): + not_root_dialog() + else: + self._update_keywords(False) + self.main.notebook.set_current_page(self.main.QUEUE_PAGE) + return True + + def cb_testing_toggled (self, button): + """Callback for toggled testing-checkbox.""" + status = button.get_active() + + if self.actual_package().is_testing(allowed = False) == status: + return False + + if not self.actual_package().is_testing(allowed = True): + self.actual_package().set_testing(False) + button.set_label("Testing") + button.set_active(True) + else: + self.actual_package().set_testing(True) + if self.actual_package().is_testing(allowed=False): + button.set_label("<i>(Testing)</i>") + button.get_child().set_use_markup(True) + button.set_active(True) + + if self.instantChange: + self._update_keywords(True, update = True) + + return True + + def cb_masked_toggled (self, button): + """Callback for toggled masking-checkbox.""" + status = button.get_active() + self.actual_package().set_masked(status) + + if self.instantChange: + self._update_keywords(True, update = True) + + return True + + def cb_use_flag_toggled (self, cell, path, store): + """Callback for a toggled use-flag button.""" + store[path][0] = not store[path][0] + prefix = "" + if not store[path][0]: + prefix = "-" + self.actual_package().set_use_flag(prefix+store[path][1]) + + if self.instantChange: + self._update_keywords(True, update = True) + + return True + +class MainWindow (Window): + """Application main window.""" + + # NOTEBOOK PAGE CONSTANTS + PKG_PAGE = 0 + QUEUE_PAGE = 1 + CONSOLE_PAGE = 2 + + def __init__ (self): + """Build up window""" + + # main window stuff + Window.__init__(self) + self.window.set_title(("Portato (%s)" % VERSION)) + mHeight = 800 + if gtk.gdk.screen_height() <= 800: mHeight = 600 + self.window.set_geometry_hints (self.window, min_width = 600, min_height = mHeight, max_height = gtk.gdk.screen_height(), max_width = gtk.gdk.screen_width()) + + # booleans + self.doUpdate = False + self.packageInit = True + + # package db + self.db = Database() + self.db.populate() + + # config + try: + self.cfg = Config(CONFIG_LOCATION) + except IOError, e: + io_ex_dialog(e) + raise e + + self.cfg.modify_external_configs() + + # set vpaned position + vpaned = self.tree.get_widget("vpaned") + vpaned.set_position(mHeight/2) + + # cat and pkg list + self.catList = self.tree.get_widget("catList") + self.pkgList = self.tree.get_widget("pkgList") + self.build_cat_list() + self.build_pkg_list() + + # queue list + self.queueList = self.tree.get_widget("queueList") + self.build_queue_list() + + # the terminal + term = vte.Terminal() + term.set_scrollback_lines(1024) + term.set_scroll_on_output(True) + term.set_font_from_string("Monospace 11") + # 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")) + termHB = self.tree.get_widget("termHB") + termScroll = gtk.VScrollbar(term.get_adjustment()) + termHB.pack_start(term, True, True) + termHB.pack_start(termScroll, False) + + # notebook + self.notebook = self.tree.get_widget("notebook") + self.window.show_all() + + # table + self.packageTable = PackageTable(self) + self.packageTable.table.hide_all() + + # popup + popupTree = gtk.glade.XML(DATA_DIR+"portato.glade", root = "queuePopup") + popupTree.signal_autoconnect(self) + self.queuePopup = popupTree.get_widget("queuePopup") + + # set emerge queue + self.queueTree = GtkTree(self.queueList.get_model()) + self.queue = EmergeQueue(console = GtkConsole(term), tree = self.queueTree, db = self.db) + + def show_package (self, *args, **kwargs): + self.packageTable.update(*args, **kwargs) + self.notebook.set_current_page(self.PKG_PAGE) + + def build_queue_list (self): + """Builds the queue list.""" + + store = gtk.TreeStore(str,str) + + self.queueList.set_model(store) + + cell = gtk.CellRendererText() + col = gtk.TreeViewColumn("Queue", cell, text = 0) + self.queueList.append_column(col) + + col = gtk.TreeViewColumn("Options", cell, markup = 1) + self.queueList.append_column(col) + + def build_cat_list (self): + """Builds the category list.""" + + store = gtk.ListStore(str) + + # build categories + for p in backend.list_categories(): + store.append([p]) + # sort them alphabetically + store.set_sort_column_id(0, gtk.SORT_ASCENDING) + + self.catList.set_model(store) + cell = gtk.CellRendererText() + col = gtk.TreeViewColumn("Categories", cell, text = 0) + self.catList.append_column(col) + + def build_pkg_list (self, name = None): + """Builds the package list. + + @param name: name of the selected catetegory + @type name: string""" + + store = gtk.ListStore(str) + self.fill_pkg_store(store,name) + + # build view + self.pkgList.set_model(store) + cell = gtk.CellRendererText() + col = gtk.TreeViewColumn("Packages", cell, text = 0) + self.pkgList.append_column(col) + + def fill_pkg_store (self, store, name = None): + """Fills a given ListStore with the packages in a category. + + @param store: the store to fill + @type store: gtk.ListStore + @param name: the name of the category + @type name: string + @returns: the filled store + @rtype: gtk.ListStore""" + + if name: + for p in self.db.get_cat(name): + store.append([p]) + return store + + def jump_to (self, cp): + """Is called when we want to jump to a specific package.""" + self.show_package(cp, self.queue) + + def cb_cat_list_selection (self, view): + """Callback for a category-list selection. Updates the package list with the packages in the category.""" + # get the selected category + sel = view.get_selection() + store, it = sel.get_selected() + if it: + self.selCatName = store.get_value(it, 0) + self.pkgList.get_model().clear() + self.fill_pkg_store(self.pkgList.get_model(), self.selCatName) + return True + + def cb_pkg_list_selection (self, view): + """Callback for a package-list selection. Updates the package info.""" + sel = view.get_selection() + store, it = sel.get_selected() + if it: + package = store.get_value(it, 0) + if package[-1] == '*': package = package[:-1] + self.show_package(self.selCatName+"/"+package, self.queue) + return True + + def cb_row_activated (self, view, path, *args): + """Callback for an activated row in the emergeQueue. Opens a package window.""" + store = self.queueTree + if len(path) > 1: + iterator = store.get_original().get_iter(path) + if store.is_in_emerge(iterator): + package = store.get_value(iterator, 0) + cat, name, vers, rev = backend.split_package_name(package) + if rev != "r0": vers = vers+"-"+rev + self.show_package(cat+"/"+name, queue = self.queue, version = vers, instantChange = True, doEmerge = False) + return True + + def cb_emerge_clicked (self, action): + """Do emerge.""" + + self.notebook.set_current_page(self.CONSOLE_PAGE) + + if len(flags.newUseFlags) > 0: + changed_flags_dialog("use flags") + flags.write_use_flags() + + if len(flags.new_masked)>0 or len(flags.new_unmasked)>0 or len(flags.newTesting)>0: + debug("new masked:",flags.new_masked) + debug("new unmasked:", flags.new_unmasked) + debug("new testing:", flags.newTesting) + changed_flags_dialog("masking keywords") + flags.write_masked() + flags.write_testing() + backend.reload_settings() + + if not self.doUpdate: + self.queue.emerge(force=True) + else: + self.queue.update_world(force=True, newuse = self.cfg.get_boolean("newuse_opt"), deep = self.cfg.get_boolean("deep_opt")) + self.doUpdate = False + + def cb_unmerge_clicked (self, button): + """Do unmerge.""" + + self.notebook.set_current_page(self.CONSOLE_PAGE) + self.queue.unmerge(force=True) + return True + + @Window.watch_cursor + def cb_update_clicked (self, action): + if not backend.am_i_root(): + not_root_dialog() + + else: + updating = backend.update_world(newuse = self.cfg.get_boolean("newuse_opt"), deep = self.cfg.get_boolean("deep_opt")) + + debug("updating list:", [(x.get_cpv(), y.get_cpv()) for x,y in updating]) + try: + for pkg, old_pkg in updating: + self.queue.append(pkg.get_cpv()) + except BlockedException, e: + blocked_dialog(e[0], e[1]) + if len(updating): self.doUpdate = True + return True + + def cb_remove_clicked (self, button): + """Removes a selected item in the (un)emerge-queue if possible.""" + selected = self.queueList.get_selection() + + if selected: + model, iter = selected.get_selected() + + if iter == None: return False + + if not model.iter_parent(iter): # top-level + if model.iter_n_children(iter) > 0: # and has children which can be removed :) + if remove_queue_dialog() == gtk.RESPONSE_YES : + self.queue.remove_children(iter) + self.doUpdate = False + + elif model.iter_parent(model.iter_parent(iter)): # this is in the 3rd level => dependency + remove_deps_dialog() + else: + self.queue.remove_children(iter) # remove children first + self.queue.remove(iter) + self.doUpdate = False + + return True + + def cb_sync_clicked (self, action): + if not backend.am_i_root(): + not_root_dialog() + else: + self.notebook.set_current_page(self.CONSOLE_PAGE) + cmd = self.cfg.get("syncCmd_opt") + + if cmd != "emerge --sync": + cmd = cmd.split() + self.queue.sync(cmd) + else: + self.queue.sync() + + def cb_save_flags_clicked (self, action): + if not backend.am_i_root(): + not_root_dialog() + else: + flags.write_use_flags() + flags.write_testing() + flags.write_masked() + + @Window.watch_cursor + def cb_reload_clicked (self, action): + """Reloads the portage settings and the database.""" + backend.reload_settings() + del self.db + self.db = Database() + self.db.populate() + + @Window.watch_cursor + def cb_search_clicked (self, entry): + """Do a search.""" + if entry.get_text() != "": + packages = backend.find_all_packages(entry.get_text(), withVersion = False) + + if packages == []: + nothing_found_dialog() + else: + if len(packages) == 1: + self.jump_to(packages[0]) + else: + SearchWindow(self.window, packages, self.jump_to) + + def cb_preferences_clicked (self, button): + PreferenceWindow(self.window, self.cfg) + return True + + def cb_about_clicked (self, button): + AboutWindow(self.window) + return True + + def cb_queue_right_click (self, queue, event): + if event.button == 3: + x = int(event.x) + y = int(event.y) + time = event.time + pthinfo = queue.get_path_at_pos(x, y) + if pthinfo is not None: + path, col, cellx, celly = pthinfo + if self.queueTree.is_in_emerge(self.queueTree.get_original().get_iter(path)): + queue.grab_focus() + queue.set_cursor(path, col, 0) + self.queuePopup.popup(None, None, None, event.button, time) + return True + else: + return False + + def cb_oneshot_clicked (self, action): + sel = self.queueList.get_selection() + store, it = sel.get_selected() + if it: + package = store.get_value(it, 0) + if not self.cfg.get_local(package, "oneshot_opt"): + set = True + else: + set = False + + self.cfg.set_local(package, "oneshot_opt", set) + self.queue.append(package, update = True, oneshot = set, forceUpdate = True) + + def cb_destroy (self, widget): + """Calls main_quit().""" + gtk.main_quit() + + 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() diff --git a/portato/gui/gtk/wrapper.py b/portato/gui/gtk/wrapper.py new file mode 100644 index 0000000..7066acb --- /dev/null +++ b/portato/gui/gtk/wrapper.py @@ -0,0 +1,105 @@ +# -*- coding: utf-8 -*- +# +# File: portato/gui/gtk/wrapper.py +# This file is part of the Portato-Project, a graphical portage-frontend. +# +# Copyright (C) 2006 René 'Necoro' Neumann +# 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 René 'Necoro' Neumann <necoro@necoro.net> + +from portato.gui.wrapper import Tree, Console + +class GtkTree (Tree): + """The implementation of the abstract tree.""" + + def __init__ (self, tree, col = 0): + """Constructor. + + @param tree: original tree + @type tree: gtk.TreeStore + @param col: the column where the cpv is stored + @type col: int""" + + self.tree = tree + self.cpv_col = col + self.emergeIt = self.append(None, ["Emerge", ""]) + self.unmergeIt = self.append(None, ["Unmerge", ""]) + + def build_append_value (self, cpv, oneshot = False, update = False, version = None): + string = "" + + if oneshot: + string += "<i>oneshot</i>" + if update: string += "; " + + if update: + string += "<i>updating</i>" + if version != None: + string += "<i> from version %s</i>" % version + + return [cpv, string] + + def get_emerge_it (self): + return self.emergeIt + + def get_unmerge_it (self): + return self.unmergeIt + + def is_in_emerge (self, it): + return self.get_path_from_iter(it).split(":")[0] == self.get_path_from_iter(self.emergeIt) + + def is_in_unmerge (self, it): + return self.get_path_from_iter(it).split(":")[0] == self.get_path_from_iter(self.unmergeIt) + + def iter_has_parent (self, it): + return (self.tree.iter_parent(it) != None) + + def parent_iter (self, it): + return self.tree.iter_parent(it) + + def first_child_iter (self, it): + return self.tree.iter_children(it) + + def iter_has_children (self, it): + return self.tree.iter_has_child(it) + + def next_iter (self, it): + return self.tree.iter_next(it) + + def get_value (self, it, column): + return self.tree.get_value(it, column) + + def get_path_from_iter (self, it): + return self.tree.get_string_from_iter(it) + + def append (self, parent = None, values = None): + return self.tree.append(parent, values) + + def remove (self, it): + return self.tree.remove(it) + + def get_original (self): + return self.tree + + def get_cpv_column (self): + return self.cpv_col + +class GtkConsole (Console): + """The implementation of the abstract Console for GTK.""" + + def __init__ (self, console): + """Constructor. + + @param console: the original console + @type console: vte.Terminal""" + + self.console = console + + def set_pty (self, pty): + self.console.set_pty(pty) + + def get_original (self): + return self.console diff --git a/portato/gui/gui_helper.py b/portato/gui/gui_helper.py new file mode 100644 index 0000000..6cc09a3 --- /dev/null +++ b/portato/gui/gui_helper.py @@ -0,0 +1,608 @@ +# -*- coding: utf-8 -*- +# +# File: portato/gui/gui_helper.py +# This file is part of the Portato-Project, a graphical portage-frontend. +# +# Copyright (C) 2006 René 'Necoro' Neumann +# 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 René 'Necoro' Neumann <necoro@necoro.net> + +# some backend things +from portato import backend +from portato.backend import flags +from portato.helper import * + +# parser +from portato.config_parser import ConfigParser + +# the wrapper +from wrapper import Console, Tree + +# some stuff needed +from subprocess import Popen, PIPE, STDOUT +from threading import Thread +import pty + +class Config: + """Wrapper around a ConfigParser and for additional local configurations.""" + const = { + "main_sec" : "Main", + "usePerVersion_opt" : "usePerVersion", + "useFile_opt" : "usefile", + "maskFile_opt" : "maskfile", + "maskPerVersion_opt" : "maskPerVersion", + "testingFile_opt" : "keywordfile", + "testingPerVersion_opt" : "keywordperversion", + "debug_opt" : "debug", + "oneshot_opt" : "oneshot", + "deep_opt" : "deep", + "newuse_opt" : "newuse", + "syncCmd_opt" : "synccommand" + } + + def __init__ (self, cfgFile): + """Constructor. + + @param cfgFile: path to config file + @type cfgFile: string""" + + # init ConfigParser + self._cfg = ConfigParser(cfgFile) + + # read config + self._cfg.parse() + + # local configs + self.local = {} + + def get(self, name, section=const["main_sec"], constName = True): + """Gets an option. + + @param name: name of the option + @type name: string + @param section: section to look in; default is Main-Section + @type section: string + @param constName: If True (the default), the option names are first looked up in the const-dict. + @type constName: boolean + @return: the option's value + @rtype: string""" + + if constName: + name = self.const[name] + + return self._cfg.get(name, section) + + def get_boolean(self, name, section=const["main_sec"], constName = True): + """Gets a boolean option. + + @param name: name of the option + @type name: string + @param section: section to look in; default is Main-Section + @type section: string + @param constName: If True (the default), the option names are first looked up in the const-dict. + @type constName: boolean + @return: the option's value + @rtype: boolean""" + + if constName: + name = self.const[name] + + return self._cfg.get_boolean(name, section) + + def modify_flags_config (self): + """Sets the internal config of the L{flags}-module. + @see: L{flags.set_config()}""" + + flagCfg = { + "usefile": self.get("useFile_opt"), + "usePerVersion" : self.get_boolean("usePerVersion_opt"), + "maskfile" : self.get("maskFile_opt"), + "maskPerVersion" : self.get_boolean("maskPerVersion_opt"), + "testingfile" : self.get("testingFile_opt"), + "testingPerVersion" : self.get_boolean("testingPerVersion_opt")} + flags.set_config(flagCfg) + + def modify_debug_config (self): + """Sets the external debug-config. + @see: L{helper.set_debug()}""" + set_debug(self.get_boolean("debug_opt")) + + def modify_external_configs (self): + """Convenience function setting all external configs.""" + self.modify_debug_config() + self.modify_flags_config() + + def set_local(self, cpv, name, val): + """Sets some local config. + + @param cpv: the cpv describing the package for which to set this option + @type cpv: string (cpv) + @param name: the option's name + @type name: string + @param val: the value to set + @type val: any""" + + if not cpv in self.local: + self.local[cpv] = {} + + self.local[cpv].update({name:val}) + + def get_local(self, cpv, name): + """Returns something out of the local config. + + @param cpv: the cpv describing the package from which to get this option + @type cpv: string (cpv) + @param name: the option's name + @type name: string + @return: value stored for the cpv and name or None if not found + @rtype: any""" + + if not cpv in self.local: + return None + if not name in self.local[cpv]: + return None + + return self.local[cpv][name] + + def set(self, name, val, section=const["main_sec"], constName = True): + """Sets an option. + + @param name: name of the option + @type name: string + @param val: value to set the option to + @type val: string + @param section: section to look in; default is Main-Section + @type section: string + @param constName: If True (the default), the option names are first looked up in the const-dict. + @type constName: boolean""" + + if constName: + name = self.const[name] + + self._cfg.set(name, val, section) + + def set_boolean (self, name, val, section=const["main_sec"], constName = True): + """Sets a boolean option. + + @param name: name of the option + @type name: string + @param val: value to set the option to + @type val: boolean + @param section: section to look in; default is Main-Section + @type section: string + @param constName: If True (the default), the option names are first looked up in the const-dict. + @type constName: boolean""" + + if constName: + name = self.const[name] + + self._cfg.set_boolean(name, val, section) + + def write(self): + """Writes to the config file and modify any external configs.""" + self._cfg.write() + self.modify_external_configs() + +class Database: + """An internal database which holds a simple dictionary cat -> [package_list].""" + + def __init__ (self): + """Constructor.""" + self._db = {} + + def populate (self, category = None): + """Populates the database. + + @param category: An optional category - so only packages of this category are inserted. + @type category: string""" + + # get the lists + packages = backend.find_all_packages(name = category, withVersion = False) + installed = backend.find_all_installed_packages(name = category, withVersion = False) + + # cycle through packages + for p in packages: + list = p.split("/") + cat = list[0] + pkg = list[1] + if p in installed: + pkg += "*" + if not cat in self._db: self._db[cat] = [] + self._db[cat].append(pkg) + + for key in self._db: # sort alphabetically + self._db[key].sort(cmp=cmp, key=str.lower) + + def get_cat (self, cat): + """Returns the packages in the category. + + @param cat: category to return the packages from + @type cat: string + @return: list of packages or [] + @rtype: string[]""" + + try: + return self._db[cat] + except KeyError: # cat is in category list - but not in portage + debug("Catched KeyError =>", cat, "seems not to be an available category. Have you played with rsync-excludes?") + return [] + + def reload (self, cat): + """Reloads the given category. + + @param cat: category + @type cat: string""" + + del self._db[cat] + self.populate(cat+"/") + +class EmergeQueue: + """This class manages the emerge queue.""" + + def __init__ (self, tree = None, console = None, db = None): + """Constructor. + + @param tree: Tree to append all the items to. + @type tree: Tree + @param console: Output is shown here. + @type console: Console + @param db: A database instance. + @type db: Database""" + + # the different queues + self.mergequeue = [] # for emerge + self.unmergequeue = [] # for emerge -C + self.oneshotmerge = [] # for emerge --oneshot + + # dictionaries with data about the packages in the queue + self.iters = {} # iterator in the tree + self.deps = {} # all the deps of the package + + # member vars + self.tree = tree + if self.tree and not isinstance(self.tree, Tree): raise TypeError, "tree passed is not a Tree-object" + + self.console = console + if self.console and not isinstance(self.console, Console): raise TypeError, "console passed is not a Console-object" + + self.db = db + + # our iterators pointing at the toplevels; they are set to None if we do not have a tree + if self.tree: + self.emergeIt = self.tree.get_emerge_it() + self.unmergeIt = self.tree.get_unmerge_it() + else: + self.emergeIt = self.unmergeIt = None + + def _get_pkg_from_cpv (self, cpv, unmask = False): + """Gets a L{backend.Package}-object from a cpv. + + @param cpv: the cpv to get the package for + @type cpv: string (cpv) + @param unmask: if True we will look for masked packages if we cannot find unmasked ones + @type unmask: boolean + @return: created package + @rtype: backend.Package + + @raises backend.PackageNotFoundException: If no package could be found - normally it is existing but masked.""" + + # for the beginning: let us create a package object - but it is not guaranteed, that it actually exists in portage + pkg = backend.Package(cpv) + masked = not (pkg.is_masked() or pkg.is_testing(allowed=True)) # we are setting this to True in case we have unmasked it already, but portage does not know this + + # and now try to find it in portage + pkg = backend.find_packages("="+cpv, masked = masked) + + if pkg: # gotcha + pkg = pkg[0] + + elif unmask: # no pkg returned, but we are allowed to unmask it + pkg = backend.find_packages("="+cpv, masked = True)[0] + if pkg.is_testing(allowed = True): + pkg.set_testing(True) + if pkg.is_masked(): + pkg.set_masked() + + else: # no pkg returned - and we are not allowed to unmask + raise backend.PackageNotFoundException(cpv) + + return pkg + + def update_tree (self, it, cpv, unmask = False, oneshot = False): + """This updates the tree recursivly, or? Isn't it? Bjorn! + + @param it: iterator where to append + @type it: Iterator + @param cpv: The package to append. + @type cpv: string (cat/pkg-ver) + @param unmask: True if we are allowed to look for masked packages + @type unmask: boolean + @param oneshot: True if we want to emerge is oneshot + @type oneshot: boolean + + @raises backend.BlockedException: When occured during dependency-calculation. + @raises backend.PackageNotFoundException: If no package could be found - normally it is existing but masked.""" + + if cpv in self.deps: + return # in list already and therefore it's already in the tree too + + update = False + uVersion = None + try: + pkg = self._get_pkg_from_cpv(cpv, unmask) + if not pkg.is_installed(): + old = backend.get_all_installed_versions(pkg.get_cp()) + if old: + old = old[0] # assume we have only one there; FIXME: slotted packages + update = True + uVersion = old.get_version() + + except backend.PackageNotFoundException, e: # package not found / package is masked -> delete current tree and re-raise the exception + if self.tree.iter_has_parent(it): + while self.tree.iter_has_parent(it): + it = self.tree.parent_iter(it) + self.remove_with_children(it) + raise e + + # add iter + subIt = self.tree.append(it, self.tree.build_append_value(cpv, oneshot = oneshot, update = update, version = uVersion)) + self.iters.update({cpv: subIt}) + + # get dependencies + deps = pkg.get_dep_packages() # this might raise a BlockedException + self.deps.update({cpv : deps}) + + # recursive call + for d in deps: + try: + self.update_tree(subIt, d, unmask) + except backend.BlockedException, e: # BlockedException occured -> delete current tree and re-raise exception + debug("Something blocked:", e[0]) + self.remove_with_children(subIt) + raise e + + def append (self, cpv, unmerge = False, update = False, forceUpdate = False, unmask = False, oneshot = False): + """Appends a cpv either to the merge queue or to the unmerge-queue. + Also updates the tree-view. + + @param cpv: Package to add + @type cpv: string (cat/pkg-ver) + @param unmerge: Set to True if you want to unmerge this package - else False. + @type unmerge: boolean + @param update: Set to True if a package is going to be updated (e.g. if the use-flags changed). + @type update: boolean + @param forceUpdate: Set to True if the update should be forced. + @type forceUpdate: boolean + @param unmask: True if we are allowed to look for masked packages + @type unmask: boolean + @param oneshot: True if this package should not be added to the world-file. + @type oneshot: boolean + + @raises portato.backend.PackageNotFoundException: if trying to add a package which does not exist""" + + if not unmerge: # emerge + # insert dependencies + pkg = self._get_pkg_from_cpv(cpv, unmask) + deps = pkg.get_dep_packages() + + if update: + if not forceUpdate and deps == self.deps[cpv]: + return # nothing changed - return + else: + hasBeenInQueue = (cpv in self.mergequeue or cpv in self.oneshotmerge) + parentIt = self.tree.parent_iter(self.iters[cpv]) + + # delete it out of the tree - but NOT the changed flags + self.remove_with_children(self.iters[cpv], removeNewFlags = False) + + if hasBeenInQueue: # package has been in queue before + self._queue_append(cpv, oneshot) + + self.update_tree(parentIt, cpv, unmask, oneshot = oneshot) + else: # not update + self._queue_append(cpv, oneshot) + if self.emergeIt: + self.update_tree(self.emergeIt, cpv, unmask, oneshot = oneshot) + + else: # unmerge + self.unmergequeue.append(cpv) + if self.unmergeIt: # update tree + self.tree.append(self.unmergeIt, self.tree.build_append_value(cpv)) + + def _queue_append (self, cpv, oneshot = False): + """Convenience function appending a cpv either to self.mergequeue or to self.oneshotmerge. + + @param cpv: cpv to add + @type cpv: string (cpv) + @param oneshot: True if this package should not be added to the world-file. + @type oneshot: boolean""" + + if not oneshot: + self.mergequeue.append(cpv) + else: + self.oneshotmerge.append(cpv) + + def _update_packages(self, packages, process = None): + """This updates the packages-list. It simply makes the db to rebuild the specific category. + + @param packages: The packages which we emerged. + @type packages: list of cpvs + @param process: The process we have to wait for before we can do our work. + @type process: subprocess.Popen""" + + if process: process.wait() + for p in packages: + if p in ["world", "system"]: continue + cat = backend.split_package_name(p)[0] # get category + while cat[0] in ["=",">","<","!"]: + cat = cat[1:] + self.db.reload(cat) + debug("Category %s refreshed" % cat) + + def _emerge (self, options, packages, it, command = ["/usr/bin/python","/usr/bin/emerge"]): + """Calls emerge and updates the terminal. + + @param options: options to send to emerge + @type options: list + @param packages: packages to emerge + @type packages: list + @param it: Iterators which point to these entries whose children will be removed after completion. + @type it: Iterator[] + @param command: the command to execute - default is "/usr/bin/python /usr/bin/emerge" + @type command: string[]""" + + # open tty + (master, slave) = pty.openpty() + self.console.set_pty(master) + + # start emerge + process = Popen(command+options+packages, stdout = slave, stderr = STDOUT, shell = False) + + # start thread waiting for the stop of emerge + Thread(target=self._update_packages, args=(packages+self.deps.keys(), process)).start() + + # remove + for i in it: + self.remove_with_children(i) + + def emerge (self, force = False): + """Emerges everything in the merge-queue. + + @param force: If False, '-pv' is send to emerge. Default: False. + @type force: boolean""" + + def prepare(queue): + """Prepares the list of iterators and the list of packages.""" + list = [] + its = [] + for k in queue: + list += ["="+k] + its.append(self.iters[k]) + + return list, its + + # oneshot-queue + if len(self.oneshotmerge) != 0: + # prepare package-list for oneshot + list, its = prepare(self.oneshotmerge) + + s = ["--oneshot"] + if not force: s += ["--verbose", "--pretend"] + + self._emerge(s, list, its) + + # normal queue + if len(self.mergequeue) != 0: + # prepare package-list + list, its = prepare(self.mergequeue) + + s = [] + if not force: s = ["--verbose", "--pretend"] + + self._emerge(s, list, its) + + def unmerge (self, force = False): + """Unmerges everything in the umerge-queue. + + @param force: If False, '-pv' is send to emerge. Default: False. + @type force: boolean""" + + if len(self.unmergequeue) == 0: return # nothing in queue + + list = self.unmergequeue[:] # copy the unmerge-queue + + # set options + s = ["-C"] + if not force: s += ["-pv"] + + self._emerge(s,list, [self.unmergeIt]) + + def update_world(self, force = False, newuse = False, deep = False): + """Does an update world. newuse and deep are the arguments handed to emerge. + + @param force: If False, '-pv' is send to emerge. Default: False. + @type force: boolean""" + + options = ["--update"] + + if newuse: options += ["--newuse"] + if deep: options += ["--deep"] + if not force: options += ["-pv"] + + self._emerge(options, ["world"], [self.emergeIt]) + + def sync (self, command = None): + """Calls "emerge --sync". + + @param command: command to execute to sync. If None "emerge --sync" is taken. + @type command: string[]""" + + if command == None: + self._emerge(["--sync"], [], []) + else: + self._emerge([],[],[], command = command) + + def remove_with_children (self, it, removeNewFlags = True): + """Convenience function which removes all children of an iterator and than the iterator itself. + + @param it: The iter which to remove. + @type it: Iterator + @param removeNewFlags: True if new flags should be removed; False otherwise. Default: True. + @type removeNewFlags: boolean""" + + self.remove_children(it, removeNewFlags) + self.remove(it, removeNewFlags) + + def remove_children (self, parentIt, removeNewFlags = True): + """Removes all children of a given parent TreeIter recursivly. + + @param parentIt: The iter from which to remove all children. + @type parentIt: Iterator + @param removeNewFlags: True if new flags should be removed; False otherwise. Default: True. + @type removeNewFlags: boolean""" + + childIt = self.tree.first_child_iter(parentIt) + + while childIt: + if (self.tree.iter_has_children(childIt)): # recursive call + self.remove_children(childIt, removeNewFlags) + temp = childIt + childIt = self.tree.next_iter(childIt) + self.remove(temp, removeNewFlags) + + def remove (self, it, removeNewFlags = True): + """Removes a specific item in the tree. This does not remove the top-entries. + + @param it: Iterator which points to the entry we are going to remove. + @type it: Iterator + @param removeNewFlags: True if new flags should be removed; False otherwise. Default: True. + @type removeNewFlags: boolean""" + + if self.tree.iter_has_parent(it): # NEVER remove our top stuff + cpv = self.tree.get_value(it, self.tree.get_cpv_column()) + if self.tree.is_in_emerge(it): # Emerge + del self.iters[cpv] + try: + del self.deps[cpv] + except KeyError: # this seems to be removed due to a BlockedException - so no deps here atm ;) + debug("Catched KeyError =>", cpv, "seems not to be in self.deps. Should be no harm in normal cases.") + try: + self.mergequeue.remove(cpv) + except ValueError: # this is a dependency - ignore + try: + self.oneshotmerge.remove(cpv) + except ValueError: + debug("Catched ValueError =>", cpv, "seems not to be in merge-queue. Should be no harm.") + + if removeNewFlags: # remove the changed flags + flags.remove_new_use_flags(cpv) + flags.remove_new_masked(cpv) + flags.remove_new_testing(cpv) + + else: # in Unmerge + self.unmergequeue.remove(cpv) + + self.tree.remove(it) diff --git a/portato/gui/wrapper.py b/portato/gui/wrapper.py new file mode 100644 index 0000000..def5c50 --- /dev/null +++ b/portato/gui/wrapper.py @@ -0,0 +1,184 @@ +# -*- coding: utf-8 -*- +# +# File: portato/gui/wrapper.py +# This file is part of the Portato-Project, a graphical portage-frontend. +# +# Copyright (C) 2006 René 'Necoro' Neumann +# 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 René 'Necoro' Neumann <necoro@necoro.net> + +class Tree: + """This represents an abstract of a Tree-Widget. It should be used for all operations not in a specific frontend, where a Tree is needed. + Each frontend _MUST_ define its own subclass and implement ALL of the methods, otherwise a NotImplementedError will be thrown.""" + + def iter_has_parent (self, it): + """Returns whether the actual iterator has a parent. + + @param it: the iterator + @type it: Iterator + @returns: True if it has a parent it, else False. + @rtype: boolean""" + raise NotImplementedError + + def parent_iter (self, it): + """Returns the parent iter. + + @param it: the iterator + @type it: Iterator + @returns: Parent iterator or None if the current it has no parent. + @rtype: Iterator; None""" + raise NotImplementedError + + def first_child_iter (self, it): + """Returns the first child iter. + + @param it: the iterator + @type it: Iterator + @returns: First child iterator or None if the current it has no children. + @rtype: Iterator; None""" + raise NotImplementedError + + def iter_has_children (self, it): + """Returns whether the actual iterator has children. + + @param it: the iterator + @type it: Iterator + @returns: True if it has children, else False. + @rtype: boolean""" + raise NotImplementedError + + def next_iter (self, it): + """Returns the next iter. + + @param it: the iterator + @type it: Iterator + @returns: Nex iterator or None if the current iter is the last one. + @rtype: Iterator; None""" + raise NotImplementedError + + def get_value (self, it, column): + """Returns the value of the specific column at the given iterator. + + @param it: the iterator + @type it: Iterator + @param column: the column of the iterator from where to get the value + @type column: int + @returns: the value + @rtype: anything""" + raise NotImplementedError + + def get_path_from_iter(self, it): + """Returns a string defining the path to the given iterator. In this path all nodes are divided by a colon ':'. + For example: 2:4:5 could mean the 6th child of the 5th child of the 3rd element. It might also mean the 5th child of the 4th child of the 2nd element. It does not matter, where counting starts as long as it is consistent. + + @param it: the iterator + @type it: Iterator + @returns: the path string + @rtype: string""" + raise NotImplementedError + + def append (self, parent = None, values = None): + """Appends some values right after the given parent. If parent is None, it is appended as the first element. + + @param parent: the iterator to append the values right after; if None it symbolizes the top + @type parent: Iterator + @param values: a list of values which are going to be appended to the tree + @type values: list + @returns: Iterator pointing to the newly appended stuff + @rtype: Iterator""" + raise NotImplementedError + + def remove(self, it): + """Removes an iterator out of the tree. + @attention: The iterator can point to anything hereafter. Do not reuse! + + @param it: iterator to remove + @type it: Iterator""" + raise NotImplementedError + + def get_original(self): + """Returns the original tree-object. + + @returns: original tree-object + @rtype: tree-object""" + raise NotImplementedError + + # + # the "design" part + # + + def get_cpv_column (self): + """Returns the number of the column where the cpv's are stored. + + @returns: column with cpv's + @rtype: int""" + raise NotImplementedError + + def is_in_emerge (self, it): + """Checks whether an iterator is part of the "Emerge" section. + + @param it: the iterator to check + @type it: Iterator + @returns: True if the iter is part; False otherwise + @rtype: boolean""" + raise NotImplementedError + + def is_in_unmerge (self, it): + """Checks whether an iterator is part of the "Unmerge" section. + + @param it: the iterator to check + @type it: Iterator + @returns: True if the iter is part; False otherwise + @rtype: boolean""" + raise NotImplementedError + + def get_emerge_it (self): + """Returns an iterator signaling the top of the emerge section. + + @returns: emerge-iterator + @rtype: Iterator""" + raise NotImplementedError + + def get_unmerge_it (self): + """Returns an iterator signaling the top of the unmerge section. + + @returns: unmerge-iterator + @rtype: Iterator""" + raise NotImplementedError + + def build_append_value (self, cpv, oneshot = False, update = False, version = None): + """Builds the list, which is going to be passed to append. + + @param cpv: the cpv + @type cpv: string (cpv) + @param oneshot: True if oneshot + @type oneshot: boolean + @param update: True if this is an update + @type update: boolean + @param version: the version we update from + @type version: string + + @returns: the created list + @rtype: list""" + raise NotImplementedError + +class Console: + """This represents the abstract of a console. It should be used for all operations not in a specific frontend, where a console is needed. + Each frontend _MUST_ define its own subclass and implement ALL of the methods, otherwise a NotImplementedError will be thrown.""" + + def set_pty (self, pty): + """This sets the pseudo-terminal where to print the incoming output to. + + @param pty: the terminal to print to + @type pty: file-descriptor""" + raise NotImplementedError + + def get_original(self): + """Returns the original console-object. + + @returns: original console-object + @rtype: console-object""" + raise NotImplementedError diff --git a/portato/helper.py b/portato/helper.py new file mode 100644 index 0000000..2a492cc --- /dev/null +++ b/portato/helper.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +# +# File: portato/helper.py +# This file is part of the Portato-Project, a graphical portage-frontend. +# +# Copyright (C) 2006 René 'Necoro' Neumann +# 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 René 'Necoro' Neumann <necoro@necoro.net> et.al. + +import traceback, os.path + +DEBUG = True + +def set_debug (d): + """Sets the global DEBUG variable. Do not set it by your own - always use this function. + + @param d: True to enable debugging; False otherwise + @type d: boolean""" + + global DEBUG + DEBUG = d + +def debug(*args, **kwargs): + """Prints a debug message including filename and lineno. + A variable number of positional arguments are allowed. + + If debug(obj0, obj1, obj2) is called, the text part of the output + looks like the output from print obj0, obj1, obj2. + + If you pass the optional keyword-argument "name", it is used for the function-name instead of the original one.""" + + + if not DEBUG : return + + stack = traceback.extract_stack() + a, b, c, d = stack[-2] + a = os.path.basename(a) + out = [] + for obj in args: + out.append(str(obj)) + text = ' '.join(out) + if "name" in kwargs: + text = 'In %s (%s:%s): %s' % (kwargs["name"], a, b, text) + else: + text = 'In %s (%s:%s): %s' % (c, a, b, text) + + text = "***DEBUG*** %s ***DEBUG***" % text + + print text + +def am_i_root (): + """Returns True if the current user is root, False otherwise. + @rtype: boolean""" + if os.getuid() == 0: + return True + else: + return False |