Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/sugar/activity/activityfactory.py2
-rw-r--r--src/sugar/activity/registry.py8
-rw-r--r--src/sugar/bundle/activitybundle.py5
-rw-r--r--src/sugar/graphics/palette.py76
-rw-r--r--src/sugar/util.py70
5 files changed, 139 insertions, 22 deletions
diff --git a/src/sugar/activity/activityfactory.py b/src/sugar/activity/activityfactory.py
index 42b8c40..4d6871f 100644
--- a/src/sugar/activity/activityfactory.py
+++ b/src/sugar/activity/activityfactory.py
@@ -195,7 +195,7 @@ class ActivityCreationHandler(gobject.GObject):
self._use_rainbow = os.path.exists('/etc/olpc-security')
if service_name in [ 'org.laptop.JournalActivity',
'org.laptop.Terminal',
- 'org.laptop.LogViewer',
+ 'org.laptop.Log',
'org.laptop.Analyze'
]:
self._use_rainbow = False
diff --git a/src/sugar/activity/registry.py b/src/sugar/activity/registry.py
index 5f5aefc..d5d0529 100644
--- a/src/sugar/activity/registry.py
+++ b/src/sugar/activity/registry.py
@@ -31,11 +31,12 @@ def _activity_info_from_dict(info_dict):
return ActivityInfo(info_dict['name'], info_dict['icon'],
info_dict['bundle_id'], info_dict['version'],
info_dict['path'], info_dict['show_launcher'],
- info_dict['command'], info_dict['favorite'])
+ info_dict['command'], info_dict['favorite'],
+ info_dict['installation_time'])
class ActivityInfo(object):
- def __init__(self, name, icon, bundle_id, version,
- path, show_launcher, command, favorite):
+ def __init__(self, name, icon, bundle_id, version, path, show_launcher,
+ command, favorite, installation_time):
self.name = name
self.icon = icon
self.bundle_id = bundle_id
@@ -44,6 +45,7 @@ class ActivityInfo(object):
self.command = command
self.show_launcher = show_launcher
self.favorite = favorite
+ self.installation_time = installation_time
class ActivityRegistry(gobject.GObject):
__gsignals__ = {
diff --git a/src/sugar/bundle/activitybundle.py b/src/sugar/bundle/activitybundle.py
index 5f1fb7b..db30555 100644
--- a/src/sugar/bundle/activitybundle.py
+++ b/src/sugar/bundle/activitybundle.py
@@ -163,6 +163,11 @@ class ActivityBundle(Bundle):
"""Get the activity user visible name."""
return self._name
+ def get_installation_time(self):
+ """Get a timestamp representing the time at which this activity was
+ installed."""
+ return os.stat(self._path).st_mtime
+
def get_bundle_id(self):
"""Get the activity bundle id"""
return self._bundle_id
diff --git a/src/sugar/graphics/palette.py b/src/sugar/graphics/palette.py
index 592a6df..82feca1 100644
--- a/src/sugar/graphics/palette.py
+++ b/src/sugar/graphics/palette.py
@@ -209,10 +209,10 @@ class Palette(gtk.Window):
self._menu_content_separator = gtk.HSeparator()
- self._popup_anim = animator.Animator(0.3, 10)
+ self._popup_anim = animator.Animator(.5, 10)
self._popup_anim.add(_PopupAnimation(self))
- self._secondary_anim = animator.Animator(1.0, 10)
+ self._secondary_anim = animator.Animator(2.0, 10)
self._secondary_anim.add(_SecondaryAnimation(self))
self._popdown_anim = animator.Animator(0.6, 10)
@@ -265,10 +265,8 @@ class Palette(gtk.Window):
# The menu is not shown here until an item is added
self.menu = _Menu(self)
- self.connect('enter-notify-event',
- self._enter_notify_event_cb)
- self.connect('leave-notify-event',
- self._leave_notify_event_cb)
+ self.connect('enter-notify-event', self.__enter_notify_event_cb)
+ self.connect('leave-notify-event', self.__leave_notify_event_cb)
self._mouse_detector = MouseSpeedDetector(self, 200, 5)
self._mouse_detector.connect('motion-slow', self._mouse_slow_cb)
@@ -314,6 +312,7 @@ class Palette(gtk.Window):
if self._invoker is not None:
self._invoker.disconnect(self._enter_invoker_hid)
self._invoker.disconnect(self._leave_invoker_hid)
+ self._invoker.disconnect(self._right_click_invoker_hid)
self._invoker = invoker
if invoker is not None:
@@ -321,6 +320,8 @@ class Palette(gtk.Window):
'mouse-enter', self._invoker_mouse_enter_cb)
self._leave_invoker_hid = self._invoker.connect(
'mouse-leave', self._invoker_mouse_leave_cb)
+ self._right_click_invoker_hid = self._invoker.connect(
+ 'right-click', self._invoker_right_click_cb)
if hasattr(invoker.props, 'widget'):
self._label.props.accel_widget = invoker.props.widget
@@ -586,9 +587,6 @@ class Palette(gtk.Window):
self.palette_state = state
- def _invoker_mouse_enter_cb(self, invoker):
- self._mouse_detector.start()
-
def _mouse_slow_cb(self, widget):
self._mouse_detector.stop()
self._palette_do_popup()
@@ -610,16 +608,26 @@ class Palette(gtk.Window):
self.popup(immediate=immediate)
+ def _invoker_mouse_enter_cb(self, invoker):
+ self._mouse_detector.start()
+
def _invoker_mouse_leave_cb(self, invoker):
self._mouse_detector.stop()
self.popdown()
- def _enter_notify_event_cb(self, widget, event):
+ def _invoker_right_click_cb(self, invoker):
+ self._popup_anim.stop()
+ self._secondary_anim.stop()
+ self._popdown_anim.stop()
+ self._set_state(self.SECONDARY)
+ self._show()
+
+ def __enter_notify_event_cb(self, widget, event):
if event.detail != gtk.gdk.NOTIFY_INFERIOR:
self._popdown_anim.stop()
self._secondary_anim.start()
- def _leave_notify_event_cb(self, widget, event):
+ def __leave_notify_event_cb(self, widget, event):
if event.detail != gtk.gdk.NOTIFY_INFERIOR:
self.popdown()
@@ -706,6 +714,7 @@ class Invoker(gobject.GObject):
__gsignals__ = {
'mouse-enter': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
'mouse-leave': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
+ 'right-click': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
'focus-out': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([]))
}
@@ -877,17 +886,23 @@ class Invoker(gobject.GObject):
self._cursor_x = -1
self._cursor_y = -1
- def notify_mouse_enter(self):
+ def _ensure_palette_exists(self):
if self.parent and self.palette is None:
palette = self.parent.create_palette()
if palette:
self.palette = palette
+ def notify_mouse_enter(self):
+ self._ensure_palette_exists()
self.emit('mouse-enter')
def notify_mouse_leave(self):
self.emit('mouse-leave')
+ def notify_right_click(self):
+ self._ensure_palette_exists()
+ self.emit('right-click')
+
def get_palette(self):
return self._palette
@@ -908,6 +923,9 @@ class WidgetInvoker(Invoker):
Invoker.__init__(self)
self._widget = None
+ self._enter_hid = None
+ self._leave_hid = None
+ self._release_hid = None
if parent or widget:
self.attach_widget(parent, widget)
@@ -919,9 +937,11 @@ class WidgetInvoker(Invoker):
self._widget = parent
self._enter_hid = widget.connect('enter-notify-event',
- self._enter_notify_event_cb)
+ self.__enter_notify_event_cb)
self._leave_hid = widget.connect('leave-notify-event',
- self._leave_notify_event_cb)
+ self.__leave_notify_event_cb)
+ self._release_hid = widget.connect('button-release-event',
+ self.__button_release_event_cb)
self.attach(parent)
@@ -929,6 +949,7 @@ class WidgetInvoker(Invoker):
Invoker.detach(self)
self._widget.disconnect(self._enter_hid)
self._widget.disconnect(self._leave_hid)
+ self._widget.disconnect(self._release_hid)
def get_rect(self):
allocation = self._widget.get_allocation()
@@ -973,12 +994,19 @@ class WidgetInvoker(Invoker):
self._widget.allocation.width,
self._widget.allocation.height)
- def _enter_notify_event_cb(self, widget, event):
+ def __enter_notify_event_cb(self, widget, event):
self.notify_mouse_enter()
- def _leave_notify_event_cb(self, widget, event):
+ def __leave_notify_event_cb(self, widget, event):
self.notify_mouse_leave()
+ def __button_release_event_cb(self, widget, event):
+ if event.button == 3:
+ self.notify_right_click()
+ return True
+ else:
+ return False
+
def get_toplevel(self):
return self._widget.get_toplevel()
@@ -999,6 +1027,8 @@ class CanvasInvoker(Invoker):
Invoker.__init__(self)
self._position_hint = self.AT_CURSOR
+ self._motion_hid = None
+ self._release_hid = None
if parent:
self.attach(parent)
@@ -1008,11 +1038,14 @@ class CanvasInvoker(Invoker):
self._item = parent
self._motion_hid = self._item.connect('motion-notify-event',
- self._motion_notify_event_cb)
+ self.__motion_notify_event_cb)
+ self._release_hid = self._item.connect('button-release-event',
+ self.__button_release_event_cb)
def detach(self):
Invoker.detach(self)
self._item.disconnect(self._motion_hid)
+ self._item.disconnect(self._release_hid)
def get_default_position(self):
return self.AT_CURSOR
@@ -1026,7 +1059,7 @@ class CanvasInvoker(Invoker):
else:
return gtk.gdk.Rectangle()
- def _motion_notify_event_cb(self, button, event):
+ def __motion_notify_event_cb(self, button, event):
if event.detail == hippo.MOTION_DETAIL_ENTER:
self.notify_mouse_enter()
elif event.detail == hippo.MOTION_DETAIL_LEAVE:
@@ -1034,6 +1067,13 @@ class CanvasInvoker(Invoker):
return False
+ def __button_release_event_cb(self, button, event):
+ if event.button == 3:
+ self.notify_right_click()
+ return True
+ else:
+ return False
+
def get_toplevel(self):
return hippo.get_canvas_for_item(self._item).get_toplevel()
diff --git a/src/sugar/util.py b/src/sugar/util.py
index 12f6824..8f81210 100644
--- a/src/sugar/util.py
+++ b/src/sugar/util.py
@@ -21,6 +21,8 @@ import sha
import random
import binascii
import string
+from gettext import gettext as _
+import gettext
def printable_hash(in_hash):
"""Convert binary hash data into printable characters."""
@@ -168,3 +170,71 @@ class LRU:
yield j
def keys(self):
return self.d.keys()
+
+units = [['%d year', '%d years', 356 * 24 * 60 * 60],
+ ['%d month', '%d months', 30 * 24 * 60 * 60],
+ ['%d week', '%d weeks', 7 * 24 * 60 * 60],
+ ['%d day', '%d days', 24 * 60 * 60],
+ ['%d hour', '%d hours', 60 * 60],
+ ['%d minute', '%d minutes', 60]]
+
+AND = _(' and ')
+COMMA = _(', ')
+
+# TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+NOW = _('Seconds ago')
+
+# TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+# "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+ELAPSED = _('%s ago')
+
+# Explanation of the following hack:
+# The xgettext utility extracts plural forms by reading the strings included as
+# parameters of ngettext(). As our plurals are not passed to ngettext()
+# straight away because there needs to be a calculation before we know which
+# strings need to be used, then we need to call ngettext() in a fake way so
+# xgettext will pick them up as plurals.
+
+def ngettext(singular, plural, n):
+ pass
+
+# TRANS: Relative dates (eg. 1 month and 5 days).
+ngettext('%d year', '%d years', 1)
+ngettext('%d month', '%d months', 1)
+ngettext('%d week', '%d weeks', 1)
+ngettext('%d day', '%d days', 1)
+ngettext('%d hour', '%d hours', 1)
+ngettext('%d minute', '%d minutes', 1)
+
+del ngettext
+
+# End of plurals hack
+
+def timestamp_to_elapsed_string(timestamp, max_levels=2):
+ levels = 0
+ time_period = ''
+ elapsed_seconds = int(time.time() - timestamp)
+
+ for name_singular, name_plural, factor in units:
+ elapsed_units = elapsed_seconds / factor
+ if elapsed_units > 0:
+
+ if levels > 0:
+ time_period += COMMA
+
+ time_period += gettext.ngettext(name_singular, name_plural,
+ elapsed_units) % elapsed_units
+
+ elapsed_seconds -= elapsed_units * factor
+
+ if time_period != '':
+ levels += 1
+
+ if levels == max_levels:
+ break
+
+ if levels == 0:
+ return NOW
+
+ return ELAPSED % time_period
+