# Copyright (C) 2009,2010 Canonical
#
# Authors:
# Michael Vogt
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; version 3.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
import logging
from gi.repository import Gtk, GObject
from gettext import gettext as _
from softwarecenter.enums import SortMethods
from softwarecenter.ui.gtk3.em import StockEms
from softwarecenter.ui.gtk3.models.appstore2 import AppTreeStore
from softwarecenter.ui.gtk3.widgets.apptreeview import AppTreeView
from softwarecenter.ui.gtk3.models.appstore2 import AppPropertiesHelper
from softwarecenter.utils import ExecutionTime
LOG = logging.getLogger(__name__)
class AppView(Gtk.VBox):
__gsignals__ = {
"sort-method-changed": (GObject.SignalFlags.RUN_LAST,
None,
(GObject.TYPE_PYOBJECT, ),
),
"application-activated": (GObject.SignalFlags.RUN_LAST,
None,
(GObject.TYPE_PYOBJECT, ),
),
"application-selected": (GObject.SignalFlags.RUN_LAST,
None,
(GObject.TYPE_PYOBJECT, ),
),
}
(INSTALLED_MODE, AVAILABLE_MODE, DIFF_MODE) = range(3)
_SORT_METHOD_INDEX = (SortMethods.BY_ALPHABET,
SortMethods.BY_TOP_RATED,
SortMethods.BY_CATALOGED_TIME,
SortMethods.BY_SEARCH_RANKING,)
# indices that relate to the above tuple
_SORT_BY_ALPHABET = 0
_SORT_BY_TOP_RATED = 1
_SORT_BY_NEWEST_FIRST = 2
_SORT_BY_SEARCH_RANKING = 3
def __init__(self, db, cache, icons, show_ratings):
Gtk.VBox.__init__(self)
#~ self.set_name("app-view")
# app properties helper
self.helper = AppPropertiesHelper(db, cache, icons)
# misc internal containers
self.header_hbox = Gtk.HBox()
self.header_hbox.set_border_width(StockEms.MEDIUM)
self.pack_start(self.header_hbox, False, False, 0)
self.tree_view_scroll = Gtk.ScrolledWindow()
self.pack_start(self.tree_view_scroll, True, True, 0)
# category label
self.header_label = Gtk.Label()
self.header_label.set_use_markup(True)
self.header_hbox.pack_start(self.header_label, False, False, 0)
# sort methods comboboxs
# variant 1 includes sort by search relevance
self.sort_methods_combobox = self._get_sort_methods_combobox()
combo_alignment = Gtk.Alignment.new(0.5, 0.5, 1.0, 0.0)
combo_alignment.add(self.sort_methods_combobox)
self.header_hbox.pack_end(combo_alignment, False, False, 0)
# content views
self.tree_view = AppTreeView(self, db, icons,
show_ratings, store=None)
self.tree_view_scroll.add(self.tree_view)
self.appcount = None
self.vadj = 0.0
# list view sorting stuff
self._force_default_sort_method = True
self._handler = self.sort_methods_combobox.connect(
"changed",
self.on_sort_method_changed)
#~ def on_draw(self, w, cr):
#~ cr.set_source_rgb(1,1,1)
#~ cr.paint()
def _append_appcount(self, appcount, mode=AVAILABLE_MODE):
#~
#~ if mode == self.INSTALLED_MODE:
#~ text = gettext.ngettext("%(amount)s item installed",
#~ "%(amount)s items installed",
#~ appcount) % { 'amount' : appcount, }
#~ elif mode == self.DIFF_MODE:
#~ text = gettext.ngettext("%(amount)s item",
#~ "%(amount)s items",
#~ appcount) % { 'amount' : appcount, }
#~ else:
#~ text = gettext.ngettext("%(amount)s item available",
#~ "%(amount)s items available",
#~ appcount) % { 'amount' : appcount, }
#~
#~ if not self.appcount:
#~ self.appcount = Gtk.Label()
#~ self.appcount.set_alignment(0.5, 0.5)
#~ self.appcount.set_margin_top(4)
#~ self.appcount.set_margin_bottom(3)
#~ self.appcount.connect("draw", self.on_draw)
#~ self.vbox.pack_start(self.appcount, False, False, 0)
#~ self.appcount.set_text(text)
#~ self.appcount.show()
pass
def on_sort_method_changed(self, *args):
self.vadj = 0.0
self.emit("sort-method-changed", self.sort_methods_combobox)
def _get_sort_methods_combobox(self):
combo = Gtk.ComboBoxText.new()
combo.append_text(_("By Name"))
combo.append_text(_("By Top Rated"))
combo.append_text(_("By Newest First"))
combo.append_text(_("By Relevance"))
combo.set_active(self._SORT_BY_TOP_RATED)
return combo
def _get_combo_children(self):
return len(self.sort_methods_combobox.get_model())
def _use_combobox_with_sort_by_search_ranking(self):
if self._get_combo_children() == 4:
return
self.sort_methods_combobox.append_text(_("By Relevance"))
def _use_combobox_without_sort_by_search_ranking(self):
if self._get_combo_children() == 3:
return
self.sort_methods_combobox.remove(self._SORT_BY_SEARCH_RANKING)
self.set_sort_method_with_no_signal(self._SORT_BY_TOP_RATED)
def set_sort_method_with_no_signal(self, sort_method):
combo = self.sort_methods_combobox
combo.handler_block(self._handler)
combo.set_active(sort_method)
combo.handler_unblock(self._handler)
def set_allow_user_sorting(self, do_allow):
self.sort_methods_combobox.set_visible(do_allow)
def set_header_labels(self, first_line, second_line):
if second_line:
markup = '%s\n%s' % (first_line, second_line)
else:
markup = "%s" % first_line
return self.header_label.set_markup(markup)
def set_model(self, model):
self.tree_view.set_model(model)
def get_model(self):
return self.tree_view.appmodel
def display_matches(self, matches, is_search=False):
# FIXME: installedpane handles display of the trees intimately,
# so for the time being lets just return None in the case of our
# TreeView displaying an AppTreeStore ... ;(
# ... also we dont currently support user sorting in the
# installedview, so issue is somewhat moot for the time being...
if isinstance(self.get_model(), AppTreeStore):
LOG.debug("display_matches called on AppTreeStore, ignoring")
return
model = self.get_model()
# disconnect the model from the view before running
# set_from_matches to ensure that the _cell_data_func_cb is not
# run when the placeholder items are set, otherwise the purpose
# of the "load-on-demand" is gone and it leads to bugs like
# LP: #964433
self.set_model(None)
if model:
model.set_from_matches(matches)
self.set_model(model)
adj = self.tree_view_scroll.get_vadjustment()
if adj:
adj.set_lower(self.vadj)
adj.set_value(self.vadj)
def reset_default_sort_mode(self):
""" force the appview to reset to the default sort method without
doing a refresh or sending any signals
"""
self._force_default_sort_method = True
def configure_sort_method(self, is_search=False):
""" configures the sort method UI appropriately based on current
conditions, including whether a search is in progress.
Note that this will not change the users current sort method,
if that is the intention, call reset_default_sort_mode()
"""
# figure out what combobox we need
if is_search:
self._use_combobox_with_sort_by_search_ranking()
else:
self._use_combobox_without_sort_by_search_ranking()
# and what sorting
if self._force_default_sort_method:
# always reset this, its the job of the user of the appview
# to call reset_default_sort_mode() to reset this
self._force_default_sort_method = False
# and now set the default sort depending on if its a view or not
if is_search:
self.set_sort_method_with_no_signal(
self._SORT_BY_SEARCH_RANKING)
else:
self.set_sort_method_with_no_signal(
self._SORT_BY_TOP_RATED)
def clear_model(self):
return self.tree_view.clear_model()
def get_sort_mode(self):
active_index = self.sort_methods_combobox.get_active()
return self._SORT_METHOD_INDEX[active_index]
def get_app_icon_details(self):
""" helper for unity dbus support to provide details about the
application icon as it is displayed on-screen
"""
icon_size = self._get_app_icon_size_on_screen()
(icon_x, icon_y) = self._get_app_icon_xy_position_on_screen()
return (icon_size, icon_x, icon_y)
def _get_app_icon_size_on_screen(self):
""" helper for unity dbus support to get the size of the maximum side
for the application icon as it is displayed on-screen
"""
icon_size = 32
if (self.tree_view.selected_row_renderer and
self.tree_view.selected_row_renderer.icon):
pb = self.tree_view.selected_row_renderer.icon
if pb.get_width() > pb.get_height():
icon_size = pb.get_width()
else:
icon_size = pb.get_height()
return icon_size
def _get_app_icon_xy_position_on_screen(self):
""" helper for unity dbus support to get the x,y position of
the application icon as it is displayed on-screen
"""
# find toplevel parent
parent = self
while parent.get_parent():
parent = parent.get_parent()
# get toplevel window position
(px, py) = parent.get_position()
# and return the coordinate values
if self.tree_view.selected_row_renderer:
return (px + self.tree_view.selected_row_renderer.icon_x_offset,
py + self.tree_view.selected_row_renderer.icon_y_offset)
else:
return (px, py)
# ----------------------------------------------- testcode
from softwarecenter.enums import NonAppVisibility
def get_query_from_search_entry(search_term):
import xapian
if not search_term:
return xapian.Query("")
parser = xapian.QueryParser()
user_query = parser.parse_query(search_term)
return user_query
def on_entry_changed(widget, data):
def _work():
new_text = widget.get_text()
(view, enquirer) = data
with ExecutionTime("total time"):
with ExecutionTime("enquire.set_query()"):
enquirer.set_query(get_query_from_search_entry(new_text),
limit=100 * 1000,
nonapps_visible=NonAppVisibility.ALWAYS_VISIBLE)
store = view.tree_view.get_model()
with ExecutionTime("store.clear()"):
store.clear()
with ExecutionTime("store.set_from_matches()"):
store.set_from_matches(enquirer.matches)
with ExecutionTime("model settle (size=%s)" % len(store)):
while Gtk.events_pending():
Gtk.main_iteration()
return
if widget.stamp:
GObject.source_remove(widget.stamp)
widget.stamp = GObject.timeout_add(250, _work)
def get_test_window():
import softwarecenter.log
softwarecenter.log.root.setLevel(level=logging.DEBUG)
softwarecenter.log.add_filters_from_string("performance")
fmt = logging.Formatter("%(name)s - %(message)s", None)
softwarecenter.log.handler.setFormatter(fmt)
from softwarecenter.testutils import (
get_test_db, get_test_pkg_info, get_test_gtk3_icon_cache)
from softwarecenter.ui.gtk3.models.appstore2 import AppListStore
db = get_test_db()
cache = get_test_pkg_info()
icons = get_test_gtk3_icon_cache()
# create a filter
from softwarecenter.db.appfilter import AppFilter
filter = AppFilter(db, cache)
filter.set_supported_only(False)
filter.set_installed_only(True)
# appview
from softwarecenter.db.enquire import AppEnquire
enquirer = AppEnquire(cache, db)
store = AppListStore(db, cache, icons)
from softwarecenter.ui.gtk3.views.appview import AppView
view = AppView(db, cache, icons, show_ratings=True)
view.set_model(store)
entry = Gtk.Entry()
entry.stamp = 0
entry.connect("changed", on_entry_changed, (view, enquirer))
box = Gtk.VBox()
box.pack_start(entry, False, True, 0)
box.pack_start(view, True, True, 0)
win = Gtk.Window()
win.set_data("appview", view)
win.set_data("entry", entry)
win.connect("destroy", lambda x: Gtk.main_quit())
win.add(box)
win.set_size_request(600, 400)
win.show_all()
return win
if __name__ == "__main__":
win = get_test_window()
win.get_data("entry").set_text("gtk3")
Gtk.main()