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/jarabe/controlpanel/cmd.py3
-rw-r--r--src/jarabe/controlpanel/gui.py2
-rw-r--r--src/jarabe/desktop/Makefile.am1
-rw-r--r--src/jarabe/desktop/activitieslist.py132
-rw-r--r--src/jarabe/desktop/favoriteslayout.py6
-rw-r--r--src/jarabe/desktop/favoritesview.py53
-rw-r--r--src/jarabe/desktop/homebox.py49
-rw-r--r--src/jarabe/desktop/homewindow.py9
-rw-r--r--src/jarabe/desktop/keydialog.py6
-rw-r--r--src/jarabe/desktop/meshbox.py44
-rwxr-xr-xsrc/jarabe/desktop/proc_smaps.py107
-rw-r--r--src/jarabe/desktop/schoolserver.py6
-rw-r--r--src/jarabe/frame/activitiestray.py36
-rw-r--r--src/jarabe/frame/clipboard.py20
-rw-r--r--src/jarabe/frame/clipboardicon.py2
-rw-r--r--src/jarabe/frame/clipboardmenu.py16
-rw-r--r--src/jarabe/frame/clipboardobject.py20
-rw-r--r--src/jarabe/frame/clipboardpanelwindow.py6
-rw-r--r--src/jarabe/frame/clipboardtray.py18
-rw-r--r--src/jarabe/frame/frame.py4
-rw-r--r--src/jarabe/frame/framewindow.py3
-rw-r--r--src/jarabe/frame/zoomtoolbar.py16
-rw-r--r--src/jarabe/intro/window.py5
-rw-r--r--src/jarabe/journal/Makefile.am2
-rw-r--r--src/jarabe/journal/__init__.py15
-rw-r--r--src/jarabe/journal/collapsedentry.py397
-rw-r--r--src/jarabe/journal/expandedentry.py40
-rw-r--r--src/jarabe/journal/journalactivity.py21
-rw-r--r--src/jarabe/journal/journalentrybundle.py5
-rw-r--r--src/jarabe/journal/journaltoolbox.py49
-rw-r--r--src/jarabe/journal/listmodel.py204
-rw-r--r--src/jarabe/journal/listview.py777
-rw-r--r--src/jarabe/journal/misc.py15
-rw-r--r--src/jarabe/journal/model.py124
-rw-r--r--src/jarabe/journal/objectchooser.py69
-rw-r--r--src/jarabe/journal/palettes.py8
-rw-r--r--src/jarabe/journal/volumestoolbar.py6
-rw-r--r--src/jarabe/model/buddy.py11
-rw-r--r--src/jarabe/model/bundleregistry.py52
-rw-r--r--src/jarabe/model/filetransfer.py21
-rw-r--r--src/jarabe/model/friends.py8
-rw-r--r--src/jarabe/model/neighborhood.py10
-rw-r--r--src/jarabe/model/network.py39
-rw-r--r--src/jarabe/model/notifications.py4
-rw-r--r--src/jarabe/model/owner.py4
-rw-r--r--src/jarabe/model/screen.py21
-rw-r--r--src/jarabe/model/shell.py28
-rw-r--r--src/jarabe/view/buddymenu.py13
-rw-r--r--src/jarabe/view/keyhandler.py172
-rw-r--r--src/jarabe/view/launcher.py3
-rw-r--r--src/jarabe/view/palettes.py84
-rw-r--r--src/jarabe/view/service.py19
-rw-r--r--src/jarabe/view/tabbinghandler.py37
-rw-r--r--src/jarabe/view/viewsource.py12
54 files changed, 1299 insertions, 1535 deletions
diff --git a/src/jarabe/controlpanel/cmd.py b/src/jarabe/controlpanel/cmd.py
index 12de1e2..5f2de68 100644
--- a/src/jarabe/controlpanel/cmd.py
+++ b/src/jarabe/controlpanel/cmd.py
@@ -105,6 +105,9 @@ def main():
for method in methods:
if method.startswith('get_'):
print ' %s' % method[4:]
+ elif method.startswith('clear_'):
+ print " %s (use the -c argument with this option)" \
+ % method[6:]
if option in ("-g"):
for module in modules:
method = getattr(module, 'print_' + key, None)
diff --git a/src/jarabe/controlpanel/gui.py b/src/jarabe/controlpanel/gui.py
index 33d0aff..39f9c4a 100644
--- a/src/jarabe/controlpanel/gui.py
+++ b/src/jarabe/controlpanel/gui.py
@@ -357,7 +357,7 @@ class ModelWrapper(object):
try:
method(self._options[key])
except Exception, detail:
- _logger.debug('Error undo option: %s' % detail)
+ _logger.debug('Error undo option: %s', detail)
class _SectionIcon(gtk.EventBox):
__gtype_name__ = "SugarSectionIcon"
diff --git a/src/jarabe/desktop/Makefile.am b/src/jarabe/desktop/Makefile.am
index 94d8ab9..5b47455 100644
--- a/src/jarabe/desktop/Makefile.am
+++ b/src/jarabe/desktop/Makefile.am
@@ -12,7 +12,6 @@ sugar_PYTHON = \
keydialog.py \
meshbox.py \
myicon.py \
- proc_smaps.py \
schoolserver.py \
snowflakelayout.py \
spreadlayout.py \
diff --git a/src/jarabe/desktop/activitieslist.py b/src/jarabe/desktop/activitieslist.py
index 598774a..8b35560 100644
--- a/src/jarabe/desktop/activitieslist.py
+++ b/src/jarabe/desktop/activitieslist.py
@@ -15,6 +15,7 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+import os
import logging
from gettext import gettext as _
@@ -22,13 +23,13 @@ import gobject
import pango
import gconf
import gtk
-import hippo
from sugar import util
from sugar.graphics import style
-from sugar.graphics.icon import CanvasIcon, CellRendererIcon
-from sugar.graphics.palette import Palette
+from sugar.graphics.icon import Icon, CellRendererIcon
from sugar.graphics.xocolor import XoColor
+from sugar.graphics.menuitem import MenuItem
+from sugar.graphics.alert import Alert
from sugar.activity import activityfactory
from sugar.activity.activityhandle import ActivityHandle
@@ -56,7 +57,7 @@ class ActivitiesTreeView(gtk.TreeView):
self.set_model(model)
cell_favorite = CellRendererFavorite(self)
- cell_favorite.connect('activate', self.__favorite_activate_cb)
+ cell_favorite.connect('clicked', self.__favorite_clicked_cb)
column = gtk.TreeViewColumn('')
column.pack_start(cell_favorite)
@@ -65,7 +66,7 @@ class ActivitiesTreeView(gtk.TreeView):
cell_icon = CellRendererActivityIcon(self)
cell_icon.connect('erase-activated', self.__erase_activated_cb)
- cell_icon.connect('activate', self.__icon_activate_cb)
+ cell_icon.connect('clicked', self.__icon_clicked_cb)
column = gtk.TreeViewColumn('')
column.pack_start(cell_icon)
@@ -127,14 +128,14 @@ class ActivitiesTreeView(gtk.TreeView):
cell.props.stroke_color = style.COLOR_BUTTON_GREY.get_svg()
cell.props.fill_color = style.COLOR_WHITE.get_svg()
- def __favorite_activate_cb(self, cell, path):
+ def __favorite_clicked_cb(self, cell, path):
row = self.get_model()[path]
registry = bundleregistry.get_registry()
registry.set_bundle_favorite(row[ListModel.COLUMN_BUNDLE_ID],
row[ListModel.COLUMN_VERSION],
not row[ListModel.COLUMN_FAVORITE])
- def __icon_activate_cb(self, cell, path):
+ def __icon_clicked_cb(self, cell, path):
row = self.get_model()[path]
registry = bundleregistry.get_registry()
@@ -190,7 +191,7 @@ class ListModel(gtk.TreeModelSort):
bundle_id = activity_info.get_bundle_id()
version = activity_info.get_activity_version()
favorite = activity_registry.is_bundle_favorite(bundle_id, version)
- for row in self:
+ for row in self._model:
if row[ListModel.COLUMN_BUNDLE_ID] == bundle_id and \
row[ListModel.COLUMN_VERSION] == version:
row[ListModel.COLUMN_FAVORITE] = favorite
@@ -199,11 +200,10 @@ class ListModel(gtk.TreeModelSort):
def __activity_removed_cb(self, activity_registry, activity_info):
bundle_id = activity_info.get_bundle_id()
version = activity_info.get_activity_version()
- favorite = activity_registry.is_bundle_favorite(bundle_id, version)
- for row in self:
+ for row in self._model:
if row[ListModel.COLUMN_BUNDLE_ID] == bundle_id and \
row[ListModel.COLUMN_VERSION] == version:
- self.remove(row.iter)
+ self._model.remove(row.iter)
return
def _add_activity(self, activity_info):
@@ -286,7 +286,7 @@ class CellRendererActivityIcon(CellRendererIcon):
bundle_id = row[ListModel.COLUMN_BUNDLE_ID]
registry = bundleregistry.get_registry()
- palette = ActivityPalette(registry.get_bundle(bundle_id))
+ palette = ActivityListPalette(registry.get_bundle(bundle_id))
palette.connect('erase-activated', self.__erase_activated_cb)
return palette
@@ -296,11 +296,6 @@ class CellRendererActivityIcon(CellRendererIcon):
class ActivitiesList(gtk.VBox):
__gtype_name__ = 'SugarActivitiesList'
- __gsignals__ = {
- 'erase-activated' : (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE, ([str]))
- }
-
def __init__(self):
logging.debug('STARTUP: Loading the activities list')
@@ -353,5 +348,106 @@ class ActivitiesList(gtk.VBox):
self._alert = None
def __erase_activated_cb(self, tree_view, bundle_id):
- self.emit('erase-activated', bundle_id)
+ registry = bundleregistry.get_registry()
+ activity_info = registry.get_bundle(bundle_id)
+
+ alert = Alert()
+ alert.props.title = _('Confirm erase')
+ alert.props.msg = \
+ _('Confirm erase: Do you want to permanently erase %s?') \
+ % activity_info.get_name()
+
+ cancel_icon = Icon(icon_name='dialog-cancel')
+ alert.add_button(gtk.RESPONSE_CANCEL, _('Keep'), cancel_icon)
+
+ erase_icon = Icon(icon_name='dialog-ok')
+ alert.add_button(gtk.RESPONSE_OK, _('Erase'), erase_icon)
+
+ alert.connect('response', self.__erase_confirmation_dialog_response_cb,
+ bundle_id)
+
+ self.add_alert(alert)
+
+ def __erase_confirmation_dialog_response_cb(self, alert, response_id,
+ bundle_id):
+ self.remove_alert()
+ if response_id == gtk.RESPONSE_OK:
+ registry = bundleregistry.get_registry()
+ bundle = registry.get_bundle(bundle_id)
+ registry.uninstall(bundle)
+
+class ActivityListPalette(ActivityPalette):
+ __gtype_name__ = 'SugarActivityListPalette'
+
+ __gsignals__ = {
+ 'erase-activated' : (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([str]))
+ }
+
+ def __init__(self, activity_info):
+ ActivityPalette.__init__(self, activity_info)
+
+ self._bundle_id = activity_info.get_bundle_id()
+ self._version = activity_info.get_activity_version()
+
+ registry = bundleregistry.get_registry()
+ self._favorite = registry.is_bundle_favorite(self._bundle_id,
+ self._version)
+
+ self._favorite_item = MenuItem('')
+ self._favorite_icon = Icon(icon_name='emblem-favorite',
+ icon_size=gtk.ICON_SIZE_MENU)
+ self._favorite_item.set_image(self._favorite_icon)
+ self._favorite_item.connect('activate',
+ self.__change_favorite_activate_cb)
+ self.menu.append(self._favorite_item)
+ self._favorite_item.show()
+
+ menu_item = MenuItem(_('Erase'), 'list-remove')
+ menu_item.connect('activate', self.__erase_activate_cb)
+ self.menu.append(menu_item)
+ menu_item.show()
+
+ if not os.access(activity_info.get_path(), os.W_OK):
+ menu_item.props.sensitive = False
+
+ registry = bundleregistry.get_registry()
+ self._activity_changed_sid = registry.connect('bundle_changed',
+ self.__activity_changed_cb)
+ self._update_favorite_item()
+
+ self.connect('destroy', self.__destroy_cb)
+
+ def __destroy_cb(self, palette):
+ self.disconnect(self._activity_changed_sid)
+
+ def _update_favorite_item(self):
+ label = self._favorite_item.child
+ if self._favorite:
+ label.set_text(_('Remove favorite'))
+ xo_color = XoColor('%s,%s' % (style.COLOR_WHITE.get_svg(),
+ style.COLOR_TRANSPARENT.get_svg()))
+ else:
+ label.set_text(_('Make favorite'))
+ client = gconf.client_get_default()
+ xo_color = XoColor(client.get_string("/desktop/sugar/user/color"))
+
+ self._favorite_icon.props.xo_color = xo_color
+
+ def __change_favorite_activate_cb(self, menu_item):
+ registry = bundleregistry.get_registry()
+ registry.set_bundle_favorite(self._bundle_id,
+ self._version,
+ not self._favorite)
+
+ def __activity_changed_cb(self, activity_registry, activity_info):
+ if activity_info.get_bundle_id() == self._bundle_id and \
+ activity_info.get_activity_version() == self._version:
+ registry = bundleregistry.get_registry()
+ self._favorite = registry.is_bundle_favorite(self._bundle_id,
+ self._version)
+ self._update_favorite_item()
+
+ def __erase_activate_cb(self, menu_item):
+ self.emit('erase-activated', self._bundle_id)
diff --git a/src/jarabe/desktop/favoriteslayout.py b/src/jarabe/desktop/favoriteslayout.py
index 506446f..3ff0ba8 100644
--- a/src/jarabe/desktop/favoriteslayout.py
+++ b/src/jarabe/desktop/favoriteslayout.py
@@ -57,14 +57,14 @@ class FavoritesLayout(gobject.GObject, hippo.CanvasLayout):
def append(self, icon, locked=False):
if not hasattr(type(icon), 'fixed_position'):
- logging.debug('Icon without fixed_position: %r' % icon)
+ logging.debug('Icon without fixed_position: %r', icon)
return
icon.props.size = max(icon.props.size, style.STANDARD_ICON_SIZE)
relative_x, relative_y = icon.fixed_position
if relative_x < 0 or relative_y < 0:
- logging.debug('Icon out of bounds: %r' % icon)
+ logging.debug('Icon out of bounds: %r', icon)
return
min_width_, width = self.box.get_width_request()
@@ -82,7 +82,7 @@ class FavoritesLayout(gobject.GObject, hippo.CanvasLayout):
raise ValueError('Child not in box.')
if not(hasattr(icon, 'get_bundle_id') and hasattr(icon, 'get_version')):
- logging.debug('Not an activity icon %r' % icon)
+ logging.debug('Not an activity icon %r', icon)
return
min_width_, width = self.box.get_width_request()
diff --git a/src/jarabe/desktop/favoritesview.py b/src/jarabe/desktop/favoritesview.py
index 411b579..e8bc7e7 100644
--- a/src/jarabe/desktop/favoritesview.py
+++ b/src/jarabe/desktop/favoritesview.py
@@ -65,11 +65,6 @@ about the layout can be accessed with fields of the class."""
class FavoritesView(hippo.Canvas):
__gtype_name__ = 'SugarFavoritesView'
- __gsignals__ = {
- 'erase-activated' : (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE, ([str]))
- }
-
def __init__(self, **kwargs):
logging.debug('STARTUP: Loading the favorites view')
@@ -97,6 +92,7 @@ class FavoritesView(hippo.Canvas):
self._layout = None
self._alert = None
self._datastore_listener = DatastoreListener()
+ self._resume_mode = True
# More DND stuff
self.add_events(gtk.gdk.BUTTON_PRESS_MASK |
@@ -134,14 +130,11 @@ class FavoritesView(hippo.Canvas):
if activity_info.get_bundle_id() == 'org.laptop.JournalActivity':
return
icon = ActivityIcon(activity_info, self._datastore_listener)
- icon.connect('erase-activated', self.__erase_activated_cb)
icon.props.size = style.STANDARD_ICON_SIZE
+ icon.set_resume_mode(self._resume_mode)
self._box.insert_sorted(icon, 0, self._layout.compare_activities)
self._layout.append(icon)
- def __erase_activated_cb(self, activity_icon, bundle_id):
- self.emit('erase-activated', bundle_id)
-
def __activity_added_cb(self, activity_registry, activity_info):
registry = bundleregistry.get_registry()
if registry.is_bundle_favorite(activity_info.get_bundle_id(),
@@ -344,6 +337,12 @@ class FavoritesView(hippo.Canvas):
def __register_alert_response_cb(self, alert, response_id):
self.remove_alert()
+ def set_resume_mode(self, resume_mode):
+ self._resume_mode = resume_mode
+ for icon in self._box.get_children():
+ if hasattr(icon, 'set_resume_mode'):
+ icon.set_resume_mode(self._resume_mode)
+
DS_DBUS_SERVICE = 'org.laptop.sugar.DataStore'
DS_DBUS_INTERFACE = 'org.laptop.sugar.DataStore'
DS_DBUS_PATH = '/org/laptop/sugar/DataStore'
@@ -377,7 +376,7 @@ class DatastoreListener(object):
def get_last_activity_async(self, bundle_id, properties, callback_cb):
query = {'activity': bundle_id,
'limit': 5,
- 'order_by': ['-mtime']}
+ 'order_by': ['+timestamp']}
reply_handler = lambda entries, total_count: self.__reply_handler_cb(
entries, total_count, callback_cb)
@@ -386,8 +385,8 @@ class DatastoreListener(object):
error, callback_cb)
self._datastore.find(query, properties, byte_arrays=True,
- reply_handler=reply_handler,
- error_handler=error_handler)
+ reply_handler=reply_handler,
+ error_handler=error_handler)
def __reply_handler_cb(self, entries, total_count, callback_cb):
logging.debug('__reply_handler_cb')
@@ -402,11 +401,6 @@ class ActivityIcon(CanvasIcon):
_BORDER_WIDTH = style.zoom(3)
- __gsignals__ = {
- 'erase-activated' : (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE, ([str]))
- }
-
def __init__(self, activity_info, datastore_listener):
CanvasIcon.__init__(self, cache=True,
file_name=activity_info.get_icon())
@@ -414,6 +408,7 @@ class ActivityIcon(CanvasIcon):
self._activity_info = activity_info
self._journal_entries = []
self._hovering = False
+ self._resume_mode = True
self.connect('hovering-changed', self.__hovering_changed_event_cb)
self.connect('button-release-event', self.__button_release_event_cb)
@@ -445,7 +440,8 @@ class ActivityIcon(CanvasIcon):
def __get_last_activity_async_cb(self, entries, error=None):
if error is not None:
- logging.error('Error retrieving most recent activities: %r' % error)
+ logging.error('Error retrieving most recent activities: %r', error)
+ return
# If there's a problem with the DS index, we may get entries not related
# to this activity.
@@ -459,7 +455,7 @@ class ActivityIcon(CanvasIcon):
def _update(self):
self.palette = None
- if not self._journal_entries:
+ if not self._resume_mode or not self._journal_entries:
self.props.stroke_color = style.COLOR_BUTTON_GREY.get_svg()
self.props.fill_color = style.COLOR_TRANSPARENT.get_svg()
else:
@@ -469,12 +465,8 @@ class ActivityIcon(CanvasIcon):
def create_palette(self):
palette = FavoritePalette(self._activity_info, self._journal_entries)
palette.connect('activate', self.__palette_activate_cb)
- palette.connect('erase-activated', self.__erase_activated_cb)
return palette
- def __erase_activated_cb(self, palette, bundle_id):
- self.emit('erase-activated', bundle_id)
-
def __palette_activate_cb(self, palette):
self._activate()
@@ -523,7 +515,7 @@ class ActivityIcon(CanvasIcon):
def _activate(self):
self.palette.popdown(immediate=True)
- if self._journal_entries:
+ if self._resume_mode and self._journal_entries:
entry = self._journal_entries[0]
shell_model = shell.get_model()
@@ -565,6 +557,10 @@ class ActivityIcon(CanvasIcon):
return registry.get_bundle_position(self.bundle_id, self.version)
fixed_position = property(_get_fixed_position, None)
+ def set_resume_mode(self, resume_mode):
+ self._resume_mode = resume_mode
+ self._update()
+
class FavoritePalette(ActivityPalette):
__gtype_name__ = 'SugarFavoritePalette'
@@ -606,7 +602,7 @@ class FavoritePalette(ActivityPalette):
def __resume_entry_cb(self, menu_item, entry):
if entry is not None:
- journal.misc.resume(entry, self._bundle_id)
+ journal.misc.resume(entry, entry['activity'])
class CurrentActivityIcon(CanvasIcon, hippo.CanvasItem):
def __init__(self):
@@ -623,7 +619,8 @@ class CurrentActivityIcon(CanvasIcon, hippo.CanvasItem):
self.connect('button-release-event', self.__button_release_event_cb)
def __button_release_event_cb(self, icon, event):
- self._home_model.get_active_activity().get_window().activate(1)
+ window = self._home_model.get_active_activity().get_window()
+ window.activate(gtk.get_current_event_time())
def _update(self):
self.props.file_name = self._home_activity.get_icon_path()
@@ -694,7 +691,7 @@ class FavoritesSetting(object):
def __init__(self):
client = gconf.client_get_default()
self._layout = client.get_string(self._FAVORITES_KEY)
- logging.debug('FavoritesSetting layout %r' % (self._layout))
+ logging.debug('FavoritesSetting layout %r', self._layout)
self._mode = None
@@ -704,7 +701,7 @@ class FavoritesSetting(object):
return self._layout
def set_layout(self, layout):
- logging.debug('set_layout %r %r' % (layout, self._layout))
+ logging.debug('set_layout %r %r', layout, self._layout)
if layout != self._layout:
self._layout = layout
diff --git a/src/jarabe/desktop/homebox.py b/src/jarabe/desktop/homebox.py
index 6fdc8f1..604001e 100644
--- a/src/jarabe/desktop/homebox.py
+++ b/src/jarabe/desktop/homebox.py
@@ -27,7 +27,6 @@ from sugar.graphics.radiotoolbutton import RadioToolButton
from sugar.graphics.alert import Alert
from sugar.graphics.icon import Icon
-from jarabe.model import bundleregistry
from jarabe.desktop import favoritesview
from jarabe.desktop.activitieslist import ActivitiesList
@@ -47,10 +46,6 @@ class HomeBox(gtk.VBox):
self._favorites_view = favoritesview.FavoritesView()
self._list_view = ActivitiesList()
- self._favorites_view.connect('erase-activated',
- self.__erase_activated_cb)
- self._list_view.connect('erase-activated', self.__erase_activated_cb)
-
self._toolbar = HomeToolbar()
self._toolbar.connect('query-changed', self.__toolbar_query_changed_cb)
self._toolbar.connect('view-changed', self.__toolbar_view_changed_cb)
@@ -58,44 +53,6 @@ class HomeBox(gtk.VBox):
self._toolbar.show()
self._set_view(_FAVORITES_VIEW)
-
- def __erase_activated_cb(self, view, bundle_id):
- registry = bundleregistry.get_registry()
- activity_info = registry.get_bundle(bundle_id)
-
- alert = Alert()
- alert.props.title = _('Confirm erase')
- alert.props.msg = \
- _('Confirm erase: Do you want to permanently erase %s?') \
- % activity_info.get_name()
-
- cancel_icon = Icon(icon_name='dialog-cancel')
- alert.add_button(gtk.RESPONSE_CANCEL, _('Keep'), cancel_icon)
-
- erase_icon = Icon(icon_name='dialog-ok')
- alert.add_button(gtk.RESPONSE_OK, _('Erase'), erase_icon)
-
- if self._list_view in self.get_children():
- self._list_view.add_alert(alert)
- else:
- self._favorites_view.add_alert(alert)
- # TODO: If the favorite layouts didn't hardcoded the box size, we could
- # just pack an alert between the toolbar and the canvas.
- #self.pack_start(alert, False)
- #self.reorder_child(alert, 1)
- alert.connect('response', self.__erase_confirmation_dialog_response_cb,
- bundle_id)
-
- def __erase_confirmation_dialog_response_cb(self, alert, response_id,
- bundle_id):
- if self._list_view in self.get_children():
- self._list_view.remove_alert()
- else:
- self._favorites_view.remove_alert()
- if response_id == gtk.RESPONSE_OK:
- registry = bundleregistry.get_registry()
- bundle = registry.get_bundle(bundle_id)
- registry.uninstall(bundle)
def show_software_updates_alert(self):
alert = Alert()
@@ -132,8 +89,8 @@ class HomeBox(gtk.VBox):
try:
os.unlink(update_trigger_file)
except OSError:
- logging.error('Software-update: Can not remove file %s' %
- update_trigger_file)
+ logging.error('Software-update: Can not remove file %s',
+ update_trigger_file)
if response_id == gtk.RESPONSE_OK:
from jarabe.controlpanel.gui import ControlPanel
@@ -184,6 +141,8 @@ class HomeBox(gtk.VBox):
def focus_search_entry(self):
self._toolbar.search_entry.grab_focus()
+ def set_resume_mode(self, resume_mode):
+ self._favorites_view.set_resume_mode(resume_mode)
class HomeToolbar(gtk.Toolbar):
__gtype_name__ = 'SugarHomeToolbar'
diff --git a/src/jarabe/desktop/homewindow.py b/src/jarabe/desktop/homewindow.py
index bbb0db1..6212970 100644
--- a/src/jarabe/desktop/homewindow.py
+++ b/src/jarabe/desktop/homewindow.py
@@ -141,3 +141,12 @@ class HomeWindow(gtk.Window):
def get_home_box(self):
return self._home_box
+
+_instance = None
+
+def get_instance():
+ global _instance
+ if not _instance:
+ _instance = HomeWindow()
+ return _instance
+
diff --git a/src/jarabe/desktop/keydialog.py b/src/jarabe/desktop/keydialog.py
index f5995e6..b9b229b 100644
--- a/src/jarabe/desktop/keydialog.py
+++ b/src/jarabe/desktop/keydialog.py
@@ -14,7 +14,7 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-import md5
+import hashlib
from gettext import gettext as _
import gtk
@@ -53,7 +53,7 @@ def hash_passphrase(passphrase):
elif len(passphrase) < 64:
while len(passphrase) < 64:
passphrase += passphrase[:64 - len(passphrase)]
- passphrase = md5.new(passphrase).digest()
+ passphrase = hashlib.md5(passphrase).digest()
return string_to_hex(passphrase)[:26]
class CanceledKeyRequestError(dbus.DBusException):
@@ -239,7 +239,7 @@ class WPAKeyDialog(KeyDialog):
elif len(key) >= 8 and len(key) <= 63:
# passphrase
from subprocess import Popen, PIPE
- p = Popen(['/usr/sbin/wpa_passphrase', ssid, key], stdout=PIPE)
+ p = Popen(['wpa_passphrase', ssid, key], stdout=PIPE)
for line in p.stdout:
if line.strip().startswith("psk="):
real_key = line.strip()[4:]
diff --git a/src/jarabe/desktop/meshbox.py b/src/jarabe/desktop/meshbox.py
index abbdaa6..ba9c103 100644
--- a/src/jarabe/desktop/meshbox.py
+++ b/src/jarabe/desktop/meshbox.py
@@ -1,4 +1,5 @@
# Copyright (C) 2006-2007 Red Hat, Inc.
+# Copyright (C) 2009 Tomeu Vizoso, Simon Schampijer
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -16,7 +17,7 @@
from gettext import gettext as _
import logging
-import sha
+import hashlib
import dbus
import hippo
@@ -46,6 +47,7 @@ from jarabe.model import bundleregistry
from jarabe.model import network
from jarabe.model import shell
from jarabe.model.network import Settings
+from jarabe.model.network import IP4Config
from jarabe.model.network import WirelessSecurity
_NM_SERVICE = 'org.freedesktop.NetworkManager'
@@ -74,7 +76,7 @@ class AccessPointView(CanvasPulsingIcon):
self._flags = 0
self._wpa_flags = 0
self._rsn_flags = 0
- self._mode = 0
+ self._mode = network.NM_802_11_MODE_UNKNOWN
self._device_caps = 0
self._device_state = None
self._connection = None
@@ -156,8 +158,12 @@ class AccessPointView(CanvasPulsingIcon):
self._update_state()
def _update_properties(self, properties):
+ if 'Mode' in properties:
+ self._mode = properties['Mode']
+ self._color = None
if 'Ssid' in properties:
self._name = properties['Ssid']
+ self._color = None
if 'Strength' in properties:
self._strength = properties['Strength']
if 'Flags' in properties:
@@ -166,18 +172,22 @@ class AccessPointView(CanvasPulsingIcon):
self._wpa_flags = properties['WpaFlags']
if 'RsnFlags' in properties:
self._rsn_flags = properties['RsnFlags']
- if 'Mode' in properties:
- self._mode = properties['Mode']
-
- sh = sha.new()
- data = self._name + hex(self._flags)
- sh.update(data)
- h = hash(sh.digest())
- idx = h % len(xocolor.colors)
-
- self._color = XoColor('%s,%s' % (xocolor.colors[idx][0],
- xocolor.colors[idx][1]))
+ if self._color == None:
+ if self._mode == network.NM_802_11_MODE_ADHOC:
+ encoded_color = self._name.split("#", 1)
+ if len(encoded_color) == 2:
+ self._color = XoColor('#' + encoded_color[1])
+ if self._mode == network.NM_802_11_MODE_INFRA:
+ sha_hash = hashlib.sha1()
+ data = self._name + hex(self._flags)
+ sha_hash.update(data)
+ digest = hash(sha_hash.digest())
+ index = digest % len(xocolor.colors)
+
+ self._color = XoColor('%s,%s' %
+ (xocolor.colors[index][0],
+ xocolor.colors[index][1]))
self._update()
def __get_active_ap_reply_cb(self, ap):
@@ -228,7 +238,8 @@ class AccessPointView(CanvasPulsingIcon):
if state == network.DEVICE_STATE_ACTIVATED:
connection = network.find_connection(self._name)
if connection:
- connection.set_connected()
+ if self._mode == network.NM_802_11_MODE_INFRA:
+ connection.set_connected()
icon_name = '%s-connected' % _ICON_NAME
else:
@@ -357,6 +368,9 @@ class AccessPointView(CanvasPulsingIcon):
settings.wireless.mode = 'infrastructure'
elif self._mode == network.NM_802_11_MODE_ADHOC:
settings.wireless.mode = 'adhoc'
+ settings.wireless.band = 'bg'
+ settings.ip4_config = IP4Config()
+ settings.ip4_config.method = 'shared'
wireless_security = self._get_security()
settings.wireless_security = wireless_security
@@ -862,7 +876,7 @@ class MeshBox(gtk.VBox):
self._layout.remove(icon)
del self.access_points[ap_o]
else:
- logging.error('Can not remove access point %s' % ap_o)
+ logging.error('Can not remove access point %s', ap_o)
def suspend(self):
if not self._suspended:
diff --git a/src/jarabe/desktop/proc_smaps.py b/src/jarabe/desktop/proc_smaps.py
deleted file mode 100755
index 090a4cf..0000000
--- a/src/jarabe/desktop/proc_smaps.py
+++ /dev/null
@@ -1,107 +0,0 @@
-# Copyright (C) 2007 Red Hat, Inc.
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
-# USA
-
-import os
-
-# /proc/PID/maps consists of a number of lines like this:
-# 00400000-004b1000 r-xp 00000000 fd:00 5767206 /bin/bash
-# 006b1000-006bb000 rw-p 000b1000 fd:00 5767206 /bin/bash
-# 006bb000-006c0000 rw-p 006bb000 00:00 0
-# ...
-# The fields are: address, permissions, offset, device, inode, and
-# (for non-anonymous mappings) pathname.
-#
-# /proc/PID/smaps gives additional information for each mapping:
-# 00400000-004b1000 r-xp 00000000 fd:00 5767206 /bin/bash
-# Size: 708 kB
-# Rss: 476 kB
-# Shared_Clean: 468 kB
-# Shared_Dirty: 0 kB
-# Private_Clean: 8 kB
-# Private_Dirty: 0 kB
-# Referenced: 0 kb
-#
-# The "Referenced" line only appears in kernel 2.6.22 and later.
-
-def get_shared_mapping_names(pid):
- """Returns a set of the files for which PID has a shared mapping"""
-
- mappings = set()
- infile = open("/proc/%s/maps" % pid, "r")
- for line in infile:
- # sharable mappings are non-anonymous and either read-only
- # (permissions "r-..") or writable but explicitly marked
- # shared ("rw.s")
- fields = line.split()
- if len(fields) < 6 or not fields[5].startswith('/'):
- continue
- if fields[1][0] != 'r' or (fields[1][1] == 'w' and fields[1][3] != 's'):
- continue
- mappings.add(fields[5])
- infile.close()
- return mappings
-
-_smaps_lines_per_entry = None
-
-def get_mappings(pid, ignored_shared_mappings):
- """Returns a list of (name, private, shared) tuples describing the
- memory mappings of PID. Shared mappings named in
- ignored_shared_mappings are ignored
- """
-
- global _smaps_lines_per_entry
- if _smaps_lines_per_entry is None:
- if os.path.isfile('/proc/%s/clear_refs' % os.getpid()):
- _smaps_lines_per_entry = 8
- else:
- _smaps_lines_per_entry = 7
-
- mappings = []
-
- smapfile = "/proc/%s/smaps" % pid
- infile = open(smapfile, "r")
- data = infile.read()
- infile.close()
- lines = data.splitlines()
-
- for line_idx in range(0, len(lines), _smaps_lines_per_entry):
- name_idx = lines[line_idx].find('/')
- if name_idx == -1:
- name = None
- else:
- name = lines[line_idx][name_idx:]
-
- private_clean = int(lines[line_idx + 5][14:-3])
- private_dirty = int(lines[line_idx + 6][14:-3])
- if name in ignored_shared_mappings:
- shared_clean = 0
- shared_dirty = 0
- else:
- shared_clean = int(lines[line_idx + 3][14:-3])
- shared_dirty = int(lines[line_idx + 4][14:-3])
-
- mapping = Mapping(name, private_clean + private_dirty,
- shared_clean + shared_dirty)
- mappings.append (mapping)
-
- return mappings
-
-class Mapping:
- def __init__ (self, name, private, shared):
- self.name = name
- self.private = private
- self.shared = shared
diff --git a/src/jarabe/desktop/schoolserver.py b/src/jarabe/desktop/schoolserver.py
index 1dd9edc..2df2a40 100644
--- a/src/jarabe/desktop/schoolserver.py
+++ b/src/jarabe/desktop/schoolserver.py
@@ -46,12 +46,12 @@ def register_laptop(url=REGISTER_URL):
server = ServerProxy(url)
try:
data = server.register(sn, nick, uuid, profile.pubkey)
- except (Error, socket.error), e:
- logging.error('Registration: cannot connect to server: %s' % e)
+ except (Error, socket.error):
+ logging.exception('Registration: cannot connect to server')
raise RegisterError(_('Cannot connect to the server.'))
if data['success'] != 'OK':
- logging.error('Registration: server could not complete request: %s' %
+ logging.error('Registration: server could not complete request: %s',
data['error'])
raise RegisterError(_('The server could not complete the request.'))
diff --git a/src/jarabe/frame/activitiestray.py b/src/jarabe/frame/activitiestray.py
index 1e2b8e8..390cf88 100644
--- a/src/jarabe/frame/activitiestray.py
+++ b/src/jarabe/frame/activitiestray.py
@@ -335,7 +335,7 @@ class ActivitiesTray(HTray):
filetransfer.new_file_transfer.connect(self.__new_file_transfer_cb)
def __activity_added_cb(self, home_model, home_activity):
- logging.debug('__activity_added_cb: %r' % home_activity)
+ logging.debug('__activity_added_cb: %r', home_activity)
if self.get_children():
group = self.get_children()[0]
else:
@@ -348,7 +348,7 @@ class ActivitiesTray(HTray):
button.show()
def __activity_removed_cb(self, home_model, home_activity):
- logging.debug('__activity_removed_cb: %r' % home_activity)
+ logging.debug('__activity_removed_cb: %r', home_activity)
button = self._buttons[home_activity.get_activity_id()]
self.remove_item(button)
del self._buttons[home_activity.get_activity_id()]
@@ -366,14 +366,14 @@ class ActivitiesTray(HTray):
self.window.process_updates(True)
def __activity_changed_cb(self, home_model, home_activity):
- logging.debug('__activity_changed_cb: %r' % home_activity)
+ logging.debug('__activity_changed_cb: %r', home_activity)
# Only select the new activity, if there is no tabbing activity.
if home_model.get_tabbing_activity() is None:
self._activate_activity(home_activity)
def __tabbing_activity_changed_cb(self, home_model, home_activity):
- logging.debug('__tabbing_activity_changed_cb: %r' % home_activity)
+ logging.debug('__tabbing_activity_changed_cb: %r', home_activity)
# If the tabbing_activity is set to None just do nothing.
# The active activity will be updated a bit later (and it will
# be set to the activity that is currently selected).
@@ -424,7 +424,7 @@ class ActivitiesTray(HTray):
def __new_file_transfer_cb(self, **kwargs):
file_transfer = kwargs['file_transfer']
- logging.debug('__new_file_transfer_cb %r' % file_transfer)
+ logging.debug('__new_file_transfer_cb %r', file_transfer)
if isinstance(file_transfer, filetransfer.IncomingFileTransfer):
button = IncomingTransferButton(file_transfer)
@@ -463,8 +463,8 @@ class BaseTransferButton(ToolButton):
self.props.parent.remove(self)
def __notify_state_cb(self, file_transfer, pspec):
- logging.debug('_update state: %r %r' % (file_transfer.props.state,
- file_transfer.reason_last_change))
+ logging.debug('_update state: %r %r', file_transfer.props.state,
+ file_transfer.reason_last_change)
if file_transfer.props.state == filetransfer.FT_STATE_CANCELLED:
if file_transfer.reason_last_change == \
filetransfer.FT_REASON_LOCAL_STOPPED:
@@ -565,10 +565,10 @@ class IncomingTransferButton(BaseTransferButton):
return dbus.Interface(remote_object, DS_DBUS_INTERFACE)
def __reply_handler_cb(self):
- logging.debug('__reply_handler_cb %r' % self._object_id)
+ logging.debug('__reply_handler_cb %r', self._object_id)
def __error_handler_cb(self, error):
- logging.debug('__error_handler_cb %r %s' % (self._object_id, error))
+ logging.debug('__error_handler_cb %r %s', self._object_id, error)
def __dismiss_clicked_cb(self, palette):
self.remove()
@@ -654,7 +654,7 @@ class BaseTransferPalette(Palette):
return _('%dMB') % (size / 1048576)
def update_progress(self):
- logging.debug('update_progress: %r' %
+ logging.debug('update_progress: %r',
self.file_transfer.props.transferred_bytes)
if self.progress_bar is None:
@@ -663,7 +663,7 @@ class BaseTransferPalette(Palette):
self.progress_bar.props.fraction = \
self.file_transfer.props.transferred_bytes / \
float(self.file_transfer.file_size)
- logging.debug('update_progress: %r' % self.progress_bar.props.fraction)
+ logging.debug('update_progress: %r', self.progress_bar.props.fraction)
transferred = self._format_size(
self.file_transfer.props.transferred_bytes)
@@ -688,7 +688,7 @@ class IncomingTransferPalette(BaseTransferPalette):
self._update()
def _update(self):
- logging.debug('_update state: %r' % self.file_transfer.props.state)
+ logging.debug('_update state: %r', self.file_transfer.props.state)
if self.file_transfer.props.state == filetransfer.FT_STATE_PENDING:
menu_item = MenuItem(_('Accept'), icon_name='dialog-ok')
menu_item.connect('activate', self.__accept_activate_cb)
@@ -815,8 +815,9 @@ class OutgoingTransferPalette(BaseTransferPalette):
self._update()
def _update(self):
- logging.debug('_update state: %r' % self.file_transfer.props.state)
- if self.file_transfer.props.state == filetransfer.FT_STATE_PENDING:
+ new_state = self.file_transfer.props.state
+ logging.debug('_update state: %r', new_state)
+ if new_state == filetransfer.FT_STATE_PENDING:
menu_item = MenuItem(_('Cancel'), icon_name='dialog-cancel')
menu_item.connect('activate', self.__cancel_activate_cb)
@@ -840,8 +841,8 @@ class OutgoingTransferPalette(BaseTransferPalette):
vbox.add(label)
label.show()
- elif self.file_transfer.props.state in \
- [filetransfer.FT_STATE_ACCEPTED, filetransfer.FT_STATE_OPEN]:
+ elif new_state in [filetransfer.FT_STATE_ACCEPTED,
+ filetransfer.FT_STATE_OPEN]:
for item in self.menu.get_children():
self.menu.remove(item)
@@ -865,7 +866,8 @@ class OutgoingTransferPalette(BaseTransferPalette):
self.update_progress()
- elif self.file_transfer.props.state == filetransfer.FT_STATE_COMPLETED:
+ elif new_state in [filetransfer.FT_STATE_COMPLETED,
+ filetransfer.FT_STATE_CANCELLED]:
for item in self.menu.get_children():
self.menu.remove(item)
diff --git a/src/jarabe/frame/clipboard.py b/src/jarabe/frame/clipboard.py
index 0e3e125..1cefcc1 100644
--- a/src/jarabe/frame/clipboard.py
+++ b/src/jarabe/frame/clipboard.py
@@ -59,9 +59,9 @@ class Clipboard(gobject.GObject):
cb_object = self._objects[object_id]
if format_type == 'XdndDirectSave0':
- format = Format('text/uri-list', data + '\r\n', on_disk)
- format.owns_disk_data = True
- cb_object.add_format(format)
+ format_ = Format('text/uri-list', data + '\r\n', on_disk)
+ format_.owns_disk_data = True
+ cb_object.add_format(format_)
elif on_disk and cb_object.get_percent() == 100:
new_uri = self._copy_file(data)
cb_object.add_format(Format(format_type, new_uri, on_disk))
@@ -77,7 +77,7 @@ class Clipboard(gobject.GObject):
cb_object = self._objects.pop(object_id)
cb_object.destroy()
self.emit('object-deleted', object_id)
- logging.debug('Deleted object with object_id %r' % object_id)
+ logging.debug('Deleted object with object_id %r', object_id)
def set_object_percent(self, object_id, percent):
cb_object = self._objects[object_id]
@@ -98,10 +98,10 @@ class Clipboard(gobject.GObject):
def _process_object(self, cb_object):
formats = cb_object.get_formats()
- for format_name, format in formats.iteritems():
- if format.is_on_disk() and not format.owns_disk_data:
- new_uri = self._copy_file(format.get_data())
- format.set_data(new_uri)
+ for format_name, format_ in formats.iteritems():
+ if format_.is_on_disk() and not format_.owns_disk_data:
+ new_uri = self._copy_file(format_.get_data())
+ format_.set_data(new_uri)
# Add a text/plain format to objects that are text but lack it
if 'text/plain' not in formats.keys():
@@ -121,8 +121,8 @@ class Clipboard(gobject.GObject):
def get_object_data(self, object_id, format_type):
logging.debug('Clipboard.get_object_data')
cb_object = self._objects[object_id]
- format = cb_object.get_formats()[format_type]
- return format
+ format_ = cb_object.get_formats()[format_type]
+ return format_
def _copy_file(self, original_uri):
uri = urlparse.urlparse(original_uri)
diff --git a/src/jarabe/frame/clipboardicon.py b/src/jarabe/frame/clipboardicon.py
index ff63ad9..93a177e 100644
--- a/src/jarabe/frame/clipboardicon.py
+++ b/src/jarabe/frame/clipboardicon.py
@@ -96,7 +96,7 @@ class ClipboardIcon(RadioToolButton):
def _clipboard_data_get_cb(self, x_clipboard, selection, info, targets):
if not selection.target in [target[0] for target in targets]:
logging.warning('ClipboardIcon._clipboard_data_get_cb: asked %s' \
- ' but only have %r.' % (selection.target, targets))
+ ' but only have %r.', selection.target, targets)
return
data = self._cb_object.get_formats()[selection.target].get_data()
selection.set(selection.target, 8, data)
diff --git a/src/jarabe/frame/clipboardmenu.py b/src/jarabe/frame/clipboardmenu.py
index d1d4105..e2eba68 100644
--- a/src/jarabe/frame/clipboardmenu.py
+++ b/src/jarabe/frame/clipboardmenu.py
@@ -74,7 +74,7 @@ class ClipboardMenu(Palette):
def _update_open_submenu(self):
activities = self._get_activities()
- logging.debug('_update_open_submenu: %r' % activities)
+ logging.debug('_update_open_submenu: %r', activities)
child = self._open_item.get_child()
if activities is None or len(activities) <= 1:
child.set_text(_('Open'))
@@ -97,7 +97,7 @@ class ClipboardMenu(Palette):
activity_info = registry.get_bundle(service_name)
if not activity_info:
- logging.warning('Activity %s is unknown.' % service_name)
+ logging.warning('Activity %s is unknown.', service_name)
item = gtk.MenuItem(activity_info.get_name())
item.connect('activate', self._open_submenu_item_activate_cb,
@@ -204,26 +204,26 @@ class ClipboardMenu(Palette):
def _copy_to_journal(self):
formats = self._cb_object.get_formats().keys()
most_significant_mime_type = mime.choose_most_significant(formats)
- format = self._cb_object.get_formats()[most_significant_mime_type]
+ format_ = self._cb_object.get_formats()[most_significant_mime_type]
transfer_ownership = False
if most_significant_mime_type == 'text/uri-list':
- uris = mime.split_uri_list(format.get_data())
+ uris = mime.split_uri_list(format_.get_data())
if len(uris) == 1 and uris[0].startswith('file://'):
file_path = urlparse.urlparse(uris[0]).path
transfer_ownership = False
mime_type = mime.get_for_file(file_path)
else:
- file_path = self._write_to_temp_file(format.get_data())
+ file_path = self._write_to_temp_file(format_.get_data())
transfer_ownership = True
mime_type = 'text/uri-list'
else:
- if format.is_on_disk():
- file_path = urlparse.urlparse(format.get_data()).path
+ if format_.is_on_disk():
+ file_path = urlparse.urlparse(format_.get_data()).path
transfer_ownership = False
mime_type = mime.get_for_file(file_path)
else:
- file_path = self._write_to_temp_file(format.get_data())
+ file_path = self._write_to_temp_file(format_.get_data())
transfer_ownership = True
sniffed_mime_type = mime.get_for_file(file_path)
if sniffed_mime_type == 'application/octet-stream':
diff --git a/src/jarabe/frame/clipboardobject.py b/src/jarabe/frame/clipboardobject.py
index 38da151..d6a55db 100644
--- a/src/jarabe/frame/clipboardobject.py
+++ b/src/jarabe/frame/clipboardobject.py
@@ -33,8 +33,8 @@ class ClipboardObject(object):
self._formats = {}
def destroy(self):
- for format in self._formats.itervalues():
- format.destroy()
+ for format_ in self._formats.itervalues():
+ format_.destroy()
def get_id(self):
return self._id
@@ -91,8 +91,8 @@ class ClipboardObject(object):
def set_percent(self, percent):
self._percent = percent
- def add_format(self, format):
- self._formats[format.get_type()] = format
+ def add_format(self, format_):
+ self._formats[format_.get_type()] = format_
def get_formats(self):
return self._formats
@@ -101,18 +101,18 @@ class ClipboardObject(object):
if not self._formats:
return ''
- format = mime.choose_most_significant(self._formats.keys())
- if format == 'text/uri-list':
+ format_ = mime.choose_most_significant(self._formats.keys())
+ if format_ == 'text/uri-list':
data = self._formats['text/uri-list'].get_data()
uri = urlparse.urlparse(mime.split_uri_list(data)[0], 'file')
if uri.scheme == 'file':
if os.path.exists(uri.path):
- format = mime.get_for_file(uri.path)
+ format_ = mime.get_for_file(uri.path)
else:
- format = mime.get_from_file_name(uri.path)
- logging.debug('Choosed %r!' % format)
+ format_ = mime.get_from_file_name(uri.path)
+ logging.debug('Chose %r!', format_)
- return format
+ return format_
class Format(object):
diff --git a/src/jarabe/frame/clipboardpanelwindow.py b/src/jarabe/frame/clipboardpanelwindow.py
index 7093199..7d08d32 100644
--- a/src/jarabe/frame/clipboardpanelwindow.py
+++ b/src/jarabe/frame/clipboardpanelwindow.py
@@ -63,10 +63,10 @@ class ClipboardPanelWindow(FrameWindow):
for target in targets:
if target not in ('TIMESTAMP', 'TARGETS',
'MULTIPLE', 'SAVE_TARGETS'):
- logging.debug('Asking for target %s.' % target)
+ logging.debug('Asking for target %s.', target)
selection = x_clipboard.wait_for_contents(target)
if not selection:
- logging.warning('no data for selection target %s.' % target)
+ logging.warning('no data for selection target %s.', target)
continue
self._add_selection(key, selection)
@@ -74,7 +74,7 @@ class ClipboardPanelWindow(FrameWindow):
def _add_selection(self, key, selection):
if not selection.data:
- logging.warning('no data for selection target %s.' % selection.type)
+ logging.warning('no data for selection target %s.', selection.type)
return
logging.debug('adding type ' + selection.type + '.')
diff --git a/src/jarabe/frame/clipboardtray.py b/src/jarabe/frame/clipboardtray.py
index 40f0a32..75c1ecf 100644
--- a/src/jarabe/frame/clipboardtray.py
+++ b/src/jarabe/frame/clipboardtray.py
@@ -79,7 +79,7 @@ class ClipboardTray(tray.VTray):
if not selection.data:
return
- logging.debug('ClipboardTray: adding type %r' % selection.type)
+ logging.debug('ClipboardTray: adding type %r', selection.type)
cb_service = clipboard.get_instance()
if selection.type == 'text/uri-list':
@@ -115,13 +115,13 @@ class ClipboardTray(tray.VTray):
cb_service = clipboard.get_instance()
cb_service.delete_object(icon.get_object_id())
- logging.debug('ClipboardTray: %r was added' % cb_object.get_id())
+ logging.debug('ClipboardTray: %r was added', cb_object.get_id())
def _object_deleted_cb(self, cb_service, object_id):
icon = self._icons[object_id]
self.remove_item(icon)
del self._icons[object_id]
- logging.debug('ClipboardTray: %r was deleted' % object_id)
+ logging.debug('ClipboardTray: %r was deleted', object_id)
def drag_motion_cb(self, widget, context, x, y, time):
logging.debug('ClipboardTray._drag_motion_cb')
@@ -153,7 +153,7 @@ class ClipboardTray(tray.VTray):
if 'XdndDirectSave0' in context.targets:
window = context.source_window
- prop_type, format, filename = \
+ prop_type, format_, filename = \
window.property_get('XdndDirectSave0','text/plain')
# FIXME query the clipboard service for a filename?
@@ -165,7 +165,7 @@ class ClipboardTray(tray.VTray):
dest_uri = 'file://' + os.path.join(base_dir, dest_filename)
- window.property_change('XdndDirectSave0', prop_type, format,
+ window.property_change('XdndDirectSave0', prop_type, format_,
gtk.gdk.PROP_MODE_REPLACE, dest_uri)
widget.drag_get_data(context, 'XdndDirectSave0', time)
@@ -180,14 +180,14 @@ class ClipboardTray(tray.VTray):
def drag_data_received_cb(self, widget, context, x, y, selection,
targetType, time):
- logging.debug('ClipboardTray: got data for target %r'
- % selection.target)
+ logging.debug('ClipboardTray: got data for target %r',
+ selection.target)
object_id = self._context_map.get_object_id(context)
try:
if selection is None:
- logging.warn('ClipboardTray: empty selection for target %s'
- % selection.target)
+ logging.warn('ClipboardTray: empty selection for target %s',
+ selection.target)
elif selection.target == 'XdndDirectSave0':
if selection.data == 'S':
window = context.source_window
diff --git a/src/jarabe/frame/frame.py b/src/jarabe/frame/frame.py
index 0bb8d92..1e02fc8 100644
--- a/src/jarabe/frame/frame.py
+++ b/src/jarabe/frame/frame.py
@@ -318,7 +318,7 @@ class Frame(object):
def remove_notification(self, icon):
if icon not in self._notif_by_icon:
- logging.debug('icon %r not in list of notifications.' % icon)
+ logging.debug('icon %r not in list of notifications.', icon)
return
window = self._notif_by_icon[icon]
@@ -326,7 +326,7 @@ class Frame(object):
del self._notif_by_icon[icon]
def __notification_received_cb(self, **kwargs):
- logging.debug('__notification_received_cb %r' % kwargs)
+ logging.debug('__notification_received_cb %r', kwargs)
icon = NotificationIcon()
hints = kwargs['hints']
diff --git a/src/jarabe/frame/framewindow.py b/src/jarabe/frame/framewindow.py
index 02bb131..87eefb9 100644
--- a/src/jarabe/frame/framewindow.py
+++ b/src/jarabe/frame/framewindow.py
@@ -93,7 +93,8 @@ class FrameWindow(gtk.Window):
self.resize(self.size, gtk.gdk.screen_height())
def _realize_cb(self, widget):
- self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
+ self.window.set_override_redirect(True)
+ self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DOCK)
self.window.set_accept_focus(False)
def _enter_notify_cb(self, window, event):
diff --git a/src/jarabe/frame/zoomtoolbar.py b/src/jarabe/frame/zoomtoolbar.py
index 43cc358..4f44600 100644
--- a/src/jarabe/frame/zoomtoolbar.py
+++ b/src/jarabe/frame/zoomtoolbar.py
@@ -1,4 +1,5 @@
# Copyright (C) 2006-2007 Red Hat, Inc.
+# Copyright (C) 2009 Simon Schampijer
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -33,25 +34,26 @@ class ZoomToolbar(gtk.Toolbar):
self.set_direction(gtk.TEXT_DIR_LTR)
self._mesh_button = self._add_button('zoom-neighborhood',
- _('Neighborhood'), shell.ShellModel.ZOOM_MESH)
+ _('Neighborhood'), _('F1'), shell.ShellModel.ZOOM_MESH)
self._groups_button = self._add_button('zoom-groups',
- _('Group'), shell.ShellModel.ZOOM_GROUP)
+ _('Group'), _('F2'), shell.ShellModel.ZOOM_GROUP)
self._home_button = self._add_button('zoom-home',
- _('Home'), shell.ShellModel.ZOOM_HOME)
+ _('Home'), _('F3'), shell.ShellModel.ZOOM_HOME)
self._activity_button = self._add_button('zoom-activity',
- _('Activity'), shell.ShellModel.ZOOM_ACTIVITY)
+ _('Activity'), _('F4'), shell.ShellModel.ZOOM_ACTIVITY)
shell_model = shell.get_model()
self._set_zoom_level(shell_model.zoom_level)
shell_model.zoom_level_changed.connect(self.__zoom_level_changed_cb)
- def _add_button(self, icon_name, label, zoom_level):
+ def _add_button(self, icon_name, label, accelerator, zoom_level):
if self.get_children():
group = self.get_children()[0]
else:
group = None
- button = RadioToolButton(named_icon=icon_name, group=group)
+ button = RadioToolButton(named_icon=icon_name, group=group,
+ accelerator=accelerator)
button.connect('clicked', self.__level_clicked_cb, zoom_level)
self.add(button)
button.show()
@@ -73,7 +75,7 @@ class ZoomToolbar(gtk.Toolbar):
self._set_zoom_level(kwargs['new_level'])
def _set_zoom_level(self, new_level):
- logging.debug('new zoom level: %r' % new_level)
+ logging.debug('new zoom level: %r', new_level)
if new_level == shell.ShellModel.ZOOM_MESH:
self._mesh_button.props.active = True
elif new_level == shell.ShellModel.ZOOM_GROUP:
diff --git a/src/jarabe/intro/window.py b/src/jarabe/intro/window.py
index 3975900..94c6782 100644
--- a/src/jarabe/intro/window.py
+++ b/src/jarabe/intro/window.py
@@ -55,7 +55,7 @@ def create_profile(name, color=None, pixbuf=None):
cmd = "ssh-keygen -q -t dsa -f %s -C '' -N ''" % keypath
(s, o) = commands.getstatusoutput(cmd)
if s != 0:
- logging.error("Could not generate key pair: %d %s" % (s, o))
+ logging.error("Could not generate key pair: %d %s", s, o)
else:
logging.error("Keypair exists, skip generation.")
@@ -241,6 +241,9 @@ class IntroWindow(gtk.Window):
def __init__(self):
gtk.Window.__init__(self)
+ self.props.decorated = False
+ self.maximize()
+
self._canvas = hippo.Canvas()
self._intro_box = _IntroBox()
self._intro_box.connect('done', self._done_cb)
diff --git a/src/jarabe/journal/Makefile.am b/src/jarabe/journal/Makefile.am
index 5f66480..f4bf273 100644
--- a/src/jarabe/journal/Makefile.am
+++ b/src/jarabe/journal/Makefile.am
@@ -1,13 +1,13 @@
sugardir = $(pythondir)/jarabe/journal
sugar_PYTHON = \
__init__.py \
- collapsedentry.py \
detailview.py \
expandedentry.py \
journalactivity.py \
journalentrybundle.py \
journaltoolbox.py \
keepicon.py \
+ listmodel.py \
listview.py \
misc.py \
modalalert.py \
diff --git a/src/jarabe/journal/__init__.py b/src/jarabe/journal/__init__.py
index e69de29..6373228 100644
--- a/src/jarabe/journal/__init__.py
+++ b/src/jarabe/journal/__init__.py
@@ -0,0 +1,15 @@
+# Copyright (C) 2007, One Laptop Per Child
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
diff --git a/src/jarabe/journal/collapsedentry.py b/src/jarabe/journal/collapsedentry.py
deleted file mode 100644
index 3eeb087..0000000
--- a/src/jarabe/journal/collapsedentry.py
+++ /dev/null
@@ -1,397 +0,0 @@
-# Copyright (C) 2007, One Laptop Per Child
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-
-import logging
-from gettext import gettext as _
-
-import gobject
-import gtk
-import hippo
-import cjson
-
-from sugar.graphics.icon import CanvasIcon
-from sugar.graphics.xocolor import XoColor
-from sugar.graphics import style
-from sugar.graphics.entry import CanvasEntry
-
-from jarabe.journal.keepicon import KeepIcon
-from jarabe.journal.palettes import ObjectPalette, BuddyPalette
-from jarabe.journal import misc
-from jarabe.journal import model
-
-class BuddyIcon(CanvasIcon):
- def __init__(self, buddy, **kwargs):
- CanvasIcon.__init__(self, **kwargs)
- self._buddy = buddy
-
- def create_palette(self):
- return BuddyPalette(self._buddy)
-
-class BuddyList(hippo.CanvasBox):
- def __init__(self, buddies, width):
- hippo.CanvasBox.__init__(self,
- orientation=hippo.ORIENTATION_HORIZONTAL,
- box_width=width,
- xalign=hippo.ALIGNMENT_START)
- self.set_buddies(buddies)
-
- def set_buddies(self, buddies):
- for item in self.get_children():
- self.remove(item)
-
- for buddy in buddies[0:3]:
- nick_, color = buddy
- icon = BuddyIcon(buddy,
- icon_name='computer-xo',
- xo_color=XoColor(color),
- cache=True)
- self.append(icon)
-
-class EntryIcon(CanvasIcon):
-
- __gtype_name__ = 'EntryIcon'
-
- __gsignals__ = {
- 'detail-clicked': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
- ([]))
- }
-
- def __init__(self, **kwargs):
- CanvasIcon.__init__(self, **kwargs)
- self._metadata = None
-
- def set_metadata(self, metadata):
- self._metadata = metadata
- self.props.file_name = misc.get_icon_name(metadata)
- self.palette = None
-
- def create_palette(self):
- if self.show_palette:
- palette = ObjectPalette(self._metadata, detail=True)
- palette.connect('detail-clicked',
- self.__detail_clicked_cb)
- return palette
- else:
- return None
-
- def __detail_clicked_cb(self, event):
- self.emit('detail-clicked')
-
- show_palette = gobject.property(type=bool, default=False)
-
-class BaseCollapsedEntry(hippo.CanvasBox):
- __gtype_name__ = 'BaseCollapsedEntry'
-
- _DATE_COL_WIDTH = style.GRID_CELL_SIZE * 3
- _BUDDIES_COL_WIDTH = style.GRID_CELL_SIZE * 3
- _PROGRESS_COL_WIDTH = style.GRID_CELL_SIZE * 5
-
- def __init__(self):
- hippo.CanvasBox.__init__(self,
- spacing=style.DEFAULT_SPACING,
- padding_top=style.DEFAULT_PADDING,
- padding_bottom=style.DEFAULT_PADDING,
- padding_left=style.DEFAULT_PADDING * 2,
- padding_right=style.DEFAULT_PADDING * 2,
- box_height=style.GRID_CELL_SIZE,
- orientation=hippo.ORIENTATION_HORIZONTAL)
-
- self._metadata = None
- self._is_selected = False
-
- self.keep_icon = self._create_keep_icon()
- self.append(self.keep_icon)
-
- self.icon = self._create_icon()
- self.append(self.icon)
-
- self.title = self._create_title()
- self.append(self.title, hippo.PACK_EXPAND)
-
- self.buddies_list = self._create_buddies_list()
- self.append(self.buddies_list)
-
- self.date = self._create_date()
- self.append(self.date)
-
- # Progress controls
- self.progress_bar = self._create_progress_bar()
- self.append(self.progress_bar)
-
- self.cancel_button = self._create_cancel_button()
- self.append(self.cancel_button)
-
- def _create_keep_icon(self):
- keep_icon = KeepIcon(False)
- keep_icon.connect('button-release-event',
- self.__keep_icon_button_release_event_cb)
- return keep_icon
-
- def _create_date(self):
- date = hippo.CanvasText(text='',
- xalign=hippo.ALIGNMENT_START,
- font_desc=style.FONT_NORMAL.get_pango_desc(),
- box_width=self._DATE_COL_WIDTH)
- return date
-
- def _create_icon(self):
- icon = EntryIcon(size=style.STANDARD_ICON_SIZE, cache=True)
- return icon
-
- def _create_title(self):
- # TODO: We'd prefer to ellipsize in the middle
- title = hippo.CanvasText(text='',
- xalign=hippo.ALIGNMENT_START,
- font_desc=style.FONT_BOLD.get_pango_desc(),
- size_mode=hippo.CANVAS_SIZE_ELLIPSIZE_END)
- return title
-
- def _create_buddies_list(self):
- return BuddyList([], self._BUDDIES_COL_WIDTH)
-
- def _create_progress_bar(self):
- progress_bar = gtk.ProgressBar()
- return hippo.CanvasWidget(widget=progress_bar,
- yalign=hippo.ALIGNMENT_CENTER,
- box_width=self._PROGRESS_COL_WIDTH)
-
- def _create_cancel_button(self):
- button = CanvasIcon(icon_name='activity-stop',
- size=style.SMALL_ICON_SIZE,
- box_width=style.GRID_CELL_SIZE)
- button.connect('button-release-event',
- self._cancel_button_release_event_cb)
- return button
-
- def _decode_buddies(self):
- if self.metadata.has_key('buddies') and \
- self.metadata['buddies']:
- buddies = cjson.decode(self.metadata['buddies']).values()
- else:
- buddies = []
- return buddies
-
- def update_visibility(self):
- in_process = self.is_in_progress()
-
- self.buddies_list.set_visible(not in_process)
- self.date.set_visible(not in_process)
-
- self.progress_bar.set_visible(in_process)
- self.cancel_button.set_visible(in_process)
-
- # TODO: determine the appearance of in-progress entries
- def _update_color(self):
- if self.is_in_progress():
- self.props.background_color = style.COLOR_WHITE.get_int()
- else:
- self.props.background_color = style.COLOR_WHITE.get_int()
-
- def is_in_progress(self):
- return self.metadata.has_key('progress') and \
- int(self.metadata['progress']) < 100
-
- def get_keep(self):
- keep = int(self.metadata.get('keep', 0))
- return keep == 1
-
- def __keep_icon_button_release_event_cb(self, button, event):
- logging.debug('__keep_icon_button_release_event_cb')
- metadata = model.get(self._metadata['uid'])
- if self.get_keep():
- metadata['keep'] = 0
- else:
- metadata['keep'] = 1
- model.write(metadata, update_mtime=False)
-
- self.keep_icon.props.keep = self.get_keep()
- self._update_color()
-
- return True
-
- def _cancel_button_release_event_cb(self, button, event):
- logging.debug('_cancel_button_release_event_cb')
- model.delete(self._metadata['uid'])
- return True
-
- def set_selected(self, is_selected):
- self._is_selected = is_selected
- self._update_color()
-
- def set_metadata(self, metadata):
- self._metadata = metadata
- self._is_selected = False
-
- self.keep_icon.props.keep = self.get_keep()
-
- self.date.props.text = misc.get_date(metadata)
-
- self.icon.set_metadata(metadata)
- if misc.is_activity_bundle(metadata):
- self.icon.props.fill_color = style.COLOR_TRANSPARENT.get_svg()
- self.icon.props.stroke_color = style.COLOR_BUTTON_GREY.get_svg()
- else:
- if metadata.has_key('icon-color') and \
- metadata['icon-color']:
- self.icon.props.xo_color = XoColor( \
- metadata['icon-color'])
- else:
- self.icon.props.xo_color = None
-
- if metadata.get('title', ''):
- title_text = metadata['title']
- else:
- title_text = _('Untitled')
- self.title.props.text = title_text
-
- self.buddies_list.set_buddies(self._decode_buddies())
-
- if metadata.has_key('progress'):
- self.progress_bar.props.widget.props.fraction = \
- int(metadata['progress']) / 100.0
-
- self.update_visibility()
- self._update_color()
-
- def get_metadata(self):
- return self._metadata
-
- metadata = property(get_metadata, set_metadata)
-
- def update_date(self):
- self.date.props.text = misc.get_date(self._metadata)
-
-class CollapsedEntry(BaseCollapsedEntry):
- __gtype_name__ = 'CollapsedEntry'
-
- __gsignals__ = {
- 'detail-clicked': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
- ([]))
- }
-
- def __init__(self):
- BaseCollapsedEntry.__init__(self)
-
- self.icon.props.show_palette = True
- self.icon.connect('button-release-event',
- self.__icon_button_release_event_cb)
- self.icon.connect('detail-clicked',
- self.__detail_clicked_palette_cb)
-
- self.title.connect('button_release_event',
- self.__title_button_release_event_cb)
-
- self._title_entry = self._create_title_entry()
- self.insert_after(self._title_entry, self.title, hippo.PACK_EXPAND)
- self._title_entry.set_visible(False)
-
- self._detail_button = self._create_detail_button()
- self._detail_button.connect('motion-notify-event',
- self.__detail_button_motion_notify_event_cb)
- self.append(self._detail_button)
-
- if gtk.widget_get_default_direction() == gtk.TEXT_DIR_RTL:
- self.reverse()
-
- def _create_title_entry(self):
- title_entry = CanvasEntry()
- title_entry.set_background(style.COLOR_WHITE.get_html())
- title_entry.props.widget.connect('focus-out-event',
- self.__title_entry_focus_out_event_cb)
- title_entry.props.widget.connect('activate',
- self.__title_entry_activate_cb)
- title_entry.connect('key-press-event',
- self.__title_entry_key_press_event_cb)
- return title_entry
-
- def _create_detail_button(self):
- button = CanvasIcon(icon_name='go-right',
- size=style.SMALL_ICON_SIZE,
- box_width=style.GRID_CELL_SIZE * 3 / 5,
- fill_color=style.COLOR_BUTTON_GREY.get_svg())
- button.connect('button-release-event',
- self.__detail_button_release_event_cb)
- return button
-
- def update_visibility(self):
- BaseCollapsedEntry.update_visibility(self)
- self._detail_button.set_visible(not self.is_in_progress())
-
- def set_metadata(self, metadata):
- BaseCollapsedEntry.set_metadata(self, metadata)
- self._title_entry.props.text = self.title.props.text
-
- metadata = property(BaseCollapsedEntry.get_metadata, set_metadata)
-
- def _detail_clicked(self):
- if not self.is_in_progress():
- self.emit('detail-clicked')
-
- def __detail_clicked_palette_cb(self, entry):
- self._detail_clicked()
-
- def __detail_button_release_event_cb(self, button, event):
- logging.debug('_detail_button_release_event_cb')
- self._detail_clicked()
- return True
-
- def __detail_button_motion_notify_event_cb(self, button, event):
- if event.detail == hippo.MOTION_DETAIL_ENTER:
- button.props.fill_color = style.COLOR_TOOLBAR_GREY.get_svg()
- elif event.detail == hippo.MOTION_DETAIL_LEAVE:
- button.props.fill_color = style.COLOR_BUTTON_GREY.get_svg()
-
- def __icon_button_release_event_cb(self, button, event):
- logging.debug('__icon_button_release_event_cb')
- misc.resume(self.metadata)
- return True
-
- def __title_button_release_event_cb(self, button, event):
- self.title.set_visible(False)
- self._title_entry.set_visible(True)
- self._title_entry.props.widget.grab_focus()
-
- def __title_entry_focus_out_event_cb(self, entry, event):
- self._apply_title_change(entry.props.text)
-
- def __title_entry_activate_cb(self, entry):
- self._apply_title_change(entry.props.text)
-
- def __title_entry_key_press_event_cb(self, entry, event):
- if event.key == hippo.KEY_ESCAPE:
- self._cancel_title_change()
-
- def _apply_title_change(self, title):
- self._title_entry.set_visible(False)
- self.title.set_visible(True)
-
- if title == '':
- self._cancel_title_change()
- elif self.title.props.text != title:
- self.title.props.text = title
-
- self._metadata = model.get(self._metadata['uid'])
- self._metadata['title'] = title
- self._metadata['title_set_by_user'] = '1'
- model.write(self._metadata, update_mtime=False)
-
- def _cancel_title_change(self):
- self._title_entry.props.text = self.title.props.text
- self._title_entry.set_visible(False)
- self.title.set_visible(True)
-
diff --git a/src/jarabe/journal/expandedentry.py b/src/jarabe/journal/expandedentry.py
index 6ef531b..924f872 100644
--- a/src/jarabe/journal/expandedentry.py
+++ b/src/jarabe/journal/expandedentry.py
@@ -17,6 +17,7 @@
import logging
from gettext import gettext as _
import StringIO
+import time
import hippo
import cairo
@@ -29,6 +30,7 @@ from sugar.graphics.icon import CanvasIcon
from sugar.graphics.xocolor import XoColor
from sugar.graphics.entry import CanvasEntry
from sugar.graphics.canvastextview import CanvasTextView
+from sugar.util import format_size
from jarabe.journal.keepicon import KeepIcon
from jarabe.journal.palettes import ObjectPalette, BuddyPalette
@@ -118,6 +120,9 @@ class ExpandedEntry(hippo.CanvasBox):
self._preview = self._create_preview()
first_column.append(self._preview)
+ technical_box = self._create_technical()
+ first_column.append(technical_box)
+
# Second column
description_box, self._description = self._create_description()
@@ -187,8 +192,8 @@ class ExpandedEntry(hippo.CanvasBox):
try:
surface = cairo.ImageSurface.create_from_png(png_file)
has_preview = True
- except Exception, e:
- logging.error('Error while loading the preview: %r' % e)
+ except Exception:
+ logging.exception('Error while loading the preview')
has_preview = False
else:
has_preview = False
@@ -216,6 +221,37 @@ class ExpandedEntry(hippo.CanvasBox):
box.append(preview_box)
return box
+ def _create_technical(self):
+ vbox = hippo.CanvasBox()
+ vbox.props.spacing = style.DEFAULT_SPACING
+
+ lines = [
+ _('Kind: %s') % (self._metadata.get('mime_type') or _('Unknown'),),
+ _('Date: %s') % (self._format_date(),),
+ _('Size: %s') % (format_size(model.get_file_size(self._metadata['uid'])),),
+ ]
+
+ for line in lines:
+ text = hippo.CanvasText(text=line,
+ font_desc=style.FONT_NORMAL.get_pango_desc())
+ text.props.color = style.COLOR_BUTTON_GREY.get_int()
+
+ if gtk.widget_get_default_direction() == gtk.TEXT_DIR_RTL:
+ text.props.xalign = hippo.ALIGNMENT_END
+ else:
+ text.props.xalign = hippo.ALIGNMENT_START
+
+ vbox.append(text)
+
+ return vbox
+
+ def _format_date(self):
+ if 'timestamp' in self._metadata:
+ timestamp = float(self._metadata['timestamp'])
+ return time.strftime('%x', time.localtime(timestamp))
+ else:
+ return _('No date')
+
def _create_buddy_list(self):
vbox = hippo.CanvasBox()
diff --git a/src/jarabe/journal/journalactivity.py b/src/jarabe/journal/journalactivity.py
index 4ca751c..08a5a0f 100644
--- a/src/jarabe/journal/journalactivity.py
+++ b/src/jarabe/journal/journalactivity.py
@@ -185,8 +185,8 @@ class JournalActivity(Window):
if keyname == 'Escape':
self.show_main_view()
- def __detail_clicked_cb(self, list_view, entry):
- self._show_secondary_view(entry.metadata)
+ def __detail_clicked_cb(self, list_view, object_id):
+ self._show_secondary_view(object_id)
def __clear_clicked_cb(self, list_view):
self._main_toolbox.search_toolbar.clear_query()
@@ -207,9 +207,8 @@ class JournalActivity(Window):
self.set_canvas(self._main_view)
self._main_view.show()
- def _show_secondary_view(self, metadata):
- # Need to get the full set of properties
- metadata = model.get(metadata['uid'])
+ def _show_secondary_view(self, object_id):
+ metadata = model.get(object_id)
try:
self._detail_toolbox.entry_toolbar.set_metadata(metadata)
except Exception:
@@ -233,11 +232,11 @@ class JournalActivity(Window):
if metadata is None:
return False
else:
- self._show_secondary_view(metadata)
+ self._show_secondary_view(object_id)
return True
def __volume_changed_cb(self, volume_toolbar, mount_point):
- logging.debug('Selected volume: %r.' % mount_point)
+ logging.debug('Selected volume: %r.', mount_point)
self._main_toolbox.search_toolbar.set_mount_point(mount_point)
self._main_toolbox.set_current_toolbar(0)
@@ -282,12 +281,12 @@ class JournalActivity(Window):
try:
registry.install(bundle)
except (ZipExtractException, RegistrationException):
- logging.warning('Could not install bundle %s:\n%s' % \
- (bundle.get_path(), traceback.format_exc()))
+ logging.exception('Could not install bundle %s', bundle.get_path())
return
if metadata['mime_type'] == JournalEntryBundle.MIME_TYPE:
model.delete(object_id)
+ return
metadata['bundle_id'] = bundle.get_bundle_id()
model.write(metadata)
@@ -297,14 +296,14 @@ class JournalActivity(Window):
search_toolbar.give_entry_focus()
def __window_state_event_cb(self, window, event):
- logging.debug('window_state_event_cb %r' % self)
+ logging.debug('window_state_event_cb %r', self)
if event.changed_mask & gtk.gdk.WINDOW_STATE_ICONIFIED:
state = event.new_window_state
visible = not state & gtk.gdk.WINDOW_STATE_ICONIFIED
self._list_view.set_is_visible(visible)
def __visibility_notify_event_cb(self, window, event):
- logging.debug('visibility_notify_event_cb %r' % self)
+ logging.debug('visibility_notify_event_cb %r', self)
visible = event.state != gtk.gdk.VISIBILITY_FULLY_OBSCURED
self._list_view.set_is_visible(visible)
diff --git a/src/jarabe/journal/journalentrybundle.py b/src/jarabe/journal/journalentrybundle.py
index 5d4086c..ebe7ec3 100644
--- a/src/jarabe/journal/journalentrybundle.py
+++ b/src/jarabe/journal/journalentrybundle.py
@@ -40,7 +40,7 @@ class JournalEntryBundle(Bundle):
def __init__(self, path):
Bundle.__init__(self, path)
- def install(self):
+ def install(self, install_path):
if os.environ.has_key('SUGAR_ACTIVITY_ROOT'):
install_dir = os.path.join(os.environ['SUGAR_ACTIVITY_ROOT'],
'data')
@@ -62,6 +62,9 @@ class JournalEntryBundle(Bundle):
finally:
shutil.rmtree(bundle_dir, ignore_errors=True)
+ def get_bundle_id(self):
+ return None
+
def _read_metadata(self, bundle_dir):
metadata_path = os.path.join(bundle_dir, '_metadata.json')
if not os.path.exists(metadata_path):
diff --git a/src/jarabe/journal/journaltoolbox.py b/src/jarabe/journal/journaltoolbox.py
index 17a65e6..201bf76 100644
--- a/src/jarabe/journal/journaltoolbox.py
+++ b/src/jarabe/journal/journaltoolbox.py
@@ -1,4 +1,5 @@
# Copyright (C) 2007, One Laptop Per Child
+# Copyright (C) 2009, Walter Bender
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -55,7 +56,8 @@ _ACTION_ANYTHING = 0
_ACTION_EVERYBODY = 0
_ACTION_MY_FRIENDS = 1
_ACTION_MY_CLASS = 2
-
+
+
class MainToolbox(Toolbox):
def __init__(self):
Toolbox.__init__(self)
@@ -72,8 +74,8 @@ class SearchToolbar(gtk.Toolbar):
'query-changed': (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE,
([object]))
- }
-
+ }
+
def __init__(self):
gtk.Toolbar.__init__(self)
@@ -203,7 +205,7 @@ class SearchToolbar(gtk.Toolbar):
date_range = (today_start - timedelta(30), right_now)
elif self._when_search_combo.props.value == _ACTION_PAST_YEAR:
date_range = (today_start - timedelta(356), right_now)
-
+
return (time.mktime(date_range[0].timetuple()),
time.mktime(date_range[1].timetuple()))
@@ -256,7 +258,7 @@ class SearchToolbar(gtk.Toolbar):
break
if what_filter_index == -1:
- logging.warning('what_filter %r not known' % what_filter)
+ logging.warning('what_filter %r not known', what_filter)
else:
self._what_search_combo.set_active(what_filter_index)
@@ -268,17 +270,30 @@ class SearchToolbar(gtk.Toolbar):
try:
self._what_search_combo.remove_all()
# TRANS: Item in a combo box that filters by entry type.
- self._what_search_combo.append_item(_ACTION_ANYTHING, _('Anything'))
+ self._what_search_combo.append_item(_ACTION_ANYTHING,
+ _('Anything'))
registry = bundleregistry.get_registry()
appended_separator = False
+
+ types = mime.get_all_generic_types()
+ for generic_type in types:
+ if not appended_separator:
+ self._what_search_combo.append_separator()
+ appended_separator = True
+ self._what_search_combo.append_item(
+ generic_type.type_id, generic_type.name, generic_type.icon)
+ if generic_type.type_id == current_value:
+ current_value_index = \
+ len(self._what_search_combo.get_model()) - 1
+
+ self._what_search_combo.set_active(current_value_index)
+
+ self._what_search_combo.append_separator()
+
for service_name in model.get_unique_values('activity'):
activity_info = registry.get_bundle(service_name)
if not activity_info is None:
- if not appended_separator:
- self._what_search_combo.append_separator()
- appended_separator = True
-
if os.path.exists(activity_info.get_icon()):
self._what_search_combo.append_item(service_name,
activity_info.get_name(),
@@ -291,18 +306,6 @@ class SearchToolbar(gtk.Toolbar):
if service_name == current_value:
current_value_index = \
len(self._what_search_combo.get_model()) - 1
-
- self._what_search_combo.append_separator()
-
- types = mime.get_all_generic_types()
- for generic_type in types :
- self._what_search_combo.append_item(
- generic_type.type_id, generic_type.name, generic_type.icon)
- if generic_type.type_id == current_value:
- current_value_index = \
- len(self._what_search_combo.get_model()) - 1
-
- self._what_search_combo.set_active(current_value_index)
finally:
self._what_search_combo.handler_unblock(
self._what_combo_changed_sid)
@@ -398,7 +401,7 @@ class EntryToolbar(gtk.Toolbar):
def _refresh_copy_palette(self):
palette = self._copy.get_palette()
-
+
for menu_item in palette.menu.get_children():
palette.menu.remove(menu_item)
menu_item.destroy()
diff --git a/src/jarabe/journal/listmodel.py b/src/jarabe/journal/listmodel.py
new file mode 100644
index 0000000..917fbb1
--- /dev/null
+++ b/src/jarabe/journal/listmodel.py
@@ -0,0 +1,204 @@
+# Copyright (C) 2009, Tomeu Vizoso
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import logging
+
+import cjson
+import gobject
+import gtk
+
+from sugar.graphics.xocolor import XoColor
+from sugar.graphics import style
+from sugar import util
+
+from jarabe.journal import model
+from jarabe.journal import misc
+
+DS_DBUS_SERVICE = 'org.laptop.sugar.DataStore'
+DS_DBUS_INTERFACE = 'org.laptop.sugar.DataStore'
+DS_DBUS_PATH = '/org/laptop/sugar/DataStore'
+
+class ListModel(gtk.GenericTreeModel, gtk.TreeDragSource):
+ __gtype_name__ = 'JournalListModel'
+
+ __gsignals__ = {
+ 'ready': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([])),
+ 'progress': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([])),
+ }
+
+ COLUMN_UID = 0
+ COLUMN_FAVORITE = 1
+ COLUMN_ICON = 2
+ COLUMN_ICON_COLOR = 3
+ COLUMN_TITLE = 4
+ COLUMN_DATE = 5
+ COLUMN_PROGRESS = 6
+ COLUMN_BUDDY_1 = 7
+ COLUMN_BUDDY_2 = 8
+ COLUMN_BUDDY_3 = 9
+
+ _COLUMN_TYPES = {COLUMN_UID: str,
+ COLUMN_FAVORITE: bool,
+ COLUMN_ICON: str,
+ COLUMN_ICON_COLOR: object,
+ COLUMN_TITLE: str,
+ COLUMN_DATE: str,
+ COLUMN_PROGRESS: int,
+ COLUMN_BUDDY_1: object,
+ COLUMN_BUDDY_3: object,
+ COLUMN_BUDDY_2: object}
+
+ _PAGE_SIZE = 10
+
+ def __init__(self, query):
+ gobject.GObject.__init__(self)
+
+ self._last_requested_index = None
+ self._cached_row = None
+ self._result_set = model.find(query, ListModel._PAGE_SIZE)
+ self._temp_drag_file_path = None
+
+ # HACK: The view will tell us that it is resizing so the model can
+ # avoid hitting D-Bus and disk.
+ self.view_is_resizing = False
+
+ self._result_set.ready.connect(self.__result_set_ready_cb)
+ self._result_set.progress.connect(self.__result_set_progress_cb)
+
+ def __result_set_ready_cb(self, **kwargs):
+ self.emit('ready')
+
+ def __result_set_progress_cb(self, **kwargs):
+ self.emit('progress')
+
+ def setup(self):
+ self._result_set.setup()
+
+ def stop(self):
+ self._result_set.stop()
+
+ def get_metadata(self, path):
+ return model.get(self[path][ListModel.COLUMN_UID])
+
+ def on_get_n_columns(self):
+ return len(ListModel._COLUMN_TYPES)
+
+ def on_get_column_type(self, index):
+ return ListModel._COLUMN_TYPES[index]
+
+ def on_iter_n_children(self, iter):
+ if iter == None:
+ return self._result_set.length
+ else:
+ return 0
+
+ def on_get_value(self, index, column):
+ if self.view_is_resizing:
+ return None
+
+ if index == self._last_requested_index:
+ return self._cached_row[column]
+
+ if index >= self._result_set.length:
+ return None
+
+ self._result_set.seek(index)
+ metadata = self._result_set.read()
+
+ self._last_requested_index = index
+ self._cached_row = []
+ self._cached_row.append(metadata['uid'])
+ self._cached_row.append(metadata.get('keep', '0') == '1')
+ self._cached_row.append(misc.get_icon_name(metadata))
+
+ if misc.is_activity_bundle(metadata):
+ xo_color = XoColor('%s,%s' % (style.COLOR_BUTTON_GREY.get_svg(),
+ style.COLOR_TRANSPARENT.get_svg()))
+ else:
+ if metadata.get('icon-color', ''):
+ xo_color = XoColor(metadata['icon-color'])
+ else:
+ xo_color = None
+ self._cached_row.append(xo_color)
+
+ title = gobject.markup_escape_text(metadata.get('title', None))
+ self._cached_row.append('<b>%s</b>' % title)
+
+ timestamp = int(metadata.get('timestamp', 0))
+ self._cached_row.append(util.timestamp_to_elapsed_string(timestamp))
+
+ self._cached_row.append(int(metadata.get('progress', 100)))
+
+ if metadata.get('buddies', ''):
+ buddies = cjson.decode(metadata['buddies']).values()
+ else:
+ buddies = []
+
+ for n in xrange(0, 3):
+ if buddies:
+ nick, color = buddies.pop(0)
+ self._cached_row.append((nick, XoColor(color)))
+ else:
+ self._cached_row.append(None)
+
+ return self._cached_row[column]
+
+ def on_iter_nth_child(self, iter, n):
+ return n
+
+ def on_get_path(self, iter):
+ return (iter)
+
+ def on_get_iter(self, path):
+ return path[0]
+
+ def on_iter_next(self, iter):
+ if iter != None:
+ if iter >= self._result_set.length - 1:
+ return None
+ return iter + 1
+ return None
+
+ def on_get_flags(self):
+ return gtk.TREE_MODEL_ITERS_PERSIST | gtk.TREE_MODEL_LIST_ONLY
+
+ def on_iter_children(self, iter):
+ return None
+
+ def on_iter_has_child(self, iter):
+ return False
+
+ def on_iter_parent(self, iter):
+ return None
+
+ def do_drag_data_get(self, path, selection):
+ uid = self[path][ListModel.COLUMN_UID]
+ if selection.target == 'text/uri-list':
+ # Get hold of a reference so the temp file doesn't get deleted
+ self._temp_drag_file_path = model.get_file(uid)
+ logging.debug('putting %r in selection', self._temp_drag_file_path)
+ selection.set(selection.target, 8, self._temp_drag_file_path)
+ return True
+ elif selection.target == 'journal-object-id':
+ selection.set(selection.target, 8, uid)
+ return True
+
+ return False
+
diff --git a/src/jarabe/journal/listview.py b/src/jarabe/journal/listview.py
index 4578853..251388d 100644
--- a/src/jarabe/journal/listview.py
+++ b/src/jarabe/journal/listview.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2007, One Laptop Per Child
+# Copyright (C) 2009, Tomeu Vizoso
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -15,33 +15,50 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import logging
-import traceback
-import sys
from gettext import gettext as _
import time
-import hippo
import gobject
import gtk
-import dbus
+import hippo
+import gconf
+import pango
from sugar.graphics import style
-from sugar.graphics.icon import CanvasIcon, Icon
+from sugar.graphics.icon import CanvasIcon, Icon, CellRendererIcon
+from sugar.graphics.xocolor import XoColor
+from sugar import util
-from jarabe.journal.collapsedentry import CollapsedEntry
+from jarabe.journal.listmodel import ListModel
+from jarabe.journal.palettes import ObjectPalette, BuddyPalette
from jarabe.journal import model
-
-DS_DBUS_SERVICE = 'org.laptop.sugar.DataStore'
-DS_DBUS_INTERFACE = 'org.laptop.sugar.DataStore'
-DS_DBUS_PATH = '/org/laptop/sugar/DataStore'
+from jarabe.journal import misc
UPDATE_INTERVAL = 300
-EMPTY_JOURNAL = _("Your Journal is empty")
-NO_MATCH = _("No matching entries ")
+MESSAGE_EMPTY_JOURNAL = 0
+MESSAGE_NO_MATCH = 1
-class BaseListView(gtk.HBox):
- __gtype_name__ = 'BaseListView'
+class TreeView(gtk.TreeView):
+ __gtype_name__ = 'JournalTreeView'
+
+ def __init__(self):
+ gtk.TreeView.__init__(self)
+
+ def do_size_request(self, requisition):
+ # HACK: We tell the model that the view is just resizing so it can avoid
+ # hitting both D-Bus and disk.
+ tree_model = self.get_model()
+ if tree_model is not None:
+ tree_model.view_is_resizing = True
+ try:
+ gtk.TreeView.do_size_request(self, requisition)
+ finally:
+ if tree_model is not None:
+ tree_model.view_is_resizing = False
+
+class BaseListView(gtk.Bin):
+ __gtype_name__ = 'JournalBaseListView'
__gsignals__ = {
'clear-clicked': (gobject.SIGNAL_RUN_FIRST,
@@ -51,57 +68,36 @@ class BaseListView(gtk.HBox):
def __init__(self):
self._query = {}
- self._result_set = None
- self._entries = []
- self._page_size = 0
- self._reflow_sid = 0
- self._do_scroll_hid = None
+ self._model = None
self._progress_bar = None
self._last_progress_bar_pulse = None
- gtk.HBox.__init__(self)
- self.set_flags(gtk.HAS_FOCUS|gtk.CAN_FOCUS)
- self.connect('key-press-event', self._key_press_event_cb)
-
- self._box = hippo.CanvasBox(
- orientation=hippo.ORIENTATION_VERTICAL,
- background_color=style.COLOR_WHITE.get_int())
-
- self._canvas = hippo.Canvas()
- self._canvas.set_root(self._box)
+ gobject.GObject.__init__(self)
- self.pack_start(self._canvas)
- self._canvas.show()
-
- self._vadjustment = gtk.Adjustment(value=0, lower=0, upper=0,
- step_incr=1, page_incr=0,
- page_size=0)
- self._vadjustment.connect('value-changed',
- self._vadjustment_value_changed_cb)
- self._vadjustment.connect('changed', self._vadjustment_changed_cb)
-
- self._vscrollbar = gtk.VScrollbar(self._vadjustment)
- self.pack_end(self._vscrollbar, expand=False, fill=False)
- self._vscrollbar.show()
-
- self.connect('scroll-event', self._scroll_event_cb)
self.connect('destroy', self.__destroy_cb)
- # DND stuff
- self._temp_file_path = None
- self._pressed_button = None
- self._press_start_x = None
- self._press_start_y = None
- self._last_clicked_entry = None
- self._canvas.drag_source_set(0, [], 0)
- self._canvas.add_events(gtk.gdk.BUTTON_PRESS_MASK |
- gtk.gdk.POINTER_MOTION_HINT_MASK)
- self._canvas.connect_after("motion_notify_event",
- self._canvas_motion_notify_event_cb)
- self._canvas.connect("button_press_event",
- self._canvas_button_press_event_cb)
- self._canvas.connect("drag_end", self._drag_end_cb)
- self._canvas.connect("drag_data_get", self._drag_data_get_cb)
+ self._scrolled_window = gtk.ScrolledWindow()
+ self._scrolled_window.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+ self.add(self._scrolled_window)
+ self._scrolled_window.show()
+
+ self.tree_view = TreeView()
+ self.tree_view.props.fixed_height_mode = True
+ self.tree_view.modify_base(gtk.STATE_NORMAL,
+ style.COLOR_WHITE.get_gdk_color())
+ self._scrolled_window.add(self.tree_view)
+ self.tree_view.show()
+
+ self.cell_title = None
+ self.cell_icon = None
+ self._title_column = None
+ self.date_column = None
+ self._add_columns()
+
+ self.tree_view.enable_model_drag_source(gtk.gdk.BUTTON1_MASK,
+ [('text/uri-list', 0, 0),
+ ('journal-object-id', 0, 0)],
+ gtk.gdk.ACTION_COPY)
# Auto-update stuff
self._fully_obscured = True
@@ -109,123 +105,204 @@ class BaseListView(gtk.HBox):
self._refresh_idle_handler = None
self._update_dates_timer = None
- bus = dbus.SessionBus()
- datastore = dbus.Interface(
- bus.get_object(DS_DBUS_SERVICE, DS_DBUS_PATH), DS_DBUS_INTERFACE)
- self._datastore_created_handler = \
- datastore.connect_to_signal('Created',
- self.__datastore_created_cb)
- self._datastore_updated_handler = \
- datastore.connect_to_signal('Updated',
- self.__datastore_updated_cb)
-
- self._datastore_deleted_handler = \
- datastore.connect_to_signal('Deleted',
- self.__datastore_deleted_cb)
+ model.created.connect(self.__model_created_cb)
+ model.updated.connect(self.__model_updated_cb)
+ model.deleted.connect(self.__model_deleted_cb)
- def __destroy_cb(self, widget):
- self._datastore_created_handler.remove()
- self._datastore_updated_handler.remove()
- self._datastore_deleted_handler.remove()
-
- def _vadjustment_changed_cb(self, vadjustment):
- if vadjustment.props.upper > self._page_size:
- self._vscrollbar.show()
- else:
- self._vscrollbar.hide()
+ def __model_created_cb(self, sender, **kwargs):
+ self._set_dirty()
- def _vadjustment_value_changed_cb(self, vadjustment):
- if self._do_scroll_hid is None:
- self._do_scroll_hid = gobject.idle_add(self._do_scroll)
+ def __model_updated_cb(self, sender, **kwargs):
+ self._set_dirty()
- def _do_scroll(self):
- current_position = int(self._vadjustment.props.value)
+ def __model_deleted_cb(self, sender, **kwargs):
+ self._set_dirty()
- self._result_set.seek(current_position)
- metadata_list = self._result_set.read(self._page_size)
+ def _add_columns(self):
+ cell_favorite = CellRendererFavorite(self.tree_view)
+ cell_favorite.connect('clicked', self.__favorite_clicked_cb)
+
+ column = gtk.TreeViewColumn('')
+ column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED
+ column.props.fixed_width = cell_favorite.props.width
+ column.pack_start(cell_favorite)
+ column.set_cell_data_func(cell_favorite, self.__favorite_set_data_cb)
+ self.tree_view.append_column(column)
+
+ self.cell_icon = CellRendererActivityIcon(self.tree_view)
+
+ column = gtk.TreeViewColumn('')
+ column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED
+ column.props.fixed_width = self.cell_icon.props.width
+ column.pack_start(self.cell_icon)
+ column.add_attribute(self.cell_icon, 'file-name', ListModel.COLUMN_ICON)
+ column.add_attribute(self.cell_icon, 'xo-color',
+ ListModel.COLUMN_ICON_COLOR)
+ self.tree_view.append_column(column)
+
+ self.cell_title = gtk.CellRendererText()
+ self.cell_title.props.ellipsize = pango.ELLIPSIZE_MIDDLE
+ self.cell_title.props.ellipsize_set = True
+
+ self._title_column = gtk.TreeViewColumn(_('Title'))
+ self._title_column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED
+ self._title_column.props.expand = True
+ self._title_column.props.clickable = True
+ self._title_column.pack_start(self.cell_title)
+ self._title_column.add_attribute(self.cell_title, 'markup',
+ ListModel.COLUMN_TITLE)
+ self._title_column.connect('clicked', self.__header_clicked_cb)
+ self.tree_view.append_column(self._title_column)
+
+ buddies_column = gtk.TreeViewColumn('')
+ buddies_column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED
+ self.tree_view.append_column(buddies_column)
+
+ for column_index in [ListModel.COLUMN_BUDDY_1, ListModel.COLUMN_BUDDY_2,
+ ListModel.COLUMN_BUDDY_3]:
+ cell_icon = CellRendererBuddy(self.tree_view,
+ column_index=column_index)
+ buddies_column.pack_start(cell_icon)
+ buddies_column.props.fixed_width += cell_icon.props.width
+ buddies_column.add_attribute(cell_icon, 'buddy', column_index)
+
+ cell_text = gtk.CellRendererText()
+ cell_text.props.xalign = 1
+
+ # Measure the required width for a date in the form of "10 hours, 10
+ # minutes ago"
+ timestamp = time.time() - 10 * 60 - 10 * 60 * 60
+ date = util.timestamp_to_elapsed_string(timestamp)
+ date_width = self._get_width_for_string(date)
+
+ self.date_column = gtk.TreeViewColumn(_('Date'))
+ self.date_column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED
+ self.date_column.props.fixed_width = date_width
+ self.date_column.set_alignment(1)
+ self.date_column.props.resizable = True
+ self.date_column.props.clickable = True
+ self.date_column.props.sort_indicator = True
+ self.date_column.props.sort_order = gtk.SORT_ASCENDING
+ self.date_column.pack_start(cell_text)
+ self.date_column.add_attribute(cell_text, 'text', ListModel.COLUMN_DATE)
+ self.date_column.connect('clicked', self.__header_clicked_cb)
+ self.tree_view.append_column(self.date_column)
+
+ def __header_clicked_cb(self, column_clicked):
+ if column_clicked == self._title_column:
+ if self._title_column.props.sort_indicator:
+ if self._title_column.props.sort_order == gtk.SORT_DESCENDING:
+ self._query['order_by'] = ['+title']
+ else:
+ self._query['order_by'] = ['-title']
+ else:
+ self._query['order_by'] = ['+title']
+ elif column_clicked == self.date_column:
+ if self.date_column.props.sort_indicator:
+ if self.date_column.props.sort_order == gtk.SORT_DESCENDING:
+ self._query['order_by'] = ['+timestamp']
+ else:
+ self._query['order_by'] = ['-timestamp']
+ else:
+ self._query['order_by'] = ['+timestamp']
- if self._result_set.length != self._vadjustment.props.upper:
- self._vadjustment.props.upper = self._result_set.length
- self._vadjustment.changed()
+ self.refresh()
- self._refresh_view(metadata_list)
- self._dirty = False
+ # Need to update the column indicators after the model has been reset
+ if self._query['order_by'] == ['-timestamp']:
+ self.date_column.props.sort_indicator = True
+ self._title_column.props.sort_indicator = False
+ self.date_column.props.sort_order = gtk.SORT_DESCENDING
+ elif self._query['order_by'] == ['+timestamp']:
+ self.date_column.props.sort_indicator = True
+ self._title_column.props.sort_indicator = False
+ self.date_column.props.sort_order = gtk.SORT_ASCENDING
+ elif self._query['order_by'] == ['-title']:
+ self.date_column.props.sort_indicator = False
+ self._title_column.props.sort_indicator = True
+ self._title_column.props.sort_order = gtk.SORT_DESCENDING
+ elif self._query['order_by'] == ['+title']:
+ self.date_column.props.sort_indicator = False
+ self._title_column.props.sort_indicator = True
+ self._title_column.props.sort_order = gtk.SORT_ASCENDING
+
+ def _get_width_for_string(self, text):
+ # Add some extra margin
+ text = text + 'aaaaa'
+
+ widget = gtk.Label('')
+ context = widget.get_pango_context()
+ layout = pango.Layout(context)
+ layout.set_text(text)
+ width, height_ = layout.get_size()
+ return pango.PIXELS(width)
- self._do_scroll_hid = None
- return False
+ def do_size_allocate(self, allocation):
+ self.allocation = allocation
+ self.child.size_allocate(allocation)
- def _refresh_view(self, metadata_list):
- logging.debug('ListView %r' % self)
- # Indicate when the Journal is empty
- if len(metadata_list) == 0:
- if self._is_query_empty():
- self._show_message(EMPTY_JOURNAL)
- else:
- self._show_message(NO_MATCH)
- return
+ def do_size_request(self, requisition):
+ requisition.width, requisition.height = self.child.size_request()
- # Refresh view and create the entries if they don't exist yet.
- for i in range(0, self._page_size):
- try:
- if i < len(metadata_list):
- if i >= len(self._entries):
- entry = self.create_entry()
- self._box.append(entry)
- self._entries.append(entry)
- entry.metadata = metadata_list[i]
- else:
- entry = self._entries[i]
- entry.metadata = metadata_list[i]
- entry.set_visible(True)
- elif i < len(self._entries):
- entry = self._entries[i]
- entry.set_visible(False)
- except Exception:
- logging.error('Exception while displaying entry:\n' + \
- ''.join(traceback.format_exception(*sys.exc_info())))
-
- def create_entry(self):
- """ Create a descendant of BaseCollapsedEntry
- """
- raise NotImplementedError
+ def __destroy_cb(self, widget):
+ if self._model is not None:
+ self._model.stop()
+
+ def __favorite_set_data_cb(self, column, cell, tree_model, tree_iter):
+ favorite = self._model[tree_iter][ListModel.COLUMN_FAVORITE]
+ if favorite:
+ client = gconf.client_get_default()
+ color = XoColor(client.get_string('/desktop/sugar/user/color'))
+ cell.props.xo_color = color
+ else:
+ cell.props.stroke_color = style.COLOR_BUTTON_GREY.get_svg()
+ cell.props.fill_color = style.COLOR_WHITE.get_svg()
+
+ def __favorite_clicked_cb(self, cell, path):
+ row = self._model[path]
+ metadata = model.get(row[ListModel.COLUMN_UID])
+ if metadata['keep'] == '1':
+ metadata['keep'] = '0'
+ else:
+ metadata['keep'] = '1'
+ model.write(metadata, update_mtime=False)
def update_with_query(self, query_dict):
logging.debug('ListView.update_with_query')
self._query = query_dict
- if self._page_size > 0:
- self.refresh()
+
+ if 'order_by' not in self._query:
+ self._query['order_by'] = ['+timestamp']
+
+ self.refresh()
def refresh(self):
- logging.debug('ListView.refresh query %r' % self._query)
+ logging.debug('ListView.refresh query %r', self._query)
self._stop_progress_bar()
self._start_progress_bar()
- if self._result_set is not None:
- self._result_set.stop()
- self._result_set = model.find(self._query, self._page_size)
- self._result_set.ready.connect(self.__result_set_ready_cb)
- self._result_set.progress.connect(self.__result_set_progress_cb)
- self._result_set.setup()
+ if self._model is not None:
+ self._model.stop()
- def __result_set_ready_cb(self, **kwargs):
- if kwargs['sender'] != self._result_set:
- return
+ self._model = ListModel(self._query)
+ self._model.connect('ready', self.__model_ready_cb)
+ self._model.connect('progress', self.__model_progress_cb)
+ self._model.setup()
+ def __model_ready_cb(self, tree_model):
self._stop_progress_bar()
- self._vadjustment.props.upper = self._result_set.length
- self._vadjustment.changed()
+ # Cannot set it up earlier because will try to access the model and it
+ # needs to be ready.
+ self.tree_view.set_model(self._model)
- self._vadjustment.props.value = min(self._vadjustment.props.value,
- self._result_set.length - self._page_size)
- if self._result_set.length == 0:
+ if len(tree_model) == 0:
if self._is_query_empty():
- self._show_message(EMPTY_JOURNAL)
+ self._show_message(MESSAGE_EMPTY_JOURNAL)
else:
- self._show_message(NO_MATCH)
+ self._show_message(MESSAGE_NO_MATCH)
else:
self._clear_message()
- self._do_scroll()
def _is_query_empty(self):
# FIXME: This is a hack, we shouldn't have to update this every time
@@ -237,18 +314,16 @@ class BaseListView(gtk.HBox):
else:
return True
- def __result_set_progress_cb(self, **kwargs):
+ def __model_progress_cb(self, tree_model):
if time.time() - self._last_progress_bar_pulse > 0.05:
if self._progress_bar is not None:
self._progress_bar.pulse()
self._last_progress_bar_pulse = time.time()
def _start_progress_bar(self):
- self.remove(self._canvas)
- self.remove(self._vscrollbar)
-
alignment = gtk.Alignment(xalign=0.5, yalign=0.5, xscale=0.5)
- self.pack_start(alignment)
+ self.remove(self.child)
+ self.add(alignment)
alignment.show()
self._progress_bar = gtk.ProgressBar()
@@ -258,102 +333,23 @@ class BaseListView(gtk.HBox):
self._progress_bar.show()
def _stop_progress_bar(self):
- for widget in self.get_children():
- self.remove(widget)
- self._progress_bar = None
-
- self.pack_start(self._canvas)
- self.pack_end(self._vscrollbar, expand=False, fill=False)
-
- def _scroll_event_cb(self, hbox, event):
- if event.direction == gtk.gdk.SCROLL_UP:
- if self._vadjustment.props.value > self._vadjustment.props.lower:
- self._vadjustment.props.value -= 1
- elif event.direction == gtk.gdk.SCROLL_DOWN:
- max_value = self._result_set.length - self._page_size
- if self._vadjustment.props.value < max_value:
- self._vadjustment.props.value += 1
-
- def do_focus(self, direction):
- if not self.is_focus():
- self.grab_focus()
- return True
- return False
-
- def _key_press_event_cb(self, widget, event):
- keyname = gtk.gdk.keyval_name(event.keyval)
-
- if keyname == 'Up':
- if self._vadjustment.props.value > self._vadjustment.props.lower:
- self._vadjustment.props.value -= 1
- elif keyname == 'Down':
- max_value = self._result_set.length - self._page_size
- if self._vadjustment.props.value < max_value:
- self._vadjustment.props.value += 1
- elif keyname == 'Page_Up' or keyname == 'KP_Page_Up':
- new_position = max(0,
- self._vadjustment.props.value - self._page_size)
- if new_position != self._vadjustment.props.value:
- self._vadjustment.props.value = new_position
- elif keyname == 'Page_Down' or keyname == 'KP_Page_Down':
- new_position = min(self._result_set.length - self._page_size,
- self._vadjustment.props.value + self._page_size)
- if new_position != self._vadjustment.props.value:
- self._vadjustment.props.value = new_position
- elif keyname == 'Home' or keyname == 'KP_Home':
- new_position = 0
- if new_position != self._vadjustment.props.value:
- self._vadjustment.props.value = new_position
- elif keyname == 'End' or keyname == 'KP_End':
- new_position = max(0, self._result_set.length - self._page_size)
- if new_position != self._vadjustment.props.value:
- self._vadjustment.props.value = new_position
- else:
- return False
-
- return True
-
- def do_size_allocate(self, allocation):
- gtk.HBox.do_size_allocate(self, allocation)
- new_page_size = int(allocation.height / style.GRID_CELL_SIZE)
-
- logging.debug("do_size_allocate: %r" % new_page_size)
-
- if new_page_size != self._page_size:
- self._page_size = new_page_size
- self._queue_reflow()
-
- def _queue_reflow(self):
- if not self._reflow_sid:
- self._reflow_sid = gobject.idle_add(self._reflow_idle_cb)
-
- def _reflow_idle_cb(self):
- self._box.clear()
- self._entries = []
-
- self._vadjustment.props.page_size = self._page_size
- self._vadjustment.props.page_increment = self._page_size
- self._vadjustment.changed()
-
- if self._result_set is not None:
- self._result_set.stop()
- self._result_set = model.find(self._query, self._page_size)
-
- max_value = max(0, self._result_set.length - self._page_size)
- if self._vadjustment.props.value > max_value:
- self._vadjustment.props.value = max_value
- else:
- self._do_scroll()
-
- self._reflow_sid = 0
+ if self.child != self._progress_bar:
+ return
+ self.remove(self.child)
+ self.add(self._scrolled_window)
def _show_message(self, message):
+ canvas = hippo.Canvas()
+ self.remove(self.child)
+ self.add(canvas)
+ canvas.show()
+
box = hippo.CanvasBox(orientation=hippo.ORIENTATION_VERTICAL,
background_color=style.COLOR_WHITE.get_int(),
yalign=hippo.ALIGNMENT_CENTER,
spacing=style.DEFAULT_SPACING,
padding_bottom=style.GRID_CELL_SIZE)
- self._canvas.set_root(box)
+ canvas.set_root(box)
icon = CanvasIcon(size=style.LARGE_ICON_SIZE,
icon_name='activity-journal',
@@ -361,141 +357,65 @@ class BaseListView(gtk.HBox):
fill_color = style.COLOR_TRANSPARENT.get_svg())
box.append(icon)
- text = hippo.CanvasText(text=message,
+ if message == MESSAGE_EMPTY_JOURNAL:
+ text = _('Your Journal is empty')
+ elif message == MESSAGE_NO_MATCH:
+ text = _('No matching entries')
+ else:
+ raise ValueError('Invalid message')
+
+ text = hippo.CanvasText(text=text,
xalign=hippo.ALIGNMENT_CENTER,
font_desc=style.FONT_BOLD.get_pango_desc(),
color = style.COLOR_BUTTON_GREY.get_int())
box.append(text)
- button = gtk.Button(label=_('Clear search'))
- button.connect('clicked', self.__clear_button_clicked_cb)
- button.props.image = Icon(icon_name='dialog-cancel',
- icon_size=gtk.ICON_SIZE_BUTTON)
- canvas_button = hippo.CanvasWidget(widget=button,
- xalign=hippo.ALIGNMENT_CENTER)
- box.append(canvas_button)
+ if message == MESSAGE_NO_MATCH:
+ button = gtk.Button(label=_('Clear search'))
+ button.connect('clicked', self.__clear_button_clicked_cb)
+ button.props.image = Icon(icon_name='dialog-cancel',
+ icon_size=gtk.ICON_SIZE_BUTTON)
+ canvas_button = hippo.CanvasWidget(widget=button,
+ xalign=hippo.ALIGNMENT_CENTER)
+ box.append(canvas_button)
def __clear_button_clicked_cb(self, button):
self.emit('clear-clicked')
def _clear_message(self):
- self._canvas.set_root(self._box)
-
- # TODO: Dnd methods. This should be merged somehow inside hippo-canvas.
- def _canvas_motion_notify_event_cb(self, widget, event):
- if not self._pressed_button:
- return True
-
- # if the mouse button is not pressed, no drag should occurr
- if not event.state & gtk.gdk.BUTTON1_MASK:
- self._pressed_button = None
- return True
-
- logging.debug("motion_notify_event_cb")
-
- if event.is_hint:
- x, y, state_ = event.window.get_pointer()
- else:
- x = event.x
- y = event.y
-
- if widget.drag_check_threshold(int(self._press_start_x),
- int(self._press_start_y),
- int(x),
- int(y)):
- context_ = widget.drag_begin([('text/uri-list', 0, 0),
- ('journal-object-id', 0, 0)],
- gtk.gdk.ACTION_COPY,
- 1,
- event)
- return True
-
- def _drag_end_cb(self, widget, drag_context):
- logging.debug("drag_end_cb")
- self._pressed_button = None
- self._press_start_x = None
- self._press_start_y = None
- self._last_clicked_entry = None
-
- # Release and delete the temp file
- self._temp_file_path = None
-
- def _drag_data_get_cb(self, widget, context, selection, target_type,
- event_time):
- logging.debug("drag_data_get_cb: requested target " + selection.target)
-
- metadata = self._last_clicked_entry.metadata
- if selection.target == 'text/uri-list':
- # Get hold of a reference so the temp file doesn't get deleted
- self._temp_file_path = model.get_file(metadata['uid'])
- selection.set(selection.target, 8, self._temp_file_path)
- elif selection.target == 'journal-object-id':
- selection.set(selection.target, 8, metadata['uid'])
-
- def _canvas_button_press_event_cb(self, widget, event):
- logging.debug("button_press_event_cb")
-
- if event.button == 1 and event.type == gtk.gdk.BUTTON_PRESS:
- self._last_clicked_entry = \
- self._get_entry_at_coords(event.x, event.y)
- if self._last_clicked_entry:
- self._pressed_button = event.button
- self._press_start_x = event.x
- self._press_start_y = event.y
-
- return False
-
- def _get_entry_at_coords(self, x, y):
- for entry in self._box.get_children():
- entry_x, entry_y = entry.get_context().translate_to_widget(entry)
- entry_width, entry_height = entry.get_allocation()
-
- if (x >= entry_x ) and (x <= entry_x + entry_width) and \
- (y >= entry_y ) and (y <= entry_y + entry_height):
- return entry
- return None
+ self.remove(self.child)
+ self.add(self._scrolled_window)
+ self._scrolled_window.show()
def update_dates(self):
logging.debug('ListView.update_dates')
- for entry in self._entries:
- if entry.get_visible():
- entry.update_date()
-
- def __datastore_created_cb(self, uid):
- self._set_dirty()
-
- def __datastore_updated_cb(self, uid):
- self._set_dirty()
-
- def __datastore_deleted_cb(self, uid):
- self._set_dirty()
+ visible_range = self.tree_view.get_visible_range()
+ if visible_range is None:
+ return
+ path, end_path = visible_range
+ while True:
+ x, y, width, height = self.tree_view.get_cell_area(path,
+ self.date_column)
+ x, y = self.tree_view.convert_tree_to_widget_coords(x, y)
+ self.tree_view.queue_draw_area(x, y, width, height)
+ if path == end_path:
+ break
+ else:
+ next_iter = self._model.iter_next(self._model.get_iter(path))
+ path = self._model.get_path(next_iter)
def _set_dirty(self):
if self._fully_obscured:
self._dirty = True
else:
- self._schedule_refresh()
-
- def _schedule_refresh(self):
- if self._refresh_idle_handler is None:
- logging.debug('Add refresh idle callback')
- self._refresh_idle_handler = \
- gobject.idle_add(self.__refresh_idle_cb)
-
- def __refresh_idle_cb(self):
- self.refresh()
- if self._refresh_idle_handler is not None:
- logging.debug('Remove refresh idle callback')
- gobject.source_remove(self._refresh_idle_handler)
- self._refresh_idle_handler = None
- return False
+ self.refresh()
def set_is_visible(self, visible):
- logging.debug('canvas_visibility_notify_event_cb %r' % visible)
+ logging.debug('canvas_visibility_notify_event_cb %r', visible)
if visible:
self._fully_obscured = False
if self._dirty:
- self._schedule_refresh()
+ self.refresh()
if self._update_dates_timer is None:
logging.debug('Adding date updating timer')
self._update_dates_timer = \
@@ -513,7 +433,7 @@ class BaseListView(gtk.HBox):
return True
class ListView(BaseListView):
- __gtype_name__ = 'ListView'
+ __gtype_name__ = 'JournalListView'
__gsignals__ = {
'detail-clicked': (gobject.SIGNAL_RUN_FIRST,
@@ -524,11 +444,142 @@ class ListView(BaseListView):
def __init__(self):
BaseListView.__init__(self)
- def create_entry(self):
- entry = CollapsedEntry()
- entry.connect('detail-clicked', self.__entry_activated_cb)
- return entry
+ self.cell_title.props.editable = True
+ self.cell_title.connect('edited', self.__cell_title_edited_cb)
+
+ self.cell_icon.connect('clicked', self.__icon_clicked_cb)
+ self.cell_icon.connect('detail-clicked', self.__detail_clicked_cb)
+
+ cell_detail = CellRendererDetail(self.tree_view)
+ cell_detail.connect('clicked', self.__detail_cell_clicked_cb)
+
+ column = gtk.TreeViewColumn('')
+ column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED
+ column.props.fixed_width = cell_detail.props.width
+ column.pack_start(cell_detail)
+ self.tree_view.append_column(column)
+
+ def __detail_cell_clicked_cb(self, cell, path):
+ row = self.tree_view.get_model()[path]
+ self.emit('detail-clicked', row[ListModel.COLUMN_UID])
+
+ def __detail_clicked_cb(self, cell, uid):
+ self.emit('detail-clicked', uid)
+
+ def __icon_clicked_cb(self, cell, path):
+ row = self.tree_view.get_model()[path]
+ metadata = model.get(row[ListModel.COLUMN_UID])
+ misc.resume(metadata)
+
+ def __cell_title_edited_cb(self, cell, path, new_text):
+ row = self._model[path]
+ metadata = model.get(row[ListModel.COLUMN_UID])
+ metadata['title'] = new_text
+ model.write(metadata, update_mtime=False)
+
+class CellRendererFavorite(CellRendererIcon):
+ __gtype_name__ = 'JournalCellRendererFavorite'
+
+ def __init__(self, tree_view):
+ CellRendererIcon.__init__(self, tree_view)
+
+ self.props.width = style.GRID_CELL_SIZE
+ self.props.height = style.GRID_CELL_SIZE
+ self.props.size = style.SMALL_ICON_SIZE
+ self.props.icon_name = 'emblem-favorite'
+ self.props.mode = gtk.CELL_RENDERER_MODE_ACTIVATABLE
+ self.props.prelit_stroke_color = style.COLOR_BUTTON_GREY.get_svg()
+ self.props.prelit_fill_color = style.COLOR_BUTTON_GREY.get_svg()
+
+class CellRendererDetail(CellRendererIcon):
+ __gtype_name__ = 'JournalCellRendererDetail'
+
+ def __init__(self, tree_view):
+ CellRendererIcon.__init__(self, tree_view)
+
+ self.props.width = style.GRID_CELL_SIZE
+ self.props.height = style.GRID_CELL_SIZE
+ self.props.size = style.SMALL_ICON_SIZE
+ self.props.icon_name = 'go-right'
+ self.props.mode = gtk.CELL_RENDERER_MODE_ACTIVATABLE
+ self.props.stroke_color = style.COLOR_TRANSPARENT.get_svg()
+ self.props.fill_color = style.COLOR_BUTTON_GREY.get_svg()
+ self.props.prelit_stroke_color = style.COLOR_TRANSPARENT.get_svg()
+ self.props.prelit_fill_color = style.COLOR_BLACK.get_svg()
+
+class CellRendererActivityIcon(CellRendererIcon):
+ __gtype_name__ = 'JournalCellRendererActivityIcon'
+
+ __gsignals__ = {
+ 'detail-clicked': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([str])),
+ }
+
+ def __init__(self, tree_view):
+ self._show_palette = True
+
+ CellRendererIcon.__init__(self, tree_view)
+
+ self.props.width = style.GRID_CELL_SIZE
+ self.props.height = style.GRID_CELL_SIZE
+ self.props.size = style.STANDARD_ICON_SIZE
+ self.props.mode = gtk.CELL_RENDERER_MODE_ACTIVATABLE
+
+ self.tree_view = tree_view
+
+ def create_palette(self):
+ if not self._show_palette:
+ return None
+
+ tree_model = self.tree_view.get_model()
+ metadata = tree_model.get_metadata(self.props.palette_invoker.path)
+
+ palette = ObjectPalette(metadata, detail=True)
+ palette.connect('detail-clicked',
+ self.__detail_clicked_cb)
+ return palette
+
+ def __detail_clicked_cb(self, palette, uid):
+ self.emit('detail-clicked', uid)
+
+ def set_show_palette(self, show_palette):
+ self._show_palette = show_palette
+
+ show_palette = gobject.property(type=bool, default=True,
+ setter=set_show_palette)
+
+class CellRendererBuddy(CellRendererIcon):
+ __gtype_name__ = 'JournalCellRendererBuddy'
+
+ def __init__(self, tree_view, column_index):
+ CellRendererIcon.__init__(self, tree_view)
+
+ self.props.width = style.STANDARD_ICON_SIZE
+ self.props.height = style.STANDARD_ICON_SIZE
+ self.props.size = style.STANDARD_ICON_SIZE
+ self.props.mode = gtk.CELL_RENDERER_MODE_ACTIVATABLE
+
+ self.tree_view = tree_view
+ self._model_column_index = column_index
+
+ def create_palette(self):
+ tree_model = self.tree_view.get_model()
+ row = tree_model[self.props.palette_invoker.path]
+
+ if row[self._model_column_index] is not None:
+ nick, xo_color = row[self._model_column_index]
+ return BuddyPalette((nick, xo_color.to_string()))
+ else:
+ return None
+
+ def set_buddy(self, buddy):
+ if buddy is None:
+ self.props.icon_name = None
+ else:
+ nick_, xo_color = buddy
+ self.props.icon_name = 'computer-xo'
+ self.props.xo_color = xo_color
- def __entry_activated_cb(self, entry):
- self.emit('detail-clicked', entry)
+ buddy = gobject.property(type=object, setter=set_buddy)
diff --git a/src/jarabe/journal/misc.py b/src/jarabe/journal/misc.py
index b29b744..e6e5abf 100644
--- a/src/jarabe/journal/misc.py
+++ b/src/jarabe/journal/misc.py
@@ -17,7 +17,6 @@
import logging
import time
import traceback
-import sys
import os
from gettext import gettext as _
@@ -45,7 +44,7 @@ def _get_icon_for_mime(mime_type):
return file_name
icons = gio.content_type_get_icon(mime_type)
- logging.debug('icons for this file: %r' % icons.props.names)
+ logging.debug('icons for this file: %r', icons.props.names)
for icon_name in icons.props.names:
file_name = get_icon_file_name(icon_name)
if file_name is not None:
@@ -69,7 +68,7 @@ def get_icon_name(metadata):
try:
bundle = ActivityBundle(file_path)
file_name = bundle.get_icon()
- except:
+ except Exception:
logging.warning('Could not read bundle:\n' + \
traceback.format_exc())
@@ -97,27 +96,27 @@ def get_bundle(metadata):
if is_activity_bundle(metadata):
file_path = util.TempFilePath(model.get_file(metadata['uid']))
if not os.path.exists(file_path):
- logging.warning('Invalid path: %r' % file_path)
+ logging.warning('Invalid path: %r', file_path)
return None
return ActivityBundle(file_path)
elif is_content_bundle(metadata):
file_path = util.TempFilePath(model.get_file(metadata['uid']))
if not os.path.exists(file_path):
- logging.warning('Invalid path: %r' % file_path)
+ logging.warning('Invalid path: %r', file_path)
return None
return ContentBundle(file_path)
elif is_journal_bundle(metadata):
file_path = util.TempFilePath(model.get_file(metadata['uid']))
if not os.path.exists(file_path):
- logging.warning('Invalid path: %r' % file_path)
+ logging.warning('Invalid path: %r', file_path)
return None
return JournalEntryBundle(file_path)
else:
return None
except MalformedBundleException, e:
- logging.warning('Incorrect bundle: %r' % e)
+ logging.warning('Incorrect bundle: %r', e)
return None
def _get_activities_for_mime(mime_type):
@@ -197,7 +196,7 @@ def resume(metadata, bundle_id=None):
if bundle_id is None:
activities = get_activities(metadata)
if not activities:
- logging.warning('No activity can open this object, %s.' %
+ logging.warning('No activity can open this object, %s.',
metadata.get('mime_type', None))
return
bundle_id = activities[0].get_bundle_id()
diff --git a/src/jarabe/journal/model.py b/src/jarabe/journal/model.py
index b6d2bde..02ce26a 100644
--- a/src/jarabe/journal/model.py
+++ b/src/jarabe/journal/model.py
@@ -20,7 +20,6 @@ from datetime import datetime
import time
import shutil
from stat import S_IFMT, S_IFDIR, S_IFREG
-import traceback
import re
import gobject
@@ -41,7 +40,8 @@ PROPERTIES = ['uid', 'title', 'mtime', 'timestamp', 'keep', 'buddies',
'icon-color', 'mime_type', 'progress', 'activity', 'mountpoint',
'activity_id', 'bundle_id']
-PAGES_TO_CACHE = 5
+MIN_PAGES_TO_CACHE = 3
+MAX_PAGES_TO_CACHE = 5
class _Cache(object):
@@ -64,11 +64,10 @@ class _Cache(object):
self._dict[entry['uid']] = entry
def remove_all(self, entries):
- entries = entries[:]
- for entry in entries:
- obj = self._dict[entry['uid']]
+ for uid in [entry['uid'] for entry in entries]:
+ obj = self._dict[uid]
self._array.remove(obj)
- del self._dict[entry['uid']]
+ del self._dict[uid]
def __len__(self):
return len(self._array)
@@ -83,11 +82,11 @@ class BaseResultSet(object):
"""Encapsulates the result of a query
"""
- def __init__(self, query, cache_limit):
+ def __init__(self, query, page_size):
self._total_count = -1
self._position = -1
self._query = query
- self._cache_limit = cache_limit
+ self._page_size = page_size
self._offset = 0
self._cache = _Cache()
@@ -104,7 +103,7 @@ class BaseResultSet(object):
def get_length(self):
if self._total_count == -1:
query = self._query.copy()
- query['limit'] = self._cache_limit
+ query['limit'] = self._page_size * MIN_PAGES_TO_CACHE
entries, self._total_count = self.find(query)
self._cache.append_all(entries)
self._offset = 0
@@ -118,13 +117,8 @@ class BaseResultSet(object):
def seek(self, position):
self._position = position
- def read(self, max_count):
- logging.debug('ResultSet.read position: %r' % self._position)
-
- if max_count * PAGES_TO_CACHE > self._cache_limit:
- raise RuntimeError(
- 'max_count (%i) too big for self._cache_limit'
- ' (%i).' % (max_count, self._cache_limit))
+ def read(self):
+ logging.debug('ResultSet.read position: %r', self._position)
if self._position == -1:
self.seek(0)
@@ -142,31 +136,29 @@ class BaseResultSet(object):
last_cached_entry = self._offset + len(self._cache)
- if (remaining_forward_entries <= 0 and
- remaining_backwards_entries <= 0) or \
- max_count > self._cache_limit:
+ if remaining_forward_entries <= 0 and remaining_backwards_entries <= 0:
# Total cache miss: remake it
- offset = max(0, self._position - max_count)
- logging.debug('remaking cache, offset: %r limit: %r' % \
- (offset, max_count * 2))
+ limit = self._page_size * MIN_PAGES_TO_CACHE
+ offset = max(0, self._position - limit / 2)
+ logging.debug('remaking cache, offset: %r limit: %r', offset,
+ limit)
query = self._query.copy()
- query['limit'] = self._cache_limit
+ query['limit'] = limit
query['offset'] = offset
entries, self._total_count = self.find(query)
self._cache.remove_all(self._cache)
self._cache.append_all(entries)
self._offset = offset
-
- elif remaining_forward_entries < 2 * max_count and \
- last_cached_entry < self._total_count:
+
+ elif remaining_forward_entries <= 0 and remaining_backwards_entries > 0:
# Add one page to the end of cache
- logging.debug('appending one more page, offset: %r' % \
- last_cached_entry)
+ logging.debug('appending one more page, offset: %r',
+ last_cached_entry)
query = self._query.copy()
- query['limit'] = max_count
+ query['limit'] = self._page_size
query['offset'] = last_cached_entry
entries, self._total_count = self.find(query)
@@ -174,21 +166,23 @@ class BaseResultSet(object):
self._cache.append_all(entries)
# apply the cache limit
- objects_excess = len(self._cache) - self._cache_limit
+ cache_limit = self._page_size * MAX_PAGES_TO_CACHE
+ objects_excess = len(self._cache) - cache_limit
if objects_excess > 0:
self._offset += objects_excess
self._cache.remove_all(self._cache[:objects_excess])
- elif remaining_backwards_entries < 2 * max_count and self._offset > 0:
+ elif remaining_forward_entries > 0 and \
+ remaining_backwards_entries <= 0 and self._offset > 0:
# Add one page to the beginning of cache
- limit = min(self._offset, max_count)
- self._offset = max(0, self._offset - max_count)
+ limit = min(self._offset, self._page_size)
+ self._offset = max(0, self._offset - limit)
- logging.debug('prepending one more page, offset: %r limit: %r' %
- (self._offset, limit))
+ logging.debug('prepending one more page, offset: %r limit: %r',
+ self._offset, limit)
query = self._query.copy()
- query['limit'] = limit
+ query['limit'] = self._page_size
query['offset'] = self._offset
entries, self._total_count = self.find(query)
@@ -196,20 +190,19 @@ class BaseResultSet(object):
self._cache.prepend_all(entries)
# apply the cache limit
- objects_excess = len(self._cache) - self._cache_limit
+ cache_limit = self._page_size * MAX_PAGES_TO_CACHE
+ objects_excess = len(self._cache) - cache_limit
if objects_excess > 0:
self._cache.remove_all(self._cache[-objects_excess:])
else:
logging.debug('cache hit and no need to grow the cache')
- first_pos = self._position - self._offset
- last_pos = self._position - self._offset + max_count
- return self._cache[first_pos:last_pos]
+ return self._cache[self._position - self._offset]
class DatastoreResultSet(BaseResultSet):
"""Encapsulates the result of a query on the datastore
"""
- def __init__(self, query, cache_limit):
+ def __init__(self, query, page_size):
if query.get('query', '') and not query['query'].startswith('"'):
query_text = ''
@@ -222,7 +215,7 @@ class DatastoreResultSet(BaseResultSet):
query['query'] = query_text
- BaseResultSet.__init__(self, query, cache_limit)
+ BaseResultSet.__init__(self, query, page_size)
def find(self, query):
entries, total_count = _get_datastore().find(query, PROPERTIES,
@@ -236,8 +229,8 @@ class DatastoreResultSet(BaseResultSet):
class InplaceResultSet(BaseResultSet):
"""Encapsulates the result of a query on a mount point
"""
- def __init__(self, query, cache_limit, mount_point):
- BaseResultSet.__init__(self, query, cache_limit)
+ def __init__(self, query, page_size, mount_point):
+ BaseResultSet.__init__(self, query, page_size)
self._mount_point = mount_point
self._file_list = None
self._pending_directories = 0
@@ -295,7 +288,7 @@ class InplaceResultSet(BaseResultSet):
metadata['mountpoint'] = self._mount_point
entries.append(metadata)
- logging.debug('InplaceResultSet.find took %f s.' % (time.time() - t))
+ logging.debug('InplaceResultSet.find took %f s.', time.time() - t)
return entries, total_count
@@ -337,8 +330,7 @@ class InplaceResultSet(BaseResultSet):
self.progress.send(self)
except Exception:
- logging.error('Error reading file %r: %s' % \
- (full_path, traceback.format_exc()))
+ logging.exception('Error reading file %r', full_path)
if self._pending_directories == 0:
self.setup_ready()
@@ -389,11 +381,10 @@ def find(query, page_size):
if mount_points is None or len(mount_points) != 1:
raise ValueError('Exactly one mount point must be specified')
- cache_limit = page_size * PAGES_TO_CACHE
if mount_points[0] == '/':
- return DatastoreResultSet(query, cache_limit)
+ return DatastoreResultSet(query, page_size)
else:
- return InplaceResultSet(query, cache_limit, mount_points[0])
+ return InplaceResultSet(query, page_size, mount_points[0])
def _get_mount_point(path):
dir_path = os.path.dirname(path)
@@ -419,16 +410,31 @@ def get_file(object_id):
"""Returns the file for an object
"""
if os.path.exists(object_id):
- logging.debug('get_file asked for file with path %r' % object_id)
+ logging.debug('get_file asked for file with path %r', object_id)
return object_id
else:
- logging.debug('get_file asked for entry with id %r' % object_id)
+ logging.debug('get_file asked for entry with id %r', object_id)
file_path = _get_datastore().get_filename(object_id)
if file_path:
return util.TempFilePath(file_path)
else:
return None
+def get_file_size(object_id):
+ """Return the file size for an object
+ """
+ logging.debug('get_file_size %r', object_id)
+ if os.path.exists(object_id):
+ return os.stat(object_id).st_size
+
+ file_path = dbus_helpers.get_filename(object_id)
+ if file_path:
+ size = os.stat(file_path).st_size
+ os.remove(file_path)
+ return size
+
+ return 0
+
def get_unique_values(key):
"""Returns a list with the different values a property has taken
"""
@@ -457,8 +463,8 @@ def copy(metadata, mount_point):
def write(metadata, file_path='', update_mtime=True):
"""Creates or updates an entry for that id
"""
- logging.debug('model.write %r %r %r' % (metadata.get('uid', ''), file_path,
- update_mtime))
+ logging.debug('model.write %r %r %r', metadata.get('uid', ''), file_path,
+ update_mtime)
if update_mtime:
metadata['mtime'] = datetime.now().isoformat()
metadata['timestamp'] = int(time.time())
@@ -490,9 +496,11 @@ def write(metadata, file_path='', update_mtime=True):
def _get_file_name(title, mime_type):
file_name = title
- extension = '.' + mime.get_primary_extension(mime_type)
- if not file_name.endswith(extension):
- file_name += extension
+ extension = mime.get_primary_extension(mime_type)
+ if extension is not None and extension:
+ extension = '.' + extension
+ if not file_name.endswith(extension):
+ file_name += extension
# Invalid characters in VFAT filenames. From
# http://en.wikipedia.org/wiki/File_Allocation_Table
@@ -505,7 +513,7 @@ def _get_file_name(title, mime_type):
max_len = 250
if len(file_name) > max_len:
name, extension = os.path.splitext(file_name)
- file_name = name[0:max_len - extension] + extension
+ file_name = name[0:max_len - len(extension)] + extension
return file_name
diff --git a/src/jarabe/journal/objectchooser.py b/src/jarabe/journal/objectchooser.py
index b70cf72..31bdba8 100644
--- a/src/jarabe/journal/objectchooser.py
+++ b/src/jarabe/journal/objectchooser.py
@@ -19,14 +19,13 @@ import logging
import gobject
import gtk
-import hippo
import wnck
from sugar.graphics import style
from sugar.graphics.toolbutton import ToolButton
-from jarabe.journal.listview import ListView
-from jarabe.journal.collapsedentry import BaseCollapsedEntry
+from jarabe.journal.listview import BaseListView
+from jarabe.journal.listmodel import ListModel
from jarabe.journal.journaltoolbox import SearchToolbar
from jarabe.journal.volumestoolbar import VolumesToolbar
@@ -107,8 +106,8 @@ class ObjectChooser(gtk.Window):
if window.get_xid() == parent.xid:
self.destroy()
- def __entry_activated_cb(self, list_view, entry):
- self._selected_object_id = entry.metadata['uid']
+ def __entry_activated_cb(self, list_view, uid):
+ self._selected_object_id = uid
self.emit('response', gtk.RESPONSE_ACCEPT)
def __delete_event_cb(self, chooser, event):
@@ -128,12 +127,12 @@ class ObjectChooser(gtk.Window):
def __query_changed_cb(self, toolbar, query):
self._list_view.update_with_query(query)
- def __volume_changed_cb(self, volume_toolbar, volume_id):
- logging.debug('Selected volume: %r.' % volume_id)
- self._toolbar.set_volume_id(volume_id)
+ def __volume_changed_cb(self, volume_toolbar, mount_point):
+ logging.debug('Selected volume: %r.', mount_point)
+ self._toolbar.set_mount_point(mount_point)
def __visibility_notify_event_cb(self, window, event):
- logging.debug('visibility_notify_event_cb %r' % self)
+ logging.debug('visibility_notify_event_cb %r', self)
visible = event.state == gtk.gdk.VISIBILITY_FULLY_OBSCURED
self._list_view.set_is_visible(visible)
@@ -163,50 +162,38 @@ class TitleBox(VolumesToolbar):
self.insert(tool_item, -1)
tool_item.show()
-class ChooserCollapsedEntry(BaseCollapsedEntry):
- __gtype_name__ = 'ChooserCollapsedEntry'
+class ChooserListView(BaseListView):
+ __gtype_name__ = 'ChooserListView'
__gsignals__ = {
'entry-activated': (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE,
- ([]))
+ ([str])),
}
def __init__(self):
- BaseCollapsedEntry.__init__(self)
-
- self.connect_after('button-release-event',
- self.__button_release_event_cb)
- self.connect('motion-notify-event', self.__motion_notify_event_cb)
+ BaseListView.__init__(self)
- def __button_release_event_cb(self, entry, event):
- self.emit('entry-activated')
- return True
+ self.cell_icon.props.show_palette = False
+ self.tree_view.props.hover_selection = True
- def __motion_notify_event_cb(self, entry, event):
- if event.detail == hippo.MOTION_DETAIL_ENTER:
- self.props.background_color = style.COLOR_PANEL_GREY.get_int()
- elif event.detail == hippo.MOTION_DETAIL_LEAVE:
- self.props.background_color = style.COLOR_WHITE.get_int()
- return False
+ self.tree_view.connect('button-release-event',
+ self.__button_release_event_cb)
-class ChooserListView(ListView):
- __gtype_name__ = 'ChooserListView'
+ def __entry_activated_cb(self, entry):
+ self.emit('entry-activated', entry)
- __gsignals__ = {
- 'entry-activated': (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE,
- ([object]))
- }
+ def __button_release_event_cb(self, tree_view, event):
+ if event.window != tree_view.get_bin_window():
+ return False
- def __init__(self):
- ListView.__init__(self)
+ pos = tree_view.get_path_at_pos(event.x, event.y)
+ if pos is None:
+ return False
- def create_entry(self):
- entry = ChooserCollapsedEntry()
- entry.connect('entry-activated', self.__entry_activated_cb)
- return entry
+ path, column_, x_, y_ = pos
+ uid = tree_view.get_model()[path][ListModel.COLUMN_UID]
+ self.emit('entry-activated', uid)
- def __entry_activated_cb(self, entry):
- self.emit('entry-activated', entry)
+ return False
diff --git a/src/jarabe/journal/palettes.py b/src/jarabe/journal/palettes.py
index 2c15591..49cc676 100644
--- a/src/jarabe/journal/palettes.py
+++ b/src/jarabe/journal/palettes.py
@@ -41,7 +41,7 @@ class ObjectPalette(Palette):
__gsignals__ = {
'detail-clicked': (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE,
- ([]))
+ ([str])),
}
def __init__(self, metadata, detail=False):
@@ -61,7 +61,7 @@ class ObjectPalette(Palette):
style.COLOR_TRANSPARENT.get_svg()))
if metadata.has_key('title'):
- title = metadata['title']
+ title = gobject.markup_escape_text(metadata['title'])
else:
title = _('Untitled')
@@ -126,7 +126,7 @@ class ObjectPalette(Palette):
def __clipboard_get_func_cb(self, clipboard, selection_data, info, data):
# Get hold of a reference so the temp file doesn't get deleted
self._temp_file_path = model.get_file(self._metadata['uid'])
- logging.debug('__clipboard_get_func_cb %r' % self._temp_file_path)
+ logging.debug('__clipboard_get_func_cb %r', self._temp_file_path)
selection_data.set_uris(['file://' + self._temp_file_path])
def __clipboard_clear_func_cb(self, clipboard, data):
@@ -142,7 +142,7 @@ class ObjectPalette(Palette):
model.delete(self._metadata['uid'])
def __detail_activate_cb(self, menu_item):
- self.emit('detail-clicked')
+ self.emit('detail-clicked', self._metadata['uid'])
def __friend_selected_cb(self, menu_item, buddy):
logging.debug('__friend_selected_cb')
diff --git a/src/jarabe/journal/volumestoolbar.py b/src/jarabe/journal/volumestoolbar.py
index b21832e..6cb3f8d 100644
--- a/src/jarabe/journal/volumestoolbar.py
+++ b/src/jarabe/journal/volumestoolbar.py
@@ -76,7 +76,7 @@ class VolumesToolbar(gtk.Toolbar):
self._remove_button(mount)
def _add_button(self, mount):
- logging.debug('VolumeToolbar._add_button: %r' % mount.get_name())
+ logging.debug('VolumeToolbar._add_button: %r', mount.get_name())
button = VolumeButton(mount)
button.props.group = self._volume_buttons[0]
@@ -99,14 +99,14 @@ class VolumesToolbar(gtk.Toolbar):
mount.unmount(self.__unmount_cb)
def __unmount_cb(self, source, result):
- logging.debug('__unmount_cb %r %r' % (source, result))
+ logging.debug('__unmount_cb %r %r', source, result)
def _get_button_for_mount(self, mount):
mount_point = mount.get_root().get_path()
for button in self.get_children():
if button.mount_point == mount_point:
return button
- logging.error('Couldnt find button with mount_point %r' % mount_point)
+ logging.error('Couldnt find button with mount_point %r', mount_point)
return None
def _remove_button(self, mount):
diff --git a/src/jarabe/model/buddy.py b/src/jarabe/model/buddy.py
index b51b808..1fa9e2c 100644
--- a/src/jarabe/model/buddy.py
+++ b/src/jarabe/model/buddy.py
@@ -35,6 +35,9 @@ class BuddyModel(gobject.GObject):
'icon-changed': (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE,
([])),
+ 'tags-changed': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
'current-activity-changed': (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT]))
@@ -47,6 +50,7 @@ class BuddyModel(gobject.GObject):
gobject.GObject.__init__(self)
self._color = None
+ self._tags = None
self._ba_handler = None
self._pc_handler = None
self._dis_handler = None
@@ -99,6 +103,9 @@ class BuddyModel(gobject.GObject):
def get_color(self):
return self._color
+ def get_tags(self):
+ return self._tags
+
def get_buddy(self):
return self._buddy
@@ -124,6 +131,7 @@ class BuddyModel(gobject.GObject):
self._buddy = buddy
self._key = self._buddy.props.key
self._nick = self._buddy.props.nick
+ self._tags = self._buddy.props.tags
self._set_color_from_string(self._buddy.props.color)
self._pc_handler = self._buddy.connect('property-changed',
@@ -155,6 +163,9 @@ class BuddyModel(gobject.GObject):
if 'nick' in keys:
self._nick = self._buddy.props.nick
self.emit('nick-changed', self.get_nick())
+ if 'tags' in keys:
+ self._tags = self._buddy.props.tags
+ self.emit('tags-changed', self.get_tags())
def _buddy_disappeared_cb(self, buddy):
if buddy != self._buddy:
diff --git a/src/jarabe/model/bundleregistry.py b/src/jarabe/model/bundleregistry.py
index 068cf8d..1d23f0c 100644
--- a/src/jarabe/model/bundleregistry.py
+++ b/src/jarabe/model/bundleregistry.py
@@ -18,7 +18,6 @@
import os
import logging
import traceback
-import sys
import gobject
import gio
@@ -26,6 +25,7 @@ import cjson
from sugar.bundle.activitybundle import ActivityBundle
from sugar.bundle.contentbundle import ContentBundle
+from jarabe.journal.journalentrybundle import JournalEntryBundle
from sugar.bundle.bundle import MalformedBundleException, \
AlreadyInstalledException, RegistrationException
from sugar import env
@@ -68,8 +68,7 @@ class BundleRegistry(gobject.GObject):
try:
self._load_favorites()
except Exception:
- logging.error('Error while loading favorite_activities\n%s.' \
- % traceback.format_exc())
+ logging.exception('Error while loading favorite_activities.')
self._merge_default_favorites()
@@ -78,7 +77,7 @@ class BundleRegistry(gobject.GObject):
if not one_file.get_path().endswith('.activity'):
return
if event_type == gio.FILE_MONITOR_EVENT_CREATED:
- self.add_bundle(one_file.get_path())
+ self.add_bundle(one_file.get_path(), install_mime_type=True)
elif event_type == gio.FILE_MONITOR_EVENT_DELETED:
self.remove_bundle(one_file.get_path())
@@ -154,7 +153,7 @@ class BundleRegistry(gobject.GObject):
if max_version > -1 and key not in self._favorite_bundles:
self._favorite_bundles[key] = None
- logging.debug('After merging: %r' % self._favorite_bundles)
+ logging.debug('After merging: %r', self._favorite_bundles)
self._write_favorites_file()
@@ -168,6 +167,9 @@ class BundleRegistry(gobject.GObject):
def __iter__(self):
return self._bundles.__iter__()
+ def __len__(self):
+ return len(self._bundles)
+
def _scan_directory(self, path):
if not os.path.isdir(path):
return
@@ -181,9 +183,10 @@ class BundleRegistry(gobject.GObject):
bundle_dir = os.path.join(path, f)
if os.path.isdir(bundle_dir):
bundles[bundle_dir] = os.stat(bundle_dir).st_mtime
- except Exception, e:
+ except Exception:
logging.error('Error while processing installed activity ' \
- 'bundle: %s, %s, %s' % (f, e.__class__, e))
+ 'bundle %s:\n%s' % \
+ (bundle_dir, traceback.format_exc()))
bundle_dirs = bundles.keys()
bundle_dirs.sort(lambda d1, d2: cmp(bundles[d1], bundles[d2]))
@@ -192,10 +195,11 @@ class BundleRegistry(gobject.GObject):
self._add_bundle(folder)
except Exception, e:
logging.error('Error while processing installed activity ' \
- 'bundle: %s, %s, %s' % (folder, e.__class__, e))
+ 'bundle %s:\n%s' % \
+ (folder, traceback.format_exc()))
- def add_bundle(self, bundle_path):
- bundle = self._add_bundle(bundle_path)
+ def add_bundle(self, bundle_path, install_mime_type=False):
+ bundle = self._add_bundle(bundle_path, install_mime_type)
if bundle is not None:
self._set_bundle_favorite(bundle.get_bundle_id(),
bundle.get_activity_version(),
@@ -205,13 +209,14 @@ class BundleRegistry(gobject.GObject):
else:
return False
- def _add_bundle(self, bundle_path):
- logging.debug('STARTUP: Adding bundle %r' % bundle_path)
+ def _add_bundle(self, bundle_path, install_mime_type=False):
+ logging.debug('STARTUP: Adding bundle %r', bundle_path)
try:
bundle = ActivityBundle(bundle_path)
+ if install_mime_type:
+ bundle.install_mime_type(bundle_path)
except MalformedBundleException:
- logging.error('Error loading bundle %r:\n%s' % (bundle_path,
- ''.join(traceback.format_exception(*sys.exc_info()))))
+ logging.exception('Error loading bundle %r', bundle_path)
return None
if self.get_bundle(bundle.get_bundle_id()):
@@ -313,7 +318,8 @@ class BundleRegistry(gobject.GObject):
def is_installed(self, bundle):
# TODO treat ContentBundle in special way
# needs rethinking while fixing ContentBundle support
- if isinstance(bundle, ContentBundle):
+ if isinstance(bundle, ContentBundle) or \
+ isinstance(bundle, JournalEntryBundle):
return bundle.is_installed()
for installed_bundle in self._bundles:
@@ -326,12 +332,9 @@ class BundleRegistry(gobject.GObject):
def install(self, bundle):
activities_path = env.get_user_activities_path()
- if self.get_bundle(bundle.get_bundle_id()):
- raise AlreadyInstalledException
-
for installed_bundle in self._bundles:
if bundle.get_bundle_id() == installed_bundle.get_bundle_id() and \
- bundle.get_activity_version() == \
+ bundle.get_activity_version() <= \
installed_bundle.get_activity_version():
raise AlreadyInstalledException
elif bundle.get_bundle_id() == installed_bundle.get_bundle_id():
@@ -342,7 +345,8 @@ class BundleRegistry(gobject.GObject):
# TODO treat ContentBundle in special way
# needs rethinking while fixing ContentBundle support
- if isinstance(bundle, ContentBundle):
+ if isinstance(bundle, ContentBundle) or \
+ isinstance(bundle, JournalEntryBundle):
pass
elif not self.add_bundle(install_path):
raise RegistrationException
@@ -350,7 +354,8 @@ class BundleRegistry(gobject.GObject):
def uninstall(self, bundle, force=False):
# TODO treat ContentBundle in special way
# needs rethinking while fixing ContentBundle support
- if isinstance(bundle, ContentBundle):
+ if isinstance(bundle, ContentBundle) or \
+ isinstance(bundle, JournalEntryBundle):
if bundle.is_installed():
bundle.uninstall()
else:
@@ -377,13 +382,16 @@ class BundleRegistry(gobject.GObject):
act = self.get_bundle(bundle.get_bundle_id())
if act is None:
logging.warning('Activity not installed')
+ elif act.get_activity_version() == bundle.get_activity_version():
+ logging.debug('No upgrade needed, same version already installed.')
+ return
elif act.get_path().startswith(env.get_user_activities_path()):
try:
self.uninstall(bundle, force=True)
except Exception:
logging.error('Uninstall failed, still trying to install ' \
'newer bundle:\n' + \
- ''.join(traceback.format_exception(*sys.exc_info())))
+ traceback.format_exc())
else:
logging.warning('Unable to uninstall system activity, ' \
'installing upgraded version in user activities')
diff --git a/src/jarabe/model/filetransfer.py b/src/jarabe/model/filetransfer.py
index 6419f28..3c188ff 100644
--- a/src/jarabe/model/filetransfer.py
+++ b/src/jarabe/model/filetransfer.py
@@ -51,6 +51,7 @@ FT_REASON_REMOTE_ERROR = 6
CHANNEL_TYPE_FILE_TRANSFER = \
'org.freedesktop.Telepathy.Channel.Type.FileTransfer'
+# TODO Move to use splice_async() in Sugar 0.88
class StreamSplicer(gobject.GObject):
_CHUNK_SIZE = 10240 # 10K
__gsignals__ = {
@@ -146,7 +147,7 @@ class BaseFileTransfer(gobject.GObject):
handle)
def __transferred_bytes_changed_cb(self, transferred_bytes):
- logging.debug('__transferred_bytes_changed_cb %r' % transferred_bytes)
+ logging.debug('__transferred_bytes_changed_cb %r', transferred_bytes)
self.props.transferred_bytes = transferred_bytes
def _set_transferred_bytes(self, transferred_bytes):
@@ -159,11 +160,11 @@ class BaseFileTransfer(gobject.GObject):
getter=_get_transferred_bytes, setter=_set_transferred_bytes)
def __initial_offset_defined_cb(self, offset):
- logging.debug('__initial_offset_defined_cb %r' % offset)
+ logging.debug('__initial_offset_defined_cb %r', offset)
self.initial_offset = offset
def __state_changed_cb(self, state, reason):
- logging.debug('__state_changed_cb %r %r' % (state, reason))
+ logging.debug('__state_changed_cb %r %r', state, reason)
self.reason_last_change = reason
self.props.state = state
@@ -204,7 +205,7 @@ class IncomingFileTransfer(BaseFileTransfer):
SOCKET_ACCESS_CONTROL_LOCALHOST, '', 0, byte_arrays=True)
def __notify_state_cb(self, file_transfer, pspec):
- logging.debug('__notify_state_cb %r' % self.props.state)
+ logging.debug('__notify_state_cb %r', self.props.state)
if self.props.state == FT_STATE_OPEN:
# Need to hold a reference to the socket so that python doesn't
# close the fd when it goes out of scope
@@ -275,14 +276,14 @@ class OutgoingFileTransfer(BaseFileTransfer):
'org.laptop.Sugar.Presence.Buddy')
handles = ps_buddy.GetTelepathyHandles()
- logging.debug('_get_buddy_handle %r' % handles)
+ logging.debug('_get_buddy_handle %r', handles)
bus_name, object_path, handle = handles[0]
return handle
def __notify_state_cb(self, file_transfer, pspec):
- logging.debug('__notify_state_cb %r' % self.props.state)
+ logging.debug('__notify_state_cb %r', self.props.state)
if self.props.state == FT_STATE_OPEN:
# Need to hold a reference to the socket so that python doesn't
# closes the fd when it goes out of scope
@@ -290,7 +291,7 @@ class OutgoingFileTransfer(BaseFileTransfer):
self._socket.connect(self._socket_address)
output_stream = gio.unix.OutputStream(self._socket.fileno(), True)
- logging.debug('opening %s for reading' % self._file_name)
+ logging.debug('opening %s for reading', self._file_name)
input_stream = gio.File(self._file_name).read()
if self.initial_offset > 0:
input_stream.skip(self.initial_offset)
@@ -307,14 +308,14 @@ def _new_channels_cb(connection, channels):
if props[CHANNEL + '.ChannelType'] == CHANNEL_TYPE_FILE_TRANSFER and \
not props[CHANNEL + '.Requested']:
- logging.debug('__new_channels_cb %r' % object_path)
+ logging.debug('__new_channels_cb %r', object_path)
incoming_file_transfer = IncomingFileTransfer(connection,
object_path, props)
new_file_transfer.send(None, file_transfer=incoming_file_transfer)
def _monitor_connection(connection):
- logging.debug('connection added %r' % connection)
+ logging.debug('connection added %r', connection)
connection[CONNECTION_INTERFACE_REQUESTS].connect_to_signal('NewChannels',
lambda channels: _new_channels_cb(connection, channels))
@@ -322,7 +323,7 @@ def _connection_addded_cb(conn_watcher, connection):
_monitor_connection(connection)
def _connection_removed_cb(conn_watcher, connection):
- logging.debug('connection removed %r' % connection)
+ logging.debug('connection removed %r', connection)
_conn_watcher = None
diff --git a/src/jarabe/model/friends.py b/src/jarabe/model/friends.py
index 27a11dd..b7bf7f1 100644
--- a/src/jarabe/model/friends.py
+++ b/src/jarabe/model/friends.py
@@ -72,8 +72,8 @@ class Friends(gobject.GObject):
continue
buddy = BuddyModel(key=key, nick=cp.get(key, 'nick'))
self.add_friend(buddy)
- except Exception, exc:
- logging.error("Error parsing friends file: %s" % exc)
+ except Exception:
+ logging.exception('Error parsing friends file')
def save(self):
cp = ConfigParser()
@@ -98,8 +98,8 @@ class Friends(gobject.GObject):
pass
def friends_synced_error(e):
- logging.error("Error asking presence service to sync friends: %s"
- % e)
+ logging.error('Error asking presence service to sync friends: %s',
+ e)
keys = []
for friend in self:
diff --git a/src/jarabe/model/neighborhood.py b/src/jarabe/model/neighborhood.py
index f4ef9ac..2b223e4 100644
--- a/src/jarabe/model/neighborhood.py
+++ b/src/jarabe/model/neighborhood.py
@@ -122,16 +122,16 @@ class Neighborhood(gobject.GObject):
gconf_client = gconf.client_get_default()
key = '/desktop/sugar/collaboration/publish_gadget'
publish = gconf_client.get_bool(key)
- logging.debug("Gadget discovered on connection %s."
- " Publish our status: %r" %
- (conn.service_name.split('.')[-1], publish))
+ logging.debug('Gadget discovered on connection %s.'
+ ' Publish our status: %r', conn.service_name.split('.')[-1],
+ publish)
conn[CONN_INTERFACE_GADGET].Publish(publish)
self._request_random_buddies(conn, NB_RANDOM_BUDDIES)
self._request_random_activities(conn, NB_RANDOM_ACTIVITIES)
def _request_random_buddies(self, conn, nb):
- logging.debug("Request %d random buddies" % nb)
+ logging.debug('Request %d random buddies', nb)
path, props_ = conn[CONNECTION_INTERFACE_REQUESTS].CreateChannel(
{ 'org.freedesktop.Telepathy.Channel.ChannelType':
@@ -145,7 +145,7 @@ class Neighborhood(gobject.GObject):
lambda: self._request_random_buddies(conn, nb)))
def _request_random_activities(self, conn, nb):
- logging.debug("Request %d random activities" % nb)
+ logging.debug('Request %d random activities', nb)
path, props_ = conn[CONNECTION_INTERFACE_REQUESTS].CreateChannel(
{ 'org.freedesktop.Telepathy.Channel.ChannelType':
diff --git a/src/jarabe/model/network.py b/src/jarabe/model/network.py
index bff5197..4dbc309 100644
--- a/src/jarabe/model/network.py
+++ b/src/jarabe/model/network.py
@@ -1,4 +1,5 @@
# Copyright (C) 2008 Red Hat, Inc.
+# Copyright (C) 2009 Tomeu Vizoso, Simon Schampijer
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -103,7 +104,7 @@ class Wireless(object):
self.ssid = None
self.security = None
self.mode = None
- self.channel = None
+ self.band = None
def get_dict(self):
wireless = {'ssid': self.ssid}
@@ -111,8 +112,8 @@ class Wireless(object):
wireless['security'] = self.security
if self.mode:
wireless['mode'] = self.mode
- if self.channel:
- wireless['channel'] = self.channel
+ if self.band:
+ wireless['band'] = self.band
return wireless
class Connection(object):
@@ -138,7 +139,6 @@ class IP4Config(object):
def get_dict(self):
ip4_config = {}
- print self.method
if self.method is not None:
ip4_config['method'] = self.method
return ip4_config
@@ -254,8 +254,8 @@ class NMSettingsConnection(dbus.service.Object):
if not config.read(config_path):
logging.error('Error reading the nm config file')
return
- except ConfigParser.ParsingError, e:
- logging.error('Error reading the nm config file: %s' % e)
+ except ConfigParser.ParsingError:
+ logging.exception('Error reading the nm config file')
return
identifier = self._settings.connection.id
@@ -269,7 +269,6 @@ class NMSettingsConnection(dbus.service.Object):
if self._settings.connection.timestamp is not None:
config.set(identifier, 'timestamp',
self._settings.connection.timestamp)
-
if self._settings.wireless_security is not None:
if self._settings.wireless_security.key_mgmt is not None:
config.set(identifier, 'key-mgmt',
@@ -293,13 +292,13 @@ class NMSettingsConnection(dbus.service.Object):
elif self._settings.wireless_security.key_mgmt == 'wpa-psk':
config.set(identifier, 'key', self._secrets.psk)
except ConfigParser.Error, e:
- logging.error('Error constructing %s: %s' % (identifier, e))
+ logging.exception('Error constructing %s', identifier)
else:
f = open(config_path, 'w')
try:
config.write(f)
- except ConfigParser.Error, e:
- logging.error('Can not write %s error: %s' % (config_path, e))
+ except ConfigParser.Error:
+ logging.exception('Can not write %s', config_path)
f.close()
@dbus.service.method(dbus_interface=NM_CONNECTION_IFACE,
@@ -311,16 +310,16 @@ class NMSettingsConnection(dbus.service.Object):
async_callbacks=('reply', 'error'),
in_signature='sasb', out_signature='a{sa{sv}}')
def GetSecrets(self, setting_name, hints, request_new, reply, error):
- logging.debug('Secrets requested for connection %s request_new=%s'
- % (self.path, request_new))
+ logging.debug('Secrets requested for connection %s request_new=%s',
+ self.path, request_new)
if request_new or self._secrets is None:
# request_new is for example the case when the pw on the AP changes
response = SecretsResponse(self, reply, error)
try:
self.secrets_request.send(self, response=response)
- except Exception, e:
- logging.error('Error requesting the secrets via dialog: %s' % e)
+ except Exception:
+ logging.exception('Error requesting the secrets via dialog')
else:
reply(self._secrets.get_dict())
@@ -329,8 +328,8 @@ def get_settings():
if _nm_settings is None:
try:
_nm_settings = NMSettings()
- except dbus.DBusException, e:
- logging.error('Cannot create the UserSettings service %s.', e)
+ except dbus.DBusException:
+ logging.exception('Cannot create the UserSettings service.')
load_connections()
return _nm_settings
@@ -368,8 +367,8 @@ def load_connections():
if not config.read(config_path):
logging.error('Error reading the nm config file')
return
- except ConfigParser.ParsingError, e:
- logging.error('Error reading the nm config file: %s' % e)
+ except ConfigParser.ParsingError:
+ logging.exception('Error reading the nm config file')
return
for section in config.sections():
@@ -413,7 +412,7 @@ def load_connections():
if config.has_option(section, 'pairwise'):
value = config.get(section, 'pairwise')
settings.wireless_security.pairwise = value
- except ConfigParser.Error, e:
- logging.error('Error reading section: %s' % e)
+ except ConfigParser.Error:
+ logging.exception('Error reading section')
else:
add_connection(ssid, settings, secrets)
diff --git a/src/jarabe/model/notifications.py b/src/jarabe/model/notifications.py
index da5c590..a2345f7 100644
--- a/src/jarabe/model/notifications.py
+++ b/src/jarabe/model/notifications.py
@@ -42,8 +42,8 @@ class NotificationService(dbus.service.Object):
def Notify(self, app_name, replaces_id, app_icon, summary, body, actions,
hints, expire_timeout):
- logging.debug('Received notification: %r' % ([app_name, replaces_id,
- app_icon, summary, body, actions, hints, expire_timeout]))
+ logging.debug('Received notification: %r', [app_name, replaces_id,
+ app_icon, summary, body, actions, hints, expire_timeout])
if replaces_id > 0:
notification_id = replaces_id
diff --git a/src/jarabe/model/owner.py b/src/jarabe/model/owner.py
index bdfd9a8..2075f08 100644
--- a/src/jarabe/model/owner.py
+++ b/src/jarabe/model/owner.py
@@ -62,8 +62,8 @@ class Owner(gobject.GObject):
raise RuntimeError("invalid buddy icon")
# Get the icon's hash
- import md5
- digest = md5.new(self._icon).digest()
+ import hashlib
+ digest = hashlib.md5(self._icon).digest()
self._icon_hash = util.printable_hash(digest)
self._pservice = presenceservice.get_instance()
diff --git a/src/jarabe/model/screen.py b/src/jarabe/model/screen.py
index 87dc370..4403c1c 100644
--- a/src/jarabe/model/screen.py
+++ b/src/jarabe/model/screen.py
@@ -22,9 +22,6 @@ _HARDWARE_MANAGER_INTERFACE = 'org.freedesktop.ohm.Keystore'
_HARDWARE_MANAGER_SERVICE = 'org.freedesktop.ohm'
_HARDWARE_MANAGER_OBJECT_PATH = '/org/freedesktop/ohm/Keystore'
-COLOR_MODE = 0
-B_AND_W_MODE = 1
-
_ohm_service = None
def _get_ohm():
@@ -44,21 +41,3 @@ def set_dcon_freeze(frozen):
except dbus.DBusException:
logging.error('Cannot unfreeze the DCON')
-def set_display_mode(mode):
- try:
- _get_ohm().SetKey("display.dcon_mode", mode)
- except dbus.DBusException:
- logging.error('Cannot change DCON mode')
-
-def set_display_brightness(level):
- try:
- _get_ohm().SetKey("backlight.hardware_brightness", level)
- except dbus.DBusException:
- logging.error('Cannot set display brightness')
-
-def get_display_brightness():
- try:
- return _get_ohm().GetKey("backlight.hardware_brightness")
- except dbus.DBusException:
- logging.error('Cannot get display brightness')
- return 0
diff --git a/src/jarabe/model/shell.py b/src/jarabe/model/shell.py
index 709eb03..ec315ca 100644
--- a/src/jarabe/model/shell.py
+++ b/src/jarabe/model/shell.py
@@ -269,7 +269,7 @@ class Activity(gobject.GObject):
pass
def _set_active_error(self, err):
- logging.error("set_active() failed: %s" % err)
+ logging.error("set_active() failed: %s", err)
class ShellModel(gobject.GObject):
"""Model of the shell (activity management)
@@ -457,6 +457,12 @@ class ShellModel(gobject.GObject):
if activity_id:
home_activity = self.get_activity_by_id(activity_id)
+ xid = window.get_xid()
+ gdk_window = gtk.gdk.window_foreign_new(xid)
+ gdk_window.set_decorations(0)
+
+ window.maximize()
+
if not home_activity:
home_activity = Activity(activity_info, activity_id, window)
self._add_activity(home_activity)
@@ -468,8 +474,8 @@ class ShellModel(gobject.GObject):
self.emit('launch-completed', home_activity)
startup_time = time.time() - home_activity.get_launch_time()
- logging.debug('%s launched in %f seconds.' %
- (home_activity.get_type(), startup_time))
+ logging.debug('%s launched in %f seconds.',
+ home_activity.get_type(), startup_time)
if self._active_activity is None:
self._set_active_activity(home_activity)
@@ -531,7 +537,7 @@ class ShellModel(gobject.GObject):
if home_activity:
self._remove_activity(home_activity)
else:
- logging.error('Model for window %d does not exist.' % xid)
+ logging.error('Model for window %d does not exist.', xid)
def notify_launch(self, activity_id, service_name):
registry = get_registry()
@@ -556,13 +562,13 @@ class ShellModel(gobject.GObject):
def notify_launch_failed(self, activity_id):
home_activity = self.get_activity_by_id(activity_id)
if home_activity:
- logging.debug("Activity %s (%s) launch failed" % \
- (activity_id, home_activity.get_type()))
+ logging.debug("Activity %s (%s) launch failed", activity_id,
+ home_activity.get_type())
home_activity.props.launching = False
self._remove_activity(home_activity)
else:
- logging.error('Model for activity id %s does not exist.'
- % activity_id)
+ logging.error('Model for activity id %s does not exist.',
+ activity_id)
self.emit('launch-failed', home_activity)
@@ -570,12 +576,12 @@ class ShellModel(gobject.GObject):
home_activity = self.get_activity_by_id(activity_id)
if not home_activity:
- logging.debug('Activity %s has been closed already.' % activity_id)
+ logging.debug('Activity %s has been closed already.', activity_id)
return False
if home_activity.props.launching:
- logging.debug('Activity %s still launching, assuming it failed...'
- % activity_id)
+ logging.debug('Activity %s still launching, assuming it failed.',
+ activity_id)
self.notify_launch_failed(activity_id)
return False
diff --git a/src/jarabe/view/buddymenu.py b/src/jarabe/view/buddymenu.py
index 35a8301..e9e9f8e 100644
--- a/src/jarabe/view/buddymenu.py
+++ b/src/jarabe/view/buddymenu.py
@@ -79,8 +79,8 @@ class BuddyMenu(Palette):
self._update_invite_menu(activity)
def _add_my_items(self):
- item = MenuItem(_('My Settings'), 'preferences-system')
- item.connect('activate', self.__controlpanel_activate_cb)
+ item = MenuItem(_('Shutdown'), 'system-shutdown')
+ item.connect('activate', self.__shutdown_activate_cb)
self.menu.append(item)
item.show()
@@ -92,13 +92,8 @@ class BuddyMenu(Palette):
self.menu.append(item)
item.show()
- item = MenuItem(_('Restart'), 'system-restart')
- item.connect('activate', self.__reboot_activate_cb)
- self.menu.append(item)
- item.show()
-
- item = MenuItem(_('Shutdown'), 'system-shutdown')
- item.connect('activate', self.__shutdown_activate_cb)
+ item = MenuItem(_('My Settings'), 'preferences-system')
+ item.connect('activate', self.__controlpanel_activate_cb)
self.menu.append(item)
item.show()
diff --git a/src/jarabe/view/keyhandler.py b/src/jarabe/view/keyhandler.py
index 08856c0..26a7f26 100644
--- a/src/jarabe/view/keyhandler.py
+++ b/src/jarabe/view/keyhandler.py
@@ -22,12 +22,12 @@ import errno
import traceback
import sys
+import gconf
import dbus
import gtk
from sugar._sugarext import KeyGrabber
-from jarabe.model import screen
from jarabe.model import sound
from jarabe.model import shell
from jarabe.model import session
@@ -35,10 +35,9 @@ from jarabe.view.tabbinghandler import TabbingHandler
from jarabe.model.shell import ShellModel
from jarabe import config
from jarabe.journal import journalactivity
+from jarabe.desktop import homewindow
-_BRIGHTNESS_STEP = 2
_VOLUME_STEP = sound.VOLUME_STEP
-_BRIGHTNESS_MAX = 15
_VOLUME_MAX = 100
_TABBING_MODIFIER = gtk.gdk.MOD1_MASK
@@ -47,10 +46,6 @@ _actions_table = {
'F2' : 'zoom_group',
'F3' : 'zoom_home',
'F4' : 'zoom_activity',
- 'F9' : 'brightness_down',
- 'F10' : 'brightness_up',
- '<alt>F9' : 'brightness_min',
- '<alt>F10' : 'brightness_max',
'XF86AudioMute' : 'volume_mute',
'F11' : 'volume_down',
'XF86AudioLowerVolume' : 'volume_down',
@@ -59,7 +54,6 @@ _actions_table = {
'<alt>F11' : 'volume_min',
'<alt>F12' : 'volume_max',
'0x93' : 'frame',
- '0xEB' : 'rotate',
'<alt>Tab' : 'next_window',
'<alt><shift>Tab' : 'previous_window',
'<alt>Escape' : 'close_window',
@@ -69,8 +63,9 @@ _actions_table = {
'<alt><shift>q' : 'quit_emulator',
'XF86Search' : 'open_search',
'<alt><shift>o' : 'open_search',
- '<alt><shift>r' : 'rotate',
'<alt><shift>s' : 'say_text',
+ 'Alt_L' : 'disable_resume_mode',
+ 'Alt_R' : 'disable_resume_mode',
}
SPEECH_DBUS_SERVICE = 'org.laptop.Speech'
@@ -80,12 +75,13 @@ SPEECH_DBUS_INTERFACE = 'org.laptop.Speech'
class KeyHandler(object):
def __init__(self, frame):
self._frame = frame
- self._screen_rotation = 0
self._key_pressed = None
self._keycode_pressed = 0
self._keystate_pressed = 0
self._speech_proxy = None
+ self._ungrab_metacity_keys()
+
self._key_grabber = KeyGrabber()
self._key_grabber.connect('key-pressed',
self._key_pressed_cb)
@@ -98,7 +94,7 @@ class KeyHandler(object):
if f.endswith('.py') and not f.startswith('__'):
module_name = f[:-3]
try:
- logging.debug('Loading module %r' % module_name)
+ logging.debug('Loading module %r', module_name)
module = __import__('globalkey.' + module_name, globals(),
locals(), [module_name])
for key in module.BOUND_KEYS:
@@ -111,6 +107,15 @@ class KeyHandler(object):
self._key_grabber.grab_keys(_actions_table.keys())
+ def _ungrab_metacity_keys(self):
+ """So we can grab those instead.
+ """
+ client = gconf.client_get_default()
+ for key in ['run_command_screenshot', 'switch_windows',
+ 'cycle_windows']:
+ key = '/apps/metacity/global_keybindings/' + key
+ client.set_string(key, 'disabled')
+
def _change_volume(self, step=None, value=None):
if step is not None:
volume = sound.get_volume() + step
@@ -122,20 +127,6 @@ class KeyHandler(object):
sound.set_volume(volume)
sound.set_muted(volume == 0)
- def _change_brightness(self, step=None, value=None):
- if step is not None:
- level = screen.get_display_brightness() + step
- elif value is not None:
- level = value
-
- level = min(max(0, level), _BRIGHTNESS_MAX)
-
- screen.set_display_brightness(level)
- if level == 0:
- screen.set_display_mode(screen.B_AND_W_MODE)
- else:
- screen.set_display_mode(screen.COLOR_MODE)
-
def _get_speech_proxy(self):
if self._speech_proxy is None:
bus = dbus.SessionBus()
@@ -146,126 +137,81 @@ class KeyHandler(object):
return self._speech_proxy
def _on_speech_err(self, ex):
- logging.error("An error occurred with the ESpeak service: %r" % (ex, ))
+ logging.error('An error occurred with the ESpeak service: %r', ex)
def _primary_selection_cb(self, clipboard, text, user_data):
- logging.debug('KeyHandler._primary_selection_cb: %r' % text)
+ logging.debug('KeyHandler._primary_selection_cb: %r', text)
if text:
self._get_speech_proxy().SayText(text, reply_handler=lambda: None, \
error_handler=self._on_speech_err)
- def handle_say_text(self):
+ def handle_say_text(self, event_time):
clipboard = gtk.clipboard_get(selection="PRIMARY")
clipboard.request_text(self._primary_selection_cb)
- def handle_previous_window(self):
- self._tabbing_handler.previous_activity()
+ def handle_previous_window(self, event_time):
+ self._tabbing_handler.previous_activity(event_time)
- def handle_next_window(self):
- self._tabbing_handler.next_activity()
+ def handle_next_window(self, event_time):
+ self._tabbing_handler.next_activity(event_time)
- def handle_close_window(self):
+ def handle_close_window(self, event_time):
active_activity = shell.get_model().get_active_activity()
if active_activity.is_journal():
return
active_activity.get_window().close()
- def handle_zoom_mesh(self):
+ def handle_zoom_mesh(self, event_time):
shell.get_model().zoom_level = ShellModel.ZOOM_MESH
- def handle_zoom_group(self):
+ def handle_zoom_group(self, event_time):
shell.get_model().zoom_level = ShellModel.ZOOM_GROUP
- def handle_zoom_home(self):
+ def handle_zoom_home(self, event_time):
shell.get_model().zoom_level = ShellModel.ZOOM_HOME
- def handle_zoom_activity(self):
+ def handle_zoom_activity(self, event_time):
shell.get_model().zoom_level = ShellModel.ZOOM_ACTIVITY
- def handle_brightness_max(self):
- self._change_brightness(value=_BRIGHTNESS_MAX)
-
- def handle_brightness_min(self):
- self._change_brightness(value=0)
-
- def handle_volume_max(self):
+ def handle_volume_max(self, event_time):
self._change_volume(value=_VOLUME_MAX)
- def handle_volume_min(self):
+ def handle_volume_min(self, event_time):
self._change_volume(value=0)
- def handle_brightness_up(self):
- self._change_brightness(step=_BRIGHTNESS_STEP)
-
- def handle_brightness_down(self):
- self._change_brightness(step=-_BRIGHTNESS_STEP)
-
- def handle_volume_mute(self):
+ def handle_volume_mute(self, event_time):
if sound.get_muted() is True:
sound.set_muted(False)
else:
sound.set_muted(True)
- def handle_volume_up(self):
+ def handle_volume_up(self, event_time):
self._change_volume(step=_VOLUME_STEP)
- def handle_volume_down(self):
+ def handle_volume_down(self, event_time):
self._change_volume(step=-_VOLUME_STEP)
- def handle_frame(self):
+ def handle_frame(self, event_time):
self._frame.notify_key_press()
- def handle_rotate(self):
- """
- Handles rotation of the display (using xrandr) and of the d-pad.
-
- Notes: default mappings for keypad on MP
- KP_Up 80
- KP_Right 85
- KP_Down 88
- KP_Left 83
- """
-
- states = [ 'normal', 'left', 'inverted', 'right']
- keycodes = (80, 85, 88, 83, 80, 85, 88, 83)
- keysyms = ("KP_Up", "KP_Right", "KP_Down", "KP_Left")
-
- self._screen_rotation -= 1
- self._screen_rotation %= 4
-
- actual_keycodes = keycodes[self._screen_rotation:self._screen_rotation
- + 4]
- # code_pairs now contains a mapping of keycode -> keysym in the current
- # orientation
- code_pairs = zip(actual_keycodes, keysyms)
-
- # Using the mappings in code_pairs, we dynamically build up an xmodmap
- # command to rotate the dpad keys.
- argv = ['xmodmap']
- for arg in [('-e', 'keycode %i = %s' % p) for p in code_pairs]:
- argv.extend(arg)
-
- # If either the xmodmap or xrandr command fails, check_call will fail
- # with CalledProcessError, which we raise.
- try:
- subprocess.check_call(argv)
- subprocess.check_call(['xrandr', '-o',
- states[self._screen_rotation]])
- except OSError, e:
- if e.errno != errno.EINTR:
- raise
-
- def handle_quit_emulator(self):
+ def handle_quit_emulator(self, event_time):
session.get_session_manager().shutdown()
- def handle_open_search(self):
+ def handle_open_search(self, event_time):
journalactivity.get_journal().focus_search()
- def _key_pressed_cb(self, grabber, keycode, state):
+ def handle_disable_resume_mode(self, event_time):
+ # TODO: KeyHandler should be a singleton and interested parties
+ # would listen to it. That way it wouldn't need to reference half
+ # of the shell classes.
+ home_box = homewindow.get_instance().get_home_box()
+ home_box.set_resume_mode(False)
+
+ def _key_pressed_cb(self, grabber, keycode, state, event_time):
key = grabber.get_key(keycode, state)
- logging.debug('_key_pressed_cb: %i %i %s' % (keycode, state, key))
- if key:
+ logging.debug('_key_pressed_cb: %i %i %s', keycode, state, key)
+ if key is not None:
self._key_pressed = key
self._keycode_pressed = keycode
self._keystate_pressed = state
@@ -275,14 +221,14 @@ class KeyHandler(object):
# Only accept window tabbing events, everything else
# cancels the tabbing operation.
if not action in ["next_window", "previous_window"]:
- self._tabbing_handler.stop()
+ self._tabbing_handler.stop(event_time)
return True
if hasattr(action, 'handle_key_press'):
action.handle_key_press(key)
elif isinstance(action, basestring):
method = getattr(self, 'handle_' + action)
- method()
+ method(event_time)
else:
raise TypeError('Invalid action %r' % action)
@@ -291,17 +237,33 @@ class KeyHandler(object):
# If this is not a registered key, then cancel tabbing.
if self._tabbing_handler.is_tabbing():
if not grabber.is_modifier(keycode):
- self._tabbing_handler.stop()
+ self._tabbing_handler.stop(event_time)
return True
return False
- def _key_released_cb(self, grabber, keycode, state):
+ def _is_resume_mode_keycode(self, keycode):
+ """See if the physical key pressed matches one of the keys that modify
+ the resume mode of the favorites view.
+ """
+ keymap = gtk.gdk.keymap_get_default()
+ entries = keymap.get_entries_for_keycode(keycode)
+ for entry in entries:
+ if gtk.gdk.keyval_name(entry[0]) in ['Alt_L', 'Alt_R']:
+ return True
+ return False
+
+ def _key_released_cb(self, grabber, keycode, state, event_time):
+ logging.debug('_key_released_cb: %i %i' % (keycode, state))
+ if self._is_resume_mode_keycode(keycode):
+ home_box = homewindow.get_instance().get_home_box()
+ home_box.set_resume_mode(True)
+
if self._tabbing_handler.is_tabbing():
# We stop tabbing and switch to the new window as soon as the
# modifier key is raised again.
if grabber.is_modifier(keycode, mask=_TABBING_MODIFIER):
- self._tabbing_handler.stop()
+ self._tabbing_handler.stop(event_time)
return True
return False
diff --git a/src/jarabe/view/launcher.py b/src/jarabe/view/launcher.py
index 3071790..643baee 100644
--- a/src/jarabe/view/launcher.py
+++ b/src/jarabe/view/launcher.py
@@ -33,6 +33,7 @@ class LaunchWindow(gtk.Window):
gobject.GObject.__init__(self)
self.props.type_hint = gtk.gdk.WINDOW_TYPE_HINT_NORMAL
+ self.props.decorated = False
canvas = hippo.Canvas()
canvas.modify_bg(gtk.STATE_NORMAL, style.COLOR_WHITE.get_gdk_color())
@@ -156,4 +157,4 @@ def _destroy_launcher(home_activity):
_launchers[activity_id].destroy()
del _launchers[activity_id]
else:
- logging.error('Launcher for %s is missing' % activity_id)
+ logging.error('Launcher for %s is missing', activity_id)
diff --git a/src/jarabe/view/palettes.py b/src/jarabe/view/palettes.py
index 9d45eb5..fe711fc 100644
--- a/src/jarabe/view/palettes.py
+++ b/src/jarabe/view/palettes.py
@@ -17,10 +17,9 @@
import os
import statvfs
from gettext import gettext as _
-import gconf
import logging
-import gobject
+import gconf
import gtk
from sugar import env
@@ -32,7 +31,6 @@ from sugar.graphics.xocolor import XoColor
from sugar.activity import activityfactory
from sugar.activity.activityhandle import ActivityHandle
-from jarabe.model import bundleregistry
from jarabe.model import shell
from jarabe.view import launcher
from jarabe.view.viewsource import setup_view_source
@@ -107,12 +105,9 @@ class CurrentActivityPalette(BasePalette):
class ActivityPalette(Palette):
__gtype_name__ = 'SugarActivityPalette'
- __gsignals__ = {
- 'erase-activated' : (gobject.SIGNAL_RUN_FIRST,
- gobject.TYPE_NONE, ([str]))
- }
-
def __init__(self, activity_info):
+ self._activity_info = activity_info
+
client = gconf.client_get_default()
color = XoColor(client.get_string("/desktop/sugar/user/color"))
activity_icon = Icon(file=activity_info.get_icon(),
@@ -122,17 +117,9 @@ class ActivityPalette(Palette):
Palette.__init__(self, primary_text=activity_info.get_name(),
icon=activity_icon)
- registry = bundleregistry.get_registry()
-
- self._bundle = activity_info
- self._bundle_id = activity_info.get_bundle_id()
- self._version = activity_info.get_activity_version()
- self._favorite = registry.is_bundle_favorite(self._bundle_id,
- self._version)
-
xo_color = XoColor('%s,%s' % (style.COLOR_WHITE.get_svg(),
style.COLOR_TRANSPARENT.get_svg()))
- menu_item = MenuItem(text_label=_('Start'),
+ menu_item = MenuItem(text_label=_('Start new'),
file_name=activity_info.get_icon(),
xo_color=xo_color)
menu_item.connect('activate', self.__start_activate_cb)
@@ -141,46 +128,6 @@ class ActivityPalette(Palette):
# TODO: start-with
- self._favorite_item = MenuItem('')
- self._favorite_icon = Icon(icon_name='emblem-favorite',
- icon_size=gtk.ICON_SIZE_MENU)
- self._favorite_item.set_image(self._favorite_icon)
- self._favorite_item.connect('activate',
- self.__change_favorite_activate_cb)
- self.menu.append(self._favorite_item)
- self._favorite_item.show()
-
- menu_item = MenuItem(_('Erase'), 'list-remove')
- menu_item.connect('activate', self.__erase_activate_cb)
- self.menu.append(menu_item)
- menu_item.show()
-
- if not os.access(self._bundle.get_path(), os.W_OK):
- menu_item.props.sensitive = False
-
- registry = bundleregistry.get_registry()
- self._activity_changed_sid = registry.connect('bundle_changed',
- self.__activity_changed_cb)
- self._update_favorite_item()
-
- self.connect('destroy', self.__destroy_cb)
-
- def __destroy_cb(self, palette):
- self.disconnect(self._activity_changed_sid)
-
- def _update_favorite_item(self):
- label = self._favorite_item.child
- if self._favorite:
- label.set_text(_('Remove favorite'))
- xo_color = XoColor('%s,%s' % (style.COLOR_WHITE.get_svg(),
- style.COLOR_TRANSPARENT.get_svg()))
- else:
- label.set_text(_('Make favorite'))
- client = gconf.client_get_default()
- xo_color = XoColor(client.get_string("/desktop/sugar/user/color"))
-
- self._favorite_icon.props.xo_color = xo_color
-
def __start_activate_cb(self, menu_item):
self.popdown(immediate=True)
@@ -189,28 +136,11 @@ class ActivityPalette(Palette):
activity_id = activityfactory.create_activity_id()
launcher.add_launcher(activity_id,
- self._bundle.get_icon(),
+ self._activity_info.get_icon(),
xo_color)
handle = ActivityHandle(activity_id)
- activityfactory.create(self._bundle, handle)
-
- def __change_favorite_activate_cb(self, menu_item):
- registry = bundleregistry.get_registry()
- registry.set_bundle_favorite(self._bundle_id,
- self._version,
- not self._favorite)
-
- def __activity_changed_cb(self, activity_registry, activity_info):
- if activity_info.get_bundle_id() == self._bundle_id and \
- activity_info.get_activity_version() == self._version:
- registry = bundleregistry.get_registry()
- self._favorite = registry.is_bundle_favorite(self._bundle_id,
- self._version)
- self._update_favorite_item()
-
- def __erase_activate_cb(self, menu_item):
- self.emit('erase-activated', self._bundle_id)
+ activityfactory.create(self._activity_info, handle)
class JournalPalette(BasePalette):
def __init__(self, home_activity):
@@ -299,7 +229,7 @@ class VolumePalette(Palette):
self._mount.unmount(self.__unmount_cb)
def __unmount_cb(self, mount, result):
- logging.debug('__unmount_cb %r %r' % (mount, result))
+ logging.debug('__unmount_cb %r %r', mount, result)
mount.unmount_finish(result)
def __popup_cb(self, palette):
diff --git a/src/jarabe/view/service.py b/src/jarabe/view/service.py
index df00ba6..ef225bf 100644
--- a/src/jarabe/view/service.py
+++ b/src/jarabe/view/service.py
@@ -29,8 +29,6 @@ _DBUS_SHELL_IFACE = "org.laptop.Shell"
_DBUS_OWNER_IFACE = "org.laptop.Shell.Owner"
_DBUS_PATH = "/org/laptop/Shell"
-_DBUS_RAINBOW_IFACE = "org.laptop.security.Rainbow"
-
class UIService(dbus.service.Object):
"""Provides d-bus service to script the shell's operations
@@ -50,8 +48,6 @@ class UIService(dbus.service.Object):
anything other than add_bundle
"""
- _rainbow = None
-
def __init__(self):
bus = dbus.SessionBus()
bus_name = dbus.service.BusName(_DBUS_SERVICE, bus=bus)
@@ -123,22 +119,9 @@ class UIService(dbus.service.Object):
def _owner_icon_changed_cb(self, new_icon):
self.IconChanged(dbus.ByteArray(new_icon))
- def _get_rainbow_service(self):
- """Lazily initializes an interface to the Rainbow security daemon."""
- if self._rainbow is None:
- system_bus = dbus.SystemBus()
- obj = system_bus.get_object(_DBUS_RAINBOW_IFACE, '/',
- follow_name_owner_changes=True)
- self._rainbow = dbus.Interface(obj,
- dbus_interface=_DBUS_RAINBOW_IFACE)
- return self._rainbow
-
@dbus.service.signal(_DBUS_OWNER_IFACE, signature="s")
def CurrentActivityChanged(self, activity_id):
- if os.path.exists('/etc/olpc-security'):
- self._get_rainbow_service().ChangeActivity(
- activity_id,
- dbus_interface=_DBUS_RAINBOW_IFACE)
+ pass
def _cur_activity_changed_cb(self, shell_model, new_activity):
new_id = ""
diff --git a/src/jarabe/view/tabbinghandler.py b/src/jarabe/view/tabbinghandler.py
index b1c85c6..bb95c26 100644
--- a/src/jarabe/view/tabbinghandler.py
+++ b/src/jarabe/view/tabbinghandler.py
@@ -15,8 +15,10 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import logging
-import gtk
+import time
+
import gobject
+import gtk
from jarabe.model import shell
@@ -57,27 +59,28 @@ class TabbingHandler(object):
else:
self._frame.show(self._frame.MODE_NON_INTERACTIVE)
- def __timeout_cb(self):
- self._activate_current()
+ def __timeout_cb(self, event_time):
+ self._activate_current(event_time)
self._timeout = None
return False
- def _start_timeout(self):
+ def _start_timeout(self, event_time):
self._cancel_timeout()
- self._timeout = gobject.timeout_add(_RAISE_DELAY, self.__timeout_cb)
+ self._timeout = gobject.timeout_add(_RAISE_DELAY,
+ lambda: self.__timeout_cb(event_time))
def _cancel_timeout(self):
if self._timeout:
gobject.source_remove(self._timeout)
self._timeout = None
- def _activate_current(self):
+ def _activate_current(self, event_time):
home_model = shell.get_model()
activity = home_model.get_tabbing_activity()
if activity and activity.get_window():
- activity.get_window().activate(1)
+ activity.get_window().activate(event_time)
- def next_activity(self):
+ def next_activity(self, event_time):
if not self._tabbing:
first_switch = True
self._start_tabbing()
@@ -96,11 +99,11 @@ class TabbingHandler(object):
activity = shell_model.get_next_activity(current=activity)
shell_model.set_tabbing_activity(activity)
- self._start_timeout()
+ self._start_timeout(event_time)
else:
- self._activate_next_activity()
+ self._activate_next_activity(event_time)
- def previous_activity(self):
+ def previous_activity(self, event_time):
if not self._tabbing:
first_switch = True
self._start_tabbing()
@@ -119,16 +122,16 @@ class TabbingHandler(object):
activity = shell_model.get_previous_activity(current=activity)
shell_model.set_tabbing_activity(activity)
- self._start_timeout()
+ self._start_timeout(event_time)
else:
- self._activate_next_activity()
+ self._activate_next_activity(event_time)
- def _activate_next_activity(self):
+ def _activate_next_activity(self, event_time):
next_activity = shell.get_model().get_next_activity()
if next_activity:
- next_activity.get_window().activate(gtk.get_current_event_time())
+ next_activity.get_window().activate(event_time)
- def stop(self):
+ def stop(self, event_time):
gtk.gdk.keyboard_ungrab()
gtk.gdk.pointer_ungrab()
self._tabbing = False
@@ -136,7 +139,7 @@ class TabbingHandler(object):
self._frame.hide()
self._cancel_timeout()
- self._activate_current()
+ self._activate_current(event_time)
home_model = shell.get_model()
home_model.set_tabbing_activity(None)
diff --git a/src/jarabe/view/viewsource.py b/src/jarabe/view/viewsource.py
index 870b176..43f668a 100644
--- a/src/jarabe/view/viewsource.py
+++ b/src/jarabe/view/viewsource.py
@@ -64,8 +64,8 @@ def setup_view_source(activity):
bundle_path = activity.get_bundle_path()
if window_xid in map_activity_to_window:
- _logger.debug('Viewsource window already open for %s %s' % \
- (window_xid, bundle_path))
+ _logger.debug('Viewsource window already open for %s %s', window_xid,
+ bundle_path)
return
document_path = None
@@ -95,7 +95,7 @@ class ViewSource(gtk.Window):
def __init__(self, window_xid, bundle_path, document_path, title):
gtk.Window.__init__(self)
- logging.debug('ViewSource paths: %r %r' % (bundle_path, document_path))
+ logging.debug('ViewSource paths: %r %r', bundle_path, document_path)
self.set_decorated(False)
self.set_position(gtk.WIN_POS_CENTER_ALWAYS)
@@ -248,7 +248,7 @@ class DocumentButton(RadioToolButton):
self._jobject.destroy()
def __internal_save_error_cb(self, err):
- logging.debug("Error saving Source object to datastore: %s" % err)
+ logging.debug('Error saving Source object to datastore: %s', err)
self._jobject.destroy()
class Toolbar(gtk.Toolbar):
@@ -439,7 +439,7 @@ class SourceDisplay(gtk.ScrolledWindow):
return
mime_type = mime.get_for_file(self._file_path)
- logging.debug('Detected mime type: %r' % mime_type)
+ logging.debug('Detected mime type: %r', mime_type)
language_manager = gtksourceview2.language_manager_get_default()
detected_language = None
@@ -450,7 +450,7 @@ class SourceDisplay(gtk.ScrolledWindow):
break
if detected_language is not None:
- logging.debug('Detected language: %r' % \
+ logging.debug('Detected language: %r',
detected_language.get_name())
self._buffer.set_language(detected_language)