Merge lp:~mmcg069/software-center/pathbar-atk into lp:software-center

Proposed by Matthew McGowan
Status: Merged
Merged at revision: not available
Proposed branch: lp:~mmcg069/software-center/pathbar-atk
Merge into: lp:software-center
Diff against target: 4408 lines (+1961/-1978)
12 files modified
softwarecenter/app.py (+3/-2)
softwarecenter/view/appview.py (+8/-3)
softwarecenter/view/availablepane.py (+123/-78)
softwarecenter/view/channelpane.py (+6/-1)
softwarecenter/view/installedpane.py (+3/-1)
softwarecenter/view/navhistory.py (+147/-88)
softwarecenter/view/softwarepane.py (+1/-1)
softwarecenter/view/widgets/backforward.py (+140/-132)
softwarecenter/view/widgets/pathbar2.py (+0/-1605)
softwarecenter/view/widgets/pathbar_common.py (+827/-0)
softwarecenter/view/widgets/pathbar_gtk_atk.py (+703/-0)
softwarecenter/view/widgets/rgb.py (+0/-67)
To merge this branch: bzr merge lp:~mmcg069/software-center/pathbar-atk
Reviewer Review Type Date Requested Status
software-store-developers Pending
Review via email: mp+22778@code.launchpad.net

Description of the change

most notable:
* Modify PathBar to now use gtk.EventBox's for PathPart's
* Enable atk accessibility support
* Pathbar now provides for keyboard navigation, complete with focus box drawing
* Improve theme coverage
* All features of old PathBar retained in shift to new PathBar design

