class AvailablePane(SoftwarePane): """Widget that represents the available panel in software-center It contains a search entry and navigation buttons """ class Pages: # page names, useful for debuggin NAMES = ("lobby", "subcategory", "list", "details", "purchase") # actual page id's (LOBBY, SUBCATEGORY, LIST, DETAILS, PURCHASE) = range(5) # the default page HOME = LOBBY __gsignals__ = {"available-pane-created": (GObject.SignalFlags.RUN_FIRST, None, ())} def __init__(self, cache, db, distro, icons, datadir, navhistory_back_action, navhistory_forward_action): # parent SoftwarePane.__init__(self, cache, db, distro, icons, datadir) self.searchentry.set_sensitive(False) # navigation history actions self.navhistory_back_action = navhistory_back_action self.navhistory_forward_action = navhistory_forward_action # configure any initial state attrs self.state.filter = AppFilter(db, cache) # the spec says we mix installed/not installed # self.apps_filter.set_not_installed_only(True) self.current_app_by_category = {} self.current_app_by_subcategory = {} self.pane_name = _("Get Software") # views to be created in init_view self.cat_view = None self.subcategories_view = None def init_view(self): if self.view_initialized: return self.show_appview_spinner() window = self.get_window() if window is not None: window.set_cursor(self.busy_cursor) while Gtk.events_pending(): Gtk.main_iteration() # open the cache since we are initializing the UI for the first time GObject.idle_add(self.cache.open) SoftwarePane.init_view(self) # set the AppTreeView model, available pane uses list models liststore = AppListStore(self.db, self.cache, self.icons) # ~ def on_appcount_changed(widget, appcount): # ~ self.subcategories_view._append_appcount(appcount) # ~ self.app_view._append_appcount(appcount) # ~ liststore.connect('appcount-changed', on_appcount_changed) self.app_view.set_model(liststore) # setup purchase stuff self.app_details_view.connect("purchase-requested", self.on_purchase_requested) # purchase view self.purchase_view = PurchaseView() self.purchase_view.connect("purchase-succeeded", self.on_purchase_succeeded) self.purchase_view.connect("purchase-failed", self.on_purchase_failed) self.purchase_view.connect("purchase-cancelled-by-user", self.on_purchase_cancelled_by_user) # categories, appview and details into the notebook in the bottom self.scroll_categories = Gtk.ScrolledWindow() self.scroll_categories.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) self.cat_view = LobbyViewGtk(self.datadir, APP_INSTALL_PATH, self.cache, self.db, self.icons, self.apps_filter) self.scroll_categories.add(self.cat_view) self.notebook.append_page(self.scroll_categories, Gtk.Label(label="categories")) # sub-categories view self.subcategories_view = SubCategoryViewGtk( self.datadir, APP_INSTALL_PATH, self.cache, self.db, self.icons, self.apps_filter, root_category=self.cat_view.categories[0], ) self.subcategories_view.connect("category-selected", self.on_subcategory_activated) self.subcategories_view.connect("application-activated", self.on_application_activated) self.subcategories_view.connect("show-category-applist", self.on_show_category_applist) # FIXME: why do we have two application-{selected,activated] ?!? self.subcategories_view.connect("application-selected", self.on_application_selected) self.subcategories_view.connect("application-activated", self.on_application_activated) self.scroll_subcategories = Gtk.ScrolledWindow() self.scroll_subcategories.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) self.scroll_subcategories.add(self.subcategories_view) self.notebook.append_page(self.scroll_subcategories, Gtk.Label(label=NavButtons.SUBCAT)) # app list self.notebook.append_page(self.box_app_list, Gtk.Label(label=NavButtons.LIST)) self.cat_view.connect("category-selected", self.on_category_activated) self.cat_view.connect("application-selected", self.on_application_selected) self.cat_view.connect("application-activated", self.on_application_activated) # details self.notebook.append_page(self.scroll_details, Gtk.Label(label=NavButtons.DETAILS)) # purchase view self.notebook.append_page(self.purchase_view, Gtk.Label(label=NavButtons.PURCHASE)) # install backend self.backend.connect("transactions-changed", self._on_transactions_changed) # now we are initialized self.searchentry.set_sensitive(True) self.emit("available-pane-created") self.show_all() self.hide_appview_spinner() # consider the view initialized here already as display_page() # may run into a endless recurison otherwise (it will call init_view()) # again (LP: #851671) self.view_initialized = True vm = get_viewmanager() vm.display_page(self, AvailablePane.Pages.LOBBY, self.state, self.display_lobby_page) if window is not None: window.set_cursor(None) def on_purchase_requested(self, widget, app, url): self.appdetails = app.get_details(self.db) iconname = self.appdetails.icon self.purchase_view.initiate_purchase(app, iconname, url) vm = get_viewmanager() vm.display_page(self, AvailablePane.Pages.PURCHASE, self.state, self.display_purchase) def on_purchase_succeeded(self, widget): # switch to the details page to display the transaction is in progress self._return_to_appdetails_view() def on_purchase_failed(self, widget): self._return_to_appdetails_view() dialogs.error( None, _("Failure in the purchase process."), _("Sorry, something went wrong. Your payment " "has been cancelled."), ) def on_purchase_cancelled_by_user(self, widget): self._return_to_appdetails_view() def _return_to_appdetails_view(self): vm = get_viewmanager() vm.nav_back() # don't keep the purchase view in navigation history # as its contents are no longer valid vm.clear_forward_history() window = self.get_window() if window is not None: window.set_cursor(None) 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) query = None if self.state.channel and self.state.channel.query: query = self.state.channel.query elif self.state.subcategory: query = self.state.subcategory.query elif self.state.category: query = self.state.category.query # mix channel/category with the search terms and return query return self.db.get_query_list_from_search_entry(self.state.search_term, query) def _in_no_display_category(self): """return True if we are in a category with NoDisplay set in the XML""" return ( self.state.category and self.state.category.dont_display and not self.state.subcategory and not self.state.search_term ) def _get_header_for_view_state(self, view_state): channel = view_state.channel category = view_state.category subcategory = view_state.subcategory line1 = None line2 = None if channel is not None: name = channel.display_name or channel.name line1 = GObject.markup_escape_text(name) elif subcategory is not None: line1 = GObject.markup_escape_text(category.name) line2 = GObject.markup_escape_text(subcategory.name) elif category is not None: line1 = GObject.markup_escape_text(category.name) else: line1 = _("All Software") return line1, line2 # ~ def _show_hide_subcategories(self, show_category_applist=False): # ~ # check if have subcategories and are not in a subcategory # ~ # view - if so, show it # ~ if (self.notebook.get_current_page() == AvailablePane.Pages.LOBBY or # ~ self.notebook.get_current_page() == AvailablePane.Pages.DETAILS): # ~ return # ~ if (not show_category_applist and # ~ self.state.category and # ~ self.state.category.subcategories and # ~ not (self.state.search_term or self.state.subcategory)): # ~ self.subcategories_view.set_subcategory(self.state.category, # ~ num_items=len(self.app_view.get_model())) # ~ self.notebook.set_current_page(AvailablePane.Pages.SUBCATEGORY) # ~ else: # ~ self.notebook.set_current_page(AvailablePane.Pages.LIST) def get_current_app(self): """return the current active application object""" if self.is_category_view_showing(): return None else: if self.state.subcategory: return self.current_app_by_subcategory.get(self.state.subcategory) else: return self.current_app_by_category.get(self.state.category) def get_current_category(self): """ return the current category that is in use or None """ if self.state.subcategory: return self.state.subcategory elif self.state.category: return self.state.category return None def unset_current_category(self): """ unset the current showing category, but keep e.g. the current search """ self.state.category = None self.state.subcategory = None def _on_transactions_changed(self, *args): """internal helper that keeps the action bar up-to-date by keeping track of the transaction-started signals """ if self._is_custom_list_search(self.state.search_term): self._update_action_bar() def on_app_list_changed(self, pane, length): """internal helper that keeps the status text and the action bar up-to-date by keeping track of the app-list-changed signals """ LOG.debug("applist-changed %s %s" % (pane, length)) super(AvailablePane, self).on_app_list_changed(pane, length) self._update_action_bar() def _update_action_bar(self): self._update_action_bar_buttons() def _update_action_bar_buttons(self): """ update buttons in the action bar to implement the custom package lists feature, see https://wiki.ubuntu.com/SoftwareCenter#Custom%20package%20lists """ if self._is_custom_list_search(self.state.search_term): installable = [] for doc in self.enquirer.get_documents(): pkgname = self.db.get_pkgname(doc) if ( pkgname in self.cache and not self.cache[pkgname].is_installed and not len(self.backend.pending_transactions) > 0 ): app = Application(pkgname=pkgname) installable.append(app) button_text = gettext.ngettext("Install %(amount)s Item", "Install %(amount)s Items", len(installable)) % { "amount": len(installable) } button = self.action_bar.get_button(ActionButtons.INSTALL) if button and installable: # Install all already offered. Update offer. if button.get_label() != button_text: button.set_label(button_text) elif installable: # Install all not yet offered. Offer. self.action_bar.add_button(ActionButtons.INSTALL, button_text, self._install_current_appstore) else: # Install offered, but nothing to install. Clear offer. self.action_bar.remove_button(ActionButtons.INSTALL) else: # Ensure button is removed. self.action_bar.remove_button(ActionButtons.INSTALL) def _install_current_appstore(self): """ Function that installs all applications displayed in the pane. """ pkgnames = [] appnames = [] iconnames = [] self.action_bar.remove_button(ActionButtons.INSTALL) for doc in self.enquirer.get_documents(): pkgname = self.db.get_pkgname(doc) if ( pkgname in self.cache and not self.cache[pkgname].is_installed and pkgname not in self.backend.pending_transactions ): pkgnames.append(pkgname) appnames.append(self.db.get_appname(doc)) # add iconnames iconnames.append(self.db.get_iconname(doc)) self.backend.install_multiple(pkgnames, appnames, iconnames) def set_state(self, nav_item): return def _clear_search(self): self.searchentry.clear_with_no_signal() self.apps_limit = 0 self.apps_search_term = "" def _is_custom_list_search(self, search_term): return search_term and "," in search_term # 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""" LOG.debug("on_search_terms_changed: %s" % new_text) self.state.search_term = new_text # do not hide technical items for a custom list search if self._is_custom_list_search(self.state.search_term): self.nonapps_visible = NonAppVisibility.ALWAYS_VISIBLE vm = get_viewmanager() # 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.state.category and not new_text: # category activate will clear search etc self.state.reset() vm.display_page(self, AvailablePane.Pages.LOBBY, self.state, self.display_lobby_page) return False elif self.state.subcategory and not new_text: vm.display_page(self, AvailablePane.Pages.LIST, self.state, self.display_app_view_page) return False elif self.state.category and self.state.category.subcategories and not new_text: vm.display_page(self, AvailablePane.Pages.SUBCATEGORY, self.state, self.display_subcategory_page) return False vm.display_page(self, AvailablePane.Pages.LIST, self.state, self.display_search_page) def on_db_reopen(self, db): " called when the database is reopened" # print "on_db_open" self.refresh_apps() if self.app_details_view: self.app_details_view.refresh_app() def get_callback_for_page(self, page, state): if page == AvailablePane.Pages.LOBBY: return self.display_lobby_page elif page == AvailablePane.Pages.LIST: if state.search_term: return self.display_search_page else: return self.display_app_view_page elif page == AvailablePane.Pages.SUBCATEGORY: return self.display_subcategory_page elif page == AvailablePane.Pages.DETAILS: return self.display_details_page return self.display_lobby_page def display_lobby_page(self, page, view_state): self.state.reset() self.hide_appview_spinner() self.emit("app-list-changed", len(self.db)) self._clear_search() self.action_bar.clear() return True def display_search_page(self, page, view_state): new_text = view_state.search_term # DTRT if the search is reseted if not new_text: self._clear_search() else: self.state.limit = DEFAULT_SEARCH_LIMIT header_strings = self._get_header_for_view_state(view_state) self.app_view.set_header_labels(*header_strings) self.refresh_apps() self.cat_view.stop_carousels() return True def display_subcategory_page(self, page, view_state): category = view_state.category self.set_category(category) if self.state.search_term or self.searchentry.get_text(): self._clear_search() self.refresh_apps() query = self.get_query() n_matches = self.quick_query(query) self.subcategories_view.set_subcategory(category, n_matches) self.action_bar.clear() self.cat_view.stop_carousels() return True def display_app_view_page(self, page, view_state): category = view_state.category self.set_category(category) header_strings = self._get_header_for_view_state(view_state) self.app_view.set_header_labels(*header_strings) # hide the sort combobox headers if the category forces a # custom sort mode allow_user_sort = category is None or not category.is_forced_sort_mode self.app_view.set_allow_user_sorting(allow_user_sort) if view_state.search_term: self._clear_search() self.refresh_apps() self.cat_view.stop_carousels() return True def display_details_page(self, page, view_state): if self.searchentry.get_text() != self.state.search_term: self.searchentry.set_text_with_no_signal(self.state.search_term) self.action_bar.clear() SoftwarePane.display_details_page(self, page, view_state) self.cat_view.stop_carousels() return True def display_purchase(self, page, view_state): self.notebook.set_current_page(AvailablePane.Pages.PURCHASE) self.action_bar.clear() self.cat_view.stop_carousels() return def display_previous_purchases(self, page, view_state): self.nonapps_visible = NonAppVisibility.ALWAYS_VISIBLE self.app_view.set_header_labels(_("Previous Purchases"), None) self.notebook.set_current_page(AvailablePane.Pages.LIST) # do not emit app-list-changed here, this is done async when # the new model is ready self.refresh_apps(query=self.previous_purchases_query) self.action_bar.clear() self.cat_view.stop_carousels() return def on_subcategory_activated(self, subcat_view, category): LOG.debug("on_subcategory_activated: %s %s" % (category.name, category)) self.state.subcategory = category self.state.application = None page = AvailablePane.Pages.LIST vm = get_viewmanager() vm.display_page(self, page, self.state, self.display_app_view_page) def on_category_activated(self, lobby_view, category): """ callback when a category is selected """ LOG.debug("on_category_activated: %s %s" % (category.name, category)) if category.subcategories: page = AvailablePane.Pages.SUBCATEGORY callback = self.display_subcategory_page else: page = AvailablePane.Pages.LIST callback = self.display_app_view_page self.state.category = category self.state.subcategory = None self.state.application = None vm = get_viewmanager() vm.display_page(self, page, self.state, callback) def on_application_selected(self, appview, app): """callback when an app is selected""" LOG.debug("on_application_selected: '%s'" % app) if self.state.subcategory: self.current_app_by_subcategory[self.state.subcategory] = app else: self.current_app_by_category[self.state.category] = app @wait_for_apt_cache_ready def on_application_activated(self, appview, app): """callback when an app is clicked""" LOG.debug("on_application_activated: '%s'" % app) self.state.application = app vm = get_viewmanager() vm.display_page(self, AvailablePane.Pages.DETAILS, self.state, self.display_details_page) def on_show_category_applist(self, widget): self._show_hide_subcategories(show_category_applist=True) def on_previous_purchases_activated(self, query): """ called to activate the previous purchases view """ # print cat_view, name, query LOG.debug("on_previous_purchases_activated with query: %s" % query) self.previous_purchases_query = query vm = get_viewmanager() vm.display_page(self, AvailablePane.Pages.LIST, self.state, self.display_previous_purchases) def is_category_view_showing(self): """ Return True if we are in the category page or if we display a sub-category page """ return ( self.notebook.get_current_page() == AvailablePane.Pages.LOBBY or self.notebook.get_current_page() == AvailablePane.Pages.SUBCATEGORY ) def is_applist_view_showing(self): """Return True if we are in the applist view """ return self.notebook.get_current_page() == AvailablePane.Pages.LIST def is_app_details_view_showing(self): """Return True if we are in the app_details view """ return self.notebook.get_current_page() == AvailablePane.Pages.DETAILS def set_category(self, category): LOG.debug("set_category: %s" % category) self.state.category = category # apply flags if category: if "nonapps-visible" in category.flags: self.nonapps_visible = NonAppVisibility.ALWAYS_VISIBLE else: self.nonapps_visible = NonAppVisibility.MAYBE_VISIBLE # apply any category based filters if not self.state.filter: self.state.filter = AppFilter(self.db, self.cache) if category and category.flags and "available-only" in category.flags: self.state.filter.set_available_only(True) else: self.state.filter.set_available_only(False) if category and category.flags and "not-installed-only" in category.flags: self.state.filter.set_not_installed_only(True) else: self.state.filter.set_not_installed_only(False) def refresh_apps(self, query=None): SoftwarePane.refresh_apps(self, query) # tell the lobby to update its content if self.cat_view: self.cat_view.refresh_apps() # and the subcat view as well... if self.subcategories_view: self.subcategories_view.refresh_apps()
class AvailablePane(SoftwarePane): """Widget that represents the available panel in software-center It contains a search entry and navigation buttons """ class Pages(): # page names, useful for debugging NAMES = ('lobby', 'subcategory', 'list', 'details', 'purchase', ) # actual page id's (LOBBY, SUBCATEGORY, LIST, DETAILS, PURCHASE) = range(5) # the default page HOME = LOBBY __gsignals__ = {'available-pane-created': (GObject.SignalFlags.RUN_FIRST, None, ())} def __init__(self, cache, db, distro, icons, datadir, navhistory_back_action, navhistory_forward_action): # parent SoftwarePane.__init__(self, cache, db, distro, icons, datadir) self.searchentry.set_sensitive(False) # navigation history actions self.navhistory_back_action = navhistory_back_action self.navhistory_forward_action = navhistory_forward_action # configure any initial state attrs self.state.filter = AppFilter(db, cache) # the spec says we mix installed/not installed #self.apps_filter.set_not_installed_only(True) self.current_app_by_category = {} self.current_app_by_subcategory = {} self.pane_name = _("Get Software") # views to be created in init_view self.cat_view = None self.subcategories_view = None # integrate with the Unity launcher self.unity_launcher = UnityLauncher() # keep track of applications that are queued to be added # to the Unity launcher self.unity_launcher_transaction_queue = {} # flag to indicate whether applications should be added to the # unity launcher when installed (this value is initialized by # the config load in app.py) self.add_to_launcher_enabled = True def init_view(self): if self.view_initialized: return self.show_appview_spinner() window = self.get_window() if window is not None: window.set_cursor(self.busy_cursor) while Gtk.events_pending(): Gtk.main_iteration() SoftwarePane.init_view(self) # set the AppTreeView model, available pane uses list models liststore = AppListStore(self.db, self.cache, self.icons) #~ def on_appcount_changed(widget, appcount): #~ self.subcategories_view._append_appcount(appcount) #~ self.app_view._append_appcount(appcount) #~ liststore.connect('appcount-changed', on_appcount_changed) self.app_view.set_model(liststore) liststore.connect("needs-refresh", lambda helper, pkgname: self.app_view.queue_draw()) # purchase view self.purchase_view = PurchaseView() app_manager = get_appmanager() app_manager.connect("purchase-requested", self.on_purchase_requested) self.purchase_view.connect("purchase-succeeded", self.on_purchase_succeeded) self.purchase_view.connect("purchase-failed", self.on_purchase_failed) self.purchase_view.connect("purchase-cancelled-by-user", self.on_purchase_cancelled_by_user) self.purchase_view.connect("terms-of-service-declined", self.on_terms_of_service_declined) self.purchase_view.connect("purchase-needs-spinner", self.on_purchase_needs_spinner) # categories, appview and details into the notebook in the bottom self.scroll_categories = Gtk.ScrolledWindow() self.scroll_categories.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) self.cat_view = LobbyViewGtk(self.datadir, APP_INSTALL_PATH, self.cache, self.db, self.icons, self.apps_filter) self.scroll_categories.add(self.cat_view) self.notebook.append_page(self.scroll_categories, Gtk.Label(label="categories")) # sub-categories view self.subcategories_view = SubCategoryViewGtk(self.datadir, APP_INSTALL_PATH, self.cache, self.db, self.icons, self.apps_filter, root_category=self.cat_view.categories[0]) self.subcategories_view.connect( "category-selected", self.on_subcategory_activated) self.subcategories_view.connect( "application-activated", self.on_application_activated) self.subcategories_view.connect( "show-category-applist", self.on_show_category_applist) # FIXME: why do we have two application-{selected,activated] ?!? self.subcategories_view.connect( "application-selected", self.on_application_selected) self.subcategories_view.connect( "application-activated", self.on_application_activated) self.scroll_subcategories = Gtk.ScrolledWindow() self.scroll_subcategories.set_policy( Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) self.scroll_subcategories.add(self.subcategories_view) self.notebook.append_page(self.scroll_subcategories, Gtk.Label(label=NavButtons.SUBCAT)) # app list self.notebook.append_page(self.box_app_list, Gtk.Label(label=NavButtons.LIST)) self.cat_view.connect( "category-selected", self.on_category_activated) self.cat_view.connect( "application-selected", self.on_application_selected) self.cat_view.connect( "application-activated", self.on_application_activated) # details self.notebook.append_page(self.scroll_details, Gtk.Label(label=NavButtons.DETAILS)) # purchase view self.notebook.append_page(self.purchase_view, Gtk.Label(label=NavButtons.PURCHASE)) # install backend self.backend.connect("transaction-started", self.on_transaction_started) self.backend.connect("transactions-changed", self.on_transactions_changed) self.backend.connect("transaction-finished", self.on_transaction_complete) self.backend.connect("transaction-stopped", self.on_transaction_complete) # now we are initialized self.searchentry.set_sensitive(True) self.emit("available-pane-created") self.show_all() self.hide_appview_spinner() # consider the view initialized here already as display_page() # may run into a endless recurison otherwise (it will call init_view()) # again (LP: #851671) self.view_initialized = True if window is not None: window.set_cursor(None) def on_purchase_requested(self, appmanager, app, iconname, url): if self.purchase_view.initiate_purchase(app, iconname, url): vm = get_viewmanager() vm.display_page( self, AvailablePane.Pages.PURCHASE, self.state, self.display_purchase) def on_purchase_needs_spinner(self, appmanager, active): vm = get_viewmanager() vm.set_spinner_active(active) def on_purchase_succeeded(self, widget): # switch to the details page to display the transaction is in progress self._return_to_appdetails_view() def on_purchase_failed(self, widget): self._return_to_appdetails_view() dialogs.error(None, _("Failure in the purchase process."), _("Sorry, something went wrong. Your payment " "has been cancelled.")) def on_purchase_cancelled_by_user(self, widget): self._return_to_appdetails_view() def on_terms_of_service_declined(self, widget): """ The Terms of Service dialog was declined by the user, so we just reset the purchase button in case they want another chance """ if self.is_app_details_view_showing(): self.app_details_view.pkg_statusbar.button.set_sensitive(True) elif self.is_applist_view_showing(): self.app_view.tree_view.reset_action_button() def _return_to_appdetails_view(self): vm = get_viewmanager() vm.nav_back() # don't keep the purchase view in navigation history # as its contents are no longer valid vm.clear_forward_history() window = self.get_window() if window is not None: window.set_cursor(None) 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) query = None if self.state.channel and self.state.channel.query: query = self.state.channel.query elif self.state.subcategory: query = self.state.subcategory.query elif self.state.category: query = self.state.category.query # mix channel/category with the search terms and return query return self.db.get_query_list_from_search_entry( self.state.search_term, query) def _in_no_display_category(self): """return True if we are in a category with NoDisplay set in the XML""" return (self.state.category and self.state.category.dont_display and not self.state.subcategory and not self.state.search_term) def _get_header_for_view_state(self, view_state): channel = view_state.channel category = view_state.category subcategory = view_state.subcategory line1 = None line2 = None if channel is not None: name = channel.display_name or channel.name line1 = GObject.markup_escape_text(name) elif subcategory is not None: line1 = GObject.markup_escape_text(category.name) line2 = GObject.markup_escape_text(subcategory.name) elif category is not None: line1 = GObject.markup_escape_text(category.name) else: line1 = _("All Software") return line1, line2 #~ def _show_hide_subcategories(self, show_category_applist=False): #~ # check if have subcategories and are not in a subcategory #~ # view - if so, show it #~ current_page = self.notebook.get_current_page() #~ if (current_page == AvailablePane.Pages.LOBBY or #~ current_page == AvailablePane.Pages.DETAILS): #~ return #~ if (not show_category_applist and #~ self.state.category and #~ self.state.category.subcategories and #~ not (self.state.search_term or self.state.subcategory)): #~ self.subcategories_view.set_subcategory(self.state.category, #~ num_items=len(self.app_view.get_model())) #~ self.notebook.set_current_page(AvailablePane.Pages.SUBCATEGORY) #~ else: #~ self.notebook.set_current_page(AvailablePane.Pages.LIST) def get_current_app(self): """return the current active application object""" if self.is_category_view_showing(): return None else: if self.state.subcategory: return self.current_app_by_subcategory.get( self.state.subcategory) else: return self.current_app_by_category.get(self.state.category) def get_current_category(self): """ return the current category that is in use or None """ if self.state.subcategory: return self.state.subcategory elif self.state.category: return self.state.category def unset_current_category(self): """ unset the current showing category, but keep e.g. the current search """ self.state.category = None self.state.subcategory = None # reset the non-global filters see (LP: #985389) if self.state.filter: self.state.filter.reset() def on_transaction_started(self, backend, pkgname, appname, trans_id, trans_type): # we only care about installs for the launcher, queue here for # later, see #972710 if trans_type == TransactionTypes.INSTALL: transaction_details = TransactionDetails( pkgname, appname, trans_id, trans_type) self.unity_launcher_transaction_queue[pkgname] = ( transaction_details) def on_transactions_changed(self, backend, pending_transactions): """internal helper that keeps the action bar up-to-date by keeping track of the transaction-started signals """ if self._is_custom_list_search(self.state.search_term): self._update_action_bar() # add app to unity launcher on the first sign of progress # and remove from the pending queue once that is done for pkgname in pending_transactions: if pkgname in self.unity_launcher_transaction_queue: transaction_details = ( self.unity_launcher_transaction_queue.pop(pkgname)) self._add_application_to_unity_launcher(transaction_details) def on_transaction_complete(self, backend, result): if result.pkgname in self.unity_launcher_transaction_queue: self.unity_launcher_transaction_queue.pop(result.pkgname) def _add_application_to_unity_launcher(self, transaction_details): if not self.add_to_launcher_enabled: return # mvo: use use softwarecenter.utils explicitly so that we can monkey # patch it in the test if not softwarecenter.utils.is_unity_running(): return app = Application(pkgname=transaction_details.pkgname, appname=transaction_details.appname) appdetails = app.get_details(self.db) # we only add items to the launcher that have a desktop file if not appdetails.desktop_file: return # do not add apps that have no Exec entry in their desktop file # (e.g. wine, see LP: #848437 or ubuntu-restricted-extras, # see LP: #913756) if (os.path.exists(appdetails.desktop_file) and not get_exec_line_from_desktop(appdetails.desktop_file)): return # now gather up the unity launcher info items and send the app to the # launcher service launcher_info = self._get_unity_launcher_info(app, appdetails, transaction_details.trans_id) self.unity_launcher.send_application_to_launcher( transaction_details.pkgname, launcher_info) def _get_unity_launcher_info(self, app, appdetails, trans_id): (icon_size, icon_x, icon_y) = ( self._get_onscreen_icon_details_for_launcher_service(app)) icon_path = get_file_path_from_iconname( self.icons, iconname=appdetails.icon_file_name) launcher_info = UnityLauncherInfo(app.name, appdetails.icon, icon_path, icon_x, icon_y, icon_size, appdetails.desktop_file, trans_id) return launcher_info def _get_onscreen_icon_details_for_launcher_service(self, app): if self.is_app_details_view_showing(): return self.app_details_view.get_app_icon_details() elif self.is_applist_view_showing(): return self.app_view.get_app_icon_details() else: # set a default, even though we cannot install from the other panes return (0, 0, 0) def on_app_list_changed(self, pane, length): """internal helper that keeps the status text and the action bar up-to-date by keeping track of the app-list-changed signals """ LOG.debug("applist-changed %s %s" % (pane, length)) super(AvailablePane, self).on_app_list_changed(pane, length) self._update_action_bar() def _update_action_bar(self): self._update_action_bar_buttons() def _update_action_bar_buttons(self): """Update buttons in the action bar to implement the custom package lists feature, see https://wiki.ubuntu.com/SoftwareCenter#Custom%20package%20lists """ if self._is_custom_list_search(self.state.search_term): installable = [] for doc in self.enquirer.get_documents(): pkgname = self.db.get_pkgname(doc) if (pkgname in self.cache and not self.cache[pkgname].is_installed and not len(self.backend.pending_transactions) > 0): app = Application(pkgname=pkgname) installable.append(app) button_text = gettext.ngettext( "Install %(amount)s Item", "Install %(amount)s Items", len(installable)) % {'amount': len(installable)} button = self.action_bar.get_button(ActionButtons.INSTALL) if button and installable: # Install all already offered. Update offer. if button.get_label() != button_text: button.set_label(button_text) elif installable: # Install all not yet offered. Offer. self.action_bar.add_button(ActionButtons.INSTALL, button_text, self._install_current_appstore) else: # Install offered, but nothing to install. Clear offer. self.action_bar.remove_button(ActionButtons.INSTALL) else: # Ensure button is removed. self.action_bar.remove_button(ActionButtons.INSTALL) def _install_current_appstore(self): ''' Function that installs all applications displayed in the pane. ''' apps = [] iconnames = [] self.action_bar.remove_button(ActionButtons.INSTALL) for doc in self.enquirer.get_documents(): pkgname = self.db.get_pkgname(doc) if (pkgname in self.cache and not self.cache[pkgname].is_installed and pkgname not in self.backend.pending_transactions): apps.append(self.db.get_application(doc)) # add iconnames iconnames.append(self.db.get_iconname(doc)) self.backend.install_multiple(apps, iconnames) def _show_or_hide_search_combo_box(self, view_state): # show/hide the sort combobox headers if the category forces a # custom sort mode category = view_state.category allow_user_sort = category is None or not category.is_forced_sort_mode self.app_view.set_allow_user_sorting(allow_user_sort) def set_state(self, nav_item): pass def _clear_search(self): self.searchentry.clear_with_no_signal() self.apps_limit = 0 self.apps_search_term = "" def _is_custom_list_search(self, search_term): return (search_term and ',' in search_term) # 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""" LOG.debug("on_search_terms_changed: %s" % new_text) # reset the flag in the app_view because each new search should # reset the sort criteria self.app_view.reset_default_sort_mode() self.state.search_term = new_text # do not hide technical items for a custom list search if self._is_custom_list_search(self.state.search_term): self.nonapps_visible = NonAppVisibility.ALWAYS_VISIBLE vm = get_viewmanager() adj = self.app_view.tree_view_scroll.get_vadjustment() if adj: adj.set_value(0.0) # 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 or channel if not self.state.category and not self.state.channel and not new_text: # category activate will clear search etc self.state.reset() vm.display_page(self, AvailablePane.Pages.LOBBY, self.state, self.display_lobby_page) return False elif (self.state.subcategory and not new_text): vm.display_page(self, AvailablePane.Pages.LIST, self.state, self.display_app_view_page) return False elif (self.state.category and self.state.category.subcategories and not new_text): vm.display_page(self, AvailablePane.Pages.SUBCATEGORY, self.state, self.display_subcategory_page) return False vm.display_page(self, AvailablePane.Pages.LIST, self.state, self.display_search_page) def on_db_reopen(self, db): " called when the database is reopened" #print "on_db_open" self.refresh_apps() if self.app_details_view: self.app_details_view.refresh_app() def get_callback_for_page(self, page, state): if page == AvailablePane.Pages.LOBBY: return self.display_lobby_page elif page == AvailablePane.Pages.LIST: if state.search_term: return self.display_search_page else: return self.display_app_view_page elif page == AvailablePane.Pages.SUBCATEGORY: return self.display_subcategory_page elif page == AvailablePane.Pages.DETAILS: return self.display_details_page return self.display_lobby_page def display_lobby_page(self, page, view_state): self.state.reset() self.hide_appview_spinner() self.emit("app-list-changed", len(self.db)) self._clear_search() self.action_bar.clear() return True def display_search_page(self, page, view_state): new_text = view_state.search_term # DTRT if the search is reseted if not new_text: self._clear_search() else: self.state.limit = DEFAULT_SEARCH_LIMIT header_strings = self._get_header_for_view_state(view_state) self.app_view.set_header_labels(*header_strings) self._show_or_hide_search_combo_box(view_state) self.app_view.vadj = view_state.vadjustment self.refresh_apps() self.cat_view.stop_carousels() return True def display_subcategory_page(self, page, view_state): category = view_state.category self.set_category(category) if self.state.search_term or self.searchentry.get_text(): self._clear_search() self.refresh_apps() query = self.get_query() n_matches = self.quick_query_len(query) self.subcategories_view.set_subcategory(category, n_matches) self.action_bar.clear() self.cat_view.stop_carousels() return True def display_app_view_page(self, page, view_state): category = view_state.category self.set_category(category) header_strings = self._get_header_for_view_state(view_state) self.app_view.set_header_labels(*header_strings) self._show_or_hide_search_combo_box(view_state) if view_state.search_term: self._clear_search() self.app_view.vadj = view_state.vadjustment self.refresh_apps() self.cat_view.stop_carousels() return True def display_details_page(self, page, view_state): if self.searchentry.get_text() != self.state.search_term: self.searchentry.set_text_with_no_signal( self.state.search_term) self.action_bar.clear() SoftwarePane.display_details_page(self, page, view_state) self.cat_view.stop_carousels() return True def display_purchase(self, page, view_state): self.notebook.set_current_page(AvailablePane.Pages.PURCHASE) self.action_bar.clear() self.cat_view.stop_carousels() def display_previous_purchases(self, page, view_state): self.nonapps_visible = NonAppVisibility.ALWAYS_VISIBLE self.app_view.set_header_labels(_("Previous Purchases"), None) self.notebook.set_current_page(AvailablePane.Pages.LIST) # clear any search terms self._clear_search() # do not emit app-list-changed here, this is done async when # the new model is ready self.refresh_apps(query=self.previous_purchases_query) self.action_bar.clear() self.cat_view.stop_carousels() def on_subcategory_activated(self, subcat_view, category): LOG.debug("on_subcategory_activated: %s %s" % ( category.name, category)) self.state.subcategory = category self.state.application = None page = AvailablePane.Pages.LIST vm = get_viewmanager() vm.display_page(self, page, self.state, self.display_app_view_page) def on_category_activated(self, lobby_view, category): """ callback when a category is selected """ LOG.debug("on_category_activated: %s %s" % ( category.name, category)) if category.subcategories: page = AvailablePane.Pages.SUBCATEGORY callback = self.display_subcategory_page else: page = AvailablePane.Pages.LIST callback = self.display_app_view_page self.state.category = category self.state.subcategory = None self.state.application = None vm = get_viewmanager() vm.display_page(self, page, self.state, callback) def on_application_selected(self, appview, app): """callback when an app is selected""" LOG.debug("on_application_selected: '%s'" % app) if self.state.subcategory: self.current_app_by_subcategory[self.state.subcategory] = app else: self.current_app_by_category[self.state.category] = app @wait_for_apt_cache_ready def on_application_activated(self, appview, app): """callback when an app is clicked""" LOG.debug("on_application_activated: '%s'" % app) self.state.application = app vm = get_viewmanager() vm.display_page(self, AvailablePane.Pages.DETAILS, self.state, self.display_details_page) def on_show_category_applist(self, widget): self._show_hide_subcategories(show_category_applist=True) def on_previous_purchases_activated(self, query): """ called to activate the previous purchases view """ #print cat_view, name, query LOG.debug("on_previous_purchases_activated with query: %s" % query) self.previous_purchases_query = query vm = get_viewmanager() vm.display_page(self, AvailablePane.Pages.LIST, self.state, self.display_previous_purchases) def is_category_view_showing(self): """ Return True if we are in the category page or if we display a sub-category page """ current_page = self.notebook.get_current_page() return (current_page == AvailablePane.Pages.LOBBY or current_page == AvailablePane.Pages.SUBCATEGORY) def is_applist_view_showing(self): """Return True if we are in the applist view """ return self.notebook.get_current_page() == AvailablePane.Pages.LIST def is_app_details_view_showing(self): """Return True if we are in the app_details view """ return self.notebook.get_current_page() == AvailablePane.Pages.DETAILS def set_category(self, category): LOG.debug('set_category: %s' % category) self.state.category = category # apply flags if category: if 'nonapps-visible' in category.flags: self.nonapps_visible = NonAppVisibility.ALWAYS_VISIBLE else: self.nonapps_visible = NonAppVisibility.MAYBE_VISIBLE # apply any category based filters if not self.state.filter: self.state.filter = AppFilter(self.db, self.cache) if (category and category.flags and 'available-only' in category.flags): self.state.filter.set_available_only(True) else: self.state.filter.set_available_only(False) if (category and category.flags and 'not-installed-only' in category.flags): self.state.filter.set_not_installed_only(True) else: self.state.filter.set_not_installed_only(False) def refresh_apps(self, query=None): SoftwarePane.refresh_apps(self, query) # tell the lobby to update its content if self.cat_view: self.cat_view.refresh_apps() # and the subcat view as well... if self.subcategories_view: self.subcategories_view.refresh_apps()
class AvailablePane(SoftwarePane): """Widget that represents the available panel in software-center It contains a search entry and navigation buttons """ class Pages(): # page names, useful for debugging NAMES = ( 'lobby', 'subcategory', 'list', 'details', 'purchase', ) # actual page id's (LOBBY, SUBCATEGORY, LIST, DETAILS, PURCHASE) = range(5) # the default page HOME = LOBBY __gsignals__ = { 'available-pane-created': (GObject.SignalFlags.RUN_FIRST, None, ()) } def __init__(self, cache, db, distro, icons, datadir, navhistory_back_action, navhistory_forward_action): # parent SoftwarePane.__init__(self, cache, db, distro, icons, datadir) self.searchentry.set_sensitive(False) # navigation history actions self.navhistory_back_action = navhistory_back_action self.navhistory_forward_action = navhistory_forward_action # configure any initial state attrs self.state.filter = AppFilter(db, cache) # the spec says we mix installed/not installed #self.apps_filter.set_not_installed_only(True) self.current_app_by_category = {} self.current_app_by_subcategory = {} self.pane_name = _("Get Software") # views to be created in init_view self.cat_view = None self.subcategories_view = None # integrate with the Unity launcher self.unity_launcher = UnityLauncher() # keep track of applications that are queued to be added # to the Unity launcher self.unity_launcher_transaction_queue = {} # flag to indicate whether applications should be added to the # unity launcher when installed (this value is initialized by # the config load in app.py) self.add_to_launcher_enabled = True def init_view(self): if self.view_initialized: return self.show_appview_spinner() window = self.get_window() if window is not None: window.set_cursor(self.busy_cursor) while Gtk.events_pending(): Gtk.main_iteration() SoftwarePane.init_view(self) # set the AppTreeView model, available pane uses list models liststore = AppListStore(self.db, self.cache, self.icons) #~ def on_appcount_changed(widget, appcount): #~ self.subcategories_view._append_appcount(appcount) #~ self.app_view._append_appcount(appcount) #~ liststore.connect('appcount-changed', on_appcount_changed) self.app_view.set_model(liststore) liststore.connect("needs-refresh", lambda helper, pkgname: self.app_view.queue_draw()) # purchase view self.purchase_view = PurchaseView() app_manager = get_appmanager() app_manager.connect("purchase-requested", self.on_purchase_requested) self.purchase_view.connect("purchase-succeeded", self.on_purchase_succeeded) self.purchase_view.connect("purchase-failed", self.on_purchase_failed) self.purchase_view.connect("purchase-cancelled-by-user", self.on_purchase_cancelled_by_user) self.purchase_view.connect("terms-of-service-declined", self.on_terms_of_service_declined) self.purchase_view.connect("purchase-needs-spinner", self.on_purchase_needs_spinner) # categories, appview and details into the notebook in the bottom self.scroll_categories = Gtk.ScrolledWindow() self.scroll_categories.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) self.cat_view = LobbyViewGtk(self.datadir, APP_INSTALL_PATH, self.cache, self.db, self.icons, self.apps_filter) self.scroll_categories.add(self.cat_view) self.notebook.append_page(self.scroll_categories, Gtk.Label(label="categories")) # sub-categories view self.subcategories_view = SubCategoryViewGtk( self.datadir, APP_INSTALL_PATH, self.cache, self.db, self.icons, self.apps_filter, root_category=self.cat_view.categories[0]) self.subcategories_view.connect("category-selected", self.on_subcategory_activated) self.subcategories_view.connect("application-activated", self.on_application_activated) self.subcategories_view.connect("show-category-applist", self.on_show_category_applist) # FIXME: why do we have two application-{selected,activated] ?!? self.subcategories_view.connect("application-selected", self.on_application_selected) self.subcategories_view.connect("application-activated", self.on_application_activated) self.scroll_subcategories = Gtk.ScrolledWindow() self.scroll_subcategories.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) self.scroll_subcategories.add(self.subcategories_view) self.notebook.append_page(self.scroll_subcategories, Gtk.Label(label=NavButtons.SUBCAT)) # app list self.notebook.append_page(self.box_app_list, Gtk.Label(label=NavButtons.LIST)) self.cat_view.connect("category-selected", self.on_category_activated) self.cat_view.connect("application-selected", self.on_application_selected) self.cat_view.connect("application-activated", self.on_application_activated) # details self.notebook.append_page(self.scroll_details, Gtk.Label(label=NavButtons.DETAILS)) # purchase view self.notebook.append_page(self.purchase_view, Gtk.Label(label=NavButtons.PURCHASE)) # install backend self.backend.connect("transaction-started", self.on_transaction_started) self.backend.connect("transactions-changed", self.on_transactions_changed) self.backend.connect("transaction-finished", self.on_transaction_complete) self.backend.connect("transaction-cancelled", self.on_transaction_cancelled) # a transaction error is treated the same as a cancellation self.backend.connect("transaction-stopped", self.on_transaction_cancelled) # now we are initialized self.searchentry.set_sensitive(True) self.emit("available-pane-created") self.show_all() self.hide_appview_spinner() # consider the view initialized here already as display_page() # may run into a endless recurison otherwise (it will call init_view()) # again (LP: #851671) self.view_initialized = True # important to "seed" the initial history stack (LP: #1005104) vm = get_viewmanager() vm.display_page(self, AvailablePane.Pages.LOBBY, self.state, self.display_lobby_page) if window is not None: window.set_cursor(None) def on_purchase_requested(self, appmanager, app, iconname, url): if self.purchase_view.initiate_purchase(app, iconname, url): vm = get_viewmanager() vm.display_page(self, AvailablePane.Pages.PURCHASE, self.state, self.display_purchase) def on_purchase_needs_spinner(self, appmanager, active): vm = get_viewmanager() vm.set_spinner_active(active) def on_purchase_succeeded(self, widget): # switch to the details page to display the transaction is in progress self._return_to_appdetails_view() def on_purchase_failed(self, widget): self._return_to_appdetails_view() dialogs.error( None, _("Failure in the purchase process."), _("Sorry, something went wrong. Your payment " "has been cancelled.")) def on_purchase_cancelled_by_user(self, widget): self._return_to_appdetails_view() def on_terms_of_service_declined(self, widget): """ The Terms of Service dialog was declined by the user, so we just reset the purchase button in case they want another chance """ if self.is_app_details_view_showing(): self.app_details_view.pkg_statusbar.button.set_sensitive(True) elif self.is_applist_view_showing(): self.app_view.tree_view.reset_action_button() def _return_to_appdetails_view(self): vm = get_viewmanager() vm.nav_back() # don't keep the purchase view in navigation history # as its contents are no longer valid vm.clear_forward_history() window = self.get_window() if window is not None: window.set_cursor(None) 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) query = None if self.state.channel and self.state.channel.query: query = self.state.channel.query elif self.state.subcategory: query = self.state.subcategory.query elif self.state.category: query = self.state.category.query # mix channel/category with the search terms and return query return self.db.get_query_list_from_search_entry( self.state.search_term, query) def _in_no_display_category(self): """return True if we are in a category with NoDisplay set in the XML""" return (self.state.category and self.state.category.dont_display and not self.state.subcategory and not self.state.search_term) def _get_header_for_view_state(self, view_state): channel = view_state.channel category = view_state.category subcategory = view_state.subcategory line1 = None line2 = None if channel is not None: name = channel.display_name or channel.name line1 = GObject.markup_escape_text(name) elif subcategory is not None: line1 = GObject.markup_escape_text(category.name) line2 = GObject.markup_escape_text(subcategory.name) elif category is not None: line1 = GObject.markup_escape_text(category.name) else: line1 = _("All Software") return line1, line2 #~ def _show_hide_subcategories(self, show_category_applist=False): #~ # check if have subcategories and are not in a subcategory #~ # view - if so, show it #~ current_page = self.notebook.get_current_page() #~ if (current_page == AvailablePane.Pages.LOBBY or #~ current_page == AvailablePane.Pages.DETAILS): #~ return #~ if (not show_category_applist and #~ self.state.category and #~ self.state.category.subcategories and #~ not (self.state.search_term or self.state.subcategory)): #~ self.subcategories_view.set_subcategory(self.state.category, #~ num_items=len(self.app_view.get_model())) #~ self.notebook.set_current_page(AvailablePane.Pages.SUBCATEGORY) #~ else: #~ self.notebook.set_current_page(AvailablePane.Pages.LIST) def get_current_app(self): """return the current active application object""" if self.is_category_view_showing(): return None else: if self.state.subcategory: return self.current_app_by_subcategory.get( self.state.subcategory) else: return self.current_app_by_category.get(self.state.category) def get_current_category(self): """ return the current category that is in use or None """ if self.state.subcategory: return self.state.subcategory elif self.state.category: return self.state.category def unset_current_category(self): """ unset the current showing category, but keep e.g. the current search """ self.state.category = None self.state.subcategory = None # reset the non-global filters see (LP: #985389) if self.state.filter: self.state.filter.reset() def on_transaction_started(self, backend, pkgname, appname, trans_id, trans_type): self._register_unity_launcher_transaction_started( pkgname, appname, trans_id, trans_type) def on_transactions_changed(self, backend, pending_transactions): """internal helper that keeps the action bar up-to-date by keeping track of the transaction-started signals """ if self._is_custom_list_search(self.state.search_term): self._update_action_bar() def on_transaction_complete(self, backend, result): """ handle a transaction that has completed successfully """ if result.pkgname in self.unity_launcher_transaction_queue: transaction_details = (self.unity_launcher_transaction_queue.pop( result.pkgname)) self._add_application_to_unity_launcher(transaction_details) def on_transaction_cancelled(self, backend, result): """ handle a transaction that has been cancelled """ if result.pkgname in self.unity_launcher_transaction_queue: self.unity_launcher_transaction_queue.pop(result.pkgname) def _register_unity_launcher_transaction_started(self, pkgname, appname, trans_id, trans_type): # at the start of the transaction, we gather details for use later # when it is time to add the installed app to the Unity launcher, # (see #972710). we only care about installs for the launcher # mvo: use softwarecenter.utils explicitly here so that we can monkey # patch it in the test if (trans_type == TransactionTypes.INSTALL and self.add_to_launcher_enabled and softwarecenter.utils.is_unity_running()): transaction_details = TransactionDetails(pkgname, appname, trans_id, trans_type) self.unity_launcher_transaction_queue[pkgname] = ( transaction_details) def _add_application_to_unity_launcher(self, transaction_details): app = Application(pkgname=transaction_details.pkgname, appname=transaction_details.appname) appdetails = app.get_details(self.db) # convert the app-install desktop file location to the actual installed # desktop file location (or in the case of a purchased item from the # agent, generate the correct installed desktop file location) installed_desktop_file_path = ( convert_desktop_file_to_installed_location(appdetails.desktop_file, app.pkgname)) # we only add items to the launcher that have a desktop file if not installed_desktop_file_path: return # do not add apps that have no Exec entry in their desktop file # (e.g. wine, see LP: #848437 or ubuntu-restricted-extras, # see LP: #913756), also, don't add the item if NoDisplay is # specified (see LP: #1006483) if (os.path.exists(installed_desktop_file_path) and (not get_exec_line_from_desktop(installed_desktop_file_path) or is_no_display_desktop_file(installed_desktop_file_path))): return # now gather up the unity launcher info items and send the app to the # launcher service launcher_info = self._get_unity_launcher_info( app, appdetails, installed_desktop_file_path, transaction_details.trans_id) self.unity_launcher.send_application_to_launcher( transaction_details.pkgname, launcher_info) def _get_unity_launcher_info(self, app, appdetails, installed_desktop_file_path, trans_id): (icon_size, icon_x, icon_y) = (self._get_onscreen_icon_details_for_launcher_service(app)) icon_path = get_file_path_from_iconname(self.icons, iconname=appdetails.icon) # Note that the transaction ID is not specified because we are firing # the dbus signal at the very end of the installation now, and the # corresponding fix in Unity is expecting this value to be empty (it # would not be useful to the Unity launcher at this point anyway, # as the corresponding transaction would be complete). # Please see bug LP: #925014 and corresponding Unity branch for # further details. # TODO: If and when we re-implement firing the dbus signal at the # start of the transaction, at that time we will need to replace # the empty string below with the trans_id value. launcher_info = UnityLauncherInfo(app.name, appdetails.icon, icon_path, icon_x, icon_y, icon_size, installed_desktop_file_path, "") return launcher_info def _get_onscreen_icon_details_for_launcher_service(self, app): if self.is_app_details_view_showing(): return self.app_details_view.get_app_icon_details() elif self.is_applist_view_showing(): return self.app_view.get_app_icon_details() else: # set a default, even though we cannot install from the other panes return (0, 0, 0) def on_app_list_changed(self, pane, length): """internal helper that keeps the status text and the action bar up-to-date by keeping track of the app-list-changed signals """ LOG.debug("applist-changed %s %s" % (pane, length)) super(AvailablePane, self).on_app_list_changed(pane, length) self._update_action_bar() def _update_action_bar(self): self._update_action_bar_buttons() def _update_action_bar_buttons(self): """Update buttons in the action bar to implement the custom package lists feature, see https://wiki.ubuntu.com/SoftwareCenter#Custom%20package%20lists """ if self._is_custom_list_search(self.state.search_term): installable = [] for doc in self.enquirer.get_documents(): pkgname = self.db.get_pkgname(doc) if (pkgname in self.cache and not self.cache[pkgname].is_installed and not len(self.backend.pending_transactions) > 0): app = Application(pkgname=pkgname) installable.append(app) button_text = gettext.ngettext("Install %(amount)s Item", "Install %(amount)s Items", len(installable)) % { 'amount': len(installable) } button = self.action_bar.get_button(ActionButtons.INSTALL) if button and installable: # Install all already offered. Update offer. if button.get_label() != button_text: button.set_label(button_text) elif installable: # Install all not yet offered. Offer. self.action_bar.add_button(ActionButtons.INSTALL, button_text, self._install_current_appstore) else: # Install offered, but nothing to install. Clear offer. self.action_bar.remove_button(ActionButtons.INSTALL) else: # Ensure button is removed. self.action_bar.remove_button(ActionButtons.INSTALL) def _install_current_appstore(self): ''' Function that installs all applications displayed in the pane. ''' apps = [] iconnames = [] self.action_bar.remove_button(ActionButtons.INSTALL) for doc in self.enquirer.get_documents(): pkgname = self.db.get_pkgname(doc) if (pkgname in self.cache and not self.cache[pkgname].is_installed and pkgname not in self.backend.pending_transactions): apps.append(self.db.get_application(doc)) # add iconnames iconnames.append(self.db.get_iconname(doc)) self.backend.install_multiple(apps, iconnames) def _show_or_hide_search_combo_box(self, view_state): # show/hide the sort combobox headers if the category forces a # custom sort mode category = view_state.category allow_user_sort = category is None or not category.is_forced_sort_mode self.app_view.set_allow_user_sorting(allow_user_sort) def set_state(self, nav_item): pass def _clear_search(self): self.searchentry.clear_with_no_signal() self.apps_limit = 0 self.apps_search_term = "" self.state.search_term = "" def _is_custom_list_search(self, search_term): return (search_term and ',' in search_term) # 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""" LOG.debug("on_search_terms_changed: %s" % new_text) # reset the flag in the app_view because each new search should # reset the sort criteria self.app_view.reset_default_sort_mode() self.state.search_term = new_text # do not hide technical items for a custom list search if self._is_custom_list_search(self.state.search_term): self.nonapps_visible = NonAppVisibility.ALWAYS_VISIBLE vm = get_viewmanager() adj = self.app_view.tree_view_scroll.get_vadjustment() if adj: adj.set_value(0.0) # 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 or channel if not self.state.category and not self.state.channel and not new_text: # category activate will clear search etc self.state.reset() vm.display_page(self, AvailablePane.Pages.LOBBY, self.state, self.display_lobby_page) return False elif (self.state.subcategory and not new_text): vm.display_page(self, AvailablePane.Pages.LIST, self.state, self.display_app_view_page) return False elif (self.state.category and self.state.category.subcategories and not new_text): vm.display_page(self, AvailablePane.Pages.SUBCATEGORY, self.state, self.display_subcategory_page) return False vm.display_page(self, AvailablePane.Pages.LIST, self.state, self.display_search_page) def on_db_reopen(self, db): """Called when the database is reopened.""" super(AvailablePane, self).on_db_reopen(db) self.refresh_apps() if self.app_details_view: self.app_details_view.refresh_app() def get_callback_for_page(self, page, state): if page == AvailablePane.Pages.LOBBY: return self.display_lobby_page elif page == AvailablePane.Pages.LIST: if state.search_term: return self.display_search_page else: return self.display_app_view_page elif page == AvailablePane.Pages.SUBCATEGORY: return self.display_subcategory_page elif page == AvailablePane.Pages.DETAILS: return self.display_details_page return self.display_lobby_page def display_lobby_page(self, page, view_state): self.state.reset() self.hide_appview_spinner() self.emit("app-list-changed", len(self.db)) self._clear_search() self.action_bar.clear() return True def display_search_page(self, page, view_state): new_text = view_state.search_term # DTRT if the search is reseted if not new_text: self._clear_search() else: self.state.limit = DEFAULT_SEARCH_LIMIT header_strings = self._get_header_for_view_state(view_state) self.app_view.set_header_labels(*header_strings) self._show_or_hide_search_combo_box(view_state) self.app_view.vadj = view_state.vadjustment self.refresh_apps() self.cat_view.stop_carousels() return True def display_subcategory_page(self, page, view_state): category = view_state.category self.set_category(category) if self.state.search_term or self.searchentry.get_text(): self._clear_search() self.refresh_apps() query = self.get_query() n_matches = self.quick_query_len(query) self.subcategories_view.set_subcategory(category, n_matches) self.action_bar.clear() self.cat_view.stop_carousels() return True def display_app_view_page(self, page, view_state): category = view_state.category self.set_category(category) header_strings = self._get_header_for_view_state(view_state) self.app_view.set_header_labels(*header_strings) self._show_or_hide_search_combo_box(view_state) if view_state.search_term: self._clear_search() self.app_view.vadj = view_state.vadjustment self.refresh_apps() self.cat_view.stop_carousels() return True def display_details_page(self, page, view_state): if self.searchentry.get_text() != self.state.search_term: self.searchentry.set_text_with_no_signal(self.state.search_term) self.action_bar.clear() SoftwarePane.display_details_page(self, page, view_state) self.cat_view.stop_carousels() return True def display_purchase(self, page, view_state): self.notebook.set_current_page(AvailablePane.Pages.PURCHASE) self.action_bar.clear() self.cat_view.stop_carousels() def display_previous_purchases(self, page, view_state): self.nonapps_visible = NonAppVisibility.ALWAYS_VISIBLE header_strings = self._get_header_for_view_state(view_state) self.app_view.set_header_labels(*header_strings) self.notebook.set_current_page(AvailablePane.Pages.LIST) # clear any search terms self._clear_search() self.refresh_apps() self.action_bar.clear() self.cat_view.stop_carousels() return True def on_subcategory_activated(self, subcat_view, category): LOG.debug("on_subcategory_activated: %s %s" % (category.name, category)) self.state.subcategory = category self.state.application = None page = AvailablePane.Pages.LIST vm = get_viewmanager() vm.display_page(self, page, self.state, self.display_app_view_page) def on_category_activated(self, lobby_view, category): """ callback when a category is selected """ LOG.debug("on_category_activated: %s %s" % (category.name, category)) if category.subcategories: page = AvailablePane.Pages.SUBCATEGORY callback = self.display_subcategory_page else: page = AvailablePane.Pages.LIST callback = self.display_app_view_page self.state.category = category self.state.subcategory = None self.state.application = None vm = get_viewmanager() vm.display_page(self, page, self.state, callback) def on_application_selected(self, appview, app): """callback when an app is selected""" LOG.debug("on_application_selected: '%s'" % app) if self.state.subcategory: self.current_app_by_subcategory[self.state.subcategory] = app else: self.current_app_by_category[self.state.category] = app @wait_for_apt_cache_ready def on_application_activated(self, appview, app): """callback when an app is clicked""" LOG.debug("on_application_activated: '%s'" % app) self.state.application = app vm = get_viewmanager() vm.display_page(self, AvailablePane.Pages.DETAILS, self.state, self.display_details_page) def on_show_category_applist(self, widget): self._show_hide_subcategories(show_category_applist=True) def on_previous_purchases_activated(self, query): """ called to activate the previous purchases view """ #print cat_view, name, query LOG.debug("on_previous_purchases_activated with query: %s" % query) self.state.channel = SoftwareChannel("Previous Purchases", "software-center-agent", None, channel_query=query) vm = get_viewmanager() vm.display_page(self, AvailablePane.Pages.LIST, self.state, self.display_previous_purchases) def is_category_view_showing(self): """ Return True if we are in the category page or if we display a sub-category page """ current_page = self.notebook.get_current_page() return (current_page == AvailablePane.Pages.LOBBY or current_page == AvailablePane.Pages.SUBCATEGORY) def is_applist_view_showing(self): """Return True if we are in the applist view """ return self.notebook.get_current_page() == AvailablePane.Pages.LIST def is_app_details_view_showing(self): """Return True if we are in the app_details view """ return self.notebook.get_current_page() == AvailablePane.Pages.DETAILS def set_category(self, category): LOG.debug('set_category: %s' % category) self.state.category = category # apply flags if category: if 'nonapps-visible' in category.flags: self.nonapps_visible = NonAppVisibility.ALWAYS_VISIBLE else: self.nonapps_visible = NonAppVisibility.MAYBE_VISIBLE # apply any category based filters if not self.state.filter: self.state.filter = AppFilter(self.db, self.cache) if (category and category.flags and 'available-only' in category.flags): self.state.filter.set_available_only(True) else: self.state.filter.set_available_only(False) if (category and category.flags and 'not-installed-only' in category.flags): self.state.filter.set_not_installed_only(True) else: self.state.filter.set_not_installed_only(False) def refresh_apps(self, query=None): SoftwarePane.refresh_apps(self, query) # tell the lobby to update its content if self.cat_view: self.cat_view.refresh_apps() # and the subcat view as well... if self.subcategories_view: self.subcategories_view.refresh_apps()
class AvailablePane(SoftwarePane): """Widget that represents the available panel in software-center It contains a search entry and navigation buttons """ class Pages(): # page names, useful for debuggin NAMES = ('lobby', 'subcategory', 'list', 'details', 'purchase') # actual page id's (LOBBY, SUBCATEGORY, LIST, DETAILS, PURCHASE) = range(5) # the default page HOME = LOBBY __gsignals__ = { 'available-pane-created': (GObject.SignalFlags.RUN_FIRST, None, ()) } def __init__(self, cache, db, distro, icons, datadir, navhistory_back_action, navhistory_forward_action): # parent SoftwarePane.__init__(self, cache, db, distro, icons, datadir) self.searchentry.set_sensitive(False) # navigation history actions self.navhistory_back_action = navhistory_back_action self.navhistory_forward_action = navhistory_forward_action # configure any initial state attrs self.state.filter = AppFilter(db, cache) # the spec says we mix installed/not installed #self.apps_filter.set_not_installed_only(True) self.current_app_by_category = {} self.current_app_by_subcategory = {} self.pane_name = _("Get Software") # views to be created in init_view self.cat_view = None self.subcategories_view = None def init_view(self): if self.view_initialized: return self.show_appview_spinner() window = self.get_window() if window is not None: window.set_cursor(self.busy_cursor) while Gtk.events_pending(): Gtk.main_iteration() # open the cache since we are initializing the UI for the first time GObject.idle_add(self.cache.open) SoftwarePane.init_view(self) # set the AppTreeView model, available pane uses list models liststore = AppListStore(self.db, self.cache, self.icons) #~ def on_appcount_changed(widget, appcount): #~ self.subcategories_view._append_appcount(appcount) #~ self.app_view._append_appcount(appcount) #~ liststore.connect('appcount-changed', on_appcount_changed) self.app_view.set_model(liststore) # purchase view self.purchase_view = PurchaseView() app_manager = get_appmanager() app_manager.connect("purchase-requested", self.on_purchase_requested) self.purchase_view.connect("purchase-succeeded", self.on_purchase_succeeded) self.purchase_view.connect("purchase-failed", self.on_purchase_failed) self.purchase_view.connect("purchase-cancelled-by-user", self.on_purchase_cancelled_by_user) # categories, appview and details into the notebook in the bottom self.scroll_categories = Gtk.ScrolledWindow() self.scroll_categories.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) self.cat_view = LobbyViewGtk(self.datadir, APP_INSTALL_PATH, self.cache, self.db, self.icons, self.apps_filter) self.scroll_categories.add(self.cat_view) self.notebook.append_page(self.scroll_categories, Gtk.Label(label="categories")) # sub-categories view self.subcategories_view = SubCategoryViewGtk( self.datadir, APP_INSTALL_PATH, self.cache, self.db, self.icons, self.apps_filter, root_category=self.cat_view.categories[0]) self.subcategories_view.connect("category-selected", self.on_subcategory_activated) self.subcategories_view.connect("application-activated", self.on_application_activated) self.subcategories_view.connect("show-category-applist", self.on_show_category_applist) # FIXME: why do we have two application-{selected,activated] ?!? self.subcategories_view.connect("application-selected", self.on_application_selected) self.subcategories_view.connect("application-activated", self.on_application_activated) self.scroll_subcategories = Gtk.ScrolledWindow() self.scroll_subcategories.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) self.scroll_subcategories.add(self.subcategories_view) self.notebook.append_page(self.scroll_subcategories, Gtk.Label(label=NavButtons.SUBCAT)) # app list self.notebook.append_page(self.box_app_list, Gtk.Label(label=NavButtons.LIST)) self.cat_view.connect("category-selected", self.on_category_activated) self.cat_view.connect("application-selected", self.on_application_selected) self.cat_view.connect("application-activated", self.on_application_activated) # details self.notebook.append_page(self.scroll_details, Gtk.Label(label=NavButtons.DETAILS)) # purchase view self.notebook.append_page(self.purchase_view, Gtk.Label(label=NavButtons.PURCHASE)) # install backend self.backend.connect("transactions-changed", self._on_transactions_changed) # now we are initialized self.searchentry.set_sensitive(True) self.emit("available-pane-created") self.show_all() self.hide_appview_spinner() # consider the view initialized here already as display_page() # may run into a endless recurison otherwise (it will call init_view()) # again (LP: #851671) self.view_initialized = True vm = get_viewmanager() vm.display_page(self, AvailablePane.Pages.LOBBY, self.state, self.display_lobby_page) if window is not None: window.set_cursor(None) def on_purchase_requested(self, appmanager, app, iconname, url): self.purchase_view.initiate_purchase(app, iconname, url) vm = get_viewmanager() vm.display_page(self, AvailablePane.Pages.PURCHASE, self.state, self.display_purchase) return def on_purchase_succeeded(self, widget): # switch to the details page to display the transaction is in progress self._return_to_appdetails_view() def on_purchase_failed(self, widget): self._return_to_appdetails_view() dialogs.error( None, _("Failure in the purchase process."), _("Sorry, something went wrong. Your payment " "has been cancelled.")) def on_purchase_cancelled_by_user(self, widget): self._return_to_appdetails_view() def _return_to_appdetails_view(self): vm = get_viewmanager() vm.nav_back() # don't keep the purchase view in navigation history # as its contents are no longer valid vm.clear_forward_history() window = self.get_window() if window is not None: window.set_cursor(None) 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) query = None if self.state.channel and self.state.channel.query: query = self.state.channel.query elif self.state.subcategory: query = self.state.subcategory.query elif self.state.category: query = self.state.category.query # mix channel/category with the search terms and return query return self.db.get_query_list_from_search_entry( self.state.search_term, query) def _in_no_display_category(self): """return True if we are in a category with NoDisplay set in the XML""" return (self.state.category and self.state.category.dont_display and not self.state.subcategory and not self.state.search_term) def _get_header_for_view_state(self, view_state): channel = view_state.channel category = view_state.category subcategory = view_state.subcategory line1 = None line2 = None if channel is not None: name = channel.display_name or channel.name line1 = GObject.markup_escape_text(name) elif subcategory is not None: line1 = GObject.markup_escape_text(category.name) line2 = GObject.markup_escape_text(subcategory.name) elif category is not None: line1 = GObject.markup_escape_text(category.name) else: line1 = _("All Software") return line1, line2 #~ def _show_hide_subcategories(self, show_category_applist=False): #~ # check if have subcategories and are not in a subcategory #~ # view - if so, show it #~ if (self.notebook.get_current_page() == AvailablePane.Pages.LOBBY or #~ self.notebook.get_current_page() == AvailablePane.Pages.DETAILS): #~ return #~ if (not show_category_applist and #~ self.state.category and #~ self.state.category.subcategories and #~ not (self.state.search_term or self.state.subcategory)): #~ self.subcategories_view.set_subcategory(self.state.category, #~ num_items=len(self.app_view.get_model())) #~ self.notebook.set_current_page(AvailablePane.Pages.SUBCATEGORY) #~ else: #~ self.notebook.set_current_page(AvailablePane.Pages.LIST) def get_current_app(self): """return the current active application object""" if self.is_category_view_showing(): return None else: if self.state.subcategory: return self.current_app_by_subcategory.get( self.state.subcategory) else: return self.current_app_by_category.get(self.state.category) def get_current_category(self): """ return the current category that is in use or None """ if self.state.subcategory: return self.state.subcategory elif self.state.category: return self.state.category return None def unset_current_category(self): """ unset the current showing category, but keep e.g. the current search """ self.state.category = None self.state.subcategory = None def _on_transactions_changed(self, *args): """internal helper that keeps the action bar up-to-date by keeping track of the transaction-started signals """ if self._is_custom_list_search(self.state.search_term): self._update_action_bar() def on_app_list_changed(self, pane, length): """internal helper that keeps the status text and the action bar up-to-date by keeping track of the app-list-changed signals """ LOG.debug("applist-changed %s %s" % (pane, length)) super(AvailablePane, self).on_app_list_changed(pane, length) self._update_action_bar() def _update_action_bar(self): self._update_action_bar_buttons() def _update_action_bar_buttons(self): ''' update buttons in the action bar to implement the custom package lists feature, see https://wiki.ubuntu.com/SoftwareCenter#Custom%20package%20lists ''' if self._is_custom_list_search(self.state.search_term): installable = [] for doc in self.enquirer.get_documents(): pkgname = self.db.get_pkgname(doc) if (pkgname in self.cache and not self.cache[pkgname].is_installed and not len(self.backend.pending_transactions) > 0): app = Application(pkgname=pkgname) installable.append(app) button_text = gettext.ngettext("Install %(amount)s Item", "Install %(amount)s Items", len(installable)) % { 'amount': len(installable), } button = self.action_bar.get_button(ActionButtons.INSTALL) if button and installable: # Install all already offered. Update offer. if button.get_label() != button_text: button.set_label(button_text) elif installable: # Install all not yet offered. Offer. self.action_bar.add_button(ActionButtons.INSTALL, button_text, self._install_current_appstore) else: # Install offered, but nothing to install. Clear offer. self.action_bar.remove_button(ActionButtons.INSTALL) else: # Ensure button is removed. self.action_bar.remove_button(ActionButtons.INSTALL) def _install_current_appstore(self): ''' Function that installs all applications displayed in the pane. ''' pkgnames = [] appnames = [] iconnames = [] self.action_bar.remove_button(ActionButtons.INSTALL) for doc in self.enquirer.get_documents(): pkgname = self.db.get_pkgname(doc) if (pkgname in self.cache and not self.cache[pkgname].is_installed and pkgname not in self.backend.pending_transactions): pkgnames.append(pkgname) appnames.append(self.db.get_appname(doc)) # add iconnames iconnames.append(self.db.get_iconname(doc)) self.backend.install_multiple(pkgnames, appnames, iconnames) def set_state(self, nav_item): return def _clear_search(self): self.searchentry.clear_with_no_signal() self.apps_limit = 0 self.apps_search_term = "" def _is_custom_list_search(self, search_term): return (search_term and ',' in search_term) # 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""" LOG.debug("on_search_terms_changed: %s" % new_text) self.state.search_term = new_text # do not hide technical items for a custom list search if self._is_custom_list_search(self.state.search_term): self.nonapps_visible = NonAppVisibility.ALWAYS_VISIBLE vm = get_viewmanager() # 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 or channel if not self.state.category and not self.state.channel and not new_text: # category activate will clear search etc self.state.reset() vm.display_page(self, AvailablePane.Pages.LOBBY, self.state, self.display_lobby_page) return False elif (self.state.subcategory and not new_text): vm.display_page(self, AvailablePane.Pages.LIST, self.state, self.display_app_view_page) return False elif (self.state.category and self.state.category.subcategories and not new_text): vm.display_page(self, AvailablePane.Pages.SUBCATEGORY, self.state, self.display_subcategory_page) return False vm.display_page(self, AvailablePane.Pages.LIST, self.state, self.display_search_page) def on_db_reopen(self, db): " called when the database is reopened" #print "on_db_open" self.refresh_apps() if self.app_details_view: self.app_details_view.refresh_app() def get_callback_for_page(self, page, state): if page == AvailablePane.Pages.LOBBY: return self.display_lobby_page elif page == AvailablePane.Pages.LIST: if state.search_term: return self.display_search_page else: return self.display_app_view_page elif page == AvailablePane.Pages.SUBCATEGORY: return self.display_subcategory_page elif page == AvailablePane.Pages.DETAILS: return self.display_details_page return self.display_lobby_page def display_lobby_page(self, page, view_state): self.state.reset() self.hide_appview_spinner() self.emit("app-list-changed", len(self.db)) self._clear_search() self.action_bar.clear() return True def display_search_page(self, page, view_state): new_text = view_state.search_term # DTRT if the search is reseted if not new_text: self._clear_search() else: self.state.limit = DEFAULT_SEARCH_LIMIT header_strings = self._get_header_for_view_state(view_state) self.app_view.set_header_labels(*header_strings) self.refresh_apps() self.cat_view.stop_carousels() return True def display_subcategory_page(self, page, view_state): category = view_state.category self.set_category(category) if self.state.search_term or self.searchentry.get_text(): self._clear_search() self.refresh_apps() query = self.get_query() n_matches = self.quick_query(query) self.subcategories_view.set_subcategory(category, n_matches) self.action_bar.clear() self.cat_view.stop_carousels() return True def display_app_view_page(self, page, view_state): category = view_state.category self.set_category(category) header_strings = self._get_header_for_view_state(view_state) self.app_view.set_header_labels(*header_strings) # hide the sort combobox headers if the category forces a # custom sort mode allow_user_sort = category is None or not category.is_forced_sort_mode self.app_view.set_allow_user_sorting(allow_user_sort) if view_state.search_term: self._clear_search() self.refresh_apps() self.cat_view.stop_carousels() return True def display_details_page(self, page, view_state): if self.searchentry.get_text() != self.state.search_term: self.searchentry.set_text_with_no_signal(self.state.search_term) self.action_bar.clear() SoftwarePane.display_details_page(self, page, view_state) self.cat_view.stop_carousels() return True def display_purchase(self, page, view_state): self.notebook.set_current_page(AvailablePane.Pages.PURCHASE) self.action_bar.clear() self.cat_view.stop_carousels() return def display_previous_purchases(self, page, view_state): self.nonapps_visible = NonAppVisibility.ALWAYS_VISIBLE self.app_view.set_header_labels(_("Previous Purchases"), None) self.notebook.set_current_page(AvailablePane.Pages.LIST) # do not emit app-list-changed here, this is done async when # the new model is ready self.refresh_apps(query=self.previous_purchases_query) self.action_bar.clear() self.cat_view.stop_carousels() return def on_subcategory_activated(self, subcat_view, category): LOG.debug("on_subcategory_activated: %s %s" % (category.name, category)) self.state.subcategory = category self.state.application = None page = AvailablePane.Pages.LIST vm = get_viewmanager() vm.display_page(self, page, self.state, self.display_app_view_page) def on_category_activated(self, lobby_view, category): """ callback when a category is selected """ LOG.debug("on_category_activated: %s %s" % (category.name, category)) if category.subcategories: page = AvailablePane.Pages.SUBCATEGORY callback = self.display_subcategory_page else: page = AvailablePane.Pages.LIST callback = self.display_app_view_page self.state.category = category self.state.subcategory = None self.state.application = None vm = get_viewmanager() vm.display_page(self, page, self.state, callback) def on_application_selected(self, appview, app): """callback when an app is selected""" LOG.debug("on_application_selected: '%s'" % app) if self.state.subcategory: self.current_app_by_subcategory[self.state.subcategory] = app else: self.current_app_by_category[self.state.category] = app @wait_for_apt_cache_ready def on_application_activated(self, appview, app): """callback when an app is clicked""" LOG.debug("on_application_activated: '%s'" % app) self.state.application = app vm = get_viewmanager() vm.display_page(self, AvailablePane.Pages.DETAILS, self.state, self.display_details_page) def on_show_category_applist(self, widget): self._show_hide_subcategories(show_category_applist=True) def on_previous_purchases_activated(self, query): """ called to activate the previous purchases view """ #print cat_view, name, query LOG.debug("on_previous_purchases_activated with query: %s" % query) self.previous_purchases_query = query vm = get_viewmanager() vm.display_page(self, AvailablePane.Pages.LIST, self.state, self.display_previous_purchases) def is_category_view_showing(self): """ Return True if we are in the category page or if we display a sub-category page """ return (self.notebook.get_current_page() == AvailablePane.Pages.LOBBY or \ self.notebook.get_current_page() == AvailablePane.Pages.SUBCATEGORY) def is_applist_view_showing(self): """Return True if we are in the applist view """ return self.notebook.get_current_page() == AvailablePane.Pages.LIST def is_app_details_view_showing(self): """Return True if we are in the app_details view """ return self.notebook.get_current_page() == AvailablePane.Pages.DETAILS def set_category(self, category): LOG.debug('set_category: %s' % category) self.state.category = category # apply flags if category: if 'nonapps-visible' in category.flags: self.nonapps_visible = NonAppVisibility.ALWAYS_VISIBLE else: self.nonapps_visible = NonAppVisibility.MAYBE_VISIBLE # apply any category based filters if not self.state.filter: self.state.filter = AppFilter(self.db, self.cache) if category and category.flags and 'available-only' in category.flags: self.state.filter.set_available_only(True) else: self.state.filter.set_available_only(False) if category and category.flags and 'not-installed-only' in category.flags: self.state.filter.set_not_installed_only(True) else: self.state.filter.set_not_installed_only(False) def refresh_apps(self, query=None): SoftwarePane.refresh_apps(self, query) # tell the lobby to update its content if self.cat_view: self.cat_view.refresh_apps() # and the subcat view as well... if self.subcategories_view: self.subcategories_view.refresh_apps()