def _build_ui(self):
     # categories, appview and details into the notebook in the bottom
     self.cat_view = CategoriesView(self.datadir, APP_INSTALL_PATH,
                                    self.db,
                                    self.icons)
     scroll_categories = gtk.ScrolledWindow()
     scroll_categories.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
     scroll_categories.add(self.cat_view)
     self.notebook.append_page(scroll_categories, gtk.Label("categories"))
     # sub-categories view
     self.subcategories_view = CategoriesView(self.datadir,
                                              APP_INSTALL_PATH,
                                              self.db,
                                              self.icons,
                                              self.cat_view.categories[0])
     self.subcategories_view.connect(
         "category-selected", self.on_subcategory_activated)
     self.scroll_subcategories = gtk.ScrolledWindow()
     self.scroll_subcategories.set_policy(
         gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
     self.scroll_subcategories.add(self.subcategories_view)
     # add nav history back/forward buttons
     self.back_forward = BackForwardButton()
     self.back_forward.left.set_sensitive(False)
     self.back_forward.right.set_sensitive(False)
     self.back_forward.connect("left-clicked", self.on_nav_back_clicked)
     self.back_forward.connect("right-clicked", self.on_nav_forward_clicked)
     self.top_hbox.pack_start(self.back_forward, expand=False, padding=self.PADDING)
     # nav buttons first in the panel
     self.top_hbox.reorder_child(self.back_forward, 0)
     # now a vbox for subcategories and applist
     self.apps_vbox = gtk.VPaned()
     self.apps_vbox.pack1(self.scroll_subcategories, resize=True)
     self.apps_vbox.pack2(self.scroll_app_list)
     # app list
     self.cat_view.connect("category-selected", self.on_category_activated)
     self.notebook.append_page(self.apps_vbox, gtk.Label("installed"))
     # details
     self.notebook.append_page(self.scroll_details, gtk.Label(self.NAV_BUTTON_ID_DETAILS))
     # set status text
     self._update_status_text(len(self.db))
     # home button
     self.navigation_bar.add_with_id(_("Get Software"),
                                     self.on_navigation_category,
                                     self.NAV_BUTTON_ID_CATEGORY,
                                     do_callback=True,
                                     animate=False)
class AvailablePane(SoftwarePane):
    """Widget that represents the available panel in software-center
       It contains a search entry and navigation buttons
    """

    DEFAULT_SEARCH_APPS_LIMIT = 200

    (PAGE_CATEGORY,
     PAGE_APPLIST,
     PAGE_APP_DETAILS) = range(3)

    # define ID values for the various buttons found in the navigation bar
    NAV_BUTTON_ID_CATEGORY = "category"
    NAV_BUTTON_ID_LIST     = "list"
    NAV_BUTTON_ID_SUBCAT   = "subcat"
    NAV_BUTTON_ID_DETAILS  = "details"
    NAV_BUTTON_ID_SEARCH   = "search"

    def __init__(self, cache, db, distro, icons, datadir):
        # parent
        SoftwarePane.__init__(self, cache, db, distro, icons, datadir)
        # state
        self.apps_category = None
        self.apps_subcategory = None
        self.apps_search_term = ""
        self.apps_sorted = True
        self.apps_limit = 0
        self.apps_filter = AppViewFilter(db, cache)
        self.apps_filter.set_only_packages_without_applications(True)
        # the spec says we mix installed/not installed
        #self.apps_filter.set_not_installed_only(True)
        self._status_text = ""
        self.connect("app-list-changed", self._on_app_list_changed)
        self.current_app_by_category = {}
        self.current_app_by_subcategory = {}
        # track navigation history
        self.nav_history = NavigationHistory(self)
        # UI
        self._build_ui()

    def _build_ui(self):
        # categories, appview and details into the notebook in the bottom
        self.cat_view = CategoriesView(self.datadir, APP_INSTALL_PATH,
                                       self.db,
                                       self.icons)
        scroll_categories = gtk.ScrolledWindow()
        scroll_categories.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        scroll_categories.add(self.cat_view)
        self.notebook.append_page(scroll_categories, gtk.Label("categories"))
        # sub-categories view
        self.subcategories_view = CategoriesView(self.datadir,
                                                 APP_INSTALL_PATH,
                                                 self.db,
                                                 self.icons,
                                                 self.cat_view.categories[0])
        self.subcategories_view.connect(
            "category-selected", self.on_subcategory_activated)
        self.scroll_subcategories = gtk.ScrolledWindow()
        self.scroll_subcategories.set_policy(
            gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        self.scroll_subcategories.add(self.subcategories_view)
        # add nav history back/forward buttons
        self.back_forward = BackForwardButton()
        self.back_forward.left.set_sensitive(False)
        self.back_forward.right.set_sensitive(False)
        self.back_forward.connect("left-clicked", self.on_nav_back_clicked)
        self.back_forward.connect("right-clicked", self.on_nav_forward_clicked)
        self.top_hbox.pack_start(self.back_forward, expand=False, padding=self.PADDING)
        # nav buttons first in the panel
        self.top_hbox.reorder_child(self.back_forward, 0)
        # now a vbox for subcategories and applist
        self.apps_vbox = gtk.VPaned()
        self.apps_vbox.pack1(self.scroll_subcategories, resize=True)
        self.apps_vbox.pack2(self.scroll_app_list)
        # app list
        self.cat_view.connect("category-selected", self.on_category_activated)
        self.notebook.append_page(self.apps_vbox, gtk.Label("installed"))
        # details
        self.notebook.append_page(self.scroll_details, gtk.Label(self.NAV_BUTTON_ID_DETAILS))
        # set status text
        self._update_status_text(len(self.db))
        # home button
        self.navigation_bar.add_with_id(_("Get Software"),
                                        self.on_navigation_category,
                                        self.NAV_BUTTON_ID_CATEGORY,
                                        do_callback=True,
                                        animate=False)

    def _get_query(self):
        """helper that gets the query for the current category/search mode"""
        # NoDisplay is a specal case
        if self._in_no_display_category():
            return xapian.Query()
        # get current sub-category (or category, but sub-category wins)
        cat_query = None
        if self.apps_subcategory:
            cat_query = self.apps_subcategory.query
        elif self.apps_category:
            cat_query = self.apps_category.query
        # mix category with the search terms and return query
        return self.db.get_query_list_from_search_entry(self.apps_search_term,
                                                        cat_query)

    def _in_no_display_category(self):
        """return True if we are in a category with NoDisplay set in the XML"""
        return (self.apps_category and
                self.apps_category.dont_display and
                not self.apps_subcategory and
                not self.apps_search_term)

    def _show_hide_subcategories(self):
        # check if have subcategories and are not in a subcategory
        # view - if so, show it
        if (self.apps_category and
            self.apps_category.subcategories and
            not (self.apps_search_term or self.apps_subcategory)):
            self.scroll_subcategories.show()
            self.subcategories_view.set_subcategory(self.apps_category)
        else:
            self.scroll_subcategories.hide()

    def _show_hide_applist(self):
        # now check if the apps_category view has entries and if
        # not hide it
        model = self.app_view.get_model()
        if (model and
            len(model) == 0 and
            self.apps_category and
            self.apps_category.subcategories and
            not self.apps_subcategory):
            self.scroll_app_list.hide()
        else:
            self.scroll_app_list.show()

    def refresh_apps(self):
        """refresh the applist after search changes and update the
           navigation bar
        """
        #import traceback
        #print "refresh_apps"
        #print traceback.print_stack()
        logging.debug("refresh_apps")
        # mvo: its important to fist show the subcategories and then
        #      the new model, otherwise we run into visual lack
        self._show_hide_subcategories()
        self._refresh_apps_with_apt_cache()

    @wait_for_apt_cache_ready
    def _refresh_apps_with_apt_cache(self):
        self.refresh_seq_nr += 1
        # build query
        query = self._get_query()
        logging.debug("availablepane query: %s" % query)
        # deactivate the old model, otherwise we have a memleak and
        # a cpu leak
        self.app_view.clear_model()
        # create new model and attach it
        seq_nr = self.refresh_seq_nr
        if self.app_view.window:
            self.app_view.window.set_cursor(self.busy_cursor)
        if self.subcategories_view.window:
            self.subcategories_view.window.set_cursor(self.busy_cursor)
        if self.apps_vbox.window:
            self.apps_vbox.window.set_cursor(self.busy_cursor)
        new_model = AppStore(self.cache,
                             self.db,
                             self.icons,
                             query,
                             limit=self.apps_limit,
                             sort=self.apps_sorted,
                             filter=self.apps_filter)
        # between request of the new model and actual delivery other
        # events may have happend
        if seq_nr != self.refresh_seq_nr:
            logging.info("discarding new model (%s != %s)" % (seq_nr, self.refresh_seq_nr))
            return False

        # set model
        self.app_view.set_model(new_model)
        # check if we show subcategoriy
        self._show_hide_applist()
        self.emit("app-list-changed", len(new_model))
        if self.app_view.window:
            self.app_view.window.set_cursor(None)
        if self.subcategories_view.window:
            self.subcategories_view.window.set_cursor(None)
        if self.apps_vbox.window:
            self.apps_vbox.window.set_cursor(None)
        return False

    def update_navigation_button(self):
        """Update the navigation button"""
        if self.apps_category and not self.apps_search_term:
            cat =  self.apps_category.name
            self.navigation_bar.add_with_id(cat,
                                            self.on_navigation_list,
                                            self.NAV_BUTTON_ID_LIST, 
                                            do_callback=True, 
                                            animate=True)

        elif self.apps_search_term:
            self.navigation_bar.add_with_id(_("Search Results"),
                                            self.on_navigation_search,
                                            self.NAV_BUTTON_ID_SEARCH, 
                                            do_callback=True,
                                            animate=True)

    # status text woo
    def get_status_text(self):
        """return user readable status text suitable for a status bar"""
        # no status text in the details page
        if (self.notebook.get_current_page() == self.PAGE_APP_DETAILS or
            self._in_no_display_category()):
            return ""
        return self._status_text

    def get_current_app(self):
        """return the current active application object"""
        if self.is_category_view_showing():
            return None
        else:
            if self.apps_subcategory:
                return self.current_app_by_subcategory.get(self.apps_subcategory)
            else:
                return self.current_app_by_category.get(self.apps_category)

    def reset_navigation_history(self):
        """
        reset the navigation history and set the history buttons insensitive
        """
        self.nav_history.reset()
        self.back_forward.left.set_sensitive(False)
        self.back_forward.right.set_sensitive(False)

    def _on_app_list_changed(self, pane, length):
        """internal helper that keeps the status text up-to-date by
           keeping track of the app-list-changed signals
        """
        self._update_status_text(length)

    def _update_status_text(self, length):
        """
        update the text in the status bar
        """
        # SPECIAL CASE: in category page show all items in the DB
        if self.notebook.get_current_page() == self.PAGE_CATEGORY:
            length = len(self.db)

        if len(self.searchentry.get_text()) > 0:
            self._status_text = gettext.ngettext("%s matching item",
                                                 "%s matching items",
                                                 length) % length
        else:
            self._status_text = gettext.ngettext("%s item available",
                                                 "%s items available",
                                                 length) % length

    def _show_category_overview(self):
        " helper that shows the category overview "
        # reset category query
        self.apps_category = None
        self.apps_subcategory = None
        # remove pathbar stuff
        self.navigation_bar.remove_all()
        self.notebook.set_current_page(self.PAGE_CATEGORY)
        self.emit("app-list-changed", len(self.db))
        self.searchentry.show()

    def _clear_search(self):
        self.searchentry.clear_with_no_signal()
        self.apps_limit = 0
        self.apps_sorted = True
        self.apps_search_term = ""
        self.navigation_bar.remove_id(self.NAV_BUTTON_ID_SEARCH)

    def _check_nav_history(self, display_cb):
        if self.navigation_bar.get_last().label != self.nav_history.get_last_label():
            nav_item = NavigationItem(self, display_cb)
            self.nav_history.navigate_no_cursor_step(nav_item)
        return

    # callbacks
    def on_cache_ready(self, cache):
        """ refresh the application list when the cache is re-opened """
        # just re-draw in the available pane, nothing but the
        # "is-installed" overlay icon will change when something
        # is installed or removed in the available pane
        self.app_view.queue_draw()

    def on_search_terms_changed(self, widget, new_text):
        """callback when the search entry widget changes"""
        logging.debug("on_search_terms_changed: %s" % new_text)

        # we got the signal after we already switched to a details
        # page, ignore it
        if self.notebook.get_current_page() == self.PAGE_APP_DETAILS:
            return

        # yeah for special cases - as discussed on irc, mpt
        # wants this to return to the category screen *if*
        # we are searching but we are not in any category
        if not self.apps_category and not new_text:
            # category activate will clear search etc
            self.navigation_bar.navigate_up()
            return

        # if the user searches in the "all categories" page, reset the specific
        # category query (to ensure all apps are searched)
        if self.notebook.get_current_page() == self.PAGE_CATEGORY:
            self.apps_category = None
            self.apps_subcategory = None

        # DTRT if the search is reseted
        if not new_text:
            self._clear_search()
        else:
            self.apps_search_term = new_text
            self.apps_sorted = False
            self.apps_limit = self.DEFAULT_SEARCH_APPS_LIMIT
        self.update_navigation_button()
        self.refresh_apps()
        self.notebook.set_current_page(self.PAGE_APPLIST)

    def on_db_reopen(self, db):
        " called when the database is reopened"
        #print "on_db_open"
        self.refresh_apps()
        self._show_category_overview()

    def display_category(self):
        self._clear_search()
        self._show_category_overview()
        return

    def display_search(self):
        self.navigation_bar.remove_id(self.NAV_BUTTON_ID_DETAILS)
        self.notebook.set_current_page(self.PAGE_APPLIST)
        if self.app_view.get_model():
            list_length = len(self.app_view.get_model())
            self.emit("app-list-changed", list_length)
        self.searchentry.show()
        return

    def display_list(self):
        self.navigation_bar.remove_id(self.NAV_BUTTON_ID_SUBCAT)
        self.navigation_bar.remove_id(self.NAV_BUTTON_ID_DETAILS)

        if self.apps_subcategory:
            self.apps_subcategory = None
        self.set_category(self.apps_category)
        if self.apps_search_term:
            self._clear_search()
            self.refresh_apps()

        self.notebook.set_current_page(self.PAGE_APPLIST)
        # do not emit app-list-changed here, this is done async when
        # the new model is ready
        self.searchentry.show()
        return

    def display_list_subcat(self):
        if self.apps_search_term:
            self._clear_search()
            self.refresh_apps()
        self.set_category(self.apps_subcategory)
        self.navigation_bar.remove_id(self.NAV_BUTTON_ID_DETAILS)
        self.notebook.set_current_page(self.PAGE_APPLIST)
        model = self.app_view.get_model()
        if model is not None:
            self.emit("app-list-changed", len(model))
        self.searchentry.show()
        return

    def display_details(self):
        self.notebook.set_current_page(self.PAGE_APP_DETAILS)
        self.searchentry.hide()
        return

    def on_navigation_category(self, pathbar, part):
        """callback when the navigation button with id 'category' is clicked"""
        # clear the search
        self.display_category()
        nav_item = NavigationItem(self, self.display_category)
        self.nav_history.navigate(nav_item)

    def on_navigation_search(self, pathbar, part):
        """ callback when the navigation button with id 'search' is clicked"""
        self.display_search()
        nav_item = NavigationItem(self, self.display_search)
        self.nav_history.navigate(nav_item)

    def on_navigation_list(self, pathbar, part):
        """callback when the navigation button with id 'list' is clicked"""
        self.display_list()
        nav_item = NavigationItem(self, self.display_list)
        self.nav_history.navigate(nav_item)

    def on_navigation_list_subcategory(self, pathbar, part):
        self.display_list_subcat()
        nav_item = NavigationItem(self, self.display_list_subcat)
        self.nav_history.navigate(nav_item)

    def on_navigation_details(self, pathbar, part):
        """callback when the navigation button with id 'details' is clicked"""
        self.display_details()
        nav_item = NavigationItem(self, self.display_details)
        self.nav_history.navigate(nav_item)

    def on_subcategory_activated(self, cat_view, category):
        #print cat_view, name, query
        logging.debug("on_subcategory_activated: %s %s" % (
                category.name, category))
        self.apps_subcategory = category
        #self._check_nav_history(self.display_list)
        self.navigation_bar.add_with_id(
            category.name, self.on_navigation_list_subcategory, self.NAV_BUTTON_ID_SUBCAT)

    def on_category_activated(self, cat_view, category):
        """ callback when a category is selected """
        #print cat_view, name, query
        logging.debug("on_category_activated: %s %s" % (
                category.name, category))
        self.apps_category = category
        self.update_navigation_button()

    def on_application_selected(self, appview, app):
        """callback when an app is selected"""
        logging.debug("on_application_selected: '%s'" % app)

        if self.apps_subcategory:
            #self._check_nav_history(self.display_list_subcat)
            self.current_app_by_subcategory[self.apps_subcategory] = app
        else:
            #self._check_nav_history(self.display_list)
            self.current_app_by_category[self.apps_category] = app

    def on_nav_back_clicked(self, widget, event):
        self.nav_history.nav_back()

    def on_nav_forward_clicked(self, widget, event):
        self.nav_history.nav_forward()

    def is_category_view_showing(self):
        # check if we are in the category page or if we display a
        # sub-category page that has no visible applications
        return (self.notebook.get_current_page() == self.PAGE_CATEGORY or
                not self.scroll_app_list.props.visible)

    def set_category(self, category):
        #print "set_category", category
        #import traceback
        #traceback.print_stack()
        self.update_navigation_button()
        def _cb():
            self.refresh_apps()
            # this is already done earlier
            #self.notebook.set_current_page(self.PAGE_APPLIST)
            return False
        gobject.timeout_add(1, _cb)
        pass