Merge lp:~kiwinote/software-center/less-is-more2 into lp:software-center

Proposed by Kiwinote
Status: Merged
Merged at revision: 2896
Proposed branch: lp:~kiwinote/software-center/less-is-more2
Merge into: lp:software-center
Diff against target: 2286 lines (+53/-2058)
13 files modified
debian/control (+0/-1)
po/POTFILES.in (+0/-2)
softwarecenter/backend/installbackend_impl/packagekit_enums.py (+0/-28)
softwarecenter/backend/installbackend_impl/packagekitd.py (+2/-3)
softwarecenter/backend/zeitgeist_simple.py (+0/-139)
softwarecenter/paths.py (+5/-14)
softwarecenter/toolkit.py (+0/-18)
softwarecenter/ui/gtk3/panes/unused__channelpane.py (+0/-316)
softwarecenter/ui/gtk3/shapes.py (+0/-257)
softwarecenter/ui/gtk3/views/appdetailsview.py (+0/-40)
softwarecenter/ui/gtk3/widgets/exhibits.py (+14/-4)
softwarecenter/ui/gtk3/widgets/stars.py (+32/-1)
softwarecenter/ui/gtk3/widgets/unused__pathbar.py (+0/-1235)
To merge this branch: bzr merge lp:~kiwinote/software-center/less-is-more2
Reviewer Review Type Date Requested Status
Michael Vogt Approve
Review via email: mp+98297@code.launchpad.net

Description of the change

this is the second part of less-is-more which is an initial run through the softwarecenter/ folder

To post a comment you must log in.
Revision history for this message
Michael Vogt (mvo) wrote :

