Beispiel #1
0
 def __init__(self, parent):
     Gtk.Dialog.__init__(self)
     self.set_default_size(420, 400)
     self.set_transient_for(parent)
     self.set_title(_("Terms of Use"))
     # buttons
     self.add_button(_("Decline"), Gtk.ResponseType.NO)
     self.add_button(_("Accept"), Gtk.ResponseType.YES)
     # label
     self.label = Gtk.Label(_(u"One moment, please\u2026"))
     self.label.show()
     # add the label
     box = self.get_action_area()
     box.pack_start(self.label, False, False, 0)
     box.set_child_secondary(self.label, True)
     # hrm, hrm, there really should be a better way
     for itm in box.get_children():
         if itm.get_label() == _("Accept"):
             self.button_accept = itm
             break
     self.button_accept.set_sensitive(False)
     # webkit
     wb = ScrolledWebkitWindow()
     wb.show_all()
     self.webkit = wb.webkit
     self.webkit.connect("notify::load-status",
                         self._on_load_status_changed)
     # content
     content = self.get_content_area()
     self.spinner = SpinnerNotebook(wb)
     self.spinner.show_all()
     content.pack_start(self.spinner, True, True, 0)
Beispiel #2
0
    def __init__(self, cache, db, distro, icons, datadir, show_ratings=True):

        Gtk.VBox.__init__(self)
        BasePane.__init__(self)

        # other classes we need
        self.enquirer = AppEnquire(cache, db)
        self._query_complete_handler = self.enquirer.connect(
            "query-complete", self.on_query_complete)

        self.cache = cache
        self.db = db
        self.distro = distro
        self.icons = icons
        self.datadir = datadir
        self.show_ratings = show_ratings
        self.backend = get_install_backend()
        self.nonapps_visible = NonAppVisibility.MAYBE_VISIBLE
        # refreshes can happen out-of-bound so we need to be sure
        # that we only set the new model (when its available) if
        # the refresh_seq_nr of the ready model matches that of the
        # request (e.g. people click on ubuntu channel, get impatient, click
        # on partner channel)
        self.refresh_seq_nr = 0
        # this should be initialized
        self.apps_search_term = ""
        # Create the basic frame for the common view
        self.state = DisplayState()
        vm = get_viewmanager()
        self.searchentry = vm.get_global_searchentry()
        self.back_forward = vm.get_global_backforward()
        # a notebook below
        self.notebook = Gtk.Notebook()
        if not SOFTWARE_CENTER_DEBUG_TABS:
            self.notebook.set_show_tabs(False)
        self.notebook.set_show_border(False)
        # make a spinner view to display while the applist is loading
        self.spinner_notebook = SpinnerNotebook(self.notebook)
        self.pack_start(self.spinner_notebook, True, True, 0)

        # add a bar at the bottom (hidden by default) for contextual actions
        self.action_bar = ActionBar()
        self.pack_start(self.action_bar, False, True, 0)

        # cursor
        self.busy_cursor = Gdk.Cursor.new(Gdk.CursorType.WATCH)

        # views to be created in init_view
        self.app_view = None
        self.app_details_view = None
Beispiel #3
0
 def __init__(self, parent):
     Gtk.Dialog.__init__(self)
     self.set_default_size(420, 400)
     self.set_transient_for(parent)
     self.set_title(_("Terms of Use"))
     # buttons
     self.add_button(_("Decline"), Gtk.ResponseType.NO)
     self.add_button(_("Accept"), Gtk.ResponseType.YES)
     # label
     self.label = Gtk.Label(_(u"One moment, please\u2026"))
     self.label.show()
     # add the label
     box = self.get_action_area()
     box.pack_start(self.label, False, False, 0)
     box.set_child_secondary(self.label, True)
     # hrm, hrm, there really should be a better way
     for itm in box.get_children():
         if itm.get_label() == _("Accept"):
             self.button_accept = itm
             break
     self.button_accept.set_sensitive(False)
     # webkit
     wb = ScrolledWebkitWindow()
     wb.show_all()
     self.webkit = wb.webkit
     self.webkit.connect(
         "notify::load-status", self._on_load_status_changed)
     # content
     content = self.get_content_area()
     self.spinner = SpinnerNotebook(wb)
     self.spinner.show_all()
     content.pack_start(self.spinner, True, True, 0)
Beispiel #4
0
    def __init__(self, cache, db, distro, icons, datadir, show_ratings=True):

        Gtk.VBox.__init__(self)
        BasePane.__init__(self)

        # other classes we need
        self.enquirer = AppEnquire(cache, db)
        self._query_complete_handler = self.enquirer.connect(
                            "query-complete", self.on_query_complete)

        self.cache = cache
        self.db = db
        self.distro = distro
        self.icons = icons
        self.datadir = datadir
        self.show_ratings = show_ratings
        self.backend = get_install_backend()
        self.nonapps_visible = NonAppVisibility.MAYBE_VISIBLE
        # refreshes can happen out-of-bound so we need to be sure
        # that we only set the new model (when its available) if
        # the refresh_seq_nr of the ready model matches that of the
        # request (e.g. people click on ubuntu channel, get impatient, click
        # on partner channel)
        self.refresh_seq_nr = 0
        # this should be initialized
        self.apps_search_term = ""
        # Create the basic frame for the common view
        self.state = DisplayState()
        vm = get_viewmanager()
        self.searchentry = vm.get_global_searchentry()
        self.back_forward = vm.get_global_backforward()
        # a notebook below
        self.notebook = Gtk.Notebook()
        if not "SOFTWARE_CENTER_DEBUG_TABS" in os.environ:
            self.notebook.set_show_tabs(False)
        self.notebook.set_show_border(False)
        # make a spinner view to display while the applist is loading
        self.spinner_notebook = SpinnerNotebook(self.notebook)
        self.pack_start(self.spinner_notebook, True, True, 0)

        # add a bar at the bottom (hidden by default) for contextual actions
        self.action_bar = ActionBar()
        self.pack_start(self.action_bar, False, True, 0)

        # cursor
        self.busy_cursor = Gdk.Cursor.new(Gdk.CursorType.WATCH)

        # views to be created in init_view
        self.app_view = None
        self.app_details_view = None
 def __init__(self,
              orientation=Gtk.Orientation.VERTICAL,
              spacing=0,
              padding=0,
              show_spinner=False):
     FramedBox.__init__(self, Gtk.Orientation.VERTICAL, spacing, padding)
     # make the header
     self.header = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, spacing)
     self.header_alignment = Gtk.Alignment()
     self.header_alignment.add(self.header)
     self.box.pack_start(self.header_alignment, False, False, 0)
     # make the content box
     self.content_box = Gtk.Box.new(orientation, spacing)
     self.content_box.show()
     # finally, a notebook for the spinner and the content box to share
     self.spinner_notebook = SpinnerNotebook(self.content_box)
     self.box.add(self.spinner_notebook)
Beispiel #6
0
class DialogTos(Gtk.Dialog):

    def __init__(self, parent):
        Gtk.Dialog.__init__(self)
        self.set_default_size(420, 400)
        self.set_transient_for(parent)
        self.set_title(_("Terms of Use"))
        # buttons
        self.add_button(_("Decline"), Gtk.ResponseType.NO)
        self.add_button(_("Accept"), Gtk.ResponseType.YES)
        # label
        self.label = Gtk.Label(_(u"One moment, please\u2026"))
        self.label.show()
        # add the label
        box = self.get_action_area()
        box.pack_start(self.label, False, False, 0)
        box.set_child_secondary(self.label, True)
        # hrm, hrm, there really should be a better way
        for itm in box.get_children():
            if itm.get_label() == _("Accept"):
                self.button_accept = itm
                break
        self.button_accept.set_sensitive(False)
        # webkit
        wb = ScrolledWebkitWindow()
        wb.show_all()
        self.webkit = wb.webkit
        self.webkit.connect(
            "notify::load-status", self._on_load_status_changed)
        # content
        content = self.get_content_area()
        self.spinner = SpinnerNotebook(wb)
        self.spinner.show_all()
        content.pack_start(self.spinner, True, True, 0)

    def run(self):
        self.spinner.show_spinner()
        self.webkit.load_uri(SOFTWARE_CENTER_TOS_LINK_NO_HEADER)
        return Gtk.Dialog.run(self)

    def _on_load_status_changed(self, view, pspec):
        prop = pspec.name
        status = view.get_property(prop)
        if (status == WebKit.LoadStatus.FINISHED or
            status == WebKit.LoadStatus.FAILED):
            self.spinner.hide_spinner()
        if status == WebKit.LoadStatus.FINISHED:
            self.label.set_text(_("Do you accept these terms?"))
            self.button_accept.set_sensitive(True)
class DialogTos(Gtk.Dialog):

    def __init__(self, parent):
        Gtk.Dialog.__init__(self)
        self.set_default_size(420, 400)
        self.set_transient_for(parent)
        self.set_title(_("Terms of Use"))
        # buttons
        self.add_button(_("Decline"), Gtk.ResponseType.NO)
        self.add_button(_("Accept"), Gtk.ResponseType.YES)
        # label
        self.label = Gtk.Label.new(_(u"One moment, please\u2026"))
        self.label.show()
        # add the label
        box = self.get_action_area()
        box.pack_start(self.label, False, False, 0)
        box.set_child_secondary(self.label, True)
        # hrm, hrm, there really should be a better way
        for itm in box.get_children():
            if itm.get_label() == _("Accept"):
                self.button_accept = itm
                break
        self.button_accept.set_sensitive(False)
        # webkit
        wb = ScrolledWebkitWindow()
        wb.show_all()
        self.webkit = wb.webkit
        self.webkit.connect(
            "notify::load-status", self._on_load_status_changed)
        # content
        content = self.get_content_area()
        self.spinner = SpinnerNotebook(wb)
        self.spinner.show_all()
        content.pack_start(self.spinner, True, True, 0)

    def run(self):
        self.spinner.show_spinner()
        self.webkit.load_uri(SOFTWARE_CENTER_TOS_LINK_NO_HEADER)
        return Gtk.Dialog.run(self)

    def _on_load_status_changed(self, view, pspec):
        prop = pspec.name
        status = view.get_property(prop)
        if (status == WebKit.LoadStatus.FINISHED or
                status == WebKit.LoadStatus.FAILED):
            self.spinner.hide_spinner()
        if status == WebKit.LoadStatus.FINISHED:
            self.label.set_text(_("Do you accept these terms?"))
            self.button_accept.set_sensitive(True)
Beispiel #8
0
 def __init__(self, orientation=Gtk.Orientation.VERTICAL,
              spacing=0,
              padding=0,
              show_spinner=False):
     FramedBox.__init__(self, Gtk.Orientation.VERTICAL, spacing, padding)
     # make the header
     self.header = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, spacing)
     self.header_alignment = Gtk.Alignment()
     self.header_alignment.add(self.header)
     self.box.pack_start(self.header_alignment, False, False, 0)
     # make the content box
     self.content_box = Gtk.Box.new(orientation, spacing)
     self.content_box.show()
     # finally, a notebook for the spinner and the content box to share
     self.spinner_notebook = SpinnerNotebook(self.content_box,
                                             spinner_size=SpinnerView.SMALL)
     self.box.add(self.spinner_notebook)
     # make the "More" button, but don't add it to the header unless/until
     # we get a header_implements_more_button
     self.more = MoreLink()