other:
* Tweak navhistory behaviour to better work with new Pathbar
* Other minor tweaks

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'softwarecenter/app.py'
--- softwarecenter/app.py 2010-04-01 19:48:40 +0000
+++ softwarecenter/app.py 2010-04-04 06:00:32 +0000
@@ -214,7 +214,8 @@
214214
215 # default focus215 # default focus
216 self.available_pane.searchentry.grab_focus()216 self.available_pane.searchentry.grab_focus()
217 217 self.window_main.set_size_request(600, 400)
218
218 # restore state219 # restore state
219 self.config = get_config()220 self.config = get_config()
220 self.restore_state()221 self.restore_state()
@@ -561,7 +562,7 @@
561 def restore_state(self):562 def restore_state(self):
562 if self.config.has_option("general", "size"):563 if self.config.has_option("general", "size"):
563 (x, y) = self.config.get("general", "size").split(",")564 (x, y) = self.config.get("general", "size").split(",")
564 self.window_main.resize(int(x), int(y))565 self.window_main.set_default_size(int(x), int(y))
565 if (self.config.has_option("general", "maximized") and566 if (self.config.has_option("general", "maximized") and
566 self.config.getboolean("general", "maximized")):567 self.config.getboolean("general", "maximized")):
567 self.window_main.maximize()568 self.window_main.maximize()
568569
=== modified file 'softwarecenter/view/appview.py'
--- softwarecenter/view/appview.py 2010-04-01 19:48:40 +0000
+++ softwarecenter/view/appview.py 2010-04-04 06:00:32 +0000
@@ -96,6 +96,7 @@
96 data further. A python function that gets a pkgname96 data further. A python function that gets a pkgname
97 """97 """
98 gtk.GenericTreeModel.__init__(self)98 gtk.GenericTreeModel.__init__(self)
99 self.search_query = search_query
99 self.cache = cache100 self.cache = cache
100 self.db = db101 self.db = db
101 self.icons = icons102 self.icons = icons
@@ -258,7 +259,7 @@
258 summary = app.pkgname259 summary = app.pkgname
259 if self.db.is_appname_duplicated(appname):260 if self.db.is_appname_duplicated(appname):
260 appname = "%s (%s)" % (appname, app.pkgname)261 appname = "%s (%s)" % (appname, app.pkgname)
261 s = "%s\n<small>%s</small>" % (262 s = "<b>%s</b>\n<small>%s</small>" % (
262 gobject.markup_escape_text(appname),263 gobject.markup_escape_text(appname),
263 gobject.markup_escape_text(summary))264 gobject.markup_escape_text(summary))
264 return s265 return s
@@ -900,7 +901,9 @@
900 """901 """
901 (path, column) = self.get_cursor()902 (path, column) = self.get_cursor()
902 model = self.get_model()903 model = self.get_model()
903 action_in_progress = (model[path][AppStore.COL_ACTION_IN_PROGRESS] != -1)904 action_in_progress = False
905 if path:
906 action_in_progress = (model[path][AppStore.COL_ACTION_IN_PROGRESS] != -1)
904 return action_in_progress907 return action_in_progress
905908
906 def _on_realize(self, widget, tr):909 def _on_realize(self, widget, tr):
@@ -948,7 +951,9 @@
948 def _on_cursor_changed(self, view):951 def _on_cursor_changed(self, view):
949 # trigger callback, if we do it here get_selection() returns952 # trigger callback, if we do it here get_selection() returns
950 # the previous selected row for some reason953 # the previous selected row for some reason
951 gobject.timeout_add(10, self._app_selected_timeout_cb, view)954
955 # mvo: can we make this timeout 1? The AppView is much nicer this way...
956 gobject.timeout_add(1, self._app_selected_timeout_cb, view)
952957
953 def _app_selected_timeout_cb(self, view):958 def _app_selected_timeout_cb(self, view):
954 selection = view.get_selection()959 selection = view.get_selection()
955960
=== modified file 'softwarecenter/view/availablepane.py'
--- softwarecenter/view/availablepane.py 2010-03-05 15:53:27 +0000
+++ softwarecenter/view/availablepane.py 2010-04-04 06:00:32 +0000
@@ -48,7 +48,7 @@
48 (PAGE_CATEGORY,48 (PAGE_CATEGORY,
49 PAGE_APPLIST,49 PAGE_APPLIST,
50 PAGE_APP_DETAILS) = range(3)50 PAGE_APP_DETAILS) = range(3)
51 51
52 # define ID values for the various buttons found in the navigation bar52 # define ID values for the various buttons found in the navigation bar
53 NAV_BUTTON_ID_CATEGORY = "category"53 NAV_BUTTON_ID_CATEGORY = "category"
54 NAV_BUTTON_ID_LIST = "list"54 NAV_BUTTON_ID_LIST = "list"
@@ -73,14 +73,14 @@
73 self.connect("app-list-changed", self._on_app_list_changed)73 self.connect("app-list-changed", self._on_app_list_changed)
74 self.current_app_by_category = {}74 self.current_app_by_category = {}
75 self.current_app_by_subcategory = {}75 self.current_app_by_subcategory = {}
76 # track navigation history
77 self.nav_history = NavigationHistory(self)
76 # UI78 # UI
77 self._build_ui()79 self._build_ui()
78 # track navigation history
79 self.nav_history = NavigationHistory(self)
8080
81 def _build_ui(self):81 def _build_ui(self):
82 # categories, appview and details into the notebook in the bottom82 # categories, appview and details into the notebook in the bottom
83 self.cat_view = CategoriesView(self.datadir, APP_INSTALL_PATH, 83 self.cat_view = CategoriesView(self.datadir, APP_INSTALL_PATH,
84 self.db,84 self.db,
85 self.icons)85 self.icons)
86 scroll_categories = gtk.ScrolledWindow()86 scroll_categories = gtk.ScrolledWindow()
@@ -88,8 +88,8 @@
88 scroll_categories.add(self.cat_view)88 scroll_categories.add(self.cat_view)
89 self.notebook.append_page(scroll_categories, gtk.Label("categories"))89 self.notebook.append_page(scroll_categories, gtk.Label("categories"))
90 # sub-categories view90 # sub-categories view
91 self.subcategories_view = CategoriesView(self.datadir, 91 self.subcategories_view = CategoriesView(self.datadir,
92 APP_INSTALL_PATH, 92 APP_INSTALL_PATH,
93 self.db,93 self.db,
94 self.icons,94 self.icons,
95 self.cat_view.categories[0])95 self.cat_view.categories[0])
@@ -108,7 +108,7 @@
108 self.top_hbox.pack_start(self.back_forward, expand=False, padding=self.PADDING)108 self.top_hbox.pack_start(self.back_forward, expand=False, padding=self.PADDING)
109 # nav buttons first in the panel109 # nav buttons first in the panel
110 self.top_hbox.reorder_child(self.back_forward, 0)110 self.top_hbox.reorder_child(self.back_forward, 0)
111 # now a vbox for subcategories and applist 111 # now a vbox for subcategories and applist
112 apps_vbox = gtk.VPaned()112 apps_vbox = gtk.VPaned()
113 apps_vbox.pack1(self.scroll_subcategories, resize=True)113 apps_vbox.pack1(self.scroll_subcategories, resize=True)
114 apps_vbox.pack2(self.scroll_app_list)114 apps_vbox.pack2(self.scroll_app_list)
@@ -120,9 +120,10 @@
120 # set status text120 # set status text
121 self._update_status_text(len(self.db))121 self._update_status_text(len(self.db))
122 # home button122 # home button
123 self.navigation_bar.add_with_id(_("Get Software"), 123 self.navigation_bar.add_with_id(_("Get Software"),
124 self.on_navigation_category,124 self.on_navigation_category,
125 self.NAV_BUTTON_ID_CATEGORY,125 self.NAV_BUTTON_ID_CATEGORY,
126 do_callback=True,
126 animate=False)127 animate=False)
127128
128 def _get_query(self):129 def _get_query(self):
@@ -137,7 +138,7 @@
137 elif self.apps_category:138 elif self.apps_category:
138 cat_query = self.apps_category.query139 cat_query = self.apps_category.query
139 # mix category with the search terms and return query140 # mix category with the search terms and return query
140 return self.db.get_query_list_from_search_entry(self.apps_search_term, 141 return self.db.get_query_list_from_search_entry(self.apps_search_term,
141 cat_query)142 cat_query)
142143
143 def _in_no_display_category(self):144 def _in_no_display_category(self):
@@ -150,7 +151,7 @@
150 def _show_hide_subcategories(self):151 def _show_hide_subcategories(self):
151 # check if have subcategories and are not in a subcategory152 # check if have subcategories and are not in a subcategory
152 # view - if so, show it153 # view - if so, show it
153 if (self.apps_category and 154 if (self.apps_category and
154 self.apps_category.subcategories and155 self.apps_category.subcategories and
155 not (self.apps_search_term or self.apps_subcategory)):156 not (self.apps_search_term or self.apps_subcategory)):
156 self.scroll_subcategories.show()157 self.scroll_subcategories.show()
@@ -163,16 +164,16 @@
163 # not hide it164 # not hide it
164 model = self.app_view.get_model()165 model = self.app_view.get_model()
165 if (model and166 if (model and
166 len(model) == 0 and 167 len(model) == 0 and
167 self.apps_category and168 self.apps_category and
168 self.apps_category.subcategories and 169 self.apps_category.subcategories and
169 not self.apps_subcategory):170 not self.apps_subcategory):
170 self.scroll_app_list.hide()171 self.scroll_app_list.hide()
171 else:172 else:
172 self.scroll_app_list.show()173 self.scroll_app_list.show()
173174
174 def refresh_apps(self):175 def refresh_apps(self):
175 """refresh the applist after search changes and update the 176 """refresh the applist after search changes and update the
176 navigation bar177 navigation bar
177 """178 """
178 logging.debug("refresh_apps")179 logging.debug("refresh_apps")
@@ -185,18 +186,28 @@
185 def _refresh_apps_with_apt_cache(self):186 def _refresh_apps_with_apt_cache(self):
186 # build query187 # build query
187 query = self._get_query()188 query = self._get_query()
188 logging.debug("availablepane query: %s" % query)189
189 # *ugh* deactivate the old model because otherwise it keeps
190 # getting progress_changed events and eats CPU time until its
191 # garbage collected
192 old_model = self.app_view.get_model()190 old_model = self.app_view.get_model()
193 if old_model is not None:191 if old_model is not None:
194 old_model.active = False192 # check if new AppStore query == old query. if yes, do nothing
193 if isinstance(old_model, AppStore) and \
194 str(old_model.search_query) == str(query):
195 # check if we show subcategoriy
196 self._show_hide_applist()
197 self.emit("app-list-changed", len(old_model))
198 return
199 else:
200 # *ugh* deactivate the old model because otherwise it keeps
201 # getting progress_changed events and eats CPU time until its
202 # garbage collected
203 old_model.active = False
204
205 logging.debug("availablepane query: %s" % query)
195 # create new model and attach it206 # create new model and attach it
196 new_model = AppStore(self.cache,207 new_model = AppStore(self.cache,
197 self.db, 208 self.db,
198 self.icons, 209 self.icons,
199 query, 210 query,
200 limit=self.apps_limit,211 limit=self.apps_limit,
201 sort=self.apps_sorted,212 sort=self.apps_sorted,
202 filter=self.apps_filter)213 filter=self.apps_filter)
@@ -210,15 +221,14 @@
210 """Update the navigation button"""221 """Update the navigation button"""
211 if self.apps_category and not self.apps_search_term:222 if self.apps_category and not self.apps_search_term:
212 cat = self.apps_category.name223 cat = self.apps_category.name
213 self.navigation_bar.add_with_id(cat, 224 self.navigation_bar.add_with_id(cat,
214 self.on_navigation_list,225 self.on_navigation_list,
215 self.NAV_BUTTON_ID_LIST,226 self.NAV_BUTTON_ID_LIST, True)
216 None)227
217 elif self.apps_search_term:228 elif self.apps_search_term:
218 self.navigation_bar.add_with_id(_("Search Results"),229 self.navigation_bar.add_with_id(_("Search Results"),
219 self.on_navigation_search, 230 self.on_navigation_search,
220 self.NAV_BUTTON_ID_SEARCH,231 self.NAV_BUTTON_ID_SEARCH, True)
221 None)
222232
223 # status text woo233 # status text woo
224 def get_status_text(self):234 def get_status_text(self):
@@ -228,7 +238,7 @@
228 self._in_no_display_category()):238 self._in_no_display_category()):
229 return ""239 return ""
230 return self._status_text240 return self._status_text
231 241
232 def get_current_app(self):242 def get_current_app(self):
233 """return the current active application object"""243 """return the current active application object"""
234 if self.is_category_view_showing():244 if self.is_category_view_showing():
@@ -238,13 +248,13 @@
238 return self.current_app_by_subcategory.get(self.apps_subcategory)248 return self.current_app_by_subcategory.get(self.apps_subcategory)
239 else:249 else:
240 return self.current_app_by_category.get(self.apps_category)250 return self.current_app_by_category.get(self.apps_category)
241 251
242 def _on_app_list_changed(self, pane, length):252 def _on_app_list_changed(self, pane, length):
243 """internal helper that keeps the status text up-to-date by253 """internal helper that keeps the status text up-to-date by
244 keeping track of the app-list-changed signals254 keeping track of the app-list-changed signals
245 """255 """
246 self._update_status_text(length)256 self._update_status_text(length)
247 257
248 def _update_status_text(self, length):258 def _update_status_text(self, length):
249 """259 """
250 update the text in the status bar260 update the text in the status bar
@@ -280,11 +290,17 @@
280 self.apps_search_term = ""290 self.apps_search_term = ""
281 self.navigation_bar.remove_id(self.NAV_BUTTON_ID_SEARCH)291 self.navigation_bar.remove_id(self.NAV_BUTTON_ID_SEARCH)
282292
293 def _check_nav_history(self, display_cb):
294 if self.navigation_bar.get_last().label != self.nav_history.get_last_label():
295 nav_item = NavigationItem(self, display_cb)
296 self.nav_history.navigate_no_cursor_step(nav_item)
297 return
298
283 # callbacks299 # callbacks
284 def on_cache_ready(self, cache):300 def on_cache_ready(self, cache):
285 """ refresh the application list when the cache is re-opened """301 """ refresh the application list when the cache is re-opened """
286 # just re-draw in the available pane, nothing but the 302 # just re-draw in the available pane, nothing but the
287 # "is-installed" overlay icon will change when something 303 # "is-installed" overlay icon will change when something
288 # is installed or removed in the available pane304 # is installed or removed in the available pane
289 self.app_view.queue_draw()305 self.app_view.queue_draw()
290306
@@ -322,67 +338,89 @@
322 self.refresh_apps()338 self.refresh_apps()
323 self._show_category_overview()339 self._show_category_overview()
324340
341 def display_category(self):
342 self._clear_search()
343 self._show_category_overview()
344 return
345
346 def display_search(self):
347 self.navigation_bar.remove_id(self.NAV_BUTTON_ID_DETAILS)
348 self.notebook.set_current_page(self.PAGE_APPLIST)
349 self.emit("app-list-changed", len(self.app_view.get_model()))
350 self.searchentry.show()
351 return
352
353 def display_list(self):
354 self.navigation_bar.remove_id(self.NAV_BUTTON_ID_SUBCAT)
355 self.navigation_bar.remove_id(self.NAV_BUTTON_ID_DETAILS)
356
357 if self.apps_subcategory:
358 self.apps_subcategory = None
359 self.set_category(self.apps_category)
360 if self.apps_search_term:
361 self._clear_search()
362 self.refresh_apps()
363
364 self.notebook.set_current_page(self.PAGE_APPLIST)
365 model = self.app_view.get_model()
366 self.emit("app-list-changed", len(model))
367 self.searchentry.show()
368 return
369
370 def display_list_subcat(self):
371 if self.apps_search_term:
372 self._clear_search()
373 self.refresh_apps()
374 self.set_category(self.apps_subcategory)
375 self.navigation_bar.remove_id(self.NAV_BUTTON_ID_DETAILS)
376 self.notebook.set_current_page(self.PAGE_APPLIST)
377 self.emit("app-list-changed", len(self.app_view.get_model()))
378 self.searchentry.show()
379 return
380
381 def display_details(self):
382 self.notebook.set_current_page(self.PAGE_APP_DETAILS)
383 self.searchentry.hide()
384 return
385
325 def on_navigation_category(self, pathbar, part):386 def on_navigation_category(self, pathbar, part):
326 """callback when the navigation button with id 'category' is clicked"""387 """callback when the navigation button with id 'category' is clicked"""
327 if pathbar and not pathbar.get_active():
328 return
329 # clear the search388 # clear the search
330 self._clear_search()389 self.display_category()
331 self._show_category_overview()390 nav_item = NavigationItem(self, self.display_category)
332 self.nav_history.navigate(CategoryViewNavigationItem(self))391 self.nav_history.navigate(nav_item)
333392
334 def on_navigation_search(self, pathbar, part):393 def on_navigation_search(self, pathbar, part):
335 """ callback when the navigation button with id 'search' is clicked"""394 """ callback when the navigation button with id 'search' is clicked"""
336 self.navigation_bar.remove_id(self.NAV_BUTTON_ID_DETAILS)395 self.display_search()
337 self.notebook.set_current_page(self.PAGE_APPLIST)396 nav_item = NavigationItem(self, self.display_search)
338 self.emit("app-list-changed", len(self.app_view.get_model()))397 self.nav_history.navigate(nav_item)
339 self.nav_history.navigate(SearchNavigationItem(self))
340 self.searchentry.show()
341398
342 def on_navigation_list(self, pathbar, part):399 def on_navigation_list(self, pathbar, part):
343 """callback when the navigation button with id 'list' is clicked"""400 """callback when the navigation button with id 'list' is clicked"""
344 if pathbar and not pathbar.get_active():401 self.display_list()
345 return402 nav_item = NavigationItem(self, self.display_list)
346 self.navigation_bar.remove_id(self.NAV_BUTTON_ID_SUBCAT)403 self.nav_history.navigate(nav_item)
347 self.navigation_bar.remove_id(self.NAV_BUTTON_ID_DETAILS)
348 if self.apps_subcategory:
349 self.apps_subcategory = None
350 self.set_category(self.apps_category)
351 if self.apps_search_term:
352 self._clear_search()
353 self.refresh_apps()
354 self.notebook.set_current_page(self.PAGE_APPLIST)
355 model = self.app_view.get_model()
356 self.emit("app-list-changed", len(model))
357 self.searchentry.show()
358 self.nav_history.navigate(AppListNavigationItem(self))
359404
360 def on_navigation_list_subcategory(self, pathbar, part):405 def on_navigation_list_subcategory(self, pathbar, part):
361 if pathbar and not pathbar.get_active():406 self.display_list_subcat()
362 return407 nav_item = NavigationItem(self, self.display_list_subcat)
363 if self.apps_search_term:408 self.nav_history.navigate(nav_item)
364 self._clear_search()
365 self.refresh_apps()
366 self.set_category(self.apps_subcategory)
367 self.navigation_bar.remove_id(self.NAV_BUTTON_ID_DETAILS)
368 self.notebook.set_current_page(self.PAGE_APPLIST)
369 self.emit("app-list-changed", len(self.app_view.get_model()))
370 self.searchentry.show()
371 self.nav_history.navigate(AppListSubcategoryNavigationItem(self))
372409
373 def on_navigation_details(self, pathbar, part):410 def on_navigation_details(self, pathbar, part):
374 """callback when the navigation button with id 'details' is clicked"""411 """callback when the navigation button with id 'details' is clicked"""
375 if pathbar and not pathbar.get_active():412 self.display_details()
376 return413 nav_item = NavigationItem(self, self.display_details)
377 self.notebook.set_current_page(self.PAGE_APP_DETAILS)414 self.nav_history.navigate(nav_item)
378 self.searchentry.hide()
379 self.nav_history.navigate(AppDetailsNavigationItem(self))
380415
381 def on_subcategory_activated(self, cat_view, category):416 def on_subcategory_activated(self, cat_view, category):
382 #print cat_view, name, query417 #print cat_view, name, query
383 logging.debug("on_subcategory_activated: %s %s" % (418 logging.debug("on_subcategory_activated: %s %s" % (
384 category.name, category))419 category.name, category))
385 self.apps_subcategory = category420 self.apps_subcategory = category
421
422 #self._check_nav_history(self.display_list)
423
386 self.navigation_bar.add_with_id(424 self.navigation_bar.add_with_id(
387 category.name, self.on_navigation_list_subcategory, self.NAV_BUTTON_ID_SUBCAT)425 category.name, self.on_navigation_list_subcategory, self.NAV_BUTTON_ID_SUBCAT)
388426
@@ -392,21 +430,24 @@
392 category.name, category))430 category.name, category))
393 self.apps_category = category431 self.apps_category = category
394 self.set_category(category)432 self.set_category(category)
395 433
396 def on_application_selected(self, appview, app):434 def on_application_selected(self, appview, app):
397 """callback when an app is selected"""435 """callback when an app is selected"""
398 logging.debug("on_application_selected: '%s'" % app)436 logging.debug("on_application_selected: '%s'" % app)
437
399 if self.apps_subcategory:438 if self.apps_subcategory:
439 #self._check_nav_history(self.display_list_subcat)
400 self.current_app_by_subcategory[self.apps_subcategory] = app440 self.current_app_by_subcategory[self.apps_subcategory] = app
401 else:441 else:
442 #self._check_nav_history(self.display_list)
402 self.current_app_by_category[self.apps_category] = app443 self.current_app_by_category[self.apps_category] = app
403 444
404 def on_nav_back_clicked(self, widget, event):445 def on_nav_back_clicked(self, widget, event):
405 self.nav_history.nav_back()446 self.nav_history.nav_back()
406447
407 def on_nav_forward_clicked(self, widget, event):448 def on_nav_forward_clicked(self, widget, event):
408 self.nav_history.nav_forward()449 self.nav_history.nav_forward()
409 450
410 def is_category_view_showing(self):451 def is_category_view_showing(self):
411 # check if we are in the category page or if we display a452 # check if we are in the category page or if we display a
412 # sub-category page that has no visible applications453 # sub-category page that has no visible applications
@@ -414,9 +455,13 @@
414 not self.scroll_app_list.props.visible)455 not self.scroll_app_list.props.visible)
415456
416 def set_category(self, category):457 def set_category(self, category):
458 def _cb():
459 self.refresh_apps()
460 self.notebook.set_current_page(self.PAGE_APPLIST)
461 return False
462
417 self.update_navigation_button()463 self.update_navigation_button()
418 self.refresh_apps()464 gobject.idle_add(_cb)
419 self.notebook.set_current_page(self.PAGE_APPLIST)
420465
421if __name__ == "__main__":466if __name__ == "__main__":
422 #logging.basicConfig(level=logging.DEBUG)467 #logging.basicConfig(level=logging.DEBUG)
423468
=== modified file 'softwarecenter/view/channelpane.py'
--- softwarecenter/view/channelpane.py 2010-03-23 16:50:23 +0000
+++ softwarecenter/view/channelpane.py 2010-04-04 06:00:32 +0000
@@ -23,6 +23,7 @@
23import os23import os
24import sys24import sys
25import xapian25import xapian
26import gobject
2627
27from gettext import gettext as _28from gettext import gettext as _
2829
@@ -99,6 +100,10 @@
99 old_model = self.app_view.get_model()100 old_model = self.app_view.get_model()
100 if old_model is not None:101 if old_model is not None:
101 old_model.active = False102 old_model.active = False
103 gobject.idle_add(self._make_new_model, query)
104 return False
105
106 def _make_new_model(self, query):
102 # get a new store and attach it to the view107 # get a new store and attach it to the view
103 new_model = AppStore(self.cache,108 new_model = AppStore(self.cache,
104 self.db, 109 self.db,
@@ -110,7 +115,7 @@
110 self.app_view.set_model(new_model)115 self.app_view.set_model(new_model)
111 self.emit("app-list-changed", len(new_model))116 self.emit("app-list-changed", len(new_model))
112 return False117 return False
113 118
114 def set_channel(self, channel):119 def set_channel(self, channel):
115 """120 """
116 set the current software channel object for display in the channel pane121 set the current software channel object for display in the channel pane
117122
=== modified file 'softwarecenter/view/installedpane.py'
--- softwarecenter/view/installedpane.py 2010-03-03 15:39:40 +0000
+++ softwarecenter/view/installedpane.py 2010-04-04 06:00:32 +0000
@@ -50,6 +50,7 @@
50 # UI50 # UI
51 self._build_ui()51 self._build_ui()
52 def _build_ui(self):52 def _build_ui(self):
53 self.navigation_bar.set_size_request(26, -1)
53 self.notebook.append_page(self.scroll_app_list, gtk.Label("installed"))54 self.notebook.append_page(self.scroll_app_list, gtk.Label("installed"))
54 # details55 # details
55 self.notebook.append_page(self.scroll_details, gtk.Label("details"))56 self.notebook.append_page(self.scroll_details, gtk.Label("details"))
@@ -82,7 +83,8 @@
82 query = None83 query = None
83 self.navigation_bar.add_with_id(_("Installed Software"), 84 self.navigation_bar.add_with_id(_("Installed Software"),
84 self.on_navigation_list,85 self.on_navigation_list,
85 "list")86 "list",
87 animate=False)
86 # *ugh* deactivate the old model because otherwise it keeps88 # *ugh* deactivate the old model because otherwise it keeps
87 # getting progress_changed events and eats CPU time until it's89 # getting progress_changed events and eats CPU time until it's
88 # garbage collected90 # garbage collected
8991
=== modified file 'softwarecenter/view/navhistory.py'
--- softwarecenter/view/navhistory.py 2010-03-19 21:27:31 +0000
+++ softwarecenter/view/navhistory.py 2010-04-04 06:00:32 +0000
@@ -15,7 +15,8 @@
15# You should have received a copy of the GNU General Public License along with15# You should have received a copy of the GNU General Public License along with
16# this program; if not, write to the Free Software Foundation, Inc.,16# this program; if not, write to the Free Software Foundation, Inc.,
17# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA17# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18import copy18
19import gobject
19import logging20import logging
2021
21from softwarecenter.utils import unescape22from softwarecenter.utils import unescape
@@ -28,94 +29,117 @@
28 class to manage navigation history in the "Get Software" section (the29 class to manage navigation history in the "Get Software" section (the
29 available pane).30 available pane).
30 """31 """
31 32
33 MAX_NAV_ITEMS = 10 # limit number of NavItems allowed in the NavStack
34
35
32 def __init__(self, available_pane):36 def __init__(self, available_pane):
33 self.available_pane = available_pane37 self.available_pane = available_pane
34 # always start at main category view
35 self._current_nav_item = CategoryViewNavigationItem(available_pane)
36 # use stacks to track navigation history38 # use stacks to track navigation history
37 self._nav_back_stack = []39 self._nav_stack = NavigationStack(self.MAX_NAV_ITEMS)
38 self._nav_forward_stack = []40
39 41 def navigate(self, nav_item):
40 def navigate(self, dest_nav_item):
41 """42 """
42 append a new NavigationItem to the history stack43 append a new NavigationItem to the history stack
43 """44 """
44 if in_replay_history_mode:45 if in_replay_history_mode:
45 return46 return
46 logging.debug("submit navitem for history: %s" % dest_nav_item)47
47 # TODO: Detect multiple clicks on the same nav button and filter48 nav_item.parent = self
48 # them out - we don't want them in the history49 self._nav_stack.append(nav_item)
49 dest_nav_item.parent = self50
50 self._nav_back_stack.append(self._current_nav_item)51 if self._nav_stack.cursor > 0:
51 self._current_nav_item = dest_nav_item52 self.available_pane.back_forward.left.set_sensitive(True)
52 # reset navigation forward stack on a direct navigation
53 self._nav_forward_stack = []
54 # update buttons
55 self.available_pane.back_forward.left.set_sensitive(True)
56 self.available_pane.back_forward.right.set_sensitive(False)53 self.available_pane.back_forward.right.set_sensitive(False)
57 54
55 def navigate_no_cursor_step(self, nav_item):
56 if in_replay_history_mode:
57 return
58
59 nav_item.parent = self
60 self._nav_stack.append_no_cursor_step(nav_item)
61 return
62
58 def nav_forward(self):63 def nav_forward(self):
59 """64 """
60 navigate forward one item in the history stack65 navigate forward one item in the history stack
61 """66 """
67 nav_item = self._nav_stack.step_forward()
68 nav_item.navigate_to()
69
62 self.available_pane.back_forward.left.set_sensitive(True)70 self.available_pane.back_forward.left.set_sensitive(True)
63 if len(self._nav_forward_stack) <= 1:71 if self._nav_stack.at_end():
72 if self.available_pane.back_forward.right.has_focus():
73 self.available_pane.back_forward.left.grab_focus()
64 self.available_pane.back_forward.right.set_sensitive(False)74 self.available_pane.back_forward.right.set_sensitive(False)
65 nav_item = self._nav_forward_stack.pop()75
66 self._nav_back_stack.append(self._current_nav_item)
67 self._current_nav_item = nav_item
68 nav_item.navigate_to()
69
70 def nav_back(self):76 def nav_back(self):
71 """77 """
72 navigate back one item in the history stack78 navigate back one item in the history stack
73 """79 """
80 nav_item = self._nav_stack.step_back()
81 nav_item.navigate_to()
82
74 self.available_pane.back_forward.right.set_sensitive(True)83 self.available_pane.back_forward.right.set_sensitive(True)
75 if len(self._nav_back_stack) <= 1:84 if self._nav_stack.at_start():
85 if self.available_pane.back_forward.left.has_focus():
86 self.available_pane.back_forward.right.grab_focus()
76 self.available_pane.back_forward.left.set_sensitive(False)87 self.available_pane.back_forward.left.set_sensitive(False)
77 nav_item = self._nav_back_stack.pop()88
78 logging.debug("nav_back: %s" % nav_item)89 def get_last_label(self):
79 self._nav_forward_stack.append(self._current_nav_item)90 if self._nav_stack.stack:
80 self._current_nav_item = nav_item91 if self._nav_stack[-1].parts:
81 nav_item.navigate_to()92 return self._nav_stack[-1].parts[-1].label
82 93 return None
94
95
83class NavigationItem(object):96class NavigationItem(object):
84 """97 """
85 class to implement navigation points to be managed in the history queues98 class to implement navigation points to be managed in the history queues
86 """99 """
87100
88 def __init__(self, available_pane):101 def __init__(self, available_pane, update_available_pane_cb):
89 self.available_pane = available_pane102 self.available_pane = available_pane
103 self.update_available_pane = update_available_pane_cb
90 self.apps_category = available_pane.apps_category104 self.apps_category = available_pane.apps_category
91 self.apps_subcategory = available_pane.apps_subcategory105 self.apps_subcategory = available_pane.apps_subcategory
92 self.apps_search_term = available_pane.apps_search_term106 self.apps_search_term = available_pane.apps_search_term
93 self.current_app = available_pane.get_current_app()107 self.current_app = available_pane.get_current_app()
94 self.parts = self.available_pane.navigation_bar.get_parts()[:]108 self.parts = self.available_pane.navigation_bar.get_parts()
95 109
96 def navigate_to(self):110 def navigate_to(self):
97 """111 """
98 navigate to the view that corresponds to this NavigationItem112 navigate to the view that corresponds to this NavigationItem
99 """113 """
100 global in_replay_history_mode114 global in_replay_history_mode
101 in_replay_history_mode = True115 in_replay_history_mode = True
102 self.available_pane.apps_category = self.apps_category116 available_pane = self.available_pane
103 self.available_pane.apps_subcategory = self.apps_subcategory117 available_pane.apps_category = self.apps_category
104 self.available_pane.apps_search_term = self.apps_search_term118 available_pane.apps_subcategory = self.apps_subcategory
105 self.available_pane.searchentry.set_text(self.apps_search_term)119 available_pane.apps_search_term = self.apps_search_term
106 self.available_pane.searchentry.set_position(-1)120 available_pane.searchentry.set_text(self.apps_search_term)
107 self.available_pane.app_details.show_app(self.current_app)121 available_pane.searchentry.set_position(-1)
108 # first part is special and kept in remove_all122 available_pane.app_details.show_app(self.current_app)
109 self.available_pane.navigation_bar.remove_all()123
124 nav_bar = self.available_pane.navigation_bar
125 nav_bar.remove_all(do_callback=False)
126
110 for part in self.parts[1:]:127 for part in self.parts[1:]:
111 self.available_pane.navigation_bar.add_with_id(unescape(part.label),128 nav_bar.add_with_id(unescape(part.label),
112 part.callback,129 part.callback,
113 part.id,130 part.get_name(),
114 do_callback=False,131 do_callback=False,
115 animate=False)132 animate=False)
116 self.parts[-1].activate()133
134 gobject.idle_add(self._update_available_pane_cb, nav_bar)
117 in_replay_history_mode = False135 in_replay_history_mode = False
118 136
137 def _update_available_pane_cb(self, nav_bar):
138 last_part = nav_bar.get_parts()[-1]
139 nav_bar.set_active_no_callback(last_part)
140 self.update_available_pane()
141 return False
142
119 def __str__(self):143 def __str__(self):
120 details = []144 details = []
121 details.append("\n%s" % type(self))145 details.append("\n%s" % type(self))
@@ -130,41 +154,76 @@
130 details.append(" current_app: %s" % self.current_app)154 details.append(" current_app: %s" % self.current_app)
131 details.append(" apps_search_term: %s" % self.apps_search_term)155 details.append(" apps_search_term: %s" % self.apps_search_term)
132 return '\n'.join(details)156 return '\n'.join(details)
133 157
134class CategoryViewNavigationItem(NavigationItem):158
135 """159class NavigationStack(object):
136 navigation item that corresponds to the main category view160
137 Note: all subclasses of NavigationItem are for debug use only and161 def __init__(self, max_length):
138 can be collapsed to the NavigationItem class if desired162 self.max_length = max_length
139 """163 self.stack = []
140 164 self.cursor = 0
141class AppListNavigationItem(NavigationItem):165 return
142 """166
143 navigation item that corresponds to the application list for the167 def __len__(self):
144 specified category168 return len(self.stack)
145 Note: all subclasses of NavigationItem are for debug use only and169
146 can be collapsed to the NavigationItem class if desired170 def __repr__(self):
147 """171 BOLD = "\033[1m"
148 172 RESET = "\033[0;0m"
149class AppListSubcategoryNavigationItem(NavigationItem):173 s = '['
150 """174 for i, item in enumerate(self.stack):
151 navigation item that corresponds to the application list for the175 if i != self.cursor:
152 specified category and subcategory176 s += str(item.parts[-1].label) + ', '
153 Note: all subclasses of NavigationItem are for debug use only and177 else:
154 can be collapsed to the NavigationItem class if desired178 s += BOLD + str(item.parts[-1].label) + RESET + ', '
155 """179 return s + ']'
156 180
157class AppDetailsNavigationItem(NavigationItem):181 def __getitem__(self, item):
158 """182 return self.stack[item]
159 navigation item that corresponds to the details view for the183
160 specified application184 def _isok(self, item):
161 Note: all subclasses of NavigationItem are for debug use only and185 if len(self.stack) == 0: return True
162 can be collapsed to the NavigationItem class if desired186 pre_item = self.stack[-1]
163 """187 if pre_item.parts[-1].label == item.parts[-1].label:
164 188 if pre_item.apps_search_term != item.apps_search_term:
165class SearchNavigationItem(NavigationItem):189 return True
166 """190 return False
167 navigation item that corresponds to a search in progress191 return True
168 Note: all subclasses of NavigationItem are for debug use only and192
169 can be collapsed to the NavigationItem class if desired193 def append(self, item):
170 """194 if not self._isok(item):
195 self.cursor = len(self.stack)-1
196 print 'A:', repr(self)
197 return
198 if len(self.stack) + 1 > self.max_length:
199 self.stack.pop(0)
200 self.stack.append(item)
201 self.cursor = len(self.stack)-1
202 print 'A:', repr(self)
203 return
204
205 def append_no_cursor_step(self, item):
206 if not self._isok(item):
207 print 'a:', repr(self)
208 return
209 if len(self.stack) + 1 > self.max_length:
210 self.stack.pop(0)
211 self.stack.append(item)
212 print 'a:', repr(self)
213 return
214
215 def step_back(self):
216 self.cursor -= 1
217 print 'B:', repr(self)
218 return self.stack[self.cursor]
219
220 def step_forward(self):
221 self.cursor += 1
222 print 'B:', repr(self)
223 return self.stack[self.cursor]
224
225 def at_end(self):
226 return self.cursor == len(self.stack)-1
227
228 def at_start(self):
229 return self.cursor == 0
171230
=== modified file 'softwarecenter/view/softwarepane.py'
--- softwarecenter/view/softwarepane.py 2010-03-29 20:40:17 +0000
+++ softwarecenter/view/softwarepane.py 2010-04-04 06:00:32 +0000
@@ -29,7 +29,7 @@
29if "SOFTWARE_CENTER_OLD_PATHBAR" in os.environ:29if "SOFTWARE_CENTER_OLD_PATHBAR" in os.environ:
30 from widgets.navigationbar import NavigationBar30 from widgets.navigationbar import NavigationBar
31else:31else:
32 from widgets.pathbar2 import NavigationBar32 from widgets.pathbar_gtk_atk import NavigationBar
3333
34from widgets.searchentry import SearchEntry34from widgets.searchentry import SearchEntry
3535
3636
=== modified file 'softwarecenter/view/widgets/backforward.py'
--- softwarecenter/view/widgets/backforward.py 2010-02-15 02:53:26 +0000
+++ softwarecenter/view/widgets/backforward.py 2010-04-04 06:00:32 +0000
@@ -17,14 +17,14 @@
17# along with this program. If not, see <http://www.gnu.org/licenses/>.17# along with this program. If not, see <http://www.gnu.org/licenses/>.
1818
1919
2020import atk
21import rgb
22import gtk21import gtk
23import cairo22import cairo
24import gobject23import gobject
25import pathbar224import pathbar_common
2625
27from rgb import to_float as f26from gettext import gettext as _
27
2828
29# pi constants29# pi constants
30M_PI = 3.141592653589793130M_PI = 3.1415926535897931
@@ -43,72 +43,132 @@
4343
44 def __init__(self):44 def __init__(self):
45 gtk.HBox.__init__(self)45 gtk.HBox.__init__(self)
46 self.theme = pathbar_common.PathBarStyle(self)
46 sep = SeparatorPart()47 sep = SeparatorPart()
4748
48 if self.get_direction() != gtk.TEXT_DIR_RTL:49 if self.get_direction() != gtk.TEXT_DIR_RTL:
50 # ltr
49 self.left = ButtonPartLeft('left-clicked')51 self.left = ButtonPartLeft('left-clicked')
50 self.right = ButtonPartRight('right-clicked')52 self.right = ButtonPartRight('right-clicked')
53 self.set_button_atk_info_ltr()
51 else:54 else:
55 # rtl
52 self.left = ButtonPartRight('left-clicked')56 self.left = ButtonPartRight('left-clicked')
53 self.right = ButtonPartLeft('right-clicked')57 self.right = ButtonPartLeft('right-clicked')
58 self.set_button_atk_info_rtl()
59
60 atk_obj = self.get_accessible()
61 atk_obj.set_name(_('History Navigation'))
62 atk_obj.set_description(_('Navigate forwards and backwards.'))
63 atk_obj.set_role(atk.ROLE_PANEL)
5464
55 self.pack_start(self.left)65 self.pack_start(self.left)
56 self.pack_start(sep, False)66 self.pack_start(sep, False)
57 self.pack_end(self.right)67 self.pack_end(self.right)
5868
59 self.theme = self._pick_theme()69 sep.connect_after("style-set", self._on_style_set)
60 self.connect("realize", self._on_realize)70 return
61 return71
6272 def set_button_atk_info_ltr(self):
63 def _pick_theme(self, name=None):73 # left button
64 name = name or gtk.settings_get_default().get_property("gtk-theme-name")74 atk_obj = self.left.get_accessible()
65 themes = pathbar2.PathBarThemes.DICT75 atk_obj.set_name(_('Back Button'))
66 if themes.has_key(name):76 atk_obj.set_description(_('Navigates back.'))
67 return themes[name]()77 atk_obj.set_role(atk.ROLE_PUSH_BUTTON)
68 print "No styling hints for %s are available" % name78
69 return pathbar2.PathBarThemeHuman()79 # right button
7080 atk_obj = self.right.get_accessible()
71 def _on_realize(self, widget):81 atk_obj.set_name(_('Forward Button'))
72 self.theme.load(self.style)82 atk_obj.set_description(_('Navigates forward.'))
73 return83 atk_obj.set_role(atk.ROLE_PUSH_BUTTON)
7484 return
85
86 def set_button_atk_info_rtl(self):
87 # right button
88 atk_obj = self.right.get_accessible()
89 atk_obj.set_name(_('Back Button'))
90 atk_obj.set_description(_('Navigates back.'))
91 atk_obj.set_role(atk.ROLE_PUSH_BUTTON)
92
93 # left button
94 atk_obj = self.left.get_accessible()
95 atk_obj.set_name(_('Forward Button'))
96 atk_obj.set_description(_('Navigates forward.'))
97 atk_obj.set_role(atk.ROLE_PUSH_BUTTON)
98 return
99
100 def _on_style_set(self, widget, oldstyle):
101 # when alloc.width == 1, this is typical of an unallocated widget,
102 # lets not break a sweat for nothing...
103 if self.allocation.width == 1:
104 return
105
106 old_xthickness = self.theme['xthickness']
107 self.theme = pathbar_common.PathBarStyle(self)
108 if old_xthickness > self.theme['xthickness']:
109 a = self.allocation
110 self.queue_draw_area(a.x, a.y,
111 a.width+self.theme['xthickness'], a.height)
112 else:
113 self.queue_draw()
114 return
75115
76class SeparatorPart(gtk.DrawingArea):116class SeparatorPart(gtk.DrawingArea):
77117
78 def __init__(self):118 def __init__(self):
79 gtk.DrawingArea.__init__(self)119 gtk.DrawingArea.__init__(self)
80 self.set_size_request(1, -1)120 self.theme = pathbar_common.PathBarStyle(self)
121 self.set_size_request(self.theme['xthickness'], -1)
122
123 atk_obj = self.get_accessible()
124 atk_obj.set_role(atk.ROLE_SEPARATOR)
125
81 self.connect("expose-event", self._on_expose)126 self.connect("expose-event", self._on_expose)
127 self.connect("style-set", self._on_style_set)
82 return128 return
83129
84 def _on_expose(self, widget, event):130 def _on_expose(self, widget, event):
131 parent = self.get_parent()
132 if not parent: return
85 cr = widget.window.cairo_create()133 cr = widget.window.cairo_create()
86 a = event.area134 cr.rectangle(event.area)
87 cr.rectangle(a.x, a.y+1, a.width, a.height-2)135 cr.set_source_rgb(*self.theme.dark_line[self.state].tofloats())
88 cr.set_source_rgba(0, 0, 0, 0.45)
89 cr.fill()136 cr.fill()
90 del cr137 del cr
91 return138 return
92139
140 def _on_style_set(self, widget, old_style):
141 self.theme = pathbar_common.PathBarStyle(self)
142 self.set_size_request(self.theme['xthickness'], -1)
143 return
144
93145
94class ButtonPart(gtk.DrawingArea):146class ButtonPart(gtk.DrawingArea):
95147
96 ARROW_SIZE = (12,12)148 ARROW_SIZE = (12,12)
97 DEFAULT_SIZE = (30, 28)149 DEFAULT_SIZE = (31, 27)
98150
99 def __init__(self, arrow_type, signal_name):151 def __init__(self, arrow_type, signal_name):
100 gtk.DrawingArea.__init__(self)152 gtk.DrawingArea.__init__(self)
101 self.set_size_request(*self.DEFAULT_SIZE)153 self.set_size_request(*self.DEFAULT_SIZE)
154 self.shape = pathbar_common.SHAPE_RECTANGLE
102 self.button_down = False155 self.button_down = False
103 self.shadow_type = gtk.SHADOW_OUT156 self.shadow_type = gtk.SHADOW_OUT
104 self.arrow_type = arrow_type157 self.arrow_type = arrow_type
158
159 self.set_flags(gtk.CAN_FOCUS)
105 self.set_events(gtk.gdk.ENTER_NOTIFY_MASK|160 self.set_events(gtk.gdk.ENTER_NOTIFY_MASK|
106 gtk.gdk.LEAVE_NOTIFY_MASK|161 gtk.gdk.LEAVE_NOTIFY_MASK|
107 gtk.gdk.BUTTON_PRESS_MASK|162 gtk.gdk.BUTTON_PRESS_MASK|
108 gtk.gdk.BUTTON_RELEASE_MASK)163 gtk.gdk.BUTTON_RELEASE_MASK)
164
109 self.connect("enter-notify-event", self._on_enter)165 self.connect("enter-notify-event", self._on_enter)
110 self.connect("leave-notify-event", self._on_leave)166 self.connect("leave-notify-event", self._on_leave)
111 self.connect("button-press-event", self._on_press)167 self.connect("button-press-event", self._on_press)
168 self.connect("key-press-event", self._on_key_press)
169 self.connect("key-release-event", self._on_key_release, signal_name)
170 self.connect('focus-in-event', self._on_focus_in)
171 self.connect('focus-out-event', self._on_focus_out)
112 self.connect("button-release-event", self._on_release, signal_name)172 self.connect("button-release-event", self._on_release, signal_name)
113 return173 return
114174
@@ -135,11 +195,32 @@
135 self.set_active(True)195 self.set_active(True)
136 return196 return
137197
198 def _on_key_press(self, widget, event):
199 # react to spacebar, enter, numpad-enter
200 if event.keyval in (32, 65293, 65421):
201 self.set_state(gtk.STATE_ACTIVE)
202 return
203
204 def _on_key_release(self, widget, event, signal_name):
205 # react to spacebar, enter, numpad-enter
206 if event.keyval in (32, 65293, 65421):
207 self.set_state(gtk.STATE_SELECTED)
208 self.get_parent().emit(signal_name, event)
209 return
210
138 def _on_leave(self, widget, event):211 def _on_leave(self, widget, event):
139 if self.state == gtk.STATE_INSENSITIVE: return212 if self.state == gtk.STATE_INSENSITIVE: return
140 self.set_active(False)213 self.set_active(False)
141 return214 return
142215
216 def _on_focus_in(self, widget, event):
217 self.queue_draw()
218 return
219
220 def _on_focus_out(self, widget, event):
221 self.queue_draw()
222 return
223
143 def _on_press(self, widget, event):224 def _on_press(self, widget, event):
144 if self.state == gtk.STATE_INSENSITIVE: return225 if self.state == gtk.STATE_INSENSITIVE: return
145 self.button_down = True226 self.button_down = True
@@ -160,117 +241,41 @@
160 self.set_state(gtk.STATE_NORMAL)241 self.set_state(gtk.STATE_NORMAL)
161 return242 return
162243
163# def expose_gtk(self, widget, area, x, y, width, height):244 def expose_pathbar(self, widget, area, x, y, w, h, xo=0, wo=0):
164# # button background245 if not self.parent: return
165# widget.style.paint_box(widget.window,
166# self.state,
167# self.shadow_type,
168# area,
169# widget,
170# "button",
171# x,
172# y,
173# width,
174# height)
175
176# # arrow
177# aw, ah = self.ARROW_SIZE
178# widget.style.paint_arrow(widget.window,
179# self.state,
180# self.shadow_type,
181# area,
182# widget,
183# "button",
184# self.arrow_type,
185# True,
186# (area.width - aw)/2,
187# (area.height - ah)/2,
188# aw,
189# ah)
190# return
191
192 def expose_pathbar(self, widget, area, x, y, width, height):
193 # background246 # background
194 cr = widget.window.cairo_create()247 cr = widget.window.cairo_create()
195 cr.rectangle(area)248 cr.rectangle(area)
196 cr.clip()249 cr.clip()
197250
198 cr.translate(x, y)251 self.parent.theme.paint_bg(cr,
199252 self,
200 self._draw_bg(cr,253 x, y, w, h)
201 width,
202 height,
203 self.state,
204 self.style,
205 self.get_parent().theme,
206 self.get_parent().theme.curvature)
207 del cr254 del cr
208255
209 # arrow256 # arrow
257 if self.has_focus():
258 self.style.paint_focus(self.window,
259 self.state,
260 (x+4+xo, y+4, w-8+wo, h-8),
261 self,
262 'button',
263 x+4+xo, y+4,
264 w-8+wo, h-8)
265
210 aw, ah = self.ARROW_SIZE266 aw, ah = self.ARROW_SIZE
211 widget.style.paint_arrow(widget.window,267 ax, ay = (area.width - aw)/2, (area.height - ah)/2,
212 self.state,268
213 self.shadow_type,269 self.style.paint_arrow(self.window,
214 area,270 self.state,
215 widget,271 self.shadow_type,
216 "button",272 (ax, ay, aw, ah),
217 self.arrow_type,273 self,
218 True,274 "button",
219 (area.width - aw)/2,275 self.arrow_type,
220 (area.height - ah)/2,276 True,
221 aw,277 ax, ay,
222 ah)278 aw, ah)
223 return
224
225 def _draw_bg(self, cr, w, h, state, style, theme, r):
226 # outer slight bevel or focal highlight
227 self._draw_rect(cr, 0, 0, w, h, r)
228 cr.set_source_rgba(0, 0, 0, 0.055)
229 cr.fill()
230
231 # colour scheme dicts
232 bg = theme.bg_colors
233 outer = theme.dark_line_colors
234 inner = theme.light_line_colors
235
236 # bg linear vertical gradient
237 if state != gtk.STATE_PRELIGHT:
238 color1, color2 = bg[state]
239 else:
240 if self.state == gtk.STATE_ACTIVE:
241 color1, color2 = bg[theme.PRELIT_NORMAL]
242 else:
243 color1, color2 = bg[theme.PRELIT_ACTIVE]
244
245 self._draw_rect(cr, 1, 1, w-1, h-1, r)
246 lin = cairo.LinearGradient(0, 0, 0, h-1)
247 lin.add_color_stop_rgb(0.0, *color1)
248 lin.add_color_stop_rgb(1.0, *color2)
249 cr.set_source(lin)
250 cr.fill()
251
252 cr.set_line_width(1.0)
253 # strong outline
254 self._draw_rect(cr, 1.5, 1.5, w-1.5, h-1.5, r)
255 cr.set_source_rgb(*outer[state])
256 cr.stroke()
257
258 # inner bevel/highlight
259 if theme.light_line_colors[state]:
260 self._draw_rect(cr, 2.5, 2.5, w-2.5, h-2.5, r)
261 r, g, b = inner[state]
262 cr.set_source_rgba(r, g, b, 0.6)
263 cr.stroke()
264 return
265
266 def _draw_rect(self, cr, x, y, w, h, r):
267 global M_PI, PI_OVER_180
268 cr.new_sub_path()
269 cr.arc(r+x, r+y, r, M_PI, 270*PI_OVER_180)
270 cr.arc(w-r, r+y, r, 270*PI_OVER_180, 0)
271 cr.arc(w-r, h-r, r, 0, 90*PI_OVER_180)
272 cr.arc(r+x, h-r, r, 90*PI_OVER_180, M_PI)
273 cr.close_path()
274 return279 return
275280
276281
@@ -288,7 +293,8 @@
288 area.x,293 area.x,
289 area.y,294 area.y,
290 area.width + 10,295 area.width + 10,
291 area.height)296 area.height,
297 wo=-10)
292 return298 return
293299
294300
@@ -303,8 +309,10 @@
303 area = event.area309 area = event.area
304 expose_func(widget,310 expose_func(widget,
305 area,311 area,
306 area.x - 10,312 area.x-10,
307 area.y,313 area.y,
308 area.width + 10,314 area.width+10,
309 area.height)315 area.height,
316 xo=10,
317 wo=-10)
310 return318 return
311319
=== removed file 'softwarecenter/view/widgets/pathbar2.py'
--- softwarecenter/view/widgets/pathbar2.py 2010-03-03 09:18:00 +0000
+++ softwarecenter/view/widgets/pathbar2.py 1970-01-01 00:00:00 +0000
@@ -1,1605 +0,0 @@
1# Copyright (C) 2009 Matthew McGowan
2#
3# Authors:
4# Matthew McGowan
5#
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation, either version 3 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18
19import atk
20import cairo
21import gobject
22import gtk
23import pango
24import rgb
25
26from rgb import to_float as f
27
28# pi constants
29M_PI = 3.1415926535897931
30PI_OVER_180 = 0.017453292519943295
31
32from gettext import gettext as _
33
34class PathBar(gtk.DrawingArea):
35
36 # shapes
37 SHAPE_RECTANGLE = 0
38 SHAPE_START_ARROW = 1
39 SHAPE_MID_ARROW = 2
40 SHAPE_END_CAP = 3
41
42 def __init__(self, group=None):
43 gtk.DrawingArea.__init__(self)
44 self.__init_drawing()
45 self.set_redraw_on_allocate(False)
46
47 self.__parts = []
48 self.__active_part = None
49 self.__focal_part = None
50 self.__button_down = False, None
51
52 self.__scroller = None
53 self.__scroll_xO = 0
54
55 self.theme = self.__pick_theme()
56
57 atk_desc = self.get_accessible()
58 # Accessibility name for the pathbar
59 atk_desc.set_name(_("You are here:"))
60 atk_desc.set_role(atk.ROLE_PANEL)
61
62 # setup event handling
63 self.set_flags(gtk.CAN_FOCUS)
64 self.set_events(gtk.gdk.POINTER_MOTION_MASK|
65 gtk.gdk.BUTTON_PRESS_MASK|
66 gtk.gdk.BUTTON_RELEASE_MASK|
67 gtk.gdk.KEY_RELEASE_MASK|
68 gtk.gdk.KEY_PRESS_MASK|
69 gtk.gdk.ENTER_NOTIFY_MASK|
70 gtk.gdk.LEAVE_NOTIFY_MASK)
71
72 self.connect("motion-notify-event", self.__motion_notify_cb)
73 self.connect("enter-notify-event", self.__enter_notify_cb)
74 self.connect("leave-notify-event", self.__leave_notify_cb)
75 self.connect("button-press-event", self.__button_press_cb)
76 self.connect("button-release-event", self.__button_release_cb)
77# self.connect("key-release-event", self.__key_release_cb)
78
79 self.connect("realize", self.__realize_cb)
80 self.connect("expose-event", self.__expose_cb)
81 self.connect("style-set", self.__style_change_cb)
82 self.connect("size-allocate", self.__allocation_change_cb)
83 return
84
85 def get_parts(self):
86 return self.__parts
87
88 def set_active(self, part, do_callback=True):
89 part.set_state(gtk.STATE_ACTIVE)
90 prev, redraw = self.__set_active(part, do_callback)
91 if redraw:
92 self.queue_draw_area(*prev.get_allocation_tuple())
93 self.queue_draw_area(*part.get_allocation_tuple())
94 return
95
96 def get_active(self):
97 return self.__active_part
98
99# def get_left_part(self):
100# active = self.get_active()
101# if not active:
102# return self.__parts[0]
103
104# i = self.__parts.index(active)+1
105# if i > len(self.__parts)-1:
106# i = 0
107# return self.__parts[i]
108
109# def get_right_part(self):
110# active = self.get_active()
111# if not active:
112# return self.__parts[0]
113
114# i = self.__parts.index(active)-1
115# if i < 0:
116# i = len(self.__parts)-1
117# return self.__parts[i]
118
119 def append(self, part, do_callback=True, animate=True):
120 prev, did_shrink = self.__append(part, do_callback)
121 if not self.get_property("visible"):
122 return False
123
124 if animate and self.theme.animate and len(self.__parts) > 1:
125 aw = self.theme.arrow_width
126
127 # calc draw_area
128 x,y,w,h = part.get_allocation_tuple()
129 w += aw
130
131 # begin scroll animation
132 self.__hscroll_out_init(
133 part.get_width(),
134 gtk.gdk.Rectangle(x,y,w,h),
135 self.theme.scroll_duration_ms,
136 self.theme.scroll_fps
137 )
138 else:
139 self.queue_draw_area(*part.get_allocation_tuple())
140 return False
141
142 def append_no_callback(self, part):
143 self.append(part, do_callback=False)
144
145 def remove(self, part):
146 if len(self.__parts)-1 < 1:
147 print 'The first part is sacred ;)'
148 return
149
150 old_w = self.__draw_width()
151
152 # remove part from interal part list
153 try:
154 del self.__parts[self.__parts.index(part)]
155 except:
156 print 'part not in list!'
157 return
158 self.__compose_parts(self.__parts[-1], False)
159
160 if old_w >= self.allocation.width:
161 self.__grow_check(old_w, self.allocation)
162 self.queue_draw()
163
164 else:
165 self.queue_draw_area(*part.get_allocation_tuple())
166 self.queue_draw_area(*self.__parts[-1].get_allocation_tuple())
167 return
168
169 def remove_all(self, keep_first_part=True):
170 """remove all elements"""
171 if keep_first_part:
172 self.__parts = [self.__parts[0],] # keep first part though!
173 self.__compose_parts(self.__parts[-1], False)
174 else:
175 self.__parts = []
176 self.id_to_part = {}
177 self.queue_draw()
178 return
179
180 def navigate_up(self):
181 if len(self.__parts) > 1:
182 nav_part = self.__parts[len(self.__parts) - 2]
183 self.set_active(nav_part)
184 return
185
186# def navigate_up(self, remove_pathparts=False):
187# index = self.__parts.index(self.__active_part)
188# if index-1 == -1: return None, index-1, len(self.__parts)
189# self.set_active(self.__parts[index-1], remove_pathparts)
190# return self.__parts[index-1], index-1, len(self.__parts)
191
192# def navigate_down(self):
193# index = self.__parts.index(self.__active_part)
194# if self.__parts[index] == self.__parts[-1]: return None, index+1, len(self.__parts)
195# self.set_active(self.__parts[index+1], False)
196# return self.__parts[index+1], index+1, len(self.__parts)
197
198 def __set_active(self, part, do_callback):
199 prev_active = self.__active_part
200 redraw = False
201 if part.callback and do_callback:
202 part.callback(self, part)
203 if prev_active and prev_active != part:
204 prev_active.set_state(gtk.STATE_NORMAL)
205 redraw = True
206
207 self.__active_part = part
208 return prev_active, redraw
209
210 def __append(self, part, do_callback=True):
211 # clean up any exisitng scroll callbacks
212 if self.__scroller:
213 gobject.source_remove(self.__scroller)
214 self.__scroll_xO = 0
215
216 # the basics
217 x = self.__draw_width()
218 self.__parts.append(part)
219 part.set_pathbar(self)
220
221 self.set_active(part, do_callback)
222
223 # determin part shapes, and calc modified parts widths
224 prev = self.__compose_parts(part, True)
225 # set the position of new part
226 part.set_x(x)
227
228 # check parts fit to widgets allocated width
229 if x + part.get_width() > self.allocation.width and \
230 self.allocation.width != 1:
231 self.__shrink_check(self.allocation)
232 return prev, True
233
234 return prev, False
235
236# def __shorten(self, n):
237# n = int(n)
238# old_w = self.__draw_width()
239# end_active = self.get_active() == self.__parts[-1]
240
241# if len(self.__parts)-n < 1:
242# print WARNING + 'The first part is sacred ;)' + ENDC
243# return old_w, False
244
245# del self.__parts[-n:]
246# self.__compose_parts(self.__parts[-1], False)
247
248# if end_active:
249# self.set_active(self.__parts[-1])
250
251# if old_w >= self.allocation.width:
252# self.__grow_check(old_w, self.allocation)
253# return old_w, True
254
255# return old_w, False
256
257 def __shrink_check(self, allocation):
258 path_w = self.__draw_width()
259 shrinkage = path_w - allocation.width
260 mpw = self.theme.min_part_width
261 xO = 0
262
263 for part in self.__parts[:-1]:
264 w = part.get_width()
265 dw = 0
266
267 if w - shrinkage <= mpw:
268 dw = w - mpw
269 shrinkage -= dw
270 part.set_size(mpw, -1)
271 part.set_x(part.get_x() - xO)
272
273 else:
274 part.set_size(w - shrinkage, -1)
275 part.set_x(part.get_x() - xO)
276 dw = shrinkage
277 shrinkage = 0
278
279 xO += dw
280
281 last = self.__parts[-1]
282 last.set_x(last.get_x() - xO)
283 return
284
285 def __grow_check(self, old_width, allocation):
286 parts = self.__parts
287 if len(parts) == 0:
288 return
289
290 growth = old_width - self.__draw_width()
291 parts.reverse()
292
293 for part in parts:
294 bw = part.get_size_requisition()[0]
295 w = part.get_width()
296
297 if w < bw:
298 dw = bw - w
299
300 if dw <= growth:
301 growth -= dw
302 part.set_size(bw, -1)
303 part.set_x(part.get_x() + growth)
304
305 else:
306 part.set_size(w + growth, -1)
307 growth = 0
308
309 else:
310 part.set_x(part.get_x() + growth)
311
312 parts.reverse()
313 shift = parts[0].get_x()
314
315 # left align parts
316 if shift > 0:
317 for part in parts: part.set_x(part.get_x() - shift)
318 return
319
320 def __compose_parts(self, last, prev_set_size):
321 parts = self.__parts
322
323 if len(parts) == 1:
324 last.set_shape(self.SHAPE_RECTANGLE)
325 last.set_size(*last.calc_size_requisition())
326 prev = None
327
328 elif len(parts) == 2:
329 prev = parts[0]
330 prev.set_shape(self.SHAPE_START_ARROW)
331 prev.calc_size_requisition()
332
333 last.set_shape(self.SHAPE_END_CAP)
334 last.set_size(*last.calc_size_requisition())
335
336 else:
337 prev = parts[-2]
338 prev.set_shape(self.SHAPE_MID_ARROW)
339 prev.calc_size_requisition()
340
341 last.set_shape(self.SHAPE_END_CAP)
342 last.set_size(*last.calc_size_requisition())
343
344 if prev and prev_set_size:
345 prev.set_size(*prev.get_size_requisition())
346 return prev
347
348 def __draw_width(self):
349 l = len(self.__parts)
350 if l == 0:
351 return 0
352 a = self.__parts[-1].allocation
353 return a[0] + a[2]
354
355 def __hscroll_out_init(self, distance, draw_area, duration, fps):
356 self.__scroller = gobject.timeout_add(
357 int(1000.0 / fps), # interval
358 self.__hscroll_out_cb,
359 distance,
360 duration*0.001, # 1 over duration (converted to seconds)
361 gobject.get_current_time(),
362 draw_area.x,
363 draw_area.y,
364 draw_area.width,
365 draw_area.height)
366 return
367
368 def __hscroll_out_cb(self, distance, duration, start_t, x, y, w, h):
369 cur_t = gobject.get_current_time()
370 xO = distance - distance*((cur_t - start_t) / duration)
371
372 if xO > 0:
373 self.__scroll_xO = xO
374 self.queue_draw_area(x, y, w, h)
375 else: # final frame
376 self.__scroll_xO = 0
377 # redraw the entire widget
378 # incase some timeouts are skipped due to high system load
379 self.queue_draw()
380 self.__scroller = None
381 return False
382 return True
383
384 def __part_at_xy(self, x, y):
385 for part in self.__parts:
386 a = part.get_allocation()
387 region = gtk.gdk.region_rectangle(a)
388
389 if region.point_in(int(x), int(y)):
390 return part
391 return None
392
393 def __draw_hscroll(self, cr):
394 if len(self.__parts) < 2:
395 return
396
397 # draw the last two parts
398 prev, last = self.__parts[-2:]
399
400 # style theme stuff
401 style, r, aw, shapes = self.style, self.theme.curvature, \
402 self.theme.arrow_width, self.__shapes
403
404 # draw part that need scrolling
405 self.__draw_part(cr,
406 last,
407 style,
408 r,
409 aw,
410 shapes,
411 self.__scroll_xO)
412
413 # draw the last part that does not scroll
414 self.__draw_part(cr,
415 prev,
416 style,
417 r,
418 aw,
419 shapes)
420 return
421
422 def __draw_all(self, cr, event_area):
423 style = self.style
424 r = self.theme.curvature
425 aw = self.theme.arrow_width
426 shapes = self.__shapes
427 region = gtk.gdk.region_rectangle(event_area)
428
429 # if a scroll is pending we want to not draw the final part,
430 # as we don't want to prematurely reveal the part befor the
431 # scroll animation has had a chance to start
432 if self.__scroller:
433 parts = self.__parts[:-1]
434 else:
435 parts = self.__parts
436
437 parts.reverse()
438 for part in parts:
439 if region.rect_in(part.get_allocation()) != gtk.gdk.OVERLAP_RECTANGLE_OUT:
440 self.__draw_part(cr, part, style, r, aw, shapes)
441 parts.reverse()
442 return
443
444 def __draw_part_ltr(self, cr, part, style, r, aw, shapes, sxO=0):
445 x, y, w, h = part.get_allocation()
446 shape = part.shape
447 state = part.state
448 icon_pb = part.icon.pixbuf
449
450 cr.save()
451 cr.translate(x-sxO, y)
452
453 # draw bg
454 self.__draw_part_bg(cr, part, w, h, state, shape, style,r, aw, shapes)
455
456 # determine left margin. left margin depends on part shape
457 # and whether there exists an icon or not
458 if shape == self.SHAPE_MID_ARROW or shape == self.SHAPE_END_CAP:
459 margin = int(0.75*self.theme.arrow_width + self.theme.xpadding)
460 else:
461 margin = self.theme.xpadding
462
463 # draw icon
464 if icon_pb:
465 cr.set_source_pixbuf(
466 icon_pb,
467 self.theme.xpadding-sxO,
468 (alloc.height - icon_pb.get_height())/2)
469 cr.paint()
470 margin += icon_pb.get_width() + self.theme.spacing
471
472 # if space is limited and an icon is set, dont draw label
473 # otherwise, draw label
474 if w == self.theme.min_part_width and icon_pb:
475 pass
476
477 else:
478 layout = part.get_layout()
479 lw, lh = layout.get_pixel_size()
480 dst_x = x + margin - int(sxO)
481 dst_y = (self.allocation.height - lh)/2+1
482 style.paint_layout(
483 self.window,
484 self.theme.text_state[state],
485 False,
486 (dst_x, dst_y, lw+4, lh), # clip area
487 self,
488 None,
489 dst_x,
490 dst_y,
491 layout)
492
493 cr.restore()
494 return
495
496 def __draw_part_rtl(self, cr, part, style, r, aw, shapes, sxO=0):
497 x, y, w, h = part.get_allocation()
498 shape = part.shape
499 state = part.state
500 icon_pb = part.icon.pixbuf
501
502 cr.save()
503 cr.translate(x+sxO, y)
504
505 # draw bg
506 self.__draw_part_bg(cr, part, w, h, state, shape, style,r, aw, shapes)
507
508 # determine left margin. left margin depends on part shape
509 # and whether there exists an icon or not
510 if shape == self.SHAPE_MID_ARROW or shape == self.SHAPE_END_CAP:
511 margin = self.theme.arrow_width + self.theme.xpadding
512 else:
513 margin = self.theme.xpadding
514
515 # draw icon
516 if icon_pb:
517 margin += icon_pb.get_width()
518 cr.set_source_pixbuf(
519 icon_pb,
520 w - margin + sxO,
521 (h - icon_pb.get_height())/2)
522 cr.paint()
523 margin += self.spacing
524
525 # if space is limited and an icon is set, dont draw label
526 # otherwise, draw label
527 if w == self.theme.min_part_width and icon_pb:
528 pass
529
530 else:
531 layout = part.get_layout()
532 lw, lh = layout.get_pixel_size()
533 dst_x = x + part.get_width() - margin - lw + int(sxO)
534 dst_y = (self.allocation.height - lh)/2+1
535 style.paint_layout(
536 self.window,
537 self.theme.text_state[state],
538 False,
539 None,
540 self,
541 None,
542 dst_x,
543 dst_y,
544 layout)
545
546 cr.restore()
547 return
548
549 def __draw_part_bg(self, cr, part, w, h, state, shape, style, r, aw, shapes):
550 # outer slight bevel or focal highlight
551 shapes[shape](cr, 0, 0, w, h, r, aw)
552 cr.set_source_rgba(0, 0, 0, 0.055)
553 cr.fill()
554
555 # colour scheme dicts
556 bg = self.theme.bg_colors
557 outer = self.theme.dark_line_colors
558 inner = self.theme.light_line_colors
559
560 # bg linear vertical gradient
561 if state != gtk.STATE_PRELIGHT:
562 color1, color2 = bg[state]
563 else:
564 if part != self.get_active():
565 color1, color2 = bg[self.theme.PRELIT_NORMAL]
566 else:
567 color1, color2 = bg[self.theme.PRELIT_ACTIVE]
568
569 shapes[shape](cr, 1, 1, w-1, h-1, r, aw)
570 lin = cairo.LinearGradient(0, 0, 0, h-1)
571 lin.add_color_stop_rgb(0.0, *color1)
572 lin.add_color_stop_rgb(1.0, *color2)
573 cr.set_source(lin)
574 cr.fill()
575
576 cr.set_line_width(1.0)
577 # strong outline
578 shapes[shape](cr, 1.5, 1.5, w-1.5, h-1.5, r, aw)
579 cr.set_source_rgb(*outer[state])
580 cr.stroke()
581
582 # inner bevel/highlight
583 if self.theme.light_line_colors[state]:
584 shapes[shape](cr, 2.5, 2.5, w-2.5, h-2.5, r, aw)
585 r, g, b = inner[state]
586 cr.set_source_rgba(r, g, b, 0.6)
587 cr.stroke()
588 return
589
590 def __shape_rect(self, cr, x, y, w, h, r, aw):
591 global M_PI, PI_OVER_180
592 cr.new_sub_path()
593 cr.arc(r+x, r+y, r, M_PI, 270*PI_OVER_180)
594 cr.arc(w-r, r+y, r, 270*PI_OVER_180, 0)
595 cr.arc(w-r, h-r, r, 0, 90*PI_OVER_180)
596 cr.arc(r+x, h-r, r, 90*PI_OVER_180, M_PI)
597 cr.close_path()
598 return
599
600 def __shape_start_arrow_ltr(self, cr, x, y, w, h, r, aw):
601 global M_PI, PI_OVER_180
602 cr.new_sub_path()
603 cr.arc(r+x, r+y, r, M_PI, 270*PI_OVER_180)
604 # arrow head
605 cr.line_to(w-aw+1, y)
606 cr.line_to(w, (h+y)*0.5)
607 cr.line_to(w-aw+1, h)
608 cr.arc(r+x, h-r, r, 90*PI_OVER_180, M_PI)
609 cr.close_path()
610 return
611
612 def __shape_mid_arrow_ltr(self, cr, x, y, w, h, r, aw):
613 cr.move_to(-1, y)
614 # arrow head
615 cr.line_to(w-aw+1, y)
616 cr.line_to(w, (h+y)*0.5)
617 cr.line_to(w-aw+1, h)
618 cr.line_to(-1, h)
619 cr.close_path()
620 return
621
622 def __shape_end_cap_ltr(self, cr, x, y, w, h, r, aw):
623 global M_PI, PI_OVER_180
624 cr.move_to(-1, y)
625 cr.arc(w-r, r+y, r, 270*PI_OVER_180, 0)
626 cr.arc(w-r, h-r, r, 0, 90*PI_OVER_180)
627 cr.line_to(-1, h)
628 cr.close_path()
629 return
630
631 def __shape_start_arrow_rtl(self, cr, x, y, w, h, r, aw):
632 global M_PI, PI_OVER_180
633 cr.new_sub_path()
634 cr.move_to(x, (h+y)*0.5)
635 cr.line_to(aw-1, y)
636 cr.arc(w-r, r+y, r, 270*PI_OVER_180, 0)
637 cr.arc(w-r, h-r, r, 0, 90*PI_OVER_180)
638 cr.line_to(aw-1, h)
639 cr.close_path()
640 return
641
642 def __shape_mid_arrow_rtl(self, cr, x, y, w, h, r, aw):
643 cr.move_to(x, (h+y)*0.5)
644 cr.line_to(aw-1, y)
645 cr.line_to(w+1, y)
646 cr.line_to(w+1, h)
647 cr.line_to(aw-1, h)
648 cr.close_path()
649 return
650
651 def __shape_end_cap_rtl(self, cr, x, y, w, h, r, aw):
652 global M_PI, PI_OVER_180
653 cr.arc(r+x, r+y, r, M_PI, 270*PI_OVER_180)
654 cr.line_to(w+1, y)
655 cr.line_to(w+1, h)
656 cr.arc(r+x, h-r, r, 90*PI_OVER_180, M_PI)
657 cr.close_path()
658 return
659
660 def __state(self, part):
661 # returns the idle state of the part depending on
662 # whether part is active or not.
663 if part == self.__active_part:
664 return gtk.STATE_ACTIVE
665 return gtk.STATE_NORMAL
666
667 def __tooltip_check(self, part):
668 # only show a tooltip if part is truncated, i.e. not all label text is
669 # visible.
670 if part.is_truncated():
671 self.set_has_tooltip(False)
672 gobject.timeout_add(50, self.__set_tooltip_cb, part.label)
673 else:
674 self.set_has_tooltip(False)
675 return
676
677 def __set_tooltip_cb(self, text):
678 # callback allows the tooltip position to be updated as pointer moves
679 # accross different parts
680 self.set_has_tooltip(True)
681 self.set_tooltip_markup(text)
682 return False
683
684 def __pick_theme(self, name=None):
685 name = name or gtk.settings_get_default().get_property("gtk-theme-name")
686 themes = PathBarThemes.DICT
687 if themes.has_key(name):
688 return themes[name]()
689 print "No styling hints for %s are available" % name
690 return PathBarThemeHuman()
691
692 def __init_drawing(self):
693 if self.get_direction() != gtk.TEXT_DIR_RTL:
694 self.__draw_part = self.__draw_part_ltr
695 self.__shapes = {
696 self.SHAPE_RECTANGLE : self.__shape_rect,
697 self.SHAPE_START_ARROW : self.__shape_start_arrow_ltr,
698 self.SHAPE_MID_ARROW : self.__shape_mid_arrow_ltr,
699 self.SHAPE_END_CAP : self.__shape_end_cap_ltr}
700 else:
701 self.__draw_part = self.__draw_part_rtl
702 self.__shapes = {
703 self.SHAPE_RECTANGLE : self.__shape_rect,
704 self.SHAPE_START_ARROW : self.__shape_start_arrow_rtl,
705 self.SHAPE_MID_ARROW : self.__shape_mid_arrow_rtl,
706 self.SHAPE_END_CAP : self.__shape_end_cap_rtl}
707 return
708
709 def __motion_notify_cb(self, widget, event):
710 if self.__scroll_xO > 0:
711 return
712
713 part = self.__part_at_xy(event.x, event.y)
714 prev_focal = self.__focal_part
715
716 if self.__button_down[0]:
717 if part and prev_focal and part != prev_focal:
718 if self.__button_down[1] == part:
719 part.set_state(gtk.STATE_SELECTED)
720 else:
721 part.set_state(gtk.STATE_PRELIGHT)
722
723 prev_focal.set_state(self.__state(prev_focal))
724 self.queue_draw_area(*prev_focal.get_allocation_tuple())
725 self.queue_draw_area(*part.get_allocation_tuple())
726 self.__focal_part = part
727 return
728
729 if part and part.state != gtk.STATE_PRELIGHT:
730 self.__tooltip_check(part)
731 part.set_state(gtk.STATE_PRELIGHT)
732
733 if prev_focal:
734 prev_focal.set_state(self.__state(prev_focal))
735 self.queue_draw_area(*prev_focal.get_allocation_tuple())
736
737 self.__focal_part = part
738 self.queue_draw_area(*part.get_allocation_tuple())
739
740 elif not part and prev_focal != None and \
741 not widget.window.get_pointer()[2] & gtk.gdk.BUTTON1_MASK:
742 prev_focal.set_state(self.__state(prev_focal))
743 self.queue_draw_area(*prev_focal.get_allocation_tuple())
744 self.__focal_part = None
745 return
746
747 def __enter_notify_cb(self, widget, event):
748 if not self.__button_down[0] and not widget.window.get_pointer()[2] & gtk.gdk.BUTTON1_MASK:
749 return
750
751 part = self.__part_at_xy(event.x, event.y)
752 prev_focal = self.__focal_part
753
754 if part and prev_focal == part:
755 part.set_state(gtk.STATE_SELECTED)
756 self.queue_draw_area(*part.get_allocation_tuple())
757 return
758
759 def __leave_notify_cb(self, widget, event):
760 prev_focal = self.__focal_part
761 if prev_focal:
762 prev_focal.set_state(self.__state(prev_focal))
763 self.queue_draw_area(*prev_focal.get_allocation_tuple())
764
765 if not widget.window.get_pointer()[2] & gtk.gdk.BUTTON1_MASK:
766 self.__focal_part = None
767 return
768
769 def __button_press_cb(self, widget, event):
770 part = self.__part_at_xy(event.x, event.y)
771 self.__button_down = True, part
772 if part:
773 part.set_state(gtk.STATE_SELECTED)
774 self.queue_draw_area(*part.get_allocation_tuple())
775 self.__focal_part = part
776 return
777
778 def __button_release_cb(self, widget, event):
779 part = self.__part_at_xy(event.x, event.y)
780 if self.__focal_part and self.__focal_part != part:
781 pass
782 elif part and self.__button_down[0]:
783 prev_active, redraw = self.__set_active(part, True)
784 part.set_state(gtk.STATE_PRELIGHT)
785 self.queue_draw_area(*part.get_allocation_tuple())
786
787 if redraw:
788 self.queue_draw_area(*prev_active.get_allocation_tuple())
789 self.__button_down = False, None
790 return
791
792# def __key_release_cb(self, widget, event):
793# part = None
794
795# # left key pressed
796# if event.keyval == 65363:
797# part = self.get_left_part()
798
799# # right key pressed
800# elif event.keyval == 65361:
801# part = self.get_right_part()
802
803# if not part: return
804
805# prev_active = self.set_active(part)
806# self.queue_draw_area(*part.allocation)
807# if prev_active:
808# self.queue_draw_area(*prev_active.allocation)
809
810# part.emit("clicked", event.copy())
811# return
812
813 def __realize_cb(self, widget):
814 self.theme.load(widget.style)
815 return
816
817 def __expose_cb(self, widget, event):
818 cr = widget.window.cairo_create()
819
820 if self.theme.base_hack:
821 cr.set_source_rgb(*self.theme.base_hack)
822 cr.paint()
823
824 if self.__scroll_xO:
825 self.__draw_hscroll(cr)
826 else:
827 self.__draw_all(cr, event.area)
828
829 del cr
830 return
831
832 def __style_change_cb(self, widget, old_style):
833 # when alloc.width == 1, this is typical of an unallocated widget,
834 # lets not break a sweat for nothing...
835 if self.allocation.width == 1:
836 return
837
838 self.theme = self.__pick_theme()
839 self.theme.load(widget.style)
840 # set height to 0 so that if part height has been reduced the widget will
841 # shrink to an appropriate new height based on new font size
842 self.set_size_request(-1, 28)
843
844 parts = self.__parts
845 self.__parts = []
846
847 # recalc best fits, re-append then draw all
848 for part in parts:
849
850 if part.icon.pixbuf:
851 part.icon.load_pixbuf()
852
853 part.calc_size_requisition()
854 self.__append(part)
855
856 self.queue_draw()
857 return
858
859 def __allocation_change_cb(self, widget, allocation):
860 if allocation.width == 1:
861 return
862
863 path_w = self.__draw_width()
864 if path_w == allocation.width:
865 return
866 elif path_w > allocation.width:
867 self.__shrink_check(allocation)
868 else:
869 self.__grow_check(allocation.width, allocation)
870
871 self.queue_draw()
872 return
873
874# FIXME: stubs currently and not working
875class IAtkComponent(atk.Component):
876 # atk --------------------------------------------------------
877 def contains(x, y, coord_type):
878 # atk stub
879 return False
880 def ref_accessible_at_point(x, y, coord_type):
881 # atk stub
882 pass
883 def get_extents(coord_type):
884 # atk stub
885 (0, 0, 0, 0)
886 def get_position(coord_type):
887 # atk stub
888 (0, 0)
889 def get_size(self):
890 # atk stub
891 (0, 0)
892 def grab_focus(self):
893 # atk stub
894 return False
895 def remove_focus_handler(self, handler_id):
896 # atk stub
897 pass
898 def set_extents(self, x, y, width, height, coord_type):
899 # atk stub
900 return False
901 def set_position(self, x, y, coord_type):
902 # atk stub
903 return False
904 def set_size(self, width, height):
905 # atk stub
906 return False
907 def get_layer(self):
908 # atk stub
909 return atk.LAYER_WIDGET
910 def get_mdi_zorder(self):
911 # atk stub
912 return 1
913 #--------------------------------
914
915
916class PathPart(atk.Object, IAtkComponent):
917
918 def __init__(self, parent, label=None, callback=None):
919 atk.Object.__init__(self)
920 self.__requisition = (0,0)
921 self.__layout = None
922 self.__pbar = None
923
924 # self.set_name() would work as well, *but* we have that
925 # function already for a different purpose, so we need to
926 # explicitely call
927 parent_atk = parent.get_accessible()
928 atk.Object.set_name(self, label)
929 atk.Object.set_role(self, atk.ROLE_PUSH_BUTTON)
930 atk.Object.add_relationship(self, atk.RELATION_MEMBER_OF, parent_atk)
931 atk.Object.set_parent(self, parent_atk)
932 #print parent_atk
933 #print parent_atk.get_n_accessible_children()
934
935 self.allocation = [0, 0, 0, 0]
936 self.state = gtk.STATE_NORMAL
937 self.shape = PathBar.SHAPE_RECTANGLE
938
939 self.name = None
940 self.callback = callback
941 self.set_label(label or "")
942 self.icon = PathBarIcon()
943 return
944
945 def set_callback(self, cb):
946 self.callback = cb
947 return
948
949 def set_name(self, name):
950 self.name = name
951 return
952
953 def set_label(self, label):
954 # escape special characters
955 label = gobject.markup_escape_text(label.strip())
956 # some hackery to preserve italics markup
957 label = label.replace('&lt;i&gt;', '<i>').replace('&lt;/i&gt;', '</i>')
958 self.label = label
959 atk.Object.set_name(self, label)
960 return
961
962 def set_icon(self, stock_icon, size=gtk.ICON_SIZE_BUTTON):
963 self.icon.specify(stock_icon, size)
964 self.icon.load_pixbuf()
965 return
966
967 def set_state(self, gtk_state):
968 self.state = gtk_state
969 return
970
971 def set_shape(self, shape):
972 self.shape = shape
973 return
974
975 def set_x(self, x):
976 self.allocation[0] = int(x)
977 return
978
979 def set_size(self, w, h):
980 if w != -1: self.allocation[2] = int(w)
981 if h != -1: self.allocation[3] = int(h)
982 self.__calc_layout_width(self.__layout, self.shape, self.__pbar)
983 return
984
985 def set_pathbar(self, path_bar):
986 self.__pbar = path_bar
987 return
988
989 def get_x(self):
990 return self.allocation[0]
991
992 def get_width(self):
993 return self.allocation[2]
994
995 def get_height(self):
996 return self.allocation[3]
997
998 def get_label(self):
999 return self.label
1000
1001 def get_allocation(self):
1002 return gtk.gdk.Rectangle(*self.get_allocation_tuple())
1003
1004 def get_allocation_tuple(self):
1005 if self.__pbar.get_direction() != gtk.TEXT_DIR_RTL:
1006 return self.allocation
1007 x, y, w, h = self.allocation
1008 x = self.__pbar.allocation[2]-x-w
1009 return x, y, w, h
1010
1011 def get_size_requisition(self):
1012 return self.__requisition
1013
1014 def get_layout(self):
1015 return self.__layout
1016
1017 def activate(self, do_callback=True):
1018 self.__pbar.set_active(self, do_callback)
1019 return
1020
1021 def calc_size_requisition(self):
1022 pbar = self.__pbar
1023
1024 # determine widget size base on label width
1025 self.__layout = self.__layout_text(self.label, pbar.get_pango_context())
1026 extents = self.__layout.get_pixel_extents()
1027
1028 # calc text width + 2 * padding, text height + 2 * ypadding
1029 w = extents[1][2] + 2*pbar.theme.xpadding
1030 h = max(extents[1][3] + 2*pbar.theme.ypadding, pbar.get_size_request()[1])
1031
1032 # if has icon add some more pixels on
1033 if self.icon.pixbuf:
1034 w += self.icon.pixbuf.get_width() + pbar.theme.spacing
1035 h = max(self.icon.pixbuf.get_height() + 2*pbar.theme.ypadding, h)
1036
1037 # extend width depending on part shape ...
1038 if self.shape == PathBar.SHAPE_START_ARROW or \
1039 self.shape == PathBar.SHAPE_END_CAP:
1040 w += pbar.theme.arrow_width
1041
1042 elif self.shape == PathBar.SHAPE_MID_ARROW:
1043 w += 2*pbar.theme.arrow_width
1044
1045 # if height greater than current height request,
1046 # reset height request to higher value
1047 # i get the feeling this should be in set_size_request(), but meh
1048 if h > pbar.get_size_request()[1]:
1049 pbar.set_size_request(-1, h)
1050
1051 self.__requisition = (w,h)
1052 return w, h
1053
1054 def is_truncated(self):
1055 return self.__requisition[0] != self.allocation[2]
1056
1057 def __layout_text(self, text, pango_context):
1058 layout = pango.Layout(pango_context)
1059 layout.set_markup('%s' % text)
1060 layout.set_ellipsize(pango.ELLIPSIZE_END)
1061 return layout
1062
1063 def __calc_layout_width(self, layout, shape, pbar):
1064 # set layout width
1065 if self.icon.pixbuf:
1066 icon_w = self.icon.pixbuf.get_width() + pbar.theme.spacing
1067 else:
1068 icon_w = 0
1069
1070 w = self.allocation[2]
1071 if shape == PathBar.SHAPE_MID_ARROW:
1072 layout.set_width((w - 2*pbar.theme.arrow_width -
1073 2*pbar.theme.xpadding - icon_w)*pango.SCALE)
1074
1075 elif shape == PathBar.SHAPE_START_ARROW or \
1076 shape == PathBar.SHAPE_END_CAP:
1077 layout.set_width((w - pbar.theme.arrow_width - 2*pbar.theme.xpadding -
1078 icon_w)*pango.SCALE)
1079 else:
1080 layout.set_width((w - 2*pbar.theme.xpadding - icon_w)*pango.SCALE)
1081 return
1082
1083
1084class PathBarIcon:
1085
1086 def __init__(self, name=None, size=None):
1087 self.name = name
1088 self.size = size
1089 self.pixbuf = None
1090 return
1091
1092 def specify(self, name, size):
1093 self.name = name
1094 self.size = size
1095 return
1096
1097 def load_pixbuf(self):
1098 if not self.name:
1099 print 'Error: No icon specified.'
1100 return
1101 if not self.size:
1102 print 'Note: No icon size specified.'
1103
1104 def render_icon(icon_set, name, size):
1105 self.pixbuf = icon_set.render_icon(
1106 style,
1107 gtk.TEXT_DIR_NONE,
1108 gtk.STATE_NORMAL,
1109 self.size or gtk.ICON_SIZE_BUTTON,
1110 gtk.Image(),
1111 None)
1112 return
1113
1114 style = gtk.Style()
1115 icon_set = style.lookup_icon_set(self.name)
1116
1117 if not icon_set:
1118 t = gtk.icon_theme_get_default()
1119 self.pixbuf = t.lookup_icon(self.name, self.size, 0).load_icon()
1120 else:
1121 icon_set = style.lookup_icon_set(self.name)
1122 render_icon(icon_set, self.name, self.size)
1123
1124 if not self.pixbuf:
1125 print 'Error: No name failed to match any installed icon set.'
1126 self.name = gtk.STOCK_MISSING_IMAGE
1127 icon_set = style.lookup_icon_set(self.name)
1128 render_icon(icon_set, self.name, self.size)
1129 return
1130
1131
1132class PathBarThemeHuman:
1133
1134 PRELIT_NORMAL = 10
1135 PRELIT_ACTIVE = 11
1136
1137 curvature = 2.5
1138 min_part_width = 56
1139 xpadding = 8
1140 ypadding = 2
1141 spacing = 4
1142 arrow_width = 13
1143 scroll_duration_ms = 150
1144 scroll_fps = 50
1145 animate = gtk.settings_get_default().get_property("gtk-enable-animations")
1146
1147 def __init__(self):
1148 return
1149
1150 def load(self, style):
1151 mid = style.mid
1152 dark = style.dark
1153 light = style.light
1154 text = style.text
1155 active = rgb.mix_color(mid[gtk.STATE_NORMAL],
1156 mid[gtk.STATE_SELECTED], 0.25)
1157
1158 self.bg_colors = {
1159 gtk.STATE_NORMAL: (f(rgb.shade(mid[gtk.STATE_NORMAL], 1.2)),
1160 f(mid[gtk.STATE_NORMAL])),
1161
1162 gtk.STATE_ACTIVE: (f(rgb.shade(active, 1.2)),
1163 f(active)),
1164
1165 gtk.STATE_SELECTED: (f(mid[gtk.STATE_ACTIVE]),
1166 f(mid[gtk.STATE_ACTIVE])),
1167
1168 gtk.STATE_INSENSITIVE: (f(mid[gtk.STATE_INSENSITIVE]),
1169 f(mid[gtk.STATE_INSENSITIVE])),
1170
1171 self.PRELIT_NORMAL: (f(rgb.shade(mid[gtk.STATE_NORMAL], 1.25)),
1172 f(rgb.shade(mid[gtk.STATE_NORMAL], 1.05))),
1173
1174 self.PRELIT_ACTIVE: (f(rgb.shade(active, 1.25)),
1175 f(rgb.shade(active, 1.05)))
1176 }
1177
1178 self.dark_line_colors = {
1179 gtk.STATE_NORMAL: f(dark[gtk.STATE_NORMAL]),
1180 gtk.STATE_ACTIVE: f(dark[gtk.STATE_ACTIVE]),
1181 gtk.STATE_SELECTED: f(rgb.shade(dark[gtk.STATE_ACTIVE], 0.9)),
1182 gtk.STATE_PRELIGHT: f(dark[gtk.STATE_PRELIGHT]),
1183 gtk.STATE_INSENSITIVE: f(dark[gtk.STATE_PRELIGHT])
1184 }
1185
1186 self.light_line_colors = {
1187 gtk.STATE_NORMAL: f(light[gtk.STATE_NORMAL]),
1188 gtk.STATE_ACTIVE: f(light[gtk.STATE_ACTIVE]),
1189 gtk.STATE_SELECTED: None,
1190 gtk.STATE_PRELIGHT: f(light[gtk.STATE_PRELIGHT]),
1191 gtk.STATE_INSENSITIVE: f(mid[gtk.STATE_PRELIGHT])
1192 }
1193
1194 self.text_state = {
1195 gtk.STATE_NORMAL: gtk.STATE_NORMAL,
1196 gtk.STATE_ACTIVE: gtk.STATE_ACTIVE,
1197 gtk.STATE_SELECTED: gtk.STATE_ACTIVE,
1198 gtk.STATE_PRELIGHT: gtk.STATE_PRELIGHT,
1199 gtk.STATE_INSENSITIVE: gtk.STATE_INSENSITIVE
1200 }
1201
1202 self.base_hack = None
1203 return
1204
1205
1206class PathBarThemeInHuman(PathBarThemeHuman):
1207
1208 def __init__(self):
1209 PathBarThemeHuman.__init__(self)
1210 return
1211
1212 def load(self, style):
1213 mid = style.mid
1214 dark = style.dark
1215 light = style.light
1216 text = style.text
1217 active = rgb.mix_color(mid[gtk.STATE_NORMAL],
1218 mid[gtk.STATE_SELECTED], 0.25)
1219
1220 self.bg_colors = {
1221 gtk.STATE_NORMAL: (f(rgb.shade(mid[gtk.STATE_NORMAL], 1.175)),
1222 f(mid[gtk.STATE_NORMAL])),
1223
1224 gtk.STATE_ACTIVE: (f(rgb.shade(active, 1.2)),
1225 f(active)),
1226
1227 gtk.STATE_SELECTED: (f(mid[gtk.STATE_ACTIVE]),
1228 f(mid[gtk.STATE_ACTIVE])),
1229
1230 gtk.STATE_INSENSITIVE: (f(rgb.shade(mid[gtk.STATE_INSENSITIVE], 1.15)),
1231 f(rgb.shade(mid[gtk.STATE_INSENSITIVE], 1.1))),
1232
1233 self.PRELIT_NORMAL: (f(rgb.shade(mid[gtk.STATE_NORMAL], 1.25)),
1234 f(rgb.shade(mid[gtk.STATE_NORMAL], 1.05))),
1235
1236 self.PRELIT_ACTIVE: (f(rgb.shade(active, 1.25)),
1237 f(rgb.shade(active, 1.05)))
1238 }
1239
1240 self.dark_line_colors = {
1241 gtk.STATE_NORMAL: f(dark[gtk.STATE_NORMAL]),
1242 gtk.STATE_ACTIVE: f(dark[gtk.STATE_ACTIVE]),
1243 gtk.STATE_SELECTED: f(rgb.shade(dark[gtk.STATE_ACTIVE], 0.9)),
1244 gtk.STATE_PRELIGHT: f(dark[gtk.STATE_PRELIGHT]),
1245 gtk.STATE_INSENSITIVE: f(dark[gtk.STATE_PRELIGHT])
1246 }
1247
1248 self.light_line_colors = {
1249 gtk.STATE_NORMAL: f(light[gtk.STATE_NORMAL]),
1250 gtk.STATE_ACTIVE: f(light[gtk.STATE_ACTIVE]),
1251 gtk.STATE_SELECTED: None,
1252 gtk.STATE_PRELIGHT: f(light[gtk.STATE_PRELIGHT]),
1253 gtk.STATE_INSENSITIVE: f(light[gtk.STATE_PRELIGHT])
1254 }
1255
1256 self.text_state = {
1257 gtk.STATE_NORMAL: gtk.STATE_NORMAL,
1258 gtk.STATE_ACTIVE: gtk.STATE_ACTIVE,
1259 gtk.STATE_SELECTED: gtk.STATE_ACTIVE,
1260 gtk.STATE_PRELIGHT: gtk.STATE_PRELIGHT,
1261 gtk.STATE_INSENSITIVE: gtk.STATE_INSENSITIVE
1262 }
1263
1264 self.base_hack = None
1265 return
1266
1267
1268class PathBarThemeHumanClearlooks(PathBarThemeHuman):
1269
1270 def __init__(self):
1271 PathBarThemeHuman.__init__(self)
1272 return
1273
1274 def load(self, style):
1275 mid = style.mid
1276 dark = style.dark
1277 light = style.light
1278 text = style.text
1279 active = rgb.mix_color(mid[gtk.STATE_NORMAL],
1280 mid[gtk.STATE_SELECTED], 0.25)
1281
1282 self.bg_colors = {
1283 gtk.STATE_NORMAL: (f(rgb.shade(mid[gtk.STATE_NORMAL], 1.20)),
1284 f(rgb.shade(mid[gtk.STATE_NORMAL], 1.05))),
1285
1286 gtk.STATE_ACTIVE: (f(rgb.shade(active, 1.20)),
1287 f(rgb.shade(active, 1.05))),
1288
1289 gtk.STATE_SELECTED: (f(rgb.shade(mid[gtk.STATE_ACTIVE], 1.15)),
1290 f(mid[gtk.STATE_ACTIVE])),
1291
1292 gtk.STATE_INSENSITIVE: (f(mid[gtk.STATE_INSENSITIVE]),
1293 f(mid[gtk.STATE_INSENSITIVE])),
1294
1295 self.PRELIT_NORMAL: (f(rgb.shade(mid[gtk.STATE_NORMAL], 1.35)),
1296 f(rgb.shade(mid[gtk.STATE_NORMAL], 1.15))),
1297
1298 self.PRELIT_ACTIVE: (f(rgb.shade(active, 1.35)),
1299 f(rgb.shade(active, 1.15)))
1300 }
1301
1302 self.dark_line_colors = {
1303 gtk.STATE_NORMAL: f(rgb.shade(dark[gtk.STATE_ACTIVE], 0.975)),
1304 gtk.STATE_ACTIVE: f(rgb.shade(dark[gtk.STATE_ACTIVE], 0.975)),
1305 gtk.STATE_SELECTED: f(rgb.shade(dark[gtk.STATE_ACTIVE], 0.95)),
1306 gtk.STATE_PRELIGHT: f(dark[gtk.STATE_PRELIGHT]),
1307 gtk.STATE_INSENSITIVE: f(dark[gtk.STATE_INSENSITIVE])
1308 }
1309
1310 self.light_line_colors = {
1311 gtk.STATE_NORMAL: None,
1312 gtk.STATE_ACTIVE: None,
1313 gtk.STATE_SELECTED: f(mid[gtk.STATE_ACTIVE]),
1314 gtk.STATE_PRELIGHT: f(light[gtk.STATE_PRELIGHT]),
1315 gtk.STATE_INSENSITIVE: f(light[gtk.STATE_INSENSITIVE])
1316 }
1317
1318 self.text_state = {
1319 gtk.STATE_NORMAL: gtk.STATE_NORMAL,
1320 gtk.STATE_ACTIVE: gtk.STATE_ACTIVE,
1321 gtk.STATE_SELECTED: gtk.STATE_NORMAL,
1322 gtk.STATE_PRELIGHT: gtk.STATE_PRELIGHT,
1323 gtk.STATE_INSENSITIVE: gtk.STATE_INSENSITIVE
1324 }
1325
1326 self.base_hack = None
1327 return
1328
1329
1330class PathBarThemeDust(PathBarThemeHuman):
1331
1332 def __init__(self):
1333 PathBarThemeHuman.__init__(self)
1334 return
1335
1336 def load(self, style):
1337 mid = style.mid
1338 dark = style.dark
1339 light = style.light
1340 text = style.text
1341 active = rgb.mix_color(mid[gtk.STATE_NORMAL],
1342 light[gtk.STATE_SELECTED], 0.3)
1343
1344 self.bg_colors = {
1345 gtk.STATE_NORMAL: (f(rgb.shade(mid[gtk.STATE_NORMAL], 1.3)),
1346 f(mid[gtk.STATE_NORMAL])),
1347
1348 gtk.STATE_ACTIVE: (f(rgb.shade(active, 1.3)),
1349 f(active)),
1350
1351 gtk.STATE_SELECTED: (f(rgb.shade(mid[gtk.STATE_NORMAL], 0.95)),
1352 f(rgb.shade(mid[gtk.STATE_NORMAL], 0.95))),
1353
1354 self.PRELIT_NORMAL: (f(rgb.shade(mid[gtk.STATE_NORMAL], 1.35)),
1355 f(rgb.shade(mid[gtk.STATE_NORMAL], 1.15))),
1356
1357 gtk.STATE_INSENSITIVE: (f(rgb.shade(mid[gtk.STATE_INSENSITIVE], 1.09)),
1358 f(rgb.shade(mid[gtk.STATE_INSENSITIVE], 1.08))),
1359
1360 self.PRELIT_ACTIVE: (f(rgb.shade(active, 1.35)),
1361 f(rgb.shade(active, 1.15)))
1362 }
1363
1364 self.dark_line_colors = {
1365 gtk.STATE_NORMAL: f(dark[gtk.STATE_ACTIVE]),
1366 gtk.STATE_ACTIVE: f(dark[gtk.STATE_ACTIVE]),
1367 gtk.STATE_SELECTED: f(rgb.shade(dark[gtk.STATE_ACTIVE], 0.95)),
1368 gtk.STATE_PRELIGHT: f(dark[gtk.STATE_PRELIGHT]),
1369 gtk.STATE_INSENSITIVE: f(dark[gtk.STATE_INSENSITIVE])
1370 }
1371
1372 self.light_line_colors = {
1373 gtk.STATE_NORMAL: f(light[gtk.STATE_NORMAL]),
1374 gtk.STATE_ACTIVE: f(light[gtk.STATE_NORMAL]),
1375 gtk.STATE_SELECTED: None,
1376 gtk.STATE_PRELIGHT: f(light[gtk.STATE_PRELIGHT]),
1377 gtk.STATE_INSENSITIVE: f(rgb.shade(light[gtk.STATE_INSENSITIVE], 0.96))
1378 }
1379
1380 self.text_state = {
1381 gtk.STATE_NORMAL: gtk.STATE_NORMAL,
1382 gtk.STATE_ACTIVE: gtk.STATE_ACTIVE,
1383 gtk.STATE_SELECTED: gtk.STATE_NORMAL,
1384 gtk.STATE_PRELIGHT: gtk.STATE_PRELIGHT,
1385 gtk.STATE_INSENSITIVE: gtk.STATE_INSENSITIVE
1386 }
1387
1388 self.base_hack = None
1389 return
1390
1391
1392class PathBarThemeNewWave(PathBarThemeHuman):
1393
1394 curvature = 1.5
1395
1396 def __init__(self):
1397 PathBarThemeHuman.__init__(self)
1398 return
1399
1400 def load(self, style):
1401 mid = style.mid
1402 dark = style.dark
1403 light = style.light
1404 text = style.text
1405 active = rgb.mix_color(mid[gtk.STATE_NORMAL],
1406 light[gtk.STATE_SELECTED], 0.5)
1407 top_step = gtk.gdk.color_parse('#FDCF9D')
1408 btm_step = gtk.gdk.color_parse('#FCAE87')
1409
1410 self.bg_colors = {
1411 gtk.STATE_NORMAL: (f(rgb.shade(mid[gtk.STATE_NORMAL], 1.01)),
1412 f(mid[gtk.STATE_NORMAL])),
1413
1414 gtk.STATE_ACTIVE: (f(top_step),
1415 f(btm_step)),
1416
1417 gtk.STATE_SELECTED: (f(top_step),
1418 f(btm_step)),
1419
1420 gtk.STATE_INSENSITIVE: (f(rgb.shade(mid[gtk.STATE_INSENSITIVE], 1.075)),
1421 f(rgb.shade(mid[gtk.STATE_INSENSITIVE], 1.075))),
1422
1423 self.PRELIT_NORMAL: (f(rgb.shade(mid[gtk.STATE_NORMAL], 1.2)),
1424 f(rgb.shade(mid[gtk.STATE_NORMAL], 1.15))),
1425
1426 self.PRELIT_ACTIVE: (f(rgb.shade(top_step, 1.11)),
1427 f(rgb.shade(btm_step, 1.06))),
1428 }
1429
1430 self.dark_line_colors = {
1431 gtk.STATE_NORMAL: f(rgb.shade(dark[gtk.STATE_ACTIVE], 0.95)),
1432 gtk.STATE_ACTIVE: f(rgb.shade(dark[gtk.STATE_ACTIVE], 0.95)),
1433 gtk.STATE_SELECTED: f(rgb.shade(dark[gtk.STATE_ACTIVE], 0.95)),
1434 gtk.STATE_PRELIGHT: f(dark[gtk.STATE_PRELIGHT]),
1435 gtk.STATE_INSENSITIVE: f(dark[gtk.STATE_INSENSITIVE])
1436 }
1437
1438 self.light_line_colors = {
1439 gtk.STATE_NORMAL: f(rgb.shade(light[gtk.STATE_NORMAL], 1.2)),
1440 gtk.STATE_ACTIVE: f(rgb.shade(light[gtk.STATE_NORMAL], 1.2)),
1441 gtk.STATE_SELECTED: None,
1442 gtk.STATE_PRELIGHT: f(rgb.shade(light[gtk.STATE_PRELIGHT], 1.2)),
1443 gtk.STATE_INSENSITIVE: f(light[gtk.STATE_INSENSITIVE])
1444 }
1445
1446 self.text_state = {
1447 gtk.STATE_NORMAL: gtk.STATE_NORMAL,
1448 gtk.STATE_ACTIVE: gtk.STATE_ACTIVE,
1449 gtk.STATE_SELECTED: gtk.STATE_NORMAL,
1450 gtk.STATE_PRELIGHT: gtk.STATE_PRELIGHT,
1451 gtk.STATE_INSENSITIVE: gtk.STATE_INSENSITIVE
1452 }
1453
1454 self.base_hack = f(gtk.gdk.color_parse("#F2F2F2"))
1455 return
1456
1457
1458class PathBarThemeHicolor:
1459
1460 PRELIT_NORMAL = 10
1461 PRELIT_ACTIVE = 11
1462
1463 curvature = 0.5
1464 min_part_width = 56
1465 xpadding = 15
1466 ypadding = 10
1467 spacing = 10
1468 arrow_width = 15
1469 scroll_duration_ms = 150
1470 scroll_fps = 50
1471 animate = gtk.settings_get_default().get_property("gtk-enable-animations")
1472
1473 def __init__(self):
1474 return
1475
1476 def load(self, style):
1477 mid = style.mid
1478 dark = style.dark
1479 light = style.light
1480 text = style.text
1481
1482 self.bg_colors = {
1483 gtk.STATE_NORMAL: (f(mid[gtk.STATE_NORMAL]),
1484 f(mid[gtk.STATE_NORMAL])),
1485
1486 gtk.STATE_ACTIVE: (f(mid[gtk.STATE_ACTIVE]),
1487 f(mid[gtk.STATE_ACTIVE])),
1488
1489 gtk.STATE_SELECTED: (f(mid[gtk.STATE_SELECTED]),
1490 f(mid[gtk.STATE_SELECTED])),
1491
1492 gtk.STATE_INSENSITIVE: (f(mid[gtk.STATE_INSENSITIVE]),
1493 f(mid[gtk.STATE_INSENSITIVE])),
1494
1495 self.PRELIT_NORMAL: (f(mid[gtk.STATE_PRELIGHT]),
1496 f(mid[gtk.STATE_PRELIGHT])),
1497
1498 self.PRELIT_ACTIVE: (f(mid[gtk.STATE_PRELIGHT]),
1499 f(mid[gtk.STATE_PRELIGHT]))
1500 }
1501
1502 self.dark_line_colors = {
1503 gtk.STATE_NORMAL: f(dark[gtk.STATE_NORMAL]),
1504 gtk.STATE_ACTIVE: f(dark[gtk.STATE_ACTIVE]),
1505 gtk.STATE_SELECTED: f(dark[gtk.STATE_SELECTED]),
1506 gtk.STATE_PRELIGHT: f(dark[gtk.STATE_PRELIGHT]),
1507 gtk.STATE_INSENSITIVE: f(dark[gtk.STATE_INSENSITIVE])
1508 }
1509
1510 self.light_line_colors = {
1511 gtk.STATE_NORMAL: f(light[gtk.STATE_NORMAL]),
1512 gtk.STATE_ACTIVE: f(light[gtk.STATE_ACTIVE]),
1513 gtk.STATE_SELECTED: None,
1514 gtk.STATE_PRELIGHT: f(light[gtk.STATE_PRELIGHT]),
1515 gtk.STATE_INSENSITIVE: f(light[gtk.STATE_INSENSITIVE])
1516 }
1517
1518 self.text_state = {
1519 gtk.STATE_NORMAL: gtk.STATE_NORMAL,
1520 gtk.STATE_ACTIVE: gtk.STATE_ACTIVE,
1521 gtk.STATE_SELECTED: gtk.STATE_SELECTED,
1522 gtk.STATE_PRELIGHT: gtk.STATE_PRELIGHT,
1523 gtk.STATE_INSENSITIVE: gtk.STATE_INSENSITIVE
1524 }
1525
1526 self.base_hack = None
1527 return
1528
1529
1530class PathBarThemes:
1531
1532 DICT = {
1533 "Human": PathBarThemeHuman,
1534 "Human-Clearlooks": PathBarThemeHumanClearlooks,
1535 "InHuman": PathBarThemeInHuman,
1536 "HighContrastInverse": PathBarThemeHicolor,
1537 "HighContrastLargePrintInverse": PathBarThemeHicolor,
1538 "Dust": PathBarThemeDust,
1539 "Dust Sand": PathBarThemeDust,
1540 "New Wave": PathBarThemeNewWave
1541 }
1542
1543
1544class NavigationBar(PathBar):
1545 def __init__(self, group=None):
1546 PathBar.__init__(self)
1547 self.set_size_request(-1, 28)
1548 self.id_to_part = {}
1549 return
1550
1551 def add_with_id(self, label, callback, id, icon=None, do_callback=True, animate=True):
1552 """
1553 Add a new button with the given label/callback
1554
1555 If there is the same id already, replace the existing one
1556 with the new one
1557 """
1558
1559 # check if we have the button of that id or need a new one
1560 if id in self.id_to_part:
1561 part = self.id_to_part[id]
1562 part.set_label(label)
1563 else:
1564 part = PathPart(parent=self, label=label, callback=callback)
1565 part.set_name(id)
1566 part.set_pathbar(self)
1567 part.id = id
1568 self.id_to_part[id] = part
1569 # check if animation should be used
1570 if animate:
1571 if do_callback:
1572 gobject.timeout_add(150, self.append, part)
1573 else:
1574 gobject.timeout_add(150, self.append_no_callback, part)
1575 else:
1576 self.append(part, do_callback, animate=False)
1577
1578 if icon:
1579 part.set_icon(icon)
1580 return
1581
1582 def remove_id(self, id):
1583 if not id in self.id_to_part:
1584 return
1585
1586 part = self.id_to_part[id]
1587 del self.id_to_part[id]
1588 self.remove(part)
1589 return
1590
1591 def get_button_from_id(self, id):
1592 """
1593 return the button for the given id (or None)
1594 """
1595 if not id in self.id_to_part:
1596 return None
1597 return self.id_to_part[id]
1598
1599 def get_label(self, id):
1600 """
1601 Return the label of the navigation button with the given id
1602 """
1603 if not id in self.id_to_part:
1604 return
1605
16060
=== added file 'softwarecenter/view/widgets/pathbar_common.py'
--- softwarecenter/view/widgets/pathbar_common.py 1970-01-01 00:00:00 +0000
+++ softwarecenter/view/widgets/pathbar_common.py 2010-04-04 06:00:32 +0000
@@ -0,0 +1,827 @@
1# Copyright (C) 2010 Matthew McGowan
2#
3# Authors:
4# Matthew McGowan
5#
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation, either version 3 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18
19
20import gtk
21import cairo
22import colorsys
23
24
25# pi constants
26M_PI = 3.1415926535897931
27PI_OVER_180 = 0.017453292519943295
28
29SHAPE_RECTANGLE = 0
30SHAPE_START_ARROW = 1
31SHAPE_MID_ARROW = 2
32SHAPE_END_CAP = 3
33
34
35class PathBarStyle:
36
37 def __init__(self, pathbar):
38 self.shape_map = self._load_shape_map(pathbar)
39
40 gtk_settings = gtk.settings_get_default()
41 self.theme = self._load_theme(gtk_settings)
42 self.theme.build_palette(gtk_settings)
43 self.properties = self.theme.get_properties(gtk_settings)
44 self.gradients = self.theme.get_grad_palette()
45 self.dark_line = self.theme.get_dark_line_palette()
46 self.light_line = self.theme.get_light_line_palette()
47 self.text = self.theme.get_text_palette()
48 self.text_states = self.theme.get_text_states()
49 self.base_color = None
50 return
51
52 def __getitem__(self, item):
53 if self.properties.has_key(item):
54 return self.properties[item]
55 print 'Key does not exist in the style profile:', item
56 return None
57
58 def _load_shape_map(self, pathbar):
59 if pathbar.get_direction() != gtk.TEXT_DIR_RTL:
60 shmap = {SHAPE_RECTANGLE: self._shape_rectangle,
61 SHAPE_START_ARROW: self._shape_start_arrow_ltr,
62 SHAPE_MID_ARROW: self._shape_mid_arrow_ltr,
63 SHAPE_END_CAP: self._shape_end_cap_ltr}
64 else:
65 shmap = {SHAPE_RECTANGLE: self._shape_rectangle,
66 SHAPE_START_ARROW: self._shape_start_arrow_rtl,
67 SHAPE_MID_ARROW: self._shape_mid_arrow_rtl,
68 SHAPE_END_CAP: self._shape_end_cap_rtl}
69 return shmap
70
71 def _load_theme(self, gtksettings):
72 name = gtksettings.get_property("gtk-theme-name")
73 r = ThemeRegistry()
74 return r.retrieve(name)
75
76 def _shape_rectangle(self, cr, x, y, w, h, r, aw):
77 global M_PI, PI_OVER_180
78 cr.new_sub_path()
79 cr.arc(r+x, r+y, r, M_PI, 270*PI_OVER_180)
80 cr.arc(w-r, r+y, r, 270*PI_OVER_180, 0)
81 cr.arc(w-r, h-r, r, 0, 90*PI_OVER_180)
82 cr.arc(r+x, h-r, r, 90*PI_OVER_180, M_PI)
83 cr.close_path()
84 return
85
86 def _shape_start_arrow_ltr(self, cr, x, y, w, h, r, aw):
87 global M_PI, PI_OVER_180
88 cr.new_sub_path()
89 cr.arc(r+x, r+y, r, M_PI, 270*PI_OVER_180)
90 # arrow head
91 cr.line_to(w-aw, y)
92 cr.line_to(w-x+1, (h+y)/2)
93 cr.line_to(w-aw, h)
94 cr.arc(r+x, h-r, r, 90*PI_OVER_180, M_PI)
95 cr.close_path()
96 return
97
98 def _shape_mid_arrow_ltr(self, cr, x, y, w, h, r, aw):
99 cr.move_to(0, y)
100 # arrow head
101 cr.line_to(w-aw, y)
102 cr.line_to(w-x+1, (h+y)/2)
103 cr.line_to(w-aw, h)
104 cr.line_to(0, h)
105 cr.close_path()
106 return
107
108 def _shape_end_cap_ltr(self, cr, x, y, w, h, r, aw):
109 global M_PI, PI_OVER_180
110 cr.move_to(0, y)
111 cr.arc(w-r, r+y, r, 270*PI_OVER_180, 0)
112 cr.arc(w-r, h-r, r, 0, 90*PI_OVER_180)
113 cr.line_to(0, h)
114 cr.close_path()
115 return
116
117 def _shape_start_arrow_rtl(self, cr, x, y, w, h, r, aw):
118 global M_PI, PI_OVER_180
119 cr.new_sub_path()
120 cr.move_to(x, (h+y)/2)
121 cr.line_to(aw, y)
122 cr.arc(w-r, r+y, r, 270*PI_OVER_180, 0)
123 cr.arc(w-r, h-r, r, 0, 90*PI_OVER_180)
124 cr.line_to(aw, h)
125 cr.close_path()
126 return
127
128 def _shape_mid_arrow_rtl(self, cr, x, y, w, h, r, aw):
129 cr.move_to(x, (h+y)/2)
130 cr.line_to(aw, y)
131 cr.line_to(w, y)
132 cr.line_to(w, h)
133 cr.line_to(aw, h)
134 cr.close_path()
135 return
136
137 def _shape_end_cap_rtl(self, cr, x, y, w, h, r, aw):
138 global M_PI, PI_OVER_180
139 cr.arc(r+x, r+y, r, M_PI, 270*PI_OVER_180)
140 cr.line_to(w, y)
141 cr.line_to(w, h)
142 cr.arc(r+x, h-r, r, 90*PI_OVER_180, M_PI)
143 cr.close_path()
144 return
145
146 def set_direction(self, direction):
147 if direction != gtk.TEXT_DIR_RTL:
148 self.shape_map = {SHAPE_RECTANGLE: self._shape_rectangle,
149 SHAPE_START_ARROW: self._shape_start_arrow_ltr,
150 SHAPE_MID_ARROW: self._shape_mid_arrow_ltr,
151 SHAPE_END_CAP: self._shape_end_cap_ltr}
152 else:
153 self.shape_map = {SHAPE_RECTANGLE: self._shape_rectangle,
154 SHAPE_START_ARROW: self._shape_start_arrow_rtl,
155 SHAPE_MID_ARROW: self._shape_mid_arrow_rtl,
156 SHAPE_END_CAP: self._shape_end_cap_rtl}
157 return
158
159 def paint_bg(self, cr, part, x, y, w, h, sxO=0):
160 shape = self.shape_map[part.shape]
161 state = part.state
162 r = self["curvature"]
163 aw = self["arrow_width"]
164
165 cr.save()
166 cr.rectangle(x, y, w+1, h)
167 cr.clip()
168 cr.translate(x+0.5-sxO, y+0.5)
169
170 w -= 1
171 h -= 1
172
173 # bg linear vertical gradient
174 color1, color2 = self.gradients[state]
175
176 shape(cr, 0, 0, w, h, r, aw)
177 lin = cairo.LinearGradient(0, 0, 0, h)
178 lin.add_color_stop_rgb(0.0, *color1.tofloats())
179 lin.add_color_stop_rgb(1.0, *color2.tofloats())
180 cr.set_source(lin)
181 cr.fill()
182
183 cr.set_line_width(1.0)
184 # strong outline
185 shape(cr, 0, 0, w, h, r, aw)
186 cr.set_source_rgb(*self.dark_line[state].tofloats())
187 cr.stroke()
188
189 # inner bevel/highlight
190 if r == 0: w += 1
191 shape(cr, 1, 1, w-1, h-1, r, aw)
192 cr.set_source_rgb(*self.light_line[state].tofloats())
193 cr.stroke()
194 cr.restore()
195 return
196
197 def paint_layout(self, widget, part, x, y, sxO=0):
198 # draw layout
199 layout = part.get_layout()
200 widget.style.paint_layout(widget.window,
201 self.text_states[part.state],
202 False,
203 None, # clip area
204 widget,
205 None,
206 x, y,
207 layout)
208 return
209
210 def paint_focus(self, cr, x, y, w, h):
211 self._shape_rectangle(cr, 4, 4, w-4, h-4, self["curvature"], 0)
212 cr.set_source_rgb(*self.theme.bg[gtk.STATE_SELECTED].tofloats())
213 cr.stroke()
214 return
215
216
217class PathBarColorArray:
218
219 def __init__(self, color_array):
220 self.color_array = {}
221 for state in (gtk.STATE_NORMAL, gtk.STATE_ACTIVE, gtk.STATE_SELECTED, \
222 gtk.STATE_PRELIGHT, gtk.STATE_INSENSITIVE):
223 self.color_array[state] = color_from_gdkcolor(color_array[state])
224 return
225
226 def __getitem__(self, state):
227 return self.color_array[state]
228
229
230class PathBarColor:
231
232 def __init__(self, red, green, blue):
233 self.red = red
234 self.green = green
235 self.blue = blue
236 return
237
238 def set_alpha(self, value):
239 self.alpha = value
240 return
241
242 def tofloats(self):
243 return self.red, self.green, self.blue
244
245 def toclutter(self):
246 try:
247 from clutter import Color
248 except Exception, e:
249 print 'Error parsing color:', e
250 raise SystemExit
251 r,g,b = self.tofloats()
252 return Color(int(r*255), int(g*255), int(b*255))
253
254 def togtkgdk(self):
255 r,g,b = self.tofloats()
256 return gtk.gdk.Color(int(r*65535), int(g*65535), int(b*65535))
257
258 def lighten(self):
259 return self.shade(1.3)
260
261 def darken(self):
262 return self.shade(0.7)
263
264 def shade(self, factor):
265 # as seen in clutter-color.c
266 h,l,s = colorsys.rgb_to_hls(*self.tofloats())
267
268 l *= factor
269 if l > 1.0:
270 l = 1.0
271 elif l < 0:
272 l = 0
273
274 s *= factor
275 if s > 1.0:
276 s = 1.0
277 elif s < 0:
278 s = 0
279
280 r,g,b = colorsys.hls_to_rgb(h,l,s)
281 return PathBarColor(r,g,b)
282
283 def mix(self, color2, mix_factor):
284 # as seen in Murrine's cairo-support.c
285 r1, g1, b1 = self.tofloats()
286 r2, g2, b2 = color2.tofloats()
287 r = r1*(1-mix_factor)+r2*mix_factor
288 g = g1*(1-mix_factor)+g2*mix_factor
289 b = b1*(1-mix_factor)+b2*mix_factor
290 return PathBarColor(r,g,b)
291
292
293class Theme:
294
295 def build_palette(self, gtksettings):
296 style = gtk.rc_get_style_by_paths(gtksettings,
297 'GtkWindow',
298 'GtkWindow',
299 gtk.Window)
300
301 style = style or gtk.widget_get_default_style()
302
303 # build pathbar color palette
304 self.fg = PathBarColorArray(style.fg)
305 self.bg = PathBarColorArray(style.bg)
306 self.text = PathBarColorArray(style.text)
307 self.base = PathBarColorArray(style.base)
308 self.light = PathBarColorArray(style.base)
309 self.mid = PathBarColorArray(style.base)
310 self.dark = PathBarColorArray(style.base)
311 return
312
313
314class Human(Theme):
315
316 def get_properties(self, gtksettings):
317 props = {
318 'curvature': 2.5,
319 'min_part_width': 48,
320 'xpad': 8,
321 'ypad': 4,
322 'xthickness': 1,
323 'ythickness': 1,
324 'spacing': 5,
325 'arrow_width': 13,
326 'scroll_duration': 150,
327 'enable-animations': gtksettings.get_property("gtk-enable-animations"),
328 'override_base': False
329 }
330 return props
331
332 def get_grad_palette(self):
333 # provide two colours per state for background vertical linear gradients
334 palette = {gtk.STATE_NORMAL: (self.bg[gtk.STATE_NORMAL].shade(1.1),
335 self.bg[gtk.STATE_NORMAL].shade(0.95)),
336
337 gtk.STATE_ACTIVE: (self.bg[gtk.STATE_NORMAL].shade(1.00),
338 self.bg[gtk.STATE_NORMAL].shade(0.75)),
339
340 gtk.STATE_SELECTED: (self.bg[gtk.STATE_NORMAL].shade(1.11),
341 self.bg[gtk.STATE_NORMAL]),
342
343 gtk.STATE_PRELIGHT: (self.bg[gtk.STATE_NORMAL].shade(0.96),
344 self.bg[gtk.STATE_NORMAL].shade(0.91)),
345
346 gtk.STATE_INSENSITIVE: (self.bg[gtk.STATE_INSENSITIVE],
347 self.bg[gtk.STATE_INSENSITIVE])
348 }
349 return palette
350
351 def get_text_palette(self):
352 palette = {gtk.STATE_NORMAL: self.fg[gtk.STATE_NORMAL],
353 gtk.STATE_ACTIVE: self.fg[gtk.STATE_NORMAL],
354 gtk.STATE_SELECTED: self.fg[gtk.STATE_NORMAL],
355 gtk.STATE_PRELIGHT: self.fg[gtk.STATE_NORMAL],
356 gtk.STATE_INSENSITIVE: self.text[gtk.STATE_INSENSITIVE]}
357 return palette
358
359 def get_dark_line_palette(self):
360 palette = {gtk.STATE_NORMAL: self.bg[gtk.STATE_NORMAL].darken(),
361 gtk.STATE_ACTIVE: self.bg[gtk.STATE_NORMAL].darken(),
362 gtk.STATE_PRELIGHT: self.bg[gtk.STATE_NORMAL].darken(),
363 gtk.STATE_SELECTED: self.bg[gtk.STATE_NORMAL].darken(),
364 gtk.STATE_INSENSITIVE: self.bg[gtk.STATE_INSENSITIVE].darken()}
365 return palette
366
367 def get_light_line_palette(self):
368 palette = {gtk.STATE_NORMAL: self.bg[gtk.STATE_NORMAL].lighten(),
369 gtk.STATE_ACTIVE: self.fg[gtk.STATE_NORMAL],
370 gtk.STATE_PRELIGHT: self.bg[gtk.STATE_NORMAL].lighten(),
371 gtk.STATE_SELECTED: self.bg[gtk.STATE_NORMAL].lighten(),
372 gtk.STATE_INSENSITIVE: self.light[gtk.STATE_INSENSITIVE]}
373 return palette
374
375 def get_text_states(self):
376 states = {gtk.STATE_NORMAL: gtk.STATE_NORMAL,
377 gtk.STATE_ACTIVE: gtk.STATE_NORMAL,
378 gtk.STATE_PRELIGHT: gtk.STATE_NORMAL,
379 gtk.STATE_SELECTED: gtk.STATE_NORMAL,
380 gtk.STATE_INSENSITIVE: gtk.STATE_INSENSITIVE}
381 return states
382
383
384class Clearlooks(Human):
385
386 def get_properties(self, gtksettings):
387 props = Human.get_properties(self, gtksettings)
388 props['curvature'] = 3.5
389 return props
390
391 def get_grad_palette(self):
392 # provide two colours per state for background vertical linear gradients
393
394 selected_color = self.bg[gtk.STATE_NORMAL].mix(self.bg[gtk.STATE_SELECTED],
395 0.2)
396
397 palette = {gtk.STATE_NORMAL: (self.bg[gtk.STATE_NORMAL].shade(1.15),
398 self.bg[gtk.STATE_NORMAL].shade(0.95)),
399
400 gtk.STATE_ACTIVE: (self.bg[gtk.STATE_ACTIVE],
401 self.bg[gtk.STATE_ACTIVE]),
402
403 gtk.STATE_SELECTED: (selected_color.shade(1.175),
404 selected_color),
405
406 gtk.STATE_PRELIGHT: (self.bg[gtk.STATE_NORMAL].shade(1.3),
407 selected_color.shade(1.1)),
408
409 gtk.STATE_INSENSITIVE: (self.bg[gtk.STATE_INSENSITIVE],
410 self.bg[gtk.STATE_INSENSITIVE])
411 }
412 return palette
413
414 def get_light_line_palette(self):
415 palette = Human.get_light_line_palette(self)
416 palette[gtk.STATE_ACTIVE] = self.bg[gtk.STATE_ACTIVE]
417 return palette
418
419
420class InHuman(Theme):
421
422 def get_properties(self, gtksettings):
423 props = {
424 'curvature': 2.5,
425 'min_part_width': 48,
426 'xpad': 8,
427 'ypad': 4,
428 'xthickness': 1,
429 'ythickness': 1,
430 'spacing': 5,
431 'arrow_width': 13,
432 'scroll_duration': 150,
433 'enable-animations': gtksettings.get_property("gtk-enable-animations"),
434 'override_base': False
435 }
436 return props
437
438 def get_grad_palette(self):
439 # provide two colours per state for background vertical linear gradients
440 palette = {gtk.STATE_NORMAL: (self.bg[gtk.STATE_NORMAL].shade(1.1),
441 self.bg[gtk.STATE_NORMAL].shade(0.95)),
442
443 gtk.STATE_ACTIVE: (self.bg[gtk.STATE_NORMAL].shade(1.00),
444 self.bg[gtk.STATE_NORMAL].shade(0.75)),
445
446 gtk.STATE_SELECTED: (self.bg[gtk.STATE_NORMAL].shade(1.09),
447 self.bg),
448
449 gtk.STATE_PRELIGHT: (self.bg[gtk.STATE_SELECTED].shade(1.35),
450 self.bg[gtk.STATE_SELECTED].shade(1.1)),
451
452 gtk.STATE_INSENSITIVE: (self.bg[gtk.STATE_INSENSITIVE],
453 self.bg[gtk.STATE_INSENSITIVE])
454 }
455 return palette
456
457 def get_text_palette(self):
458 palette = {gtk.STATE_NORMAL: self.text[gtk.STATE_NORMAL],
459 gtk.STATE_ACTIVE: self.text[gtk.STATE_NORMAL],
460 gtk.STATE_SELECTED: self.text[gtk.STATE_NORMAL],
461 gtk.STATE_PRELIGHT: self.text[gtk.STATE_PRELIGHT],
462 gtk.STATE_INSENSITIVE: self.text[gtk.STATE_INSENSITIVE]}
463 return palette
464
465 def get_dark_line_palette(self):
466 palette = {gtk.STATE_NORMAL: self.bg[gtk.STATE_NORMAL].darken(),
467 gtk.STATE_ACTIVE: self.bg[gtk.STATE_NORMAL].darken(),
468 gtk.STATE_PRELIGHT: self.bg[gtk.STATE_NORMAL].darken(),
469 gtk.STATE_SELECTED: self.bg[gtk.STATE_NORMAL].darken(),
470 gtk.STATE_INSENSITIVE: self.bg[gtk.STATE_INSENSITIVE].darken()}
471 return palette
472
473 def get_light_line_palette(self):
474 palette = {gtk.STATE_NORMAL: self.bg[gtk.STATE_NORMAL].lighten(),
475 gtk.STATE_ACTIVE: self.bg[gtk.STATE_ACTIVE].lighten(),
476 gtk.STATE_PRELIGHT: self.bg[gtk.STATE_NORMAL].lighten(),
477 gtk.STATE_SELECTED: self.bg[gtk.STATE_NORMAL].lighten(),
478 gtk.STATE_INSENSITIVE: self.bg[gtk.STATE_INSENSITIVE]}
479 return palette
480
481 def get_text_states(self):
482 states = {gtk.STATE_NORMAL: gtk.STATE_NORMAL,
483 gtk.STATE_ACTIVE: gtk.STATE_NORMAL,
484 gtk.STATE_PRELIGHT: gtk.STATE_NORMAL,
485 gtk.STATE_SELECTED: gtk.STATE_NORMAL,
486 gtk.STATE_INSENSITIVE: gtk.STATE_INSENSITIVE}
487 return states
488
489
490class DustSand(Theme):
491
492 def get_properties(self, gtksettings):
493 props = {
494 'curvature': 2.5,
495 'min_part_width': 48,
496 'xpad': 8,
497 'ypad': 4,
498 'xthickness': 1,
499 'ythickness': 1,
500 'spacing': 5,
501 'arrow_width': 13,
502 'scroll_duration': 150,
503 'enable-animations': gtksettings.get_property("gtk-enable-animations"),
504 'override_base': False
505 }
506 return props
507
508 def get_grad_palette(self):
509
510 selected_color = self.bg[gtk.STATE_NORMAL].mix(self.bg[gtk.STATE_SELECTED],
511 0.4)
512
513 prelight_color = self.bg[gtk.STATE_NORMAL].mix(self.bg[gtk.STATE_SELECTED],
514 0.175)
515
516 # provide two colours per state for background vertical linear gradients
517 palette = {gtk.STATE_NORMAL: (self.bg[gtk.STATE_NORMAL].shade(1.42),
518 self.bg[gtk.STATE_NORMAL].shade(1.1)),
519
520 gtk.STATE_ACTIVE: (prelight_color,
521 prelight_color.shade(1.07)),
522
523 gtk.STATE_SELECTED: (selected_color.shade(1.35),
524 selected_color.shade(1.1)),
525
526 gtk.STATE_PRELIGHT: (prelight_color.shade(1.74),
527 prelight_color.shade(1.42)),
528
529 gtk.STATE_INSENSITIVE: (self.bg[gtk.STATE_INSENSITIVE],
530 self.bg[gtk.STATE_INSENSITIVE])
531 }
532 return palette
533
534 def get_text_palette(self):
535 palette = {gtk.STATE_NORMAL: self.text[gtk.STATE_NORMAL],
536 gtk.STATE_ACTIVE: self.text[gtk.STATE_ACTIVE],
537 gtk.STATE_SELECTED: self.text[gtk.STATE_SELECTED],
538 gtk.STATE_PRELIGHT: self.text[gtk.STATE_PRELIGHT],
539 gtk.STATE_INSENSITIVE: self.text[gtk.STATE_INSENSITIVE]}
540 return palette
541
542 def get_dark_line_palette(self):
543 palette = {gtk.STATE_NORMAL: self.bg[gtk.STATE_NORMAL].shade(0.575),
544 gtk.STATE_ACTIVE: self.bg[gtk.STATE_ACTIVE].shade(0.5),
545 gtk.STATE_PRELIGHT: self.bg[gtk.STATE_PRELIGHT].shade(0.575),
546 gtk.STATE_SELECTED: self.bg[gtk.STATE_SELECTED].shade(0.575),
547 gtk.STATE_INSENSITIVE: self.bg[gtk.STATE_NORMAL].darken()}
548 return palette
549
550 def get_light_line_palette(self):
551 palette = {gtk.STATE_NORMAL: self.bg[gtk.STATE_NORMAL].lighten(),
552 gtk.STATE_ACTIVE: self.bg[gtk.STATE_ACTIVE].shade(0.95),
553 gtk.STATE_PRELIGHT: self.bg[gtk.STATE_PRELIGHT].lighten(),
554 gtk.STATE_SELECTED: self.bg[gtk.STATE_NORMAL].lighten(),
555 gtk.STATE_INSENSITIVE: self.bg[gtk.STATE_INSENSITIVE]}
556 return palette
557
558 def get_text_states(self):
559 states = {gtk.STATE_NORMAL: gtk.STATE_NORMAL,
560 gtk.STATE_ACTIVE: gtk.STATE_NORMAL,
561 gtk.STATE_PRELIGHT: gtk.STATE_NORMAL,
562 gtk.STATE_SELECTED: gtk.STATE_NORMAL,
563 gtk.STATE_INSENSITIVE: gtk.STATE_INSENSITIVE}
564 return states
565
566
567class Dust(DustSand):
568
569 def get_grad_palette(self):
570
571 selected_color = self.bg[gtk.STATE_NORMAL].mix(self.bg[gtk.STATE_SELECTED],
572 0.5)
573
574 prelight_color = self.bg[gtk.STATE_NORMAL].mix(self.bg[gtk.STATE_SELECTED],
575 0.175)
576
577 # provide two colours per state for background vertical linear gradients
578 palette = {gtk.STATE_NORMAL: (self.bg[gtk.STATE_NORMAL].shade(1.4),
579 self.bg[gtk.STATE_NORMAL].shade(1.1)),
580
581 gtk.STATE_ACTIVE: (self.bg[gtk.STATE_ACTIVE].shade(1.2),
582 self.bg[gtk.STATE_ACTIVE]),
583
584 gtk.STATE_SELECTED: (selected_color.shade(1.5),
585 selected_color.shade(1.2)),
586
587 gtk.STATE_PRELIGHT: (prelight_color.shade(1.74),
588 prelight_color.shade(1.42)),
589
590 gtk.STATE_INSENSITIVE: (self.bg[gtk.STATE_INSENSITIVE],
591 self.bg[gtk.STATE_INSENSITIVE])
592 }
593 return palette
594
595 def get_dark_line_palette(self):
596 palette = DustSand.get_dark_line_palette(self)
597 palette[gtk.STATE_SELECTED] = self.bg[gtk.STATE_NORMAL].shade(0.575)
598 return palette
599
600 def get_light_line_palette(self):
601 palette = DustSand.get_light_line_palette(self)
602 palette[gtk.STATE_SELECTED] = self.bg[gtk.STATE_NORMAL].shade(1.15)
603 return palette
604
605
606class Ambiance(DustSand):
607
608 def get_properties(self, gtksettings):
609 props = DustSand.get_properties(self, gtksettings)
610 props['curvature'] = 4.5
611 return props
612
613 def get_grad_palette(self):
614 focus_color = color_from_string('#FE765E')
615 selected_color = self.bg[gtk.STATE_NORMAL].mix(focus_color,
616 0.07)
617 prelight_color = self.bg[gtk.STATE_NORMAL].mix(focus_color,
618 0.33)
619
620 # provide two colours per state for background vertical linear gradients
621 palette = {gtk.STATE_NORMAL: (self.bg[gtk.STATE_NORMAL].shade(1.2),
622 self.bg[gtk.STATE_NORMAL].shade(0.85)),
623
624 gtk.STATE_ACTIVE: (self.bg[gtk.STATE_NORMAL].shade(0.96),
625 self.bg[gtk.STATE_NORMAL].shade(0.65)),
626
627 gtk.STATE_SELECTED: (selected_color.shade(1.075),
628 selected_color.shade(0.875)),
629
630 gtk.STATE_PRELIGHT: (prelight_color.shade(1.35),
631 prelight_color.shade(1.1)),
632
633 gtk.STATE_INSENSITIVE: (self.bg[gtk.STATE_INSENSITIVE],
634 self.bg[gtk.STATE_INSENSITIVE])
635 }
636 return palette
637
638
639class Radiance(Ambiance):
640
641 def get_grad_palette(self):
642 palette = Ambiance.get_grad_palette(self)
643 palette[gtk.STATE_NORMAL] = (self.mid[gtk.STATE_NORMAL].shade(1.25),
644 self.bg[gtk.STATE_NORMAL].shade(0.9))
645 return palette
646
647
648class NewWave(Theme):
649
650 def get_properties(self, gtksettings):
651 props = {
652 'curvature': 2,
653 'min_part_width': 48,
654 'xpad': 8,
655 'ypad': 4,
656 'xthickness': 1,
657 'ythickness': 1,
658 'spacing': 4,
659 'arrow_width': 13,
660 'scroll_duration': 150,
661 'enable-animations': gtksettings.get_property("gtk-enable-animations"),
662 'override_base': True
663 }
664 return props
665
666 def get_grad_palette(self):
667 # provide two colours per state for background vertical linear gradients
668
669 active_color = self.bg[gtk.STATE_ACTIVE].mix(color_from_string('#FDCF9D'),
670 0.45)
671
672 selected_color = self.bg[gtk.STATE_NORMAL].mix(color_from_string('#FDCF9D'),
673 0.2)
674
675 palette = {gtk.STATE_NORMAL: (self.bg[gtk.STATE_NORMAL].shade(1.1),
676 self.bg[gtk.STATE_NORMAL].shade(0.95)),
677
678 gtk.STATE_ACTIVE: (active_color.shade(1.1),
679 self.bg[gtk.STATE_ACTIVE].shade(0.95)),
680
681 gtk.STATE_PRELIGHT: (color_from_string('#FDCF9D'),
682 color_from_string('#FCAE87')),
683
684 gtk.STATE_SELECTED: (selected_color.shade(1.2),
685 selected_color),
686
687 gtk.STATE_INSENSITIVE: (self.bg[gtk.STATE_INSENSITIVE],
688 self.bg[gtk.STATE_INSENSITIVE])
689 }
690 return palette
691
692 def get_text_palette(self):
693 palette = {gtk.STATE_NORMAL: self.text[gtk.STATE_NORMAL],
694 gtk.STATE_ACTIVE: self.text[gtk.STATE_NORMAL],
695 gtk.STATE_PRELIGHT: self.text[gtk.STATE_NORMAL],
696 gtk.STATE_SELECTED: self.text[gtk.STATE_SELECTED],
697 gtk.STATE_INSENSITIVE: self.text[gtk.STATE_INSENSITIVE]}
698 return palette
699
700 def get_dark_line_palette(self):
701 palette = {gtk.STATE_NORMAL: self.bg[gtk.STATE_NORMAL].darken(),
702 gtk.STATE_ACTIVE: self.bg[gtk.STATE_NORMAL].darken(),
703 gtk.STATE_PRELIGHT: self.bg[gtk.STATE_NORMAL].darken(),
704 gtk.STATE_SELECTED: self.bg[gtk.STATE_NORMAL].darken(),
705 gtk.STATE_INSENSITIVE: self.bg[gtk.STATE_INSENSITIVE].darken()}
706 return palette
707
708 def get_light_line_palette(self):
709 palette = {gtk.STATE_NORMAL: self.bg[gtk.STATE_NORMAL].lighten(),
710 gtk.STATE_ACTIVE: self.bg[gtk.STATE_ACTIVE].shade(0.97),
711 gtk.STATE_PRELIGHT: color_from_string('#FDCF9D'),
712 gtk.STATE_SELECTED: self.bg[gtk.STATE_SELECTED].lighten(),
713 gtk.STATE_INSENSITIVE: self.bg[gtk.STATE_INSENSITIVE]}
714 return palette
715
716 def get_text_states(self):
717 states = {gtk.STATE_NORMAL: gtk.STATE_NORMAL,
718 gtk.STATE_ACTIVE: gtk.STATE_NORMAL,
719 gtk.STATE_PRELIGHT: gtk.STATE_NORMAL,
720 gtk.STATE_SELECTED: gtk.STATE_NORMAL,
721 gtk.STATE_INSENSITIVE: gtk.STATE_INSENSITIVE}
722 return states
723
724
725class Hicolor(Theme):
726
727 def get_properties(self, gtksettings):
728 props = {
729 'curvature': 0,
730 'min_part_width': 48,
731 'xpad': 15,
732 'ypad': 10,
733 'xthickness': 2,
734 'ythickness': 2,
735 'spacing': 10,
736 'arrow_width': 15,
737 'scroll_duration': 150,
738 'enable-animations': gtksettings.get_property("gtk-enable-animations"),
739 'override_base': False
740 }
741 return props
742
743 def get_grad_palette(self):
744 # provide two colours per state for background vertical linear gradients
745 palette = {gtk.STATE_NORMAL: (self.mid[gtk.STATE_NORMAL],
746 self.mid[gtk.STATE_NORMAL]),
747
748 gtk.STATE_ACTIVE: (self.mid[gtk.STATE_ACTIVE],
749 self.mid[gtk.STATE_ACTIVE]),
750
751 gtk.STATE_SELECTED: (self.mid[gtk.STATE_SELECTED],
752 self.mid[gtk.STATE_SELECTED]),
753
754 gtk.STATE_PRELIGHT: (self.mid[gtk.STATE_PRELIGHT],
755 self.mid[gtk.STATE_PRELIGHT]),
756
757 gtk.STATE_INSENSITIVE: (self.bg[gtk.STATE_INSENSITIVE],
758 self.bg[gtk.STATE_INSENSITIVE])
759 }
760 return palette
761
762 def get_text_palette(self):
763 palette = {gtk.STATE_NORMAL: self.text[gtk.STATE_NORMAL],
764 gtk.STATE_ACTIVE: self.text[gtk.STATE_ACTIVE],
765 gtk.STATE_SELECTED: self.text[gtk.STATE_SELECTED],
766 gtk.STATE_PRELIGHT: self.text[gtk.STATE_PRELIGHT],
767 gtk.STATE_INSENSITIVE: self.text[gtk.STATE_INSENSITIVE]}
768 return palette
769
770 def get_dark_line_palette(self):
771 palette = {gtk.STATE_NORMAL: self.bg[gtk.STATE_SELECTED],
772 gtk.STATE_ACTIVE: self.dark[gtk.STATE_ACTIVE],
773 gtk.STATE_PRELIGHT: self.dark[gtk.STATE_PRELIGHT],
774 gtk.STATE_SELECTED: self.dark[gtk.STATE_SELECTED],
775 gtk.STATE_INSENSITIVE: self.dark[gtk.STATE_INSENSITIVE]}
776 return palette
777
778 def get_light_line_palette(self):
779 palette = {gtk.STATE_NORMAL: self.bg[gtk.STATE_SELECTED],
780 gtk.STATE_ACTIVE: self.light[gtk.STATE_ACTIVE],
781 gtk.STATE_PRELIGHT: self.light[gtk.STATE_PRELIGHT],
782 gtk.STATE_SELECTED: self.light[gtk.STATE_SELECTED],
783 gtk.STATE_INSENSITIVE: self.light[gtk.STATE_INSENSITIVE]}
784 return palette
785
786 def get_text_states(self):
787 states = {gtk.STATE_NORMAL: gtk.STATE_NORMAL,
788 gtk.STATE_ACTIVE: gtk.STATE_ACTIVE,
789 gtk.STATE_PRELIGHT: gtk.STATE_PRELIGHT,
790 gtk.STATE_SELECTED: gtk.STATE_SELECTED,
791 gtk.STATE_INSENSITIVE: gtk.STATE_INSENSITIVE}
792 return states
793
794
795class ThemeRegistry:
796
797 REGISTRY = {"Human": Human,
798 "Human-Clearlooks": Clearlooks,
799 "Clearlooks": Clearlooks,
800 "InHuman": InHuman,
801 "HighContrastInverse": Hicolor,
802 "HighContrastLargePrintInverse": Hicolor,
803 "Dust": Dust,
804 "Dust Sand": DustSand,
805 "New Wave": NewWave,
806 "Ambiance": Ambiance,
807 "Radiance": Radiance}
808
809 def retrieve(self, theme_name):
810 if self.REGISTRY.has_key(theme_name):
811 print 'Styling hints found for %s...' % theme_name
812 return self.REGISTRY[theme_name]()
813 print "No styling hints for %s were found... using Human hints." % theme_name
814 return Clearlooks()
815
816
817def color_from_gdkcolor(gdkcolor):
818 return PathBarColor(gdkcolor.red_float, gdkcolor.green_float, gdkcolor.blue_float)
819
820
821def color_from_string(spec):
822 color = gtk.gdk.color_parse(spec)
823 return PathBarColor(color.red_float, color.green_float, color.blue_float)
824
825
826
827
0828
=== added file 'softwarecenter/view/widgets/pathbar_gtk_atk.py'
--- softwarecenter/view/widgets/pathbar_gtk_atk.py 1970-01-01 00:00:00 +0000
+++ softwarecenter/view/widgets/pathbar_gtk_atk.py 2010-04-04 06:00:32 +0000
@@ -0,0 +1,703 @@
1# Copyright (C) 2010 Matthew McGowan
2#
3# Authors:
4# Matthew McGowan
5#
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation, either version 3 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18
19import atk
20import cairo
21import gobject
22import gtk
23import pango
24import pathbar_common
25
26from gettext import gettext as _
27
28
29class PathBar(gtk.HBox):
30
31 ANIMATE_FPS = 50
32 ANIMATE_DELAY = 150
33 ANIMATE_DURATION = 150
34
35 def __init__(self, group=None):
36 gtk.HBox.__init__(self)
37 self.set_redraw_on_allocate(False)
38
39 self._width = 0
40 self._queue = []
41 self._active_part = None
42 self._out_of_width = False
43 self._button_press_origin = None
44
45 self._animate = False, None
46 self._scroll_xO = 0
47 self._no_draw = False
48 self._scroller = None
49
50 self.theme = pathbar_common.PathBarStyle(self)
51
52 # Accessibility info
53 atk_desc = self.get_accessible()
54 atk_desc.set_name(_("You are here:"))
55 atk_desc.set_role(atk.ROLE_PANEL)
56
57 self.set_events(gtk.gdk.EXPOSURE_MASK)
58 self.connect('expose-event', self._on_expose_event)
59 self.connect('size-allocate', self._on_size_allocate)
60 self.connect('style-set', self._on_style_set)
61 self.connect('realize', self._append_on_realize)
62 return
63
64 def _shrink_check(self, allocation):
65 path_w = self._width
66 overhang = path_w - allocation.width
67 self._width -= overhang
68 mpw = self.theme['min_part_width']
69 for part in self.get_children():
70 w = part.get_size_request()[0]
71 dw = 0
72 if w - overhang <= mpw:
73 overhang -= w-mpw
74 part.set_width(mpw)
75 else:
76 part.set_width(w-overhang)
77 break
78 self._out_of_width = True
79 self.queue_draw()
80 return
81
82 def _grow_check(self, allocation):
83 w_freed = allocation.width - self._width
84 parts = self.get_children()
85 parts.reverse()
86 for part in parts:
87 bw = part.get_best_width()
88 w = part.get_size_request()[0]
89 if w < bw:
90 dw = bw - w
91 if dw <= w_freed:
92 w_freed -= dw
93 part.restore_best_width()
94 else:
95 part.set_width(w + w_freed)
96 w_freed = 0
97 break
98
99 self._width = allocation.width - w_freed
100 if self._width < allocation.width:
101 self._out_of_width = False
102 self.queue_draw()
103 return
104
105 def _compose_on_append(self, last_part):
106 parts = self.get_children()
107 if len(parts) == 0:
108 last_part.set_shape(pathbar_common.SHAPE_RECTANGLE)
109 elif len(parts) == 1:
110 root_part = parts[0]
111 root_part.set_shape(pathbar_common.SHAPE_START_ARROW)
112 last_part.set_shape(pathbar_common.SHAPE_END_CAP)
113 else:
114 tail_part = parts[-1]
115 tail_part.set_shape(pathbar_common.SHAPE_MID_ARROW)
116 last_part.set_shape(pathbar_common.SHAPE_END_CAP)
117 return
118
119 def _compose_on_remove(self, last_part):
120 parts = self.get_children()
121 if len(parts) == 1:
122 last_part.set_shape(pathbar_common.SHAPE_RECTANGLE)
123 elif len(parts) == 2:
124 root_part = parts[0]
125 root_part.set_shape(pathbar_common.SHAPE_START_ARROW)
126 last_part.set_shape(pathbar_common.SHAPE_END_CAP)
127 else:
128 tail_part = parts[-1]
129 tail_part.set_shape(pathbar_common.SHAPE_MID_ARROW)
130 last_part.set_shape(pathbar_common.SHAPE_END_CAP)
131 return
132
133 def _part_enter_notify(self, part, event):
134 if part == self._button_press_origin:
135 part.set_state(gtk.STATE_ACTIVE)
136 else:
137 part.set_state(gtk.STATE_PRELIGHT)
138 self._part_queue_draw(part)
139 return
140
141 def _part_leave_notify(self, part, event):
142 if part == self._active_part:
143 part.set_state(gtk.STATE_SELECTED)
144 else:
145 part.set_state(gtk.STATE_NORMAL)
146 self._part_queue_draw(part)
147 return
148
149 def _part_button_press(self, part, event):
150 if event.button != 1: return
151 self._button_press_origin = part
152 part.set_state(gtk.STATE_ACTIVE)
153 self._part_queue_draw(part)
154 return
155
156 def _part_button_release(self, part, event):
157 if event.button != 1: return
158
159 part_region = gtk.gdk.region_rectangle(part.allocation)
160 if not part_region.point_in(*self.window.get_pointer()[:2]):
161 self._button_press_origin = None
162 return
163 if part != self._button_press_origin: return
164 if self._active_part:
165 self._active_part.set_state(gtk.STATE_NORMAL)
166 self._part_queue_draw(self._active_part)
167
168 self.set_active(part)
169 part.set_state(gtk.STATE_PRELIGHT)
170 self._button_press_origin = None
171 self._part_queue_draw(part)
172 return
173
174 def _part_key_press(self, part, event):
175 # react to spacebar, enter, numpad-enter
176 if event.keyval in (32, 65293, 65421):
177 part.set_state(gtk.STATE_ACTIVE)
178 self._part_queue_draw(part)
179 return
180
181 def _part_key_release(self, part, event):
182 # react to spacebar, enter, numpad-enter
183 if event.keyval in (32, 65293, 65421):
184 self.set_active(part)
185 part.set_state(gtk.STATE_SELECTED)
186 self._part_queue_draw(part)
187 return
188
189 def _part_focus_in(self, part, event):
190 self._part_queue_draw(part)
191 return
192
193 def _part_focus_out(self, part, event):
194 self._part_queue_draw(part)
195 return
196
197 def _part_connect_signals(self, part):
198 part.connect('enter-notify-event', self._part_enter_notify)
199 part.connect('leave-notify-event', self._part_leave_notify)
200 part.connect("button-press-event", self._part_button_press)
201 part.connect("button-release-event", self._part_button_release)
202 part.connect("key-press-event", self._part_key_press)
203 part.connect("key-release-event", self._part_key_release)
204 part.connect('focus-in-event', self._part_focus_in)
205 part.connect('focus-out-event', self._part_focus_out)
206 return
207
208 def _part_queue_draw(self, part):
209 a = part.get_allocation()
210 x, y, h = a.x, a.y, a.height
211 w = part.get_draw_width()
212 xo = part.get_draw_xoffset()
213 self.queue_draw_area(x+xo, y, w, h)
214 return
215
216 def _on_expose_event(self, widget, event):
217 if self._scroll_xO:
218 self._expose_scroll(widget, event)
219 else:
220 self._expose_normal(widget, event)
221 return
222
223 def _expose_normal(self, widget, event):
224 theme = self.theme
225 parts = self.get_children()
226 parts.reverse()
227 region = gtk.gdk.region_rectangle(event.area)
228
229 cr = widget.window.cairo_create()
230 cr.rectangle(event.area)
231 cr.clip()
232
233 for part in parts:
234 if not part.invisible:
235 a = part.get_allocation()
236 xo = part.get_draw_xoffset()
237 x, y, w, h = a.x, a.y, a.width, a.height
238 w = part.get_draw_width()
239 theme.paint_bg(cr, part, x+xo, y, w, h)
240
241 x, y, w, h = part.get_layout_points()
242
243 if part.has_focus():
244 self.style.paint_focus(self.window,
245 part.state,
246 (a.x+x-4, a.y+y-2, w+8, h+4),
247 self,
248 'button',
249 a.x+x-4, a.y+y-2, w+8, h+4)
250
251 theme.paint_layout(widget, part, a.x+x, a.y+y)
252 else:
253 part.invisible = False
254 del cr
255 return
256
257 def _expose_scroll(self, widget, event):
258 parts = self.get_children()
259 if len(parts) < 2: return
260 static_tail, scroller = parts[-2:]
261
262 if self.get_direction() != gtk.TEXT_DIR_RTL:
263 sxO = self._scroll_xO
264 else:
265 sxO = -self._scroll_xO
266
267 theme = self.theme
268
269 cr = widget.window.cairo_create()
270 cr.rectangle(event.area)
271 cr.clip()
272
273 a = scroller.get_allocation()
274 x, y, w, h = a.x, a.y, a.width, a.height
275 w = scroller.get_draw_width()
276 xo = scroller.get_draw_xoffset()
277 theme.paint_bg(cr, scroller, x+xo-sxO, y, w, h)
278 x, y, w, h = scroller.get_layout_points()
279 theme.paint_layout(widget, scroller, a.x+x-int(sxO), a.y+y)
280
281 a = static_tail.get_allocation()
282 x, y, w, h = a.x, a.y, a.width, a.height
283 w = static_tail.get_draw_width()
284 xo = static_tail.get_draw_xoffset()
285 theme.paint_bg(cr, static_tail, x+xo, y, w, h)
286 del cr
287 return
288
289 def _on_size_allocate(self, widget, allocation):
290 if self._width < allocation.width and self._out_of_width:
291 self._grow_check(allocation)
292 elif self._width >= allocation.width:
293 self._shrink_check(allocation)
294
295 if self._animate[0] and self.theme['enable-animations']:
296 part = self._animate[1]
297 part.invisible = True
298 self._animate = False, None
299 gobject.timeout_add(self.ANIMATE_DELAY, self._scroll_out_init, part)
300 else:
301 self.queue_draw()
302 return
303
304 def _on_style_set(self, widget, old_style):
305 self.theme = pathbar_common.PathBarStyle(self)
306 for part in self.get_children():
307 part.recalc_dimensions()
308 self.queue_draw()
309 return
310
311 def _append_on_realize(self, widget):
312 for part, do_callback, animate in self._queue:
313 self.append(part, do_callback, animate)
314 return
315
316 def _scroll_out_init(self, part):
317 draw_area = part.get_allocation()
318 self._scroller = gobject.timeout_add(
319 max(int(1000.0 / self.ANIMATE_FPS), 10), # interval
320 self._scroll_out_cb,
321 part.get_size_request()[0],
322 self.ANIMATE_DURATION*0.001, # 1 over duration (converted to seconds)
323 gobject.get_current_time(),
324 (draw_area.x, draw_area.y,
325 draw_area.width, draw_area.height))
326 return False
327
328 def _scroll_out_cb(self, distance, duration, start_t, draw_area):
329 cur_t = gobject.get_current_time()
330 xO = distance - distance*((cur_t - start_t) / duration)
331
332 if xO > 0:
333 self._scroll_xO = xO
334 self.queue_draw_area(*draw_area)
335
336 else: # final frame
337 self._scroll_xO = 0
338 # redraw the entire widget
339 # incase some timeouts are skipped due to high system load
340 self.queue_draw_area(*draw_area)
341 self._scroller = None
342 return False
343 return True
344
345 def has_parts(self):
346 return self.get_children() == True
347
348 def get_parts(self):
349 return self.get_children()
350
351 def get_last(self):
352 if self.get_children():
353 return self.get_children()[-1]
354 return None
355
356 def set_active(self, part, do_callback=True):
357 if part == self._active_part: return
358 if self._active_part:
359 self._active_part.set_state(gtk.STATE_NORMAL)
360 self._part_queue_draw(self._active_part)
361
362 part.set_state(gtk.STATE_SELECTED)
363 self._part_queue_draw(part)
364 self._active_part = part
365 if do_callback and part.callback:
366 part.callback(self, part)
367 return
368
369 def get_active(self):
370 return self._active_part
371
372 def set_active_no_callback(self, part):
373 self.set_active(part, False)
374 return
375
376 def append(self, part, do_callback=True, animate=True):
377 if not self.get_property('visible'):
378 self._queue.append([part, do_callback, animate])
379 return
380
381 if self._scroller:
382 gobject.source_remove(self._scroller)
383 self._scroll_xO = 0
384
385 self._compose_on_append(part)
386 self._width += part.get_size_request()[0]
387
388 self.pack_start(part, False)
389 self._part_connect_signals(part)
390 self._animate = animate, part
391 part.show()
392
393 if do_callback:
394 self.set_active(part)
395 else:
396 self.set_active_no_callback(part)
397 return
398
399 def append_no_callback(self, part):
400 self.append(part, do_callback=False)
401
402 def remove(self, part):
403 parts = self.get_children()
404 if len(parts) <= 1: return # protect last part
405 self._width -= part.get_size_request()[0]
406 part.destroy()
407 self._compose_on_remove(parts[-2])
408 return
409
410 def remove_all(self, keep_first_part=True, do_callback=True):
411 parts = self.get_children()
412 if len(parts) < 1: return
413 if keep_first_part:
414 if len(parts) <= 1: return
415 parts = parts[1:]
416 for part in parts:
417 part.destroy()
418
419 self._width = 0
420 if keep_first_part:
421 root = self.get_parts()[0]
422 root.set_shape(pathbar_common.SHAPE_RECTANGLE)
423 self._width = root.get_size_request()[0]
424 if do_callback: root.callback(self, root)
425 return
426
427 def navigate_up(self):
428 parts = self.get_children()
429 if len(parts) > 1:
430 nav_part = parts[len(parts) - 2]
431 self.set_active(nav_part)
432 return
433
434
435class PathPart(gtk.EventBox):
436
437 def __init__(self, parent, label, callback=None):
438 gtk.EventBox.__init__(self)
439 self.set_redraw_on_allocate(False)
440 self.set_visible_window(False)
441
442 part_atk = self.get_accessible()
443 part_atk.set_name(label)
444 part_atk.set_description(_('Navigates to the %s page.' % label))
445 part_atk.set_role(atk.ROLE_PUSH_BUTTON)
446
447 self.invisible = False
448 self._parent = parent
449 self._draw_shift = 0
450 self._draw_width = 0
451 self._best_width = 0
452 self._layout_points = 0,0,0,0
453 self._size_requisition = 0,0
454
455 self.label = None
456 self.shape = pathbar_common.SHAPE_RECTANGLE
457 self.layout = None
458 self.callback = callback
459 self.set_label(label)
460
461 self.set_flags(gtk.CAN_FOCUS)
462 self.set_events(gtk.gdk.BUTTON_PRESS_MASK|
463 gtk.gdk.BUTTON_RELEASE_MASK|
464 gtk.gdk.KEY_RELEASE_MASK|
465 gtk.gdk.KEY_PRESS_MASK|
466 gtk.gdk.ENTER_NOTIFY_MASK|
467 gtk.gdk.LEAVE_NOTIFY_MASK)
468 return
469
470 def __repr__(self):
471 return self.label
472
473 def _make_layout(self):
474 pc = self._parent.get_pango_context()
475 layout = pango.Layout(pc)
476 layout.set_markup(self.label)
477 layout.set_ellipsize(pango.ELLIPSIZE_END)
478 self.layout = layout
479 return
480
481 def _set_best_width(self, best_width):
482 self._best_width = best_width
483 return
484
485 def _calc_layout_points(self):
486 if not self.layout: self._make_layout()
487 x = self._parent.theme['xpad']
488 y = self._parent.theme['ypad']
489 w, h = self.layout.get_pixel_extents()[1][2:]
490 self._layout_points = [x, y, w, h]
491 return
492
493 def _adjust_width(self, shape, w):
494 self._draw_xoffset = 0
495 self._draw_width = w
496
497 arrow_width = self._parent.theme['arrow_width']
498 if shape == pathbar_common.SHAPE_RECTANGLE:
499 return w
500
501 elif shape == pathbar_common.SHAPE_START_ARROW:
502 self._draw_width += arrow_width
503 if self.get_direction() == gtk.TEXT_DIR_RTL:
504 self._draw_xoffset -= arrow_width
505
506 elif shape == pathbar_common.SHAPE_END_CAP:
507 w += arrow_width
508 self._draw_width += arrow_width
509 if self.get_direction() != gtk.TEXT_DIR_RTL:
510 self._layout_points[0] += arrow_width
511
512 elif shape == pathbar_common.SHAPE_MID_ARROW:
513 w += arrow_width
514 self._draw_width += 2*arrow_width
515 if self.get_direction() == gtk.TEXT_DIR_RTL:
516 self._draw_xoffset -= arrow_width
517 else:
518 self._layout_points[0] += arrow_width
519 return w
520
521 def _calc_size(self, shape):
522 lx, ly, w, h = self.layout.get_pixel_extents()[1]
523 w += 2*self._parent.theme['xpad']
524 h += 2*self._parent.theme['ypad']
525
526 w = self._adjust_width(shape, w)
527 if not self.get_best_width():
528 self._set_best_width(w)
529 self.set_size_request(w, h)
530 return
531
532 def do_callback(self):
533 self.callback(self._parent, self)
534 return
535
536 def set_label(self, label):
537 if label == self.label: return
538 self.label = gobject.markup_escape_text(label.strip())
539 if not self.layout:
540 self._make_layout()
541 else:
542 self.layout.set_markup(self.label)
543
544 self._calc_layout_points()
545 self._calc_size(self.shape)
546 return
547
548 def set_shape(self, shape):
549 self.shape = shape
550 self._calc_layout_points()
551 self._calc_size(shape)
552 return
553
554 def set_width(self, w):
555 theme = self._parent.theme
556 lw = w-theme['arrow_width']
557 if self.shape != pathbar_common.SHAPE_START_ARROW:
558 lw -= theme['xpad']
559 if self.shape == pathbar_common.SHAPE_MID_ARROW:
560 lw -= theme['arrow_width']
561 self.layout.set_width(lw*pango.SCALE)
562
563 self._draw_width = w+theme['arrow_width']
564 self.set_size_request(w, -1)
565 return
566
567 def restore_best_width(self):
568 w = self.get_best_width()
569 arrow_width = self._parent.theme['arrow_width']
570
571 if self.shape == pathbar_common.SHAPE_MID_ARROW:
572 w += arrow_width
573 if self.shape == pathbar_common.SHAPE_END_CAP and \
574 self.get_direction() == gtk.TEXT_DIR_RTL:
575 self._draw_xoffset -= arrow_width
576 self._layout_points[0] -= arrow_width
577
578 self.layout.set_width(-1)
579 self._draw_width = w+arrow_width
580 self.set_size_request(w, -1)
581 return
582
583 def get_draw_width(self):
584 return self._draw_width
585
586 def get_draw_xoffset(self):
587 return self._draw_xoffset
588
589 def get_best_width(self):
590 return self._best_width
591
592 def get_layout_points(self):
593 x, y, w, h = self._layout_points
594 y = int(max((self.allocation.height-h)*0.5+0.5, y))
595 return x, y, w, h
596
597 def get_layout(self):
598 return self.layout
599
600 def recalc_dimensions(self):
601 self.layout = None
602 self._calc_layout_points()
603 self._calc_size(self.shape)
604 return
605
606class NavigationBar(PathBar):
607
608 APPEND_DELAY = 150
609
610 def __init__(self, group=None):
611 PathBar.__init__(self)
612 self.id_to_part = {}
613 return
614
615 def add_with_id(self, label, callback, id, do_callback=True, animate=True):
616 """
617 Add a new button with the given label/callback
618
619 If there is the same id already, replace the existing one
620 with the new one
621 """
622
623 # check if we have the button of that id or need a new one
624 if id in self.id_to_part:
625 part = self.id_to_part[id]
626 part.set_label(label)
627 else:
628 part = PathPart(parent=self, label=label, callback=callback)
629 part.set_name(id)
630 self.id_to_part[id] = part
631 self.append(part, do_callback, animate)
632 return
633
634 def remove_id(self, id):
635 if not id in self.id_to_part:
636 return
637 part = self.id_to_part[id]
638 del self.id_to_part[id]
639 self.remove(part)
640 return
641
642 def remove_all(self, keep_first_part=True, do_callback=True):
643 if len(self.get_parts()) <= 1: return
644 root = self.get_children()[0]
645 self.id_to_part = {root.get_name(): root}
646 PathBar.remove_all(self, do_callback=do_callback)
647 return
648
649 def get_button_from_id(self, id):
650 """
651 return the button for the given id (or None)
652 """
653 if not id in self.id_to_part:
654 return None
655 return self.id_to_part[id]
656
657
658class Test:
659
660 def __init__(self):
661 self.counter = 0
662 w = gtk.Window()
663 w.connect("destroy", gtk.main_quit)
664 w.set_size_request(384, -1)
665 w.set_default_size(512, -1)
666 w.set_border_width(3)
667
668 vb = gtk.VBox()
669 w.add(vb)
670
671 pb = PathBar()
672 vb.pack_start(pb, False)
673 part = PathPart(pb, 'Get Free Software?')
674 pb.append(part)
675
676 add = gtk.Button(stock=gtk.STOCK_ADD)
677 rem = gtk.Button(stock=gtk.STOCK_REMOVE)
678 self.entry = gtk.Entry()
679
680 vb.pack_start(add, False)
681 vb.pack_start(self.entry, False)
682 vb.pack_start(rem, False)
683 add.connect('clicked', self.add_cb, pb)
684 rem.connect('clicked', self.rem_cb, pb)
685
686 w.show_all()
687 gtk.main()
688 return
689
690 def add_cb(self, widget, pb):
691 text = self.entry.get_text() or ('unnammed%s' % self.counter)
692 part = PathPart(pb, text)
693 pb.append(part)
694 self.counter += 1
695 return
696
697 def rem_cb(self, widget, pb):
698 last = pb.get_children()[-1]
699 pb.remove(last)
700 return
701
702if __name__ == '__main__':
703 Test()
0704
=== removed file 'softwarecenter/view/widgets/rgb.py'
--- softwarecenter/view/widgets/rgb.py 2009-10-09 13:44:41 +0000
+++ softwarecenter/view/widgets/rgb.py 1970-01-01 00:00:00 +0000
@@ -1,67 +0,0 @@
1# Copyright (C) 2009 Matthew McGowan
2#
3# Authors:
4# Matthew McGowan
5#
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation, either version 3 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18
19
20import colorsys
21from gtk.gdk import Color
22
23
24def parse_colour_scheme(colour_scheme_str):
25 scheme_dict = {}
26 for ln in colour_scheme_str.splitlines():
27 k, v = ln.split(':')
28 scheme_dict[k.strip()] = gtk.gdk.color_parse(v.strip())
29 return scheme_dict
30
31
32def shade(color, k):
33 # as seen in Murrine's cairo-support.c
34 r = color.red_float
35 g = color.green_float
36 b = color.blue_float
37
38 if (k == 1.0):
39 return color
40
41 h,l,s = colorsys.rgb_to_hls(r,g,b)
42
43 l *= k
44 if (l > 1.0):
45 l = 1.0
46 elif (l < 0.0):
47 l = 0.0
48
49 s *= k
50 if (s > 1.0):
51 s = 1.0
52 elif (s < 0.0):
53 s = 0.0
54
55 r, g, b = colorsys.hls_to_rgb(h,l,s)
56
57 return Color(int(r*65535), int(g*65535), int(b*65535))
58
59def mix_color(color1, color2, mix_factor):
60 # as seen in Murrine's cairo-support.c
61 r = color1.red_float*(1-mix_factor)+color2.red_float*mix_factor
62 g = color1.green_float*(1-mix_factor)+color2.green_float*mix_factor
63 b = color1.blue_float*(1-mix_factor)+color2.blue_float*mix_factor
64 return Color(int(r*65535), int(g*65535), int(b*65535))
65
66def to_float(color):
67 return color.red_float, color.green_float, color.blue_float