#
# BitBake Graphical GTK User Interface
#
# Copyright (C) 2011        Intel Corporation
#
# Authored by Joshua Lock <josh@linux.intel.com>
# Authored by Dongxiao Xu <dongxiao.xu@intel.com>
# Authored by Shane Wang <shane.wang@intel.com>
#
# 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
from bb.ui.crumbs.hobpages import HobPage

#
# PackageListModel
#
class PackageListModel(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_VER, COL_REV, COL_RNM, COL_SEC, COL_SUM, COL_RDEP, COL_RPROV, COL_SIZE, COL_RCP, COL_BINB, COL_INC, COL_FADE_INC, COL_FONT, COL_FLIST) = range(15)

    __gsignals__ = {
        "package-selection-changed" : (gobject.SIGNAL_RUN_LAST,
                                gobject.TYPE_NONE,
                                ()),
    }

    __toolchain_required_packages__ = ["packagegroup-core-standalone-sdk-target", "packagegroup-core-standalone-sdk-target-dbg"]

    def __init__(self):
        self.rprov_pkg = {}
        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_STRING,
                                gobject.TYPE_STRING,
                                gobject.TYPE_STRING,
                                gobject.TYPE_STRING,
                                gobject.TYPE_BOOLEAN,
                                gobject.TYPE_BOOLEAN,
                                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):
        pkg = item_name
        if item_name not in self.pn_path.keys():
            if item_name not in self.rprov_pkg.keys():
                return None
            pkg = self.rprov_pkg[item_name]
            if pkg not in self.pn_path.keys():
                return None

        return self.pn_path[pkg]

    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):
        name = model.get_value(it, self.COL_NAME)

        for key in filter.keys():
            if key == self.COL_NAME:
                if filter[key] != 'Search packages by name':
                    if name and filter[key] not in name:
                        return False
            else:
                if model.get_value(it, key) not in filter[key]:
                    return False
        self.filtered_nb += 1
        return True

    """
    Create, if required, and return a filtered gtk.TreeModelSort
    containing only the items specified by filter
    """
    def tree_model(self, filter, excluded_items_ahead=False, included_items_ahead=False, search_data=None, initial=False):
        model = self.filter_new()
        self.filtered_nb = 0
        model.set_visible_func(self.tree_model_filter, filter)

        sort = gtk.TreeModelSort(model)
        if initial:
            sort.set_sort_column_id(PackageListModel.COL_NAME, gtk.SORT_ASCENDING)
            sort.set_default_sort_func(None)

        if excluded_items_ahead:
            sort.set_default_sort_func(self.exclude_item_sort_func, search_data)
        elif included_items_ahead:
            sort.set_default_sort_func(self.include_item_sort_func, search_data)
        else:
            if search_data and search_data!='Search recipes by name' and search_data!='Search package groups by name':
                sort.set_default_sort_func(self.sort_func, search_data)
            else:
                sort.set_sort_column_id(PackageListModel.COL_NAME, gtk.SORT_ASCENDING)
                sort.set_default_sort_func(None)

        sort.set_sort_func(PackageListModel.COL_INC, self.sort_column, PackageListModel.COL_INC)
        sort.set_sort_func(PackageListModel.COL_SIZE, self.sort_column, PackageListModel.COL_SIZE)
        sort.set_sort_func(PackageListModel.COL_BINB, self.sort_column, PackageListModel.COL_BINB)
        sort.set_sort_func(PackageListModel.COL_RCP, self.sort_column, PackageListModel.COL_RCP)
        return sort

    def sort_column(self, model, row1, row2, col):
        value1 = model.get_value(row1, col)
        value2 = model.get_value(row2, col)
        if col==PackageListModel.COL_SIZE:
            value1 = HobPage._string_to_size(value1)
            value2 = HobPage._string_to_size(value2)

        cmp_res = cmp(value1, value2)
        if cmp_res!=0:
            if col==PackageListModel.COL_INC:
                return -cmp_res
            else:
                return cmp_res
        else:
            name1 = model.get_value(row1, PackageListModel.COL_NAME)
            name2 = model.get_value(row2, PackageListModel.COL_NAME)
            return cmp(name1,name2)

    def exclude_item_sort_func(self, model, iter1, iter2, user_data=None):
        if user_data:
            val1 = model.get_value(iter1, PackageListModel.COL_NAME)
            val2 = model.get_value(iter2, PackageListModel.COL_NAME)
            if val1.startswith(user_data) and not val2.startswith(user_data):
                return -1
            elif not val1.startswith(user_data) and val2.startswith(user_data):
                return 1
            else:
                return 0
        else:
            val1 = model.get_value(iter1, PackageListModel.COL_FADE_INC)
            val2 = model.get_value(iter2, PackageListModel.COL_INC)
            return ((val1 == True) and (val2 == False))

    def include_item_sort_func(self, model, iter1, iter2, user_data=None):
        if user_data:
            val1 = model.get_value(iter1, PackageListModel.COL_NAME)
            val2 = model.get_value(iter2, PackageListModel.COL_NAME)
            if val1.startswith(user_data) and not val2.startswith(user_data):
                return -1
            elif not val1.startswith(user_data) and val2.startswith(user_data):
                return 1
            else:
                return 0
        else:
            val1 = model.get_value(iter1, PackageListModel.COL_INC)
            val2 = model.get_value(iter2, PackageListModel.COL_INC)
            return ((val1 == False) and (val2 == True))

    def sort_func(self, model, iter1, iter2, user_data):
        val1 = model.get_value(iter1, PackageListModel.COL_NAME)
        val2 = model.get_value(iter2, PackageListModel.COL_NAME)
        if val1.startswith(user_data) and not val2.startswith(user_data):
            return -1
        elif not val1.startswith(user_data) and val2.startswith(user_data):
            return 1
        else:
            return 0

    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):
        it = view_model.get_iter_first()
        while it:
            name = self.find_item_for_path(path)
            view_name = view_model.get_value(it, PackageListModel.COL_NAME)
            if view_name == name:
                view_path = view_model.get_path(it)
                return view_path
            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.
    """
    def populate(self, pkginfolist):
        # First clear the model, in case repopulating
        self.clear()

        def getpkgvalue(pkgdict, key, pkgname, defaultval = None):
            value = pkgdict.get('%s_%s' % (key, pkgname), None)
            if not value:
                value = pkgdict.get(key, defaultval)
            return value

        for pkginfo in pkginfolist:
            pn = pkginfo['PN']
            pv = pkginfo['PV']
            pr = pkginfo['PR']
            pkg = pkginfo['PKG']
            pkgv = getpkgvalue(pkginfo, 'PKGV', pkg)
            pkgr = getpkgvalue(pkginfo, 'PKGR', pkg)
            # PKGSIZE is artificial, will always be overridden with the package name if present
            pkgsize = pkginfo.get('PKGSIZE_%s' % pkg, "0")
            # PKG_%s is the renamed version
            pkg_rename = pkginfo.get('PKG_%s' % pkg, "")
            # The rest may be overridden or not
            section = getpkgvalue(pkginfo, 'SECTION', pkg, "")
            summary = getpkgvalue(pkginfo, 'SUMMARY', pkg, "")
            rdep = getpkgvalue(pkginfo, 'RDEPENDS', pkg, "")
            rrec = getpkgvalue(pkginfo, 'RRECOMMENDS', pkg, "")
            rprov = getpkgvalue(pkginfo, 'RPROVIDES', pkg, "")
            files_list = getpkgvalue(pkginfo, 'FILES_INFO', pkg, "")
            for i in rprov.split():
                self.rprov_pkg[i] = pkg

            recipe = pn + '-' + pv + '-' + pr

            allow_empty = getpkgvalue(pkginfo, 'ALLOW_EMPTY', pkg, "")

            if pkgsize == "0" and not allow_empty:
                continue

            # pkgsize is in KB
            size = HobPage._size_to_string(HobPage._string_to_size(pkgsize + ' KB'))
            self.set(self.append(), 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_RCP, recipe, self.COL_BINB, "",
                     self.COL_INC, False, self.COL_FONT, '10', self.COL_FLIST, files_list)

        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)

    """
    Update the model, send out the notification.
    """
    def selection_change_notification(self):
        self.emit("package-selection-changed")

    """
    Check whether the item at item_path is included or not
    """
    def path_included(self, item_path):
        return self[item_path][self.COL_INC]

    """
    Add this item, and any of its dependencies, to the image contents
    """
    def include_item(self, item_path, binb=""):
        if self.path_included(item_path):
            return

        item_name = self[item_path][self.COL_NAME]
        item_deps = self[item_path][self.COL_RDEP]

        self[item_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_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 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)

    def exclude_item(self, item_path):
        if not self.path_included(item_path):
            return

        self[item_path][self.COL_INC] = False

        item_name = self[item_path][self.COL_NAME]
        item_deps = self[item_path][self.COL_RDEP]
        if item_deps:
            for dep in item_deps.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)

    """
    Empty self.contents by setting the include of each entry to None
    """
    def reset(self):
        it = self.get_iter_first()
        while it:
            self.set(it,
                     self.COL_INC, False,
                     self.COL_BINB, "")
            it = self.iter_next(it)

        self.selection_change_notification()

    def get_selected_packages(self):
        packagelist = []

        it = self.get_iter_first()
        while it:
            if self.get_value(it, self.COL_INC):
                name = self.get_value(it, self.COL_NAME)
                packagelist.append(name)
            it = self.iter_next(it)

        return packagelist

    def get_user_selected_packages(self):
        packagelist = []

        it = self.get_iter_first()
        while it:
            if self.get_value(it, self.COL_INC):
                binb = self.get_value(it, self.COL_BINB)
                if binb == "User Selected":
                    name = self.get_value(it, self.COL_NAME)
                    packagelist.append(name)
            it = self.iter_next(it)

        return packagelist

    def get_selected_packages_toolchain(self):
        packagelist = []

        it = self.get_iter_first()
        while it:
            if self.get_value(it, self.COL_INC):
                name = self.get_value(it, self.COL_NAME)
                if name.endswith("-dev") or name.endswith("-dbg"):
                    packagelist.append(name)
            it = self.iter_next(it)

        return list(set(packagelist + self.__toolchain_required_packages__));

    """
    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, user_selected=False):
        left = []
        binb = 'User Selected' if user_selected else ''
        for pn in packagelist:
            if pn in self.pn_path.keys():
                path = self.pn_path[pn]
                self.include_item(item_path=path, binb=binb)
            else:
                left.append(pn)

        self.selection_change_notification()
        return left

    """
    Return the selected package size, unit is B.
    """
    def get_packages_size(self):
        packages_size = 0
        it = self.get_iter_first()
        while it:
            if self.get_value(it, self.COL_INC):
                str_size = self.get_value(it, self.COL_SIZE)
                if not str_size:
                    continue

                packages_size += HobPage._string_to_size(str_size)

            it = self.iter_next(it)
        return packages_size

    """
    Resync the state of included items to a backup column before performing the fadeout visible effect
    """
    def resync_fadeout_column(self, model_first_iter=None):
        it = model_first_iter
        while it:
            active = self.get_value(it, self.COL_INC)
            self.set(it, self.COL_FADE_INC, active)
            it = self.iter_next(it)