Beispiel #9
0
class InstalledPane(SoftwarePane, CategoriesParser):
    """Widget that represents the installed panel in software-center
       It contains a search entry and navigation buttons
    """
    class Pages():
        # page names, useful for debugging
        NAMES = ('list', 'details')
        # the actual page id's
        (LIST, DETAILS) = range(2)
        # the default page
        HOME = LIST

    # pages for the installed view spinner notebook
    (PAGE_SPINNER, PAGE_INSTALLED) = range(2)

    __gsignals__ = {
        'installed-pane-created': (GObject.SignalFlags.RUN_FIRST, None, ())
    }

    def __init__(self, cache, db, distro, icons, datadir):

        # parent
        SoftwarePane.__init__(self,
                              cache,
                              db,
                              distro,
                              icons,
                              datadir,
                              show_ratings=False)
        CategoriesParser.__init__(self, db)

        self.current_appview_selection = None
        self.icons = icons
        self.loaded = False
        self.pane_name = _("Installed Software")

        self.installed_apps = 0
        # None is local
        self.current_hostid = None
        self.current_hostname = None
        self.oneconf_additional_pkg = set()
        self.oneconf_missing_pkg = set()

        # switches to terminate build in progress
        self._build_in_progress = False
        self._halt_build = False

        self.nonapps_visible = NonAppVisibility.NEVER_VISIBLE

        self.visible_docids = None
        self.visible_cats = {}

        self.installed_spinner_notebook = None

    def init_view(self):
        if self.view_initialized:
            return

        SoftwarePane.init_view(self)

        # show a busy cursor and display the main spinner while we build the
        # view
        window = self.get_window()
        if window:
            window.set_cursor(self.busy_cursor)
        self.show_appview_spinner()

        self.oneconf_viewpickler = OneConfViews(self.icons)
        self.oneconf_viewpickler.register_computer(
            None,
            _("This computer (%s)") % platform.node())
        self.oneconf_viewpickler.select_first()
        self.oneconf_viewpickler.connect('computer-changed',
                                         self._selected_computer_changed)
        self.oneconf_viewpickler.connect('current-inventory-refreshed',
                                         self._current_inventory_need_refresh)

        # Start OneConf
        self.oneconf_handler = get_oneconf_handler(self.oneconf_viewpickler)
        if self.oneconf_handler:
            self.oneconf_handler.connect('show-oneconf-changed',
                                         self._show_oneconf_changed)
            self.oneconf_handler.connect('last-time-sync-changed',
                                         self._last_time_sync_oneconf_changed)

        # OneConf pane
        self.computerpane = Gtk.Paned.new(Gtk.Orientation.HORIZONTAL)
        self.oneconfcontrol = Gtk.Box()
        self.oneconfcontrol.set_orientation(Gtk.Orientation.VERTICAL)
        self.computerpane.pack1(self.oneconfcontrol, False, False)
        # size negotiation takes everything for the first one
        self.oneconfcontrol.set_property('width-request', 200)
        self.box_app_list.pack_start(self.computerpane, True, True, 0)

        scroll = Gtk.ScrolledWindow()
        scroll.set_shadow_type(Gtk.ShadowType.IN)
        scroll.add(self.oneconf_viewpickler)
        self.oneconfcontrol.pack_start(scroll, True, True, 0)

        oneconftoolbar = Gtk.Box()
        oneconftoolbar.set_orientation(Gtk.Orientation.HORIZONTAL)
        oneconfpropertymenu = Gtk.Menu()
        self.oneconfproperty = MenuButton(
            oneconfpropertymenu,
            Gtk.Image.new_from_stock(Gtk.STOCK_PROPERTIES,
                                     Gtk.IconSize.BUTTON))
        self.stopsync_label = _(u"Stop Syncing “%s”")
        stop_oneconf_share_menuitem = Gtk.MenuItem(label=self.stopsync_label %
                                                   platform.node())
        stop_oneconf_share_menuitem.connect(
            "activate", self._on_stop_oneconf_hostshare_clicked)
        stop_oneconf_share_menuitem.show()
        oneconfpropertymenu.append(stop_oneconf_share_menuitem)
        self.oneconfcontrol.pack_start(oneconftoolbar, False, False, 1)
        self.oneconf_last_sync = Gtk.Label()
        self.oneconf_last_sync.set_line_wrap(True)
        oneconftoolbar.pack_start(self.oneconfproperty, False, False, 0)
        oneconftoolbar.pack_start(self.oneconf_last_sync, True, True, 1)

        self.notebook.append_page(self.box_app_list, Gtk.Label(label="list"))

        # details
        self.notebook.append_page(self.scroll_details,
                                  Gtk.Label(label="details"))
        # initial refresh
        self.state.search_term = ""

        # build models and filters
        self.base_model = AppTreeStore(self.db, self.cache, self.icons)

        self.treefilter = self.base_model.filter_new(None)
        self.treefilter.set_visible_func(self._row_visibility_func,
                                         AppTreeStore.COL_ROW_DATA)
        self.app_view.set_model(self.treefilter)
        self.app_view.tree_view.connect("row-collapsed",
                                        self._on_row_collapsed)

        self._all_cats = self.parse_applications_menu(APP_INSTALL_PATH)
        self._all_cats = categories_sorted_by_name(self._all_cats)

        # we do not support the search aid feature in the installedview
        self.box_app_list.remove(self.search_aid)

        # remove here
        self.box_app_list.remove(self.app_view)

        # create a local spinner notebook for the installed view
        self.installed_spinner_notebook = SpinnerNotebook(self.app_view)

        self.computerpane.pack2(self.installed_spinner_notebook, True, True)
        self.show_installed_view_spinner()

        self.show_all()

        # initialize view to hide the oneconf computer selector
        self.oneconf_viewpickler.select_first()
        self.oneconfcontrol.hide()

        # hacky, hide the header
        self.app_view.header_hbox.hide()

        self.hide_appview_spinner()

        # keep track of the current view by tracking its origin
        self.current_displayed_origin = None

        # now we are initialized
        self.emit("installed-pane-created")

        self.view_initialized = True
        return False

    def show_installed_view_spinner(self):
        """ display the local spinner for the installed view panel """
        if self.installed_spinner_notebook:
            self.installed_spinner_notebook.show_spinner()

    def hide_installed_view_spinner(self):
        """ hide the local spinner for the installed view panel """
        if self.installed_spinner_notebook:
            self.installed_spinner_notebook.hide_spinner()

    def _selected_computer_changed(self, oneconf_pickler, hostid, hostname):
        if self.current_hostid == hostid:
            return
        LOG.debug("Selected computer changed to %s (%s)" % (hostid, hostname))
        self.current_hostid = hostid
        self.current_hostname = hostname
        menuitem = self.oneconfproperty.get_menu().get_children()[0]
        if self.current_hostid:
            diff = self.oneconf_handler.oneconf.diff(self.current_hostid, '')
            self.oneconf_additional_pkg, self.oneconf_missing_pkg = diff
            stopsync_hostname = self.current_hostname
            # FIXME for P: oneconf views don't support search
            if self.state.search_term:
                self._search()
        else:
            stopsync_hostname = platform.node()
            self.searchentry.show()
        menuitem.set_label(self.stopsync_label %
                           stopsync_hostname.encode('utf-8'))
        self.refresh_apps()

    def _last_time_sync_oneconf_changed(self, oneconf_handler, msg):
        LOG.debug("refresh latest sync date")
        self.oneconf_last_sync.set_label(msg)

    def _show_oneconf_changed(self, oneconf_handler, oneconf_inventory_shown):
        LOG.debug('Share inventory status changed')
        if oneconf_inventory_shown:
            self.oneconfcontrol.show()
        else:
            self.oneconf_viewpickler.select_first()
            self.oneconfcontrol.hide()

    def _on_stop_oneconf_hostshare_clicked(self, widget):
        LOG.debug("Stop sharing inventory for %s" % self.current_hostname)
        self.oneconf_handler.sync_between_computers(False, self.current_hostid)
        # stop sharing another host than the local one.
        if self.current_hostid:
            self.oneconf_viewpickler.remove_computer(self.current_hostid)
            self.oneconf_viewpickler.select_first()

    def _current_inventory_need_refresh(self, oneconfviews):
        if self.current_hostid:
            diff = self.oneconf_handler.oneconf.diff(self.current_hostid, '')
            self.oneconf_additional_pkg, self.oneconf_missing_pkg = diff
        self.refresh_apps()

    def _on_row_collapsed(self, view, it, path):
        pass

    def _row_visibility_func(self, model, it, col):
        row = model.get_value(it, col)
        if self.visible_docids is None:
            if isinstance(row, CategoryRowReference):
                row.vis_count = row.pkg_count
            return True

        elif isinstance(row, CategoryRowReference):
            return row.untranslated_name in self.visible_cats.keys()

        elif row is None:
            return False

        return row.get_docid() in self.visible_docids

    def _use_category(self, cat):
        # System cat is large and slow to search, filter it in default mode

        if ('carousel-only' in cat.flags
                or ((self.nonapps_visible == NonAppVisibility.NEVER_VISIBLE)
                    and cat.untranslated_name == 'System')):
            return False

        return True

    # override its SoftwarePane._hide_nonapp_pkgs...
    def _hide_nonapp_pkgs(self):
        self.nonapps_visible = NonAppVisibility.NEVER_VISIBLE
        self.refresh_apps()
        return True

    def _save_treeview_state(self):
        # store the state
        expanded_rows = []
        self.app_view.tree_view.map_expanded_rows(
            lambda view, path, data: expanded_rows.append(path.to_string()),
            None)
        va = self.app_view.tree_view_scroll.get_vadjustment()
        if va:
            vadj = va.get_value()
        else:
            vadj = 0
        return expanded_rows, vadj

    def _restore_treeview_state(self, state):
        expanded_rows, vadj = state
        for ind in expanded_rows:
            path = Gtk.TreePath.new_from_string(ind)
            self.app_view.tree_view.expand_row(path, False)
        va = self.app_view.tree_view_scroll.get_vadjustment()
        if va:
            va.set_lower(vadj)
            va.set_value(vadj)

    #~ @interrupt_build_and_wait
    def _build_categorised_installedview(self, keep_state=False):
        LOG.debug('Rebuilding categorised installedview...')

        # display the busy cursor and a local spinner while we build the view
        window = self.get_window()
        if window:
            window.set_cursor(self.busy_cursor)
        self.show_installed_view_spinner()

        if keep_state:
            treeview_state = self._save_treeview_state()

        # disconnect the model to avoid e.g. updates of "cursor-changed"
        #  AppTreeView.expand_path while the model is in rebuild-flux
        self.app_view.set_model(None)

        model = self.base_model  # base model not treefilter
        model.clear()

        def profiled_rebuild_categorised_view():
            with ExecutionTime("rebuild_categorized_view"):
                rebuild_categorised_view()

        def rebuild_categorised_view():
            self.cat_docid_map = {}
            enq = self.enquirer

            i = 0

            while Gtk.events_pending():
                Gtk.main_iteration()

            xfilter = AppFilter(self.db, self.cache)
            xfilter.set_installed_only(True)

            for cat in self._all_cats:
                # for each category do category query and append as a new
                # node to tree_view
                if not self._use_category(cat):
                    continue
                query = self.get_query_for_cat(cat)
                LOG.debug("xfilter.installed_only: %s" %
                          xfilter.installed_only)
                enq.set_query(query,
                              sortmode=SortMethods.BY_ALPHABET,
                              nonapps_visible=self.nonapps_visible,
                              filter=xfilter,
                              nonblocking_load=False,
                              persistent_duplicate_filter=(i > 0))

                L = len(enq.matches)
                if L:
                    i += L
                    docs = enq.get_documents()
                    self.cat_docid_map[cat.untranslated_name] = \
                                        set([doc.get_docid() for doc in docs])
                    model.set_category_documents(cat, docs)

            while Gtk.events_pending():
                Gtk.main_iteration()

            # check for uncategorised pkgs
            if self.state.channel:
                self._run_channel_enquirer(persistent_duplicate_filter=(i > 0))
                L = len(enq.matches)
                if L:
                    # some foo for channels
                    # if no categorised results but in channel, then use
                    # the channel name for the category
                    channel_name = None
                    if not i and self.state.channel:
                        channel_name = self.state.channel.display_name
                    docs = enq.get_documents()
                    tag = channel_name or 'Uncategorized'
                    self.cat_docid_map[tag] = set(
                        [doc.get_docid() for doc in docs])
                    model.set_nocategory_documents(docs,
                                                   untranslated_name=tag,
                                                   display_name=channel_name)
                    i += L

            if i:
                self.app_view.tree_view.set_cursor(Gtk.TreePath(), None, False)
                if i <= 10:
                    self.app_view.tree_view.expand_all()

            # cache the installed app count
            self.installed_count = i
            self.app_view._append_appcount(self.installed_count,
                                           mode=AppView.INSTALLED_MODE)

            self.app_view.set_model(self.treefilter)
            if keep_state:
                self._restore_treeview_state(treeview_state)

            # hide the local spinner
            self.hide_installed_view_spinner()

            if window:
                window.set_cursor(None)

            # reapply search if needed
            if self.state.search_term:
                self._do_search(self.state.search_term)

            self.emit("app-list-changed", i)
            return

        GObject.idle_add(profiled_rebuild_categorised_view)

    def _build_oneconfview(self, keep_state=False):
        LOG.debug('Rebuilding oneconfview for %s...' % self.current_hostid)

        # display the busy cursor and the local spinner while we build the view
        window = self.get_window()
        if window:
            window.set_cursor(self.busy_cursor)
        self.show_installed_view_spinner()

        if keep_state:
            treeview_state = self._save_treeview_state()

        # disconnect the model to avoid e.g. updates of "cursor-changed"
        #  AppTreeView.expand_path while the model is in rebuild-flux
        self.app_view.set_model(None)

        model = self.base_model  # base model not treefilter
        model.clear()

        def profiled_rebuild_oneconfview():
            with ExecutionTime("rebuild_oneconfview"):
                rebuild_oneconfview()

        def rebuild_oneconfview():

            # FIXME for P: hide the search entry
            self.searchentry.hide()

            self.cat_docid_map = {}
            enq = self.enquirer
            query = xapian.Query("")
            if self.state.channel and self.state.channel.query:
                query = xapian.Query(xapian.Query.OP_AND, query,
                                     self.state.channel.query)

            i = 0

            # First search: missing apps only
            xfilter = AppFilter(self.db, self.cache)
            xfilter.set_restricted_list(self.oneconf_additional_pkg)
            xfilter.set_not_installed_only(True)

            enq.set_query(
                query,
                sortmode=SortMethods.BY_ALPHABET,
                nonapps_visible=self.nonapps_visible,
                filter=xfilter,
                nonblocking_load=True,  # we don't block this one for
                # better oneconf responsiveness
                persistent_duplicate_filter=(i > 0))

            L = len(enq.matches)

            if L:
                cat_title = utf8(
                    ngettext(
                        u'%(amount)s item on “%(machine)s” not on this computer',
                        u'%(amount)s items on “%(machine)s” not on this computer',
                        L)) % {
                            'amount': L,
                            'machine': utf8(self.current_hostname)
                        }
                i += L
                docs = enq.get_documents()
                self.cat_docid_map["missingpkg"] = set(
                    [doc.get_docid() for doc in docs])
                model.set_nocategory_documents(
                    docs,
                    untranslated_name="additionalpkg",
                    display_name=cat_title)

            # Second search: additional apps
            xfilter.set_restricted_list(self.oneconf_missing_pkg)
            xfilter.set_not_installed_only(False)
            xfilter.set_installed_only(True)
            enq.set_query(query,
                          sortmode=SortMethods.BY_ALPHABET,
                          nonapps_visible=self.nonapps_visible,
                          filter=xfilter,
                          nonblocking_load=False,
                          persistent_duplicate_filter=(i > 0))

            L = len(enq.matches)
            if L:
                cat_title = utf8(
                    ngettext(
                        u'%(amount)s item on this computer not on “%(machine)s”',
                        '%(amount)s items on this computer not on “%(machine)s”',
                        L)) % {
                            'amount': L,
                            'machine': utf8(self.current_hostname)
                        }
                i += L
                docs = enq.get_documents()
                self.cat_docid_map["additionalpkg"] = set(
                    [doc.get_docid() for doc in docs])
                model.set_nocategory_documents(
                    docs,
                    untranslated_name="additionalpkg",
                    display_name=cat_title)

            if i:
                self.app_view.tree_view.set_cursor(Gtk.TreePath(), None, False)
                if i <= 10:
                    self.app_view.tree_view.expand_all()

            # cache the installed app count
            self.installed_count = i
            self.app_view._append_appcount(self.installed_count,
                                           mode=AppView.DIFF_MODE)

            self.app_view.set_model(self.treefilter)
            if keep_state:
                self._restore_treeview_state(treeview_state)

            # hide the local spinner
            self.hide_installed_view_spinner()

            if window:
                window.set_cursor(None)

            self.emit("app-list-changed", i)
            return

        GObject.idle_add(profiled_rebuild_oneconfview)

    def _check_expand(self):
        it = self.treefilter.get_iter_first()
        while it:
            path = self.treefilter.get_path(it)
            if self.state.search_term:  # or path in self._user_expanded_paths:
                self.app_view.tree_view.expand_row(path, False)
            else:
                self.app_view.tree_view.collapse_row(path)

            it = self.treefilter.iter_next(it)

    def _do_search(self, terms):
        self.state.search_term = terms
        xfilter = AppFilter(self.db, self.cache)
        xfilter.set_installed_only(True)
        self.enquirer.set_query(self.get_query(),
                                nonapps_visible=self.nonapps_visible,
                                filter=xfilter,
                                nonblocking_load=True)

        self.visible_docids = self.enquirer.get_docids()
        self.visible_cats = self._get_vis_cats(self.visible_docids)
        self.treefilter.refilter()
        self.app_view.tree_view.expand_all()

    def _run_channel_enquirer(self, persistent_duplicate_filter=True):
        xfilter = AppFilter(self.db, self.cache)
        xfilter.set_installed_only(True)
        if self.state.channel:
            self.enquirer.set_query(
                self.state.channel.query,
                sortmode=SortMethods.BY_ALPHABET,
                nonapps_visible=NonAppVisibility.MAYBE_VISIBLE,
                filter=xfilter,
                nonblocking_load=False,
                persistent_duplicate_filter=persistent_duplicate_filter)

    def _search(self, terms=None):
        if not terms:
            self.visible_docids = None
            self.state.search_term = ""
            self._clear_search()
            self.treefilter.refilter()
            self._check_expand()
            # run channel enquirer to ensure that the channel specific
            # info for show/hide nonapps is actually correct
            self._run_channel_enquirer()
            # trigger update of the show/hide
            self.emit("app-list-changed", 0)
        elif self.state.search_term != terms:
            self._do_search(terms)

    def get_query(self):
        # search terms
        return self.db.get_query_list_from_search_entry(self.state.search_term)

    def get_query_for_cat(self, cat):
        LOG.debug("self.state.channel: %s" % self.state.channel)
        if self.state.channel and self.state.channel.query:
            query = xapian.Query(xapian.Query.OP_AND, cat.query,
                                 self.state.channel.query)
            return query
        return cat.query

    @wait_for_apt_cache_ready
    def refresh_apps(self, *args, **kwargs):
        """refresh the applist and update the navigation bar """
        logging.debug("installedpane refresh_apps")
        keep_state = kwargs.get("keep_state", False)
        if self.current_hostid:
            self._build_oneconfview(keep_state)
        else:
            self._build_categorised_installedview(keep_state)

    def _clear_search(self):
        # remove the details and clear the search
        self.searchentry.clear_with_no_signal()

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

        self._search(terms.strip())
        self.state.search_term = terms
        self.notebook.set_current_page(InstalledPane.Pages.LIST)
        self.hide_installed_view_spinner()

    def _get_vis_cats(self, visids):
        vis_cats = {}
        appcount = 0
        visids = set(visids)
        for cat_uname, docids in self.cat_docid_map.iteritems():
            children = len(docids & visids)
            if children:
                appcount += children
                vis_cats[cat_uname] = children
        self.app_view._append_appcount(appcount, mode=AppView.DIFF_MODE)
        return vis_cats

    def _refresh_on_cache_or_db_change(self):
        self.refresh_apps(keep_state=True)
        self.app_details_view.refresh_app()

    def on_db_reopen(self, db):
        LOG.debug("on_db_reopen")
        self._refresh_on_cache_or_db_change()

    def on_cache_ready(self, cache):
        LOG.debug("on_cache_ready")
        self._refresh_on_cache_or_db_change()

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

    def get_callback_for_page(self, page, state):
        if page == InstalledPane.Pages.LIST:
            return self.display_overview_page
        return self.display_details_page

    def display_search(self):
        model = self.app_view.get_model()
        if model:
            self.emit("app-list-changed", len(model))
        self.searchentry.show()

    @wait_for_apt_cache_ready
    def display_overview_page(self, page, view_state):
        LOG.debug("view_state: %s" % view_state)
        if self.current_hostid:
            # FIXME for P: oneconf views don't support search
            # this one ensure that even when switching between pane, we
            # don't have the search item
            if self.state.search_term:
                self._search()
            self._build_oneconfview()
        elif (view_state and view_state.channel and view_state.channel.origin
              is not self.current_displayed_origin):
            # we don't need to refresh the full installed view every time it
            # is displayed, so we check to see if we are viewing the same
            # channel and if so we don't refresh the view, note that the view
            # *is* is refreshed whenever the contents change and this is
            # sufficient (see LP: #828887)
            self._build_categorised_installedview()
            self.current_displayed_origin = view_state.channel.origin

            if self.state.search_term:
                self._search(self.state.search_term)
        return True

    def get_current_app(self):
        """return the current active application object applicable
           to the context"""
        return self.current_appview_selection

    def is_category_view_showing(self):
        # there is no category view in the installed pane
        return False

    def is_applist_view_showing(self):
        """Return True if we are in the applist view """
        return (self.notebook.get_current_page() == InstalledPane.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() == InstalledPane.Pages.DETAILS
Beispiel #10
0
    def init_view(self):
        if self.view_initialized:
            return

        SoftwarePane.init_view(self)

        # show a busy cursor and display the main spinner while we build the
        # view
        window = self.get_window()
        if window:
            window.set_cursor(self.busy_cursor)
        self.show_appview_spinner()

        self.oneconf_viewpickler = OneConfViews(self.icons)
        self.oneconf_viewpickler.register_computer(
            None,
            _("This computer (%s)") % platform.node())
        self.oneconf_viewpickler.select_first()
        self.oneconf_viewpickler.connect('computer-changed',
                                         self._selected_computer_changed)
        self.oneconf_viewpickler.connect('current-inventory-refreshed',
                                         self._current_inventory_need_refresh)

        # Start OneConf
        self.oneconf_handler = get_oneconf_handler(self.oneconf_viewpickler)
        if self.oneconf_handler:
            self.oneconf_handler.connect('show-oneconf-changed',
                                         self._show_oneconf_changed)
            self.oneconf_handler.connect('last-time-sync-changed',
                                         self._last_time_sync_oneconf_changed)

        # OneConf pane
        self.computerpane = Gtk.Paned.new(Gtk.Orientation.HORIZONTAL)
        self.oneconfcontrol = Gtk.Box()
        self.oneconfcontrol.set_orientation(Gtk.Orientation.VERTICAL)
        self.computerpane.pack1(self.oneconfcontrol, False, False)
        # size negotiation takes everything for the first one
        self.oneconfcontrol.set_property('width-request', 200)
        self.box_app_list.pack_start(self.computerpane, True, True, 0)

        scroll = Gtk.ScrolledWindow()
        scroll.set_shadow_type(Gtk.ShadowType.IN)
        scroll.add(self.oneconf_viewpickler)
        self.oneconfcontrol.pack_start(scroll, True, True, 0)

        oneconftoolbar = Gtk.Box()
        oneconftoolbar.set_orientation(Gtk.Orientation.HORIZONTAL)
        oneconfpropertymenu = Gtk.Menu()
        self.oneconfproperty = MenuButton(
            oneconfpropertymenu,
            Gtk.Image.new_from_stock(Gtk.STOCK_PROPERTIES,
                                     Gtk.IconSize.BUTTON))
        self.stopsync_label = _(u"Stop Syncing “%s”")
        stop_oneconf_share_menuitem = Gtk.MenuItem(label=self.stopsync_label %
                                                   platform.node())
        stop_oneconf_share_menuitem.connect(
            "activate", self._on_stop_oneconf_hostshare_clicked)
        stop_oneconf_share_menuitem.show()
        oneconfpropertymenu.append(stop_oneconf_share_menuitem)
        self.oneconfcontrol.pack_start(oneconftoolbar, False, False, 1)
        self.oneconf_last_sync = Gtk.Label()
        self.oneconf_last_sync.set_line_wrap(True)
        oneconftoolbar.pack_start(self.oneconfproperty, False, False, 0)
        oneconftoolbar.pack_start(self.oneconf_last_sync, True, True, 1)

        self.notebook.append_page(self.box_app_list, Gtk.Label(label="list"))

        # details
        self.notebook.append_page(self.scroll_details,
                                  Gtk.Label(label="details"))
        # initial refresh
        self.state.search_term = ""

        # build models and filters
        self.base_model = AppTreeStore(self.db, self.cache, self.icons)

        self.treefilter = self.base_model.filter_new(None)
        self.treefilter.set_visible_func(self._row_visibility_func,
                                         AppTreeStore.COL_ROW_DATA)
        self.app_view.set_model(self.treefilter)
        self.app_view.tree_view.connect("row-collapsed",
                                        self._on_row_collapsed)

        self._all_cats = self.parse_applications_menu(APP_INSTALL_PATH)
        self._all_cats = categories_sorted_by_name(self._all_cats)

        # we do not support the search aid feature in the installedview
        self.box_app_list.remove(self.search_aid)

        # remove here
        self.box_app_list.remove(self.app_view)

        # create a local spinner notebook for the installed view
        self.installed_spinner_notebook = SpinnerNotebook(self.app_view)

        self.computerpane.pack2(self.installed_spinner_notebook, True, True)
        self.show_installed_view_spinner()

        self.show_all()

        # initialize view to hide the oneconf computer selector
        self.oneconf_viewpickler.select_first()
        self.oneconfcontrol.hide()

        # hacky, hide the header
        self.app_view.header_hbox.hide()

        self.hide_appview_spinner()

        # keep track of the current view by tracking its origin
        self.current_displayed_origin = None

        # now we are initialized
        self.emit("installed-pane-created")

        self.view_initialized = True
        return False
Beispiel #11
0
class HistoryPane(Gtk.VBox, BasePane):

    __gsignals__ = {
        "app-list-changed": (
            GObject.SignalFlags.RUN_LAST,
            None,
            (int, ),
        ),
        "history-pane-created": (GObject.SignalFlags.RUN_FIRST, None, ()),
    }

    (COL_WHEN, COL_ACTION, COL_PKG) = range(3)
    COL_TYPES = (object, int, object)

    (ALL, INSTALLED, REMOVED, UPGRADED) = range(4)

    ICON_SIZE = 1.2 * get_em()
    PADDING = 4

    # pages for the spinner notebook
    (PAGE_HISTORY_VIEW, PAGE_SPINNER) = range(2)

    def __init__(self, cache, db, distro, icons):
        Gtk.VBox.__init__(self)
        self.cache = cache
        self.db = db
        self.distro = distro
        self.icons = icons

        self.apps_filter = None
        self.state = DisplayState()

        self.pane_name = _("History")

        # Icon cache, invalidated upon icon theme changes
        self._app_icon_cache = {}
        self._reset_icon_cache()
        self.icons.connect('changed', self._reset_icon_cache)

        self._emblems = {}
        self._get_emblems(self.icons)

        vm = get_viewmanager()
        self.searchentry = vm.get_global_searchentry()

        self.toolbar = Gtk.Toolbar()
        self.toolbar.show()
        self.toolbar.set_style(Gtk.ToolbarStyle.TEXT)
        self.pack_start(self.toolbar, False, True, 0)

        all_action = Gtk.RadioAction('filter_all', _('All Changes'), None,
                                     None, self.ALL)
        all_action.connect('changed', self.change_filter)
        all_button = all_action.create_tool_item()
        self.toolbar.insert(all_button, 0)

        installs_action = Gtk.RadioAction('filter_installs',
                                          _('Installations'), None, None,
                                          self.INSTALLED)
        installs_action.join_group(all_action)
        installs_button = installs_action.create_tool_item()
        self.toolbar.insert(installs_button, 1)

        upgrades_action = Gtk.RadioAction('filter_upgrads', _('Updates'), None,
                                          None, self.UPGRADED)
        upgrades_action.join_group(all_action)
        upgrades_button = upgrades_action.create_tool_item()
        self.toolbar.insert(upgrades_button, 2)

        removals_action = Gtk.RadioAction('filter_removals', _('Removals'),
                                          None, None, self.REMOVED)
        removals_action.join_group(all_action)
        removals_button = removals_action.create_tool_item()
        self.toolbar.insert(removals_button, 3)
        self.toolbar.connect('draw', self.on_toolbar_draw)

        self._actions_list = all_action.get_group()
        self._set_actions_sensitive(False)

        self.view = Gtk.TreeView()
        self.view.set_headers_visible(False)
        self.view.show()
        self.history_view = Gtk.ScrolledWindow()
        self.history_view.set_policy(Gtk.PolicyType.AUTOMATIC,
                                     Gtk.PolicyType.AUTOMATIC)
        self.history_view.show()
        self.history_view.add(self.view)

        # make a spinner to display while history is loading
        self.spinner_notebook = SpinnerNotebook(self.history_view,
                                                _('Loading history'))

        self.pack_start(self.spinner_notebook, True, True, 0)

        self.store = Gtk.TreeStore(*self.COL_TYPES)
        self.visible_changes = 0
        self.store_filter = self.store.filter_new(None)
        self.store_filter.set_visible_func(self.filter_row, None)
        self.view.set_model(self.store_filter)
        all_action.set_active(True)
        self.last = None

        # to save (a lot of) time at startup we load history later, only when
        # it is selected to be viewed
        self.history = None

        self.column = Gtk.TreeViewColumn(_('Date'))
        self.view.append_column(self.column)
        self.cell_icon = Gtk.CellRendererPixbuf()
        self.cell_icon.set_padding(self.PADDING, self.PADDING / 2)
        self.column.pack_start(self.cell_icon, False)
        self.column.set_cell_data_func(self.cell_icon, self.render_cell_icon)
        self.cell_text = Gtk.CellRendererText()
        self.column.pack_start(self.cell_text, True)
        self.column.set_cell_data_func(self.cell_text, self.render_cell_text)
        self.cell_time = Gtk.CellRendererText()
        self.cell_time.set_padding(6, 0)
        self.cell_time.set_alignment(1.0, 0.5)
        self.column.pack_end(self.cell_time, False)
        self.column.set_cell_data_func(self.cell_time, self.render_cell_time)

        # busy cursor
        self.busy_cursor = Gdk.Cursor.new(Gdk.CursorType.WATCH)

    def init_view(self):
        if self.history is None:
            # if the history is not yet initialized we have to load and parse
            # it show a spinner while we do that
            self.realize()
            window = self.get_window()
            window.set_cursor(self.busy_cursor)
            self.spinner_notebook.show_spinner()
            self.load_and_parse_history()
            self.spinner_notebook.hide_spinner()
            self._set_actions_sensitive(True)
            window.set_cursor(None)
            self.emit("history-pane-created")

    def on_toolbar_draw(self, widget, cr):
        a = widget.get_allocation()
        context = widget.get_style_context()

        color = context.get_border_color(widget.get_state_flags())
        cr.set_source_rgba(color.red, color.green, color.blue, 0.5)
        cr.set_line_width(1)
        cr.move_to(0.5, a.height - 0.5)
        cr.rel_line_to(a.width - 1, 0)
        cr.stroke()

    def _get_emblems(self, icons):
        from softwarecenter.enums import USE_PACKAGEKIT_BACKEND
        if USE_PACKAGEKIT_BACKEND:
            emblem_names = ("pk-package-add", "pk-package-delete",
                            "pk-package-update")
        else:
            emblem_names = ("package-install", "package-remove",
                            "package-upgrade")

        for i, emblem in enumerate(emblem_names):
            pb = icons.load_icon(emblem, self.ICON_SIZE, 0)
            self._emblems[i + 1] = pb

    def _set_actions_sensitive(self, sensitive):
        for action in self._actions_list:
            action.set_sensitive(sensitive)

    def _reset_icon_cache(self, theme=None):
        self._app_icon_cache.clear()
        try:
            missing = self.icons.load_icon(Icons.MISSING_APP, self.ICON_SIZE,
                                           0)
        except GObject.GError:
            missing = None
        self._app_icon_cache[Icons.MISSING_APP] = missing

    def load_and_parse_history(self):
        from softwarecenter.db.history import get_pkg_history
        self.history = get_pkg_history()
        # FIXME: a signal from AptHistory is nicer
        while not self.history.history_ready:
            while Gtk.events_pending():
                Gtk.main_iteration()
        self.parse_history()
        self.history.set_on_update(self.parse_history)

    def parse_history(self):
        date = None
        when = None
        last_row = None
        day = self.store.get_iter_first()
        if day is not None:
            date = self.store.get_value(day, self.COL_WHEN)
        if len(self.history.transactions) == 0:
            logging.debug("AptHistory is currently empty")
            return
        new_last = self.history.transactions[0].start_date
        for trans in self.history.transactions:
            while Gtk.events_pending():
                Gtk.main_iteration()
            when = trans.start_date
            if self.last is not None and when <= self.last:
                break
            if when.date() != date:
                date = when.date()
                day = self.store.append(None, (date, self.ALL, None))
                last_row = None
            actions = {
                self.INSTALLED: trans.install,
                self.REMOVED: trans.remove,
                self.UPGRADED: trans.upgrade,
            }
            for action, pkgs in actions.items():
                for pkgname in pkgs:
                    row = (when, action, pkgname)
                    last_row = self.store.insert_after(day, last_row, row)
        self.last = new_last
        self.update_view()

    def on_search_terms_changed(self, entry, terms):
        self.update_view()

    def change_filter(self, action, current):
        self.filter = action.get_current_value()
        self.update_view()

    def update_view(self):
        self.store_filter.refilter()
        self.view.collapse_all()
        # Expand all the matching rows
        if self.searchentry.get_text():
            self.view.expand_all()

        # Compute the number of visible changes
        # don't do this atm - the spec doesn't mention that the history pane
        # should have a status text and it gives us a noticeable performance
        # gain if we don't calculate this
        #self.visible_changes = 0
        #day = self.store_filter.get_iter_first()
        #while day is not None:
        #    self.visible_changes += self.store_filter.iter_n_children(day)
        #    day = self.store_filter.iter_next(day)

        # Expand the most recent day
        day = self.store.get_iter_first()
        if day is not None:
            path = self.store.get_path(day)
            self.view.expand_row(path, False)
            self.view.scroll_to_cell(path)

        #self.emit('app-list-changed', self.visible_changes)

    def _row_matches(self, store, iter):
        # Whether a child row matches the current filter and the search entry
        pkg = store.get_value(iter, self.COL_PKG) or ''
        filter_values = (self.ALL, store.get_value(iter, self.COL_ACTION))
        filter_matches = self.filter in filter_values
        search_matches = self.searchentry.get_text().lower() in pkg.lower()
        return filter_matches and search_matches

    def filter_row(self, store, iter, user_data):
        pkg = store.get_value(iter, self.COL_PKG)
        if pkg is not None:
            return self._row_matches(store, iter)
        else:
            i = store.iter_children(iter)
            while i is not None:
                if self._row_matches(store, i):
                    return True
                i = store.iter_next(i)
            return False

    def render_cell_icon(self, column, cell, store, iter, user_data):
        pkg = store.get_value(iter, self.COL_PKG)
        if pkg is None:
            cell.set_visible(False)
            return

        cell.set_visible(True)

        when = store.get_value(iter, self.COL_WHEN)
        if isinstance(when, datetime.datetime):
            action = store.get_value(iter, self.COL_ACTION)
            cell.set_property('pixbuf', self._emblems[action])

            #~ icon_name = Icons.MISSING_APP
            #~ for m in self.db.xapiandb.postlist("AP" + pkg):
            #~ doc = self.db.xapiandb.get_document(m.docid)
            #~ icon_value = doc.get_value(XapianValues.ICON)
            #~ if icon_value:
            #~ icon_name = os.path.splitext(icon_value)[0]
            #~ break
            #~ if icon_name in self._app_icon_cache:
            #~ icon = self._app_icon_cache[icon_name]
            #~ else:
            #~ try:
            #~ icon = self.icons.load_icon(icon_name, self.ICON_SIZE,
            #~ 0)
            #~ except GObject.GError:
            #~ icon = self._app_icon_cache[Icons.MISSING_APP]
            #~ self._app_icon_cache[icon_name] = icon

    def render_cell_text(self, column, cell, store, iter, user_data):
        when = store.get_value(iter, self.COL_WHEN)
        if isinstance(when, datetime.datetime):
            pkg = store.get_value(iter, self.COL_PKG)
            text = pkg
        elif isinstance(when, datetime.date):
            today = datetime.date.today()
            monday = today - datetime.timedelta(days=today.weekday())
            if when == today:
                text = _("Today")
            elif when >= monday:
                # Current week, display the name of the day
                text = when.strftime(_('%A'))
            else:
                if when.year == today.year:
                    # Current year, display the day and month
                    text = when.strftime(_('%d %B'))
                else:
                    # Display the full date: day, month, year
                    text = when.strftime(_('%d %B %Y'))
        cell.set_property('markup', text)

    def render_cell_time(self, column, cell, store, iter, user_data):
        when = store.get_value(iter, self.COL_WHEN)
        text = ''
        if isinstance(when, datetime.datetime):
            action = store.get_value(iter, self.COL_ACTION)
            # Translators : time displayed in history, display hours
            # (0-12), minutes and AM/PM. %H should be used instead
            # of %I to display hours 0-24
            time_text = when.time().strftime(_('%I:%M %p'))
            if self.filter is not self.ALL:
                action_text = time_text
            else:
                if action == self.INSTALLED:
                    action_text = _('installed %s') % time_text
                elif action == self.REMOVED:
                    action_text = _('removed %s') % time_text
                elif action == self.UPGRADED:
                    action_text = _('updated %s') % time_text
            color = {'color': '#8A8A8A', 'action': action_text}
            text = '<span color="%(color)s">%(action)s</span>' % color
        cell.set_property('markup', text)
Beispiel #12
0
class HistoryPane(Gtk.VBox, BasePane):

    __gsignals__ = {
        "app-list-changed": (GObject.SignalFlags.RUN_LAST,
                             None,
                             (int, ),
                            ),
        "history-pane-created": (GObject.SignalFlags.RUN_FIRST,
                                 None,
                                 ()),
    }

    (COL_WHEN, COL_ACTION, COL_PKG) = range(3)
    COL_TYPES = (object, int, object)

    (ALL, INSTALLED, REMOVED, UPGRADED) = range(4)

    ICON_SIZE = 32
    PADDING = 6

    # pages for the spinner notebook
    (PAGE_HISTORY_VIEW,
     PAGE_SPINNER) = range(2)

    def __init__(self, cache, db, distro, icons, datadir):
        Gtk.VBox.__init__(self)
        self.cache = cache
        self.db = db
        self.distro = distro
        self.icons = icons
        self.datadir = datadir

        self.apps_filter = None
        self.state = DisplayState()

        self.pane_name = _("History")

        # Icon cache, invalidated upon icon theme changes
        self._app_icon_cache = {}
        self._reset_icon_cache()
        self.icons.connect('changed', self._reset_icon_cache)

        self._emblems = {}
        self._get_emblems(self.icons)

        vm = get_viewmanager()
        self.searchentry = vm.get_global_searchentry()

        self.toolbar = Gtk.Toolbar()
        self.toolbar.show()
        self.toolbar.set_style(Gtk.ToolbarStyle.TEXT)
        self.pack_start(self.toolbar, False, True, 0)

        all_action = Gtk.RadioAction('filter_all', _('All Changes'), None,
            None, self.ALL)
        all_action.connect('changed', self.change_filter)
        all_button = all_action.create_tool_item()
        self.toolbar.insert(all_button, 0)

        installs_action = Gtk.RadioAction('filter_installs',
            _('Installations'), None, None, self.INSTALLED)
        installs_action.join_group(all_action)
        installs_button = installs_action.create_tool_item()
        self.toolbar.insert(installs_button, 1)

        upgrades_action = Gtk.RadioAction(
            'filter_upgrads', _('Updates'), None, None, self.UPGRADED)
        upgrades_action.join_group(all_action)
        upgrades_button = upgrades_action.create_tool_item()
        self.toolbar.insert(upgrades_button, 2)

        removals_action = Gtk.RadioAction(
            'filter_removals', _('Removals'), None, None, self.REMOVED)
        removals_action.join_group(all_action)
        removals_button = removals_action.create_tool_item()
        self.toolbar.insert(removals_button, 3)
        self.toolbar.connect('draw', self.on_toolbar_draw)

        self._actions_list = all_action.get_group()
        self._set_actions_sensitive(False)

        self.view = Gtk.TreeView()
        self.view.set_headers_visible(False)
        self.view.show()
        self.history_view = Gtk.ScrolledWindow()
        self.history_view.set_policy(Gtk.PolicyType.AUTOMATIC,
                                      Gtk.PolicyType.AUTOMATIC)
        self.history_view.show()
        self.history_view.add(self.view)

        # make a spinner to display while history is loading
        self.spinner_notebook = SpinnerNotebook(
            self.history_view, _('Loading history'))

        self.pack_start(self.spinner_notebook, True, True, 0)

        self.store = Gtk.TreeStore(*self.COL_TYPES)
        self.visible_changes = 0
        self.store_filter = self.store.filter_new(None)
        self.store_filter.set_visible_func(self.filter_row, None)
        self.view.set_model(self.store_filter)
        all_action.set_active(True)
        self.last = None

        # to save (a lot of) time at startup we load history later, only when
        # it is selected to be viewed
        self.history = None

        self.column = Gtk.TreeViewColumn(_('Date'))
        self.view.append_column(self.column)
        self.cell_icon = Gtk.CellRendererPixbuf()
        self.column.pack_start(self.cell_icon, False)
        self.column.set_cell_data_func(self.cell_icon, self.render_cell_icon)
        self.cell_text = Gtk.CellRendererText()
        self.column.pack_start(self.cell_text, True)
        self.column.set_cell_data_func(self.cell_text, self.render_cell_text)

        # busy cursor
        self.busy_cursor = Gdk.Cursor.new(Gdk.CursorType.WATCH)

    def init_view(self):
        if self.history == None:
            # if the history is not yet initialized we have to load and parse
            # it show a spinner while we do that
            self.realize()
            window = self.get_window()
            window.set_cursor(self.busy_cursor)
            self.spinner_notebook.show_spinner()
            self.load_and_parse_history()
            self.spinner_notebook.hide_spinner()
            self._set_actions_sensitive(True)
            window.set_cursor(None)
            self.emit("history-pane-created")

    def on_toolbar_draw(self, widget, cr):
        a = widget.get_allocation()
        context = widget.get_style_context()

        color = context.get_border_color(widget.get_state_flags())
        cr.set_source_rgba(color.red, color.green, color.blue, 0.5)
        cr.set_line_width(1)
        cr.move_to(0.5, a.height - 0.5)
        cr.rel_line_to(a.width - 1, 0)
        cr.stroke()

    def _get_emblems(self, icons):
        from softwarecenter.enums import USE_PACKAGEKIT_BACKEND
        if USE_PACKAGEKIT_BACKEND:
            emblem_names = ("pk-package-add",
                            "pk-package-delete",
                            "pk-package-update")
        else:
            emblem_names = ("package-install",
                            "package-remove",
                            "package-upgrade")

        for i, emblem in enumerate(emblem_names):
            pb = icons.load_icon(emblem, self.ICON_SIZE, 0)
            self._emblems[i + 1] = pb

    def _set_actions_sensitive(self, sensitive):
        for action in self._actions_list:
            action.set_sensitive(sensitive)

    def _reset_icon_cache(self, theme=None):
        self._app_icon_cache.clear()
        try:
            missing = self.icons.load_icon(Icons.MISSING_APP, self.ICON_SIZE,
                0)
        except GObject.GError:
            missing = None
        self._app_icon_cache[Icons.MISSING_APP] = missing

    def load_and_parse_history(self):
        from softwarecenter.db.history import get_pkg_history
        self.history = get_pkg_history()
        # FIXME: a signal from AptHistory is nicer
        while not self.history.history_ready:
            while Gtk.events_pending():
                Gtk.main_iteration()
        self.parse_history()
        self.history.set_on_update(self.parse_history)

    def parse_history(self):
        date = None
        when = None
        last_row = None
        day = self.store.get_iter_first()
        if day is not None:
            date = self.store.get_value(day, self.COL_WHEN)
        if len(self.history.transactions) == 0:
            logging.debug("AptHistory is currently empty")
            return
        new_last = self.history.transactions[0].start_date
        for trans in self.history.transactions:
            while Gtk.events_pending():
                Gtk.main_iteration()
            when = trans.start_date
            if self.last is not None and when <= self.last:
                break
            if when.date() != date:
                date = when.date()
                day = self.store.append(None, (date, self.ALL, None))
                last_row = None
            actions = {self.INSTALLED: trans.install,
                       self.REMOVED: trans.remove,
                       self.UPGRADED: trans.upgrade,
                      }
            for action, pkgs in actions.items():
                for pkgname in pkgs:
                    row = (when, action, pkgname)
                    last_row = self.store.insert_after(day, last_row, row)
        self.last = new_last
        self.update_view()

    def get_current_page(self):
        # single page views can return None here
        pass

    def get_callback_for_page(self, page, state):
        # single page views can return None here
        pass

    def on_search_terms_changed(self, entry, terms):
        self.update_view()

    def on_nav_back_clicked(self, widget):
        vm = get_viewmanager()
        vm.nav_back()

    def on_nav_forward_clicked(self, widget):
        vm = get_viewmanager()
        vm.nav_forward()

    def change_filter(self, action, current):
        self.filter = action.get_current_value()
        self.update_view()

    def update_view(self):
        self.store_filter.refilter()
        self.view.collapse_all()
        # Expand all the matching rows
        if self.searchentry.get_text():
            self.view.expand_all()

        # Compute the number of visible changes
        # don't do this atm - the spec doesn't mention that the history pane
        # should have a status text and it gives us a noticable performance
        # gain if we don't calculate this
#        self.visible_changes = 0
 #       day = self.store_filter.get_iter_first()
  #      while day is not None:
   #         self.visible_changes += self.store_filter.iter_n_children(day)
    #        day = self.store_filter.iter_next(day)

        # Expand the most recent day
        day = self.store.get_iter_first()
        if day is not None:
            path = self.store.get_path(day)
            self.view.expand_row(path, False)
            self.view.scroll_to_cell(path)

#        self.emit('app-list-changed', self.visible_changes)

    def _row_matches(self, store, iter):
        # Whether a child row matches the current filter and the search entry
        pkg = store.get_value(iter, self.COL_PKG) or ''
        filter_values = (self.ALL, store.get_value(iter, self.COL_ACTION))
        filter_matches = self.filter in filter_values
        search_matches = self.searchentry.get_text().lower() in pkg.lower()
        return filter_matches and search_matches

    def filter_row(self, store, iter, user_data):
        pkg = store.get_value(iter, self.COL_PKG)
        if pkg is not None:
            return self._row_matches(store, iter)
        else:
            i = store.iter_children(iter)
            while i is not None:
                if self._row_matches(store, i):
                    return True
                i = store.iter_next(i)
            return False

    def render_cell_icon(self, column, cell, store, iter, user_data):
        pkg = store.get_value(iter, self.COL_PKG)
        if pkg is None:
            cell.set_visible(False)
            return

        cell.set_visible(True)

        when = store.get_value(iter, self.COL_WHEN)
        if isinstance(when, datetime.datetime):
            action = store.get_value(iter, self.COL_ACTION)
            cell.set_property('pixbuf', self._emblems[action])

            #~ icon_name = Icons.MISSING_APP
            #~ for m in self.db.xapiandb.postlist("AP" + pkg):
                #~ doc = self.db.xapiandb.get_document(m.docid)
                #~ icon_value = doc.get_value(XapianValues.ICON)
                #~ if icon_value:
                    #~ icon_name = os.path.splitext(icon_value)[0]
                #~ break
            #~ if icon_name in self._app_icon_cache:
                #~ icon = self._app_icon_cache[icon_name]
            #~ else:
                #~ try:
                    #~ icon = self.icons.load_icon(icon_name, self.ICON_SIZE,
                        #~ 0)
                #~ except GObject.GError:
                    #~ icon = self._app_icon_cache[Icons.MISSING_APP]
                #~ self._app_icon_cache[icon_name] = icon

    def render_cell_text(self, column, cell, store, iter, user_data):
        when = store.get_value(iter, self.COL_WHEN)
        if isinstance(when, datetime.datetime):
            action = store.get_value(iter, self.COL_ACTION)
            pkg = store.get_value(iter, self.COL_PKG)
            subs = {'pkgname': pkg,
                    'color': '#8A8A8A',
                    # Translators : time displayed in history, display hours
                    # (0-12), minutes and AM/PM. %H should be used instead
                    # of %I to display hours 0-24
                    'time': when.time().strftime(_('%I:%M %p')),
                   }
            if action == self.INSTALLED:
                text = _('%(pkgname)s <span color="%(color)s">'
                    'installed %(time)s</span>') % subs
            elif action == self.REMOVED:
                text = _('%(pkgname)s <span color="%(color)s">'
                    'removed %(time)s</span>') % subs
            elif action == self.UPGRADED:
                text = _('%(pkgname)s <span color="%(color)s">'
                    'updated %(time)s</span>') % subs
        elif isinstance(when, datetime.date):
            today = datetime.date.today()
            monday = today - datetime.timedelta(days=today.weekday())
            if when == today:
                text = _("Today")
            elif when >= monday:
                # Current week, display the name of the day
                text = when.strftime(_('%A'))
            else:
                if when.year == today.year:
                    # Current year, display the day and month
                    text = when.strftime(_('%d %B'))
                else:
                    # Display the full date: day, month, year
                    text = when.strftime(_('%d %B %Y'))
        cell.set_property('markup', text)
Beispiel #13
0
class SoftwarePane(Gtk.VBox, BasePane):
    """ Common base class for AvailablePane and InstalledPane"""

    class Pages:
        NAMES = ('appview', 'details', 'spinner')
        APPVIEW = 0
        DETAILS = 1
        SPINNER = 2

    __gsignals__ = {
        "app-list-changed": (GObject.SignalFlags.RUN_LAST,
                             None,
                             (int,),
                            ),
    }
    PADDING = 6

    def __init__(self, cache, db, distro, icons, datadir, show_ratings=True):

        Gtk.VBox.__init__(self)
        BasePane.__init__(self)

        # other classes we need
        self.enquirer = AppEnquire(cache, db)
        self._query_complete_handler = self.enquirer.connect(
                            "query-complete", self.on_query_complete)

        self.cache = cache
        self.db = db
        self.distro = distro
        self.icons = icons
        self.datadir = datadir
        self.show_ratings = show_ratings
        self.backend = get_install_backend()
        self.nonapps_visible = NonAppVisibility.MAYBE_VISIBLE
        # refreshes can happen out-of-bound so we need to be sure
        # that we only set the new model (when its available) if
        # the refresh_seq_nr of the ready model matches that of the
        # request (e.g. people click on ubuntu channel, get impatient, click
        # on partner channel)
        self.refresh_seq_nr = 0
        # this should be initialized
        self.apps_search_term = ""
        # Create the basic frame for the common view
        self.state = DisplayState()
        vm = get_viewmanager()
        self.searchentry = vm.get_global_searchentry()
        self.back_forward = vm.get_global_backforward()
        # a notebook below
        self.notebook = Gtk.Notebook()
        if not "SOFTWARE_CENTER_DEBUG_TABS" in os.environ:
            self.notebook.set_show_tabs(False)
        self.notebook.set_show_border(False)
        # make a spinner view to display while the applist is loading
        self.spinner_notebook = SpinnerNotebook(self.notebook)
        self.pack_start(self.spinner_notebook, True, True, 0)

        # add a bar at the bottom (hidden by default) for contextual actions
        self.action_bar = ActionBar()
        self.pack_start(self.action_bar, False, True, 0)

        # cursor
        self.busy_cursor = Gdk.Cursor.new(Gdk.CursorType.WATCH)

        # views to be created in init_view
        self.app_view = None
        self.app_details_view = None

    def init_view(self):
        """
        Initialize those UI components that are common to all subclasses of
        SoftwarePane.  Note that this method is intended to be called by
        the subclass itself at the start of its own init_view() implementation.
        """
        # common UI elements (applist and appdetails)
        # its the job of the Child class to put it into a good location
        # list
        self.box_app_list = Gtk.VBox()

        # search aid
        self.search_aid = SearchAid(self)
        self.box_app_list.pack_start(self.search_aid, False, False, 0)

        self.app_view = AppView(self.db, self.cache,
                                self.icons, self.show_ratings)
        self.app_view.connect("sort-method-changed",
            self.on_app_view_sort_method_changed)

        self.init_atk_name(self.app_view, "app_view")
        self.box_app_list.pack_start(self.app_view, True, True, 0)
        self.app_view.connect("application-selected",
                              self.on_application_selected)
        self.app_view.connect("application-activated",
                              self.on_application_activated)

        # details
        self.scroll_details = Gtk.ScrolledWindow()
        self.scroll_details.set_policy(Gtk.PolicyType.AUTOMATIC,
                                        Gtk.PolicyType.AUTOMATIC)
        self.app_details_view = AppDetailsView(self.db,
                                               self.distro,
                                               self.icons,
                                               self.cache,
                                               self.datadir)
        self.app_details_view.connect(
            "different-application-selected", self.on_application_activated)
        self.scroll_details.add(self.app_details_view)
        # when the cache changes, refresh the app list
        self.cache.connect("cache-ready", self.on_cache_ready)

        # connect signals
        self.connect("app-list-changed", self.on_app_list_changed)

        # db reopen
        if self.db:
            self.db.connect("reopen", self.on_db_reopen)

    def init_atk_name(self, widget, name):
        """ init the atk name for a given gtk widget based on parent-pane
            and variable name (used for the mago tests)
        """
        name = self.__class__.__name__ + "." + name
        Atk.Object.set_name(widget.get_accessible(), name)

    def on_cache_ready(self, cache):
        " refresh the application list when the cache is re-opened "
        LOG.debug("on_cache_ready")
        # it only makes sense to refresh if there is something to
        # refresh, otherwise we create a bunch of (not yet needed)
        # AppStore objects on startup when the cache sends its
        # initial "cache-ready" signal
        model = self.app_view.tree_view.get_model()
        if model is None:
            return
        # FIXME: preserve selection too
        self.refresh_apps()

    @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, SoftwarePane.Pages.DETAILS, self.state,
            self.display_details_page)

    def show_app(self, app):
        self.on_application_activated(None, app)

    def on_nav_back_clicked(self, widget):
        vm = get_viewmanager()
        vm.nav_back()

    def on_nav_forward_clicked(self, widget):
        vm = get_viewmanager()
        vm.nav_forward()

    def on_query_complete(self, enquirer):
        self.emit("app-list-changed", len(enquirer.matches))
        self.app_view.display_matches(enquirer.matches,
                                      self._is_in_search_mode())
        self.hide_appview_spinner()

    def on_app_view_sort_method_changed(self, app_view, combo):
        if app_view.get_sort_mode() == self.enquirer.sortmode:
            return

        self.show_appview_spinner()
        app_view.clear_model()
        query = self.get_query()
        self._refresh_apps_with_apt_cache(query)

    def _is_in_search_mode(self):
        return (self.state.search_term and
                len(self.state.search_term) >= 2)

    def show_appview_spinner(self):
        """ display the spinner in the appview panel """
        LOG.debug("show_appview_spinner")
        # FIXME: totally the wrong place!
        if not self.state.search_term:
            self.action_bar.clear()
        self.spinner_notebook.show_spinner()

    def hide_appview_spinner(self):
        """ hide the spinner and display the appview in the panel """
        LOG.debug("hide_appview_spinner")
        self.spinner_notebook.hide_spinner()

    def set_section(self, section):
        self.section = section
        self.app_details_view.set_section(section)

    def section_sync(self):
        self.app_details_view.set_section(self.section)

    def on_app_list_changed(self, pane, length):
        """internal helper that keeps the the action bar up-to-date by
           keeping track of the app-list-changed signals
        """

        self.show_nonapps_if_required(len(self.enquirer.matches))
        self.search_aid.update_search_help(self.state)

    def hide_nonapps(self):
        """ hide non-applications control in the action bar """
        self.action_bar.unset_label()

    def show_nonapps_if_required(self, length):
        """
        update the state of the show/hide non-applications control
        in the action_bar
        """

        enquirer = self.enquirer
        n_apps = enquirer.nr_apps
        n_pkgs = enquirer.nr_pkgs

        # calculate the number of apps/pkgs
        if enquirer.limit > 0 and enquirer.limit < n_pkgs:
            n_apps = min(enquirer.limit, n_apps)
            n_pkgs = min(enquirer.limit - n_apps, n_pkgs)

        if not (n_apps and n_pkgs):
            self.hide_nonapps()
            return

        LOG.debug("nonapps_visible value=%s (always visible: %s)" % (
                self.nonapps_visible,
                self.nonapps_visible == NonAppVisibility.ALWAYS_VISIBLE))

        self.action_bar.unset_label()
        if self.nonapps_visible == NonAppVisibility.ALWAYS_VISIBLE:
            LOG.debug('non-apps-ALWAYS-visible')
            # TRANSLATORS: the text inbetween the underscores acts as a link
            # In most/all languages you will want the whole string as a link
            label = gettext.ngettext("_Hide %(amount)i technical item_",
                                     "_Hide %(amount)i technical items_",
                                     n_pkgs) % {'amount': n_pkgs}
            self.action_bar.set_label(
                        label, link_result=self._hide_nonapp_pkgs)
        else:
            label = gettext.ngettext("_Show %(amount)i technical item_",
                                     "_Show %(amount)i technical items_",
                                     n_pkgs) % {'amount': n_pkgs}
            self.action_bar.set_label(
                        label, link_result=self._show_nonapp_pkgs)

    def _on_label_app_list_header_activate_link(self, link, uri):
        #print "actiavte: ", link, uri
        if uri.startswith("search:"):
            self.searchentry.set_text(uri[len("search:"):])
        elif uri.startswith("search-all:"):
            self.unset_current_category()
            self.refresh_apps()
        elif uri.startswith("search-parent:"):
            self.apps_subcategory = None
            self.refresh_apps()
        elif uri.startswith("search-unsupported:"):
            self.apps_filter.set_supported_only(False)
            self.refresh_apps()
        # FIXME: add ability to remove categories restriction here
        # True stops event propergation
        return True

    def _show_nonapp_pkgs(self):
        self.nonapps_visible = NonAppVisibility.ALWAYS_VISIBLE
        self.refresh_apps()

    def _hide_nonapp_pkgs(self):
        self.nonapps_visible = NonAppVisibility.MAYBE_VISIBLE
        self.refresh_apps()

    def get_query(self):
        channel_query = None
        #name = self.pane_name
        if self.channel:
            channel_query = self.channel.query
            #name = self.channel.display_name

        # search terms
        if self.apps_search_term:
            query = self.db.get_query_list_from_search_entry(
                self.apps_search_term, channel_query)

            return query
        # overview list
        # if we are in a channel, limit to that
        if channel_query:
            return channel_query
        # ... otherwise show all
        return xapian.Query("")

    def refresh_apps(self, query=None):
        """refresh the applist and update the navigation bar """
        LOG.debug("refresh_apps")

        # FIXME: make this available for all panes
        if query is None:
            query = self.get_query()

        self.app_view.clear_model()
        self.search_aid.reset()
        self.show_appview_spinner()
        self._refresh_apps_with_apt_cache(query)

    def quick_query(self, query):
        # a blocking query and does not emit "query-complete"
        with ExecutionTime("enquirer.set_query() quick query"):
            self.enquirer.set_query(
                                query,
                                limit=self.get_app_items_limit(),
                                nonapps_visible=self.nonapps_visible,
                                nonblocking_load=False,
                                filter=self.state.filter)
        return len(self.enquirer.matches)

    @wait_for_apt_cache_ready
    def _refresh_apps_with_apt_cache(self, query):
        LOG.debug("softwarepane query: %s" % query)
        # a nonblocking query calls on_query_complete once finished
        with ExecutionTime("enquirer.set_query()"):
            self.enquirer.set_query(
                                query,
                                limit=self.get_app_items_limit(),
                                sortmode=self.get_sort_mode(),
                                exact=self.is_custom_list(),
                                nonapps_visible=self.nonapps_visible,
                                filter=self.state.filter)

    def display_details_page(self, page, view_state):
        self.app_details_view.show_app(view_state.application)
        self.action_bar.unset_label()
        return True

    def is_custom_list(self):
        return self.apps_search_term and ',' in self.apps_search_term

    def get_current_page(self):
        return self.notebook.get_current_page()

    def get_app_items_limit(self):
        if self.state.search_term:
            return DEFAULT_SEARCH_LIMIT
        elif self.state.subcategory and self.state.subcategory.item_limit > 0:
            return self.state.subcategory.item_limit
        elif self.state.category and self.state.category.item_limit > 0:
            return self.state.category.item_limit
        return 0

    def get_sort_mode(self):
        # if the category sets a custom sort order, that wins, this
        # is required for top-rated and whats-new
        if (self.state.category and
            self.state.category.sortmode != SortMethods.BY_ALPHABET):
            return self.state.category.sortmode
        # searches are always by ranking unless the user decided differently
        if (self._is_in_search_mode() and
            not self.app_view.user_defined_sort_method):
            return SortMethods.BY_SEARCH_RANKING
        # use the appview combo
        return self.app_view.get_sort_mode()

    def on_search_entry_key_press_event(self, event):
        """callback when a key is pressed in the search entry widget"""
        if not self.is_applist_view_showing():
            return
        if ((event.keyval == Gdk.keyval_from_name("Down") or
            event.keyval == Gdk.keyval_from_name("KP_Down")) and
            self.is_applist_view_showing() and
            len(self.app_view.tree_view.get_model()) > 0):
            # select the first item in the applist search result
            self.app_view.tree_view.grab_focus()
            self.app_view.tree_view.set_cursor(Gtk.TreePath(),
                                                   None, False)

    def on_search_terms_changed(self, terms):
        " stub implementation "
        pass

    def on_db_reopen(self):
        " stub implementation "
        pass

    def is_category_view_showing(self):
        " stub implementation "
        pass

    def is_applist_view_showing(self):
        " stub implementation "
        pass

    def is_app_details_view_showing(self):
        " stub implementation "
        pass

    def get_current_app(self):
        " stub implementation "
        pass

    def on_application_selected(self, widget, app):
        " stub implementation "
        pass

    def get_current_category(self):
        " stub implementation "
        pass

    def unset_current_category(self):
        " stub implementation "
        pass
