add copyrights information
[appstream:software-center.git] / appcenter / app.py
1 # Copyright (C) 2009 Canonical
2 #
3 # Authors:
4 #  Michael Vogt
5 #
6 # This program is free software; you can redistribute it and/or modify it under
7 # the terms of the GNU General Public License as published by the Free Software
8 # Foundation; either version 2 of the License, or (at your option) any later
9 # version.
10 #
11 # This program is distributed in the hope that it will be useful, but WITHOUT
12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13 # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
14 # details.
15 #
16 # You should have received a copy of the GNU General Public License along with
17 # this program; if not, write to the Free Software Foundation, Inc.,
18 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
20 import apt
21 import logging
22 import gtk
23 import os
24 import xapian
25
26 from SimpleGtkbuilderApp import SimpleGtkbuilderApp
27
28 from view.appview import AppView, AppStore, AppViewFilter
29 from view.catview import CategoriesView
30 from view.viewswitcher import ViewSwitcher, ViewSwitcherList
31 from view.appdetailsview import AppDetailsView
32 from view.pendingview import PendingView
33
34 from gettext import gettext as _
35
36 XAPIAN_BASE_PATH = "/var/cache/app-center"
37 APP_INSTALL_PATH = "/usr/share/app-install"
38 ICON_PATH = APP_INSTALL_PATH+"/icons/"
39
40 class AppCenter(SimpleGtkbuilderApp):
41     
42     (NOTEBOOK_PAGE_CATEGORIES,
43      NOTEBOOK_PAGE_APPLIST,
44      NOTEBOOK_PAGE_APP_DETAILS,
45      NOTEBOOK_PAGE_PENDING) = range(4)
46
47     DEFAULT_SEARCH_APPS_LIMIT = 200
48
49     def __init__(self, datadir):
50         SimpleGtkbuilderApp.__init__(self, datadir+"/ui/AppCenter.ui")
51
52         # xapian
53         xapian_base_path = XAPIAN_BASE_PATH
54         pathname = os.path.join(xapian_base_path, "xapian")
55         self.xapiandb = xapian.Database(pathname)
56         self.xapian_parser = xapian.QueryParser()
57         self.xapian_parser.set_database(self.xapiandb)
58         self.xapian_parser.add_boolean_prefix("pkg", "AP")
59         #self.xapian_parser.add_boolean_prefix("section", "AS")
60
61         # additional icons come from app-install-data
62         self.icons = gtk.icon_theme_get_default()
63         self.icons.append_search_path(ICON_PATH)
64
65         # data 
66         # FIXME: progress or thread
67         self.cache = apt.Cache()
68
69         # view switcher
70         self.view_switcher = ViewSwitcher()
71         self.scrolledwindow_viewswitcher.add(self.view_switcher)
72         self.view_switcher.show()
73         self.view_switcher.connect("row-activated", 
74                                    self.on_view_switcher_activated)
75
76         # categories
77         self.cat_view = CategoriesView(APP_INSTALL_PATH, self.xapiandb, self.icons)
78         self.scrolledwindow_categories.add(self.cat_view)
79         self.cat_view.show()
80         self.cat_view.connect("item-activated", self.on_category_activated)
81         
82         # apps
83         empty_store = gtk.ListStore(gtk.gdk.Pixbuf, str)
84         self.app_view = AppView(empty_store)
85         self.app_view.connect("row-activated", self.on_app_activated)
86         self.scrolledwindow_applist.add(self.app_view)
87         self.app_view.show()
88
89         # pending
90         self.pending_view = PendingView(self.icons)
91         self.scrolledwindow_transactions.add(self.pending_view)
92
93         # details
94         self.app_details_view = AppDetailsView(self.xapiandb, self.icons, self.cache)
95         self.scrolledwindow_app_details.add(self.app_details_view)
96         self.app_details_view.show()
97
98         # search
99         self.entry_search.connect("changed", self.on_entry_search_changed)
100
101         # state
102         self.apps_filter = AppViewFilter(self.cache)
103         self.apps_category_query = None
104         self.apps_search_query = None
105         self.apps_sorted = True
106         self.apps_limit = 0
107
108         # default focus
109         self.entry_search.grab_focus()
110
111     # xapian query
112     def get_query_from_search_entry(self, search_term):
113         """ get xapian.Query from a search term string """
114         query = self.xapian_parser.parse_query(search_term, 
115                                                xapian.QueryParser.FLAG_PARTIAL)
116         # FIXME: expand to add "AA" and "AP" before each search term?
117         return query
118
119     # navigation buttons
120     def add_navigation_button(self, name, callback, type):
121         self.remove_navigation_button(type)
122         button = gtk.Button()
123         button.set_label(name)
124         button.show()
125         button.connect("clicked", callback)
126         button.set_data("navigation-type", type)
127         self.hbox_navigation_buttons.pack_start(button, expand=False)
128
129     def remove_navigation_buttons(self):
130         for w in self.hbox_navigation_buttons:
131             self.hbox_navigation_buttons.remove(w)
132
133     def remove_navigation_button(self, type):
134         for w in self.hbox_navigation_buttons:
135             if w.get_data("navigation-type") == type:
136                 self.hbox_navigation_buttons.remove(w)
137
138     # callbacks
139     def on_menuitem_close_activate(self, widget):
140         gtk.main_quit()
141
142     def on_menuitem_about_activate(self, widget):
143         print "about"
144         # FIXME: add once we have a name
145
146     def on_menuitem_view_all_activate(self, widget):
147         print "on_menuitem_view_all_activate", widget
148         self.apps_filter.set_supported_only(False)
149         self.refresh_apps()
150
151     def on_menuitem_view_canonical_activate(self, widget):
152         print "on_menuitem_view_canonical_activate", widget
153         self.apps_filter.set_supported_only(True)
154         self.refresh_apps()
155
156     def on_button_home_clicked(self, widget):
157         logging.debug("on_button_home_clicked")
158         self.apps_category_query = None
159         self.remove_navigation_buttons()
160         self.notebook_view.set_current_page(self.NOTEBOOK_PAGE_CATEGORIES)
161
162     def on_entry_search_changed(self, widget):
163         self.remove_navigation_button("search")
164         new_text = widget.get_text()
165         logging.debug("on_entry_changed: %s" % new_text)
166         if not new_text:
167             self.apps_limit = 0
168             self.apps_sorted = True
169             self.apps_search_query = None
170         else:
171             self.apps_search_query = self.get_query_from_search_entry(new_text)
172             self.apps_sorted = False
173             self.apps_limit = self.DEFAULT_SEARCH_APPS_LIMIT
174             self.add_navigation_button(_("Search"), 
175                                        self.on_navigation_button_category, 
176                                        "search")
177         self.refresh_apps()
178         self.notebook_view.set_current_page(self.NOTEBOOK_PAGE_APPLIST)
179
180     def on_button_search_entry_clear_clicked(self, widget):
181         self.entry_search.set_text("")
182
183     def on_view_switcher_activated(self, view_switcher, row, column):
184         logging.debug("view_switcher_activated")
185         model = view_switcher.get_model()
186         action = model[row][ViewSwitcherList.COL_ACTION]
187         if action == ViewSwitcherList.ACTION_ITEM_AVAILABLE:
188             logging.debug("show available")
189             if self.notebook_view.get_current_page() == self.NOTEBOOK_PAGE_PENDING:
190                 self.notebook_view.set_current_page(self.NOTEBOOK_PAGE_CATEGORIES)
191             self.apps_filter.set_installed_only(False)
192             self.refresh_apps()
193         elif action == ViewSwitcherList.ACTION_ITEM_INSTALLED:
194             logging.debug("show installed")
195             if self.notebook_view.get_current_page() == self.NOTEBOOK_PAGE_PENDING:
196                 self.notebook_view.set_current_page(self.NOTEBOOK_PAGE_CATEGORIES)
197             self.apps_filter.set_installed_only(True)
198             self.refresh_apps()
199         elif action == ViewSwitcherList.ACTION_ITEM_PENDING:
200             logging.debug("show pending")
201             self.notebook_view.set_current_page(self.NOTEBOOK_PAGE_PENDING)
202         else:
203             assert False, "Not reached"
204
205     def on_navigation_button_category(self, widget):
206         self.notebook_view.set_current_page(self.NOTEBOOK_PAGE_APPLIST)
207
208     def on_navigation_button_app_details(self, widget):
209         self.notebook_view.set_current_page(self.NOTEBOOK_PAGE_APP_DETAILS)
210
211     def on_app_activated(self, app_view, path, column):
212         (name, icon) = app_view.get_model()[path]
213         # show new app
214         self.app_details_view.show_app(name)
215         self.notebook_view.set_current_page(self.NOTEBOOK_PAGE_APP_DETAILS)
216         # add navigation button
217         self.add_navigation_button(name, self.on_navigation_button_app_details, "app")
218
219     def on_category_activated(self, cat_view, path):
220         (name, pixbuf, query) = cat_view.get_model()[path]
221         self.apps_category_query = query
222         # show new category
223         self.refresh_apps()
224         self.notebook_view.set_current_page(self.NOTEBOOK_PAGE_APPLIST)
225         # update navigation bar
226         self.add_navigation_button(name, self.on_navigation_button_category, "category")
227
228     # gui helper
229     def refresh_apps(self):
230         # build query
231         if self.apps_category_query and self.apps_search_query:
232             query = xapian.Query(xapian.Query.OP_AND, 
233                                  self.apps_category_query,
234                                  self.apps_search_query)
235         elif self.apps_category_query:
236             query = self.apps_category_query
237         elif self.apps_search_query:
238             query = self.apps_search_query
239         else:
240             query = None
241
242         # create new model and attach it
243         new_model = AppStore(self.xapiandb, 
244                              self.icons, 
245                              query, 
246                              limit=self.apps_limit,
247                              sort=self.apps_sorted,
248                              filter=self.apps_filter)
249         self.app_view.set_model(new_model)
250         id = self.statusbar_main.get_context_id("items")
251         self.statusbar_main.push(id, _("%s items available") % len(new_model))
252
253
254     def run(self):
255         self.window_main.show_all()
256         SimpleGtkbuilderApp.run(self)