diff options
78 files changed, 2775 insertions, 1949 deletions
diff --git a/bin/sugar-emulator b/bin/sugar-emulator index acd5976..7f06609 100644 --- a/bin/sugar-emulator +++ b/bin/sugar-emulator @@ -88,11 +88,10 @@ def _start_xephyr(dpi, dimensions, fullscreen): tries -= 1 time.sleep(0.1) -def _start_matchbox(): - cmd = ['matchbox-window-manager'] +def _start_window_manager(): + cmd = ['metacity'] - cmd.extend(['-use_titlebar', 'no']) - cmd.extend(['-theme', 'sugar']) + cmd.extend(['--no-force-fullscreen']) gobject.spawn_async(cmd, flags=gobject.SPAWN_SEARCH_PATH) @@ -157,7 +156,7 @@ def main(): if not args: command.append('sugar') else: - _start_matchbox() + _start_window_manager() if args[0].endswith('.py'): command.append('python') diff --git a/bin/sugar-session b/bin/sugar-session index 6a5e9a4..c891bdb 100644 --- a/bin/sugar-session +++ b/bin/sugar-session @@ -45,7 +45,7 @@ gettext.bindtextdomain('sugar', config.locale_path) gettext.bindtextdomain('sugar-toolkit', config.locale_path) gettext.textdomain('sugar') -from jarabe.desktop.homewindow import HomeWindow +from jarabe.desktop import homewindow from jarabe.model import sound from jarabe.view import launcher from jarabe import intro @@ -117,9 +117,10 @@ def setup_journal_cb(): from jarabe.journal import journalactivity journalactivity.start() -def show_software_updates_cb(home_window): +def show_software_updates_cb(): logging.debug('STARTUP: show_software_updates_cb') if os.path.isfile(os.path.expanduser('~/.sugar-update')): + home_window = homewindow.get_instance() home_window.get_home_box().show_software_updates_alert() def setup_notification_service_cb(): @@ -175,14 +176,17 @@ def main(): cleanup_logs() logger.start('shell') - intro.check_profile() - client = gconf.client_get_default() - timezone = client.get_string('/desktop/sugar/date/timezone') + client.set_string('/desktop/gnome/peripherals/mouse/cursor_theme', 'sugar') + client.set_string('/apps/metacity/general/mouse_button_modifier', + 'disabled') + timezone = client.get_string('/desktop/sugar/date/timezone') if timezone is not None and timezone: os.environ['TZ'] = timezone + intro.check_profile() + start_ui_service() start_session_manager() @@ -195,7 +199,7 @@ def main(): launcher.setup() - home_window = HomeWindow() + home_window = homewindow.get_instance() home_window.show() gobject.idle_add(unfreeze_dcon_cb) @@ -204,7 +208,7 @@ def main(): gobject.idle_add(setup_journal_cb) gobject.idle_add(setup_notification_service_cb) gobject.idle_add(setup_file_transfer_cb) - gobject.idle_add(show_software_updates_cb, home_window) + gobject.idle_add(show_software_updates_cb) try: import xklavier diff --git a/bin/sugar.in b/bin/sugar.in index 3c9b9b6..1c29a12 100644 --- a/bin/sugar.in +++ b/bin/sugar.in @@ -6,6 +6,9 @@ fi export GTK2_RC_FILES="@prefix@/share/sugar/data/sugar-$SUGAR_SCALING.gtkrc" +# Needed for executing wpa_passphrase +export PATH="$PATH":/sbin:/usr/sbin + if ! test -f "$GTK2_RC_FILES"; then echo "sugar: ERROR: Gtk theme for scaling $SUGAR_SCALING not available in path $GTK2_RC_FILES" exit 1 @@ -16,7 +19,6 @@ if [ -f ~/.sugar/debug ]; then . ~/.sugar/debug fi -matchbox-window-manager -use_titlebar no -theme sugar \ - -kbdconfig @prefix@/share/sugar/data/kbdconfig & +metacity --no-force-fullscreen & exec sugar-session diff --git a/configure.ac b/configure.ac index 1f95513..c781cc6 100644 --- a/configure.ac +++ b/configure.ac @@ -1,11 +1,11 @@ -AC_INIT([Sugar],[0.84.1],[],[sugar]) +AC_INIT([Sugar],[0.85.3],[],[sugar]) AC_PREREQ([2.59]) AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_SRCDIR([configure.ac]) -SUCROSE_VERSION="0.84.1" +SUCROSE_VERSION="0.85.1" AC_SUBST(SUCROSE_VERSION) AM_INIT_AUTOMAKE([1.9 foreign dist-bzip2 no-dist-gzip]) @@ -62,6 +62,8 @@ extensions/cpsection/keyboard/Makefile extensions/cpsection/language/Makefile extensions/cpsection/network/Makefile extensions/cpsection/power/Makefile +extensions/cpsection/updater/Makefile +extensions/cpsection/updater/backends/Makefile extensions/deviceicon/Makefile extensions/globalkey/Makefile src/Makefile diff --git a/data/sugar.schemas.in b/data/sugar.schemas.in index d84e062..8c43930 100644 --- a/data/sugar.schemas.in +++ b/data/sugar.schemas.in @@ -175,9 +175,8 @@ <type>bool</type> <default>true</default> <locale name="C"> - <!-- FIXME: Enable these after string freeze is over <short>Show Log out</short> - <long>If TRUE, Sugar will show a "Log out" option.</long>--> + <long>If TRUE, Sugar will show a "Log out" option.</long> </locale> </schema> diff --git a/extensions/cpsection/Makefile.am b/extensions/cpsection/Makefile.am index 4bee4ff..dd0a6b8 100644 --- a/extensions/cpsection/Makefile.am +++ b/extensions/cpsection/Makefile.am @@ -1,4 +1,4 @@ -SUBDIRS = aboutme aboutcomputer datetime frame keyboard language network power +SUBDIRS = aboutme aboutcomputer datetime frame keyboard language network power updater sugardir = $(pkgdatadir)/extensions/cpsection sugar_PYTHON = __init__.py diff --git a/extensions/cpsection/aboutcomputer/model.py b/extensions/cpsection/aboutcomputer/model.py index 6cddb4c..898d79c 100644 --- a/extensions/cpsection/aboutcomputer/model.py +++ b/extensions/cpsection/aboutcomputer/model.py @@ -111,7 +111,7 @@ def _read_file(path): value = value.strip('\n') return value else: - _logger.debug('No information in file or directory: %s' % path) + _logger.debug('No information in file or directory: %s', path) return None def get_license(): diff --git a/extensions/cpsection/language/view.py b/extensions/cpsection/language/view.py index 30dc05d..a896aa5 100644 --- a/extensions/cpsection/language/view.py +++ b/extensions/cpsection/language/view.py @@ -1,4 +1,5 @@ # Copyright (C) 2008, OLPC +# 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 @@ -52,6 +53,15 @@ class Language(SectionView): self.set_border_width(style.DEFAULT_SPACING * 2) self.set_spacing(style.DEFAULT_SPACING) + explanation = gettext.gettext("Add languages in the order you prefer." \ + " If a translation is not available,"\ + " the next in the list will be used.") + self._text = gtk.Label(explanation) + self._text.set_line_wrap(True) + self._text.set_alignment(0, 0) + self.pack_start(self._text, False) + self._text.show() + self._table = gtk.Table(rows=1, columns=3, homogeneous=False) self.pack_start(self._table, False) self._table.show() diff --git a/extensions/cpsection/network/view.py b/extensions/cpsection/network/view.py index ef28f00..588daeb 100644 --- a/extensions/cpsection/network/view.py +++ b/extensions/cpsection/network/view.py @@ -49,13 +49,16 @@ class Network(SectionView): self._radio_alert_box = gtk.HBox(spacing=style.DEFAULT_SPACING) self._jabber_alert_box = gtk.HBox(spacing=style.DEFAULT_SPACING) + workspace = gtk.VBox() + workspace.show() + separator_wireless = gtk.HSeparator() - self.pack_start(separator_wireless, expand=False) + workspace.pack_start(separator_wireless, expand=False) separator_wireless.show() label_wireless = gtk.Label(_('Wireless')) label_wireless.set_alignment(0, 0) - self.pack_start(label_wireless, expand=False) + workspace.pack_start(label_wireless, expand=False) label_wireless.show() box_wireless = gtk.VBox() box_wireless.set_border_width(style.DEFAULT_SPACING * 2) @@ -105,16 +108,16 @@ class Network(SectionView): box_wireless.pack_start(box_clear_history, expand=False) box_clear_history.show() - self.pack_start(box_wireless, expand=False) + workspace.pack_start(box_wireless, expand=False) box_wireless.show() separator_mesh = gtk.HSeparator() - self.pack_start(separator_mesh, False) + workspace.pack_start(separator_mesh, False) separator_mesh.show() label_mesh = gtk.Label(_('Collaboration')) label_mesh.set_alignment(0, 0) - self.pack_start(label_mesh, expand=False) + workspace.pack_start(label_mesh, expand=False) label_mesh.show() box_mesh = gtk.VBox() box_mesh.set_border_width(style.DEFAULT_SPACING * 2) @@ -161,9 +164,15 @@ class Network(SectionView): self._jabber_alert.props.msg = self.restart_msg self._jabber_alert.show() - self.pack_start(box_mesh, expand=False) + workspace.pack_start(box_mesh, expand=False) box_mesh.show() + scrolled = gtk.ScrolledWindow() + scrolled.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) + scrolled.add_with_viewport(workspace) + scrolled.show() + self.add(scrolled) + self.setup() def setup(self): diff --git a/extensions/cpsection/updater/Makefile.am b/extensions/cpsection/updater/Makefile.am new file mode 100644 index 0000000..897dbf3 --- /dev/null +++ b/extensions/cpsection/updater/Makefile.am @@ -0,0 +1,8 @@ +SUBDIRS = backends + +sugardir = $(pkgdatadir)/extensions/cpsection/updater + +sugar_PYTHON = \ + __init__.py \ + model.py \ + view.py diff --git a/extensions/cpsection/updater/__init__.py b/extensions/cpsection/updater/__init__.py new file mode 100644 index 0000000..6010615 --- /dev/null +++ b/extensions/cpsection/updater/__init__.py @@ -0,0 +1,22 @@ +# Copyright (C) 2008, OLPC +# +# 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 + +from gettext import gettext as _ + +CLASS = 'ActivityUpdater' +ICON = 'module-updater' +TITLE = _('Software update') +KEYWORDS = ['software', 'activity', 'update'] diff --git a/extensions/cpsection/updater/backends/Makefile.am b/extensions/cpsection/updater/backends/Makefile.am new file mode 100644 index 0000000..e280a07 --- /dev/null +++ b/extensions/cpsection/updater/backends/Makefile.am @@ -0,0 +1,5 @@ +sugardir = $(pkgdatadir)/extensions/cpsection/updater/backends + +sugar_PYTHON = \ + aslo.py \ + __init__.py diff --git a/extensions/cpsection/updater/backends/__init__.py b/extensions/cpsection/updater/backends/__init__.py new file mode 100644 index 0000000..0dd0174 --- /dev/null +++ b/extensions/cpsection/updater/backends/__init__.py @@ -0,0 +1,16 @@ +#!/usr/bin/python +# Copyright (C) 2009, Sugar Labs +# +# 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/extensions/cpsection/updater/backends/aslo.py b/extensions/cpsection/updater/backends/aslo.py new file mode 100644 index 0000000..443fd1c --- /dev/null +++ b/extensions/cpsection/updater/backends/aslo.py @@ -0,0 +1,156 @@ +#!/usr/bin/python +# Copyright (C) 2009, Sugar Labs +# +# 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 + +'''Activity information microformat parser. + +Activity information is embedded in HTML/XHTML/XML pages using a +Resource Description Framework (RDF) http://www.w3.org/RDF/ . + +An example:: + +<?xml version="1.0" encoding="UTF-8"?> +<RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:em="http://www.mozilla.org/2004/em-rdf#"> +<RDF:Description about="urn:mozilla:extension:bounce"> + <em:updates> + <RDF:Seq> + <RDF:li resource="urn:mozilla:extension:bounce:7"/> + </RDF:Seq> + </em:updates> +</RDF:Description> + +<RDF:Description about="urn:mozilla:extension:bounce:7"> + <em:version>7</em:version> + <em:targetApplication> + <RDF:Description> + <em:id>{3ca105e0-2280-4897-99a0-c277d1b733d2}</em:id> + <em:minVersion>0.82</em:minVersion> + <em:maxVersion>0.84</em:maxVersion> + <em:updateLink>http://foo.xo</em:updateLink> + <em:updateSize>7</em:updateSize> + <em:updateHash>sha256:816a7c43b4f1ea4769c61c03ea4..</em:updateHash> + </RDF:Description> + </em:targetApplication> +</RDF:Description></RDF:RDF> +''' + +import logging +from xml.etree.ElementTree import XML +import traceback + +import gio + +from jarabe import config + +_FIND_DESCRIPTION = \ + './/{http://www.w3.org/1999/02/22-rdf-syntax-ns#}Description' +_FIND_VERSION = './/{http://www.mozilla.org/2004/em-rdf#}version' +_FIND_LINK = './/{http://www.mozilla.org/2004/em-rdf#}updateLink' +_FIND_SIZE = './/{http://www.mozilla.org/2004/em-rdf#}updateSize' + +_UPDATE_PATH = 'http://activities.sugarlabs.org/services/update.php' + +_fetcher = None + + +class _UpdateFetcher(object): + + _CHUNK_SIZE = 10240 + + def __init__(self, bundle, completion_cb): + + url = '%s?id=%s&appVersion=%s' % \ + (_UPDATE_PATH, bundle.get_bundle_id(), config.version) + + self._completion_cb = completion_cb + self._file = gio.File(url) + self._stream = None + self._xml_data = '' + self._bundle = bundle + + self._file.read_async(self.__file_read_async_cb) + + def __file_read_async_cb(self, gfile, result): + try: + self._stream = self._file.read_finish(result) + except: + global _fetcher + _fetcher = None + self._completion_cb(None, None, None, None, traceback.format_exc()) + return + + self._stream.read_async(self._CHUNK_SIZE, self.__stream_read_async_cb) + + def __stream_read_async_cb(self, stream, result): + xml_data = self._stream.read_finish(result) + if xml_data is None: + global _fetcher + _fetcher = None + self._completion_cb(self._bundle, None, None, None, + 'Error reading update information for %s from ' + 'server.' % self._bundle.get_bundle_id()) + return + elif not xml_data: + self._process_result() + else: + self._xml_data += xml_data + self._stream.read_async(self._CHUNK_SIZE, + self.__stream_read_async_cb) + + def _process_result(self): + document = XML(self._xml_data) + + if document.find(_FIND_DESCRIPTION) is None: + logging.debug('Bundle %s not available in the server for the ' + 'version %s', self._bundle.get_bundle_id(), config.version) + version = None + link = None + size = None + else: + try: + version = int(document.find(_FIND_VERSION).text) + except ValueError: + logging.error(traceback.format_exc()) + version = 0 + + link = document.find(_FIND_LINK).text + + try: + size = long(document.find(_FIND_SIZE).text) * 1024 + except ValueError: + logging.error(traceback.format_exc()) + size = 0 + + global _fetcher + _fetcher = None + self._completion_cb(self._bundle, version, link, size, None) + + +def fetch_update_info(bundle, completion_cb): + '''Queries the server for a newer version of the ActivityBundle. + + completion_cb receives bundle, version, link, size and possibly an error + message: + + def completion_cb(bundle, version, link, size, error_message): + ''' + global _fetcher + + if _fetcher is not None: + raise RuntimeError('Multiple simultaneous requests are not supported') + + _fetcher = _UpdateFetcher(bundle, completion_cb) diff --git a/extensions/cpsection/updater/model.py b/extensions/cpsection/updater/model.py new file mode 100755 index 0000000..102edea --- /dev/null +++ b/extensions/cpsection/updater/model.py @@ -0,0 +1,292 @@ +# Copyright (C) 2009, Sugar Labs +# 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 +'''Sugar bundle updater: model. + +This module implements the non-GUI portions of the bundle updater, including +list of installed bundls, whether updates are needed, and the URL at which to +find the bundle updated. +''' + +import os +import logging +import tempfile +from urlparse import urlparse +import traceback + +import gobject +import gio + +from sugar import env +from sugar.datastore import datastore +from sugar.bundle.activitybundle import ActivityBundle + +from jarabe.model import bundleregistry + +from backends import aslo + + +class UpdateModel(gobject.GObject): + __gtype_name__ = 'SugarUpdateModel' + + __gsignals__ = { + 'progress': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([int, str, float, int])), + } + + ACTION_CHECKING = 0 + ACTION_UPDATING = 1 + ACTION_DOWNLOADING = 2 + + def __init__(self): + gobject.GObject.__init__(self) + + self.updates = None + self._bundles_to_check = None + self._bundles_to_update = None + self._total_bundles_to_update = 0 + self._downloader = None + + def check_updates(self): + self.updates = [] + self._bundles_to_check = \ + [bundle for bundle in bundleregistry.get_registry()] + self._check_next_update() + + def _check_next_update(self): + total = len(bundleregistry.get_registry()) + current = total - len(self._bundles_to_check) + + bundle = self._bundles_to_check.pop() + self.emit('progress', UpdateModel.ACTION_CHECKING, bundle.get_name(), + current, total) + + aslo.fetch_update_info(bundle, self.__check_completed_cb) + + def __check_completed_cb(self, bundle, version, link, size, error_message): + if error_message is not None: + logging.error('Error getting update information from server:\n' + '%s' % error_message) + + if version is not None and version > bundle.get_activity_version(): + self.updates.append(BundleUpdate(bundle, version, link, size)) + + if self._bundles_to_check: + gobject.idle_add(self._check_next_update) + else: + total = len(bundleregistry.get_registry()) + if bundle is None: + name = '' + else: + name = bundle.get_name() + self.emit('progress', UpdateModel.ACTION_CHECKING, name, total, + total) + + def update(self, bundle_ids): + self._bundles_to_update = [] + for bundle_update in self.updates: + if bundle_update.bundle.get_bundle_id() in bundle_ids: + self._bundles_to_update.append(bundle_update) + + self._total_bundles_to_update = len(self._bundles_to_update) + self._download_next_update() + + def _download_next_update(self): + bundle_update = self._bundles_to_update.pop() + + total = self._total_bundles_to_update * 2 + current = total - len(self._bundles_to_update) * 2 - 2 + + self.emit('progress', UpdateModel.ACTION_DOWNLOADING, + bundle_update.bundle.get_name(), current, total) + + self._downloader = _Downloader(bundle_update) + self._downloader.connect('progress', self.__downloader_progress_cb) + self._downloader.connect('error', self.__downloader_error_cb) + + def __downloader_progress_cb(self, downloader, progress): + logging.debug('__downloader_progress_cb %r', progress) + total = self._total_bundles_to_update * 2 + current = total - len(self._bundles_to_update) * 2 - 2 + progress + + self.emit('progress', UpdateModel.ACTION_DOWNLOADING, + self._downloader.bundle_update.bundle.get_name(), + current, total) + + if progress == 1: + self._install_update(self._downloader.bundle_update, + self._downloader.get_local_file_path()) + self._downloader = None + + def __downloader_error_cb(self, downloader, error_message): + logging.error('Error downloading update:\n%s', error_message) + + total = self._total_bundles_to_update * 2 + current = total - len(self._bundles_to_update) * 2 + self.emit('progress', UpdateModel.ACTION_UPDATING, '', current, total) + + if self._bundles_to_update: + # do it in idle so the UI has a chance to refresh + gobject.idle_add(self._download_next_update) + + def _install_update(self, bundle_update, local_file_path): + + total = self._total_bundles_to_update * 2 + current = total - len(self._bundles_to_update) * 2 - 1 + + self.emit('progress', UpdateModel.ACTION_UPDATING, + bundle_update.bundle.get_name(), + current, total) + + # TODO: Should we first expand the zip async so we can provide progress + # and only then copy to the journal? + jobject = datastore.create() + try: + title = '%s-%s' % (bundle_update.bundle.get_name(), + bundle_update.version) + jobject.metadata['title'] = title + jobject.metadata['mime_type'] = ActivityBundle.MIME_TYPE + jobject.file_path = local_file_path + datastore.write(jobject, transfer_ownership=True) + finally: + jobject.destroy() + + current += 1 + self.emit('progress', UpdateModel.ACTION_UPDATING, + bundle_update.bundle.get_name(), + current, total) + + if self._bundles_to_update: + # do it in idle so the UI has a chance to refresh + gobject.idle_add(self._download_next_update) + + def get_total_bundles_to_update(self): + return self._total_bundles_to_update + + +class BundleUpdate(object): + + def __init__(self, bundle, version, link, size): + self.bundle = bundle + self.version = version + self.link = link + self.size = size + + +class _Downloader(gobject.GObject): + _CHUNK_SIZE = 10240 # 10K + __gsignals__ = { + 'progress': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([float])), + 'error': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([str])), + } + + def __init__(self, bundle_update): + gobject.GObject.__init__(self) + + self.bundle_update = bundle_update + self._input_stream = None + self._output_stream = None + self._pending_buffers = [] + self._input_file = gio.File(bundle_update.link) + self._output_file = None + self._downloaded_size = 0 + + self._input_file.read_async(self.__file_read_async_cb) + + def __file_read_async_cb(self, gfile, result): + try: + self._input_stream = self._input_file.read_finish(result) + except: + self.emit('error', traceback.format_exc()) + return + + temp_file_path = self._get_temp_file_path(self.bundle_update.link) + self._output_file = gio.File(temp_file_path) + self._output_stream = self._output_file.create() + + self._input_stream.read_async(self._CHUNK_SIZE, self.__read_async_cb, + gobject.PRIORITY_LOW) + + def __read_async_cb(self, input_stream, result): + data = input_stream.read_finish(result) + + if data is None: + # TODO + pass + elif not data: + logging.debug('closing input stream') + self._input_stream.close() + self._check_if_finished_writing() + else: + self._pending_buffers.append(data) + self._input_stream.read_async(self._CHUNK_SIZE, + self.__read_async_cb, + gobject.PRIORITY_LOW) + + self._write_next_buffer() + + def __write_async_cb(self, output_stream, result, user_data): + count = output_stream.write_finish(result) + + self._downloaded_size += count + progress = self._downloaded_size / float(self.bundle_update.size) + self.emit('progress', progress) + + self._check_if_finished_writing() + + if self._pending_buffers: + self._write_next_buffer() + + def _write_next_buffer(self): + if self._pending_buffers and not self._output_stream.has_pending(): + data = self._pending_buffers.pop(0) + # TODO: we pass the buffer as user_data because of + # http://bugzilla.gnome.org/show_bug.cgi?id=564102 + self._output_stream.write_async(data, self.__write_async_cb, + gobject.PRIORITY_LOW, + user_data=data) + + def _get_temp_file_path(self, uri): + # TODO: Should we use the HTTP headers for the file name? + scheme_, netloc_, path, params_, query_, fragment_ = \ + urlparse(uri) + path = os.path.basename(path) + + base_name, extension_ = os.path.splitext(path) + fd, file_path = tempfile.mkstemp(dir=env.get_user_activities_path(), + prefix=base_name, suffix='.xo') + os.close(fd) + os.unlink(file_path) + + return file_path + + def get_local_file_path(self): + return self._output_file.get_path() + + def _check_if_finished_writing(self): + if not self._pending_buffers and \ + not self._output_stream.has_pending() and \ + self._input_stream.is_closed(): + + logging.debug('closing output stream') + self._output_stream.close() + + self.emit('progress', 1.0) diff --git a/extensions/cpsection/updater/view.py b/extensions/cpsection/updater/view.py new file mode 100644 index 0000000..9a77743 --- /dev/null +++ b/extensions/cpsection/updater/view.py @@ -0,0 +1,382 @@ +# Copyright (C) 2008, 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 +# 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 + +from gettext import gettext as _ +from gettext import ngettext +import locale + +import gobject +import gtk + +from sugar.graphics import style +from sugar.graphics.icon import Icon, CellRendererIcon + +from jarabe.controlpanel.sectionview import SectionView + +from model import UpdateModel + +_DEBUG_VIEW_ALL = True + + +class ActivityUpdater(SectionView): + + def __init__(self, model, alerts): + SectionView.__init__(self) + + self._model = UpdateModel() + self._model.connect('progress', self.__progress_cb) + + self.set_spacing(style.DEFAULT_SPACING) + self.set_border_width(style.DEFAULT_SPACING * 2) + + self._top_label = gtk.Label() + self._top_label.set_line_wrap(True) + self._top_label.set_justify(gtk.JUSTIFY_LEFT) + self._top_label.props.xalign = 0 + self.pack_start(self._top_label, expand=False) + self._top_label.show() + + separator = gtk.HSeparator() + self.pack_start(separator, expand=False) + separator.show() + + bottom_label = gtk.Label() + bottom_label.set_line_wrap(True) + bottom_label.set_justify(gtk.JUSTIFY_LEFT) + bottom_label.props.xalign = 0 + bottom_label.set_markup( + _('Software updates correct errors, eliminate security ' \ + 'vulnerabilities, and provide new features.')) + self.pack_start(bottom_label, expand=False) + bottom_label.show() + + self._update_box = None + self._progress_pane = None + + self._refresh() + + def _switch_to_update_box(self): + if self._update_box in self.get_children(): + return + + if self._progress_pane in self.get_children(): + self.remove(self._progress_pane) + self._progress_pane = None + + if self._update_box is None: + self._update_box = UpdateBox(self._model) + self._update_box.refresh_button.connect('clicked', + self.__refresh_button_clicked_cb) + self._update_box.install_button.connect('clicked', + self.__install_button_clicked_cb) + + self.pack_start(self._update_box, expand=True, fill=True) + self._update_box.show() + + def _switch_to_progress_pane(self): + if self._progress_pane in self.get_children(): + return + + if self._update_box in self.get_children(): + self.remove(self._update_box) + self._update_box = None + + if self._progress_pane is None: + self._progress_pane = ProgressPane() + + self.pack_start(self._progress_pane, expand=True, fill=False) + self._progress_pane.show() + + def _clear_center(self): + if self._progress_pane in self.get_children(): + self.remove(self._progress_pane) + self._progress_pane = None + + if self._update_box in self.get_children(): + self.remove(self._update_box) + self._update_box = None + + def __progress_cb(self, model, action, bundle_name, current, total): + if current == total and action == UpdateModel.ACTION_CHECKING: + self._finished_checking() + return + elif current == total: + self._finished_updating() + return + + if action == UpdateModel.ACTION_CHECKING: + message = _('Checking %s...') % bundle_name + elif action == UpdateModel.ACTION_DOWNLOADING: + message = _('Downloading %s...') % bundle_name + elif action == UpdateModel.ACTION_UPDATING: + message = _('Updating %s...') % bundle_name + + self._switch_to_progress_pane() + self._progress_pane.set_message(message) + self._progress_pane.set_progress(current / float(total)) + + def _finished_checking(self): + available_updates = len(self._model.updates) + if not available_updates: + top_message = _('Your software is up-to-date') + else: + top_message = ngettext('You can install %s update', + 'You can install %s updates', + available_updates) + top_message = top_message % available_updates + top_message = gobject.markup_escape_text(top_message) + + self._top_label.set_markup('<big>%s</big>' % top_message) + + if not available_updates: + self._clear_center() + else: + self._switch_to_update_box() + self._update_box.refresh() + + def __refresh_button_clicked_cb(self, button): + self._refresh() + + def _refresh(self): + top_message = _('Checking for updates...') + self._top_label.set_markup('<big>%s</big>' % top_message) + self._model.check_updates() + + def __install_button_clicked_cb(self, button): + self._top_label.set_markup('<big>%s</big>' % _('Installing updates...')) + self._model.update(self._update_box.get_bundles_to_update()) + + def _finished_updating(self): + installed_updates = self._model.get_total_bundles_to_update() + top_message = ngettext('%s update was installed', + '%s updates were installed', installed_updates) + top_message = top_message % installed_updates + top_message = gobject.markup_escape_text(top_message) + self._top_label.set_markup('<big>%s</big>' % top_message) + self._clear_center() + + +class ProgressPane(gtk.VBox): + '''Container which replaces the `ActivityPane` during refresh or + install.''' + + def __init__(self): + gtk.VBox.__init__(self) + self.set_spacing(style.DEFAULT_PADDING) + self.set_border_width(style.DEFAULT_SPACING * 2) + + self._progress = gtk.ProgressBar() + self.pack_start(self._progress) + self._progress.show() + + self._label = gtk.Label() + self._label.set_line_wrap(True) + self._label.set_property('xalign', 0.5) + self._label.modify_fg(gtk.STATE_NORMAL, + style.COLOR_BUTTON_GREY.get_gdk_color()) + self.pack_start(self._label) + self._label.show() + + alignment_box = gtk.Alignment(xalign=0.5, yalign=0.5) + self.pack_start(alignment_box) + alignment_box.show() + + cancel_button = gtk.Button(stock=gtk.STOCK_CANCEL) + alignment_box.add(cancel_button) + cancel_button.show() + + def set_message(self, message): + self._label.set_text(message) + + def set_progress(self, fraction): + self._progress.props.fraction = fraction + + +class UpdateBox(gtk.VBox): + + def __init__(self, model): + gtk.VBox.__init__(self) + + self._model = model + + self.set_spacing(style.DEFAULT_PADDING) + + scrolled_window = gtk.ScrolledWindow() + scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + self.pack_start(scrolled_window) + scrolled_window.show() + + self._update_list = UpdateList(model) + self._update_list.props.model.connect('row-changed', + self.__row_changed_cb) + scrolled_window.add(self._update_list) + self._update_list.show() + + bottom_box = gtk.HBox() + bottom_box.set_spacing(style.DEFAULT_SPACING) + self.pack_start(bottom_box, expand=False) + bottom_box.show() + + self._size_label = gtk.Label() + self._size_label.props.xalign = 0 + self._size_label.set_justify(gtk.JUSTIFY_LEFT) + bottom_box.pack_start(self._size_label, expand=True) + self._size_label.show() + + self.refresh_button = gtk.Button(stock=gtk.STOCK_REFRESH) + bottom_box.pack_start(self.refresh_button, expand=False) + self.refresh_button.show() + + self.install_button = gtk.Button(_('Install selected')) + self.install_button.props.image = Icon(icon_name='emblem-downloads', + icon_size=gtk.ICON_SIZE_BUTTON) + bottom_box.pack_start(self.install_button, expand=False) + self.install_button.show() + + self._update_total_size_label() + + def refresh(self): + self._update_list.refresh() + + def __row_changed_cb(self, list_model, path, iterator): + self._update_total_size_label() + self._update_install_button() + + def _update_total_size_label(self): + total_size = 0 + for row in self._update_list.props.model: + if row[UpdateListModel.SELECTED]: + total_size += row[UpdateListModel.SIZE] + + markup = _('Download size: %s') % _format_size(total_size) + self._size_label.set_markup(markup) + + def _update_install_button(self): + for row in self._update_list.props.model: + if row[UpdateListModel.SELECTED]: + self.install_button.props.sensitive = True + return + self.install_button.props.sensitive = False + + def get_bundles_to_update(self): + bundles_to_update = [] + for row in self._update_list.props.model: + if row[UpdateListModel.SELECTED]: + bundles_to_update.append(row[UpdateListModel.BUNDLE_ID]) + return bundles_to_update + + +class UpdateList(gtk.TreeView): + + def __init__(self, model): + list_model = UpdateListModel(model) + gtk.TreeView.__init__(self, list_model) + + self.set_reorderable(False) + self.set_enable_search(False) + self.set_headers_visible(False) + + toggle_renderer = gtk.CellRendererToggle() + toggle_renderer.props.activatable = True + toggle_renderer.props.xpad = style.DEFAULT_PADDING + toggle_renderer.props.indicator_size = style.zoom(26) + toggle_renderer.connect('toggled', self.__toggled_cb) + + toggle_column = gtk.TreeViewColumn() + toggle_column.pack_start(toggle_renderer) + toggle_column.add_attribute(toggle_renderer, 'active', + UpdateListModel.SELECTED) + self.append_column(toggle_column) + + icon_renderer = CellRendererIcon(self) + icon_renderer.props.width = style.STANDARD_ICON_SIZE + icon_renderer.props.height = style.STANDARD_ICON_SIZE + icon_renderer.props.size = style.STANDARD_ICON_SIZE + icon_renderer.props.xpad = style.DEFAULT_PADDING + icon_renderer.props.ypad = style.DEFAULT_PADDING + icon_renderer.props.stroke_color = style.COLOR_TOOLBAR_GREY.get_svg() + icon_renderer.props.fill_color = style.COLOR_TRANSPARENT.get_svg() + + icon_column = gtk.TreeViewColumn() + icon_column.pack_start(icon_renderer) + icon_column.add_attribute(icon_renderer, 'file-name', + UpdateListModel.ICON_FILE_NAME) + self.append_column(icon_column) + + text_renderer = gtk.CellRendererText() + + description_column = gtk.TreeViewColumn() + description_column.pack_start(text_renderer) + description_column.add_attribute(text_renderer, 'markup', + UpdateListModel.DESCRIPTION) + self.append_column(description_column) + + def __toggled_cb(self, cell_renderer, path): + row = self.props.model[path] + row[UpdateListModel.SELECTED] = not row[UpdateListModel.SELECTED] + + def refresh(self): + pass + + +class UpdateListModel(gtk.ListStore): + + BUNDLE_ID = 0 + SELECTED = 1 + ICON_FILE_NAME = 2 + DESCRIPTION = 3 + SIZE = 4 + + def __init__(self, model): + gtk.ListStore.__init__(self, str, bool, str, str, int) + + for bundle_update in model.updates: + row = [None] * 5 + row[self.BUNDLE_ID] = bundle_update.bundle.get_bundle_id() + row[self.SELECTED] = True + row[self.ICON_FILE_NAME] = bundle_update.bundle.get_icon() + + details = _('From version %(current)d to %(new)s (Size: %(size)s)') + details = details % \ + {'current': bundle_update.bundle.get_activity_version(), + 'new': bundle_update.version, + 'size': _format_size(bundle_update.size)} + + row[self.DESCRIPTION] = '<b>%s</b>\n%s' % \ + (bundle_update.bundle.get_name(), details) + + row[self.SIZE] = bundle_update.size + + self.append(row) + + +def _format_size(size): + ''' + Convert a given size in bytes to a nicer better readable unit + ''' + if size == 0: + # TRANS: download size is 0 + return _('None') + elif size < 1024: + # TRANS: download size of very small updates + return _('1 KB') + elif size < 1024 * 1024: + # TRANS: download size of small updates, e.g. '250 KB' + return locale.format(_('%.0f KB'), size / 1024.0) + else: + # TRANS: download size of updates, e.g. '2.3 MB' + return locale.format(_('%.1f MB'), size / 1024.0 / 1024) diff --git a/extensions/deviceicon/battery.py b/extensions/deviceicon/battery.py index 76b1565..edfcce4 100644 --- a/extensions/deviceicon/battery.py +++ b/extensions/deviceicon/battery.py @@ -189,28 +189,28 @@ class DeviceModel(gobject.GObject): try: return self._battery.GetProperty(_LEVEL_PROP) except dbus.DBusException: - logging.error('Cannot access %s' % _LEVEL_PROP) + logging.error('Cannot access %s', _LEVEL_PROP) return 0 def _get_charging(self): try: return self._battery.GetProperty(_CHARGING_PROP) except dbus.DBusException: - logging.error('Cannot access %s' % _CHARGING_PROP) + logging.error('Cannot access %s', _CHARGING_PROP) return False def _get_discharging(self): try: return self._battery.GetProperty(_DISCHARGING_PROP) except dbus.DBusException: - logging.error('Cannot access %s' % _DISCHARGING_PROP) + logging.error('Cannot access %s', _DISCHARGING_PROP) return False def _get_present(self): try: return self._battery.GetProperty(_PRESENT_PROP) except dbus.DBusException: - logging.error('Cannot access %s' % _PRESENT_PROP) + logging.error('Cannot access %s', _PRESENT_PROP) return False def do_get_property(self, pspec): diff --git a/extensions/deviceicon/network.py b/extensions/deviceicon/network.py index 0abf25e..0803936 100644 --- a/extensions/deviceicon/network.py +++ b/extensions/deviceicon/network.py @@ -1,5 +1,6 @@ # # Copyright (C) 2008 One Laptop Per Child +# 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 @@ -17,7 +18,7 @@ from gettext import gettext as _ import logging -import sha +import hashlib import socket import struct @@ -124,6 +125,12 @@ class WirelessPalette(Palette): self._set_channel(frequency) self._set_ip_address(iaddress) self._disconnect_item.show() + + def set_disconnected(self): + self.props.primary_text = '' + self.props.secondary_text = '' + self._disconnect_item.hide() + self.set_content(None) def __disconnect_activate_cb(self, menuitem): self.emit('deactivate-connection') @@ -208,6 +215,7 @@ class WirelessDeviceView(ToolButton): self._device_props = None self._flags = 0 self._name = '' + self._mode = network.NM_802_11_MODE_UNKNOWN self._strength = 0 self._frequency = 0 self._device_state = None @@ -301,8 +309,12 @@ class WirelessDeviceView(ToolButton): self._update_properties(properties) 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: @@ -310,14 +322,21 @@ class WirelessDeviceView(ToolButton): if 'Frequency' in properties: self._frequency = properties['Frequency'] - sh = sha.new() - data = self._name + hex(self._flags) - sh.update(data) - h = hash(sh.digest()) - idx = h % len(xocolor.colors) - - self._color = xocolor.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.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.XoColor('%s,%s' % + (xocolor.colors[index][0], + xocolor.colors[index][1])) self._update() def __get_all_ap_props_reply_cb(self, properties): @@ -362,6 +381,12 @@ class WirelessDeviceView(ToolButton): address = self._device_props.Get(_NM_DEVICE_IFACE, 'Ip4Address') self._palette.set_connected(self._frequency, address) self._icon.props.pulsing = False + else: + self._icon.props.badge_name = None + self._icon.props.pulsing = False + self._icon.props.pulse_color = self._inactive_color + self._icon.props.base_color = self._inactive_color + self._palette.set_disconnected() def _update_color(self): self._icon.props.base_color = self._color @@ -386,7 +411,8 @@ class WirelessDeviceView(ToolButton): def __create_connection_cb(self, palette, data=None): client = gconf.client_get_default() nick = client.get_string('/desktop/sugar/user/nick') - connection_name = _('%s\'s network') % nick + color = client.get_string('/desktop/sugar/user/color') + connection_name = _('%s\'s network %s') % (nick, color) connection = network.find_connection(connection_name) if connection is None: @@ -394,11 +420,9 @@ class WirelessDeviceView(ToolButton): settings.connection.id = 'Auto ' + connection_name settings.connection.uuid = unique_id() settings.connection.type = '802-11-wireless' - settings.connection.mode = 'adhoc' settings.wireless.ssid = dbus.ByteArray(connection_name) - settings.wireless.channel = 'bg' + settings.wireless.band = 'bg' settings.wireless.mode = 'adhoc' - settings.ip4_config = IP4Config() settings.ip4_config.method = 'shared' diff --git a/extensions/deviceicon/volume.py b/extensions/deviceicon/volume.py index 5c4c49e..4ccae7a 100644 --- a/extensions/deviceicon/volume.py +++ b/extensions/deviceicon/volume.py @@ -97,7 +97,7 @@ def _mount(volume, tray): volume.mount(gtk.MountOperation(tray.get_toplevel()), _mount_cb) def _mount_cb(volume, result): - logging.debug('_mount_cb %r %r' % (volume, result)) + logging.debug('_mount_cb %r %r', volume, result) volume.mount_finish(result) def _mount_added_cb(volume_monitor, mount, tray): diff --git a/po/POTFILES.in b/po/POTFILES.in index 2e46597..a93f555 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -20,6 +20,8 @@ extensions/cpsection/network/view.py extensions/cpsection/power/__init__.py extensions/cpsection/power/model.py extensions/cpsection/power/view.py +extensions/cpsection/updater/__init__.py +extensions/cpsection/updater/view.py extensions/deviceicon/battery.py extensions/deviceicon/network.py extensions/deviceicon/speaker.py @@ -30,6 +32,7 @@ src/jarabe/controlpanel/cmd.py src/jarabe/controlpanel/gui.py src/jarabe/controlpanel/sectionview.py src/jarabe/controlpanel/toolbar.py +src/jarabe/desktop/activitieslist.py src/jarabe/desktop/favoriteslayout.py src/jarabe/desktop/favoritesview.py src/jarabe/desktop/homebox.py @@ -42,7 +45,6 @@ src/jarabe/frame/clipboardobject.py src/jarabe/frame/devicestray.py src/jarabe/frame/zoomtoolbar.py src/jarabe/intro/window.py -src/jarabe/journal/collapsedentry.py src/jarabe/journal/detailview.py src/jarabe/journal/expandedentry.py src/jarabe/journal/journalactivity.py @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: sugar\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2009-03-19 00:30-0400\n" -"PO-Revision-Date: 2009-05-01 11:59-0400\n" +"POT-Creation-Date: 2009-08-21 00:33-0400\n" +"PO-Revision-Date: 2009-08-24 16:27-0400\n" "Last-Translator: samy boutayeb <s.boutayeb@free.fr>\n" "Language-Team: French <traduc@traduc.org>\n" "Language: fr\n" @@ -65,50 +65,43 @@ msgstr "Cliquer pour changer de couleur :" msgid "About my Computer" msgstr "Mon ordinateur" -#: ../extensions/cpsection/aboutcomputer/model.py:26 +#: ../extensions/cpsection/aboutcomputer/model.py:28 msgid "Not available" msgstr "Non disponible" -#: ../extensions/cpsection/aboutcomputer/view.py:59 +#: ../extensions/cpsection/aboutcomputer/view.py:60 msgid "Identity" msgstr "Identité" -#: ../extensions/cpsection/aboutcomputer/view.py:68 +#: ../extensions/cpsection/aboutcomputer/view.py:69 msgid "Serial Number:" msgstr "Numéro de série :" -#: ../extensions/cpsection/aboutcomputer/view.py:90 +#: ../extensions/cpsection/aboutcomputer/view.py:91 msgid "Software" msgstr "Logiciel" -#: ../extensions/cpsection/aboutcomputer/view.py:99 +#: ../extensions/cpsection/aboutcomputer/view.py:100 msgid "Build:" msgstr "Version :" -#: ../extensions/cpsection/aboutcomputer/view.py:114 +#: ../extensions/cpsection/aboutcomputer/view.py:115 msgid "Sugar:" msgstr "Sugar :" -#: ../extensions/cpsection/aboutcomputer/view.py:130 +#: ../extensions/cpsection/aboutcomputer/view.py:131 msgid "Firmware:" msgstr "Micrologiciel :" -#: ../extensions/cpsection/aboutcomputer/view.py:145 +#: ../extensions/cpsection/aboutcomputer/view.py:146 msgid "Wireless Firmware:" msgstr "Micrologiciel sans fil :" -#: ../extensions/cpsection/aboutcomputer/view.py:168 +#: ../extensions/cpsection/aboutcomputer/view.py:169 msgid "Copyright and License" msgstr "Copyright et licence" -#: ../extensions/cpsection/aboutcomputer/view.py:176 -msgid "" -"© 2008 One Laptop per Child Association Inc; Red Hat Inc; and Contributors." -msgstr "" -"© 2008 One Laptop per Child Association Inc ; Red Hat Inc ; et " -"contributeurs." - -#: ../extensions/cpsection/aboutcomputer/view.py:183 +#: ../extensions/cpsection/aboutcomputer/view.py:184 msgid "" "Sugar is the graphical user interface that you are looking at. Sugar is free " "software, covered by the GNU General Public License, and you are welcome to " @@ -120,7 +113,7 @@ msgstr "" "License). Vous êtes autorisé à le modifier et/ou à en distribuer des copies " "aux conditions spécifiées." -#: ../extensions/cpsection/aboutcomputer/view.py:195 +#: ../extensions/cpsection/aboutcomputer/view.py:196 msgid "Full license:" msgstr "Licence complète :" @@ -132,7 +125,7 @@ msgstr "Date & heure" msgid "Error timezone does not exist." msgstr "Erreur : le fuseau horaire n'existe pas." -#: ../extensions/cpsection/datetime/view.py:68 ../data/sugar.schemas.in.h:19 +#: ../extensions/cpsection/datetime/view.py:68 ../data/sugar.schemas.in.h:21 msgid "Timezone" msgstr "Fuseau horaire" @@ -171,7 +164,7 @@ msgid "Edge" msgstr "Bord" #: ../extensions/cpsection/language/__init__.py:21 -#: ../extensions/cpsection/language/view.py:32 +#: ../extensions/cpsection/language/view.py:33 msgid "Language" msgstr "Langue" @@ -189,6 +182,14 @@ msgstr "La langue associée au code = %s n'a pas pu être déterminée." msgid "Sorry I do not speak '%s'." msgstr "Désolé je ne parle pas '%s'." +#: ../extensions/cpsection/language/view.py:56 +msgid "" +"Add languages in the order you prefer. If a translation is not available, " +"the next in the list will be used." +msgstr "" +"Ajoutez des langues dans l'ordre souhaité. Si la traduction n'est pas " +"disponible, la suivante dans la liste sera utilisée." + #: ../extensions/cpsection/network/__init__.py:21 #: ../extensions/cpsection/network/view.py:28 msgid "Network" @@ -206,33 +207,33 @@ msgstr "Argument 'radio' spécifié incorrect. Utiliser marche/arrêt." msgid "Error in specified argument use 0/1." msgstr "Argument spécifié incorrect. Utiliser 0/1." -#: ../extensions/cpsection/network/view.py:56 +#: ../extensions/cpsection/network/view.py:59 msgid "Wireless" msgstr "Réseau sans fil" -#: ../extensions/cpsection/network/view.py:64 +#: ../extensions/cpsection/network/view.py:67 msgid "Turn off the wireless radio to save battery life" msgstr "Désactiver la radio sans fil pour prolonger la batterie" -#: ../extensions/cpsection/network/view.py:77 +#: ../extensions/cpsection/network/view.py:80 msgid "Radio" msgstr "Radio" -#: ../extensions/cpsection/network/view.py:93 +#: ../extensions/cpsection/network/view.py:96 msgid "Discard network history if you have trouble connecting to the network" msgstr "" "Ignorer l'historique du réseau si vous avez du mal à vous connecter au " "réseau" -#: ../extensions/cpsection/network/view.py:102 +#: ../extensions/cpsection/network/view.py:105 msgid "Discard network history" msgstr "Ignorer l'historique du réseau" -#: ../extensions/cpsection/network/view.py:115 +#: ../extensions/cpsection/network/view.py:118 msgid "Collaboration" msgstr "Collaboration" -#: ../extensions/cpsection/network/view.py:123 +#: ../extensions/cpsection/network/view.py:126 msgid "" "The server is the equivalent of what room you are in; people on the same " "server will be able to see each other, even when they aren't on the same " @@ -242,7 +243,7 @@ msgstr "" "personnes présentes sur le même serveur pourront se voir même si elles ne se " "trouvent pas sur le même réseau." -#: ../extensions/cpsection/network/view.py:133 +#: ../extensions/cpsection/network/view.py:136 msgid "Server:" msgstr "Serveur :" @@ -298,7 +299,7 @@ msgstr "%(hour)d:%(min).2d restantes" msgid "Charged" msgstr "Charge complète" -#: ../extensions/deviceicon/network.py:40 +#: ../extensions/deviceicon/network.py:44 #, python-format msgid "IP address: %s" msgstr "Adresse IP : %s" @@ -307,50 +308,83 @@ msgstr "Adresse IP : %s" # priority over the normal wireless device. NM doesn't have a "disconnect" # method for a device either (for various reasons) so this doesn't # have a good mapping -#: ../extensions/deviceicon/network.py:104 +#: ../extensions/deviceicon/network.py:110 msgid "Disconnect..." msgstr "Déconnexion..." -#: ../extensions/deviceicon/network.py:109 -#: ../src/jarabe/desktop/meshbox.py:248 +#: ../extensions/deviceicon/network.py:114 +msgid "Create new wireless network" +msgstr "Créer un nouveau réseau sans fil" + +#: ../extensions/deviceicon/network.py:120 +#: ../src/jarabe/desktop/meshbox.py:261 msgid "Connecting..." msgstr "Connexion..." # TODO: show the channel number -#: ../extensions/deviceicon/network.py:113 -#: ../extensions/deviceicon/network.py:166 -#: ../src/jarabe/desktop/meshbox.py:254 +#: ../extensions/deviceicon/network.py:124 +#: ../extensions/deviceicon/network.py:186 +#: ../src/jarabe/desktop/meshbox.py:267 msgid "Connected" msgstr "Connecté" -#: ../extensions/deviceicon/network.py:126 +#: ../extensions/deviceicon/network.py:146 msgid "Channel" msgstr "Canal" -#: ../extensions/deviceicon/network.py:141 +#: ../extensions/deviceicon/network.py:161 msgid "Wired Network" msgstr "Réseau filaire" -#: ../extensions/deviceicon/network.py:169 +#: ../extensions/deviceicon/network.py:189 msgid "Speed" msgstr "Vitesse" +#: ../extensions/deviceicon/network.py:415 +#, python-format +msgid "%s's network %s" +msgstr "Réseau %s %s" + #: ../extensions/deviceicon/speaker.py:59 msgid "My Speakers" msgstr "Haut-parleurs" -#: ../extensions/deviceicon/speaker.py:135 +#: ../extensions/deviceicon/speaker.py:133 msgid "Unmute" msgstr "Activer le son" -#: ../extensions/deviceicon/speaker.py:138 +#: ../extensions/deviceicon/speaker.py:136 msgid "Mute" msgstr "Mettre en sourdine" -#: ../extensions/globalkey/screenshot.py:51 +#: ../extensions/globalkey/screenshot.py:56 +msgid "Mesh" +msgstr "Réseau maillé" + +#: ../extensions/globalkey/screenshot.py:58 +#: ../src/jarabe/frame/zoomtoolbar.py:39 +msgid "Group" +msgstr "Groupe" + +#: ../extensions/globalkey/screenshot.py:60 +#: ../src/jarabe/frame/zoomtoolbar.py:41 +msgid "Home" +msgstr "Accueil" + +#: ../extensions/globalkey/screenshot.py:66 +#: ../src/jarabe/frame/zoomtoolbar.py:43 +msgid "Activity" +msgstr "Activité" + +#: ../extensions/globalkey/screenshot.py:69 msgid "Screenshot" msgstr "Capture d'écran" +#: ../extensions/globalkey/screenshot.py:71 +#, python-format +msgid "Screenshot of \"%s\"" +msgstr "Capture d'écran de \"%s\"" + #: ../data/sugar.schemas.in.h:1 msgid "Backup URL" msgstr "Sauvegarde de l'URL" @@ -394,78 +428,86 @@ msgid "" "If TRUE, Sugar will make us searchable for the other users of the Jabber " "server." msgstr "" -"Si VRAI, Sugar les autres utilisateurs du serveur Jabber pourront nous " +"Si VRAI, Sugar permettra aux autres utilisateurs du serveur Jabber de nous " "retrouver." #: ../data/sugar.schemas.in.h:10 +msgid "If TRUE, Sugar will show a \"Log out\" option." +msgstr "Si VRAI, Sugar affichera une option \"Déconnexion\"." + +#: ../data/sugar.schemas.in.h:11 msgid "Jabber Server" msgstr "Serveur Jabber" -#: ../data/sugar.schemas.in.h:11 +#: ../data/sugar.schemas.in.h:12 msgid "Layout of the favorites view." msgstr "Disposition de la vue favorite." -#: ../data/sugar.schemas.in.h:12 +#: ../data/sugar.schemas.in.h:13 msgid "Power Automatic" msgstr "Alimentation automatique" -#: ../data/sugar.schemas.in.h:13 +#: ../data/sugar.schemas.in.h:14 msgid "Power Automatic." msgstr "Alimentation automatique." -#: ../data/sugar.schemas.in.h:14 +#: ../data/sugar.schemas.in.h:15 msgid "Power Extreme" msgstr "Alimentation extrême" -#: ../data/sugar.schemas.in.h:15 +#: ../data/sugar.schemas.in.h:16 msgid "Power Extreme." msgstr "Alimentation extrême." -#: ../data/sugar.schemas.in.h:16 +#: ../data/sugar.schemas.in.h:17 msgid "Publish to Gadget" msgstr "Publication vers Gadget" -#: ../data/sugar.schemas.in.h:17 +#: ../data/sugar.schemas.in.h:18 msgid "Setting for muting the sound device." msgstr "Configuration de la mise en sourdine du périphérique audio." -#: ../data/sugar.schemas.in.h:18 +#: ../data/sugar.schemas.in.h:19 +msgid "Show Log out" +msgstr "Afficher Déconnexion" + +#: ../data/sugar.schemas.in.h:20 msgid "Sound Muted" msgstr "Audio désactivé" -#: ../data/sugar.schemas.in.h:20 +#: ../data/sugar.schemas.in.h:22 msgid "Timezone setting for the system." msgstr "Configuration du fuseau horaire du système." -#: ../data/sugar.schemas.in.h:21 +#: ../data/sugar.schemas.in.h:23 msgid "Url of the jabber server to use." msgstr "URL du serveur Jabber à utiliser." -#: ../data/sugar.schemas.in.h:22 +#: ../data/sugar.schemas.in.h:24 msgid "Url where the backup is saved to." msgstr "URL d'enregistrement de la sauvegarde." -#: ../data/sugar.schemas.in.h:23 +#: ../data/sugar.schemas.in.h:25 msgid "User Color" msgstr "Couleurs de l'utilisateur" -#: ../data/sugar.schemas.in.h:24 +#: ../data/sugar.schemas.in.h:26 msgid "User Name" msgstr "Nom de l'utilisateur" -#: ../data/sugar.schemas.in.h:25 +#: ../data/sugar.schemas.in.h:27 msgid "User name that is used throughout the desktop." msgstr "Nom identifiant l'utilisateur sur le bureau." -#: ../data/sugar.schemas.in.h:26 +#: ../data/sugar.schemas.in.h:28 msgid "Volume Level" msgstr "Niveau de volume" -#: ../data/sugar.schemas.in.h:27 +#: ../data/sugar.schemas.in.h:29 msgid "Volume level for the sound device." msgstr "Niveau de volume du périphérique audio." -#: ../data/sugar.schemas.in.h:28 +#: ../data/sugar.schemas.in.h:30 msgid "" "When in resume mode, clicking on a favorite icon will cause the last entry " "for that activity to be resumed." @@ -536,7 +578,7 @@ msgstr "Relancer pour valider" msgid "Cancel changes" msgstr "Abandonner" -#: ../src/jarabe/controlpanel/gui.py:284 ../src/jarabe/desktop/homebox.py:113 +#: ../src/jarabe/controlpanel/gui.py:284 ../src/jarabe/desktop/homebox.py:70 msgid "Later" msgstr "Plus tard" @@ -549,18 +591,72 @@ msgid "Done" msgstr "Accepter" #: ../src/jarabe/controlpanel/toolbar.py:115 -#: ../src/jarabe/desktop/homebox.py:111 +#: ../src/jarabe/desktop/homebox.py:68 #: ../src/jarabe/frame/activitiestray.py:726 -#: ../src/jarabe/frame/activitiestray.py:821 -#: ../src/jarabe/frame/activitiestray.py:849 +#: ../src/jarabe/frame/activitiestray.py:822 +#: ../src/jarabe/frame/activitiestray.py:850 msgid "Cancel" msgstr "Annuler" #: ../src/jarabe/controlpanel/toolbar.py:121 -#: ../src/jarabe/desktop/favoritesview.py:339 +#: ../src/jarabe/desktop/favoritesview.py:330 msgid "Ok" msgstr "Ok" +#: ../src/jarabe/desktop/activitieslist.py:80 +#: ../src/jarabe/journal/listview.py:147 +msgid "Title" +msgstr "Titre" + +#: ../src/jarabe/desktop/activitieslist.py:91 +msgid "Version" +msgstr "Version" + +#: ../src/jarabe/desktop/activitieslist.py:105 +#: ../src/jarabe/journal/listview.py:178 +msgid "Date" +msgstr "Date" + +#: ../src/jarabe/desktop/activitieslist.py:234 +#, python-format +msgid "Version %s" +msgstr "Version %s" + +#: ../src/jarabe/desktop/activitieslist.py:355 +msgid "Confirm erase" +msgstr "Confirmer la suppression" + +# Conformer la suppression : faut-il supprimer %s définitivement ? +#: ../src/jarabe/desktop/activitieslist.py:357 +#, python-format +msgid "Confirm erase: Do you want to permanently erase %s?" +msgstr "Confirmer la suppression : faut-il supprimer %s définitivement ?" + +# self._stop_item = MenuItem(_('Stop download'), 'stock-close') +# TODO: Implement stopping downloads +# self._stop_item.connect('activate', self._stop_item_activate_cb) +# self.append_menu_item(self._stop_item) +#: ../src/jarabe/desktop/activitieslist.py:361 +#: ../src/jarabe/frame/clipboardmenu.py:62 +#: ../src/jarabe/view/viewsource.py:218 +msgid "Keep" +msgstr "Conserver" + +#: ../src/jarabe/desktop/activitieslist.py:364 +#: ../src/jarabe/desktop/activitieslist.py:406 +#: ../src/jarabe/journal/journaltoolbox.py:360 +#: ../src/jarabe/journal/palettes.py:112 +msgid "Erase" +msgstr "Supprimer" + +#: ../src/jarabe/desktop/activitieslist.py:427 +msgid "Remove favorite" +msgstr "Retirer le favori" + +#: ../src/jarabe/desktop/activitieslist.py:431 +msgid "Make favorite" +msgstr "Ajouter aux favoris" + # TRANS: label for the freeform layout in the favorites view #. TRANS: label for the freeform layout in the favorites view #: ../src/jarabe/desktop/favoriteslayout.py:116 @@ -591,77 +687,52 @@ msgstr "Boîte" msgid "Triangle" msgstr "Triangle" -#: ../src/jarabe/desktop/favoritesview.py:330 +#: ../src/jarabe/desktop/favoritesview.py:321 msgid "Registration Failed" msgstr "Echec de l'enregistrement" -#: ../src/jarabe/desktop/favoritesview.py:331 +#: ../src/jarabe/desktop/favoritesview.py:322 #, python-format msgid "%s" msgstr "%s" -#: ../src/jarabe/desktop/favoritesview.py:333 +#: ../src/jarabe/desktop/favoritesview.py:324 msgid "Registration Successful" msgstr "Enregistrement réussi" -#: ../src/jarabe/desktop/favoritesview.py:334 +#: ../src/jarabe/desktop/favoritesview.py:325 msgid "You are now registered with your school server." msgstr "Vous êtes maintenant enregistré sur le serveur de l'école" -#: ../src/jarabe/desktop/favoritesview.py:674 +#: ../src/jarabe/desktop/favoritesview.py:658 msgid "Register" msgstr "S'enregistrer" -#: ../src/jarabe/desktop/homebox.py:67 -msgid "Confirm erase" -msgstr "Confirmer la suppression" - -# Conformer la suppression : faut-il supprimer %s définitivement ? -#: ../src/jarabe/desktop/homebox.py:69 -#, python-format -msgid "Confirm erase: Do you want to permanently erase %s?" -msgstr "Confirmer la suppression : faut-il supprimer %s définitivement ?" - -# self._stop_item = MenuItem(_('Stop download'), 'stock-close') -# TODO: Implement stopping downloads -# self._stop_item.connect('activate', self._stop_item_activate_cb) -# self.append_menu_item(self._stop_item) -#: ../src/jarabe/desktop/homebox.py:73 ../src/jarabe/frame/clipboardmenu.py:62 -#: ../src/jarabe/view/viewsource.py:218 -msgid "Keep" -msgstr "Conserver" - -#: ../src/jarabe/desktop/homebox.py:76 -#: ../src/jarabe/journal/journaltoolbox.py:357 -#: ../src/jarabe/journal/palettes.py:112 ../src/jarabe/view/palettes.py:153 -msgid "Erase" -msgstr "Supprimer" - -#: ../src/jarabe/desktop/homebox.py:106 +#: ../src/jarabe/desktop/homebox.py:63 msgid "Software Update" msgstr "Mise à jour logicielle" -#: ../src/jarabe/desktop/homebox.py:107 +#: ../src/jarabe/desktop/homebox.py:64 msgid "Update your activities to ensure compatibility with your new software" msgstr "Actualiser les activités pour assurer la compatibilité logicielle" -#: ../src/jarabe/desktop/homebox.py:116 +#: ../src/jarabe/desktop/homebox.py:73 msgid "Check now" msgstr "Vérifier maintenant" -#: ../src/jarabe/desktop/homebox.py:233 +#: ../src/jarabe/desktop/homebox.py:190 msgid "List view" msgstr "Écran liste" -#: ../src/jarabe/desktop/homebox.py:234 +#: ../src/jarabe/desktop/homebox.py:191 msgid "<Ctrl>2" msgstr "<Ctrl>2" -#: ../src/jarabe/desktop/homebox.py:296 +#: ../src/jarabe/desktop/homebox.py:253 msgid "Favorites view" msgstr "Écran favoris" -#: ../src/jarabe/desktop/homebox.py:297 +#: ../src/jarabe/desktop/homebox.py:254 msgid "<Ctrl>1" msgstr "<Ctrl>1" @@ -681,24 +752,24 @@ msgstr "WPA & WPA2 Personal" msgid "Wireless Security:" msgstr "Sécurité sans fil :" -#: ../src/jarabe/desktop/meshbox.py:132 +#: ../src/jarabe/desktop/meshbox.py:136 msgid "Connect" msgstr "Connecter" -#: ../src/jarabe/desktop/meshbox.py:136 +#: ../src/jarabe/desktop/meshbox.py:140 msgid "Disconnect" msgstr "Déconnecter" # TRANS: Action label for resuming an activity. #. TRANS: Action label for resuming an activity. -#: ../src/jarabe/desktop/meshbox.py:442 +#: ../src/jarabe/desktop/meshbox.py:463 #: ../src/jarabe/frame/activitiestray.py:761 -#: ../src/jarabe/journal/journaltoolbox.py:425 -#: ../src/jarabe/journal/palettes.py:72 ../src/jarabe/view/palettes.py:66 +#: ../src/jarabe/journal/journaltoolbox.py:428 +#: ../src/jarabe/journal/palettes.py:72 ../src/jarabe/view/palettes.py:64 msgid "Resume" msgstr "Reprendre" -#: ../src/jarabe/desktop/meshbox.py:447 +#: ../src/jarabe/desktop/meshbox.py:468 #: ../src/jarabe/frame/activitiestray.py:235 msgid "Join" msgstr "Rejoindre" @@ -750,13 +821,13 @@ msgid "Accept" msgstr "Accepter" #: ../src/jarabe/frame/activitiestray.py:716 -#: ../src/jarabe/frame/activitiestray.py:839 +#: ../src/jarabe/frame/activitiestray.py:840 #, python-format msgid "%s (%s)" msgstr "%s (%s)" #: ../src/jarabe/frame/activitiestray.py:750 -#: ../src/jarabe/frame/activitiestray.py:873 +#: ../src/jarabe/frame/activitiestray.py:875 msgid "Dismiss" msgstr "Refuser" @@ -783,21 +854,25 @@ msgstr "Ouvrir avec" msgid "%s clipping" msgstr "%s coupure" -#: ../src/jarabe/frame/zoomtoolbar.py:36 +#: ../src/jarabe/frame/zoomtoolbar.py:37 msgid "Neighborhood" msgstr "Voisinage" -#: ../src/jarabe/frame/zoomtoolbar.py:38 -msgid "Group" -msgstr "Groupe" +#: ../src/jarabe/frame/zoomtoolbar.py:37 +msgid "F1" +msgstr "F1" -#: ../src/jarabe/frame/zoomtoolbar.py:40 -msgid "Home" -msgstr "Accueil" +#: ../src/jarabe/frame/zoomtoolbar.py:39 +msgid "F2" +msgstr "F2" -#: ../src/jarabe/frame/zoomtoolbar.py:42 -msgid "Activity" -msgstr "Activité" +#: ../src/jarabe/frame/zoomtoolbar.py:41 +msgid "F3" +msgstr "F3" + +#: ../src/jarabe/frame/zoomtoolbar.py:43 +msgid "F4" +msgstr "F4" #: ../src/jarabe/intro/window.py:124 msgid "Click to change color:" @@ -811,7 +886,6 @@ msgstr "Précédent" msgid "Next" msgstr "Suivant" -#: ../src/jarabe/journal/collapsedentry.py:258 #: ../src/jarabe/journal/expandedentry.py:159 #: ../src/jarabe/journal/palettes.py:66 msgid "Untitled" @@ -838,84 +912,83 @@ msgstr "Étiquettes :" msgid "Journal" msgstr "Journal" -#: ../src/jarabe/journal/journaltoolbox.py:65 +#: ../src/jarabe/journal/journaltoolbox.py:67 msgid "Search" msgstr "Rechercher" -#: ../src/jarabe/journal/journaltoolbox.py:124 +#: ../src/jarabe/journal/journaltoolbox.py:126 msgid "Anytime" msgstr "N'importe quand" -#: ../src/jarabe/journal/journaltoolbox.py:126 +#: ../src/jarabe/journal/journaltoolbox.py:128 msgid "Today" msgstr "Aujourd'hui" -#: ../src/jarabe/journal/journaltoolbox.py:128 +#: ../src/jarabe/journal/journaltoolbox.py:130 msgid "Since yesterday" msgstr "Depuis hier" # TRANS: Filter entries modified during the last 7 days. #. TRANS: Filter entries modified during the last 7 days. -#: ../src/jarabe/journal/journaltoolbox.py:130 +#: ../src/jarabe/journal/journaltoolbox.py:132 msgid "Past week" msgstr "Depuis une semaine" # TRANS: Filter entries modified during the last 30 days. #. TRANS: Filter entries modified during the last 30 days. -#: ../src/jarabe/journal/journaltoolbox.py:132 +#: ../src/jarabe/journal/journaltoolbox.py:134 msgid "Past month" msgstr "Depuis un mois" # TRANS: Filter entries modified during the last 356 days. #. TRANS: Filter entries modified during the last 356 days. -#: ../src/jarabe/journal/journaltoolbox.py:134 +#: ../src/jarabe/journal/journaltoolbox.py:136 msgid "Past year" msgstr "Depuis une année" -#: ../src/jarabe/journal/journaltoolbox.py:141 +#: ../src/jarabe/journal/journaltoolbox.py:143 msgid "Anyone" msgstr "Tout le monde" -#: ../src/jarabe/journal/journaltoolbox.py:143 +#: ../src/jarabe/journal/journaltoolbox.py:145 msgid "My friends" msgstr "Mes amis" -#: ../src/jarabe/journal/journaltoolbox.py:144 +#: ../src/jarabe/journal/journaltoolbox.py:146 msgid "My class" msgstr "Ma classe" # TRANS: Item in a combo box that filters by entry type. -#. TRANS: Item in a combo box that filters by entry type. -#: ../src/jarabe/journal/journaltoolbox.py:271 +#: ../src/jarabe/journal/journaltoolbox.py:274 msgid "Anything" msgstr "Tout" # TODO: Add "Start with" menu item -#: ../src/jarabe/journal/journaltoolbox.py:347 +#: ../src/jarabe/journal/journaltoolbox.py:350 #: ../src/jarabe/journal/palettes.py:90 msgid "Copy" msgstr "Copier" # TRANS: Action label for starting an entry. #. TRANS: Action label for starting an entry. -#: ../src/jarabe/journal/journaltoolbox.py:428 -#: ../src/jarabe/journal/palettes.py:75 ../src/jarabe/view/palettes.py:135 +#: ../src/jarabe/journal/journaltoolbox.py:431 +#: ../src/jarabe/journal/palettes.py:75 msgid "Start" msgstr "Lancer" -#: ../src/jarabe/journal/listview.py:40 +#: ../src/jarabe/journal/listview.py:361 msgid "Your Journal is empty" msgstr "Le journal est vide" -#: ../src/jarabe/journal/listview.py:41 -msgid "No matching entries " +#: ../src/jarabe/journal/listview.py:363 +msgid "No matching entries" msgstr "Aucune entrée correspondante" -#: ../src/jarabe/journal/listview.py:369 +#: ../src/jarabe/journal/listview.py:374 msgid "Clear search" msgstr "Effacer la recherche" -#: ../src/jarabe/journal/misc.py:93 +#: ../src/jarabe/journal/misc.py:92 msgid "No date" msgstr "Sans date" @@ -933,11 +1006,11 @@ msgstr "" msgid "Show Journal" msgstr "Montre le Journal" -#: ../src/jarabe/journal/objectchooser.py:147 +#: ../src/jarabe/journal/objectchooser.py:146 msgid "Choose an object" msgstr "Choisir un objet" -#: ../src/jarabe/journal/objectchooser.py:152 +#: ../src/jarabe/journal/objectchooser.py:151 #: ../src/jarabe/view/viewsource.py:308 msgid "Close" msgstr "Fermer" @@ -1003,37 +1076,33 @@ msgstr "Éteindre" msgid "Invite to %s" msgstr "Inviter à %s" -#: ../src/jarabe/view/palettes.py:47 +#: ../src/jarabe/view/palettes.py:45 msgid "Starting..." msgstr "Démarrage..." #. TODO: share-with, keep -#: ../src/jarabe/view/palettes.py:73 +#: ../src/jarabe/view/palettes.py:71 msgid "View Source" msgstr "Afficher la source" -#: ../src/jarabe/view/palettes.py:84 +#: ../src/jarabe/view/palettes.py:82 msgid "Stop" msgstr "Arrêter" -#: ../src/jarabe/view/palettes.py:171 -msgid "Remove favorite" -msgstr "Retirer le favori" - -#: ../src/jarabe/view/palettes.py:175 -msgid "Make favorite" -msgstr "Ajouter aux favoris" +#: ../src/jarabe/view/palettes.py:120 +msgid "Start new" +msgstr "Commencer un nouveau" -#: ../src/jarabe/view/palettes.py:238 +#: ../src/jarabe/view/palettes.py:169 msgid "Show contents" msgstr "Afficher les contenus" -#: ../src/jarabe/view/palettes.py:260 ../src/jarabe/view/palettes.py:309 +#: ../src/jarabe/view/palettes.py:191 ../src/jarabe/view/palettes.py:241 #, python-format msgid "%(free_space)d MB Free" msgstr "%(free_space)d Mo de libre" -#: ../src/jarabe/view/palettes.py:285 +#: ../src/jarabe/view/palettes.py:216 msgid "Unmount" msgstr "Démonter" @@ -1054,6 +1123,12 @@ msgstr "Source du paquet activité" msgid "View source: %r" msgstr "Afficher le code source : %r" +#~ msgid "" +#~ "© 2008 One Laptop per Child Association Inc; Red Hat Inc; and Contributors." +#~ msgstr "" +#~ "© 2008 One Laptop per Child Association Inc ; Red Hat Inc ; et " +#~ "contributeurs." + #~ msgid "Document" #~ msgstr "Document" @@ -1079,9 +1154,6 @@ msgstr "Afficher le code source : %r" #~ msgid "About my XO" #~ msgstr "Mon XO" -#~ msgid "Mesh" -#~ msgstr "Réseau maillé" - #~ msgid "Connected to a School Mesh Portal" #~ msgstr "Connecté au portail du réseau maillé d'école" @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2009-03-19 00:30-0400\n" -"PO-Revision-Date: 2009-04-30 09:08-0400\n" +"POT-Creation-Date: 2009-06-11 00:31-0400\n" +"PO-Revision-Date: 2009-07-07 12:04-0400\n" "Last-Translator: Carlo Falciola <cfalciola@yahoo.it>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "Language: it\n" @@ -65,49 +65,43 @@ msgstr "Seleziona per cambiare il tuo colore:" msgid "About my Computer" msgstr "Il mio Computer" -#: ../extensions/cpsection/aboutcomputer/model.py:26 +#: ../extensions/cpsection/aboutcomputer/model.py:28 msgid "Not available" msgstr "Non disponibile" -#: ../extensions/cpsection/aboutcomputer/view.py:59 +#: ../extensions/cpsection/aboutcomputer/view.py:60 msgid "Identity" msgstr "Identità " -#: ../extensions/cpsection/aboutcomputer/view.py:68 +#: ../extensions/cpsection/aboutcomputer/view.py:69 msgid "Serial Number:" msgstr "Numero di Serie:" -#: ../extensions/cpsection/aboutcomputer/view.py:90 +#: ../extensions/cpsection/aboutcomputer/view.py:91 msgid "Software" msgstr "Software" -#: ../extensions/cpsection/aboutcomputer/view.py:99 +#: ../extensions/cpsection/aboutcomputer/view.py:100 msgid "Build:" msgstr "Build:" -#: ../extensions/cpsection/aboutcomputer/view.py:114 +#: ../extensions/cpsection/aboutcomputer/view.py:115 msgid "Sugar:" msgstr "Sugar:" -#: ../extensions/cpsection/aboutcomputer/view.py:130 +#: ../extensions/cpsection/aboutcomputer/view.py:131 msgid "Firmware:" msgstr "Firmware:" -#: ../extensions/cpsection/aboutcomputer/view.py:145 +#: ../extensions/cpsection/aboutcomputer/view.py:146 msgid "Wireless Firmware:" msgstr "Wireless Firmware:" -#: ../extensions/cpsection/aboutcomputer/view.py:168 +#: ../extensions/cpsection/aboutcomputer/view.py:169 msgid "Copyright and License" msgstr "Copyright e Licenza" -#: ../extensions/cpsection/aboutcomputer/view.py:176 -msgid "" -"© 2008 One Laptop per Child Association Inc; Red Hat Inc; and Contributors." -msgstr "" -"© 2008 One Laptop per Child Association Inc; Red Hat Inc; e Contributori." - -#: ../extensions/cpsection/aboutcomputer/view.py:183 +#: ../extensions/cpsection/aboutcomputer/view.py:184 msgid "" "Sugar is the graphical user interface that you are looking at. Sugar is free " "software, covered by the GNU General Public License, and you are welcome to " @@ -119,7 +113,7 @@ msgstr "" "chiunque è il benvenuto per apportare modifiche e migliorie e/o distribuirne " "copie, alle condizioni descritte nella licenza medesima." -#: ../extensions/cpsection/aboutcomputer/view.py:195 +#: ../extensions/cpsection/aboutcomputer/view.py:196 msgid "Full license:" msgstr "Testo della Licenza:" @@ -300,7 +294,7 @@ msgstr "%(hour)d:%(min).2d rimanenti" msgid "Charged" msgstr "Carica" -#: ../extensions/deviceicon/network.py:40 +#: ../extensions/deviceicon/network.py:43 #, python-format msgid "IP address: %s" msgstr "Indirizzo IP: %s" @@ -309,50 +303,83 @@ msgstr "Indirizzo IP: %s" # priority over the normal wireless device. NM doesn't have a "disconnect" # method for a device either (for various reasons) so this doesn't # have a good mapping -#: ../extensions/deviceicon/network.py:104 +#: ../extensions/deviceicon/network.py:109 msgid "Disconnect..." msgstr "Disconnessione..." -#: ../extensions/deviceicon/network.py:109 -#: ../src/jarabe/desktop/meshbox.py:248 +#: ../extensions/deviceicon/network.py:113 +msgid "Create new wireless network" +msgstr "Crea una nuova rete wireless" + +#: ../extensions/deviceicon/network.py:119 +#: ../src/jarabe/desktop/meshbox.py:250 msgid "Connecting..." msgstr "Connessione..." # TODO: show the channel number -#: ../extensions/deviceicon/network.py:113 -#: ../extensions/deviceicon/network.py:166 -#: ../src/jarabe/desktop/meshbox.py:254 +#: ../extensions/deviceicon/network.py:123 +#: ../extensions/deviceicon/network.py:179 +#: ../src/jarabe/desktop/meshbox.py:256 msgid "Connected" msgstr "Connesso" -#: ../extensions/deviceicon/network.py:126 +#: ../extensions/deviceicon/network.py:139 msgid "Channel" msgstr "Canale" -#: ../extensions/deviceicon/network.py:141 +#: ../extensions/deviceicon/network.py:154 msgid "Wired Network" msgstr "Rete su cavo" -#: ../extensions/deviceicon/network.py:169 +#: ../extensions/deviceicon/network.py:182 msgid "Speed" msgstr "Velocità " +#: ../extensions/deviceicon/network.py:389 +#, python-format +msgid "%s's network" +msgstr "rete di %s" + #: ../extensions/deviceicon/speaker.py:59 msgid "My Speakers" msgstr "I miei Altoparlanti" -#: ../extensions/deviceicon/speaker.py:135 +#: ../extensions/deviceicon/speaker.py:133 msgid "Unmute" msgstr "Attiva" -#: ../extensions/deviceicon/speaker.py:138 +#: ../extensions/deviceicon/speaker.py:136 msgid "Mute" msgstr "Silenzia" -#: ../extensions/globalkey/screenshot.py:51 +#: ../extensions/globalkey/screenshot.py:56 +msgid "Mesh" +msgstr "Mesh" + +#: ../extensions/globalkey/screenshot.py:58 +#: ../src/jarabe/frame/zoomtoolbar.py:38 +msgid "Group" +msgstr "Gruppo" + +#: ../extensions/globalkey/screenshot.py:60 +#: ../src/jarabe/frame/zoomtoolbar.py:40 +msgid "Home" +msgstr "Casa" + +#: ../extensions/globalkey/screenshot.py:66 +#: ../src/jarabe/frame/zoomtoolbar.py:42 +msgid "Activity" +msgstr "Attività " + +#: ../extensions/globalkey/screenshot.py:69 msgid "Screenshot" msgstr "Schermata" +#: ../extensions/globalkey/screenshot.py:71 +#, python-format +msgid "Screenshot of \"%s\"" +msgstr "Schermata di \"%s\"" + #: ../data/sugar.schemas.in.h:1 msgid "Backup URL" msgstr "URL di salvataggio" @@ -688,24 +715,24 @@ msgstr "WPA & WPA2 Personal" msgid "Wireless Security:" msgstr "Wireless Security:" -#: ../src/jarabe/desktop/meshbox.py:132 +#: ../src/jarabe/desktop/meshbox.py:134 msgid "Connect" msgstr "Connetti" -#: ../src/jarabe/desktop/meshbox.py:136 +#: ../src/jarabe/desktop/meshbox.py:138 msgid "Disconnect" msgstr "Disconnetti" # TRANS: Action label for resuming an activity. #. TRANS: Action label for resuming an activity. -#: ../src/jarabe/desktop/meshbox.py:442 +#: ../src/jarabe/desktop/meshbox.py:449 #: ../src/jarabe/frame/activitiestray.py:761 #: ../src/jarabe/journal/journaltoolbox.py:425 #: ../src/jarabe/journal/palettes.py:72 ../src/jarabe/view/palettes.py:66 msgid "Resume" msgstr "Riprendi" -#: ../src/jarabe/desktop/meshbox.py:447 +#: ../src/jarabe/desktop/meshbox.py:454 #: ../src/jarabe/frame/activitiestray.py:235 msgid "Join" msgstr "Associa" @@ -795,18 +822,6 @@ msgstr "ritaglio %s" msgid "Neighborhood" msgstr "I miei vicini" -#: ../src/jarabe/frame/zoomtoolbar.py:38 -msgid "Group" -msgstr "Gruppo" - -#: ../src/jarabe/frame/zoomtoolbar.py:40 -msgid "Home" -msgstr "Casa" - -#: ../src/jarabe/frame/zoomtoolbar.py:42 -msgid "Activity" -msgstr "Attività " - #: ../src/jarabe/intro/window.py:124 msgid "Click to change color:" msgstr "Seleziona per cambiare colore:" @@ -919,7 +934,7 @@ msgstr "Il tuo Diario è vuoto" msgid "No matching entries " msgstr "Non ci sono dati corrispondenti " -#: ../src/jarabe/journal/listview.py:369 +#: ../src/jarabe/journal/listview.py:370 msgid "Clear search" msgstr "Annulla ricerca" @@ -1024,24 +1039,24 @@ msgstr "Visualizza Sorgente" msgid "Stop" msgstr "Chiudi" -#: ../src/jarabe/view/palettes.py:171 +#: ../src/jarabe/view/palettes.py:174 msgid "Remove favorite" msgstr "Rimuovi preferito" -#: ../src/jarabe/view/palettes.py:175 +#: ../src/jarabe/view/palettes.py:178 msgid "Make favorite" msgstr "Definisci preferito" -#: ../src/jarabe/view/palettes.py:238 +#: ../src/jarabe/view/palettes.py:241 msgid "Show contents" msgstr "Mostra i contenuti" -#: ../src/jarabe/view/palettes.py:260 ../src/jarabe/view/palettes.py:309 +#: ../src/jarabe/view/palettes.py:263 ../src/jarabe/view/palettes.py:313 #, python-format msgid "%(free_space)d MB Free" msgstr "%(free_space)d MB Liberi" -#: ../src/jarabe/view/palettes.py:285 +#: ../src/jarabe/view/palettes.py:288 msgid "Unmount" msgstr "Rimuovi" @@ -1062,6 +1077,11 @@ msgstr "Sorgente della Attività " msgid "View source: %r" msgstr "Vedi codice sorgente: %r" +#~ msgid "" +#~ "© 2008 One Laptop per Child Association Inc; Red Hat Inc; and Contributors." +#~ msgstr "" +#~ "© 2008 One Laptop per Child Association Inc; Red Hat Inc; e Contributori." + #~ msgid "Document" #~ msgstr "Documento" @@ -1088,9 +1108,6 @@ msgstr "Vedi codice sorgente: %r" #~ msgid "About my XO" #~ msgstr "Informazioni sul mio XO" -#~ msgid "Mesh" -#~ msgstr "Mesh" - #~ msgid "Connected to a School Mesh Portal" #~ msgstr "Connesso ad un Portale Mesh di scuola" @@ -6,14 +6,16 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2009-02-21 00:30-0500\n" -"PO-Revision-Date: 2009-03-01 00:19-0500\n" -"Last-Translator: Pradosh Kharel <pradosh@olenepal.org>\n" +"POT-Creation-Date: 2009-06-11 00:31-0400\n" +"PO-Revision-Date: 2009-07-27 00:08-0400\n" +"Last-Translator: Daniel Drake <dsd@laptop.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n" +"Language: ne\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Generator: Pootle 1.1.0rc2\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Pootle 1.2.1\n" #: ../extensions/cpsection/aboutme/__init__.py:24 msgid "About Me" @@ -63,50 +65,43 @@ msgstr "रङà¥à¤— परिवरà¥à¤¤à¤¨ गरà¥à¤¨ कà¥à¤²à¤¿à¤• गठmsgid "About my Computer" msgstr "मेरो कमà¥à¤ªà¤¯à¥‚टरको बारेमा" -#: ../extensions/cpsection/aboutcomputer/model.py:26 +#: ../extensions/cpsection/aboutcomputer/model.py:28 msgid "Not available" msgstr "उपलबà¥à¤§ छैन" -#: ../extensions/cpsection/aboutcomputer/view.py:59 +#: ../extensions/cpsection/aboutcomputer/view.py:60 msgid "Identity" msgstr "वà¥à¤¯à¤•à¥à¤¤à¤¿à¤¤à¥à¤¬" -#: ../extensions/cpsection/aboutcomputer/view.py:68 +#: ../extensions/cpsection/aboutcomputer/view.py:69 msgid "Serial Number:" msgstr "कà¥à¤°à¤®à¤¿à¤• अङà¥à¤•" -#: ../extensions/cpsection/aboutcomputer/view.py:90 +#: ../extensions/cpsection/aboutcomputer/view.py:91 msgid "Software" msgstr "सफà¥à¤Ÿà¤µà¥‡à¤°" -#: ../extensions/cpsection/aboutcomputer/view.py:99 +#: ../extensions/cpsection/aboutcomputer/view.py:100 msgid "Build:" msgstr "बनाइ:" -#: ../extensions/cpsection/aboutcomputer/view.py:114 +#: ../extensions/cpsection/aboutcomputer/view.py:115 msgid "Sugar:" msgstr "सà¥à¤—र:" -#: ../extensions/cpsection/aboutcomputer/view.py:130 +#: ../extensions/cpsection/aboutcomputer/view.py:131 msgid "Firmware:" msgstr "फरà¥à¤®à¤µà¥‡à¤°:" -#: ../extensions/cpsection/aboutcomputer/view.py:145 +#: ../extensions/cpsection/aboutcomputer/view.py:146 msgid "Wireless Firmware:" msgstr "तारबिनाको फरà¥à¤®à¤µà¥‡à¤°:" -#: ../extensions/cpsection/aboutcomputer/view.py:168 +#: ../extensions/cpsection/aboutcomputer/view.py:169 msgid "Copyright and License" msgstr "कपिराइट र अनà¥à¤®à¤¤à¤¿à¤ªà¤¤à¥à¤°" -#: ../extensions/cpsection/aboutcomputer/view.py:176 -msgid "" -"© 2008 One Laptop per Child Association Inc; Red Hat Inc; and Contributors." -msgstr "" -"© २००८ वान लà¥à¤¯à¤ªà¥ टप पर चाईलà¥à¤¡ असोसिà¤à¤¸à¤¨ आइ à¤à¤¨ सि; रेड हà¥à¤¯à¤Ÿ आइ à¤à¤¨ सि; र अरॠ" -"निरà¥à¤®à¤¾à¤¤à¤¾à¤¹à¤°à¥à¥¤" - -#: ../extensions/cpsection/aboutcomputer/view.py:183 +#: ../extensions/cpsection/aboutcomputer/view.py:184 msgid "" "Sugar is the graphical user interface that you are looking at. Sugar is free " "software, covered by the GNU General Public License, and you are welcome to " @@ -117,7 +112,7 @@ msgstr "" "हो, सà¥à¤—र ले जि à¤à¤¨ यॠजनरल पबà¥à¤²à¤¿à¤• अनà¥à¤®à¤¤à¤¿ पतà¥à¤° पà¥à¤°à¤¯à¥‹à¤— गरà¥à¤› र तपाईंले यसलाई " "परिवरà¥à¤¤à¤¨ गरेर नियम अनà¥à¤¸à¤¾à¤° अरà¥à¤²à¤¾à¤ˆ दिन सकà¥à¤¨à¥à¤¹à¥à¤¨à¥à¤›à¥¤" -#: ../extensions/cpsection/aboutcomputer/view.py:195 +#: ../extensions/cpsection/aboutcomputer/view.py:196 msgid "Full license:" msgstr "समà¥à¤ªà¥‚रà¥à¤£ अनà¥à¤®à¤¤à¤¿ पतà¥à¤°:" @@ -267,7 +262,7 @@ msgstr "" "अति किफायत शकà¥à¤¤à¤¿à¤•à¥‹ पà¥à¤°à¤¬à¤¨à¥à¤§à¤•à¤°à¥à¤¤à¤¾ (तार बिनाको रेडियो असमरà¥à¤¥ पारà¥à¤›, बà¥à¤¯à¤¾à¤Ÿà¤°à¤¿à¤•à¥‹ " "जीवन बढाउà¤à¤›)" -#: ../extensions/deviceicon/battery.py:56 +#: ../extensions/deviceicon/battery.py:58 msgid "My Battery" msgstr "मेरो बेटरी" @@ -286,13 +281,13 @@ msgstr "à¤à¤•à¤¦à¤®à¥ˆ थोरै शकà¥â€à¤¤à¤¿ बाà¤à¤•à¥€ छ" #: ../extensions/deviceicon/battery.py:149 #, python-format msgid "%(hour)d:%(min).2d remaining" -msgstr "%(घणà¥à¤Ÿà¤¾)d:%(मिनेट).2d बाà¤à¤•à¥€" +msgstr "%(hour)d:%(min).2d बाà¤à¤•à¥€" #: ../extensions/deviceicon/battery.py:152 msgid "Charged" msgstr "चारà¥à¤œ à¤à¤¯à¥‹" -#: ../extensions/deviceicon/network.py:40 +#: ../extensions/deviceicon/network.py:43 #, python-format msgid "IP address: %s" msgstr "आइपी अडà¥à¤°à¥‡à¤¸: %s" @@ -301,54 +296,87 @@ msgstr "आइपी अडà¥à¤°à¥‡à¤¸: %s" # priority over the normal wireless device. NM doesn't have a "disconnect" # method for a device either (for various reasons) so this doesn't # have a good mapping -#: ../extensions/deviceicon/network.py:104 +#: ../extensions/deviceicon/network.py:109 msgid "Disconnect..." msgstr "जडान विचà¥à¤›à¥‡à¤¦..." +#: ../extensions/deviceicon/network.py:113 +msgid "Create new wireless network" +msgstr "" + # Only show disconnect when there's a mesh device, because mesh takes # priority over the normal wireless device. NM doesn't have a "disconnect" # method for a device either (for various reasons) so this doesn't # have a good mapping -#: ../extensions/deviceicon/network.py:109 -#: ../src/jarabe/desktop/meshbox.py:247 +#: ../extensions/deviceicon/network.py:119 +#: ../src/jarabe/desktop/meshbox.py:250 msgid "Connecting..." msgstr "जडान हà¥à¤¦à¥ˆà¤›..." # TODO: show the channel number -#: ../extensions/deviceicon/network.py:113 -#: ../extensions/deviceicon/network.py:166 -#: ../src/jarabe/desktop/meshbox.py:253 +#: ../extensions/deviceicon/network.py:123 +#: ../extensions/deviceicon/network.py:179 +#: ../src/jarabe/desktop/meshbox.py:256 msgid "Connected" msgstr "जडान à¤à¤¯à¥‹" -#: ../extensions/deviceicon/network.py:126 +#: ../extensions/deviceicon/network.py:139 msgid "Channel" msgstr "माधà¥à¤¯à¤®" -#: ../extensions/deviceicon/network.py:141 +#: ../extensions/deviceicon/network.py:154 msgid "Wired Network" msgstr "तार यà¥à¤•à¥à¤¤ संजाल" -#: ../extensions/deviceicon/network.py:169 +#: ../extensions/deviceicon/network.py:182 msgid "Speed" msgstr "गति" -#: ../extensions/deviceicon/speaker.py:46 +#: ../extensions/deviceicon/network.py:389 +#, python-format +msgid "%s's network" +msgstr "" + +#: ../extensions/deviceicon/speaker.py:59 msgid "My Speakers" msgstr "मेरो सà¥à¤ªà¤¿à¤•à¤°à¤¹à¤°à¥" -#: ../extensions/deviceicon/speaker.py:128 +#: ../extensions/deviceicon/speaker.py:133 msgid "Unmute" msgstr "आवाज खोल" -#: ../extensions/deviceicon/speaker.py:131 +#: ../extensions/deviceicon/speaker.py:136 msgid "Mute" msgstr "आवाज बनà¥à¤¦" -#: ../extensions/globalkey/screenshot.py:50 +#: ../extensions/globalkey/screenshot.py:56 +msgid "Mesh" +msgstr "मेश" + +#: ../extensions/globalkey/screenshot.py:58 +#: ../src/jarabe/frame/zoomtoolbar.py:38 +msgid "Group" +msgstr "समà¥à¤¹" + +#: ../extensions/globalkey/screenshot.py:60 +#: ../src/jarabe/frame/zoomtoolbar.py:40 +msgid "Home" +msgstr "गृह" + +#: ../extensions/globalkey/screenshot.py:66 +#: ../src/jarabe/frame/zoomtoolbar.py:42 +msgid "Activity" +msgstr "कà¥à¤°à¤¿à¤¯à¤¾à¤•à¤²à¤¾à¤ª" + +#: ../extensions/globalkey/screenshot.py:69 msgid "Screenshot" msgstr "परà¥à¤¦à¤¾à¤›à¤µà¤¿" +#: ../extensions/globalkey/screenshot.py:71 +#, python-format +msgid "Screenshot of \"%s\"" +msgstr "" + #: ../data/sugar.schemas.in.h:1 msgid "Backup URL" msgstr "बैकअप URL" @@ -475,8 +503,8 @@ msgid "" "sugar-control-panel: WARNING, found more than one option with the same name: " "%s module: %r" msgstr "" -"सà¥à¤—र-कनà¥à¤Ÿà¥à¤°à¥‹à¤²-पà¥à¤¯à¤¾à¤¨à¤²: सावधान, तà¥à¤¯à¤¹à¥€ नाम à¤à¤à¤•à¥‹ à¤à¤‰à¤Ÿà¤¾ à¤à¤¨à¥à¤¦à¤¾ धेरै रोजाई à¤à¥‡à¤Ÿà¤¿à¤¯à¥‹: " -"%s मोडियà¥à¤²: %r" +"सà¥à¤—र-कनà¥à¤Ÿà¥à¤°à¥‹à¤²-पà¥à¤¯à¤¾à¤¨à¤²: सावधान, तà¥à¤¯à¤¹à¥€ नाम à¤à¤à¤•à¥‹ à¤à¤‰à¤Ÿà¤¾ à¤à¤¨à¥à¤¦à¤¾ धेरै रोजाई à¤à¥‡à¤Ÿà¤¿à¤¯à¥‹: %" +"s मोडियà¥à¤²: %r" #: ../src/jarabe/controlpanel/cmd.py:30 #, python-format @@ -547,65 +575,65 @@ msgstr "à¤à¤¯à¥‹" #: ../src/jarabe/controlpanel/toolbar.py:115 #: ../src/jarabe/desktop/homebox.py:111 -#: ../src/jarabe/frame/activitiestray.py:687 -#: ../src/jarabe/frame/activitiestray.py:766 -#: ../src/jarabe/frame/activitiestray.py:794 +#: ../src/jarabe/frame/activitiestray.py:726 +#: ../src/jarabe/frame/activitiestray.py:821 +#: ../src/jarabe/frame/activitiestray.py:849 msgid "Cancel" msgstr "रदà¥à¤¦" #: ../src/jarabe/controlpanel/toolbar.py:121 -#: ../src/jarabe/desktop/favoritesview.py:338 +#: ../src/jarabe/desktop/favoritesview.py:339 msgid "Ok" msgstr "हà¥à¤¨à¥à¤›" # TRANS: label for the freeform layout in the favorites view #. TRANS: label for the freeform layout in the favorites view -#: ../src/jarabe/desktop/favoriteslayout.py:114 +#: ../src/jarabe/desktop/favoriteslayout.py:116 msgid "Freeform" msgstr "सà¥à¤µà¤¤à¤¨à¥à¤¤à¥à¤° आकारको" # TRANS: label for the ring layout in the favorites view #. TRANS: label for the ring layout in the favorites view -#: ../src/jarabe/desktop/favoriteslayout.py:196 +#: ../src/jarabe/desktop/favoriteslayout.py:198 msgid "Ring" msgstr "औà¤à¤ ी" # TRANS: label for the spiral layout in the favorites view #. TRANS: label for the spiral layout in the favorites view -#: ../src/jarabe/desktop/favoriteslayout.py:332 +#: ../src/jarabe/desktop/favoriteslayout.py:334 msgid "Spiral" msgstr "पेंचदार" # TRANS: label for the box layout in the favorites view #. TRANS: label for the box layout in the favorites view -#: ../src/jarabe/desktop/favoriteslayout.py:399 +#: ../src/jarabe/desktop/favoriteslayout.py:401 msgid "Box" msgstr "बाकस" # TRANS: label for the box layout in the favorites view #. TRANS: label for the box layout in the favorites view -#: ../src/jarabe/desktop/favoriteslayout.py:440 +#: ../src/jarabe/desktop/favoriteslayout.py:442 msgid "Triangle" msgstr "तà¥à¤°à¤¿à¤•à¥‹à¤£" -#: ../src/jarabe/desktop/favoritesview.py:329 +#: ../src/jarabe/desktop/favoritesview.py:330 msgid "Registration Failed" msgstr "दरà¥à¤¤à¤¾ असफल à¤à¤¯à¥‹" -#: ../src/jarabe/desktop/favoritesview.py:330 +#: ../src/jarabe/desktop/favoritesview.py:331 #, python-format msgid "%s" msgstr "%s" -#: ../src/jarabe/desktop/favoritesview.py:332 +#: ../src/jarabe/desktop/favoritesview.py:333 msgid "Registration Successful" msgstr "दरà¥à¤¤à¤¾ सफल" -#: ../src/jarabe/desktop/favoritesview.py:333 +#: ../src/jarabe/desktop/favoritesview.py:334 msgid "You are now registered with your school server." msgstr "अब तपाईà¤à¤•à¥‹ नाम बिदà¥à¤¯à¤¾à¤²à¤¯à¤•à¥‹ सरà¥à¤à¤°à¤®à¤¾ दरà¥à¤¤à¤¾ à¤à¤‡à¤¸à¤•à¥à¤¯à¥‹à¥¤" -#: ../src/jarabe/desktop/favoritesview.py:668 +#: ../src/jarabe/desktop/favoritesview.py:674 msgid "Register" msgstr "दरà¥à¤¤à¤¾" @@ -677,25 +705,25 @@ msgstr "WPA र WPA2 निजी" msgid "Wireless Security:" msgstr "तारबिनाको लागि सà¥à¤°à¤•à¥à¤·à¤¾" -#: ../src/jarabe/desktop/meshbox.py:131 +#: ../src/jarabe/desktop/meshbox.py:134 msgid "Connect" msgstr "जोड" -#: ../src/jarabe/desktop/meshbox.py:135 +#: ../src/jarabe/desktop/meshbox.py:138 msgid "Disconnect" msgstr "विचà¥à¤›à¥‡à¤¦ à¤à¤¯à¥‹" # TRANS: Action label for resuming an activity. #. TRANS: Action label for resuming an activity. -#: ../src/jarabe/desktop/meshbox.py:441 -#: ../src/jarabe/frame/activitiestray.py:711 +#: ../src/jarabe/desktop/meshbox.py:449 +#: ../src/jarabe/frame/activitiestray.py:761 #: ../src/jarabe/journal/journaltoolbox.py:425 #: ../src/jarabe/journal/palettes.py:72 ../src/jarabe/view/palettes.py:66 msgid "Resume" msgstr "पà¥à¤¨à¤°à¤¾à¤°à¤®à¥à¤" -#: ../src/jarabe/desktop/meshbox.py:446 -#: ../src/jarabe/frame/activitiestray.py:227 +#: ../src/jarabe/desktop/meshbox.py:454 +#: ../src/jarabe/frame/activitiestray.py:235 msgid "Join" msgstr "सहà¤à¤¾à¤—ी होऊ" @@ -711,47 +739,52 @@ msgstr "सरà¥à¤à¤°à¤®à¤¾ जड़ान हà¥à¤¨ सकेन।" msgid "The server could not complete the request." msgstr "सरà¥à¤à¤°à¤²à¥‡ अनà¥à¤°à¥‹à¤§ पà¥à¤°à¤¾ गरà¥à¤¨ सकेन।" -#: ../src/jarabe/frame/activitiestray.py:232 -#: ../src/jarabe/frame/activitiestray.py:659 +#: ../src/jarabe/frame/activitiestray.py:240 +#: ../src/jarabe/frame/activitiestray.py:698 msgid "Decline" msgstr "नाई" -#: ../src/jarabe/frame/activitiestray.py:612 +#: ../src/jarabe/frame/activitiestray.py:650 #, python-format msgid "%dB" msgstr "%dB" -#: ../src/jarabe/frame/activitiestray.py:614 +#: ../src/jarabe/frame/activitiestray.py:652 #, python-format msgid "%dKB" msgstr "%dKB" -#: ../src/jarabe/frame/activitiestray.py:616 +#: ../src/jarabe/frame/activitiestray.py:654 #, python-format msgid "%dMB" msgstr "%dMB" -#: ../src/jarabe/frame/activitiestray.py:633 +#: ../src/jarabe/frame/activitiestray.py:671 #, python-format msgid "%s of %s" msgstr "%s को %s" -#: ../src/jarabe/frame/activitiestray.py:644 +#: ../src/jarabe/frame/activitiestray.py:683 #, python-format msgid "Transfer from %r" msgstr "%r बाट सारà¥à¤¨à¥à¤¹à¥‹à¤¸" -#: ../src/jarabe/frame/activitiestray.py:654 +#: ../src/jarabe/frame/activitiestray.py:693 msgid "Accept" msgstr "सà¥à¤µà¤¿à¤•à¤¾à¤°" -#: ../src/jarabe/frame/activitiestray.py:677 -#: ../src/jarabe/frame/activitiestray.py:784 +#: ../src/jarabe/frame/activitiestray.py:716 +#: ../src/jarabe/frame/activitiestray.py:839 #, python-format msgid "%s (%s)" msgstr "%s (%s)" -#: ../src/jarabe/frame/activitiestray.py:755 +#: ../src/jarabe/frame/activitiestray.py:750 +#: ../src/jarabe/frame/activitiestray.py:873 +msgid "Dismiss" +msgstr "" + +#: ../src/jarabe/frame/activitiestray.py:810 #, python-format msgid "Transfer to %r" msgstr "%r मा सारà¥à¤¨à¥à¤¹à¥‹à¤¸" @@ -778,18 +811,6 @@ msgstr "%s कà¥à¤²à¤¿à¤ªà¤¿à¤™" msgid "Neighborhood" msgstr "छिमेक" -#: ../src/jarabe/frame/zoomtoolbar.py:38 -msgid "Group" -msgstr "समà¥à¤¹" - -#: ../src/jarabe/frame/zoomtoolbar.py:40 -msgid "Home" -msgstr "गृह" - -#: ../src/jarabe/frame/zoomtoolbar.py:42 -msgid "Activity" -msgstr "कà¥à¤°à¤¿à¤¯à¤¾à¤•à¤²à¤¾à¤ª" - #: ../src/jarabe/intro/window.py:124 msgid "Click to change color:" msgstr "रङà¥à¤— परिवरà¥à¤¤à¤¨ गरà¥à¤¨ कà¥à¤²à¤¿à¤• गरः" @@ -902,11 +923,11 @@ msgstr "तिमà¥à¤°à¥‹ पंजिका खाली छ" msgid "No matching entries " msgstr "मिलà¥à¤¨à¥‡ लेखा छैन " -#: ../src/jarabe/journal/listview.py:369 +#: ../src/jarabe/journal/listview.py:370 msgid "Clear search" msgstr "खोजलाई सफा गर" -#: ../src/jarabe/journal/misc.py:91 +#: ../src/jarabe/journal/misc.py:93 msgid "No date" msgstr "मिति छैन" @@ -1005,24 +1026,24 @@ msgstr "सà¥à¤°à¥‹à¤¤ हेर" msgid "Stop" msgstr "बनà¥à¤¦ गर" -#: ../src/jarabe/view/palettes.py:171 +#: ../src/jarabe/view/palettes.py:174 msgid "Remove favorite" msgstr "पà¥à¤°à¤¿à¤¯ हटाउ" -#: ../src/jarabe/view/palettes.py:175 +#: ../src/jarabe/view/palettes.py:178 msgid "Make favorite" msgstr "पà¥à¤°à¤¿à¤¯ बनाउ" -#: ../src/jarabe/view/palettes.py:238 +#: ../src/jarabe/view/palettes.py:241 msgid "Show contents" msgstr "वसà¥à¤¤à¥à¤¹à¤°à¥ देखाउ" -#: ../src/jarabe/view/palettes.py:260 ../src/jarabe/view/palettes.py:309 +#: ../src/jarabe/view/palettes.py:263 ../src/jarabe/view/palettes.py:313 #, python-format msgid "%(free_space)d MB Free" -msgstr "%(खालि॒_ठाउठ)d MB खालि" +msgstr "%(free_space)d MB खालि" -#: ../src/jarabe/view/palettes.py:285 +#: ../src/jarabe/view/palettes.py:288 msgid "Unmount" msgstr "हटाऊ" @@ -1043,6 +1064,12 @@ msgstr "कà¥à¤°à¤¿à¤¯à¤¾à¤•à¤²à¤¾à¤ª बनà¥à¤¡à¤²à¤•à¥‹ मà¥à¤²" msgid "View source: %r" msgstr "सà¥à¤°à¥‹à¤¤ हेर : %r" +#~ msgid "" +#~ "© 2008 One Laptop per Child Association Inc; Red Hat Inc; and Contributors." +#~ msgstr "" +#~ "© २००८ वान लà¥à¤¯à¤ªà¥ टप पर चाईलà¥à¤¡ असोसिà¤à¤¸à¤¨ आइ à¤à¤¨ सि; रेड हà¥à¤¯à¤Ÿ आइ à¤à¤¨ सि; र अरॠ" +#~ "निरà¥à¤®à¤¾à¤¤à¤¾à¤¹à¤°à¥à¥¤" + #~ msgid "Document" #~ msgstr "कागतपतà¥à¤°" @@ -1069,9 +1096,6 @@ msgstr "सà¥à¤°à¥‹à¤¤ हेर : %r" #~ msgid "About my XO" #~ msgstr "मिरो XO बारे" -#~ msgid "Mesh" -#~ msgstr "मेश" - #~ msgid "Connected to a School Mesh Portal" #~ msgstr "विदà¥à¤¯à¤¾à¤²à¤¯ मेश पोरà¥à¤Ÿà¤²à¤®à¤¾ जडान à¤à¤¯à¥‹" @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2009-03-19 00:30-0400\n" -"PO-Revision-Date: 2009-05-21 02:42-0400\n" +"POT-Creation-Date: 2009-06-11 00:31-0400\n" +"PO-Revision-Date: 2009-07-31 16:03-0400\n" "Last-Translator: Myckel Habets <myckel@sdf.lonestar.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "Language: nl\n" @@ -65,50 +65,43 @@ msgstr "Klik om de kleur te veranderen:" msgid "About my Computer" msgstr "Over mijn computer" -#: ../extensions/cpsection/aboutcomputer/model.py:26 +#: ../extensions/cpsection/aboutcomputer/model.py:28 msgid "Not available" msgstr "Niet beschikbaar" -#: ../extensions/cpsection/aboutcomputer/view.py:59 +#: ../extensions/cpsection/aboutcomputer/view.py:60 msgid "Identity" msgstr "Identiteit" -#: ../extensions/cpsection/aboutcomputer/view.py:68 +#: ../extensions/cpsection/aboutcomputer/view.py:69 msgid "Serial Number:" msgstr "Serienummer:" -#: ../extensions/cpsection/aboutcomputer/view.py:90 +#: ../extensions/cpsection/aboutcomputer/view.py:91 msgid "Software" msgstr "Software" -#: ../extensions/cpsection/aboutcomputer/view.py:99 +#: ../extensions/cpsection/aboutcomputer/view.py:100 msgid "Build:" msgstr "Bouw:" -#: ../extensions/cpsection/aboutcomputer/view.py:114 +#: ../extensions/cpsection/aboutcomputer/view.py:115 msgid "Sugar:" msgstr "Sugar:" -#: ../extensions/cpsection/aboutcomputer/view.py:130 +#: ../extensions/cpsection/aboutcomputer/view.py:131 msgid "Firmware:" msgstr "Firmware:" -#: ../extensions/cpsection/aboutcomputer/view.py:145 +#: ../extensions/cpsection/aboutcomputer/view.py:146 msgid "Wireless Firmware:" msgstr "Firmware draadloos netwerk:" -#: ../extensions/cpsection/aboutcomputer/view.py:168 +#: ../extensions/cpsection/aboutcomputer/view.py:169 msgid "Copyright and License" msgstr "Copyright en licentie" -#: ../extensions/cpsection/aboutcomputer/view.py:176 -msgid "" -"© 2008 One Laptop per Child Association Inc; Red Hat Inc; and Contributors." -msgstr "" -"© 2008 One Laptop per Child Association Inc; Red Hat Inc; en anderen die " -"bijgedragen hebben." - -#: ../extensions/cpsection/aboutcomputer/view.py:183 +#: ../extensions/cpsection/aboutcomputer/view.py:184 msgid "" "Sugar is the graphical user interface that you are looking at. Sugar is free " "software, covered by the GNU General Public License, and you are welcome to " @@ -120,7 +113,7 @@ msgstr "" "je mag het aanpassen en/of kopieën distribueren onder de condities zoals " "vermeld in de licentie." -#: ../extensions/cpsection/aboutcomputer/view.py:195 +#: ../extensions/cpsection/aboutcomputer/view.py:196 msgid "Full license:" msgstr "Volledige licentie:" @@ -298,55 +291,88 @@ msgstr "%(hour)d:%(min).2d over" msgid "Charged" msgstr "Opgeladen" -#: ../extensions/deviceicon/network.py:40 +#: ../extensions/deviceicon/network.py:43 #, python-format msgid "IP address: %s" msgstr "IP adres: %s" -#: ../extensions/deviceicon/network.py:104 +#: ../extensions/deviceicon/network.py:109 msgid "Disconnect..." msgstr "Verbinding verbreken..." -#: ../extensions/deviceicon/network.py:109 -#: ../src/jarabe/desktop/meshbox.py:248 +#: ../extensions/deviceicon/network.py:113 +msgid "Create new wireless network" +msgstr "Nieuw draadloos netwerk aanmaken" + +#: ../extensions/deviceicon/network.py:119 +#: ../src/jarabe/desktop/meshbox.py:250 msgid "Connecting..." msgstr "Verbinden..." # TODO: show the channel number -#: ../extensions/deviceicon/network.py:113 -#: ../extensions/deviceicon/network.py:166 -#: ../src/jarabe/desktop/meshbox.py:254 +#: ../extensions/deviceicon/network.py:123 +#: ../extensions/deviceicon/network.py:179 +#: ../src/jarabe/desktop/meshbox.py:256 msgid "Connected" msgstr "Verbonden" -#: ../extensions/deviceicon/network.py:126 +#: ../extensions/deviceicon/network.py:139 msgid "Channel" msgstr "Kanaal" -#: ../extensions/deviceicon/network.py:141 +#: ../extensions/deviceicon/network.py:154 msgid "Wired Network" msgstr "Bedraad netwerk" -#: ../extensions/deviceicon/network.py:169 +#: ../extensions/deviceicon/network.py:182 msgid "Speed" msgstr "Snelheid" +#: ../extensions/deviceicon/network.py:389 +#, python-format +msgid "%s's network" +msgstr "%s's netwerk" + #: ../extensions/deviceicon/speaker.py:59 msgid "My Speakers" msgstr "Mijn Speakers" -#: ../extensions/deviceicon/speaker.py:135 +#: ../extensions/deviceicon/speaker.py:133 msgid "Unmute" msgstr "Ontdempen" -#: ../extensions/deviceicon/speaker.py:138 +#: ../extensions/deviceicon/speaker.py:136 msgid "Mute" msgstr "Dempen" -#: ../extensions/globalkey/screenshot.py:51 +#: ../extensions/globalkey/screenshot.py:56 +msgid "Mesh" +msgstr "Mesh" + +#: ../extensions/globalkey/screenshot.py:58 +#: ../src/jarabe/frame/zoomtoolbar.py:38 +msgid "Group" +msgstr "Groep" + +#: ../extensions/globalkey/screenshot.py:60 +#: ../src/jarabe/frame/zoomtoolbar.py:40 +msgid "Home" +msgstr "Thuis" + +#: ../extensions/globalkey/screenshot.py:66 +#: ../src/jarabe/frame/zoomtoolbar.py:42 +msgid "Activity" +msgstr "Activiteit" + +#: ../extensions/globalkey/screenshot.py:69 msgid "Screenshot" msgstr "Schermafdruk" +#: ../extensions/globalkey/screenshot.py:71 +#, python-format +msgid "Screenshot of \"%s\"" +msgstr "Schermafdruk van \"%s\"" + #: ../data/sugar.schemas.in.h:1 msgid "Backup URL" msgstr "Backup URL" @@ -680,24 +706,24 @@ msgstr "WPA en WPA 2 Persoonlijk" msgid "Wireless Security:" msgstr "Draadloze netwerkbeveiliging:" -#: ../src/jarabe/desktop/meshbox.py:132 +#: ../src/jarabe/desktop/meshbox.py:134 msgid "Connect" msgstr "Verbinden" -#: ../src/jarabe/desktop/meshbox.py:136 +#: ../src/jarabe/desktop/meshbox.py:138 msgid "Disconnect" msgstr "Verbinding verbreken" # TRANS: Action label for resuming an activity. #. TRANS: Action label for resuming an activity. -#: ../src/jarabe/desktop/meshbox.py:442 +#: ../src/jarabe/desktop/meshbox.py:449 #: ../src/jarabe/frame/activitiestray.py:761 #: ../src/jarabe/journal/journaltoolbox.py:425 #: ../src/jarabe/journal/palettes.py:72 ../src/jarabe/view/palettes.py:66 msgid "Resume" msgstr "Hervatten" -#: ../src/jarabe/desktop/meshbox.py:447 +#: ../src/jarabe/desktop/meshbox.py:454 #: ../src/jarabe/frame/activitiestray.py:235 msgid "Join" msgstr "Bijvoegen" @@ -787,18 +813,6 @@ msgstr "%s in klembord zetten" msgid "Neighborhood" msgstr "Omgeving" -#: ../src/jarabe/frame/zoomtoolbar.py:38 -msgid "Group" -msgstr "Groep" - -#: ../src/jarabe/frame/zoomtoolbar.py:40 -msgid "Home" -msgstr "Thuis" - -#: ../src/jarabe/frame/zoomtoolbar.py:42 -msgid "Activity" -msgstr "Activiteit" - #: ../src/jarabe/intro/window.py:124 msgid "Click to change color:" msgstr "Klik om de kleur te veranderen:" @@ -911,7 +925,7 @@ msgstr "Je dagboek is leeg" msgid "No matching entries " msgstr "Geen overeenkomende ingangen " -#: ../src/jarabe/journal/listview.py:369 +#: ../src/jarabe/journal/listview.py:370 msgid "Clear search" msgstr "Zoekopdracht wissen" @@ -1014,24 +1028,24 @@ msgstr "Bron weergeven" msgid "Stop" msgstr "Stop" -#: ../src/jarabe/view/palettes.py:171 +#: ../src/jarabe/view/palettes.py:174 msgid "Remove favorite" msgstr "Verwijder favoriet" -#: ../src/jarabe/view/palettes.py:175 +#: ../src/jarabe/view/palettes.py:178 msgid "Make favorite" msgstr "Maak favoriet" -#: ../src/jarabe/view/palettes.py:238 +#: ../src/jarabe/view/palettes.py:241 msgid "Show contents" msgstr "Inhoud weergeven" -#: ../src/jarabe/view/palettes.py:260 ../src/jarabe/view/palettes.py:309 +#: ../src/jarabe/view/palettes.py:263 ../src/jarabe/view/palettes.py:313 #, python-format msgid "%(free_space)d MB Free" msgstr "%(free_space)d MB vrij" -#: ../src/jarabe/view/palettes.py:285 +#: ../src/jarabe/view/palettes.py:288 msgid "Unmount" msgstr "Loskoppelen" @@ -1052,6 +1066,12 @@ msgstr "Activiteitsbundel Bron" msgid "View source: %r" msgstr "Bron weergeven: %r" +#~ msgid "" +#~ "© 2008 One Laptop per Child Association Inc; Red Hat Inc; and Contributors." +#~ msgstr "" +#~ "© 2008 One Laptop per Child Association Inc; Red Hat Inc; en anderen die " +#~ "bijgedragen hebben." + #~ msgid "Document" #~ msgstr "Document" @@ -1073,9 +1093,6 @@ msgstr "Bron weergeven: %r" #~ msgid "About my XO" #~ msgstr "Over mijn XO" -#~ msgid "Mesh" -#~ msgstr "Mesh" - #~ msgid "Connected to a School Mesh Portal" #~ msgstr "Verbonden met een School Mesh Portaal" 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) |