Beispiel #14
0
class SoftwarePane(Gtk.VBox, BasePane):
    """ Common base class for AvailablePane and InstalledPane"""
    class Pages:
        NAMES = ('appview', 'details', 'spinner')
        APPVIEW = 0
        DETAILS = 1
        SPINNER = 2

    __gsignals__ = {
        "app-list-changed": (
            GObject.SignalFlags.RUN_LAST,
            None,
            (int, ),
        ),
    }
    PADDING = 6

    def __init__(self, cache, db, distro, icons, datadir, show_ratings=True):

        Gtk.VBox.__init__(self)
        BasePane.__init__(self)

        # other classes we need
        self.enquirer = AppEnquire(cache, db)
        self._query_complete_handler = self.enquirer.connect(
            "query-complete", self.on_query_complete)

        self.cache = cache
        self.db = db
        self.distro = distro
        self.icons = icons
        self.datadir = datadir
        self.show_ratings = show_ratings
        self.backend = get_install_backend()
        self.nonapps_visible = NonAppVisibility.MAYBE_VISIBLE
        # refreshes can happen out-of-bound so we need to be sure
        # that we only set the new model (when its available) if
        # the refresh_seq_nr of the ready model matches that of the
        # request (e.g. people click on ubuntu channel, get impatient, click
        # on partner channel)
        self.refresh_seq_nr = 0
        # this should be initialized
        self.apps_search_term = ""
        # Create the basic frame for the common view
        self.state = DisplayState()
        vm = get_viewmanager()
        self.searchentry = vm.get_global_searchentry()
        self.back_forward = vm.get_global_backforward()
        # a notebook below
        self.notebook = Gtk.Notebook()
        if not SOFTWARE_CENTER_DEBUG_TABS:
            self.notebook.set_show_tabs(False)
        self.notebook.set_show_border(False)
        # make a spinner view to display while the applist is loading
        self.spinner_notebook = SpinnerNotebook(self.notebook)
        self.pack_start(self.spinner_notebook, True, True, 0)

        # add a bar at the bottom (hidden by default) for contextual actions
        self.action_bar = ActionBar()
        self.pack_start(self.action_bar, False, True, 0)

        # cursor
        self.busy_cursor = Gdk.Cursor.new(Gdk.CursorType.WATCH)

        # views to be created in init_view
        self.app_view = None
        self.app_details_view = None

    def init_view(self):
        """
        Initialize those UI components that are common to all subclasses of
        SoftwarePane.  Note that this method is intended to be called by
        the subclass itself at the start of its own init_view() implementation.
        """
        # common UI elements (applist and appdetails)
        # its the job of the Child class to put it into a good location
        # list
        self.box_app_list = Gtk.VBox()

        # search aid
        self.search_aid = SearchAid(self)
        self.box_app_list.pack_start(self.search_aid, False, False, 0)

        self.app_view = AppView(self.db, self.cache, self.icons,
                                self.show_ratings)
        self.app_view.connect("sort-method-changed",
                              self.on_app_view_sort_method_changed)

        self.init_atk_name(self.app_view, "app_view")
        self.box_app_list.pack_start(self.app_view, True, True, 0)
        self.app_view.connect("application-selected",
                              self.on_application_selected)
        self.app_view.connect("application-activated",
                              self.on_application_activated)

        # details
        self.scroll_details = Gtk.ScrolledWindow()
        self.scroll_details.set_policy(Gtk.PolicyType.AUTOMATIC,
                                       Gtk.PolicyType.AUTOMATIC)
        self.app_details_view = AppDetailsView(self.db, self.distro,
                                               self.icons, self.cache,
                                               self.datadir)
        self.app_details_view.connect("different-application-selected",
                                      self.on_application_activated)
        self.scroll_details.add(self.app_details_view)
        # when the cache changes, refresh the app list
        self.cache.connect("cache-ready", self.on_cache_ready)

        # connect signals
        self.connect("app-list-changed", self.on_app_list_changed)

        # db reopen
        if self.db:
            self.db.connect("reopen", self.on_db_reopen)

    def init_atk_name(self, widget, name):
        """ init the atk name for a given gtk widget based on parent-pane
            and variable name (used for the mago tests)
        """
        name = self.__class__.__name__ + "." + name
        Atk.Object.set_name(widget.get_accessible(), name)

    def on_cache_ready(self, cache):
        " refresh the application list when the cache is re-opened "
        LOG.debug("on_cache_ready")

    @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, SoftwarePane.Pages.DETAILS, self.state,
                        self.display_details_page)

    def show_app(self, app):
        self.on_application_activated(None, app)

    def on_nav_back_clicked(self, widget):
        vm = get_viewmanager()
        vm.nav_back()

    def on_nav_forward_clicked(self, widget):
        vm = get_viewmanager()
        vm.nav_forward()

    def on_query_complete(self, enquirer):
        self.emit("app-list-changed", len(enquirer.matches))
        self.app_view.display_matches(enquirer.matches,
                                      self._is_in_search_mode())
        self.hide_appview_spinner()

    def on_app_view_sort_method_changed(self, app_view, combo):
        if app_view.get_sort_mode() == self.enquirer.sortmode:
            return

        self.show_appview_spinner()
        app_view.clear_model()
        query = self.get_query()
        self._refresh_apps_with_apt_cache(query)

    def _is_in_search_mode(self):
        return (self.state.search_term and len(self.state.search_term) >= 2)

    def show_appview_spinner(self):
        """ display the spinner in the appview panel """
        LOG.debug("show_appview_spinner")
        # FIXME: totally the wrong place!
        if not self.state.search_term:
            self.action_bar.clear()
        self.spinner_notebook.show_spinner()

    def hide_appview_spinner(self):
        """ hide the spinner and display the appview in the panel """
        LOG.debug("hide_appview_spinner")
        self.spinner_notebook.hide_spinner()

    def set_section(self, section):
        self.section = section
        self.app_details_view.set_section(section)

    def section_sync(self):
        self.app_details_view.set_section(self.section)

    def on_app_list_changed(self, pane, length):
        """internal helper that keeps the the action bar up-to-date by
           keeping track of the app-list-changed signals
        """

        self.show_nonapps_if_required(len(self.enquirer.matches))
        self.search_aid.update_search_help(self.state)

    def hide_nonapps(self):
        """ hide non-applications control in the action bar """
        self.action_bar.unset_label()

    def show_nonapps_if_required(self, length):
        """
        update the state of the show/hide non-applications control
        in the action_bar
        """

        enquirer = self.enquirer
        n_apps = enquirer.nr_apps
        n_pkgs = enquirer.nr_pkgs

        # calculate the number of apps/pkgs
        if enquirer.limit > 0 and enquirer.limit < n_pkgs:
            n_apps = min(enquirer.limit, n_apps)
            n_pkgs = min(enquirer.limit - n_apps, n_pkgs)

        if not (n_apps and n_pkgs):
            self.hide_nonapps()
            return

        LOG.debug("nonapps_visible value=%s (always visible: %s)" %
                  (self.nonapps_visible, self.nonapps_visible
                   == NonAppVisibility.ALWAYS_VISIBLE))

        self.action_bar.unset_label()
        if self.nonapps_visible == NonAppVisibility.ALWAYS_VISIBLE:
            LOG.debug('non-apps-ALWAYS-visible')
            # TRANSLATORS: the text inbetween the underscores acts as a link
            # In most/all languages you will want the whole string as a link
            label = gettext.ngettext("_Hide %(amount)i technical item_",
                                     "_Hide %(amount)i technical items_",
                                     n_pkgs) % {
                                         'amount': n_pkgs
                                     }
            self.action_bar.set_label(label,
                                      link_result=self._hide_nonapp_pkgs)
        else:
            label = gettext.ngettext("_Show %(amount)i technical item_",
                                     "_Show %(amount)i technical items_",
                                     n_pkgs) % {
                                         'amount': n_pkgs
                                     }
            self.action_bar.set_label(label,
                                      link_result=self._show_nonapp_pkgs)

    def _show_nonapp_pkgs(self):
        self.nonapps_visible = NonAppVisibility.ALWAYS_VISIBLE
        self.refresh_apps()
        return True

    def _hide_nonapp_pkgs(self):
        self.nonapps_visible = NonAppVisibility.MAYBE_VISIBLE
        self.refresh_apps()
        return True

    def get_query(self):
        channel_query = None
        #name = self.pane_name
        if self.channel:
            channel_query = self.channel.query
            #name = self.channel.display_name

        # search terms
        if self.apps_search_term:
            query = self.db.get_query_list_from_search_entry(
                self.apps_search_term, channel_query)

            return query
        # overview list
        # if we are in a channel, limit to that
        if channel_query:
            return channel_query
        # ... otherwise show all
        return xapian.Query("")

    def refresh_apps(self, query=None):
        """refresh the applist and update the navigation bar """
        LOG.debug("refresh_apps")

        # FIXME: make this available for all panes
        if query is None:
            query = self.get_query()

        # this can happen e.g. when opening a deb file, see bug #951238
        if not self.app_view:
            return
        self.app_view.clear_model()
        self.search_aid.reset()
        self.show_appview_spinner()
        self._refresh_apps_with_apt_cache(query)

    def quick_query_len(self, query):
        """ do a blocking query that only returns the amount of
            matches from this query
        """
        with ExecutionTime("enquirer.set_query() quick query"):
            self.enquirer.set_query(query,
                                    limit=self.get_app_items_limit(),
                                    nonapps_visible=self.nonapps_visible,
                                    nonblocking_load=False,
                                    filter=self.state.filter)
        return len(self.enquirer.matches)

    @wait_for_apt_cache_ready
    def _refresh_apps_with_apt_cache(self, query):
        LOG.debug("softwarepane query: %s" % query)

        self.app_view.configure_sort_method(self._is_in_search_mode())

        # a nonblocking query calls on_query_complete once finished
        with ExecutionTime("enquirer.set_query()"):
            self.enquirer.set_query(query,
                                    limit=self.get_app_items_limit(),
                                    sortmode=self.get_sort_mode(),
                                    exact=self.is_custom_list(),
                                    nonapps_visible=self.nonapps_visible,
                                    filter=self.state.filter)

    def display_details_page(self, page, view_state):
        self.app_details_view.show_app(view_state.application)
        self.action_bar.unset_label()
        return True

    def is_custom_list(self):
        return self.apps_search_term and ',' in self.apps_search_term

    def get_current_page(self):
        return self.notebook.get_current_page()

    def get_app_items_limit(self):
        if self.state.search_term:
            return DEFAULT_SEARCH_LIMIT
        elif self.state.subcategory and self.state.subcategory.item_limit > 0:
            return self.state.subcategory.item_limit
        elif self.state.category and self.state.category.item_limit > 0:
            return self.state.category.item_limit
        return 0

    def get_sort_mode(self):
        # if the category sets a custom sort order, that wins, this
        # is required for top-rated and whats-new
        if (self.state.category
                and self.state.category.sortmode != SortMethods.BY_ALPHABET):
            return self.state.category.sortmode
        # ask the app_view for the sort-mode
        return self.app_view.get_sort_mode()

    def on_search_entry_key_press_event(self, event):
        """callback when a key is pressed in the search entry widget"""
        if not self.is_applist_view_showing():
            return
        if ((event.keyval == Gdk.keyval_from_name("Down")
             or event.keyval == Gdk.keyval_from_name("KP_Down"))
                and self.is_applist_view_showing()
                and len(self.app_view.tree_view.get_model()) > 0):
            # select the first item in the applist search result
            self.app_view.tree_view.grab_focus()
            self.app_view.tree_view.set_cursor(Gtk.TreePath(), None, False)

    def on_search_terms_changed(self, terms):
        """Stub implementation."""
        pass

    def on_db_reopen(self, db):
        """Stub implementation."""
        LOG.debug("%r: on_db_reopen (db is %r).", self.__class__.__name__, db)

    def is_category_view_showing(self):
        """Stub implementation."""
        pass

    def is_applist_view_showing(self):
        """Stub implementation."""
        pass

    def is_app_details_view_showing(self):
        """Stub implementation."""
        pass

    def get_current_app(self):
        """Stub implementation."""
        pass

    def on_application_selected(self, widget, app):
        """Stub implementation."""
        pass

    def get_current_category(self):
        """Stub implementation."""
        pass

    def unset_current_category(self):
        """Stub implementation."""
        pass
