From 656f9a07588cc00704825a78de9649ca4a1552b8 Mon Sep 17 00:00:00 2001 From: Dongxiao Xu Date: Mon, 28 Nov 2011 14:32:40 +0800 Subject: Hob: A new implemetation (v2) This commit implements a new design for hob Some of the new features: - Friendly new designed GUI. Quick response to user actions. - Two step builds support package generation and image generation. - Support running GUI seprarately from bitbake server. - Recipe/package selection and deselection. - Accurate customization for image contents and size. - Progress bars showing the parsing and build status. - Load/save user configurations from/into templates. (Bitbake rev: 4dacd29f9c957d20f4583330b51e5420f9c3338d) Signed-off-by: Dongxiao Xu Signed-off-by: Shane Wang Signed-off-by: Liming An Signed-off-by: Fengxia Hua Designed-by: Belen Barros Pena Signed-off-by: Richard Purdie --- bitbake/lib/bb/ui/crumbs/hoblistmodel.py | 765 +++++++++++++++++++++++++++++++ 1 file changed, 765 insertions(+) create mode 100644 bitbake/lib/bb/ui/crumbs/hoblistmodel.py (limited to 'bitbake/lib/bb/ui/crumbs/hoblistmodel.py') diff --git a/bitbake/lib/bb/ui/crumbs/hoblistmodel.py b/bitbake/lib/bb/ui/crumbs/hoblistmodel.py new file mode 100644 index 0000000000..227ae4b5de --- /dev/null +++ b/bitbake/lib/bb/ui/crumbs/hoblistmodel.py @@ -0,0 +1,765 @@ +# +# BitBake Graphical GTK User Interface +# +# Copyright (C) 2011 Intel Corporation +# +# Authored by Joshua Lock +# Authored by Dongxiao Xu +# Authored by Shane Wang +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import gtk +import gobject + +# +# PackageListModel +# +class PackageListModel(gtk.TreeStore): + """ + This class defines an gtk.TreeStore subclass which will convert the output + of the bb.event.TargetsTreeGenerated event into a gtk.TreeStore whilst also + providing convenience functions to access gtk.TreeModel subclasses which + provide filtered views of the data. + """ + (COL_NAME, COL_VER, COL_REV, COL_RNM, COL_SEC, COL_SUM, COL_RDEP, COL_RPROV, COL_SIZE, COL_BINB, COL_INC) = range(11) + + __gsignals__ = { + "packagelist-populated" : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + ()), + "package-selection-changed" : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + ()), + } + + def __init__(self): + + self.contents = None + self.images = None + self.pkgs_size = 0 + self.pn_path = {} + self.pkg_path = {} + + gtk.TreeStore.__init__ (self, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_BOOLEAN) + + + """ + Find the model path for the item_name + Returns the path in the model or None + """ + def find_path_for_item(self, item_name): + if item_name not in self.pkg_path.keys(): + return None + else: + return self.pkg_path[item_name] + + def find_item_for_path(self, item_path): + return self[item_path][self.COL_NAME] + + """ + Helper function to determine whether an item is an item specified by filter + """ + def tree_model_filter(self, model, it, filter): + for key in filter.keys(): + if model.get_value(it, key) not in filter[key]: + return False + + return True + + """ + Create, if required, and return a filtered gtk.TreeModelSort + containing only the items specified by filter + """ + def tree_model(self, filter): + model = self.filter_new() + model.set_visible_func(self.tree_model_filter, filter) + + sort = gtk.TreeModelSort(model) + sort.set_sort_column_id(PackageListModel.COL_NAME, gtk.SORT_ASCENDING) + sort.set_default_sort_func(None) + return sort + + def convert_vpath_to_path(self, view_model, view_path): + # view_model is the model sorted + # get the path of the model filtered + filtered_model_path = view_model.convert_path_to_child_path(view_path) + # get the model filtered + filtered_model = view_model.get_model() + # get the path of the original model + path = filtered_model.convert_path_to_child_path(filtered_model_path) + return path + + def convert_path_to_vpath(self, view_model, path): + name = self.find_item_for_path(path) + it = view_model.get_iter_first() + while it: + child_it = view_model.iter_children(it) + while child_it: + view_name = view_model.get_value(child_it, self.COL_NAME) + if view_name == name: + view_path = view_model.get_path(child_it) + return view_path + child_it = view_model.iter_next(child_it) + it = view_model.iter_next(it) + return None + + """ + The populate() function takes as input the data from a + bb.event.PackageInfo event and populates the package list. + Once the population is done it emits gsignal packagelist-populated + to notify any listeners that the model is ready + """ + def populate(self, pkginfolist): + self.clear() + self.pkgs_size = 0 + self.pn_path = {} + self.pkg_path = {} + + for pkginfo in pkginfolist: + pn = pkginfo['PN'] + pv = pkginfo['PV'] + pr = pkginfo['PR'] + if pn in self.pn_path.keys(): + pniter = self.get_iter(self.pn_path[pn]) + else: + pniter = self.append(None) + self.set(pniter, self.COL_NAME, pn + '-' + pv + '-' + pr, + self.COL_INC, False) + self.pn_path[pn] = self.get_path(pniter) + + pkg = pkginfo['PKG'] + pkgv = pkginfo['PKGV'] + pkgr = pkginfo['PKGR'] + pkgsize = pkginfo['PKGSIZE_%s' % pkg] if 'PKGSIZE_%s' % pkg in pkginfo.keys() else "0" + pkg_rename = pkginfo['PKG_%s' % pkg] if 'PKG_%s' % pkg in pkginfo.keys() else "" + section = pkginfo['SECTION_%s' % pkg] if 'SECTION_%s' % pkg in pkginfo.keys() else "" + summary = pkginfo['SUMMARY_%s' % pkg] if 'SUMMARY_%s' % pkg in pkginfo.keys() else "" + rdep = pkginfo['RDEPENDS_%s' % pkg] if 'RDEPENDS_%s' % pkg in pkginfo.keys() else "" + rrec = pkginfo['RRECOMMENDS_%s' % pkg] if 'RRECOMMENDS_%s' % pkg in pkginfo.keys() else "" + rprov = pkginfo['RPROVIDES_%s' % pkg] if 'RPROVIDES_%s' % pkg in pkginfo.keys() else "" + + if 'ALLOW_EMPTY_%s' % pkg in pkginfo.keys(): + allow_empty = pkginfo['ALLOW_EMPTY_%s' % pkg] + elif 'ALLOW_EMPTY' in pkginfo.keys(): + allow_empty = pkginfo['ALLOW_EMPTY'] + else: + allow_empty = "" + + if pkgsize == "0" and not allow_empty: + continue + + if len(pkgsize) > 3: + size = '%.1f' % (int(pkgsize)*1.0/1024) + ' MB' + else: + size = pkgsize + ' KB' + + it = self.append(pniter) + self.pkg_path[pkg] = self.get_path(it) + self.set(it, self.COL_NAME, pkg, self.COL_VER, pkgv, + self.COL_REV, pkgr, self.COL_RNM, pkg_rename, + self.COL_SEC, section, self.COL_SUM, summary, + self.COL_RDEP, rdep + ' ' + rrec, + self.COL_RPROV, rprov, self.COL_SIZE, size, + self.COL_BINB, "", self.COL_INC, False) + + self.emit("packagelist-populated") + + """ + Check whether the item at item_path is included or not + """ + def path_included(self, item_path): + return self[item_path][self.COL_INC] + + """ + Update the model, send out the notification. + """ + def selection_change_notification(self): + self.emit("package-selection-changed") + + """ + Mark a certain package as selected. + All its dependencies are marked as selected. + The recipe provides the package is marked as selected. + If user explicitly selects a recipe, all its providing packages are selected + """ + def include_item(self, item_path, binb=""): + if self.path_included(item_path): + return + + item_name = self[item_path][self.COL_NAME] + item_rdep = self[item_path][self.COL_RDEP] + + self[item_path][self.COL_INC] = True + + self.selection_change_notification() + + it = self.get_iter(item_path) + + # If user explicitly selects a recipe, all its providing packages are selected. + if not self[item_path][self.COL_VER] and binb == "User Selected": + child_it = self.iter_children(it) + while child_it: + child_path = self.get_path(child_it) + child_included = self.path_included(child_path) + if not child_included: + self.include_item(child_path, binb="User Selected") + child_it = self.iter_next(child_it) + return + + # The recipe provides the package is also marked as selected + parent_it = self.iter_parent(it) + if parent_it: + parent_path = self.get_path(parent_it) + self[parent_path][self.COL_INC] = True + + item_bin = self[item_path][self.COL_BINB].split(', ') + if binb and not binb in item_bin: + item_bin.append(binb) + self[item_path][self.COL_BINB] = ', '.join(item_bin).lstrip(', ') + + if item_rdep: + # Ensure all of the items deps are included and, where appropriate, + # add this item to their COL_BINB + for dep in item_rdep.split(" "): + if dep.startswith('('): + continue + # If the contents model doesn't already contain dep, add it + dep_path = self.find_path_for_item(dep) + if not dep_path: + continue + dep_included = self.path_included(dep_path) + + if dep_included and not dep in item_bin: + # don't set the COL_BINB to this item if the target is an + # item in our own COL_BINB + dep_bin = self[dep_path][self.COL_BINB].split(', ') + if not item_name in dep_bin: + dep_bin.append(item_name) + self[dep_path][self.COL_BINB] = ', '.join(dep_bin).lstrip(', ') + elif not dep_included: + self.include_item(dep_path, binb=item_name) + + """ + Mark a certain package as de-selected. + All other packages that depends on this package are marked as de-selected. + If none of the packages provided by the recipe, the recipe should be marked as de-selected. + If user explicitly de-select a recipe, all its providing packages are de-selected. + """ + def exclude_item(self, item_path): + if not self.path_included(item_path): + return + + self[item_path][self.COL_INC] = False + + self.selection_change_notification() + + item_name = self[item_path][self.COL_NAME] + item_rdep = self[item_path][self.COL_RDEP] + it = self.get_iter(item_path) + + # If user explicitly de-select a recipe, all its providing packages are de-selected. + if not self[item_path][self.COL_VER]: + child_it = self.iter_children(it) + while child_it: + child_path = self.get_path(child_it) + child_included = self[child_path][self.COL_INC] + if child_included: + self.exclude_item(child_path) + child_it = self.iter_next(child_it) + return + + # If none of the packages provided by the recipe, the recipe should be marked as de-selected. + parent_it = self.iter_parent(it) + peer_iter = self.iter_children(parent_it) + enabled = 0 + while peer_iter: + peer_path = self.get_path(peer_iter) + if self[peer_path][self.COL_INC]: + enabled = 1 + break + peer_iter = self.iter_next(peer_iter) + if not enabled: + parent_path = self.get_path(parent_it) + self[parent_path][self.COL_INC] = False + + # All packages that depends on this package are de-selected. + if item_rdep: + for dep in item_rdep.split(" "): + if dep.startswith('('): + continue + dep_path = self.find_path_for_item(dep) + if not dep_path: + continue + dep_bin = self[dep_path][self.COL_BINB].split(', ') + if item_name in dep_bin: + dep_bin.remove(item_name) + self[dep_path][self.COL_BINB] = ', '.join(dep_bin).lstrip(', ') + + item_bin = self[item_path][self.COL_BINB].split(', ') + if item_bin: + for binb in item_bin: + binb_path = self.find_path_for_item(binb) + if not binb_path: + continue + self.exclude_item(binb_path) + + """ + Package model may be incomplete, therefore when calling the + set_selected_packages(), some packages will not be set included. + Return the un-set packages list. + """ + def set_selected_packages(self, packagelist): + left = [] + for pn in packagelist: + if pn in self.pkg_path.keys(): + path = self.pkg_path[pn] + self.include_item(item_path=path, + binb="User Selected") + else: + left.append(pn) + + return left + + def get_selected_packages(self): + packagelist = [] + + it = self.get_iter_first() + while it: + child_it = self.iter_children(it) + while child_it: + if self.get_value(child_it, self.COL_INC): + name = self.get_value(child_it, self.COL_NAME) + packagelist.append(name) + child_it = self.iter_next(child_it) + it = self.iter_next(it) + + return packagelist + + """ + Return the selected package size, unit is KB. + """ + def get_packages_size(self): + packages_size = 0 + it = self.get_iter_first() + while it: + child_it = self.iter_children(it) + while child_it: + if self.get_value(child_it, self.COL_INC): + str_size = self.get_value(child_it, self.COL_SIZE) + if not str_size: + continue + + unit = str_size.split() + if unit[1] == 'MB': + size = float(unit[0])*1024 + else: + size = float(unit[0]) + packages_size += size + + child_it = self.iter_next(child_it) + it = self.iter_next(it) + return "%f" % packages_size + + """ + Empty self.contents by setting the include of each entry to None + """ + def reset(self): + self.pkgs_size = 0 + it = self.get_iter_first() + while it: + self.set(it, self.COL_INC, False) + child_it = self.iter_children(it) + while child_it: + self.set(child_it, + self.COL_INC, False, + self.COL_BINB, "") + child_it = self.iter_next(child_it) + it = self.iter_next(it) + + self.selection_change_notification() + +# +# RecipeListModel +# +class RecipeListModel(gtk.ListStore): + """ + This class defines an gtk.ListStore subclass which will convert the output + of the bb.event.TargetsTreeGenerated event into a gtk.ListStore whilst also + providing convenience functions to access gtk.TreeModel subclasses which + provide filtered views of the data. + """ + (COL_NAME, COL_DESC, COL_LIC, COL_GROUP, COL_DEPS, COL_BINB, COL_TYPE, COL_INC, COL_IMG, COL_INSTALL, COL_PN) = range(11) + + __dummy_image__ = "--select a base image--" + + __gsignals__ = { + "recipelist-populated" : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + ()), + "recipe-selection-changed" : (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + ()), + } + + """ + """ + def __init__(self): + gtk.ListStore.__init__ (self, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_BOOLEAN, + gobject.TYPE_BOOLEAN, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_STRING) + + """ + Find the model path for the item_name + Returns the path in the model or None + """ + def find_path_for_item(self, item_name): + if self.non_target_name(item_name) or item_name not in self.pn_path.keys(): + return None + else: + return self.pn_path[item_name] + + def find_item_for_path(self, item_path): + return self[item_path][self.COL_NAME] + + """ + Helper method to determine whether name is a target pn + """ + def non_target_name(self, name): + if name and ('-native' in name): + return True + return False + + """ + Helper function to determine whether an item is an item specified by filter + """ + def tree_model_filter(self, model, it, filter): + name = model.get_value(it, self.COL_NAME) + if self.non_target_name(name): + return False + + for key in filter.keys(): + if model.get_value(it, key) not in filter[key]: + return False + + return True + + def sort_func(self, model, iter1, iter2): + val1 = model.get_value(iter1, RecipeListModel.COL_NAME) + val2 = model.get_value(iter2, RecipeListModel.COL_NAME) + return val1 > val2 + + """ + Create, if required, and return a filtered gtk.TreeModelSort + containing only the items which are items specified by filter + """ + def tree_model(self, filter): + model = self.filter_new() + model.set_visible_func(self.tree_model_filter, filter) + + sort = gtk.TreeModelSort(model) + sort.set_default_sort_func(self.sort_func) + return sort + + def convert_vpath_to_path(self, view_model, view_path): + filtered_model_path = view_model.convert_path_to_child_path(view_path) + filtered_model = view_model.get_model() + + # get the path of the original model + path = filtered_model.convert_path_to_child_path(filtered_model_path) + return path + + def convert_path_to_vpath(self, view_model, path): + it = view_model.get_iter_first() + while it: + name = self.find_item_for_path(path) + view_name = view_model.get_value(it, RecipeListModel.COL_NAME) + if view_name == name: + view_path = view_model.get_path(it) + return view_path + it = view_model.iter_next(it) + return None + + def map_runtime(self, event_model, runtime, rdep_type, name): + if rdep_type not in ['pkg', 'pn'] or runtime not in ['rdepends', 'rrecs']: + return + package_depends = event_model["%s-%s" % (runtime, rdep_type)].get(name, []) + pn_depends = [] + for package_depend in package_depends: + if 'task-' not in package_depend and package_depend in event_model["packages"].keys(): + pn_depends.append(event_model["packages"][package_depend]["pn"]) + else: + pn_depends.append(package_depend) + return list(set(pn_depends)) + + def subpkg_populate(self, event_model, pkg, desc, lic, group, atype, pn): + pn_depends = self.map_runtime(event_model, "rdepends", "pkg", pkg) + pn_depends += self.map_runtime(event_model, "rrecs", "pkg", pkg) + self.set(self.append(), self.COL_NAME, pkg, self.COL_DESC, desc, + self.COL_LIC, lic, self.COL_GROUP, group, + self.COL_DEPS, " ".join(pn_depends), self.COL_BINB, "", + self.COL_TYPE, atype, self.COL_INC, False, + self.COL_IMG, False, self.COL_INSTALL, "", self.COL_PN, pn) + + """ + The populate() function takes as input the data from a + bb.event.TargetsTreeGenerated event and populates the RecipeList. + Once the population is done it emits gsignal recipelist-populated + to notify any listeners that the model is ready + """ + def populate(self, event_model): + # First clear the model, in case repopulating + self.clear() + + # dummy image for prompt + self.set(self.append(), self.COL_NAME, self.__dummy_image__, + self.COL_DESC, "", + self.COL_LIC, "", self.COL_GROUP, "", + self.COL_DEPS, "", self.COL_BINB, "", + self.COL_TYPE, "image", self.COL_INC, False, + self.COL_IMG, False, self.COL_INSTALL, "", self.COL_PN, self.__dummy_image__) + + for item in event_model["pn"]: + name = item + desc = event_model["pn"][item]["description"] + lic = event_model["pn"][item]["license"] + group = event_model["pn"][item]["section"] + install = [] + + if ('task-' in name): + if ('lib32-' in name or 'lib64-' in name): + atype = 'mltask' + else: + atype = 'task' + for pkg in event_model["pn"][name]["packages"]: + self.subpkg_populate(event_model, pkg, desc, lic, group, atype, name) + continue + + elif ('-image-' in name): + atype = 'image' + depends = event_model["depends"].get(item, []) + rdepends = self.map_runtime(event_model, 'rdepends', 'pn', name) + depends = depends + rdepends + install = event_model["rdepends-pn"].get(item, []) + + elif ('meta-' in name): + atype = 'toolchain' + + elif (name == 'dummy-image' or name == 'dummy-toolchain'): + atype = 'dummy' + + else: + if ('lib32-' in name or 'lib64-' in name): + atype = 'mlrecipe' + else: + atype = 'recipe' + depends = event_model["depends"].get(item, []) + depends += self.map_runtime(event_model, 'rdepends', 'pn', item) + for pkg in event_model["pn"][name]["packages"]: + depends += self.map_runtime(event_model, 'rdepends', 'pkg', item) + depends += self.map_runtime(event_model, 'rrecs', 'pkg', item) + + self.set(self.append(), self.COL_NAME, item, self.COL_DESC, desc, + self.COL_LIC, lic, self.COL_GROUP, group, + self.COL_DEPS, " ".join(depends), self.COL_BINB, "", + self.COL_TYPE, atype, self.COL_INC, False, + self.COL_IMG, False, self.COL_INSTALL, " ".join(install), self.COL_PN, item) + + self.pn_path = {} + it = self.get_iter_first() + while it: + pn = self.get_value(it, self.COL_NAME) + path = self.get_path(it) + self.pn_path[pn] = path + it = self.iter_next(it) + + self.emit("recipelist-populated") + + """ + Update the model, send out the notification. + """ + def selection_change_notification(self): + self.emit("recipe-selection-changed") + + def path_included(self, item_path): + return self[item_path][self.COL_INC] + + """ + Append a certain image into the combobox + """ + def image_list_append(self, name, deps, install): + # check whether a certain image is there + if not name or self.find_path_for_item(name): + return + it = self.append() + self.set(it, self.COL_NAME, name, self.COL_DESC, "", + self.COL_LIC, "", self.COL_GROUP, "", + self.COL_DEPS, deps, self.COL_BINB, "", + self.COL_TYPE, "image", self.COL_INC, False, + self.COL_IMG, False, self.COL_INSTALL, install, + self.COL_PN, name) + self.pn_path[name] = self.get_path(it) + + """ + Add this item, and any of its dependencies, to the image contents + """ + def include_item(self, item_path, binb="", image_contents=False): + if self.path_included(item_path): + return + + item_name = self[item_path][self.COL_NAME] + item_deps = self[item_path][self.COL_DEPS] + + self[item_path][self.COL_INC] = True + self.selection_change_notification() + + item_bin = self[item_path][self.COL_BINB].split(', ') + if binb and not binb in item_bin: + item_bin.append(binb) + self[item_path][self.COL_BINB] = ', '.join(item_bin).lstrip(', ') + + # We want to do some magic with things which are brought in by the + # base image so tag them as so + if image_contents: + self[item_path][self.COL_IMG] = True + + if item_deps: + # Ensure all of the items deps are included and, where appropriate, + # add this item to their COL_BINB + for dep in item_deps.split(" "): + # If the contents model doesn't already contain dep, add it + dep_path = self.find_path_for_item(dep) + if not dep_path: + continue + dep_included = self.path_included(dep_path) + + if dep_included and not dep in item_bin: + # don't set the COL_BINB to this item if the target is an + # item in our own COL_BINB + dep_bin = self[dep_path][self.COL_BINB].split(', ') + if not item_name in dep_bin: + dep_bin.append(item_name) + self[dep_path][self.COL_BINB] = ', '.join(dep_bin).lstrip(', ') + elif not dep_included: + self.include_item(dep_path, binb=item_name, image_contents=image_contents) + + def exclude_item(self, item_path): + if not self.path_included(item_path): + return + + self[item_path][self.COL_INC] = False + + self.selection_change_notification() + + item_name = self[item_path][self.COL_NAME] + item_deps = self[item_path][self.COL_DEPS] + if item_deps: + for dep in item_deps.split(" "): + dep_path = self.find_path_for_item(dep) + if not dep_path: + continue + dep_bin = self[dep_path][self.COL_BINB].split(', ') + if item_name in dep_bin: + dep_bin.remove(item_name) + self[dep_path][self.COL_BINB] = ', '.join(dep_bin).lstrip(', ') + + item_bin = self[item_path][self.COL_BINB].split(', ') + if item_bin: + for binb in item_bin: + binb_path = self.find_path_for_item(binb) + if not binb_path: + continue + self.exclude_item(binb_path) + + def reset(self): + it = self.get_iter_first() + while it: + self.set(it, + self.COL_INC, False, + self.COL_BINB, "", + self.COL_IMG, False) + it = self.iter_next(it) + + self.selection_change_notification() + + """ + Returns two lists. One of user selected recipes and the other containing + all selected recipes + """ + def get_selected_recipes(self): + allrecipes = [] + userrecipes = [] + + it = self.get_iter_first() + while it: + if self.get_value(it, self.COL_INC): + name = self.get_value(it, self.COL_PN) + type = self.get_value(it, self.COL_TYPE) + if type != "image": + allrecipes.append(name) + sel = "User Selected" in self.get_value(it, self.COL_BINB) + if sel: + userrecipes.append(name) + it = self.iter_next(it) + + return list(set(userrecipes)), list(set(allrecipes)) + + def set_selected_recipes(self, recipelist): + for pn in recipelist: + if pn in self.pn_path.keys(): + path = self.pn_path[pn] + self.include_item(item_path=path, + binb="User Selected") + + def get_selected_image(self): + it = self.get_iter_first() + while it: + if self.get_value(it, self.COL_INC): + name = self.get_value(it, self.COL_PN) + type = self.get_value(it, self.COL_TYPE) + if type == "image": + sel = "User Selected" in self.get_value(it, self.COL_BINB) + if sel: + return name + it = self.iter_next(it) + return None + + def set_selected_image(self, img): + if img == None: + return + path = self.find_path_for_item(img) + self.include_item(item_path=path, + binb="User Selected", + image_contents=True) -- cgit v1.2.3-54-g00ecf