Thanks, looks fantastic!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/control'
2--- debian/control 2012-02-07 15:19:12 +0000
3+++ debian/control 2012-03-19 22:53:18 +0000
4@@ -52,7 +52,6 @@
5 update-notifier,
6 software-properties-gtk,
7 sessioninstaller,
8- zeitgeist-core,
9 lzma
10 Conflicts: gnome-app-install (<< 1), oneconf (<< 0.2.6.1)
11 Replaces: gnome-app-install
12
13=== modified file 'po/POTFILES.in'
14--- po/POTFILES.in 2012-03-19 21:01:41 +0000
15+++ po/POTFILES.in 2012-03-19 22:53:18 +0000
16@@ -48,7 +48,6 @@
17 softwarecenter/ui/gtk3/panes/installedpane.py
18 softwarecenter/ui/gtk3/panes/pendingpane.py
19 softwarecenter/ui/gtk3/panes/softwarepane.py
20-softwarecenter/ui/gtk3/panes/unused__channelpane.py
21 softwarecenter/ui/gtk3/panes/viewswitcher.py
22 softwarecenter/ui/gtk3/views/appdetailsview.py
23 softwarecenter/ui/gtk3/views/appview.py
24@@ -65,7 +64,6 @@
25 softwarecenter/ui/gtk3/widgets/searchentry.py
26 softwarecenter/ui/gtk3/widgets/stars.py
27 softwarecenter/ui/gtk3/widgets/thumbnail.py
28-softwarecenter/ui/gtk3/widgets/unused__pathbar.py
29 softwarecenter/ui/gtk3/widgets/videoplayer.py
30 softwarecenter/ui/gtk3/widgets/weblivedialog.py
31 softwarecenter/ui/gtk3/widgets/recommendations.py
32
33=== removed file 'softwarecenter/backend/installbackend_impl/packagekit_enums.py'
34--- softwarecenter/backend/installbackend_impl/packagekit_enums.py 2012-03-16 20:12:57 +0000
35+++ softwarecenter/backend/installbackend_impl/packagekit_enums.py 1970-01-01 00:00:00 +0000
36@@ -1,28 +0,0 @@
37-# Copyright (C) 2007-2008 Richard Hughes <richard@hughsie.com>
38-# 2011 Giovanni Campagna <scampa.giovanni@gmail.com>
39-#
40-# This program is free software; you can redistribute it and/or modify it under
41-# the terms of the GNU General Public License as published by the Free Software
42-# Foundation; version 3.
43-#
44-# This program is distributed in the hope that it will be useful, but WITHOUT
45-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
46-# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
47-# details.
48-#
49-# You should have received a copy of the GNU General Public License along with
50-# this program; if not, write to the Free Software Foundation, Inc.,
51-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
52-
53-# stolen from gnome-packagekit, which is GPL2+
54-
55-from gi.repository import PackageKitGlib as packagekit
56-
57-
58-# this requires packagekit 0.7.2 or better
59-def status_enum_to_localised_text(status):
60- return packagekit.info_enum_to_localised_present(status)
61-
62-
63-def role_enum_to_localised_present(role):
64- return packagekit.role_enum_to_localised_present(role)
65
66=== modified file 'softwarecenter/backend/installbackend_impl/packagekitd.py'
67--- softwarecenter/backend/installbackend_impl/packagekitd.py 2012-03-16 20:12:57 +0000
68+++ softwarecenter/backend/installbackend_impl/packagekitd.py 2012-03-19 22:53:18 +0000
69@@ -31,7 +31,6 @@
70 TransactionProgress
71 )
72 from softwarecenter.backend.installbackend import InstallBackend
73-from softwarecenter.backend.installbackend_impl import packagekit_enums
74
75 # temporary, must think of better solution
76 from softwarecenter.db.pkginfo import get_pkg_info
77@@ -100,13 +99,13 @@
78 def get_role_description(self, role=None):
79 role = role if role is not None else self._trans.get_property('role')
80 return self.meta_data.get('sc_appname',
81- packagekit_enums.role_enum_to_localised_present(role))
82+ packagekit.role_enum_to_localised_present(role))
83
84 def get_status_description(self, status=None):
85 if status is None:
86 status = self._trans.get_property('status')
87
88- return packagekit_enums.status_enum_to_localised_text(status)
89+ return packagekit.info_enum_to_localised_present(status)
90
91 def is_waiting(self):
92 """ return true if a time consuming task is taking place """
93
94=== removed file 'softwarecenter/backend/zeitgeist_simple.py'
95--- softwarecenter/backend/zeitgeist_simple.py 2012-03-19 13:35:47 +0000
96+++ softwarecenter/backend/zeitgeist_simple.py 1970-01-01 00:00:00 +0000
97@@ -1,139 +0,0 @@
98-# Copyright (C) 2009-2010 Canonical
99-#
100-# Authors:
101-# Seif Lotfy
102-# Michael Vogt
103-#
104-# This program is free software; you can redistribute it and/or modify it under
105-# the terms of the GNU General Public License as published by the Free Software
106-# Foundation; version 3.
107-#
108-# This program is distributed in the hope that it will be useful, but WITHOUT
109-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
110-# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
111-# details.
112-#
113-# You should have received a copy of the GNU General Public License along with
114-# this program; if not, write to the Free Software Foundation, Inc.,
115-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
116-
117-import logging
118-import time
119-LOG = logging.getLogger("sofwarecenter.zeitgeist")
120-
121-try:
122- from zeitgeist.client import ZeitgeistClient
123- from zeitgeist.datamodel import Event, Interpretation, ResultType
124-except ImportError:
125- LOG.exception("zeitgeist import failed")
126- ZEITGEIST_AVAILABLE = False
127-else:
128- ZEITGEIST_AVAILABLE = True
129-
130-
131-class SoftwareCenterZeitgeist():
132- """ simple wrapper around zeitgeist """
133-
134- def __init__(self):
135- try:
136- self.zg_client = ZeitgeistClient()
137- except Exception as e:
138- logging.warn("can not get zeitgeist client: '%s'" % e)
139- self.zg_client = None
140-
141- def get_usage_counter(self, application, callback, timerange=None):
142- """Request the usage count as integer for the given application.
143- When the request is there, "callback" is called. A optional
144- timerange like [time.time(), time.time() - 30*24*60*60] can
145- also be specified
146- """
147- # helper
148- def _callback(event_ids):
149- callback(len(event_ids))
150- # no client or empty query -> empty result
151- if not self.zg_client or not application:
152- callback(0)
153- return
154- # the app we are looking for
155- application = "application://" + application.split("/")[-1]
156- # the event_templates
157- e1 = Event.new_for_values(
158- actor=application, interpretation=Interpretation.MODIFY_EVENT.uri)
159- e2 = Event.new_for_values(
160- actor=application, interpretation=Interpretation.CREATE_EVENT.uri)
161- # run it
162- self.zg_client.find_event_ids_for_templates(
163- [e1, e2], _callback, timerange=timerange, num_events=0)
164-
165- def get_popular_mimetypes(self, callback, num=3):
166- """ get the "num" (default to 3) most popular mimetypes based
167- on the last 1000 events that zeitgeist recorded and
168- call "callback" with [(count1, "mime1"), (count2, "mime2"), ...]
169- as arguement
170- """
171- def _callback(events):
172- # gather
173- mimetypes = {}
174- for event in events:
175- if event.subjects is None:
176- continue
177- mimetype = event.subjects[0].mimetype
178- if not mimetype in mimetypes:
179- mimetypes[mimetype] = 0
180- mimetypes[mimetype] += 1
181- # return early if empty
182- results = []
183- if not mimetypes:
184- callback([])
185- # convert to result and sort
186- for k, v in mimetypes.items():
187- results.append([v, k])
188- results.sort(reverse=True)
189- # tell the client about it
190- callback(results[:num])
191- # no zeitgeist
192- if not self.zg_client:
193- return
194- # trigger event (actual processing is done in _callback)
195- # FIXME: investigate how result_type MostRecentEvents or
196- # MostRecentSubjects would affect the results
197- self.zg_client.find_events_for_templates(
198- [], _callback, num_events=1000,
199- result_type=ResultType.MostRecentEvents)
200-
201-
202-class SoftwareCenterZeitgeistDummy():
203- def get_usage_counter(self, application, callback, timerange=None):
204- callback(0)
205-
206- def get_popular_mimetypes(self, callback):
207- callback([])
208-
209-# singleton
210-if ZEITGEIST_AVAILABLE:
211- zeitgeist_singleton = SoftwareCenterZeitgeist()
212-else:
213- zeitgeist_singleton = SoftwareCenterZeitgeistDummy()
214-
215-if __name__ == "__main__":
216-
217- def _callback_counter(events):
218- print("test _callback: %s" % events)
219- # all time gedit
220- zeitgeist_singleton.get_usage_counter("gedit.desktop", _callback_counter)
221-
222- # yesterday gedit
223- end = time.time()
224- start = end - 24 * 60 * 60
225- zeitgeist_singleton.get_usage_counter("gedit.desktop", _callback_counter,
226- timerange=[start, end])
227-
228- # most popular
229- def _callback_popular(mimetypes):
230- print("test _callback: ")
231- for tuple in mimetypes:
232- print(tuple)
233- zeitgeist_singleton.get_popular_mimetypes(_callback_popular)
234-
235- from gi.repository import Gtk
236- Gtk.main()
237
238=== modified file 'softwarecenter/paths.py'
239--- softwarecenter/paths.py 2012-03-15 22:36:31 +0000
240+++ softwarecenter/paths.py 2012-03-19 22:53:18 +0000
241@@ -34,8 +34,6 @@
242 # xdg_cache_home=os.path.expanduser("~/.cache"))
243 from xdg import BaseDirectory as xdg
244
245-from softwarecenter.toolkit import CURRENT_TOOLKIT, UIToolkits
246-
247 # global datadir, this maybe overriden at startup
248 datadir = "/usr/share/software-center/"
249
250@@ -70,18 +68,11 @@
251 # ratings&review
252 # relative to datadir
253 class RNRApps:
254- if CURRENT_TOOLKIT is UIToolkits.GTK2:
255- SUBMIT_REVIEW = "submit_review.py"
256- REPORT_REVIEW = "report_review.py"
257- SUBMIT_USEFULNESS = "submit_usefulness.py"
258- MODIFY_REVIEW = "modify_review.py"
259- DELETE_REVIEW = "delete_review.py"
260- elif CURRENT_TOOLKIT is UIToolkits.GTK3:
261- SUBMIT_REVIEW = "submit_review_gtk3.py"
262- REPORT_REVIEW = "report_review_gtk3.py"
263- SUBMIT_USEFULNESS = "submit_usefulness_gtk3.py"
264- MODIFY_REVIEW = "modify_review_gtk3.py"
265- DELETE_REVIEW = "delete_review_gtk3.py"
266+ SUBMIT_REVIEW = "submit_review_gtk3.py"
267+ REPORT_REVIEW = "report_review_gtk3.py"
268+ SUBMIT_USEFULNESS = "submit_usefulness_gtk3.py"
269+ MODIFY_REVIEW = "modify_review_gtk3.py"
270+ DELETE_REVIEW = "delete_review_gtk3.py"
271
272
273 # piston helpers
274
275=== removed file 'softwarecenter/toolkit.py'
276--- softwarecenter/toolkit.py 2012-03-15 10:43:13 +0000
277+++ softwarecenter/toolkit.py 1970-01-01 00:00:00 +0000
278@@ -1,18 +0,0 @@
279-import sys
280-
281-
282-class UIToolkits:
283- GTK2 = 0
284- GTK3 = 1
285- QML = 2
286- FALLBACK = GTK3
287-
288-
289-if 'software-center' in sys.argv[0]:
290- CURRENT_TOOLKIT = UIToolkits.GTK3
291-elif 'software-center-gtk2' in sys.argv[0]:
292- CURRENT_TOOLKIT = UIToolkits.GTK2
293-elif 'software-center-qml' in sys.argv[0]:
294- CURRENT_TOOLKIT = UIToolkits.QML
295-else:
296- CURRENT_TOOLKIT = UIToolkits.FALLBACK
297
298=== removed file 'softwarecenter/ui/gtk3/models/navlogstore.py'
299=== removed file 'softwarecenter/ui/gtk3/panes/unused__channelpane.py'
300--- softwarecenter/ui/gtk3/panes/unused__channelpane.py 2012-03-15 00:16:03 +0000
301+++ softwarecenter/ui/gtk3/panes/unused__channelpane.py 1970-01-01 00:00:00 +0000
302@@ -1,316 +0,0 @@
303-# Copyright (C) 2010 Canonical
304-#
305-# Authors:
306-# Michael Vogt
307-# Gary Lasker
308-#
309-# This program is free software; you can redistribute it and/or modify it under
310-# the terms of the GNU General Public License as published by the Free Software
311-# Foundation; version 3.
312-#
313-# This program is distributed in the hope that it will be useful, but WITHOUT
314-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
315-# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
316-# details.
317-#
318-# You should have received a copy of the GNU General Public License along with
319-# this program; if not, write to the Free Software Foundation, Inc.,
320-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
321-
322-import gettext
323-from gi.repository import Gtk
324-import logging
325-import os
326-import sys
327-import xapian
328-from gi.repository import GObject
329-
330-from gettext import gettext as _
331-
332-from softwarecenter.backend import get_install_backend
333-from softwarecenter.distro import get_distro
334-from softwarecenter.enums import NavButtons, NonAppVisibility
335-from softwarecenter.paths import XAPIAN_BASE_PATH
336-from softwarepane import SoftwarePane
337-from softwarecenter.ui.gtk3.views.appview import AppViewFilter
338-import softwarecenter.ui.gtk3.dialogs as dialogs
339-
340-LOG = logging.getLogger(__name__)
341-
342-
343-class ChannelPane(SoftwarePane):
344- """Widget that represents the channel pane for display of
345- individual channels (PPAs, partner repositories, etc.)
346- in software-center.
347- It contains a search entry and navigation buttons.
348- """
349-
350- (PAGE_APPLIST,
351- PAGE_APP_DETAILS,
352- PAGE_APP_PURCHASE) = range(3)
353-
354- __gsignals__ = {'channel-pane-created': (GObject.SignalFlags.RUN_FIRST,
355- None,
356- ())}
357-
358- def __init__(self, cache, db, distro, icons, datadir):
359- # parent
360- SoftwarePane.__init__(self, cache, db, distro, icons, datadir,
361- show_ratings=False)
362- self.channel = None
363- self.apps_filter = None
364- self.apps_search_term = ""
365- self.current_appview_selection = None
366- self.distro = get_distro()
367- self.pane_name = _("Software Channels")
368-
369- def init_view(self):
370- if not self.view_initialized:
371- SoftwarePane.init_view(self)
372- self.notebook.append_page(self.box_app_list,
373- Gtk.Label(label="channel"))
374- # details
375- self.notebook.append_page(self.scroll_details,
376- Gtk.Label(label="details"))
377- # purchase view
378- self.notebook.append_page(self.purchase_view,
379- Gtk.Label(label="purchase"))
380- # now we are initialized
381- self.emit("channel-pane-created")
382- self.show_all()
383- self.view_initialized = True
384-
385- def _show_channel_overview(self):
386- " helper that goes back to the overview page "
387- self.navigation_bar.remove_ids(NavButtons.DETAILS)
388- self.navigation_bar.remove_ids(NavButtons.PURCHASE)
389- self.notebook.set_current_page(self.PAGE_APPLIST)
390- self.searchentry.show()
391-
392- def _clear_search(self):
393- # remove the details and clear the search
394- self.searchentry.clear()
395- self.navigation_bar.remove_ids(NavButtons.SEARCH)
396-
397- def set_channel(self, channel):
398- """
399- set the current software channel object for display in the channel pane
400- and set up the AppViewFilter if required
401- """
402- self.channel = channel
403- # check to see if there is any section info that needs to be applied
404- # FIXME
405- #~ if channel._channel_color:
406- #~ self.section.set_color(channel._channel_color)
407- #~ if channel._channel_view_id:
408- #~ self.section.set_view_id(channel._channel_view_id)
409- #~ self.section_sync()
410-
411- # check if the channel needs to added
412- if channel.needs_adding and channel._source_entry:
413- dialog = Gtk.MessageDialog(flags=Gtk.DialogFlags.MODAL,
414- type=Gtk.MessageType.QUESTION)
415- dialog.set_title("")
416- dialog.set_markup("<big><b>%s</b></big>" % _("Add channel"))
417- dialog.format_secondary_text(_("The selected channel is not yet "
418- "added. Do you want to add it now?"))
419- dialog.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
420- Gtk.STOCK_ADD, Gtk.ResponseType.YES)
421- res = dialog.run()
422- dialog.destroy()
423- if res == Gtk.ResponseType.YES:
424- channel.needs_adding = False
425- backend = get_install_backend()
426- backend.add_sources_list_entry(channel._source_entry)
427- backend.emit("channels-changed", True)
428- backend.reload()
429- return
430- # normal operation
431- self.nonapps_visible = NonAppVisibility.MAYBE_VISIBLE
432- self.apps_filter = None
433- if self.channel.installed_only:
434- if self.apps_filter is None:
435- self.apps_filter = AppViewFilter(self.db, self.cache)
436- self.apps_filter.set_installed_only(True)
437- # switch to applist, this will clear searches too
438- self.display_list()
439-
440- def on_search_terms_changed(self, searchentry, terms):
441- """callback when the search entry widget changes"""
442- LOG.debug("on_search_terms_changed: '%s'" % terms)
443- self.apps_search_term = terms
444- if not self.apps_search_term:
445- self._clear_search()
446- self.refresh_apps()
447- self.notebook.set_current_page(self.PAGE_APPLIST)
448-
449- def on_db_reopen(self, db):
450- LOG.debug("got db-reopen signal")
451- self.refresh_apps()
452- self.app_details_view.refresh_app()
453-
454- def on_navigation_search(self, button, part):
455- """ callback when the navigation button with id 'search' is clicked"""
456- self.display_search()
457-
458- def on_navigation_list(self, button, part):
459- """callback when the navigation button with id 'list' is clicked"""
460- if not button.get_active():
461- return
462- self.display_list()
463-
464- def display_list(self):
465- self._clear_search()
466- self._show_channel_overview()
467- # only emit something if the model is there
468- model = self.app_view.get_model()
469- if model:
470- self.emit("app-list-changed", len(model))
471-
472- def on_navigation_details(self, button, part):
473- """callback when the navigation button with id 'details' is clicked"""
474- if not button.get_active():
475- return
476- self.display_details()
477-
478- def display_details(self):
479- self.navigation_bar.remove_ids(NavButtons.PURCHASE)
480- self.notebook.set_current_page(self.PAGE_APP_DETAILS)
481- self.searchentry.hide()
482- self.action_bar.clear()
483- # we want to re-enable the buy button if this is an app for purchase
484- # FIXME: hacky, find a better approach
485- button = self.app_details_view.pkg_statusbar.button
486- if button.get_label() == _(u'Buy\u2026'):
487- button.set_sensitive(True)
488-
489- def on_navigation_purchase(self, button, part):
490- """callback when the navigation button with id 'purchase' is clicked"""
491- if not button.get_active():
492- return
493- self.display_purchase()
494-
495- def display_purchase(self):
496- self.notebook.set_current_page(self.PAGE_APP_PURCHASE)
497- self.searchentry.hide()
498- self.action_bar.clear()
499-
500- def on_application_selected(self, appview, app):
501- """callback when an app is selected"""
502- LOG.debug("on_application_selected: '%s'" % app)
503- self.current_appview_selection = app
504-
505- def display_search(self):
506- self.navigation_bar.remove_ids(NavButtons.DETAILS)
507- self.navigation_bar.remove_ids(NavButtons.PURCHASE)
508- self.notebook.set_current_page(self.PAGE_APPLIST)
509- model = self.app_view.get_model()
510- if model:
511- length = len(self.app_view.get_model())
512- self.emit("app-list-changed", length)
513- self.searchentry.show()
514-
515- def get_status_text(self):
516- """return user readable status text suitable for a status bar"""
517- # no status text in the details page
518- if self.notebook.get_current_page() == self.PAGE_APP_DETAILS:
519- return ""
520- # otherwise, show status based on search or not
521- model = self.app_view.get_model()
522- if not model:
523- return ""
524- length = len(self.app_view.get_model())
525- if self.channel.installed_only:
526- if len(self.searchentry.get_text()) > 0:
527- return gettext.ngettext("%(amount)s matching item",
528- "%(amount)s matching items",
529- length) % {'amount': length}
530- else:
531- return gettext.ngettext("%(amount)s item installed",
532- "%(amount)s items installed",
533- length) % {'amount': length}
534- else:
535- if len(self.searchentry.get_text()) > 0:
536- return gettext.ngettext("%(amount)s matching item",
537- "%(amount)s matching items",
538- length) % {'amount': length}
539- else:
540- return gettext.ngettext("%(amount)s item available",
541- "%(amount)s items available",
542- length) % {'amount': length}
543-
544- def get_current_app(self):
545- """return the current active application object applicable
546- to the context"""
547- return self.current_appview_selection
548-
549- def is_category_view_showing(self):
550- # there is no category view in the channel pane
551- return False
552-
553- def is_applist_view_showing(self):
554- """Return True if we are in the applist view """
555- return self.notebook.get_current_page() == self.PAGE_APPLIST
556-
557- def is_app_details_view_showing(self):
558- """Return True if we are in the app_details view """
559- return self.notebook.get_current_page() == self.PAGE_APP_DETAILS
560-
561-if __name__ == "__main__":
562- #logging.basicConfig(level=logging.DEBUG)
563-
564- if len(sys.argv) > 1:
565- datadir = sys.argv[1]
566- elif os.path.exists("./data"):
567- datadir = "./data"
568- else:
569- datadir = "/usr/share/software-center"
570-
571- from softwarecenter.ui.gtk3.utils import get_sc_icon_theme
572- icons = get_sc_icon_theme(datadir)
573-
574- from softwarecenter.db.database import StoreDatabase
575- from softwarecenter.db.pkginfo import get_pkg_info
576- cache = get_pkg_info()
577- cache.open()
578-
579- # xapian
580- xapian_base_path = XAPIAN_BASE_PATH
581- pathname = os.path.join(xapian_base_path, "xapian")
582- try:
583- db = StoreDatabase(pathname, cache)
584- db.open()
585- except xapian.DatabaseOpeningError:
586- # Couldn't use that folder as a database
587- # This may be because we are in a bzr checkout and that
588- # folder is empty. If the folder is empty, and we can find the
589- # script that does population, populate a database in it.
590- if os.path.isdir(pathname) and not os.listdir(pathname):
591- from softwarecenter.db.update import rebuild_database
592- logging.info("building local database")
593- rebuild_database(pathname)
594- db = StoreDatabase(pathname, cache)
595- db.open()
596- except xapian.DatabaseCorruptError as e:
597- logging.exception("xapian open failed")
598- dialogs.error(None,
599- _("Sorry, can not open the software database"),
600- _("Please re-install the 'software-center' "
601- "package."))
602- # FIXME: force rebuild by providing a dbus service for this
603- sys.exit(1)
604-
605- import softwarecenter.distro
606- distro = softwarecenter.distro.get_distro()
607-
608- w = ChannelPane(cache, db, distro, icons, datadir)
609- w.show()
610-
611- win = Gtk.Window()
612- win.add(w)
613- w.init_view()
614- win.set_size_request(400, 600)
615- win.show_all()
616- win.connect("destroy", Gtk.main_quit)
617-
618- Gtk.main()
619
620=== removed file 'softwarecenter/ui/gtk3/shapes.py'
621--- softwarecenter/ui/gtk3/shapes.py 2012-03-15 09:32:18 +0000
622+++ softwarecenter/ui/gtk3/shapes.py 1970-01-01 00:00:00 +0000
623@@ -1,257 +0,0 @@
624-import gi
625-gi.require_version("Gtk", "3.0")
626-from gi.repository import Gtk
627-
628-
629-from math import sin, cos
630-
631-# pi constants
632-from math import pi as PI
633-PI_OVER_180 = PI / 180
634-
635-
636-def radian(deg):
637- return PI_OVER_180 * deg
638-
639-# directional shapes
640-
641-
642-class Shape:
643-
644- """ Base class for a Shape implementation.
645-
646- Currently implements a single method <layout> which is called
647- to layout the shape using cairo paths. It can also store the
648- 'direction' of the shape which should be on of the Gtk.TEXT_DIR
649- constants. Default 'direction' is Gtk.TextDirection.LTR.
650-
651- When implementing a Shape, there are two options available.
652-
653- If the Shape is direction dependent, the Shape MUST
654- implement <_layout_ltr> and <_layout_rtl> methods.
655-
656- If the Shape is not direction dependent, then it simply can
657- override the <layout> method.
658-
659- <layout> methods must take the following as arguments:
660-
661- cr : a CairoContext
662- x : x coordinate
663- y : y coordinate
664- w : width value
665- h : height value
666-
667- <layout> methods can then be passed Shape specific
668- keyword arguments which can be used as draw-time modifiers.
669- """
670-
671- def __init__(self, direction):
672- self.direction = direction
673-
674- def layout(self, cr, x, y, w, h, *args, **kwargs):
675- if self.direction != Gtk.TextDirection.RTL:
676- self._layout_ltr(cr, x, y, w, h, *args, **kwargs)
677- else:
678- self._layout_rtl(cr, x, y, w, h, *args, **kwargs)
679-
680-
681-class ShapeRoundedRectangle(Shape):
682-
683- """
684- RoundedRectangle lays out a rectangle with all four corners
685- rounded as specified at the layout call by the keyword argument:
686-
687- radius : an integer or float specifying the corner radius.
688- The radius must be > 0.
689-
690- RoundedRectangle is not direction sensitive.
691- """
692-
693- def __init__(self, direction=Gtk.TextDirection.LTR):
694- Shape.__init__(self, direction)
695-
696- def layout(self, cr, x, y, w, h, *args, **kwargs):
697- r = kwargs['radius']
698-
699- cr.new_sub_path()
700- cr.arc(r + x, r + y, r, PI, 270 * PI_OVER_180)
701- cr.arc(w - r, r + y, r, 270 * PI_OVER_180, 0)
702- cr.arc(w - r, h - r, r, 0, 90 * PI_OVER_180)
703- cr.arc(r + x, h - r, r, 90 * PI_OVER_180, PI)
704- cr.close_path()
705-
706-
707-class ShapeRoundedRectangleIrregular(Shape):
708-
709- """
710- RoundedRectangleIrregular lays out a rectangle for which each
711- individual corner can be rounded by a specific radius,
712- as specified at the layout call by the keyword argument:
713-
714- radii : a 4-tuple of ints or floats specifying the radius for
715- each corner. A value of 0 is acceptable as a radius, it
716- will result in a squared corner.
717-
718- RoundedRectangleIrregular is not direction sensitive.
719- """
720-
721- def __init__(self, direction=Gtk.TextDirection.LTR):
722- Shape.__init__(self, direction)
723-
724- def layout(self, cr, x, y, w, h, *args, **kwargs):
725- nw, ne, se, sw = kwargs['radii']
726-
727- cr.save()
728- cr.translate(x, y)
729- if nw:
730- cr.new_sub_path()
731- cr.arc(nw, nw, nw, PI, 270 * PI_OVER_180)
732- else:
733- cr.move_to(0, 0)
734- if ne:
735- cr.arc(w - ne, ne, ne, 270 * PI_OVER_180, 0)
736- else:
737- cr.rel_line_to(w - nw, 0)
738- if se:
739- cr.arc(w - se, h - se, se, 0, 90 * PI_OVER_180)
740- else:
741- cr.rel_line_to(0, h - ne)
742- if sw:
743- cr.arc(sw, h - sw, sw, 90 * PI_OVER_180, PI)
744- else:
745- cr.rel_line_to(-(w - se), 0)
746-
747- cr.close_path()
748- cr.restore()
749-
750-
751-class ShapeStartArrow(Shape):
752-
753- def __init__(self, direction=Gtk.TextDirection.LTR):
754- Shape.__init__(self, direction)
755-
756- def _layout_ltr(self, cr, x, y, w, h, *args, **kwargs):
757- aw = kwargs['arrow_width']
758- r = kwargs['radius']
759-
760- cr.new_sub_path()
761- cr.arc(r + x, r + y, r, PI, 270 * PI_OVER_180)
762- # arrow head
763- cr.line_to(w - aw, y)
764- cr.line_to(w - x + 1, (h + y) / 2)
765- cr.line_to(w - aw, h)
766- cr.arc(r + x, h - r, r, 90 * PI_OVER_180, PI)
767- cr.close_path()
768-
769- def _layout_rtl(self, cr, x, y, w, h, *args, **kwargs):
770- aw = kwargs['arrow_width']
771- r = kwargs['radius']
772-
773- cr.new_sub_path()
774- cr.move_to(x, (h + y) / 2)
775- cr.line_to(aw, y)
776- cr.arc(w - r, r + y, r, 270 * PI_OVER_180, 0)
777- cr.arc(w - r, h - r, r, 0, 90 * PI_OVER_180)
778- cr.line_to(aw, h)
779- cr.close_path()
780-
781-
782-class ShapeMidArrow(Shape):
783-
784- def __init__(self, direction=Gtk.TextDirection.LTR):
785- Shape.__init__(self, direction)
786-
787- def _layout_ltr(self, cr, x, y, w, h, *args, **kwargs):
788- aw = kwargs['arrow_width']
789-
790- cr.move_to(x, y)
791- # arrow head
792- cr.line_to(w - aw, y)
793- cr.line_to(w - x + 1, (h + y) / 2)
794- cr.line_to(w - aw, h)
795- cr.line_to(x, h)
796- cr.close_path()
797-
798- def _layout_rtl(self, cr, x, y, w, h, *args, **kwargs):
799- aw = kwargs['arrow_width']
800-
801- cr.move_to(x, (h + y) / 2)
802- cr.line_to(aw, y)
803- cr.line_to(w, y)
804- cr.line_to(w, h)
805- cr.line_to(aw, h)
806- cr.close_path()
807-
808-
809-class ShapeEndCap(Shape):
810-
811- def __init__(self, direction=Gtk.TextDirection.LTR):
812- Shape.__init__(self, direction)
813-
814- def _layout_ltr(self, cr, x, y, w, h, *args, **kwargs):
815- r = kwargs['radius']
816- aw = kwargs['arrow_width']
817-
818- cr.move_to(x - 1, y)
819- cr.arc(w - r, r + y, r, 270 * PI_OVER_180, 0)
820- cr.arc(w - r, h - r, r, 0, 90 * PI_OVER_180)
821- cr.line_to(x - 1, h)
822- cr.line_to(x + aw, (h + y) / 2)
823- cr.close_path()
824-
825- def _layout_rtl(self, cr, x, y, w, h, *args, **kwargs):
826- r = kwargs['radius']
827-
828- cr.arc(r + x, r + y, r, PI, 270 * PI_OVER_180)
829- cr.line_to(w, y)
830- cr.line_to(w, h)
831- cr.arc(r + x, h - r, r, 90 * PI_OVER_180, PI)
832- cr.close_path()
833-
834-
835-class Circle(Shape):
836-
837- def __init__(self, direction=Gtk.TextDirection.LTR):
838- Shape.__init__(self, direction)
839-
840- @staticmethod
841- def layout(cr, x, y, w, h, *args, **kwargs):
842- cr.new_path()
843-
844- r = min(w, h) * 0.5
845- x += int((w - 2 * r) / 2)
846- y += int((h - 2 * r) / 2)
847-
848- cr.arc(r + x, r + y, r, 0, 360 * PI_OVER_180)
849- cr.close_path()
850-
851-
852-class ShapeStar(Shape):
853-
854- def __init__(self, points, indent=0.61, direction=Gtk.TextDirection.LTR):
855- self.coords = self._calc_coords(points, 1 - indent)
856-
857- def _calc_coords(self, points, indent):
858- coords = []
859- step = radian(180.0 / points)
860-
861- for i in range(2 * points):
862- if i % 2:
863- x = (sin(step * i) + 1) * 0.5
864- y = (cos(step * i) + 1) * 0.5
865- else:
866- x = (sin(step * i) * indent + 1) * 0.5
867- y = (cos(step * i) * indent + 1) * 0.5
868-
869- coords.append((x, y))
870- return coords
871-
872- def layout(self, cr, x, y, w, h):
873- points = [(sx_sy[0] * w + x, sx_sy[1] * h + y)
874- for sx_sy in self.coords]
875- cr.move_to(*points[0])
876-
877- for p in points[1:]:
878- cr.line_to(*p)
879-
880- cr.close_path()
881
882=== modified file 'softwarecenter/ui/gtk3/views/appdetailsview.py'
883--- softwarecenter/ui/gtk3/views/appdetailsview.py 2012-03-15 21:47:29 +0000
884+++ softwarecenter/ui/gtk3/views/appdetailsview.py 2012-03-19 22:53:18 +0000
885@@ -37,7 +37,6 @@
886 from softwarecenter.db.application import Application
887 from softwarecenter.db import DebFileApplication
888 from softwarecenter.backend.reviews import ReviewStats
889-#from softwarecenter.backend.zeitgeist_simple import zeitgeist_singleton
890 from softwarecenter.enums import (AppActions,
891 PkgStates,
892 Icons,
893@@ -1165,10 +1164,6 @@
894 vb_inner.pack_start(self.title, False, False, 0)
895 vb_inner.pack_start(self.subtitle, False, False, 0)
896
897- # usage
898- #~ self.usage = mkit.BubbleLabel()
899- #~ vb_inner.pack_start(self.usage, True, True, 0)
900-
901 # star rating box/widget
902 self.review_stats_widget = StarRatingsWidget()
903 self.review_stats = Gtk.HBox()
904@@ -1539,9 +1534,6 @@
905 if not summary:
906 summary = ""
907
908- # hide stuff
909- #~ self.usage.hide()
910-
911 # depending on pkg install state set action labels
912 self.pkg_statusbar.configure(app_details, app_details.pkg_state)
913
914@@ -1570,9 +1562,6 @@
915 # show where it is
916 self._configure_where_is_it()
917
918- # async query zeitgeist and rnr
919- self._update_usage_counter()
920-
921 def _update_minimal(self, app_details):
922 self._update_app_icon(app_details)
923 self._update_pkg_info_table(app_details)
924@@ -2045,35 +2034,6 @@
925 def set_section(self, section):
926 self.section = section
927
928- def _update_usage_counter(self):
929- """ try to get the usage counter from zeitgeist """
930- def _zeitgeist_callback(counter):
931- LOG.debug("zeitgeist usage: %s" % counter)
932- if counter == 0:
933- # this probably means we just have no idea about it,
934- # so instead of saying "Used: never" we just return
935- # this can go away when zeitgeist captures more events
936- # --there are still cases when we really do want to hide this
937- self.usage.hide()
938- return
939- if counter <= 100:
940- label_string = gettext.ngettext("Used: one time",
941- "Used: %(amount)s times",
942- counter) % {'amount': counter}
943- else:
944- label_string = _("Used: over 100 times")
945- self.usage.set_text('<small>%s</small>' % label_string)
946- self.usage.show()
947-
948- # try to get it
949- # FIXME
950- # try:
951- # zeitgeist_singleton.get_usage_counter(
952- # self.app_details.desktop_file, _zeitgeist_callback)
953- # except Exception, e:
954- # LOG.warning("could not update the usage counter: %s " % e)
955- # self.usage.hide()
956-
957
958 def get_test_window_appdetails():
959
960
961=== modified file 'softwarecenter/ui/gtk3/widgets/exhibits.py'
962--- softwarecenter/ui/gtk3/widgets/exhibits.py 2012-03-09 12:50:38 +0000
963+++ softwarecenter/ui/gtk3/widgets/exhibits.py 2012-03-19 22:53:18 +0000
964@@ -32,7 +32,6 @@
965
966 from softwarecenter.utils import SimpleFileDownloader
967 from softwarecenter.ui.gtk3.em import StockEms
968-from softwarecenter.ui.gtk3.shapes import Circle
969 from softwarecenter.ui.gtk3.drawing import rounded_rect
970 from softwarecenter.ui.gtk3.utils import point_in
971 import softwarecenter.paths
972@@ -197,9 +196,20 @@
973 y = (a.height - ds_h) / 2
974 Gdk.cairo_set_source_pixbuf(cr, self._dropshadow, 0, y)
975 cr.paint()
976- Circle.layout(cr, self._margin, (a.height - ds_h) / 2 + self._margin,
977- a.width - 2 * self._margin,
978- a.width - 2 * self._margin)
979+
980+ # layout circle
981+ x = self._margin
982+ y = (a.height - ds_h) / 2 + self._margin
983+ w = a.width - 2 * self._margin
984+ h = a.width - 2 * self._margin
985+ cr.new_path()
986+ r = min(w, h) * 0.5
987+ x += int((w - 2 * r) / 2)
988+ y += int((h - 2 * r) / 2)
989+ from math import pi
990+ cr.arc(r + x, r + y, r, 0, 2 * pi)
991+ cr.close_path()
992+
993 if self.is_active:
994 color = context.get_background_color(Gtk.StateFlags.SELECTED)
995 else:
996
997=== modified file 'softwarecenter/ui/gtk3/widgets/stars.py'
998--- softwarecenter/ui/gtk3/widgets/stars.py 2012-03-08 17:42:36 +0000
999+++ softwarecenter/ui/gtk3/widgets/stars.py 2012-03-19 22:53:18 +0000
1000@@ -24,7 +24,6 @@
1001
1002 from gi.repository import Gtk, Gdk, GObject
1003
1004-from softwarecenter.ui.gtk3.shapes import ShapeStar
1005 from softwarecenter.ui.gtk3.em import StockEms, em, small_em, big_em
1006
1007
1008@@ -50,6 +49,38 @@
1009 REACTIVE = -1
1010
1011
1012+class ShapeStar():
1013+ def __init__(self, points, indent=0.61):
1014+ self.coords = self._calc_coords(points, 1 - indent)
1015+
1016+ def _calc_coords(self, points, indent):
1017+ coords = []
1018+
1019+ from math import cos, pi, sin
1020+ step = pi / points
1021+
1022+ for i in range(2 * points):
1023+ if i % 2:
1024+ x = (sin(step * i) + 1) * 0.5
1025+ y = (cos(step * i) + 1) * 0.5
1026+ else:
1027+ x = (sin(step * i) * indent + 1) * 0.5
1028+ y = (cos(step * i) * indent + 1) * 0.5
1029+
1030+ coords.append((x, y))
1031+ return coords
1032+
1033+ def layout(self, cr, x, y, w, h):
1034+ points = [(sx_sy[0] * w + x, sx_sy[1] * h + y)
1035+ for sx_sy in self.coords]
1036+ cr.move_to(*points[0])
1037+
1038+ for p in points[1:]:
1039+ cr.line_to(*p)
1040+
1041+ cr.close_path()
1042+
1043+
1044 class StarRenderer(ShapeStar):
1045
1046 def __init__(self):
1047
1048=== removed file 'softwarecenter/ui/gtk3/widgets/unused__pathbar.py'
1049--- softwarecenter/ui/gtk3/widgets/unused__pathbar.py 2012-03-08 14:09:41 +0000
1050+++ softwarecenter/ui/gtk3/widgets/unused__pathbar.py 1970-01-01 00:00:00 +0000
1051@@ -1,1235 +0,0 @@
1052-from gi.repository import Atk
1053-from gi.repository import Gtk, Gdk
1054-from gi.repository import GObject
1055-from gi.repository import Pango
1056-
1057-from softwarecenter.ui.gtk3.em import em
1058-
1059-from gettext import gettext as _
1060-
1061-import logging
1062-LOG = logging.getLogger("softwarecenter.view.widgets.NavigationBar")
1063-
1064-# pi constants
1065-from math import pi
1066-
1067-PI = pi
1068-PI_OVER_180 = pi / 180
1069-
1070-
1071-class Shape:
1072-
1073- """ Base class for a Shape implementation.
1074-
1075- Currently implements a single method <layout> which is called
1076- to layout the shape using cairo paths. It can also store the
1077- 'direction' of the shape which should be on of the Gtk.TEXT_DIR
1078- constants. Default 'direction' is Gtk.TextDirection.LTR.
1079-
1080- When implementing a Shape, there are two options available.
1081-
1082- If the Shape is direction dependent, the Shape MUST
1083- implement <_layout_ltr> and <_layout_rtl> methods.
1084-
1085- If the Shape is not direction dependent, then it simply can
1086- override the <layout> method.
1087-
1088- <layout> methods must take the following as arguments:
1089-
1090- cr : a CairoContext
1091- x : x coordinate
1092- y : y coordinate
1093- w : width value
1094- h : height value
1095-
1096- <layout> methods can then be passed Shape specific
1097- keyword arguments which can be used as paint-time modifiers.
1098- """
1099-
1100- def __init__(self, direction):
1101- self.direction = direction
1102- self.name = 'Shapeless'
1103- self.hadjustment = 0
1104- self._color = 1, 0, 0
1105-
1106- def __eq__(self, other):
1107- return self.name == other.name
1108-
1109- def layout(self, cr, x, y, w, h, r, aw):
1110- if self.direction != Gtk.TextDirection.RTL:
1111- self._layout_ltr(cr, x, y, w, h, r, aw)
1112- else:
1113- self._layout_rtl(cr, x, y, w, h, r, aw)
1114-
1115-
1116-class ShapeRoundedRect(Shape):
1117-
1118- """
1119- RoundedRect lays out a rectangle with all four corners
1120- rounded as specified at the layout call by the keyword argument:
1121-
1122- radius : an integer or float specifying the corner radius.
1123- The radius must be > 0.
1124-
1125- RoundedRectangle is not direction sensitive.
1126- """
1127-
1128- def __init__(self, direction=Gtk.TextDirection.LTR):
1129- Shape.__init__(self, direction)
1130- self.name = 'RoundedRect'
1131-
1132- def layout(self, cr, x, y, w, h, r, aw):
1133- cr.new_sub_path()
1134- cr.arc(r + x, r + y, r, PI, 270 * PI_OVER_180)
1135- cr.arc(x + w - r, r + y, r, 270 * PI_OVER_180, 0)
1136- cr.arc(x + w - r, y + h - r, r, 0, 90 * PI_OVER_180)
1137- cr.arc(r + x, y + h - r, r, 90 * PI_OVER_180, PI)
1138- cr.close_path()
1139-
1140-
1141-class ShapeStartArrow(Shape):
1142-
1143- def __init__(self, direction=Gtk.TextDirection.LTR):
1144- Shape.__init__(self, direction)
1145- self.name = 'StartArrow'
1146-
1147- def _layout_ltr(self, cr, x, y, w, h, r, aw):
1148- haw = aw / 2
1149-
1150- cr.new_sub_path()
1151- cr.arc(r + x, r + y, r, PI, 270 * PI_OVER_180)
1152-
1153- # arrow head
1154- cr.line_to(x + w - haw, y)
1155- cr.line_to(x + w + haw, y + (h / 2))
1156- cr.line_to(x + w - haw, y + h)
1157-
1158- cr.arc(r + x, y + h - r, r, 90 * PI_OVER_180, PI)
1159- cr.close_path()
1160-
1161- def _layout_rtl(self, cr, x, y, w, h, r, aw):
1162- haw = aw / 2
1163-
1164- cr.new_sub_path()
1165- cr.move_to(x - haw, (y + h) / 2)
1166- cr.line_to(x + aw - haw, y)
1167- cr.arc(x + w - r, r + y, r, 270 * PI_OVER_180, 0)
1168- cr.arc(x + w - r, y + h - r, r, 0, 90 * PI_OVER_180)
1169- cr.line_to(x + aw - haw, y + h)
1170- cr.close_path()
1171-
1172-
1173-class ShapeMidArrow(Shape):
1174-
1175- def __init__(self, direction=Gtk.TextDirection.LTR):
1176- Shape.__init__(self, direction)
1177- #~ self.draw_xoffset = -2
1178- self._color = 0, 1, 0
1179- self.name = 'MidArrow'
1180-
1181- def _layout_ltr(self, cr, x, y, w, h, r, aw):
1182- self.hadjustment = haw = aw / 2
1183- cr.move_to(x - haw - 1, y)
1184- # arrow head
1185- cr.line_to(x + w - haw, y)
1186- cr.line_to(x + w + haw, y + (h / 2))
1187- cr.line_to(x + w - haw, y + h)
1188- cr.line_to(x - haw - 1, y + h)
1189-
1190- cr.line_to(x + haw - 1, y + (h / 2))
1191-
1192- cr.close_path()
1193-
1194- def _layout_rtl(self, cr, x, y, w, h, r, aw):
1195- self.hadjustment = haw = -aw / 2
1196-
1197- cr.move_to(x + haw, (h + y) / 2)
1198- cr.line_to(x + aw + haw, y)
1199- cr.line_to(x + w - haw + 1, y)
1200- cr.line_to(x + w - aw - haw + 1, (y + h) / 2)
1201- cr.line_to(x + w - haw + 1, y + h)
1202- cr.line_to(x + aw + haw, y + h)
1203- cr.close_path()
1204-
1205-
1206-class ShapeEndCap(Shape):
1207-
1208- def __init__(self, direction=Gtk.TextDirection.LTR):
1209- Shape.__init__(self, direction)
1210- #~ self.draw_xoffset = -2
1211- self._color = 0, 0, 1
1212- self.name = 'EndCap'
1213-
1214- def _layout_ltr(self, cr, x, y, w, h, r, aw):
1215- self.hadjustment = haw = aw / 2
1216-
1217- cr.move_to(x - haw - 1, y)
1218- # rounded end
1219- cr.arc(x + w - r, r + y, r, 270 * PI_OVER_180, 0)
1220- cr.arc(x + w - r, y + h - r, r, 0, 90 * PI_OVER_180)
1221- # arrow
1222- cr.line_to(x - haw - 1, y + h)
1223- cr.line_to(x + haw - 1, y + (h / 2))
1224- cr.close_path()
1225-
1226- def _layout_rtl(self, cr, x, y, w, h, r, aw):
1227- self.hadjustment = haw = -aw / 2
1228-
1229- cr.arc(r + x, r + y, r, PI, 270 * PI_OVER_180)
1230- cr.line_to(x + w - haw + 1, y)
1231- cr.line_to(x + w - haw - aw + 1, (y + h) / 2)
1232- cr.line_to(x + w - haw + 1, y + h)
1233- cr.arc(r + x, y + h - r, r, 90 * PI_OVER_180, PI)
1234- cr.close_path()
1235-
1236-
1237-class AnimationClock(GObject.GObject):
1238- _1SECOND = 1000
1239- __gsignals__ = {
1240- "animation-frame": (GObject.SignalFlags.RUN_LAST,
1241- None,
1242- (float,),),
1243-
1244- "animation-finished": (GObject.SignalFlags.RUN_FIRST,
1245- None,
1246- (bool,),),
1247- }
1248-
1249- def __init__(self, fps, duration):
1250- GObject.GObject.__init__(self)
1251-
1252- self.fps = fps
1253- self.in_progress = False
1254- self.set_duration(duration)
1255-
1256- self._clock = None
1257- self._progress = 0 # progress as an msec offset
1258-
1259- def _get_timstep(self):
1260- d = self.duration
1261- return max(10, int(d / ((d / AnimationClock._1SECOND) * self.fps)))
1262-
1263- def _schedule_animation_frame(self):
1264- if self._progress > self.duration:
1265- self._clock = None
1266- self.in_progress = False
1267- self.emit('animation-finished', False)
1268- return False
1269-
1270- self._progress += self._timestep
1271- self.emit('animation-frame', self.progress)
1272- return True
1273-
1274- @property
1275- def progress(self):
1276- return min(1.0, self._progress / self.duration)
1277-
1278- def set_duration(self, duration):
1279- self.duration = float(duration)
1280- self._timestep = self._get_timstep()
1281-
1282- def stop(self, who_called='?'):
1283-
1284- if self._clock:
1285- #~ print who_called+'.Stop'
1286- GObject.source_remove(self._clock)
1287- self.emit('animation-finished', True)
1288-
1289- self._clock = None
1290- self._progress = 0
1291- self.in_progress = False
1292-
1293- def start(self):
1294- self.stop(who_called='start')
1295- if not self.sequence:
1296- return
1297-
1298- self._clock = GObject.timeout_add(self._timestep,
1299- self._schedule_animation_frame,
1300- priority=100)
1301- self.in_progress = True
1302-
1303-
1304-class PathBarAnimator(AnimationClock):
1305- # animation display constants
1306- FPS = 50
1307- DURATION = 150 # spec says 150ms
1308-
1309- # animation modes
1310- NONE = 'animation-none'
1311- OUT = 'animation-out'
1312- IN = 'animation-in'
1313- WIDTH_CHANGE = 'animation-width-change'
1314-
1315- def __init__(self, pathbar):
1316- AnimationClock.__init__(self, self.FPS, self.DURATION)
1317-
1318- self.pathbar = pathbar
1319- self.sequence = []
1320-
1321- self.connect('animation-frame', self._on_animation_frame)
1322- self.connect('animation-finished', self._on_animation_finished)
1323-
1324- def _animate_out(self, part, progress, kwargs):
1325- real_alloc = part.get_allocation()
1326- xo = real_alloc.width - int(real_alloc.width * progress)
1327-
1328- if self.pathbar.get_direction() == Gtk.TextDirection.RTL:
1329- xo *= -1
1330-
1331- anim_alloc = Gdk.Rectangle()
1332- anim_alloc.x = real_alloc.x - xo
1333- anim_alloc.y = real_alloc.y
1334- anim_alloc.width = real_alloc.width
1335- anim_alloc.height = real_alloc.height
1336-
1337- part.new_frame(anim_alloc)
1338-
1339- def _animate_in(self, part, progress, kwargs):
1340- real_alloc = part.get_allocation()
1341- xo = int(real_alloc.width * progress)
1342-
1343- if self.pathbar.get_direction() == Gtk.TextDirection.RTL:
1344- xo *= -1
1345-
1346- anim_alloc = Gdk.Rectangle()
1347- anim_alloc.x = real_alloc.x - xo
1348- anim_alloc.y = real_alloc.y
1349- anim_alloc.width = real_alloc.width
1350- anim_alloc.height = real_alloc.height
1351-
1352- part.new_frame(anim_alloc)
1353-
1354- def _animate_width_change(self, part, progress, kwargs):
1355- start_w = kwargs['start_width']
1356- end_w = kwargs['end_width']
1357-
1358- width = int(round(start_w + (end_w - start_w) * progress))
1359- part.set_size_request(width, part.get_height_request())
1360-
1361- def _on_animation_frame(self, clock, progress):
1362- if not self.sequence:
1363- return
1364-
1365- for actor, animation, kwargs in self.sequence:
1366- if animation == PathBarAnimator.NONE:
1367- continue
1368-
1369- if animation == PathBarAnimator.OUT:
1370- self._animate_out(actor, progress, kwargs)
1371-
1372- elif animation == PathBarAnimator.IN:
1373- self._animate_in(actor, progress, kwargs)
1374-
1375- elif animation == PathBarAnimator.WIDTH_CHANGE:
1376- self._animate_width_change(actor, progress, kwargs)
1377-
1378- def _on_animation_finished(self, clock, interrupted):
1379- for actor, animation, kwargs in self.sequence:
1380- actor.animation_finished()
1381-
1382- self.sequence = []
1383- self.pathbar.psuedo_parts = []
1384- self.pathbar.queue_draw()
1385-
1386- def append_animation(self, actor, animation, **kwargs):
1387- self.sequence.append((actor, animation, kwargs))
1388-
1389- def reset(self, who_called='?'):
1390- AnimationClock.stop(self, who_called=who_called + '.reset')
1391- self.sequence = []
1392-
1393-
1394-class PathBar(Gtk.HBox):
1395-
1396- MIN_PART_WIDTH = 25 # pixels
1397-
1398- def __init__(self):
1399- GObject.GObject.__init__(self)
1400- self.set_redraw_on_allocate(False)
1401- self.set_size_request(-1, em(1.75))
1402- self._allocation = None
1403-
1404- # Accessibility info
1405- atk_desc = self.get_accessible()
1406- atk_desc.set_name(_("You are here:"))
1407- atk_desc.set_role(Atk.Role.PANEL)
1408-
1409- self.use_animations = True
1410- self.animator = PathBarAnimator(self)
1411-
1412- self.out_of_width = False
1413- self.psuedo_parts = []
1414-
1415- # used for certain button press logic
1416- self._press_origin = None
1417- # tracks the id of the revealer timeout
1418- self._revealer = None
1419-
1420- # values derived from the gtk settings
1421- s = Gtk.Settings.get_default()
1422- # time to wait before revealing a part on enter event in ms
1423- self._timeout_reveal = s.get_property("gtk-tooltip-timeout")
1424- # time to wait until emitting click event in ms
1425- self._timeout_initial = s.get_property("gtk-timeout-initial")
1426-
1427- # les signales!
1428- self.connect('size-allocate', self._on_allocate)
1429- self.connect('draw', self._on_draw)
1430-
1431- # sugar
1432- def __len__(self):
1433- return len(self.get_children())
1434-
1435- def __getitem__(self, index):
1436- return self.get_children()[index]
1437-
1438- # signal handlers
1439- def _on_allocate(self, widget, _):
1440- allocation = self.get_allocation()
1441-
1442- if self._allocation == allocation:
1443- return True
1444-
1445- # prevent vertical bobby when the searchentry is shown/hidden
1446- if allocation.height > self.get_property('height-request'):
1447- self.set_property('height-request', allocation.height)
1448-
1449- if not self._allocation:
1450- self._allocation = allocation
1451- self.queue_draw()
1452- return True
1453-
1454- pthbr_width = allocation.width
1455- parts_width = self.get_parts_width()
1456-
1457- #~ print parts_width, pthbr_width
1458-
1459- #~ self.animator.reset('on_allocate')
1460- self.set_use_animations(True)
1461-
1462- if pthbr_width > parts_width and self.out_of_width:
1463- dw = pthbr_width - parts_width
1464- self._grow_parts(dw)
1465-
1466- elif pthbr_width < parts_width:
1467- overhang = parts_width - pthbr_width
1468- if overhang > 0:
1469- self.set_use_animations(False)
1470- self._shrink_parts(overhang)
1471-
1472- self._allocation = allocation
1473- if self.use_animations and self.animator.sequence and not \
1474- self.animator.in_progress:
1475- self.animator.start()
1476- else:
1477- self.queue_draw()
1478-
1479- def _on_draw(self, widget, cr):
1480- # always paint psuedo parts first
1481- a = self.get_allocation()
1482- context = self.get_style_context()
1483- context.save()
1484- context.add_class("button")
1485-
1486- self._paint_psuedo_parts(cr, context, a.x, a.y)
1487-
1488- # paint a frame around the entire pathbar
1489- width = self.get_parts_width()
1490- Gtk.render_background(context, cr, 1, 1, width - 2, a.height - 2)
1491-
1492- self._paint_widget_parts(cr, context, a.x, a.y)
1493-
1494- Gtk.render_frame(context, cr, 0, 0, width, a.height)
1495- context.restore()
1496- return True
1497-
1498- # private methods
1499- def _paint_widget_parts(self, cr, context, xo, yo):
1500- parts = self.get_children()
1501- # paint in reverse order, so we get correct overlapping during
1502- # animation
1503- parts.reverse()
1504- for part in parts:
1505- part.paint(cr,
1506- part.animation_allocation or part.get_allocation(),
1507- context,
1508- xo, yo)
1509-
1510- def _paint_psuedo_parts(self, cr, context, xo, yo):
1511- # a special case: paint psuedo parts paint first,
1512- # i.e those parts animating 'in' on their removal
1513- for part in self.psuedo_parts:
1514- part.paint(cr,
1515- part.animation_allocation or part.get_allocation(),
1516- context,
1517- xo, yo)
1518-
1519- def _shrink_parts(self, overhang):
1520- self.out_of_width = True
1521-
1522- for part in self:
1523- old_width = part.get_width_request()
1524- new_width = max(self.MIN_PART_WIDTH, old_width - overhang)
1525-
1526- if False: # self.use_animations:
1527- self.animator.append_animation(part,
1528- PathBarAnimator.WIDTH_CHANGE,
1529- start_width=old_width,
1530- end_width=new_width)
1531- else:
1532- part.set_size_request(new_width,
1533- part.get_height_request())
1534-
1535- overhang -= old_width - new_width
1536- if overhang <= 0:
1537- break
1538-
1539- def _grow_parts(self, claim):
1540- children = self.get_children()
1541- children.reverse()
1542-
1543- for part in children:
1544-
1545- if part.get_allocation().width == part.get_natural_width():
1546- continue
1547-
1548- growth = min(claim, (part.get_natural_width() - part.width))
1549- if growth <= 0:
1550- break
1551-
1552- claim -= growth
1553-
1554- if self.use_animations:
1555- self.animator.append_animation(part,
1556- PathBarAnimator.WIDTH_CHANGE,
1557- start_width=part.width,
1558- end_width=part.width + growth)
1559- else:
1560- part.set_size_request(part.width + growth,
1561- part.get_height_request())
1562-
1563- def _make_space(self, part):
1564- children = self.get_children()
1565- if not children:
1566- return
1567-
1568- cur_width = self.get_parts_width()
1569- incomming_width = cur_width + part.get_width_request()
1570- overhang = incomming_width - self.get_allocation().width
1571-
1572- if overhang > 0:
1573- print 'shrink parts by:', overhang
1574- self._shrink_parts(overhang)
1575-
1576- def _reclaim_space(self, part):
1577- if not self.out_of_width:
1578- return
1579-
1580- claim = part.get_width_request()
1581- self._grow_parts(claim)
1582-
1583- def _append_compose_parts(self, new_part):
1584- d = self.get_direction()
1585- children = self.get_children()
1586- n_parts = len(children)
1587-
1588- if n_parts > 0:
1589- new_part.set_shape(ShapeEndCap(d))
1590- first_part = children[0]
1591- first_part.set_shape(ShapeStartArrow(d))
1592- else:
1593- new_part.set_shape(ShapeRoundedRect(d))
1594-
1595- if not n_parts > 1:
1596- return
1597-
1598- new_mid = children[-1]
1599- new_mid.set_shape(ShapeMidArrow(d))
1600-
1601- def _remove_compose_parts(self):
1602- d = self.get_direction()
1603- children = self.get_children()
1604- n_parts = len(children)
1605-
1606- if n_parts == 0:
1607- return
1608-
1609- elif n_parts == 1:
1610- children[0].set_shape(ShapeRoundedRect(d))
1611- return
1612-
1613- last = children[-1]
1614- last.set_shape(ShapeEndCap(d))
1615- self.queue_draw()
1616-
1617- def _cleanup_revealer(self):
1618- if not self._revealer:
1619- return
1620- GObject.source_remove(self._revealer)
1621- self._revealer = None
1622-
1623- def _theme(self, part):
1624- #~ part.set_padding(self.theme['xpad'], self.theme['ypad'])
1625- part.set_padding(12, 4)
1626-
1627- # public methods
1628- @property
1629- def first_part(self):
1630- children = self.get_children()
1631- if children:
1632- return children[0]
1633-
1634- @property
1635- def last_part(self):
1636- children = self.get_children()
1637- if children:
1638- return children[-1]
1639-
1640- def reveal_part(self, part, animate=True):
1641- # do not do here:
1642- #~ self.animator.reset(who_called='reveal_animation')
1643- self.set_use_animations(animate)
1644-
1645- part_old_width = part.get_width_request()
1646- part_new_width = part.get_natural_width()
1647-
1648- if part_new_width == part_old_width:
1649- return
1650-
1651- change_amount = part_new_width - part_old_width
1652-
1653- for p in self.get_children():
1654-
1655- if p == part:
1656- old_width = part_old_width
1657- new_width = part_new_width
1658- else:
1659- if change_amount <= 0:
1660- continue
1661-
1662- old_width = p.get_width_request()
1663- new_width = max(self.MIN_PART_WIDTH, old_width - change_amount)
1664- change_amount -= old_width - new_width
1665-
1666- if self.use_animations:
1667- self.animator.append_animation(p,
1668- PathBarAnimator.WIDTH_CHANGE,
1669- start_width=old_width,
1670- end_width=new_width)
1671- else:
1672- p.set_size_request(new_width,
1673- p.get_height_request())
1674-
1675- self.animator.start()
1676-
1677- def queue_reveal_part(self, part):
1678-
1679- def reveal_part_cb(part):
1680- self.reveal_part(part)
1681- return
1682-
1683- self._cleanup_revealer()
1684- self._revealer = GObject.timeout_add(self._timeout_reveal,
1685- reveal_part_cb,
1686- part)
1687-
1688- def get_parts_width(self):
1689- last = self.last_part
1690- if not last:
1691- return 0
1692-
1693- if self.get_direction() != Gtk.TextDirection.RTL:
1694- return last.x + last.width - self.first_part.x
1695-
1696- first = self.first_part
1697- return first.x + first.width - last.x
1698-
1699- def get_visual_width(self):
1700- last = self.last_part
1701- first = self.first_part
1702- if not last:
1703- return 0
1704-
1705- la = last.animation_allocation or last.get_allocation()
1706- fa = first.animation_allocation or first.get_allocation()
1707-
1708- if self.get_direction() != Gtk.TextDirection.RTL:
1709- return la.x + la.width - fa.x
1710-
1711- return fa.x + fa.width - la.x
1712-
1713- def set_use_animations(self, use_animations):
1714- self.use_animations = use_animations
1715- if not use_animations and self.animator.in_progress:
1716- self.animator.reset()
1717-
1718- def append(self, part):
1719- print 'append', part
1720- print
1721- part.set_nopaint(True)
1722- self.animator.reset('append')
1723-
1724- self._theme(part)
1725- self._append_compose_parts(part)
1726- self._make_space(part)
1727-
1728- self.pack_start(part, False, False, 0)
1729- part.show()
1730-
1731- if self.use_animations:
1732- # XXX: please note that animations also get queued up
1733- # within _shrink_parts()
1734- self.animator.append_animation(part, PathBarAnimator.OUT)
1735- #~ print self.animator.sequence
1736- self.animator.start()
1737- else:
1738- part.set_nopaint(False)
1739- part.queue_draw()
1740-
1741- def pop(self):
1742- children = self.get_children()
1743- if not children:
1744- return
1745-
1746- self.animator.reset('pop')
1747-
1748- last = children[-1]
1749- if self.use_animations:
1750- # because we remove the real part immediately we need to
1751- # replicate just enough attributes to preform the slide in
1752- # animation
1753- part = PsuedoPathPart(self, last)
1754- self.psuedo_parts.append(part)
1755-
1756- self.remove(last)
1757-
1758- self._remove_compose_parts()
1759- self._reclaim_space(last)
1760-
1761- last.destroy()
1762-
1763- if not self.use_animations:
1764- return
1765-
1766- self.animator.append_animation(part, PathBarAnimator.IN)
1767- self.animator.start()
1768-
1769- def navigate_up(self):
1770- """ just another name for pop() """
1771- self.pop()
1772-
1773-
1774-class PathPartCommon:
1775-
1776- def __init__(self):
1777- self.animation_in_progress = False
1778- self.animation_allocation = None
1779-
1780- @property
1781- def x(self):
1782- return self.get_allocation().x
1783-
1784- @property
1785- def y(self):
1786- return self.get_allocation().y
1787-
1788- @property
1789- def width(self):
1790- return self.get_allocation().width
1791-
1792- @property
1793- def height(self):
1794- return self.get_allocation().height
1795-
1796- def new_frame(self, allocation):
1797- if self.is_nopaint:
1798- self.is_nopaint = False
1799- if not self.animation_in_progress:
1800- self.animation_in_progress = True
1801-
1802- self.animation_allocation = allocation
1803- self.queue_draw()
1804-
1805- def animation_finished(self):
1806- self.animation_in_progress = False
1807- self.animation_allocation = None
1808- if self.get_parent():
1809- self.get_parent().queue_draw()
1810-
1811- def paint(self, cr, a, context, xo, yo):
1812- if self.is_nopaint:
1813- return
1814-
1815- cr.save()
1816-
1817- x, y = 0, 0
1818- w, h = a.width, a.height
1819- arrow_width = 12 # theme['arrow-width']
1820-
1821- if isinstance(self, PathPart):
1822- _a = self.get_allocation()
1823- self.shape.layout(cr,
1824- _a.x - xo + 1, _a.y - yo,
1825- w, h, 3, arrow_width)
1826- cr.clip()
1827- else:
1828- Gtk.render_background(context, cr,
1829- a.x - xo - 10, a.y - yo,
1830- a.width + 10, a.height)
1831-
1832- cr.translate(a.x - xo, a.y - yo)
1833-
1834- if self.shape.name.find('Arrow') != -1:
1835- # draw arrow head
1836- cr.move_to(w - arrow_width / 2, 2)
1837- cr.line_to(w + 5, h / 2)
1838- cr.line_to(w - arrow_width / 2, h - 2)
1839- # fetch the line color and stroke
1840- rgba = context.get_border_color(Gtk.StateFlags.NORMAL)
1841- cr.set_source_rgb(rgba.red, rgba.green, rgba.blue)
1842- cr.set_line_width(1)
1843- cr.stroke()
1844-
1845- # render the layout
1846- e = self.layout.get_pixel_extents()[1]
1847- lw, lh = e.width, e.height
1848- pw, ph = a.width, a.height
1849-
1850- x = min(self.xpadding, (pw - lw) / 2)
1851- y = (ph - lh) / 2
1852-
1853- # layout area
1854- Gtk.render_layout(context,
1855- cr,
1856- int(x),
1857- int(y),
1858- self.layout)
1859-
1860- # paint the focus frame if need be
1861- if isinstance(self, PathPart) and self.has_focus():
1862- # layout area
1863- x, w, h = x - 2, lw + 4, lh + 1
1864- Gtk.render_focus(context, cr, x, y, w, h)
1865-
1866- cr.restore()
1867-
1868-
1869-class PsuedoPathPart(PathPartCommon):
1870-
1871- def __init__(self, pathbar, real_part):
1872- PathPartCommon.__init__(self)
1873- self.parent = pathbar
1874- self.style = pathbar.get_style()
1875- self.state = real_part.get_state()
1876- self.allocation = real_part.get_allocation()
1877- self.size_request = real_part.get_size_request()
1878- self.xpadding = real_part.xpadding
1879- self.ypadding = real_part.ypadding
1880-
1881- # PsuedoPathParts are only used during the remove animation
1882- # sequence, so the shape is always a ShapeEndCap
1883- self.shape = ShapeEndCap(pathbar.get_direction())
1884-
1885- self.label = real_part.label
1886- self.layout = real_part.create_pango_layout(self.label)
1887-
1888- self.is_nopaint = False
1889-
1890- def get_allocation(self):
1891- return self.allocation
1892-
1893- def get_state(self):
1894- return self.state
1895-
1896- def get_width_request(self):
1897- return self.size_request[0]
1898-
1899- def get_height_request(self):
1900- return self.size_request[1]
1901-
1902- def animation_finished(self):
1903- pass
1904-
1905- def queue_draw(self):
1906- a = self.allocation
1907- aw = 12
1908- self.parent.queue_draw_area(a.x - aw / 2, a.y,
1909- a.width + aw, a.height)
1910-
1911-
1912-class PathPart(Gtk.EventBox, PathPartCommon):
1913-
1914- __gsignals__ = {
1915- "clicked": (GObject.SignalFlags.RUN_LAST,
1916- None,
1917- (),),
1918- }
1919-
1920- def __init__(self, label):
1921- Gtk.EventBox.__init__(self)
1922- PathPartCommon.__init__(self)
1923- self.set_visible_window(False)
1924-
1925- self.atk = self.get_accessible()
1926- self.atk.set_role(Atk.Role.PUSH_BUTTON)
1927-
1928- self.layout = self.create_pango_layout(label)
1929- self.layout.set_ellipsize(Pango.EllipsizeMode.END)
1930-
1931- self.xpadding = 6
1932- self.ypadding = 3
1933-
1934- self.shape = ShapeRoundedRect(self.get_direction())
1935- self.is_nopaint = False
1936-
1937- self.set_label(label)
1938- self._init_event_handling()
1939-
1940- def __repr__(self):
1941- return "PathPart: '%s'" % self.label
1942-
1943- def __str__(self):
1944- return "PathPart: '%s'" % self.label
1945-
1946- # signal handlers
1947- def _on_enter_notify(self, part, event):
1948- self.pathbar.queue_reveal_part(self)
1949- if self.pathbar._press_origin == part:
1950- part.set_state(Gtk.StateFlags.ACTIVE)
1951- else:
1952- part.set_state(Gtk.StateFlags.PRELIGHT)
1953- self.queue_draw()
1954-
1955- def _on_leave_notify(self, part, event):
1956- self.pathbar.queue_reveal_part(self.pathbar.last_part)
1957- part.set_state(Gtk.StateFlags.NORMAL)
1958- self.queue_draw()
1959-
1960- def _on_button_press(self, part, event):
1961- if event.button != 1:
1962- return
1963- self.pathbar._press_origin = part
1964- part.set_state(Gtk.StateFlags.ACTIVE)
1965- self.queue_draw()
1966-
1967- def _on_button_release(self, part, event):
1968- if event.button != 1:
1969- return
1970-
1971- if self.pathbar._press_origin != part:
1972- self.pathbar._press_origin = None
1973- return
1974-
1975- self.pathbar._press_origin = None
1976-
1977- state = part.get_state()
1978- if state == Gtk.StateFlags.ACTIVE:
1979- part.set_state(Gtk.StateFlags.PRELIGHT)
1980- GObject.timeout_add(self.pathbar._timeout_initial,
1981- self.emit, 'clicked')
1982-
1983- self.queue_draw()
1984-
1985- def _on_key_press(self, part, event):
1986- if event.keyval in (Gdk.KEY_space, Gdk.KEY_Return, Gdk.KEY_KP_Enter):
1987- part.set_state(Gtk.StateFlags.ACTIVE)
1988- self.queue_draw()
1989-
1990- def _on_key_release(self, part, event):
1991- if event.keyval in (Gdk.KEY_space, Gdk.KEY_Return, Gdk.KEY_KP_Enter):
1992- part.set_state(Gtk.StateFlags.NORMAL)
1993- GObject.timeout_add(self.pathbar._timeout_initial,
1994- self.emit, 'clicked')
1995- self.queue_draw()
1996-
1997- def _on_focus_in(self, part, event):
1998- self.pathbar.reveal_part(self)
1999-
2000- def _on_focus_out(self, part, event):
2001- self.queue_draw()
2002-
2003- # private methods
2004- def _init_event_handling(self):
2005- self.set_property("can-focus", True)
2006- self.set_events(Gdk.EventMask.BUTTON_PRESS_MASK |
2007- Gdk.EventMask.BUTTON_RELEASE_MASK |
2008- Gdk.EventMask.KEY_RELEASE_MASK |
2009- Gdk.EventMask.KEY_PRESS_MASK |
2010- Gdk.EventMask.ENTER_NOTIFY_MASK |
2011- Gdk.EventMask.LEAVE_NOTIFY_MASK)
2012-
2013- self.connect("enter-notify-event", self._on_enter_notify)
2014- self.connect("leave-notify-event", self._on_leave_notify)
2015- self.connect("button-press-event", self._on_button_press)
2016- self.connect("button-release-event", self._on_button_release)
2017- self.connect("key-press-event", self._on_key_press)
2018- self.connect("key-release-event", self._on_key_release)
2019- self.connect("focus-in-event", self._on_focus_in)
2020- self.connect("focus-out-event", self._on_focus_out)
2021-
2022- def _calc_natural_size(self, who_called='?'):
2023- ne = self.natural_extents
2024- nw, nh = ne.width, ne.height
2025-
2026- nw += self.shape.hadjustment + 2 * self.xpadding
2027- nh += 2 * self.ypadding
2028-
2029- self.natural_size = nw, nh
2030- self.set_size_request(nw, nh)
2031-
2032- # public methods
2033- @property
2034- def pathbar(self):
2035- return self.get_parent()
2036-
2037- def set_padding(self, xpadding, ypadding):
2038- self.xpadding = xpadding
2039- self.ypadding = ypadding
2040- self._calc_natural_size()
2041-
2042- def set_size_request(self, width, height):
2043- width = max(2 * self.xpadding + 1, width)
2044- height = max(2 * self.ypadding + 1, height)
2045- self.layout.set_width(Pango.SCALE * (width - 2 * self.xpadding))
2046- Gtk.Widget.set_size_request(self, width, height)
2047-
2048- def set_nopaint(self, is_nopaint):
2049- self.is_nopaint = is_nopaint
2050- self.queue_draw()
2051-
2052- def set_shape(self, shape):
2053- if shape == self.shape:
2054- return
2055- self.shape = shape
2056- self._calc_natural_size()
2057- self.queue_draw()
2058-
2059- def set_label(self, label):
2060- self.label = label
2061-
2062- self.atk.set_name(label)
2063- self.atk.set_description(_('Navigates to the %s page.') % label)
2064-
2065- self.layout.set_markup(label, -1)
2066- self.layout.set_width(-1)
2067- self.natural_extents = self.layout.get_pixel_extents()[1]
2068-
2069- self._calc_natural_size()
2070- self.queue_draw()
2071-
2072- def get_natural_size(self):
2073- return self.natural_size
2074-
2075- def get_natural_width(self):
2076- return self.natural_size[0]
2077-
2078- def get_natural_height(self):
2079- return self.natural_size[1]
2080-
2081- def get_width_request(self):
2082- return self.get_property("width-request")
2083-
2084- def get_height_request(self):
2085- return self.get_property("height-request")
2086-
2087- def queue_draw(self):
2088- a = self.get_allocation()
2089- parent = self.get_parent()
2090- if parent:
2091- aw = 12
2092- else:
2093- aw = 0
2094- self.queue_draw_area(a.x - aw / 2, a.y,
2095- a.width + aw, a.height)
2096-
2097-
2098-class NavigationBar(PathBar):
2099-
2100- def __init__(self, group=None):
2101- PathBar.__init__(self)
2102- self.id_to_part = {}
2103- self._callback_id = None
2104-
2105- def _on_part_clicked(self, part):
2106- part.callback(self, part)
2107-
2108- def add_with_id(self, label, callback, id, do_callback=True, animate=True):
2109- """
2110- Add a new button with the given label/callback
2111-
2112- If there is the same id already, replace the existing one
2113- with the new one
2114- """
2115- LOG.debug("add_with_id label='%s' callback='%s' id='%s' "
2116- "do_callback=%s animate=%s" % (label, callback, id,
2117- do_callback, animate))
2118-
2119- label = GObject.markup_escape_text(label)
2120-
2121- if not self.id_to_part:
2122- self.set_use_animations(False)
2123- else:
2124- self.set_use_animations(animate)
2125-
2126- # check if we have the button of that id or need a new one
2127- if id in self.id_to_part:
2128- part = self.id_to_part[id]
2129- if part.label == label:
2130- return
2131-
2132- part.set_label(label)
2133- else:
2134- part = PathPart(label)
2135- part.connect('clicked', self._on_part_clicked)
2136-
2137- part.set_name(id)
2138- self.id_to_part[id] = part
2139-
2140- part.callback = callback
2141- if do_callback:
2142- # cleanup any superceeded idle callback
2143- if self._callback_id:
2144- GObject.source_remove(self._callback_id)
2145- self._callback_id = None
2146-
2147- # if i do not have call the callback in an idle,
2148- # all hell breaks loose
2149- self._callback_id = GObject.idle_add(callback,
2150- self, # pathbar
2151- part)
2152-
2153- self.append(part)
2154-
2155- def remove_ids(self, *ids, **kwargs):
2156- parts = self.get_parts()
2157-
2158- print 'remove ids', ids
2159-
2160- # it would seem parts can become stale within the id_to_part dict,
2161- # so we clean these up ...
2162- cleanup_ids = []
2163- # the index of the first part to be clipped
2164- index = len(parts)
2165-
2166- for id, part in self.id_to_part.iteritems():
2167- if id not in ids:
2168- continue
2169- if part not in parts:
2170- cleanup_ids.append(id)
2171- part.destroy()
2172- else:
2173- index = min(index, parts.index(part))
2174-
2175- if index == len(parts):
2176- return
2177-
2178- # cleanup any stale id:part pairs in the id_to_part dict
2179- for id in cleanup_ids:
2180- del self.id_to_part[id]
2181-
2182- # remove id:part pairs from the id_to_part dict, for whom removal
2183- # has been requested
2184- for id in ids:
2185- if id in self.id_to_part:
2186- del self.id_to_part[id]
2187-
2188- #~ print index, self.id_to_part.keys()
2189-
2190- # the index is used to remove all parts after the index but we
2191- # keep one part around to animate its removal
2192- for part in parts[index + 1:]:
2193- part.destroy()
2194-
2195- animate = True
2196- if 'animate' in kwargs:
2197- animate = kwargs['animate']
2198-
2199- # animate the removal of the final part, or not
2200- self.set_use_animations(animate)
2201- self.pop()
2202-
2203- # check if we should call the new tail parts callback
2204- if 'do_callback' in kwargs and kwargs['do_callback']:
2205- part = self[-1]
2206- part.callback(self, part)
2207-
2208- def remove_all(self, **kwargs):
2209- if len(self) <= 1:
2210- return
2211- ids = filter(lambda k: k != 'category',
2212- self.id_to_part.keys())
2213- self.remove_ids(*ids, **kwargs)
2214-
2215- def has_id(self, id):
2216- return id in self.id_to_part
2217-
2218- def get_parts(self):
2219- return self.get_children()
2220-
2221- def get_active(self):
2222- parts = self.get_parts()
2223- if parts:
2224- return parts[-1]
2225-
2226- def get_button_from_id(self, id):
2227- """
2228- return the button for the given id (or None)
2229- """
2230- return self.id_to_part.get(id)
2231-
2232- def set_active_no_callback(self, part):
2233- pass
2234-
2235-
2236-class TestIt:
2237-
2238- def __init__(self):
2239-
2240- def append(button, entry, pathbar):
2241- t = entry.get_text() or 'no label %s' % len(pathbar)
2242- part = PathPart(t)
2243- pathbar.append(part)
2244-
2245- def remove(button, entry, pathbar):
2246- pathbar.pop()
2247-
2248- win = Gtk.Window()
2249- win.set_border_width(30)
2250- win.set_size_request(600, 300)
2251-
2252- vb = Gtk.VBox(spacing=6)
2253- win.add(vb)
2254-
2255- pb = PathBar()
2256- pb.set_size_request(-1, 30)
2257-
2258- vb.pack_start(pb, False, False, 0)
2259- part = PathPart('Get Software')
2260- pb.append(part)
2261-
2262- entry = Gtk.Entry()
2263- vb.pack_start(entry, False, False, 0)
2264-
2265- b = Gtk.Button('Append')
2266- vb.pack_start(b, True, True, 0)
2267- b.connect('clicked', append, entry, pb)
2268-
2269- b = Gtk.Button('Remove')
2270- vb.pack_start(b, True, True, 0)
2271- b.connect('clicked', remove, entry, pb)
2272-
2273- win.show_all()
2274-
2275- win.connect('destroy', Gtk.main_quit)
2276- self.win = win
2277- self.win.pb = pb
2278-
2279-
2280-def get_test_pathbar_window():
2281- t = TestIt()
2282- return t.win
2283-
2284-if __name__ == '__main__':
2285- win = get_test_pathbar_window()
2286- Gtk.main()

Subscribers

People subscribed via source and target branches