Beispiel #15
0
    def __init__(self, cache, db, distro, icons):
        Gtk.VBox.__init__(self)
        self.cache = cache
        self.db = db
        self.distro = distro
        self.icons = icons

        self.apps_filter = None
        self.state = DisplayState()

        self.pane_name = _("History")

        # Icon cache, invalidated upon icon theme changes
        self._app_icon_cache = {}
        self._reset_icon_cache()
        self.icons.connect('changed', self._reset_icon_cache)

        self._emblems = {}
        self._get_emblems(self.icons)

        vm = get_viewmanager()
        self.searchentry = vm.get_global_searchentry()

        self.toolbar = Gtk.Toolbar()
        self.toolbar.show()
        self.toolbar.set_style(Gtk.ToolbarStyle.TEXT)
        self.pack_start(self.toolbar, False, True, 0)

        all_action = Gtk.RadioAction('filter_all', _('All Changes'), None,
                                     None, self.ALL)
        all_action.connect('changed', self.change_filter)
        all_button = all_action.create_tool_item()
        self.toolbar.insert(all_button, 0)

        installs_action = Gtk.RadioAction('filter_installs',
                                          _('Installations'), None, None,
                                          self.INSTALLED)
        installs_action.join_group(all_action)
        installs_button = installs_action.create_tool_item()
        self.toolbar.insert(installs_button, 1)

        upgrades_action = Gtk.RadioAction('filter_upgrads', _('Updates'), None,
                                          None, self.UPGRADED)
        upgrades_action.join_group(all_action)
        upgrades_button = upgrades_action.create_tool_item()
        self.toolbar.insert(upgrades_button, 2)

        removals_action = Gtk.RadioAction('filter_removals', _('Removals'),
                                          None, None, self.REMOVED)
        removals_action.join_group(all_action)
        removals_button = removals_action.create_tool_item()
        self.toolbar.insert(removals_button, 3)
        self.toolbar.connect('draw', self.on_toolbar_draw)

        self._actions_list = all_action.get_group()
        self._set_actions_sensitive(False)

        self.view = Gtk.TreeView()
        self.view.set_headers_visible(False)
        self.view.show()
        self.history_view = Gtk.ScrolledWindow()
        self.history_view.set_policy(Gtk.PolicyType.AUTOMATIC,
                                     Gtk.PolicyType.AUTOMATIC)
        self.history_view.show()
        self.history_view.add(self.view)

        # make a spinner to display while history is loading
        self.spinner_notebook = SpinnerNotebook(self.history_view,
                                                _('Loading history'))

        self.pack_start(self.spinner_notebook, True, True, 0)

        self.store = Gtk.TreeStore(*self.COL_TYPES)
        self.visible_changes = 0
        self.store_filter = self.store.filter_new(None)
        self.store_filter.set_visible_func(self.filter_row, None)
        self.view.set_model(self.store_filter)
        all_action.set_active(True)
        self.last = None

        # to save (a lot of) time at startup we load history later, only when
        # it is selected to be viewed
        self.history = None

        self.column = Gtk.TreeViewColumn(_('Date'))
        self.view.append_column(self.column)
        self.cell_icon = Gtk.CellRendererPixbuf()
        self.cell_icon.set_padding(self.PADDING, self.PADDING / 2)
        self.column.pack_start(self.cell_icon, False)
        self.column.set_cell_data_func(self.cell_icon, self.render_cell_icon)
        self.cell_text = Gtk.CellRendererText()
        self.column.pack_start(self.cell_text, True)
        self.column.set_cell_data_func(self.cell_text, self.render_cell_text)
        self.cell_time = Gtk.CellRendererText()
        self.cell_time.set_padding(6, 0)
        self.cell_time.set_alignment(1.0, 0.5)
        self.column.pack_end(self.cell_time, False)
        self.column.set_cell_data_func(self.cell_time, self.render_cell_time)

        # busy cursor
        self.busy_cursor = Gdk.Cursor.new(Gdk.CursorType.WATCH)