#
# 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, COL_FADE_INC, COL_SUMMARY, COL_VERSION,
     COL_REVISION, COL_HOMEPAGE, COL_BUGTRACKER) = range(17)

    __custom_image__ = "Create your own image"

    __gsignals__ = {
        "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_BOOLEAN,
                                gobject.TYPE_STRING,
                                gobject.TYPE_STRING,
                                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 key == self.COL_NAME:
                if filter[key] != 'Search recipes by name' and filter[key] != 'Search package groups by name':
                    if filter[key] not in name:
                        return False
            else:
                if model.get_value(it, key) not in filter[key]:
                    return False
        self.filtered_nb += 1

        return True

    def exclude_item_sort_func(self, model, iter1, iter2, user_data=None):
        if user_data:
            val1 = model.get_value(iter1, RecipeListModel.COL_NAME)
            val2 = model.get_value(iter2, RecipeListModel.COL_NAME)
            if val1.startswith(user_data) and not val2.startswith(user_data):
                return -1
            elif not val1.startswith(user_data) and val2.startswith(user_data):
                return 1
            else:
                return 0
        else:
            val1 = model.get_value(iter1, RecipeListModel.COL_FADE_INC)
            val2 = model.get_value(iter2, RecipeListModel.COL_INC)
            return ((val1 == True) and (val2 == False))

    def include_item_sort_func(self, model, iter1, iter2, user_data=None):
        if user_data:
            val1 = model.get_value(iter1, RecipeListModel.COL_NAME)
            val2 = model.get_value(iter2, RecipeListModel.COL_NAME)
            if val1.startswith(user_data) and not val2.startswith(user_data):
                return -1
            elif not val1.startswith(user_data) and val2.startswith(user_data):
                return 1
            else:
                return 0
        else:
            val1 = model.get_value(iter1, RecipeListModel.COL_INC)
            val2 = model.get_value(iter2, RecipeListModel.COL_INC)
            return ((val1 == False) and (val2 == True))

    def sort_func(self, model, iter1, iter2, user_data):
        val1 = model.get_value(iter1, RecipeListModel.COL_NAME)
        val2 = model.get_value(iter2, RecipeListModel.COL_NAME)
        if val1.startswith(user_data) and not val2.startswith(user_data):
            return -1
        elif not val1.startswith(user_data) and val2.startswith(user_data):
            return 1
        else:
            return 0

    """
    Create, if required, and return a filtered gtk.TreeModelSort
    containing only the items specified by filter
    """
    def tree_model(self, filter, excluded_items_ahead=False, included_items_ahead=False, search_data=None, initial=False):
        model = self.filter_new()
        self.filtered_nb = 0
        model.set_visible_func(self.tree_model_filter, filter)

        sort = gtk.TreeModelSort(model)
        if initial:
            sort.set_sort_column_id(RecipeListModel.COL_NAME, gtk.SORT_ASCENDING)
            sort.set_default_sort_func(None)

        if excluded_items_ahead:
            sort.set_default_sort_func(self.exclude_item_sort_func, search_data)
        elif included_items_ahead:
            sort.set_default_sort_func(self.include_item_sort_func, search_data)
        else:
            if search_data and search_data!='Search recipes by name' and search_data!='Search package groups by name':
                sort.set_default_sort_func(self.sort_func, search_data)
            else:
                sort.set_sort_column_id(RecipeListModel.COL_NAME, gtk.SORT_ASCENDING)
                sort.set_default_sort_func(None)

        sort.set_sort_func(RecipeListModel.COL_INC, self.sort_column, RecipeListModel.COL_INC)
        sort.set_sort_func(RecipeListModel.COL_GROUP, self.sort_column, RecipeListModel.COL_GROUP)
        sort.set_sort_func(RecipeListModel.COL_BINB, self.sort_column, RecipeListModel.COL_BINB)
        sort.set_sort_func(RecipeListModel.COL_LIC, self.sort_column, RecipeListModel.COL_LIC)
        return sort

    def sort_column(self, model, row1, row2, col):
        value1 = model.get_value(row1, col)
        value2 = model.get_value(row2, col)
        cmp_res = cmp(value1, value2)
        if cmp_res!=0:
            if col==RecipeListModel.COL_INC:
                return -cmp_res
            else:
                return cmp_res
        else:
            name1 = model.get_value(row1, RecipeListModel.COL_NAME)
            name2 = model.get_value(row2, RecipeListModel.COL_NAME)
            return cmp(name1,name2)

    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

    """
    The populate() function takes as input the data from a
    bb.event.TargetsTreeGenerated event and populates the RecipeList.
    """
    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.__custom_image__,
                 self.COL_DESC, "Use 'Edit image' to customize recipes and packages " \
                                "to be included in your image ",
                 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.__custom_image__,
                 self.COL_SUMMARY, "", self.COL_VERSION, "", self.COL_REVISION, "",
                 self.COL_HOMEPAGE, "", self.COL_BUGTRACKER, "")

        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"]
            inherits = event_model["pn"][item]["inherits"]
            summary = event_model["pn"][item]["summary"]
            version = event_model["pn"][item]["version"]
            revision = event_model["pn"][item]["revision"]
            homepage = event_model["pn"][item]["homepage"]
            bugtracker = event_model["pn"][item]["bugtracker"]
            install = []

            depends = event_model["depends"].get(item, []) + event_model["rdepends-pn"].get(item, [])

            if ('packagegroup.bbclass' in " ".join(inherits)):
                atype = 'packagegroup'
            elif ('image.bbclass' in " ".join(inherits)):
                if name != "hob-image":
                    atype = 'image'
                    install = event_model["rdepends-pkg"].get(item, []) + event_model["rrecs-pkg"].get(item, [])
            elif ('meta-' in name):
                atype = 'toolchain'
            elif (name == 'dummy-image' or name == 'dummy-toolchain'):
                atype = 'dummy'
            else:
                atype = 'recipe'

            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.COL_SUMMARY, summary, self.COL_VERSION, version, self.COL_REVISION, revision,
                     self.COL_HOMEPAGE, homepage, self.COL_BUGTRACKER, bugtracker)

        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)

    """
    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]

    """
    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

        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)
        dep_bin = self[item_path][self.COL_BINB].split(', ')
        if self[item_path][self.COL_NAME] in dep_bin:
            dep_bin.remove(self[item_path][self.COL_NAME])
        self[item_path][self.COL_BINB] = ', '.join(dep_bin).lstrip(', ')

    def exclude_item(self, item_path):
        if not self.path_included(item_path):
            return

        self[item_path][self.COL_INC] = False

        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")
        self.selection_change_notification()

    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 not img:
            return
        self.reset()
        path = self.find_path_for_item(img)
        self.include_item(item_path=path,
                          binb="User Selected",
                          image_contents=True)
        self.selection_change_notification()

    def set_custom_image_version(self, version):
        self.custom_image_version = version

    def get_custom_image_version(self):
        return self.custom_image_version