Beispiel #16
0
    def init_view(self):
        if self.view_initialized:
            return

        SoftwarePane.init_view(self)

        # show a busy cursor and display the main spinner while we build the
        # view
        window = self.get_window()
        if window:
            window.set_cursor(self.busy_cursor)
        self.show_appview_spinner()

        self.oneconf_viewpickler = OneConfViews(self.icons)
        self.oneconf_viewpickler.register_computer(None, _("This computer (%s)") % platform.node())
        self.oneconf_viewpickler.select_first()
        self.oneconf_viewpickler.connect("computer-changed", self._selected_computer_changed)
        self.oneconf_viewpickler.connect("current-inventory-refreshed", self._current_inventory_need_refresh)

        # Start OneConf
        self.oneconf_handler = get_oneconf_handler(self.oneconf_viewpickler)
        if self.oneconf_handler:
            self.oneconf_handler.connect("show-oneconf-changed", self._show_oneconf_changed)
            self.oneconf_handler.connect("last-time-sync-changed", self._last_time_sync_oneconf_changed)

        # OneConf pane
        self.computerpane = Gtk.Paned.new(Gtk.Orientation.HORIZONTAL)
        self.oneconfcontrol = Gtk.Box()
        self.oneconfcontrol.set_orientation(Gtk.Orientation.VERTICAL)
        self.computerpane.pack1(self.oneconfcontrol, False, False)
        # size negotiation takes everything for the first one
        self.oneconfcontrol.set_property("width-request", 200)
        self.box_app_list.pack_start(self.computerpane, True, True, 0)

        scroll = Gtk.ScrolledWindow()
        scroll.set_shadow_type(Gtk.ShadowType.IN)
        scroll.add(self.oneconf_viewpickler)
        self.oneconfcontrol.pack_start(scroll, True, True, 0)

        oneconftoolbar = Gtk.Box()
        oneconftoolbar.set_orientation(Gtk.Orientation.HORIZONTAL)
        oneconfpropertymenu = Gtk.Menu()
        self.oneconfproperty = MenuButton(
            oneconfpropertymenu, Gtk.Image.new_from_stock(Gtk.STOCK_PROPERTIES, Gtk.IconSize.BUTTON)
        )
        self.stopsync_label = _(u"Stop Syncing “%s”")
        stop_oneconf_share_menuitem = Gtk.MenuItem(label=self.stopsync_label % platform.node())
        stop_oneconf_share_menuitem.connect("activate", self._on_stop_oneconf_hostshare_clicked)
        stop_oneconf_share_menuitem.show()
        oneconfpropertymenu.append(stop_oneconf_share_menuitem)
        self.oneconfcontrol.pack_start(oneconftoolbar, False, False, 1)
        self.oneconf_last_sync = Gtk.Label()
        self.oneconf_last_sync.set_line_wrap(True)
        oneconftoolbar.pack_start(self.oneconfproperty, False, False, 0)
        oneconftoolbar.pack_start(self.oneconf_last_sync, True, True, 1)

        self.notebook.append_page(self.box_app_list, Gtk.Label(label="list"))

        # details
        self.notebook.append_page(self.scroll_details, Gtk.Label(label="details"))
        # initial refresh
        self.state.search_term = ""

        # build models and filters
        self.base_model = AppTreeStore(self.db, self.cache, self.icons)

        self.treefilter = self.base_model.filter_new(None)
        self.treefilter.set_visible_func(self._row_visibility_func, AppTreeStore.COL_ROW_DATA)
        self.app_view.set_model(self.treefilter)
        self.app_view.tree_view.connect("row-collapsed", self._on_row_collapsed)

        self._all_cats = self.parse_applications_menu(APP_INSTALL_PATH)
        self._all_cats = categories_sorted_by_name(self._all_cats)

        # we do not support the search aid feature in the installedview
        self.box_app_list.remove(self.search_aid)

        # remove here
        self.box_app_list.remove(self.app_view)

        # create a local spinner notebook for the installed view
        self.installed_spinner_notebook = SpinnerNotebook(self.app_view)

        self.computerpane.pack2(self.installed_spinner_notebook, True, True)
        self.show_installed_view_spinner()

        self.show_all()

        # initialize view to hide the oneconf computer selector
        self.oneconf_viewpickler.select_first()
        self.oneconfcontrol.hide()

        # hacky, hide the header
        self.app_view.header_hbox.hide()

        self.hide_appview_spinner()

        # keep track of the current view by tracking its origin
        self.current_displayed_origin = None

        # now we are initialized
        self.emit("installed-pane-created")

        self.view_initialized = True
        return False
Beispiel #17
0
class InstalledPane(SoftwarePane, CategoriesParser):
    """Widget that represents the installed panel in software-center
       It contains a search entry and navigation buttons
    """

    class Pages:
        # page names, useful for debugging
        NAMES = ("list", "details")
        # the actual page id's
        (LIST, DETAILS) = range(2)
        # the default page
        HOME = LIST

    # pages for the installed view spinner notebook
    (PAGE_SPINNER, PAGE_INSTALLED) = range(2)

    __gsignals__ = {"installed-pane-created": (GObject.SignalFlags.RUN_FIRST, None, ())}

    def __init__(self, cache, db, distro, icons, datadir):

        # parent
        SoftwarePane.__init__(self, cache, db, distro, icons, datadir, show_ratings=False)
        CategoriesParser.__init__(self, db)

        self.current_appview_selection = None
        self.icons = icons
        self.loaded = False
        self.pane_name = _("Installed Software")

        self.installed_apps = 0
        # None is local
        self.current_hostid = None
        self.current_hostname = None
        self.oneconf_additional_pkg = set()
        self.oneconf_missing_pkg = set()

        # switches to terminate build in progress
        self._build_in_progress = False
        self._halt_build = False

        self.nonapps_visible = NonAppVisibility.NEVER_VISIBLE

        self.visible_docids = None
        self.visible_cats = {}

        self.installed_spinner_notebook = None

    def init_view(self):
        if self.view_initialized:
            return

        SoftwarePane.init_view(self)

        # show a busy cursor and display the main spinner while we build the
        # view
        window = self.get_window()
        if window:
            window.set_cursor(self.busy_cursor)
        self.show_appview_spinner()

        self.oneconf_viewpickler = OneConfViews(self.icons)
        self.oneconf_viewpickler.register_computer(None, _("This computer (%s)") % platform.node())
        self.oneconf_viewpickler.select_first()
        self.oneconf_viewpickler.connect("computer-changed", self._selected_computer_changed)
        self.oneconf_viewpickler.connect("current-inventory-refreshed", self._current_inventory_need_refresh)

        # Start OneConf
        self.oneconf_handler = get_oneconf_handler(self.oneconf_viewpickler)
        if self.oneconf_handler:
            self.oneconf_handler.connect("show-oneconf-changed", self._show_oneconf_changed)
            self.oneconf_handler.connect("last-time-sync-changed", self._last_time_sync_oneconf_changed)

        # OneConf pane
        self.computerpane = Gtk.Paned.new(Gtk.Orientation.HORIZONTAL)
        self.oneconfcontrol = Gtk.Box()
        self.oneconfcontrol.set_orientation(Gtk.Orientation.VERTICAL)
        self.computerpane.pack1(self.oneconfcontrol, False, False)
        # size negotiation takes everything for the first one
        self.oneconfcontrol.set_property("width-request", 200)
        self.box_app_list.pack_start(self.computerpane, True, True, 0)

        scroll = Gtk.ScrolledWindow()
        scroll.set_shadow_type(Gtk.ShadowType.IN)
        scroll.add(self.oneconf_viewpickler)
        self.oneconfcontrol.pack_start(scroll, True, True, 0)

        oneconftoolbar = Gtk.Box()
        oneconftoolbar.set_orientation(Gtk.Orientation.HORIZONTAL)
        oneconfpropertymenu = Gtk.Menu()
        self.oneconfproperty = MenuButton(
            oneconfpropertymenu, Gtk.Image.new_from_stock(Gtk.STOCK_PROPERTIES, Gtk.IconSize.BUTTON)
        )
        self.stopsync_label = _(u"Stop Syncing “%s”")
        stop_oneconf_share_menuitem = Gtk.MenuItem(label=self.stopsync_label % platform.node())
        stop_oneconf_share_menuitem.connect("activate", self._on_stop_oneconf_hostshare_clicked)
        stop_oneconf_share_menuitem.show()
        oneconfpropertymenu.append(stop_oneconf_share_menuitem)
        self.oneconfcontrol.pack_start(oneconftoolbar, False, False, 1)
        self.oneconf_last_sync = Gtk.Label()
        self.oneconf_last_sync.set_line_wrap(True)
        oneconftoolbar.pack_start(self.oneconfproperty, False, False, 0)
        oneconftoolbar.pack_start(self.oneconf_last_sync, True, True, 1)

        self.notebook.append_page(self.box_app_list, Gtk.Label(label="list"))

        # details
        self.notebook.append_page(self.scroll_details, Gtk.Label(label="details"))
        # initial refresh
        self.state.search_term = ""

        # build models and filters
        self.base_model = AppTreeStore(self.db, self.cache, self.icons)

        self.treefilter = self.base_model.filter_new(None)
        self.treefilter.set_visible_func(self._row_visibility_func, AppTreeStore.COL_ROW_DATA)
        self.app_view.set_model(self.treefilter)
        self.app_view.tree_view.connect("row-collapsed", self._on_row_collapsed)

        self._all_cats = self.parse_applications_menu(APP_INSTALL_PATH)
        self._all_cats = categories_sorted_by_name(self._all_cats)

        # we do not support the search aid feature in the installedview
        self.box_app_list.remove(self.search_aid)

        # remove here
        self.box_app_list.remove(self.app_view)

        # create a local spinner notebook for the installed view
        self.installed_spinner_notebook = SpinnerNotebook(self.app_view)

        self.computerpane.pack2(self.installed_spinner_notebook, True, True)
        self.show_installed_view_spinner()

        self.show_all()

        # initialize view to hide the oneconf computer selector
        self.oneconf_viewpickler.select_first()
        self.oneconfcontrol.hide()

        # hacky, hide the header
        self.app_view.header_hbox.hide()

        self.hide_appview_spinner()

        # keep track of the current view by tracking its origin
        self.current_displayed_origin = None

        # now we are initialized
        self.emit("installed-pane-created")

        self.view_initialized = True
        return False

    def show_installed_view_spinner(self):
        """ display the local spinner for the installed view panel """
        if self.installed_spinner_notebook:
            self.installed_spinner_notebook.show_spinner()

    def hide_installed_view_spinner(self):
        """ hide the local spinner for the installed view panel """
        if self.installed_spinner_notebook:
            self.installed_spinner_notebook.hide_spinner()

    def _selected_computer_changed(self, oneconf_pickler, hostid, hostname):
        if self.current_hostid == hostid:
            return
        LOG.debug("Selected computer changed to %s (%s)" % (hostid, hostname))
        self.current_hostid = hostid
        self.current_hostname = hostname
        menuitem = self.oneconfproperty.get_menu().get_children()[0]
        if self.current_hostid:
            diff = self.oneconf_handler.oneconf.diff(self.current_hostid, "")
            self.oneconf_additional_pkg, self.oneconf_missing_pkg = diff
            stopsync_hostname = self.current_hostname
            # FIXME for P: oneconf views don't support search
            if self.state.search_term:
                self._search()
        else:
            stopsync_hostname = platform.node()
            self.searchentry.show()
        menuitem.set_label(self.stopsync_label % stopsync_hostname.encode("utf-8"))
        self.refresh_apps()

    def _last_time_sync_oneconf_changed(self, oneconf_handler, msg):
        LOG.debug("refresh latest sync date")
        self.oneconf_last_sync.set_label(msg)

    def _show_oneconf_changed(self, oneconf_handler, oneconf_inventory_shown):
        LOG.debug("Share inventory status changed")
        if oneconf_inventory_shown:
            self.oneconfcontrol.show()
        else:
            self.oneconf_viewpickler.select_first()
            self.oneconfcontrol.hide()

    def _on_stop_oneconf_hostshare_clicked(self, widget):
        LOG.debug("Stop sharing inventory for %s" % self.current_hostname)
        self.oneconf_handler.sync_between_computers(False, self.current_hostid)
        # stop sharing another host than the local one.
        if self.current_hostid:
            self.oneconf_viewpickler.remove_computer(self.current_hostid)
            self.oneconf_viewpickler.select_first()

    def _current_inventory_need_refresh(self, oneconfviews):
        if self.current_hostid:
            diff = self.oneconf_handler.oneconf.diff(self.current_hostid, "")
            self.oneconf_additional_pkg, self.oneconf_missing_pkg = diff
        self.refresh_apps()

    def _on_row_collapsed(self, view, it, path):
        pass

    def _row_visibility_func(self, model, it, col):
        row = model.get_value(it, col)
        if self.visible_docids is None:
            if isinstance(row, CategoryRowReference):
                row.vis_count = row.pkg_count
            return True

        elif isinstance(row, CategoryRowReference):
            return row.untranslated_name in self.visible_cats.keys()

        elif row is None:
            return False

        return row.get_docid() in self.visible_docids

    def _use_category(self, cat):
        # System cat is large and slow to search, filter it in default mode

        if "carousel-only" in cat.flags or (
            (self.nonapps_visible == NonAppVisibility.NEVER_VISIBLE) and cat.untranslated_name == "System"
        ):
            return False

        return True

    # override its SoftwarePane._hide_nonapp_pkgs...
    def _hide_nonapp_pkgs(self):
        self.nonapps_visible = NonAppVisibility.NEVER_VISIBLE
        self.refresh_apps()
        return True

    def _save_treeview_state(self):
        # store the state
        expanded_rows = []
        self.app_view.tree_view.map_expanded_rows(lambda view, path, data: expanded_rows.append(path.to_string()), None)
        va = self.app_view.tree_view_scroll.get_vadjustment()
        if va:
            vadj = va.get_value()
        else:
            vadj = 0
        return expanded_rows, vadj

    def _restore_treeview_state(self, state):
        expanded_rows, vadj = state
        for ind in expanded_rows:
            path = Gtk.TreePath.new_from_string(ind)
            self.app_view.tree_view.expand_row(path, False)
        va = self.app_view.tree_view_scroll.get_vadjustment()
        if va:
            va.set_lower(vadj)
            va.set_value(vadj)

    # ~ @interrupt_build_and_wait
    def _build_categorised_installedview(self, keep_state=False):
        LOG.debug("Rebuilding categorised installedview...")

        # display the busy cursor and a local spinner while we build the view
        window = self.get_window()
        if window:
            window.set_cursor(self.busy_cursor)
        self.show_installed_view_spinner()

        if keep_state:
            treeview_state = self._save_treeview_state()

        # disconnect the model to avoid e.g. updates of "cursor-changed"
        #  AppTreeView.expand_path while the model is in rebuild-flux
        self.app_view.set_model(None)

        model = self.base_model  # base model not treefilter
        model.clear()

        def profiled_rebuild_categorised_view():
            with ExecutionTime("rebuild_categorized_view"):
                rebuild_categorised_view()

        def rebuild_categorised_view():
            self.cat_docid_map = {}
            enq = self.enquirer

            i = 0

            while Gtk.events_pending():
                Gtk.main_iteration()

            xfilter = AppFilter(self.db, self.cache)
            xfilter.set_installed_only(True)

            for cat in self._all_cats:
                # for each category do category query and append as a new
                # node to tree_view
                if not self._use_category(cat):
                    continue
                query = self.get_query_for_cat(cat)
                LOG.debug("xfilter.installed_only: %s" % xfilter.installed_only)
                enq.set_query(
                    query,
                    sortmode=SortMethods.BY_ALPHABET,
                    nonapps_visible=self.nonapps_visible,
                    filter=xfilter,
                    nonblocking_load=False,
                    persistent_duplicate_filter=(i > 0),
                )

                L = len(enq.matches)
                if L:
                    i += L
                    docs = enq.get_documents()
                    self.cat_docid_map[cat.untranslated_name] = set([doc.get_docid() for doc in docs])
                    model.set_category_documents(cat, docs)

            while Gtk.events_pending():
                Gtk.main_iteration()

            # check for uncategorised pkgs
            if self.state.channel:
                self._run_channel_enquirer(persistent_duplicate_filter=(i > 0))
                L = len(enq.matches)
                if L:
                    # some foo for channels
                    # if no categorised results but in channel, then use
                    # the channel name for the category
                    channel_name = None
                    if not i and self.state.channel:
                        channel_name = self.state.channel.display_name
                    docs = enq.get_documents()
                    tag = channel_name or "Uncategorized"
                    self.cat_docid_map[tag] = set([doc.get_docid() for doc in docs])
                    model.set_nocategory_documents(docs, untranslated_name=tag, display_name=channel_name)
                    i += L

            if i:
                self.app_view.tree_view.set_cursor(Gtk.TreePath(), None, False)
                if i <= 10:
                    self.app_view.tree_view.expand_all()

            # cache the installed app count
            self.installed_count = i
            self.app_view._append_appcount(self.installed_count, mode=AppView.INSTALLED_MODE)

            self.app_view.set_model(self.treefilter)
            if keep_state:
                self._restore_treeview_state(treeview_state)

            # hide the local spinner
            self.hide_installed_view_spinner()

            if window:
                window.set_cursor(None)

            # reapply search if needed
            if self.state.search_term:
                self._do_search(self.state.search_term)

            self.emit("app-list-changed", i)
            return

        GObject.idle_add(profiled_rebuild_categorised_view)

    def _build_oneconfview(self, keep_state=False):
        LOG.debug("Rebuilding oneconfview for %s..." % self.current_hostid)

        # display the busy cursor and the local spinner while we build the view
        window = self.get_window()
        if window:
            window.set_cursor(self.busy_cursor)
        self.show_installed_view_spinner()

        if keep_state:
            treeview_state = self._save_treeview_state()

        # disconnect the model to avoid e.g. updates of "cursor-changed"
        #  AppTreeView.expand_path while the model is in rebuild-flux
        self.app_view.set_model(None)

        model = self.base_model  # base model not treefilter
        model.clear()

        def profiled_rebuild_oneconfview():
            with ExecutionTime("rebuild_oneconfview"):
                rebuild_oneconfview()

        def rebuild_oneconfview():

            # FIXME for P: hide the search entry
            self.searchentry.hide()

            self.cat_docid_map = {}
            enq = self.enquirer
            query = xapian.Query("")
            if self.state.channel and self.state.channel.query:
                query = xapian.Query(xapian.Query.OP_AND, query, self.state.channel.query)

            i = 0

            # First search: missing apps only
            xfilter = AppFilter(self.db, self.cache)
            xfilter.set_restricted_list(self.oneconf_additional_pkg)
            xfilter.set_not_installed_only(True)

            enq.set_query(
                query,
                sortmode=SortMethods.BY_ALPHABET,
                nonapps_visible=self.nonapps_visible,
                filter=xfilter,
                nonblocking_load=True,  # we don't block this one for
                # better oneconf responsiveness
                persistent_duplicate_filter=(i > 0),
            )

            L = len(enq.matches)

            if L:
                cat_title = utf8(
                    ngettext(
                        u"%(amount)s item on “%(machine)s” not on this computer",
                        u"%(amount)s items on “%(machine)s” not on this computer",
                        L,
                    )
                ) % {"amount": L, "machine": utf8(self.current_hostname)}
                i += L
                docs = enq.get_documents()
                self.cat_docid_map["missingpkg"] = set([doc.get_docid() for doc in docs])
                model.set_nocategory_documents(docs, untranslated_name="additionalpkg", display_name=cat_title)

            # Second search: additional apps
            xfilter.set_restricted_list(self.oneconf_missing_pkg)
            xfilter.set_not_installed_only(False)
            xfilter.set_installed_only(True)
            enq.set_query(
                query,
                sortmode=SortMethods.BY_ALPHABET,
                nonapps_visible=self.nonapps_visible,
                filter=xfilter,
                nonblocking_load=False,
                persistent_duplicate_filter=(i > 0),
            )

            L = len(enq.matches)
            if L:
                cat_title = utf8(
                    ngettext(
                        u"%(amount)s item on this computer not on “%(machine)s”",
                        "%(amount)s items on this computer not on “%(machine)s”",
                        L,
                    )
                ) % {"amount": L, "machine": utf8(self.current_hostname)}
                i += L
                docs = enq.get_documents()
                self.cat_docid_map["additionalpkg"] = set([doc.get_docid() for doc in docs])
                model.set_nocategory_documents(docs, untranslated_name="additionalpkg", display_name=cat_title)

            if i:
                self.app_view.tree_view.set_cursor(Gtk.TreePath(), None, False)
                if i <= 10:
                    self.app_view.tree_view.expand_all()

            # cache the installed app count
            self.installed_count = i
            self.app_view._append_appcount(self.installed_count, mode=AppView.DIFF_MODE)

            self.app_view.set_model(self.treefilter)
            if keep_state:
                self._restore_treeview_state(treeview_state)

            # hide the local spinner
            self.hide_installed_view_spinner()

            if window:
                window.set_cursor(None)

            self.emit("app-list-changed", i)
            return

        GObject.idle_add(profiled_rebuild_oneconfview)

    def _check_expand(self):
        it = self.treefilter.get_iter_first()
        while it:
            path = self.treefilter.get_path(it)
            if self.state.search_term:  # or path in self._user_expanded_paths:
                self.app_view.tree_view.expand_row(path, False)
            else:
                self.app_view.tree_view.collapse_row(path)

            it = self.treefilter.iter_next(it)

    def _do_search(self, terms):
        self.state.search_term = terms
        xfilter = AppFilter(self.db, self.cache)
        xfilter.set_installed_only(True)
        self.enquirer.set_query(
            self.get_query(), nonapps_visible=self.nonapps_visible, filter=xfilter, nonblocking_load=True
        )

        self.visible_docids = self.enquirer.get_docids()
        self.visible_cats = self._get_vis_cats(self.visible_docids)
        self.treefilter.refilter()
        self.app_view.tree_view.expand_all()

    def _run_channel_enquirer(self, persistent_duplicate_filter=True):
        xfilter = AppFilter(self.db, self.cache)
        xfilter.set_installed_only(True)
        if self.state.channel:
            self.enquirer.set_query(
                self.state.channel.query,
                sortmode=SortMethods.BY_ALPHABET,
                nonapps_visible=NonAppVisibility.MAYBE_VISIBLE,
                filter=xfilter,
                nonblocking_load=False,
                persistent_duplicate_filter=persistent_duplicate_filter,
            )

    def _search(self, terms=None):
        if not terms:
            self.visible_docids = None
            self.state.search_term = ""
            self._clear_search()
            self.treefilter.refilter()
            self._check_expand()
            # run channel enquirer to ensure that the channel specific
            # info for show/hide nonapps is actually correct
            self._run_channel_enquirer()
            # trigger update of the show/hide
            self.emit("app-list-changed", 0)
        elif self.state.search_term != terms:
            self._do_search(terms)

    def get_query(self):
        # search terms
        return self.db.get_query_list_from_search_entry(self.state.search_term)

    def get_query_for_cat(self, cat):
        LOG.debug("self.state.channel: %s" % self.state.channel)
        if self.state.channel and self.state.channel.query:
            query = xapian.Query(xapian.Query.OP_AND, cat.query, self.state.channel.query)
            return query
        return cat.query

    @wait_for_apt_cache_ready
    def refresh_apps(self, *args, **kwargs):
        """refresh the applist and update the navigation bar """
        logging.debug("installedpane refresh_apps")
        keep_state = kwargs.get("keep_state", False)
        if self.current_hostid:
            self._build_oneconfview(keep_state)
        else:
            self._build_categorised_installedview(keep_state)

    def _clear_search(self):
        # remove the details and clear the search
        self.searchentry.clear_with_no_signal()

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

        self._search(terms.strip())
        self.state.search_term = terms
        self.notebook.set_current_page(InstalledPane.Pages.LIST)
        self.hide_installed_view_spinner()

    def _get_vis_cats(self, visids):
        vis_cats = {}
        appcount = 0
        visids = set(visids)
        for cat_uname, docids in self.cat_docid_map.iteritems():
            children = len(docids & visids)
            if children:
                appcount += children
                vis_cats[cat_uname] = children
        self.app_view._append_appcount(appcount, mode=AppView.DIFF_MODE)
        return vis_cats

    def _refresh_on_cache_or_db_change(self):
        self.refresh_apps(keep_state=True)
        self.app_details_view.refresh_app()

    def on_db_reopen(self, db):
        LOG.debug("on_db_reopen")
        self._refresh_on_cache_or_db_change()

    def on_cache_ready(self, cache):
        LOG.debug("on_cache_ready")
        self._refresh_on_cache_or_db_change()

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

    def get_callback_for_page(self, page, state):
        if page == InstalledPane.Pages.LIST:
            return self.display_overview_page
        return self.display_details_page

    def display_search(self):
        model = self.app_view.get_model()
        if model:
            self.emit("app-list-changed", len(model))
        self.searchentry.show()

    def display_overview_page(self, page, view_state):
        LOG.debug("view_state: %s" % view_state)
        if self.current_hostid:
            # FIXME for P: oneconf views don't support search
            # this one ensure that even when switching between pane, we
            # don't have the search item
            if self.state.search_term:
                self._search()
            self._build_oneconfview()
        elif view_state and view_state.channel and view_state.channel.origin is not self.current_displayed_origin:
            # we don't need to refresh the full installed view every time it
            # is displayed, so we check to see if we are viewing the same
            # channel and if so we don't refresh the view, note that the view
            # *is* is refreshed whenever the contents change and this is
            # sufficient (see LP: #828887)
            self._build_categorised_installedview()
            self.current_displayed_origin = view_state.channel.origin

            if self.state.search_term:
                self._search(self.state.search_term)
        return True

    def get_current_app(self):
        """return the current active application object applicable
           to the context"""
        return self.current_appview_selection

    def is_category_view_showing(self):
        # there is no category view in the installed pane
        return False

    def is_applist_view_showing(self):
        """Return True if we are in the applist view """
        return self.notebook.get_current_page() == InstalledPane.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() == InstalledPane.Pages.DETAILS
Beispiel #18
0
    def __init__(self, cache, db, distro, icons, datadir):
        Gtk.VBox.__init__(self)
        self.cache = cache
        self.db = db
        self.distro = distro
        self.icons = icons
        self.datadir = datadir

        self.apps_filter = None
        self.state = DisplayState()

        self.pane_name = _("History")

        # Icon cache, invalidated upon icon theme changes
        self._app_icon_cache = {}
        self._reset_icon_cache()
        self.icons.connect('changed', self._reset_icon_cache)

        self._emblems = {}
        self._get_emblems(self.icons)

        vm = get_viewmanager()
        self.searchentry = vm.get_global_searchentry()

        self.toolbar = Gtk.Toolbar()
        self.toolbar.show()
        self.toolbar.set_style(Gtk.ToolbarStyle.TEXT)
        self.pack_start(self.toolbar, False, True, 0)

        all_action = Gtk.RadioAction('filter_all', _('All Changes'), None,
            None, self.ALL)
        all_action.connect('changed', self.change_filter)
        all_button = all_action.create_tool_item()
        self.toolbar.insert(all_button, 0)

        installs_action = Gtk.RadioAction('filter_installs',
            _('Installations'), None, None, self.INSTALLED)
        installs_action.join_group(all_action)
        installs_button = installs_action.create_tool_item()
        self.toolbar.insert(installs_button, 1)

        upgrades_action = Gtk.RadioAction(
            'filter_upgrads', _('Updates'), None, None, self.UPGRADED)
        upgrades_action.join_group(all_action)
        upgrades_button = upgrades_action.create_tool_item()
        self.toolbar.insert(upgrades_button, 2)

        removals_action = Gtk.RadioAction(
            'filter_removals', _('Removals'), None, None, self.REMOVED)
        removals_action.join_group(all_action)
        removals_button = removals_action.create_tool_item()
        self.toolbar.insert(removals_button, 3)
        self.toolbar.connect('draw', self.on_toolbar_draw)

        self._actions_list = all_action.get_group()
        self._set_actions_sensitive(False)

        self.view = Gtk.TreeView()
        self.view.set_headers_visible(False)
        self.view.show()
        self.history_view = Gtk.ScrolledWindow()
        self.history_view.set_policy(Gtk.PolicyType.AUTOMATIC,
                                      Gtk.PolicyType.AUTOMATIC)
        self.history_view.show()
        self.history_view.add(self.view)

        # make a spinner to display while history is loading
        self.spinner_notebook = SpinnerNotebook(
            self.history_view, _('Loading history'))

        self.pack_start(self.spinner_notebook, True, True, 0)

        self.store = Gtk.TreeStore(*self.COL_TYPES)
        self.visible_changes = 0
        self.store_filter = self.store.filter_new(None)
        self.store_filter.set_visible_func(self.filter_row, None)
        self.view.set_model(self.store_filter)
        all_action.set_active(True)
        self.last = None

        # to save (a lot of) time at startup we load history later, only when
        # it is selected to be viewed
        self.history = None

        self.column = Gtk.TreeViewColumn(_('Date'))
        self.view.append_column(self.column)
        self.cell_icon = Gtk.CellRendererPixbuf()
        self.column.pack_start(self.cell_icon, False)
        self.column.set_cell_data_func(self.cell_icon, self.render_cell_icon)
        self.cell_text = Gtk.CellRendererText()
        self.column.pack_start(self.cell_text, True)
        self.column.set_cell_data_func(self.cell_text, self.render_cell_text)

        # busy cursor
        self.busy_cursor = Gdk.Cursor.new(Gdk.CursorType.WATCH)