diff options
149 files changed, 9871 insertions, 5002 deletions
@@ -1,8 +1,18 @@ Building ======== -See: -http://wiki.laptop.org/go/Sugar_with_sugar-jhbuild +For details see: http://wiki.sugarlabs.org/go/Development_Team/Jhbuild + +Sugar-jhbuild will automatically download the latest of Sugar's +dependencies as well as Sugar itself directly from their source +repositories, rather than relying on source packages that may have +become stale. These are generic instructions on how to use jhbuild +to get up and running with Sugar. + + $ cd sugar-jhbuild + $ ./sugar-jhbuild update + $ ./sugar-jhbuild depscheck + $ ./sugar-jhbuild buildgit Running multiple instances on the same machine ============================================== diff --git a/bin/.gitignore b/bin/.gitignore deleted file mode 100644 index 9e78b64..0000000 --- a/bin/.gitignore +++ /dev/null @@ -1 +0,0 @@ -sugar-shell-service diff --git a/bin/sugar-session b/bin/sugar-session index 1582b65..91ebf6f 100644..100755 --- a/bin/sugar-session +++ b/bin/sugar-session @@ -20,6 +20,7 @@ import os import sys import time import subprocess +import shutil if os.environ.get('SUGAR_LOGGER_LEVEL', '') == 'debug': print '%r STARTUP: Starting the shell' % time.time() @@ -78,7 +79,6 @@ def start_ui_service(): from jarabe.view.service import UIService ui_service = UIService() - ui_service.start() def start_session_manager(): from jarabe.model.session import get_session_manager @@ -203,6 +203,10 @@ def set_fonts(): def main(): try: from sugar import env + # Remove temporary files. See http://bugs.sugarlabs.org/ticket/1876 + data_dir = os.path.join(env.get_profile_path(), 'data') + shutil.rmtree(data_dir, ignore_errors=True) + os.makedirs(data_dir) cleanup_logs(env.get_logs_path()) except OSError, e: # logs cleanup is not critical; it should not prevent sugar from @@ -225,7 +229,7 @@ def main(): client = gconf.client_get_default() client.set_string('/apps/metacity/general/mouse_button_modifier', - 'disabled') + '<Super>') timezone = client.get_string('/desktop/sugar/date/timezone') if timezone is not None and timezone: diff --git a/bin/sugar.in b/bin/sugar.in index 898bd59..12098db 100644 --- a/bin/sugar.in +++ b/bin/sugar.in @@ -36,6 +36,11 @@ while [ $# -ne 0 ] ; do shift done +# Set default profile dir +if test -z "$SUGAR_PROFILE"; then + export SUGAR_PROFILE=default +fi + if test -z "$SUGAR_SCALING"; then export SUGAR_SCALING=72 fi @@ -54,6 +59,15 @@ fi export LANG="${LANG:-en_US.utf8}" export LANGUAGE="${LANGUAGE:-${LANG}}" +# Set Sugar's telepathy accounts directory +export MC_ACCOUNT_DIR=$HOME/.sugar/$SUGAR_PROFILE/accounts + +# Workaround until gnome-keyring-daemon lets dbus activate it +# https://bugzilla.gnome.org/show_bug.cgi?id=628302 +if test "$SUGAR_EMULATOR" = "yes" -a "$(type gnome-keyring-daemon)"; then + gnome-keyring-daemon --components=secrets & +fi + # Source language settings and debug definitions if [ -f ~/.i18n ]; then . ~/.i18n diff --git a/configure.ac b/configure.ac index 4f60892..151eaa5 100644 --- a/configure.ac +++ b/configure.ac @@ -1,11 +1,11 @@ -AC_INIT([Sugar],[0.88.0],[],[sugar]) +AC_INIT([Sugar],[0.90.3],[],[sugar]) AC_PREREQ([2.59]) AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_SRCDIR([configure.ac]) -SUCROSE_VERSION="0.88.0" +SUCROSE_VERSION="0.90.1" AC_SUBST(SUCROSE_VERSION) AM_INIT_AUTOMAKE([1.9 foreign dist-bzip2 no-dist-gzip]) diff --git a/data/icons/module-updater.svg b/data/icons/module-updater.svg index 2329a4a..a521f61 100644 --- a/data/icons/module-updater.svg +++ b/data/icons/module-updater.svg @@ -1,15 +1,15 @@ <?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [ <!ENTITY stroke_color "#000"> <!ENTITY fill_color "#fff"> -]><svg height="55px" viewBox="0 0 55 55" width="55px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" stroke-linecap="round" stroke-linejoin="round" stroke-width="3.5" stroke="&stroke_color;" fill="&fill_color;"> +]><svg height="55px" viewBox="0 0 55 55" width="55px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <g> <g> - <path d="M 31.752 7.088 C 41.935 9.118 49.609 18.107 49.609 28.887 C 49.609 41.173 39.65 51.129 27.374 51.129 C 15.086 51.129 5.133 41.173 5.133 28.887 C 5.133 19.648 10.768 11.723 18.801 8.365 " fill="none" stroke="&fill_color;" /> - <path d="M 36.134 15.154 L 31.752 7.088 L 40.439 4.13 " fill="none" stroke="&fill_color;" /> + <path d="M 31.752 7.088 C 41.935 9.118 49.609 18.107 49.609 28.887 C 49.609 41.173 39.65 51.129 27.374 51.129 C 15.086 51.129 5.133 41.173 5.133 28.887 C 5.133 19.648 10.768 11.723 18.801 8.365 " fill="none" stroke="&fill_color;" stroke-linecap="round" stroke-linejoin="round" stroke-width="3.5" /> + <path d="M 36.134 15.154 L 31.752 7.088 L 40.439 4.13 " fill="none" stroke="&fill_color;" stroke-linecap="round" stroke-linejoin="round" stroke-width="3.5" /> </g> <g> <g> - <path d="M 38.57 25.886 C 37.597 25.886 36.718 26.282 36.082 26.918 L 31.021 31.979 L 31.02 17.022 C 31.018 16.124 30.675 15.221 29.99 14.533 C 28.613 13.159 26.383 13.159 25.01 14.533 C 24.321 15.222 23.98 16.122 23.979 17.023 L 23.977 31.978 L 18.918 26.918 C 18.281 26.281 17.4 25.886 16.429 25.887 C 14.484 25.885 12.908 27.465 12.908 29.408 C 12.907 30.381 13.304 31.263 13.936 31.899 L 27.5 45.463 L 41.062 31.898 C 41.697 31.262 42.09 30.382 42.093 29.41 C 42.094 27.463 40.516 25.885 38.57 25.886 Z " stroke="none" /> + <path d="M 38.57 25.886 C 37.597 25.886 36.718 26.282 36.082 26.918 L 31.021 31.979 L 31.02 17.022 C 31.018 16.124 30.675 15.221 29.99 14.533 C 28.613 13.159 26.383 13.159 25.01 14.533 C 24.321 15.222 23.98 16.122 23.979 17.023 L 23.977 31.978 L 18.918 26.918 C 18.281 26.281 17.4 25.886 16.429 25.887 C 14.484 25.885 12.908 27.465 12.908 29.408 C 12.907 30.381 13.304 31.263 13.936 31.899 L 27.5 45.463 L 41.062 31.898 C 41.697 31.262 42.09 30.382 42.093 29.41 C 42.094 27.463 40.516 25.885 38.57 25.886 Z " fill="&fill_color;" stroke="none" /> </g> </g> </g> diff --git a/data/sugar.schemas.in b/data/sugar.schemas.in index b9606ba..b13f746 100644 --- a/data/sugar.schemas.in +++ b/data/sugar.schemas.in @@ -31,9 +31,9 @@ <default></default> <locale name="C"> <short>User Color</short> - <long>Color for the XO icon that is used throughout the - desktop. The string is composed of the stroke color and fill - color, format is that of rbg colors. Example: #AC32FF,#9A5200 + <long>Color for the XO icon that is used throughout the + desktop. The string is composed of the stroke color and fill + color, format is that of rgb colors. Example: #AC32FF,#9A5200 </long> </locale> </schema> @@ -78,7 +78,7 @@ <applyto>/desktop/sugar/date/timezone</applyto> <owner>sugar</owner> <type>string</type> - <default>UTC</default> + <default></default> <locale name="C"> <short>Timezone</short> <long>Timezone setting for the system.</long> @@ -192,6 +192,18 @@ </schema> <schema> + <key>/schemas/desktop/sugar/show_restart</key> + <applyto>/desktop/sugar/show_restart</applyto> + <owner>sugar</owner> + <type>bool</type> + <default>true</default> + <locale name="C"> + <short>Show Restart</short> + <long>If TRUE, Sugar will show a "Restart" option.</long> + </locale> + </schema> + + <schema> <key>/schemas/desktop/sugar/peripherals/keyboard/layouts</key> <applyto>/desktop/sugar/peripherals/keyboard/layouts</applyto> <owner>sugar</owner> @@ -329,5 +341,33 @@ </locale> </schema> + <schema> + <key>/schemas/desktop/sugar/network/adhoc</key> + <applyto>/desktop/sugar/network/adhoc</applyto> + <owner>sugar</owner> + <type>bool</type> + <default>true</default> + <locale name="C"> + <short>Show Sugar Ad-hoc networks</short> + <long>If TRUE, Sugar will show default Ad-hoc networks for + channel 1,6 and 11. If Sugar sees no "known" network when + it starts, it does autoconnect to an Ad-hoc network.</long> + </locale> + </schema> + + <schema> + <key>/schemas/desktop/sugar/protected_activities</key> + <applyto>/desktop/sugar/protected_activities</applyto> + <owner>sugar</owner> + <type>list</type> + <list_type>string</list_type> + <default>[]</default> + <locale name="C"> + <short>Bundle IDs of protected activities</short> + <long>Users will not be allowed to erase these + activities through the list view.</long> + </locale> + </schema> + </schemalist> </gconfschemafile> diff --git a/data/sugar.xml.in b/data/sugar.xml.in index 6a7f253..9300a45 100644 --- a/data/sugar.xml.in +++ b/data/sugar.xml.in @@ -8,4 +8,8 @@ <_comment>Sugar content bundle</_comment> <glob pattern="*.xol"/> </mime-type> -</mime-info>
\ No newline at end of file + <mime-type type="application/vnd.olpc-journal-entry"> + <_comment>Sugar Journal entry bundle</_comment> + <glob pattern="*.xoj"/> + </mime-type> +</mime-info> diff --git a/docs/controls.txt b/docs/controls.txt index 52591ea..fa977ef 100644 --- a/docs/controls.txt +++ b/docs/controls.txt @@ -52,7 +52,7 @@ sugar.ToolButton (support for rollovers) * There is no palette but a tooltip. * Normal: Button grey rounded filled rectangle * Inactive: Button grey rounded stroked rectangle - + sugar.ToggleIconButton * Toggled should be like Pressed @@ -184,10 +184,10 @@ Palettes in ToolIconButton, IconButton * The popup will have a setPrimaryState(label, accelerator) method. For action buttons would be a MenuItem, for the others would only be a Label. * The primary state should already have the same width as the secondary state and the expandable areas. * Primary states appear and disappear automatically (with a short delay). A click outside makes it disappear instantly. -* Secondary states appear after a delay, or with a single click on the icon. +* Secondary states appear after a delay, or with a single click on the icon. * Secondary disappears with the esc key, clicking outside the popup or clicking on a button inside. -Toolbox +Toolbox * When an activity opens, the activity tab should be opened and the focus on the activity title. * We must provide an activity tab in the toolbox and would be good to also provide an standard Edit tab. diff --git a/docs/release_howto.txt b/docs/release_howto.txt index db877e0..841809a 100644 --- a/docs/release_howto.txt +++ b/docs/release_howto.txt @@ -1,7 +1,7 @@ -''' This is the release process of the sugar tarballs sugar(shell), +""" This is the release process of the sugar tarballs sugar(shell), sugar-toolkit and sugar-base described in a pytish way and instructions for sugar packagers -''' +""" # Release sugar tarballs @@ -10,9 +10,9 @@ for package in [sugar, sugar-toolkit, sugar-base, sugar-artwork]: Pull the latest sources. Increase the version number in configure.ac # this will create you a tarball and does a check if it builds fine - # e.g. it will check if all the files containing translations are + # e.g. it will check if all the files containing translations are # in po/POTFILES.in - make distcheck + make distcheck if that succeed: # commit the change, log it as "Release [version_number]" (e.g. 0.79.1) @@ -26,7 +26,7 @@ for package in [sugar, sugar-toolkit, sugar-base, sugar-artwork]: break # Upload the package - Upload the tarball to + Upload the tarball to shell.sugarlabs.org:/pub/sugarlabs/sources/sucrose/glucose/$name/$name-$version # Verify the upload of the package @@ -34,16 +34,16 @@ for package in [sugar, sugar-toolkit, sugar-base, sugar-artwork]: http://download.sugarlabs.org/sources/sucrose/glucose/$name/$name-$version # Package sugar for Fedora -# - For announcements of the Sucrose release subscribe at the sugar-devel +# - For announcements of the Sucrose release subscribe at the sugar-devel # mailing list; you can filter for the [ANNOUNCE] tag -# - Uploaded tarballs can be found at: +# - Uploaded tarballs can be found at: # glucose: http://download.sugarlabs.org/sources/sucrose/glucose/$name/$name-$version # fructose: http://download.sugarlabs.org/sources/sucrose/fructose/$name/$name-$version # more about the taxonomy: http://sugarlabs.org/go/Taxonomy - -# more info on fedora packaging: + +# more info on fedora packaging: # http://fedoraproject.org/wiki/PackageMaintainers/UpdatingPackageHowTo -# request permissions to contribute to the fedora package: +# request permissions to contribute to the fedora package: # https://admin.fedoraproject.org/pkgdb/packages/name/[package] if not cvs_package: @@ -70,4 +70,4 @@ cvs commit -F clog make tag make build -# Do the same for the other branches e.g. devel +# Do the same for the other branches e.g. devel diff --git a/extensions/cpsection/aboutcomputer/__init__.py b/extensions/cpsection/aboutcomputer/__init__.py index ceb515a..faf814d 100644 --- a/extensions/cpsection/aboutcomputer/__init__.py +++ b/extensions/cpsection/aboutcomputer/__init__.py @@ -19,4 +19,3 @@ from gettext import gettext as _ CLASS = 'AboutComputer' ICON = 'module-about_my_computer' TITLE = _('About my Computer') - diff --git a/extensions/cpsection/aboutcomputer/model.py b/extensions/cpsection/aboutcomputer/model.py index 898d79c..3219dd1 100644 --- a/extensions/cpsection/aboutcomputer/model.py +++ b/extensions/cpsection/aboutcomputer/model.py @@ -22,31 +22,45 @@ import subprocess from gettext import gettext as _ import errno +import dbus + from jarabe import config + +_NM_SERVICE = 'org.freedesktop.NetworkManager' +_NM_PATH = '/org/freedesktop/NetworkManager' +_NM_IFACE = 'org.freedesktop.NetworkManager' +_NM_DEVICE_IFACE = 'org.freedesktop.NetworkManager.Device' +_NM_DEVICE_TYPE_WIFI = 2 + _logger = logging.getLogger('ControlPanel - AboutComputer') _not_available = _('Not available') + def get_aboutcomputer(): msg = 'Serial Number: %s \nBuild Number: %s \nFirmware Number: %s \n' \ % (get_serial_number(), get_build_number(), get_firmware_number()) return msg + def print_aboutcomputer(): print get_aboutcomputer() + def get_serial_number(): serial_no = _read_file('/ofw/serial-number') if serial_no is None: serial_no = _not_available return serial_no - + + def print_serial_number(): serial_no = get_serial_number() if serial_no is None: serial_no = _not_available print serial_no + def get_build_number(): build_no = _read_file('/boot/olpc_build') @@ -68,38 +82,88 @@ def get_build_number(): return build_no + def print_build_number(): print get_build_number() -def get_firmware_number(): + +def get_firmware_number(): firmware_no = _read_file('/ofw/openprom/model') if firmware_no is None: firmware_no = _not_available else: - firmware_no = re.split(" +", firmware_no) + firmware_no = re.split(' +', firmware_no) if len(firmware_no) == 3: firmware_no = firmware_no[1] - return firmware_no + return firmware_no + -def print_firmware_number(): +def print_firmware_number(): print get_firmware_number() -def get_wireless_firmware(): + +def _get_wireless_interfaces(): try: - info = subprocess.Popen(["/usr/sbin/ethtool", "-i", "eth0"], - stdout=subprocess.PIPE).stdout.readlines() - except OSError: + bus = dbus.SystemBus() + manager_object = bus.get_object(_NM_SERVICE, _NM_PATH) + network_manager = dbus.Interface(manager_object, _NM_IFACE) + except dbus.DBusException: + _logger.warning('Cannot connect to NetworkManager, falling back to' + ' static list of devices') + return ['wlan0', 'eth0'] + + interfaces = [] + for device_path in network_manager.GetDevices(): + device_object = bus.get_object(_NM_SERVICE, device_path) + properties = dbus.Interface(device_object, + 'org.freedesktop.DBus.Properties') + device_type = properties.Get(_NM_DEVICE_IFACE, 'DeviceType') + if device_type != _NM_DEVICE_TYPE_WIFI: + continue + + interfaces.append(properties.Get(_NM_DEVICE_IFACE, 'Interface')) + + return interfaces + + +def get_wireless_firmware(): + environment = os.environ.copy() + environment['PATH'] = '%s:/usr/sbin' % (environment['PATH'], ) + firmware_info = {} + for interface in _get_wireless_interfaces(): + try: + output = subprocess.Popen(['ethtool', '-i', interface], + stdout=subprocess.PIPE, + env=environment).stdout.readlines() + except OSError: + _logger.exception('Error running ethtool for %r', interface) + continue + + try: + version = ([line for line in output + if line.startswith('firmware')][0].split()[1]) + except IndexError: + _logger.exception('Error parsing ethtool output for %r', + interface) + continue + + firmware_info[interface] = version + + if not firmware_info: return _not_available - try: - wireless_firmware = [line for line in info - if line.startswith('firmware')][0].split()[1] - except IndexError: - wireless_firmware = _not_available - return wireless_firmware + + if len(firmware_info) == 1: + return firmware_info.values()[0] + + return ', '.join([_('%(interface)s: %(version)s') % + {'interface': interface, 'version': version} + for interface, version in firmware_info.items()]) + def print_wireless_firmware(): print get_wireless_firmware() + def _read_file(path): if os.access(path, os.R_OK) == 0: return None @@ -114,17 +178,18 @@ def _read_file(path): _logger.debug('No information in file or directory: %s', path) return None + def get_license(): license_file = os.path.join(config.data_path, 'GPLv2') lang = os.environ['LANG'] - if lang.endswith("UTF-8"): + if lang.endswith('UTF-8'): lang = lang[:-6] - try_file = license_file + "." + lang + try_file = license_file + '.' + lang if os.path.isfile(try_file): license_file = try_file else: - try_file = license_file + "." + lang.split("_")[0] + try_file = license_file + '.' + lang.split('_')[0] if os.path.isfile(try_file): license_file = try_file diff --git a/extensions/cpsection/aboutcomputer/view.py b/extensions/cpsection/aboutcomputer/view.py index dd4f8f3..e5f2f32 100644 --- a/extensions/cpsection/aboutcomputer/view.py +++ b/extensions/cpsection/aboutcomputer/view.py @@ -26,6 +26,7 @@ from sugar.graphics import style from jarabe import config from jarabe.controlpanel.sectionview import SectionView + class AboutComputer(SectionView): def __init__(self, model, alerts=None): SectionView.__init__(self) @@ -68,7 +69,7 @@ class AboutComputer(SectionView): box_identity = gtk.HBox(spacing=style.DEFAULT_SPACING) label_serial = gtk.Label(_('Serial Number:')) label_serial.set_alignment(1, 0) - label_serial.modify_fg(gtk.STATE_NORMAL, + label_serial.modify_fg(gtk.STATE_NORMAL, style.COLOR_SELECTION_GREY.get_gdk_color()) box_identity.pack_start(label_serial, expand=False) self._group.add_widget(label_serial) @@ -83,7 +84,7 @@ class AboutComputer(SectionView): self._vbox.pack_start(vbox_identity, expand=False) vbox_identity.show() - def _setup_software(self): + def _setup_software(self): separator_software = gtk.HSeparator() self._vbox.pack_start(separator_software, expand=False) separator_software.show() @@ -99,7 +100,7 @@ class AboutComputer(SectionView): box_build = gtk.HBox(spacing=style.DEFAULT_SPACING) label_build = gtk.Label(_('Build:')) label_build.set_alignment(1, 0) - label_build.modify_fg(gtk.STATE_NORMAL, + label_build.modify_fg(gtk.STATE_NORMAL, style.COLOR_SELECTION_GREY.get_gdk_color()) box_build.pack_start(label_build, expand=False) self._group.add_widget(label_build) @@ -130,7 +131,7 @@ class AboutComputer(SectionView): box_firmware = gtk.HBox(spacing=style.DEFAULT_SPACING) label_firmware = gtk.Label(_('Firmware:')) label_firmware.set_alignment(1, 0) - label_firmware.modify_fg(gtk.STATE_NORMAL, + label_firmware.modify_fg(gtk.STATE_NORMAL, style.COLOR_SELECTION_GREY.get_gdk_color()) box_firmware.pack_start(label_firmware, expand=False) self._group.add_widget(label_firmware) @@ -174,27 +175,30 @@ class AboutComputer(SectionView): vbox_copyright.set_border_width(style.DEFAULT_SPACING * 2) vbox_copyright.set_spacing(style.DEFAULT_SPACING) - label_copyright = gtk.Label("© 2006-2009 One Laptop per Child " - "Association Inc; Red Hat Inc; Collabora Ltd; " - "and Contributors.") + copyright_text = '© 2006-2011 One Laptop per Child Association Inc,' \ + ' Sugar Labs Inc, Red Hat Inc, Collabora Ltd and' \ + ' Contributors.' + label_copyright = gtk.Label(copyright_text) label_copyright.set_alignment(0, 0) + label_copyright.set_size_request(gtk.gdk.screen_width() / 2, -1) + label_copyright.set_line_wrap(True) label_copyright.show() vbox_copyright.pack_start(label_copyright, expand=False) - label_info = gtk.Label(_("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 change it and/or " - "distribute copies of it under certain " - "conditions described therein.")) + info_text = _('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' + ' change it and/or distribute copies of it under' + ' certain conditions described therein.') + label_info = gtk.Label(info_text) label_info.set_alignment(0, 0) label_info.set_line_wrap(True) label_info.set_size_request(gtk.gdk.screen_width() / 2, -1) label_info.show() vbox_copyright.pack_start(label_info, expand=False) - expander = gtk.Expander(_("Full license:")) - expander.connect("notify::expanded", self.license_expander_cb) + expander = gtk.Expander(_('Full license:')) + expander.connect('notify::expanded', self.license_expander_cb) expander.show() vbox_copyright.pack_start(expander, expand=True) diff --git a/extensions/cpsection/aboutme/__init__.py b/extensions/cpsection/aboutme/__init__.py index 98843e1..7ded428 100644 --- a/extensions/cpsection/aboutme/__init__.py +++ b/extensions/cpsection/aboutme/__init__.py @@ -24,5 +24,3 @@ ICON = 'module-about_me' TITLE = _('About Me') client = gconf.client_get_default() COLOR = XoColor(client.get_string('/desktop/sugar/user/color')) - - diff --git a/extensions/cpsection/aboutme/model.py b/extensions/cpsection/aboutme/model.py index 8500799..fb4c2f1 100644 --- a/extensions/cpsection/aboutme/model.py +++ b/extensions/cpsection/aboutme/model.py @@ -18,38 +18,45 @@ from gettext import gettext as _ import gconf -_COLORS = {'red': {'dark':'#b20008', 'medium':'#e6000a', 'light':'#ffadce'}, - 'orange': {'dark':'#9a5200', 'medium':'#c97e00', 'light':'#ffc169'}, - 'yellow': {'dark':'#807500', 'medium':'#be9e00', 'light':'#fffa00'}, - 'green': {'dark':'#008009', 'medium':'#00b20d', 'light':'#8bff7a'}, - 'blue': {'dark':'#00588c', 'medium':'#005fe4', 'light':'#bccdff'}, - 'purple': {'dark':'#5e008c', 'medium':'#7f00bf', 'light':'#d1a3ff'} - } + +_COLORS = { + 'red': {'dark': '#b20008', 'medium': '#e6000a', 'light': '#ffadce'}, + 'orange': {'dark': '#9a5200', 'medium': '#c97e00', 'light': '#ffc169'}, + 'yellow': {'dark': '#807500', 'medium': '#be9e00', 'light': '#fffa00'}, + 'green': {'dark': '#008009', 'medium': '#00b20d', 'light': '#8bff7a'}, + 'blue': {'dark': '#00588c', 'medium': '#005fe4', 'light': '#bccdff'}, + 'purple': {'dark': '#5e008c', 'medium': '#7f00bf', 'light': '#d1a3ff'}, +} _MODIFIERS = ('dark', 'medium', 'light') - + + def get_nick(): client = gconf.client_get_default() - return client.get_string("/desktop/sugar/user/nick") + return client.get_string('/desktop/sugar/user/nick') + def print_nick(): print get_nick() - + + def set_nick(nick): """Set the nickname. nick : e.g. 'walter' """ if not nick: - raise ValueError(_("You must enter a name.")) + raise ValueError(_('You must enter a name.')) if not isinstance(nick, unicode): nick = unicode(nick, 'utf-8') client = gconf.client_get_default() - client.set_string("/desktop/sugar/user/nick", nick) + client.set_string('/desktop/sugar/user/nick', nick) return 1 + def get_color(): client = gconf.client_get_default() - return client.get_string("/desktop/sugar/user/color") + return client.get_string('/desktop/sugar/user/color') + def print_color(): color_string = get_color() @@ -64,16 +71,17 @@ def print_color(): if _COLORS[color][hue] == tmp[1]: fill_tuple = (color, hue) - if stroke_tuple is not None: - print _('stroke: color=%s hue=%s') % (stroke_tuple[0], + if stroke_tuple is not None: + print _('stroke: color=%s hue=%s') % (stroke_tuple[0], stroke_tuple[1]) else: - print _('stroke: %s') % (tmp[0]) - if fill_tuple is not None: + print _('stroke: %s') % (tmp[0]) + if fill_tuple is not None: print _('fill: color=%s hue=%s') % (fill_tuple[0], fill_tuple[1]) else: print _('fill: %s') % (tmp[1]) - + + def set_color(stroke, fill, stroke_modifier='medium', fill_modifier='medium'): """Set the system color by setting a fill and stroke color. fill : [red, orange, yellow, blue, green, purple] @@ -81,35 +89,37 @@ def set_color(stroke, fill, stroke_modifier='medium', fill_modifier='medium'): hue stroke : [dark, medium, light] (optional) hue fill : [dark, medium, light] (optional) """ - + if stroke_modifier not in _MODIFIERS or fill_modifier not in _MODIFIERS: - print (_("Error in specified color modifiers.")) + print (_('Error in specified color modifiers.')) return if stroke not in _COLORS or fill not in _COLORS: - print (_("Error in specified colors.")) + print (_('Error in specified colors.')) return - + if stroke_modifier == fill_modifier: if fill_modifier == 'medium': fill_modifier = 'light' else: fill_modifier = 'medium' - + color = _COLORS[stroke][stroke_modifier] + ',' \ + _COLORS[fill][fill_modifier] client = gconf.client_get_default() - client.set_string("/desktop/sugar/user/color", color) + client.set_string('/desktop/sugar/user/color', color) return 1 + def get_color_xo(): client = gconf.client_get_default() - return client.get_string("/desktop/sugar/user/color") + return client.get_string('/desktop/sugar/user/color') + def set_color_xo(color): - """Set a color with an XoColor + """Set a color with an XoColor This method is used by the graphical user interface """ client = gconf.client_get_default() - client.set_string("/desktop/sugar/user/color", color) + client.set_string('/desktop/sugar/user/color', color) return 1 diff --git a/extensions/cpsection/aboutme/view.py b/extensions/cpsection/aboutme/view.py index cabd66a..84daec7 100644 --- a/extensions/cpsection/aboutme/view.py +++ b/extensions/cpsection/aboutme/view.py @@ -1,4 +1,5 @@ # Copyright (C) 2008, OLPC +# Copyright (C) 2010, 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 @@ -20,18 +21,104 @@ from gettext import gettext as _ from sugar.graphics.icon import Icon from sugar.graphics import style -from sugar.graphics.xocolor import XoColor +from sugar.graphics.xocolor import XoColor, colors from jarabe.controlpanel.sectionview import SectionView from jarabe.controlpanel.inlinealert import InlineAlert +_STROKE_COLOR = 0 +_FILL_COLOR = 1 + + +def _get_next_stroke_color(color): + """ Return the next color pair in the list that shares the same fill + as color. """ + current_index = _get_current_index(color) + if current_index == -1: + return '%s,%s' % (color.stroke, color.fill) + next_index = _next_index(current_index) + while(colors[next_index][_FILL_COLOR] != \ + colors[current_index][_FILL_COLOR]): + next_index = _next_index(next_index) + return '%s,%s' % (colors[next_index][_STROKE_COLOR], + colors[next_index][_FILL_COLOR]) + + +def _get_previous_stroke_color(color): + """ Return the previous color pair in the list that shares the same fill + as color. """ + current_index = _get_current_index(color) + if current_index == -1: + return '%s,%s' % (color.stroke, color.fill) + previous_index = _previous_index(current_index) + while (colors[previous_index][_FILL_COLOR] != \ + colors[current_index][_FILL_COLOR]): + previous_index = _previous_index(previous_index) + return '%s,%s' % (colors[previous_index][_STROKE_COLOR], + colors[previous_index][_FILL_COLOR]) + + +def _get_next_fill_color(color): + """ Return the next color pair in the list that shares the same stroke + as color. """ + current_index = _get_current_index(color) + if current_index == -1: + return '%s,%s' % (color.stroke, color.fill) + next_index = _next_index(current_index) + while (colors[next_index][_STROKE_COLOR] != \ + colors[current_index][_STROKE_COLOR]): + next_index = _next_index(next_index) + return '%s,%s' % (colors[next_index][_STROKE_COLOR], + colors[next_index][_FILL_COLOR]) + + +def _get_previous_fill_color(color): + """ Return the previous color pair in the list that shares the same stroke + as color. """ + current_index = _get_current_index(color) + if current_index == -1: + return '%s,%s' % (color.stroke, color.fill) + previous_index = _previous_index(current_index) + while (colors[previous_index][_STROKE_COLOR] != \ + colors[current_index][_STROKE_COLOR]): + previous_index = _previous_index(previous_index) + return '%s,%s' % (colors[previous_index][_STROKE_COLOR], + colors[previous_index][_FILL_COLOR]) + + +def _next_index(current_index): + next_index = current_index + 1 + if next_index == len(colors): + next_index = 0 + return next_index + + +def _previous_index(current_index): + previous_index = current_index - 1 + if previous_index < 0: + previous_index = len(colors) - 1 + return previous_index + + +def _get_current_index(color): + return colors.index([color.stroke, color.fill]) + + +_PREVIOUS_FILL_COLOR = 0 +_NEXT_FILL_COLOR = 1 +_CURRENT_COLOR = 2 +_NEXT_STROKE_COLOR = 3 +_PREVIOUS_STROKE_COLOR = 4 + + class EventIcon(gtk.EventBox): - __gtype_name__ = "SugarEventIcon" - def __init__(self, **kwargs): + __gtype_name__ = 'SugarEventIcon' + + def __init__(self, **kwargs): gtk.EventBox.__init__(self) - self.icon = Icon(pixel_size = style.XLARGE_ICON_SIZE, **kwargs) - + self.icon = Icon(pixel_size=style.XLARGE_ICON_SIZE, **kwargs) + self.set_visible_window(False) self.set_app_paintable(True) self.set_events(gtk.gdk.BUTTON_PRESS_MASK) @@ -39,28 +126,45 @@ class EventIcon(gtk.EventBox): self.add(self.icon) self.icon.show() + class ColorPicker(EventIcon): __gsignals__ = { 'color-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([str])) - } - def __init__(self, xocolor=None): + ([object])), + } + + def __init__(self, picker): EventIcon.__init__(self) - self.icon.props.xo_color = xocolor + self.icon.props.icon_name = 'computer-xo' + self._picker = picker + self._color = None + self.icon.props.pixel_size = style.XLARGE_ICON_SIZE - self.connect('button_press_event', self.__pressed_cb) - def __pressed_cb(self, button, event): - self._set_random_colors() + self.connect('button_press_event', self.__pressed_cb, picker) + + def update(self, color): + if self._picker == _PREVIOUS_FILL_COLOR: + self._color = XoColor(_get_previous_fill_color(color)) + elif self._picker == _PREVIOUS_STROKE_COLOR: + self._color = XoColor(_get_previous_stroke_color(color)) + elif self._picker == _NEXT_FILL_COLOR: + self._color = XoColor(_get_next_fill_color(color)) + elif self._picker == _NEXT_STROKE_COLOR: + self._color = XoColor(_get_next_stroke_color(color)) + else: + self._color = color + self.icon.props.xo_color = self._color + + def __pressed_cb(self, button, event, picker): + if picker != _CURRENT_COLOR: + self.emit('color-changed', self._color) - def _set_random_colors(self): - xocolor = XoColor() - self.icon.props.xo_color = xocolor - self.emit('color-changed', xocolor.to_string()) class AboutMe(SectionView): + def __init__(self, model, alerts): SectionView.__init__(self) @@ -69,44 +173,44 @@ class AboutMe(SectionView): self._nick_sid = 0 self._color_valid = True self._nick_valid = True - self._color_change_handler = None - self._nick_change_handler = None self.set_border_width(style.DEFAULT_SPACING * 2) self.set_spacing(style.DEFAULT_SPACING) self._group = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL) + self._color_label = gtk.HBox(spacing=style.DEFAULT_SPACING) + self._color_box = gtk.HBox(spacing=style.DEFAULT_SPACING) + self._color_alert_box = gtk.HBox(spacing=style.DEFAULT_SPACING) + self._color_alert = None + + self._pickers = { + _PREVIOUS_FILL_COLOR: ColorPicker(_PREVIOUS_FILL_COLOR), + _NEXT_FILL_COLOR: ColorPicker(_NEXT_FILL_COLOR), + _CURRENT_COLOR: ColorPicker(_CURRENT_COLOR), + _NEXT_STROKE_COLOR: ColorPicker(_NEXT_STROKE_COLOR), + _PREVIOUS_STROKE_COLOR: ColorPicker(_PREVIOUS_STROKE_COLOR), + } + + self._setup_color() + initial_color = XoColor(self._model.get_color_xo()) + self._update_pickers(initial_color) + self._nick_box = gtk.HBox(spacing=style.DEFAULT_SPACING) self._nick_alert_box = gtk.HBox(spacing=style.DEFAULT_SPACING) self._nick_entry = None self._nick_alert = None self._setup_nick() - - self._color_box = gtk.HBox(spacing=style.DEFAULT_SPACING) - self._color_alert_box = gtk.HBox(spacing=style.DEFAULT_SPACING) - self._color_picker = None - self._color_alert = None - self._setup_color() - self.setup() def _setup_nick(self): - label_entry = gtk.Label(_('Name:')) - label_entry.modify_fg(gtk.STATE_NORMAL, - style.COLOR_SELECTION_GREY.get_gdk_color()) - self._group.add_widget(label_entry) - label_entry.set_alignment(1, 0.5) - self._nick_box.pack_start(label_entry, expand=False) - label_entry.show() - - self._nick_entry = gtk.Entry() - self._nick_entry.modify_bg(gtk.STATE_INSENSITIVE, + self._nick_entry = gtk.Entry() + self._nick_entry.modify_bg(gtk.STATE_INSENSITIVE, style.COLOR_WHITE.get_gdk_color()) - self._nick_entry.modify_base(gtk.STATE_INSENSITIVE, + self._nick_entry.modify_base(gtk.STATE_INSENSITIVE, style.COLOR_WHITE.get_gdk_color()) self._nick_entry.set_width_chars(25) self._nick_box.pack_start(self._nick_entry, expand=False) - self._nick_entry.show() + self._nick_entry.show() label_entry_error = gtk.Label() self._group.add_widget(label_entry_error) @@ -119,22 +223,36 @@ class AboutMe(SectionView): self._nick_alert.props.msg = self.restart_msg self._nick_alert.show() - self.pack_start(self._nick_box, False) + self._center_in_panel = gtk.Alignment(0.5) + self._center_in_panel.add(self._nick_box) + self.pack_start(self._center_in_panel, False) self.pack_start(self._nick_alert_box, False) self._nick_box.show() self._nick_alert_box.show() - - def _setup_color(self): + self._center_in_panel.show() + + def _setup_color(self): label_color = gtk.Label(_('Click to change your color:')) - label_color.modify_fg(gtk.STATE_NORMAL, + label_color.modify_fg(gtk.STATE_NORMAL, style.COLOR_SELECTION_GREY.get_gdk_color()) self._group.add_widget(label_color) - self._color_box.pack_start(label_color, expand=False) + self._color_label.pack_start(label_color, expand=False) label_color.show() - - self._color_picker = ColorPicker() - self._color_box.pack_start(self._color_picker, expand=False) - self._color_picker.show() + + for picker_index in sorted(self._pickers.keys()): + if picker_index == _CURRENT_COLOR: + left_separator = gtk.SeparatorToolItem() + left_separator.show() + self._color_box.pack_start(left_separator, expand=False) + + picker = self._pickers[picker_index] + picker.show() + self._color_box.pack_start(picker, expand=False) + + if picker_index == _CURRENT_COLOR: + right_separator = gtk.SeparatorToolItem() + right_separator.show() + self._color_box.pack_start(right_separator, expand=False) label_color_error = gtk.Label() self._group.add_widget(label_color_error) @@ -147,30 +265,34 @@ class AboutMe(SectionView): self._color_alert.props.msg = self.restart_msg self._color_alert.show() - self.pack_start(self._color_box, False) - self.pack_start(self._color_alert_box, False) + self._center_in_panel = gtk.Alignment(0.5) + self._center_in_panel.add(self._color_box) + self.pack_start(self._color_label, False) + self.pack_start(self._center_in_panel, False) + self.pack_start(self._color_alert_box, False) + self._color_label.show() self._color_box.show() self._color_alert_box.show() - + self._center_in_panel.show() + def setup(self): self._nick_entry.set_text(self._model.get_nick()) - color = XoColor(self._model.get_color_xo()) - self._color_picker.icon.props.xo_color = color - self._color_valid = True self._nick_valid = True self.needs_restart = False - self._nick_change_handler = self._nick_entry.connect( \ - 'changed', self.__nick_changed_cb) - self._color_change_handler = self._color_picker.connect( \ - 'color-changed', self.__color_changed_cb) + + self._nick_entry.connect('changed', self.__nick_changed_cb) + for picker in self._pickers.values(): + picker.connect('color-changed', self.__color_changed_cb) def undo(self): - self._color_picker.disconnect(self._color_change_handler) - self._nick_entry.disconnect(self._nick_change_handler) self._model.undo() self._nick_alert.hide() - self._color_alert.hide() + self._color_alert.hide() + + def _update_pickers(self, color): + for picker in self._pickers.values(): + picker.update(color) def _validate(self): if self._nick_valid and self._color_valid: @@ -178,13 +300,13 @@ class AboutMe(SectionView): else: self.props.is_valid = False - def __nick_changed_cb(self, widget, data=None): + def __nick_changed_cb(self, widget, data=None): if self._nick_sid: gobject.source_remove(self._nick_sid) - self._nick_sid = gobject.timeout_add(self._APPLY_TIMEOUT, + self._nick_sid = gobject.timeout_add(self._APPLY_TIMEOUT, self.__nick_timeout_cb, widget) - def __nick_timeout_cb(self, widget): + def __nick_timeout_cb(self, widget): self._nick_sid = 0 if widget.get_text() == self._model.get_nick(): @@ -193,18 +315,18 @@ class AboutMe(SectionView): self._model.set_nick(widget.get_text()) except ValueError, detail: self._nick_alert.props.msg = detail - self._nick_valid = False + self._nick_valid = False else: self._nick_alert.props.msg = self.restart_msg - self._nick_valid = True + self._nick_valid = True self.needs_restart = True self.restart_alerts.append('nick') self._validate() self._nick_alert.show() return False - def __color_changed_cb(self, colorpicker, xocolor): - self._model.set_color_xo(xocolor) + def __color_changed_cb(self, colorpicker, color): + self._model.set_color_xo(color.to_string()) self.needs_restart = True self._color_alert.props.msg = self.restart_msg self._color_valid = True @@ -212,4 +334,7 @@ class AboutMe(SectionView): self._validate() self._color_alert.show() + + self._update_pickers(color) + return False diff --git a/extensions/cpsection/datetime/model.py b/extensions/cpsection/datetime/model.py index 76064e4..84e1259 100644 --- a/extensions/cpsection/datetime/model.py +++ b/extensions/cpsection/datetime/model.py @@ -26,18 +26,20 @@ import gconf _zone_tab = '/usr/share/zoneinfo/zone.tab' + def _initialize(): - '''Initialize the docstring of the set function''' + """Initialize the docstring of the set function""" if set_timezone.__doc__ is None: # when running under 'python -OO', all __doc__ fields are None, # so += would fail -- and this function would be unnecessary anyway. return - timezones = read_all_timezones() + timezones = read_all_timezones() for timezone in timezones: - set_timezone.__doc__ += timezone + '\n' - + set_timezone.__doc__ += timezone + '\n' + + def read_all_timezones(fn=_zone_tab): - fd = open (fn, 'r') + fd = open(fn, 'r') lines = fd.readlines() fd.close() timezones = [] @@ -48,7 +50,7 @@ def read_all_timezones(fn=_zone_tab): if len(line) > 1: timezones.append(line[2]) timezones.sort() - + for offset in xrange(-12, 13): if offset < 0: tz = 'GMT%d' % offset @@ -56,7 +58,7 @@ def read_all_timezones(fn=_zone_tab): tz = 'GMT+%d' % offset else: tz = 'GMT' - timezones.append(tz) + timezones.append(tz) for offset in xrange(-12, 13): if offset < 0: tz = 'UTC%d' % offset @@ -64,16 +66,19 @@ def read_all_timezones(fn=_zone_tab): tz = 'UTC+%d' % offset else: tz = 'UTC' - timezones.append(tz) + timezones.append(tz) return timezones + def get_timezone(): client = gconf.client_get_default() return client.get_string('/desktop/sugar/date/timezone') + def print_timezone(): print get_timezone() + def set_timezone(timezone): """Set the system timezone timezone : e.g. 'America/Los_Angeles' @@ -84,9 +89,8 @@ def set_timezone(timezone): client = gconf.client_get_default() client.set_string('/desktop/sugar/date/timezone', timezone) else: - raise ValueError(_("Error timezone does not exist.")) + raise ValueError(_('Error timezone does not exist.')) return 1 -# inilialize the docstrings for the timezone +# inilialize the docstrings for the timezone _initialize() - diff --git a/extensions/cpsection/datetime/view.py b/extensions/cpsection/datetime/view.py index 58719b4..1cef78f 100644 --- a/extensions/cpsection/datetime/view.py +++ b/extensions/cpsection/datetime/view.py @@ -24,36 +24,38 @@ from sugar.graphics import iconentry from jarabe.controlpanel.sectionview import SectionView from jarabe.controlpanel.inlinealert import InlineAlert + class TimeZone(SectionView): def __init__(self, model, alerts): SectionView.__init__(self) self._model = model self.restart_alerts = alerts - self._zone_sid = 0 + self._zone_sid = 0 self._cursor_change_handler = None self.set_border_width(style.DEFAULT_SPACING * 2) self.set_spacing(style.DEFAULT_SPACING) - self.connect("realize", self.__realize_cb) + self.connect('realize', self.__realize_cb) self._entry = iconentry.IconEntry() self._entry.set_icon_from_name(iconentry.ICON_ENTRY_PRIMARY, 'system-search') self._entry.add_clear_button() - self._entry.modify_bg(gtk.STATE_INSENSITIVE, + self._entry.modify_bg(gtk.STATE_INSENSITIVE, style.COLOR_WHITE.get_gdk_color()) - self._entry.modify_base(gtk.STATE_INSENSITIVE, - style.COLOR_WHITE.get_gdk_color()) + self._entry.modify_base(gtk.STATE_INSENSITIVE, + style.COLOR_WHITE.get_gdk_color()) self.pack_start(self._entry, False) - self._entry.show() + self._entry.show() self._scrolled_window = gtk.ScrolledWindow() - self._scrolled_window.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) + self._scrolled_window.set_policy(gtk.POLICY_NEVER, + gtk.POLICY_AUTOMATIC) self._scrolled_window.set_shadow_type(gtk.SHADOW_IN) - self._store = gtk.ListStore(gobject.TYPE_STRING) + self._store = gtk.ListStore(gobject.TYPE_STRING) zones = model.read_all_timezones() for zone in zones: self._store.append([zone]) @@ -91,16 +93,16 @@ class TimeZone(SectionView): zone = self._model.get_timezone() for row in self._store: if zone == row[0]: - self._treeview.set_cursor(row.path, self._timezone_column, + self._treeview.set_cursor(row.path, self._timezone_column, False) - self._treeview.scroll_to_cell(row.path, self._timezone_column, - True, 0.5, 0.5) + self._treeview.scroll_to_cell(row.path, self._timezone_column, + True, 0.5, 0.5) break - - self.needs_restart = False + + self.needs_restart = False self._cursor_change_handler = self._treeview.connect( \ - "cursor-changed", self.__zone_changed_cd) - + 'cursor-changed', self.__zone_changed_cd) + def undo(self): self._treeview.disconnect(self._cursor_change_handler) self._model.undo() @@ -121,18 +123,18 @@ class TimeZone(SectionView): return False if self._model.get_timezone() == self._store.get_value(row, 0): return False - + if self._zone_sid: gobject.source_remove(self._zone_sid) - self._zone_sid = gobject.timeout_add(self._APPLY_TIMEOUT, + self._zone_sid = gobject.timeout_add(self._APPLY_TIMEOUT, self.__zone_timeout_cb, row) return True - def __zone_timeout_cb(self, row): - self._zone_sid = 0 + def __zone_timeout_cb(self, row): + self._zone_sid = 0 self._model.set_timezone(self._store.get_value(row, 0)) self.restart_alerts.append('zone') - self.needs_restart = True + self.needs_restart = True self._zone_alert.props.msg = self.restart_msg self._zone_alert.show() return False diff --git a/extensions/cpsection/frame/model.py b/extensions/cpsection/frame/model.py index 9eea9ad..4796062 100644 --- a/extensions/cpsection/frame/model.py +++ b/extensions/cpsection/frame/model.py @@ -17,47 +17,53 @@ from gettext import gettext as _ import gconf - + + def get_corner_delay(): client = gconf.client_get_default() corner_delay = client.get_int('/desktop/sugar/frame/corner_delay') return corner_delay + def print_corner_delay(): print get_corner_delay() - + + def set_corner_delay(delay): """Set a delay for the activation of the frame using hot corners. instantaneous: 0 (0 milliseconds) - delay: 100 (100 milliseconds) + delay: 100 (100 milliseconds) never: 1000 (disable activation) """ try: int(delay) - except ValueError: - raise ValueError(_("Value must be an integer.")) + except ValueError: + raise ValueError(_('Value must be an integer.')) client = gconf.client_get_default() client.set_int('/desktop/sugar/frame/corner_delay', int(delay)) return 0 - + + def get_edge_delay(): client = gconf.client_get_default() edge_delay = client.get_int('/desktop/sugar/frame/edge_delay') return edge_delay + def print_edge_delay(): print get_edge_delay() - + + def set_edge_delay(delay): - """Set a delay for the activation of the frame using warm edges. + """Set a delay for the activation of the frame using warm edges. instantaneous: 0 (0 milliseconds) - delay: 100 (100 milliseconds) + delay: 100 (100 milliseconds) never: 1000 (disable activation) """ try: int(delay) - except ValueError: - raise ValueError(_("Value must be an integer.")) + except ValueError: + raise ValueError(_('Value must be an integer.')) client = gconf.client_get_default() client.set_int('/desktop/sugar/frame/edge_delay', int(delay)) return 0 diff --git a/extensions/cpsection/frame/view.py b/extensions/cpsection/frame/view.py index cbe43bb..8b4ab83 100644 --- a/extensions/cpsection/frame/view.py +++ b/extensions/cpsection/frame/view.py @@ -23,11 +23,13 @@ from sugar.graphics import style from jarabe.controlpanel.sectionview import SectionView from jarabe.controlpanel.inlinealert import InlineAlert -_never = _('never') + +_never = _('never') _instantaneous = _('instantaneous') _seconds_label = _('%s seconds') _MAX_DELAY = 1000 + class Frame(SectionView): def __init__(self, model, alerts): SectionView.__init__(self) @@ -43,7 +45,7 @@ class Frame(SectionView): self.set_border_width(style.DEFAULT_SPACING * 2) self.set_spacing(style.DEFAULT_SPACING) - self._group = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL) + self._group = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL) separator = gtk.HSeparator() self.pack_start(separator, expand=False) @@ -67,25 +69,25 @@ class Frame(SectionView): self._setup_edge() self.pack_start(self._box_sliders, expand=False) - self._box_sliders.show() + self._box_sliders.show() self.setup() - def _setup_corner(self): + def _setup_corner(self): box_delay = gtk.HBox(spacing=style.DEFAULT_SPACING) label_delay = gtk.Label(_('Corner')) label_delay.set_alignment(1, 0.75) - label_delay.modify_fg(gtk.STATE_NORMAL, + label_delay.modify_fg(gtk.STATE_NORMAL, style.COLOR_SELECTION_GREY.get_gdk_color()) box_delay.pack_start(label_delay, expand=False) self._group.add_widget(label_delay) - label_delay.show() - - adj = gtk.Adjustment(value=100, lower=0, upper=_MAX_DELAY, + label_delay.show() + + adj = gtk.Adjustment(value=100, lower=0, upper=_MAX_DELAY, step_incr=100, page_incr=100, page_size=0) self._corner_delay_slider = gtk.HScale(adj) self._corner_delay_slider.set_digits(0) - self._corner_delay_slider.connect('format-value', + self._corner_delay_slider.connect('format-value', self.__corner_delay_format_cb) box_delay.pack_start(self._corner_delay_slider) self._corner_delay_slider.show() @@ -105,22 +107,22 @@ class Frame(SectionView): if 'corner_delay' in self.restart_alerts: self._corner_delay_alert.props.msg = self.restart_msg self._corner_delay_alert.show() - - def _setup_edge(self): + + def _setup_edge(self): box_delay = gtk.HBox(spacing=style.DEFAULT_SPACING) label_delay = gtk.Label(_('Edge')) label_delay.set_alignment(1, 0.75) - label_delay.modify_fg(gtk.STATE_NORMAL, + label_delay.modify_fg(gtk.STATE_NORMAL, style.COLOR_SELECTION_GREY.get_gdk_color()) box_delay.pack_start(label_delay, expand=False) self._group.add_widget(label_delay) - label_delay.show() - - adj = gtk.Adjustment(value=100, lower=0, upper=_MAX_DELAY, + label_delay.show() + + adj = gtk.Adjustment(value=100, lower=0, upper=_MAX_DELAY, step_incr=100, page_incr=100, page_size=0) self._edge_delay_slider = gtk.HScale(adj) self._edge_delay_slider.set_digits(0) - self._edge_delay_slider.connect('format-value', + self._edge_delay_slider.connect('format-value', self.__edge_delay_format_cb) box_delay.pack_start(self._edge_delay_slider) self._edge_delay_slider.show() @@ -140,24 +142,24 @@ class Frame(SectionView): if 'edge_delay' in self.restart_alerts: self._edge_delay_alert.props.msg = self.restart_msg self._edge_delay_alert.show() - + def setup(self): self._corner_delay_slider.set_value(self._model.get_corner_delay()) self._edge_delay_slider.set_value(self._model.get_edge_delay()) self._corner_delay_is_valid = True self._edge_delay_is_valid = True self.needs_restart = False - self._corner_delay_change_handler = self._corner_delay_slider.connect( \ + self._corner_delay_change_handler = self._corner_delay_slider.connect( 'value-changed', self.__corner_delay_changed_cb) - self._edge_delay_change_handler = self._edge_delay_slider.connect( \ + self._edge_delay_change_handler = self._edge_delay_slider.connect( 'value-changed', self.__edge_delay_changed_cb) - - def undo(self): + + def undo(self): self._corner_delay_slider.disconnect(self._corner_delay_change_handler) self._edge_delay_slider.disconnect(self._edge_delay_change_handler) self._model.undo() - self._corner_delay_alert.hide() - self._edge_delay_alert.hide() + self._corner_delay_alert.hide() + self._edge_delay_alert.hide() def _validate(self): if self._edge_delay_is_valid and self._corner_delay_is_valid: @@ -165,15 +167,15 @@ class Frame(SectionView): else: self.props.is_valid = False - def __corner_delay_changed_cb(self, scale, data=None): + def __corner_delay_changed_cb(self, scale, data=None): if self._corner_delay_sid: gobject.source_remove(self._corner_delay_sid) self._corner_delay_sid = gobject.timeout_add( \ self._APPLY_TIMEOUT, self.__corner_delay_timeout_cb, scale) - - def __corner_delay_timeout_cb(self, scale): + + def __corner_delay_timeout_cb(self, scale): self._corner_delay_sid = 0 - if scale.get_value() == self._model.get_corner_delay(): + if scale.get_value() == self._model.get_corner_delay(): return try: self._model.set_corner_delay(scale.get_value()) @@ -182,12 +184,12 @@ class Frame(SectionView): self._corner_delay_is_valid = False else: self._corner_delay_alert.props.msg = self.restart_msg - self._corner_delay_is_valid = True + self._corner_delay_is_valid = True self.needs_restart = True self.restart_alerts.append('corner_delay') - + self._validate() - self._corner_delay_alert.show() + self._corner_delay_alert.show() return False def __corner_delay_format_cb(self, scale, value): @@ -198,15 +200,15 @@ class Frame(SectionView): else: return _seconds_label % (value / _MAX_DELAY) - def __edge_delay_changed_cb(self, scale, data=None): + def __edge_delay_changed_cb(self, scale, data=None): if self._edge_delay_sid: gobject.source_remove(self._edge_delay_sid) self._edge_delay_sid = gobject.timeout_add( \ self._APPLY_TIMEOUT, self.__edge_delay_timeout_cb, scale) - - def __edge_delay_timeout_cb(self, scale): + + def __edge_delay_timeout_cb(self, scale): self._edge_delay_sid = 0 - if scale.get_value() == self._model.get_edge_delay(): + if scale.get_value() == self._model.get_edge_delay(): return try: self._model.set_edge_delay(scale.get_value()) @@ -215,12 +217,12 @@ class Frame(SectionView): self._edge_delay_is_valid = False else: self._edge_delay_alert.props.msg = self.restart_msg - self._edge_delay_is_valid = True + self._edge_delay_is_valid = True self.needs_restart = True self.restart_alerts.append('edge_delay') - + self._validate() - self._edge_delay_alert.show() + self._edge_delay_alert.show() return False def __edge_delay_format_cb(self, scale, value): diff --git a/extensions/cpsection/keyboard/__init__.py b/extensions/cpsection/keyboard/__init__.py index 568e7a5..065086c 100644 --- a/extensions/cpsection/keyboard/__init__.py +++ b/extensions/cpsection/keyboard/__init__.py @@ -19,4 +19,3 @@ from gettext import gettext as _ CLASS = 'Keyboard' ICON = 'module-keyboard' TITLE = _('Keyboard') - diff --git a/extensions/cpsection/keyboard/model.py b/extensions/cpsection/keyboard/model.py index 9d61c0c..1f92973 100644 --- a/extensions/cpsection/keyboard/model.py +++ b/extensions/cpsection/keyboard/model.py @@ -20,12 +20,13 @@ import xklavier import gconf -_GROUP_NAME = 'grp' # The XKB name for group switch options +_GROUP_NAME = 'grp' # The XKB name for group switch options _LAYOUTS_KEY = '/desktop/sugar/peripherals/keyboard/layouts' _OPTIONS_KEY = '/desktop/sugar/peripherals/keyboard/options' _MODEL_KEY = '/desktop/sugar/peripherals/keyboard/model' + class KeyboardManager(object): def __init__(self, display): self._engine = xklavier.Engine(display) @@ -33,7 +34,7 @@ class KeyboardManager(object): self._configregistry.load(False) self._configrec = xklavier.ConfigRec() self._configrec.get_from_server(self._engine) - + self._gconf_client = gconf.client_get_default() def _populate_one(self, config_registry, item, store): @@ -48,7 +49,7 @@ class KeyboardManager(object): else: description = 'Default layout, %s' % item.get_description() variant = '' - + store.append([description, ('%s(%s)' % (layout, variant))]) def get_models(self): @@ -76,8 +77,8 @@ class KeyboardManager(object): def get_options_group(self): """Return list of supported options for switching keyboard group""" options = [] - self._configregistry.foreach_option(_GROUP_NAME, self._populate_one, \ - options) + self._configregistry.foreach_option(_GROUP_NAME, self._populate_one, + options) options.sort() return options @@ -96,7 +97,7 @@ class KeyboardManager(object): layouts = self._gconf_client.get_list(_LAYOUTS_KEY, gconf.VALUE_STRING) if layouts: return layouts - + layouts = self._configrec.get_layouts() variants = self._configrec.get_variants() @@ -126,7 +127,7 @@ class KeyboardManager(object): return option return None - + def get_max_layouts(self): """Return the maximum number of layouts supported simultaneously""" return self._engine.get_max_num_groups() @@ -142,8 +143,10 @@ class KeyboardManager(object): def set_option_group(self, option_group): """Sets the supplied option for switching keyboard group""" #XXX: Merge, not overwrite previous options - if option_group is None or not option_group: + if not option_group: options = [''] + elif isinstance(option_group, list): + options = option_group else: options = [option_group] self._gconf_client.set_list(_OPTIONS_KEY, gconf.VALUE_STRING, options) @@ -160,8 +163,7 @@ class KeyboardManager(object): for layout in layouts: layouts_list.append(layout.split('(')[0]) variants_list.append(layout.split('(')[1][:-1]) - + self._configrec.set_layouts(layouts_list) self._configrec.set_variants(variants_list) - self._configrec.activate(self._engine) - + self._configrec.activate(self._engine) diff --git a/extensions/cpsection/keyboard/view.py b/extensions/cpsection/keyboard/view.py index dd62a85..e349255 100644 --- a/extensions/cpsection/keyboard/view.py +++ b/extensions/cpsection/keyboard/view.py @@ -26,6 +26,7 @@ from sugar.graphics.icon import Icon from jarabe.controlpanel.sectionview import SectionView + CLASS = 'Language' ICON = 'module-keyboard' TITLE = _('Keyboard') @@ -37,6 +38,7 @@ _APPLY_TIMEOUT = 500 # once python-xklavier has been packaged for all major distributions # For more information, see: http://dev.sugarlabs.org/ticket/407 + class LayoutCombo(gtk.HBox): """ @@ -45,8 +47,8 @@ class LayoutCombo(gtk.HBox): """ __gsignals__ = { - 'selection-changed' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, - (gobject.TYPE_STRING, gobject.TYPE_INT)) + 'selection-changed': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, + (gobject.TYPE_STRING, gobject.TYPE_INT)), } def __init__(self, keyboard_manager, n): @@ -57,7 +59,7 @@ class LayoutCombo(gtk.HBox): self.set_border_width(style.DEFAULT_SPACING) self.set_spacing(style.DEFAULT_SPACING) - label = gtk.Label(' <b>%s</b> ' % str(n+1)) + label = gtk.Label(' <b>%s</b> ' % str(n + 1)) label.set_use_markup(True) label.modify_fg(gtk.STATE_NORMAL, style.COLOR_SELECTION_GREY.get_gdk_color()) @@ -69,7 +71,7 @@ class LayoutCombo(gtk.HBox): for description, name in self._keyboard_manager.get_languages(): self._klang_store.append([name, description]) - self._klang_combo = gtk.ComboBox(model = self._klang_store) + self._klang_combo = gtk.ComboBox(model=self._klang_store) self._klang_combo_changed_id = \ self._klang_combo.connect('changed', self._klang_combo_changed_cb) cell = gtk.CellRendererText() @@ -77,10 +79,10 @@ class LayoutCombo(gtk.HBox): cell.props.ellipsize_set = True self._klang_combo.pack_start(cell) self._klang_combo.add_attribute(cell, 'text', 1) - self.pack_start(self._klang_combo, expand=True, fill = True) + self.pack_start(self._klang_combo, expand=True, fill=True) self._kvariant_store = None - self._kvariant_combo = gtk.ComboBox(model = None) + self._kvariant_combo = gtk.ComboBox(model=None) self._kvariant_combo_changed_id = \ self._kvariant_combo.connect('changed', \ self._kvariant_combo_changed_cb) @@ -89,7 +91,7 @@ class LayoutCombo(gtk.HBox): cell.props.ellipsize_set = True self._kvariant_combo.pack_start(cell) self._kvariant_combo.add_attribute(cell, 'text', 1) - self.pack_start(self._kvariant_combo, expand=True, fill = True) + self.pack_start(self._kvariant_combo, expand=True, fill=True) self._klang_combo.set_active(self._index) @@ -129,7 +131,7 @@ class LayoutCombo(gtk.HBox): model = combobox.get_model() lang = model.get(it, 0)[0] self._set_kvariant_store(lang) - + def _kvariant_combo_changed_cb(self, combobox): it = combobox.get_active_iter() model = combobox.get_model() @@ -145,18 +147,17 @@ class Keyboard(SectionView): self._kmodel = None self._selected_kmodel = None - + self._klayouts = [] self._selected_klayouts = [] - + self._group_switch_option = None self._selected_group_switch_option = None self.set_border_width(style.DEFAULT_SPACING * 2) self.set_spacing(style.DEFAULT_SPACING) - - self._layout_table = gtk.Table(rows = 4, columns = 2, \ - homogeneous = False) + + self._layout_table = gtk.Table(rows=4, columns=2, homogeneous=False) self._keyboard_manager = model.KeyboardManager(self.get_display()) self._layout_combo_list = [] @@ -169,7 +170,7 @@ class Keyboard(SectionView): self._vbox = gtk.VBox() scrollwindow.add_with_viewport(self._vbox) - + self.__kmodel_sid = None self.__layout_sid = None self.__group_switch_sid = None @@ -177,7 +178,7 @@ class Keyboard(SectionView): self._setup_kmodel() self._setup_layouts() self._setup_group_switch_option() - + self._vbox.show() def _setup_kmodel(self): @@ -200,7 +201,7 @@ class Keyboard(SectionView): for description, name in self._keyboard_manager.get_models(): kmodel_store.append([name, description]) - kmodel_combo = gtk.ComboBox(model = kmodel_store) + kmodel_combo = gtk.ComboBox(model=kmodel_store) cell = gtk.CellRendererText() cell.props.ellipsize = pango.ELLIPSIZE_MIDDLE cell.props.ellipsize_set = True @@ -214,7 +215,7 @@ class Keyboard(SectionView): kmodel_combo.set_active_iter(row.iter) break - box_kmodel.pack_start(kmodel_combo, expand = False) + box_kmodel.pack_start(kmodel_combo, expand=False) self._vbox.pack_start(box_kmodel, expand=False) box_kmodel.show_all() @@ -223,7 +224,7 @@ class Keyboard(SectionView): def __kmodel_changed_cb(self, combobox): if self.__kmodel_sid is not None: gobject.source_remove(self.__kmodel_sid) - self.__kmodel_sid = gobject.timeout_add(_APPLY_TIMEOUT, + self.__kmodel_sid = gobject.timeout_add(_APPLY_TIMEOUT, self.__kmodel_timeout_cb, combobox) def __kmodel_timeout_cb(self, combobox): @@ -240,7 +241,8 @@ class Keyboard(SectionView): return False def _setup_group_switch_option(self): - """Adds the controls for changing the group switch option of keyboard""" + """Adds the controls for changing the group switch option of keyboard + """ separator_group_option = gtk.HSeparator() self._vbox.pack_start(separator_group_option, expand=False) separator_group_option.show_all() @@ -259,7 +261,7 @@ class Keyboard(SectionView): for description, name in self._keyboard_manager.get_options_group(): group_option_store.append([name, description]) - group_option_combo = gtk.ComboBox(model = group_option_store) + group_option_combo = gtk.ComboBox(model=group_option_store) cell = gtk.CellRendererText() cell.props.ellipsize = pango.ELLIPSIZE_MIDDLE cell.props.ellipsize_set = True @@ -280,7 +282,7 @@ class Keyboard(SectionView): if not found: group_option_combo.set_active(0) - box_group_option.pack_start(group_option_combo, expand = False) + box_group_option.pack_start(group_option_combo, expand=False) self._vbox.pack_start(box_group_option, expand=False) box_group_option.show_all() @@ -290,7 +292,7 @@ class Keyboard(SectionView): def __group_switch_changed_cb(self, combobox): if self.__group_switch_sid is not None: gobject.source_remove(self.__group_switch_sid) - self.__group_switch_sid = gobject.timeout_add(_APPLY_TIMEOUT, + self.__group_switch_sid = gobject.timeout_add(_APPLY_TIMEOUT, self.__group_switch_timeout_cb, combobox) def __group_switch_timeout_cb(self, combobox): @@ -306,7 +308,6 @@ class Keyboard(SectionView): except Exception: logging.exception('Could not set new keyboard group switch option') - return False def _setup_layouts(self): @@ -324,18 +325,18 @@ class Keyboard(SectionView): for i in range(0, self._keyboard_manager.get_max_layouts()): add_remove_box = self.__create_add_remove_box() self._layout_addremovebox_list.append(add_remove_box) - self._layout_table.attach(add_remove_box, 1, 2, i, i+1) + self._layout_table.attach(add_remove_box, 1, 2, i, i + 1) layout_combo = LayoutCombo(self._keyboard_manager, i) layout_combo.connect('selection-changed', \ self.__layout_combo_selection_changed_cb) self._layout_combo_list.append(layout_combo) - self._layout_table.attach(layout_combo, 0, 1, i, i+1) + self._layout_table.attach(layout_combo, 0, 1, i, i + 1) if i < len(self._klayouts): layout_combo.show_all() layout_combo.select_layout(self._klayouts[i]) - + self._vbox.pack_start(self._layout_table, expand=False) self._layout_table.set_size_request(self._vbox.size_request()[0], -1) self._layout_table.show() @@ -359,15 +360,15 @@ class Keyboard(SectionView): i += 1 def __create_add_remove_box(self): - '''Creates gtk.Hbox with add/remove buttons''' - add_icon = Icon(icon_name='list-add') + """Creates gtk.Hbox with add/remove buttons""" + add_icon = Icon(icon_name='list-add') add_button = gtk.Button() add_button.set_image(add_icon) add_button.connect('clicked', self.__add_button_clicked_cb) - remove_icon = Icon(icon_name='list-remove') + remove_icon = Icon(icon_name='list-remove') remove_button = gtk.Button() remove_button.set_image(remove_icon) remove_button.connect('clicked', @@ -387,7 +388,7 @@ class Keyboard(SectionView): def __add_button_clicked_cb(self, button): self._layout_combo_list[len(self._selected_klayouts)].show_all() self._update_klayouts() - + def __remove_button_clicked_cb(self, button): self._layout_combo_list[len(self._selected_klayouts) - 1].hide() self._update_klayouts() @@ -403,7 +404,7 @@ class Keyboard(SectionView): if self.__layout_sid is not None: gobject.source_remove(self.__layout_sid) - self.__layout_sid = gobject.timeout_add(_APPLY_TIMEOUT, + self.__layout_sid = gobject.timeout_add(_APPLY_TIMEOUT, self.__layout_timeout_cb) def __layout_timeout_cb(self): @@ -417,10 +418,8 @@ class Keyboard(SectionView): return False - def undo(self): """Reverts back to the original keyboard configuration""" self._keyboard_manager.set_model(self._kmodel) self._keyboard_manager.set_layouts(self._klayouts) self._keyboard_manager.set_option_group(self._group_switch_option) - diff --git a/extensions/cpsection/language/__init__.py b/extensions/cpsection/language/__init__.py index a8f9f08..c93b7d1 100644 --- a/extensions/cpsection/language/__init__.py +++ b/extensions/cpsection/language/__init__.py @@ -19,4 +19,3 @@ from gettext import gettext as _ CLASS = 'Language' ICON = 'module-language' TITLE = _('Language') - diff --git a/extensions/cpsection/language/model.py b/extensions/cpsection/language/model.py index fe80410..240e562 100644 --- a/extensions/cpsection/language/model.py +++ b/extensions/cpsection/language/model.py @@ -21,11 +21,14 @@ # import os +import locale from gettext import gettext as _ import subprocess -_default_lang = 'en_US.utf8' -_standard_msg = _("Could not access ~/.i18n. Create standard settings.") + +_default_lang = '%s.%s' % locale.getdefaultlocale() +_standard_msg = _('Could not access ~/.i18n. Create standard settings.') + def read_all_languages(): fdp = subprocess.Popen(['locale', '-av'], stdout=subprocess.PIPE) @@ -42,7 +45,7 @@ def read_all_languages(): if locale.endswith('utf8') and len(lang): locales.append((lang, territory, locale)) - #FIXME: This is a temporary workaround for locales that are essential to + #FIXME: This is a temporary workaround for locales that are essential to # OLPC, but are not in Glibc yet. locales.append(('Kreyol', 'Haiti', 'ht_HT.utf8')) locales.append(('Dari', 'Afghanistan', 'fa_AF.utf8')) @@ -51,7 +54,8 @@ def read_all_languages(): locales.sort() return locales -def _initialize(): + +def _initialize(): if set_languages.__doc__ is None: # when running under 'python -OO', all __doc__ fields are None, # so += would fail -- and this function would be unnecessary anyway. @@ -59,13 +63,12 @@ def _initialize(): languages = read_all_languages() set_languages.__doc__ += '\n' for lang in languages: - set_languages.__doc__ += '%s \n' % (lang[0].replace(' ', '_') + '/' + + set_languages.__doc__ += '%s \n' % (lang[0].replace(' ', '_') + '/' + lang[1].replace(' ', '_')) - -def _write_i18n(langs): - colon = ':' - langstr = colon.join(langs) - path = os.path.join(os.environ.get("HOME"), '.i18n') + + +def _write_i18n(lang_env, language_env): + path = os.path.join(os.environ.get('HOME'), '.i18n') if not os.access(path, os.W_OK): print _standard_msg fd = open(path, 'w') @@ -74,32 +77,33 @@ def _write_i18n(langs): fd.close() else: fd = open(path, 'w') - fd.write('LANG="%s"\n' % langs[0].strip("\n")) - fd.write('LANGUAGE="%s"\n' % langstr) + fd.write('LANG="%s"\n' % lang_env) + fd.write('LANGUAGE="%s"\n' % language_env) fd.close() + def get_languages(): - path = os.path.join(os.environ.get("HOME"), '.i18n') + path = os.path.join(os.environ.get('HOME', ''), '.i18n') if not os.access(path, os.R_OK): - print _standard_msg + print _standard_msg fd = open(path, 'w') fd.write('LANG="%s"\n' % _default_lang) fd.write('LANGUAGE="%s"\n' % _default_lang) fd.close() return [_default_lang] - - fd = open(path, "r") + + fd = open(path, 'r') lines = fd.readlines() fd.close() langlist = None for line in lines: - if line.startswith("LANGUAGE="): + if line.startswith('LANGUAGE='): lang = line[9:].replace('"', '') lang = lang.strip() langlist = lang.split(':') - elif line.startswith("LANG="): + elif line.startswith('LANG='): lang = line[5:].replace('"', '') # There might be cases where .i18n may not contain a LANGUAGE field @@ -108,6 +112,7 @@ def get_languages(): else: return langlist + def print_languages(): codes = get_languages() @@ -121,30 +126,35 @@ def print_languages(): found_lang = True break if not found_lang: - print (_("Language for code=%s could not be determined.") % code) - + print (_('Language for code=%s could not be determined.') % code) + + def set_languages(languages): """Set the system language. - languages : + languages : """ - if isinstance(languages, str): - # This came from the commandline - #TODO: Support multiple languages from the command line - if languages.endswith('utf8'): - _write_i18n(languages) - return 1 - else: - langs = read_all_languages() - for lang, territory, locale in langs: - code = lang.replace(' ', '_') + '/' \ - + territory.replace(' ', '_') - if code == languages: - _write_i18n(locale) - return 1 - print (_("Sorry I do not speak \'%s\'.") % languages) + + if languages.endswith('utf8'): + set_languages_list([languages]) + return 1 else: - _write_i18n(languages) + langs = read_all_languages() + for lang, territory, locale in langs: + code = lang.replace(' ', '_') + '/' \ + + territory.replace(' ', '_') + if code == languages: + set_languages_list([locale]) + return 1 + print (_("Sorry I do not speak \'%s\'.") % languages) + + +def set_languages_list(languages): + """Set the system language using a list of preferred languages""" + colon = ':' + language_env = colon.join(languages) + lang_env = languages[0].strip('\n') + _write_i18n(lang_env, language_env) + # inilialize the docstrings for the language _initialize() - diff --git a/extensions/cpsection/language/view.py b/extensions/cpsection/language/view.py index d1a49cf..1553959 100644 --- a/extensions/cpsection/language/view.py +++ b/extensions/cpsection/language/view.py @@ -25,13 +25,14 @@ from sugar.graphics.icon import Icon from jarabe.controlpanel.sectionview import SectionView from jarabe.controlpanel.inlinealert import InlineAlert -_translate_language = lambda msg: gettext.dgettext('iso_639', msg) +_translate_language = lambda msg: gettext.dgettext('iso_639', msg) _translate_country = lambda msg: gettext.dgettext('iso_3166', msg) CLASS = 'Language' ICON = 'module-language' TITLE = gettext.gettext('Language') + class Language(SectionView): def __init__(self, model, alerts): SectionView.__init__(self) @@ -53,9 +54,9 @@ 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.") + 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_width_chars(100) self._text.set_line_wrap(True) @@ -69,14 +70,14 @@ class Language(SectionView): self.pack_start(scrolled, expand=True) self._table = gtk.Table(rows=1, columns=3, homogeneous=False) - self._table.set_border_width(style.DEFAULT_SPACING * 2) + self._table.set_border_width(style.DEFAULT_SPACING * 2) self._table.show() scrolled.add_with_viewport(self._table) self._lang_alert_box = gtk.HBox(spacing=style.DEFAULT_SPACING) self.pack_start(self._lang_alert_box, False) - self._lang_alert = InlineAlert() + self._lang_alert = InlineAlert() self._lang_alert_box.pack_start(self._lang_alert) if 'lang' in self.restart_alerts: self._lang_alert.props.msg = self.restart_msg @@ -86,10 +87,10 @@ class Language(SectionView): self.setup() def _add_row(self, locale_code=None): - '''Adds a row to the table''' + """Adds a row to the table""" self._selected_lang_count += 1 - + self._table.resize(self._selected_lang_count, 3) label = gtk.Label(str=str(self._selected_lang_count)) @@ -98,8 +99,7 @@ class Language(SectionView): self._labels.append(label) self._attach_to_table(label, 0, 1, padding=1) label.show() - - + store = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING) for language, country, code in self._available_locales: description = '%s (%s)' % (_translate_language(language), \ @@ -115,7 +115,7 @@ class Language(SectionView): for row in store: lang = locale_code.split('.')[0] lang_column = row[0].split('.')[0] - if lang in lang_column: + if lang in lang_column: combobox.set_active_iter(row.iter) break else: @@ -132,7 +132,7 @@ class Language(SectionView): self._attach_to_table(add_remove_box, 2, 3) add_remove_box.show_all() - + if self._selected_lang_count > 1: previous_add_removes = self._add_remove_boxes[-2] previous_add_removes.hide_all() @@ -149,12 +149,12 @@ class Language(SectionView): ypadding=padding) def _delete_last_row(self): - '''Deletes the last row of the table''' + """Deletes the last row of the table""" self._selected_lang_count -= 1 label, add_remove_box, combobox, store_ = self._get_last_row() - + label.destroy() add_remove_box.destroy() combobox.destroy() @@ -180,15 +180,15 @@ class Language(SectionView): self._lang_alert.hide() def _create_add_remove_box(self): - '''Creates gtk.Hbox with add/remove buttons''' - add_icon = Icon(icon_name='list-add') + """Creates gtk.Hbox with add/remove buttons""" + add_icon = Icon(icon_name='list-add') add_button = gtk.Button() add_button.set_image(add_icon) add_button.connect('clicked', self.__add_button_clicked_cb) - remove_icon = Icon(icon_name='list-remove') + remove_icon = Icon(icon_name='list-remove') remove_button = gtk.Button() remove_button.set_image(remove_icon) remove_button.connect('clicked', @@ -217,8 +217,8 @@ class Language(SectionView): selected_langs = self._get_selected_langs() last_lang = selected_langs[-1] - self._determine_add_remove_visibility(last_lang = last_lang) - + self._determine_add_remove_visibility(last_lang=last_lang) + self._changed = (selected_langs != self._selected_locales) if self._changed == False: @@ -245,10 +245,10 @@ class Language(SectionView): model = combobox.get_model() lang_code = model.get(it, 0)[0] new_codes.append(lang_code) - + return new_codes - def _determine_add_remove_visibility(self, last_lang = None): + def _determine_add_remove_visibility(self, last_lang=None): # We should not let users add fallback languages for English (USA) # This is because the software is not usually _translated_ into English # which means that the fallback gets selected automatically @@ -256,11 +256,11 @@ class Language(SectionView): if last_lang is None: selected_langs = self._get_selected_langs() last_lang = selected_langs[-1] - + add_remove_box = self._add_remove_boxes[-1] buttons = add_remove_box.get_children() add_button, remove_button = buttons - + if last_lang.startswith('en_US'): add_button.props.visible = False else: @@ -271,10 +271,9 @@ class Language(SectionView): else: remove_button.props.visible = True - def __lang_timeout_cb(self, codes): self._lang_sid = 0 - self._model.set_languages(codes) + self._model.set_languages_list(codes) self.restart_alerts.append('lang') self.needs_restart = True self._lang_alert.props.msg = self.restart_msg diff --git a/extensions/cpsection/modemconfiguration/__init__.py b/extensions/cpsection/modemconfiguration/__init__.py index 8a219dc..61f5904 100644 --- a/extensions/cpsection/modemconfiguration/__init__.py +++ b/extensions/cpsection/modemconfiguration/__init__.py @@ -19,4 +19,3 @@ from gettext import gettext as _ CLASS = 'ModemConfiguration' ICON = 'module-modemconfiguration' TITLE = _('Modem Configuration') - diff --git a/extensions/cpsection/modemconfiguration/model.py b/extensions/cpsection/modemconfiguration/model.py index 2545ce1..1e83c44 100755 --- a/extensions/cpsection/modemconfiguration/model.py +++ b/extensions/cpsection/modemconfiguration/model.py @@ -20,51 +20,62 @@ from jarabe.model.network import GSM_USERNAME_PATH, GSM_PASSWORD_PATH, \ GSM_NUMBER_PATH, GSM_APN_PATH, GSM_PIN_PATH, \ GSM_PUK_PATH + def get_username(): client = gconf.client_get_default() return client.get_string(GSM_USERNAME_PATH) or '' + def get_password(): client = gconf.client_get_default() return client.get_string(GSM_PASSWORD_PATH) or '' + def get_number(): client = gconf.client_get_default() return client.get_string(GSM_NUMBER_PATH) or '' + def get_apn(): client = gconf.client_get_default() return client.get_string(GSM_APN_PATH) or '' + def get_pin(): client = gconf.client_get_default() return client.get_string(GSM_PIN_PATH) or '' + def get_puk(): client = gconf.client_get_default() return client.get_string(GSM_PUK_PATH) or '' + def set_username(username): client = gconf.client_get_default() client.set_string(GSM_USERNAME_PATH, username) + def set_password(password): client = gconf.client_get_default() client.set_string(GSM_PASSWORD_PATH, password) + def set_number(number): client = gconf.client_get_default() client.set_string(GSM_NUMBER_PATH, number) + def set_apn(apn): client = gconf.client_get_default() client.set_string(GSM_APN_PATH, apn) + def set_pin(pin): client = gconf.client_get_default() client.set_string(GSM_PIN_PATH, pin) + def set_puk(puk): client = gconf.client_get_default() client.set_string(GSM_PUK_PATH, puk) - diff --git a/extensions/cpsection/modemconfiguration/view.py b/extensions/cpsection/modemconfiguration/view.py index b236f3f..c31edba 100644 --- a/extensions/cpsection/modemconfiguration/view.py +++ b/extensions/cpsection/modemconfiguration/view.py @@ -25,10 +25,12 @@ from sugar.graphics import style from jarabe.controlpanel.sectionview import SectionView + APPLY_TIMEOUT = 1000 + class EntryWithLabel(gtk.HBox): - __gtype_name__ = "SugarEntryWithLabel" + __gtype_name__ = 'SugarEntryWithLabel' def __init__(self, label_text): gtk.HBox.__init__(self, spacing=style.DEFAULT_SPACING) @@ -53,7 +55,7 @@ class EntryWithLabel(gtk.HBox): def __entry_changed_cb(self, widget, data=None): if self._timeout_sid: gobject.source_remove(self._timeout_sid) - self._timeout_sid = gobject.timeout_add(APPLY_TIMEOUT, + self._timeout_sid = gobject.timeout_add(APPLY_TIMEOUT, self.__timeout_cb) def __timeout_cb(self): @@ -63,7 +65,7 @@ class EntryWithLabel(gtk.HBox): return False try: - self.set_value(self._entry.get_text()) + self.set_value(self._entry.get_text()) except ValueError: self._is_valid = False else: @@ -74,18 +76,19 @@ class EntryWithLabel(gtk.HBox): return False def set_text_from_model(self): - self._entry.set_text(self.get_value()) + self._entry.set_text(self.get_value()) def get_value(self): raise NotImplementedError def set_value(self): - raise NotImplementedError + raise NotImplementedError def _get_is_valid(self): return self._is_valid is_valid = gobject.property(type=bool, getter=_get_is_valid, default=True) + class UsernameEntry(EntryWithLabel): def __init__(self, model): EntryWithLabel.__init__(self, _('Username:')) @@ -97,6 +100,7 @@ class UsernameEntry(EntryWithLabel): def set_value(self, username): self._model.set_username(username) + class PasswordEntry(EntryWithLabel): def __init__(self, model): EntryWithLabel.__init__(self, _('Password:')) @@ -108,6 +112,7 @@ class PasswordEntry(EntryWithLabel): def set_value(self, password): self._model.set_password(password) + class NumberEntry(EntryWithLabel): def __init__(self, model): EntryWithLabel.__init__(self, _('Number:')) @@ -119,6 +124,7 @@ class NumberEntry(EntryWithLabel): def set_value(self, number): self._model.set_number(number) + class ApnEntry(EntryWithLabel): def __init__(self, model): EntryWithLabel.__init__(self, _('Access Point Name (APN):')) @@ -130,6 +136,7 @@ class ApnEntry(EntryWithLabel): def set_value(self, apn): self._model.set_apn(apn) + class PinEntry(EntryWithLabel): def __init__(self, model): EntryWithLabel.__init__(self, _('Personal Identity Number (PIN):')) @@ -141,6 +148,7 @@ class PinEntry(EntryWithLabel): def set_value(self, pin): self._model.set_pin(pin) + class PukEntry(EntryWithLabel): def __init__(self, model): EntryWithLabel.__init__(self, _('Personal Unblocking Key (PUK):')) @@ -164,10 +172,9 @@ class ModemConfiguration(SectionView): self.set_spacing(style.DEFAULT_SPACING) self._group = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL) - explanation = _("You will need to provide the following " \ - "information to set up a mobile " \ - "broadband connection to a cellular "\ - "(3G) network.") + explanation = _('You will need to provide the following information' + ' to set up a mobile broadband connection to a' + ' cellular (3G) network.') self._text = gtk.Label(explanation) self._text.set_width_chars(100) self._text.set_line_wrap(True) @@ -209,12 +216,12 @@ class ModemConfiguration(SectionView): self._group.add_widget(self._pin_entry.label) self.pack_start(self._pin_entry, expand=False) self._pin_entry.show() - + self._puk_entry = PukEntry(model) self._puk_entry.connect('notify::is-valid', self.__notify_is_valid_cb) self._group.add_widget(self._puk_entry.label) - self.pack_start(self._puk_entry, expand=False) + self.pack_start(self._puk_entry, expand=False) self._puk_entry.show() self.setup() @@ -247,4 +254,3 @@ class ModemConfiguration(SectionView): if entry.is_valid: self.needs_restart = True self._validate() - diff --git a/extensions/cpsection/network/__init__.py b/extensions/cpsection/network/__init__.py index 8fea274..86546f7 100644 --- a/extensions/cpsection/network/__init__.py +++ b/extensions/cpsection/network/__init__.py @@ -20,6 +20,3 @@ CLASS = 'Network' ICON = 'module-network' TITLE = _('Network') KEYWORDS = ['network', 'jabber', 'radio', 'server'] - - - diff --git a/extensions/cpsection/network/model.py b/extensions/cpsection/network/model.py index e1c3dab..916ce8c 100644 --- a/extensions/cpsection/network/model.py +++ b/extensions/cpsection/network/model.py @@ -19,25 +19,33 @@ import dbus from gettext import gettext as _ import gconf +from jarabe.model import network + + _NM_SERVICE = 'org.freedesktop.NetworkManager' _NM_PATH = '/org/freedesktop/NetworkManager' _NM_IFACE = 'org.freedesktop.NetworkManager' KEYWORDS = ['network', 'jabber', 'radio', 'server'] + class ReadError(Exception): def __init__(self, value): self.value = value + def __str__(self): return repr(self.value) + def get_jabber(): client = gconf.client_get_default() return client.get_string('/desktop/sugar/collaboration/jabber_server') + def print_jabber(): print get_jabber() + def set_jabber(server): """Set the jabber server server : e.g. 'olpc.collabora.co.uk' @@ -45,30 +53,14 @@ def set_jabber(server): client = gconf.client_get_default() client.set_string('/desktop/sugar/collaboration/jabber_server', server) - _restart_jabber() return 0 -def _restart_jabber(): - """Call Sugar Presence Service to restart Telepathy CMs. - - This allows restarting the jabber server connection when we change it. - """ - _PS_SERVICE = "org.laptop.Sugar.Presence" - _PS_INTERFACE = "org.laptop.Sugar.Presence" - _PS_PATH = "/org/laptop/Sugar/Presence" - bus = dbus.SessionBus() - try: - ps = dbus.Interface(bus.get_object(_PS_SERVICE, _PS_PATH), - _PS_INTERFACE) - except dbus.DBusException: - raise ReadError('%s service not available' % _PS_SERVICE) - ps.RestartServerConnection() def get_radio(): try: bus = dbus.SystemBus() obj = bus.get_object(_NM_SERVICE, _NM_PATH) - nm_props = dbus.Interface(obj, 'org.freedesktop.DBus.Properties') + nm_props = dbus.Interface(obj, dbus.PROPERTIES_IFACE) except dbus.DBusException: raise ReadError('%s service not available' % _NM_SERVICE) @@ -78,18 +70,20 @@ def get_radio(): else: raise ReadError(_('State is unknown.')) + def print_radio(): print ('off', 'on')[get_radio()] - + + def set_radio(state): """Turn Radio 'on' or 'off' state : 'on/off' - """ + """ if state == 'on' or state == 1: try: bus = dbus.SystemBus() obj = bus.get_object(_NM_SERVICE, _NM_PATH) - nm_props = dbus.Interface(obj, 'org.freedesktop.DBus.Properties') + nm_props = dbus.Interface(obj, dbus.PROPERTIES_IFACE) except dbus.DBusException: raise ReadError('%s service not available' % _NM_SERVICE) nm_props.Set(_NM_IFACE, 'WirelessEnabled', True) @@ -97,15 +91,16 @@ def set_radio(state): try: bus = dbus.SystemBus() obj = bus.get_object(_NM_SERVICE, _NM_PATH) - nm_props = dbus.Interface(obj, 'org.freedesktop.DBus.Properties') + nm_props = dbus.Interface(obj, dbus.PROPERTIES_IFACE) except dbus.DBusException: raise ReadError('%s service not available' % _NM_SERVICE) nm_props.Set(_NM_IFACE, 'WirelessEnabled', False) else: - raise ValueError(_("Error in specified radio argument use on/off.")) + raise ValueError(_('Error in specified radio argument use on/off.')) return 0 + def clear_registration(): """Clear the registration with the schoolserver """ @@ -113,28 +108,36 @@ def clear_registration(): client.set_string('/desktop/sugar/backup_url', '') return 1 + def clear_networks(): """Clear saved passwords and network configurations. """ - pass + network.clear_wifi_connections() + + +def have_networks(): + return network.have_wifi_connections() + def get_publish_information(): client = gconf.client_get_default() publish = client.get_bool('/desktop/sugar/collaboration/publish_gadget') return publish - + + def print_publish_information(): print get_publish_information() + def set_publish_information(value): - """ If set to true, Sugar will make you searchable for + """ If set to true, Sugar will make you searchable for the other users of the Jabber server. value: 0/1 """ try: value = (False, True)[int(value)] except: - raise ValueError(_("Error in specified argument use 0/1.")) + raise ValueError(_('Error in specified argument use 0/1.')) client = gconf.client_get_default() client.set_bool('/desktop/sugar/collaboration/publish_gadget', value) diff --git a/extensions/cpsection/network/view.py b/extensions/cpsection/network/view.py index 588daeb..381dcb6 100644 --- a/extensions/cpsection/network/view.py +++ b/extensions/cpsection/network/view.py @@ -23,18 +23,20 @@ from sugar.graphics import style from jarabe.controlpanel.sectionview import SectionView from jarabe.controlpanel.inlinealert import InlineAlert + CLASS = 'Network' ICON = 'module-network' TITLE = _('Network') _APPLY_TIMEOUT = 3000 + class Network(SectionView): def __init__(self, model, alerts): SectionView.__init__(self) - self._model = model - self.restart_alerts = alerts + self._model = model + self.restart_alerts = alerts self._jabber_sid = 0 self._jabber_valid = True self._radio_valid = True @@ -64,8 +66,8 @@ class Network(SectionView): box_wireless.set_border_width(style.DEFAULT_SPACING * 2) box_wireless.set_spacing(style.DEFAULT_SPACING) - radio_info = gtk.Label(_("Turn off the wireless radio to save " - "battery life")) + radio_info = gtk.Label(_('Turn off the wireless radio to save battery' + ' life')) radio_info.set_alignment(0, 0) radio_info.set_line_wrap(True) radio_info.show() @@ -93,8 +95,8 @@ class Network(SectionView): self._radio_alert.props.msg = self.restart_msg self._radio_alert.show() - history_info = gtk.Label(_("Discard network history if you " - "have trouble connecting to the network")) + history_info = gtk.Label(_('Discard network history if you have' + ' trouble connecting to the network')) history_info.set_alignment(0, 0) history_info.set_line_wrap(True) history_info.show() @@ -104,6 +106,8 @@ class Network(SectionView): self._clear_history_button = gtk.Button() self._clear_history_button.set_label(_('Discard network history')) box_clear_history.pack_start(self._clear_history_button, expand=False) + if not self._model.have_networks(): + self._clear_history_button.set_sensitive(False) self._clear_history_button.show() box_wireless.pack_start(box_clear_history, expand=False) box_clear_history.show() @@ -135,24 +139,24 @@ class Network(SectionView): box_server = gtk.HBox(spacing=style.DEFAULT_SPACING) label_server = gtk.Label(_('Server:')) label_server.set_alignment(1, 0.5) - label_server.modify_fg(gtk.STATE_NORMAL, + label_server.modify_fg(gtk.STATE_NORMAL, style.COLOR_SELECTION_GREY.get_gdk_color()) box_server.pack_start(label_server, expand=False) group.add_widget(label_server) - label_server.show() + label_server.show() self._entry = gtk.Entry() self._entry.set_alignment(0) - self._entry.modify_bg(gtk.STATE_INSENSITIVE, + self._entry.modify_bg(gtk.STATE_INSENSITIVE, style.COLOR_WHITE.get_gdk_color()) - self._entry.modify_base(gtk.STATE_INSENSITIVE, - style.COLOR_WHITE.get_gdk_color()) + self._entry.modify_base(gtk.STATE_INSENSITIVE, + style.COLOR_WHITE.get_gdk_color()) self._entry.set_size_request(int(gtk.gdk.screen_width() / 3), -1) box_server.pack_start(self._entry, expand=False) - self._entry.show() + self._entry.show() box_mesh.pack_start(box_server, expand=False) box_server.show() - - self._jabber_alert = InlineAlert() + + self._jabber_alert = InlineAlert() label_jabber_error = gtk.Label() group.add_widget(label_jabber_error) self._jabber_alert_box.pack_start(label_jabber_error, expand=False) @@ -176,13 +180,13 @@ class Network(SectionView): self.setup() def setup(self): - self._entry.set_text(self._model.get_jabber()) - try: - radio_state = self._model.get_radio() + self._entry.set_text(self._model.get_jabber()) + try: + radio_state = self._model.get_radio() except self._model.ReadError, detail: - self._radio_alert.props.msg = detail + self._radio_alert.props.msg = detail self._radio_alert.show() - else: + else: self._button.set_active(radio_state) self._jabber_valid = True @@ -195,13 +199,13 @@ class Network(SectionView): self._network_configuration_reset_handler = \ self._clear_history_button.connect( \ 'clicked', self.__network_configuration_reset_cb) - + def undo(self): self._button.disconnect(self._radio_change_handler) self._entry.disconnect(self._jabber_change_handler) self._model.undo() self._jabber_alert.hide() - self._radio_alert.hide() + self._radio_alert.hide() def _validate(self): if self._jabber_valid and self._radio_valid: @@ -209,7 +213,7 @@ class Network(SectionView): else: self.props.is_valid = False - def __radio_toggled_cb(self, widget, data=None): + def __radio_toggled_cb(self, widget, data=None): radio_state = widget.get_active() try: self._model.set_radio(radio_state) @@ -217,18 +221,21 @@ class Network(SectionView): self._radio_alert.props.msg = detail self._radio_valid = False else: - self._radio_valid = True + self._radio_valid = True + if self._model.have_networks(): + self._clear_history_button.set_sensitive(True) self._validate() return False - def __jabber_changed_cb(self, widget, data=None): + def __jabber_changed_cb(self, widget, data=None): if self._jabber_sid: gobject.source_remove(self._jabber_sid) - self._jabber_sid = gobject.timeout_add(_APPLY_TIMEOUT, - self.__jabber_timeout_cb, widget) - - def __jabber_timeout_cb(self, widget): + self._jabber_sid = gobject.timeout_add(_APPLY_TIMEOUT, + self.__jabber_timeout_cb, + widget) + + def __jabber_timeout_cb(self, widget): self._jabber_sid = 0 if widget.get_text() == self._model.get_jabber: return @@ -240,11 +247,15 @@ class Network(SectionView): self._jabber_alert.show() self.restart_alerts.append('jabber') else: - self._jabber_valid = True + self._jabber_valid = True self._jabber_alert.hide() self._validate() return False def __network_configuration_reset_cb(self, widget): + # FIXME: takes effect immediately, not after CP is closed with + # confirmation button self._model.clear_networks() + if not self._model.have_networks(): + self._clear_history_button.set_sensitive(False) diff --git a/extensions/cpsection/power/__init__.py b/extensions/cpsection/power/__init__.py index 8b2e85f..35f7efd 100644 --- a/extensions/cpsection/power/__init__.py +++ b/extensions/cpsection/power/__init__.py @@ -20,4 +20,3 @@ CLASS = 'Power' ICON = 'module-power' TITLE = _('Power') KEYWORDS = ['automatic', 'extreme', 'power', 'suspend', 'battery'] - diff --git a/extensions/cpsection/power/model.py b/extensions/cpsection/power/model.py index 48d05de..041e5cf 100644 --- a/extensions/cpsection/power/model.py +++ b/extensions/cpsection/power/model.py @@ -35,14 +35,17 @@ _logger = logging.getLogger('ControlPanel - Power') class ReadError(Exception): def __init__(self, value): self.value = value + def __str__(self): return repr(self.value) + def using_powerd(): # directory exists if powerd running, and it's recent # enough to be controllable. return os.access(POWERD_FLAG_DIR, os.W_OK) + def get_automatic_pm(): if using_powerd(): return not os.access(POWERD_INHIBIT_FLAG, os.R_OK) @@ -51,9 +54,11 @@ def get_automatic_pm(): client = gconf.client_get_default() return client.get_bool('/desktop/sugar/power/automatic') + def print_automatic_pm(): print ('off', 'on')[get_automatic_pm()] + def set_automatic_pm(enabled): """Automatic suspends on/off.""" @@ -74,42 +79,45 @@ def set_automatic_pm(enabled): bus = dbus.SystemBus() proxy = bus.get_object(OHM_SERVICE_NAME, OHM_SERVICE_PATH) keystore = dbus.Interface(proxy, OHM_SERVICE_IFACE) - + if enabled == 'on' or enabled == 1: - keystore.SetKey("suspend.automatic_pm", 1) + keystore.SetKey('suspend.automatic_pm', 1) enabled = True elif enabled == 'off' or enabled == 0: - keystore.SetKey("suspend.automatic_pm", 0) + keystore.SetKey('suspend.automatic_pm', 0) enabled = False else: - raise ValueError(_("Error in automatic pm argument, use on/off.")) + raise ValueError(_('Error in automatic pm argument, use on/off.')) client = gconf.client_get_default() client.set_bool('/desktop/sugar/power/automatic', enabled) return + def get_extreme_pm(): client = gconf.client_get_default() return client.get_bool('/desktop/sugar/power/extreme') + def print_extreme_pm(): print ('off', 'on')[get_extreme_pm()] + def set_extreme_pm(enabled): """Extreme power management on/off.""" - + bus = dbus.SystemBus() proxy = bus.get_object(OHM_SERVICE_NAME, OHM_SERVICE_PATH) keystore = dbus.Interface(proxy, OHM_SERVICE_IFACE) - + if enabled == 'on' or enabled == 1: - keystore.SetKey("suspend.extreme_pm", 1) + keystore.SetKey('suspend.extreme_pm', 1) enabled = True elif enabled == 'off' or enabled == 0: - keystore.SetKey("suspend.extreme_pm", 0) + keystore.SetKey('suspend.extreme_pm', 0) enabled = False else: - raise ValueError(_("Error in extreme pm argument, use on/off.")) + raise ValueError(_('Error in extreme pm argument, use on/off.')) client = gconf.client_get_default() client.set_bool('/desktop/sugar/power/extreme', enabled) diff --git a/extensions/cpsection/power/view.py b/extensions/cpsection/power/view.py index 8f1ed56..fd89efa 100644 --- a/extensions/cpsection/power/view.py +++ b/extensions/cpsection/power/view.py @@ -22,6 +22,7 @@ from sugar.graphics import style from jarabe.controlpanel.sectionview import SectionView from jarabe.controlpanel.inlinealert import InlineAlert + class Power(SectionView): def __init__(self, model, alerts): SectionView.__init__(self) diff --git a/extensions/cpsection/updater/backends/aslo.py b/extensions/cpsection/updater/backends/aslo.py index 5f257f9..6504e9e 100644 --- a/extensions/cpsection/updater/backends/aslo.py +++ b/extensions/cpsection/updater/backends/aslo.py @@ -15,7 +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 -'''Activity information microformat parser. +"""Activity information microformat parser. Activity information is embedded in HTML/XHTML/XML pages using a Resource Description Framework (RDF) http://www.w3.org/RDF/ . @@ -46,7 +46,7 @@ An example:: </RDF:Description> </em:targetApplication> </RDF:Description></RDF:RDF> -''' +""" import logging from xml.etree.ElementTree import XML @@ -54,6 +54,9 @@ import traceback import gio +from sugar.bundle.bundleversion import NormalizedVersion +from sugar.bundle.bundleversion import InvalidVersionError + from jarabe import config _FIND_DESCRIPTION = \ @@ -127,17 +130,17 @@ class _UpdateFetcher(object): size = None else: try: - version = int(document.find(_FIND_VERSION).text) - except ValueError: - logging.error(traceback.format_exc()) - version = 0 + version = NormalizedVersion(document.find(_FIND_VERSION).text) + except InvalidVersionError: + logging.exception('Exception occured while parsing version') + version = '0' link = document.find(_FIND_LINK).text try: size = long(document.find(_FIND_SIZE).text) * 1024 except ValueError: - logging.error(traceback.format_exc()) + logging.exception('Exception occured while parsing size') size = 0 global _fetcher @@ -146,13 +149,13 @@ class _UpdateFetcher(object): def fetch_update_info(bundle, completion_cb): - '''Queries the server for a newer version of the ActivityBundle. + """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: diff --git a/extensions/cpsection/updater/model.py b/extensions/cpsection/updater/model.py index 9845371..7ea445f 100755 --- a/extensions/cpsection/updater/model.py +++ b/extensions/cpsection/updater/model.py @@ -14,12 +14,12 @@ # 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. +"""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 @@ -33,6 +33,7 @@ import gio from sugar import env from sugar.datastore import datastore from sugar.bundle.activitybundle import ActivityBundle +from sugar.bundle.bundleversion import NormalizedVersion from jarabe.model import bundleregistry @@ -64,14 +65,16 @@ class UpdateModel(gobject.GObject): def check_updates(self): self.updates = [] - self._bundles_to_check = \ - [bundle for bundle in bundleregistry.get_registry()] + self._bundles_to_check = list(bundleregistry.get_registry()) self._check_next_update() def _check_next_update(self): total = len(bundleregistry.get_registry()) current = total - len(self._bundles_to_check) + if not self._bundles_to_check: + return False + bundle = self._bundles_to_check.pop() self.emit('progress', UpdateModel.ACTION_CHECKING, bundle.get_name(), current, total) @@ -83,7 +86,8 @@ class UpdateModel(gobject.GObject): logging.error('Error getting update information from server:\n' '%s' % error_message) - if version is not None and version > bundle.get_activity_version(): + if version is not None and \ + version > NormalizedVersion(bundle.get_activity_version()): self.updates.append(BundleUpdate(bundle, version, link, size)) if self._cancelling: @@ -196,14 +200,17 @@ class UpdateModel(gobject.GObject): logging.debug('UpdateModel._cancel_checking') total = len(bundleregistry.get_registry()) current = total - len(self._bundles_to_check) - self.emit('progress', UpdateModel.ACTION_CHECKING, '', current, current) + self.emit('progress', UpdateModel.ACTION_CHECKING, '', current, + current) self._bundles_to_check = None self._cancelling = False def _cancel_updating(self): logging.debug('UpdateModel._cancel_updating') - current = self._total_bundles_to_update - len(self._bundles_to_update) - 1 - self.emit('progress', UpdateModel.ACTION_UPDATING, '', current, current) + current = (self._total_bundles_to_update - + len(self._bundles_to_update) - 1) + self.emit('progress', UpdateModel.ACTION_UPDATING, '', current, + current) if self._downloader is not None: self._downloader.cancel() @@ -216,6 +223,7 @@ class UpdateModel(gobject.GObject): self._bundles_to_update = None self._cancelling = False + class BundleUpdate(object): def __init__(self, bundle, version, link, size): @@ -226,7 +234,7 @@ class BundleUpdate(object): class _Downloader(gobject.GObject): - _CHUNK_SIZE = 10240 # 10K + _CHUNK_SIZE = 10240 # 10K __gsignals__ = { 'progress': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, @@ -262,7 +270,7 @@ class _Downloader(gobject.GObject): 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() diff --git a/extensions/cpsection/updater/view.py b/extensions/cpsection/updater/view.py index 2164c0b..814658f 100644 --- a/extensions/cpsection/updater/view.py +++ b/extensions/cpsection/updater/view.py @@ -145,7 +145,7 @@ class ActivityUpdater(SectionView): 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: @@ -161,7 +161,8 @@ class ActivityUpdater(SectionView): self._model.check_updates() def __install_button_clicked_cb(self, button): - self._top_label.set_markup('<big>%s</big>' % _('Installing updates...')) + text = '<big>%s</big>' % _('Installing updates...') + self._top_label.set_markup(text) self._model.update(self._update_box.get_bundles_to_update()) def __cancel_button_clicked_cb(self, button): @@ -176,12 +177,13 @@ class ActivityUpdater(SectionView): self._top_label.set_markup('<big>%s</big>' % top_message) self._clear_center() - def undo(self): + def undo(self): self._model.cancel() + class ProgressPane(gtk.VBox): - '''Container which replaces the `ActivityPane` during refresh or - install.''' + """Container which replaces the `ActivityPane` during refresh or + install.""" def __init__(self): gtk.VBox.__init__(self) @@ -359,7 +361,7 @@ class UpdateListModel(gtk.ListStore): 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 = _('From version %(current)s to %(new)s (Size: %(size)s)') details = details % \ {'current': bundle_update.bundle.get_activity_version(), 'new': bundle_update.version, @@ -374,9 +376,7 @@ class UpdateListModel(gtk.ListStore): def _format_size(size): - ''' - Convert a given size in bytes to a nicer better readable unit - ''' + """Convert a given size in bytes to a nicer better readable unit""" if size == 0: # TRANS: download size is 0 return _('None') @@ -385,7 +385,7 @@ def _format_size(size): 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) + return locale.format_string(_('%.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) + return locale.format_string(_('%.1f MB'), size / 1024.0 / 1024) diff --git a/extensions/deviceicon/Makefile.am b/extensions/deviceicon/Makefile.am index 8a2e765..118d866 100644 --- a/extensions/deviceicon/Makefile.am +++ b/extensions/deviceicon/Makefile.am @@ -5,4 +5,5 @@ sugar_PYTHON = \ battery.py \ network.py \ speaker.py \ + touchpad.py \ volume.py diff --git a/extensions/deviceicon/battery.py b/extensions/deviceicon/battery.py index edfcce4..4c1ef37 100644 --- a/extensions/deviceicon/battery.py +++ b/extensions/deviceicon/battery.py @@ -16,8 +16,9 @@ import logging from gettext import gettext as _ -import gconf +import sys +import gconf import gobject import gtk import dbus @@ -30,6 +31,7 @@ from sugar.graphics.xocolor import XoColor from jarabe.frame.frameinvoker import FrameWidgetInvoker + _ICON_NAME = 'battery' _STATUS_CHARGING = 0 @@ -37,35 +39,38 @@ _STATUS_DISCHARGING = 1 _STATUS_FULLY_CHARGED = 2 _STATUS_NOT_PRESENT = 3 -_LEVEL_PROP = 'battery.charge_level.percentage' -_CHARGING_PROP = 'battery.rechargeable.is_charging' -_DISCHARGING_PROP = 'battery.rechargeable.is_discharging' -_PRESENT_PROP = 'battery.present' +_UP_DEVICE_IFACE = 'org.freedesktop.UPower.Device' + +_UP_TYPE_BATTERY = 2 + +_UP_STATE_UNKNOWN = 0 +_UP_STATE_CHARGING = 1 +_UP_STATE_DISCHARGING = 2 +_UP_STATE_EMPTY = 3 +_UP_STATE_FULL = 4 +_UP_STATE_CHARGE_PENDING = 5 +_UP_STATE_DISCHARGE_PENDING = 6 + +_WARN_MIN_PERCENTAGE = 15 + class DeviceView(TrayIcon): FRAME_POSITION_RELATIVE = 102 - def __init__(self, udi): - client = gconf.client_get_default() + def __init__(self, battery): + client = gconf.client_get_default() self._color = XoColor(client.get_string('/desktop/sugar/user/color')) TrayIcon.__init__(self, icon_name=_ICON_NAME, xo_color=self._color) self.set_palette_invoker(FrameWidgetInvoker(self)) - self._model = DeviceModel(udi) + self._model = DeviceModel(battery) self.palette = BatteryPalette(_('My Battery')) self.palette.set_group_id('frame') - - self._model.connect('notify::level', - self._battery_status_changed_cb) - self._model.connect('notify::charging', - self._battery_status_changed_cb) - self._model.connect('notify::discharging', - self._battery_status_changed_cb) - self._model.connect('notify::present', - self._battery_status_changed_cb) + self._model.connect('updated', + self.__battery_status_changed_cb) self._update_info() def _update_info(self): @@ -86,27 +91,30 @@ class DeviceView(TrayIcon): style.COLOR_WHITE.get_svg())) elif self._model.props.discharging: status = _STATUS_DISCHARGING - if current_level <= 15: + if current_level <= _WARN_MIN_PERCENTAGE: badge_name = 'emblem-warning' else: status = _STATUS_FULLY_CHARGED - self.icon.props.icon_name = get_icon_state(name, current_level, step=-5) + self.icon.props.icon_name = get_icon_state(name, current_level, + step=-5) self.icon.props.xo_color = xo_color self.icon.props.badge_name = badge_name - self.palette.set_level(current_level) - self.palette.set_status(status) + self.palette.set_info(current_level, self._model.props.time_remaining, + status) - def _battery_status_changed_cb(self, pspec, param): + def __battery_status_changed_cb(self, model): self._update_info() + class BatteryPalette(Palette): def __init__(self, primary_text): Palette.__init__(self, primary_text) - self._level = 0 + self._time = 0 + self._status = _STATUS_NOT_PRESENT self._progress_bar = gtk.ProgressBar() self._progress_bar.set_size_request( style.zoom(style.GRID_CELL_SIZE * 4), -1) @@ -122,130 +130,128 @@ class BatteryPalette(Palette): self._progress_widget = vbox self.set_content(self._progress_widget) - def set_level(self, percent): - self._level = percent - fraction = percent / 100.0 - self._progress_bar.set_fraction(fraction) + def set_info(self, percentage, seconds, status): + self._level = percentage + self._time = seconds + self._status = status + self._progress_bar.set_fraction(percentage / 100.0) + self._update_secondary() - def set_status(self, status): - current_level = self._level + def _update_secondary(self): secondary_text = '' - status_text = '%s%%' % current_level + status_text = '%s%%' % (self._level, ) progress_widget = self._progress_widget - if status == _STATUS_NOT_PRESENT: + if self._status == _STATUS_NOT_PRESENT: secondary_text = _('Removed') progress_widget = None - elif status == _STATUS_CHARGING: + elif self._status == _STATUS_CHARGING: secondary_text = _('Charging') - elif status == _STATUS_DISCHARGING: - if current_level <= 15: + elif self._status == _STATUS_DISCHARGING: + if self._level <= _WARN_MIN_PERCENTAGE: secondary_text = _('Very little power remaining') else: - #TODO: make this less of an wild/educated guess - minutes_remaining = int(current_level / 0.59) - remaining_hourpart = minutes_remaining / 60 + minutes_remaining = self._time // 60 + remaining_hourpart = minutes_remaining // 60 remaining_minpart = minutes_remaining % 60 secondary_text = _('%(hour)d:%(min).2d remaining') % \ {'hour': remaining_hourpart, 'min': remaining_minpart} else: secondary_text = _('Charged') + self.set_content(progress_widget) self.props.secondary_text = secondary_text self._status_label.set_text(status_text) + class DeviceModel(gobject.GObject): __gproperties__ = { - 'level' : (int, None, None, 0, 100, 0, - gobject.PARAM_READABLE), - 'charging' : (bool, None, None, False, - gobject.PARAM_READABLE), - 'discharging' : (bool, None, None, False, - gobject.PARAM_READABLE), - 'present' : (bool, None, None, False, - gobject.PARAM_READABLE) + 'level': (int, None, None, 0, 100, 0, gobject.PARAM_READABLE), + 'time-remaining': (int, None, None, 0, sys.maxint, 0, + gobject.PARAM_READABLE), # unit: seconds + 'charging': (bool, None, None, False, gobject.PARAM_READABLE), + 'discharging': (bool, None, None, False, gobject.PARAM_READABLE), + 'present': (bool, None, None, False, gobject.PARAM_READABLE), } - def __init__(self, udi): - gobject.GObject.__init__(self) - - bus = dbus.Bus(dbus.Bus.TYPE_SYSTEM) - proxy = bus.get_object('org.freedesktop.Hal', udi, - follow_name_owner_changes=True) - self._battery = dbus.Interface(proxy, 'org.freedesktop.Hal.Device') - bus.add_signal_receiver(self._battery_changed, - 'PropertyModified', - 'org.freedesktop.Hal.Device', - 'org.freedesktop.Hal', - udi) - - self._level = self._get_level() - self._charging = self._get_charging() - self._discharging = self._get_discharging() - self._present = self._get_present() - - def _get_level(self): - try: - return self._battery.GetProperty(_LEVEL_PROP) - except dbus.DBusException: - 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) - return False + __gsignals__ = { + 'updated': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), + } - def _get_discharging(self): + def __init__(self, battery): + gobject.GObject.__init__(self) + self._battery = battery + self._battery_props_iface = dbus.Interface(self._battery, + dbus.PROPERTIES_IFACE) + self._battery.connect_to_signal('Changed', + self.__battery_properties_changed_cb, + dbus_interface=_UP_DEVICE_IFACE) + self._fetch_properties_from_upower() + + def _fetch_properties_from_upower(self): + """Get current values from UPower.""" + # pylint: disable=W0201 try: - return self._battery.GetProperty(_DISCHARGING_PROP) + dbus_props = self._battery_props_iface.GetAll(_UP_DEVICE_IFACE) except dbus.DBusException: - logging.error('Cannot access %s', _DISCHARGING_PROP) - return False + logging.error('Cannot access battery properties') + dbus_props = {} - def _get_present(self): - try: - return self._battery.GetProperty(_PRESENT_PROP) - except dbus.DBusException: - logging.error('Cannot access %s', _PRESENT_PROP) - return False + self._level = dbus_props.get('Percentage', 0) + self._state = dbus_props.get('State', _UP_STATE_UNKNOWN) + self._present = dbus_props.get('IsPresent', False) + self._time_to_empty = dbus_props.get('TimeToEmpty', 0) + self._time_to_full = dbus_props.get('TimeToFull', 0) def do_get_property(self, pspec): + """Return current value of given GObject property.""" if pspec.name == 'level': - return self._level + return self._level if pspec.name == 'charging': - return self._charging + return self._state == _UP_STATE_CHARGING if pspec.name == 'discharging': - return self._discharging + return self._state == _UP_STATE_DISCHARGING if pspec.name == 'present': return self._present + if pspec.name == 'time-remaining': + if self._state == _UP_STATE_CHARGING: + return self._time_to_full + if self._state == _UP_STATE_DISCHARGING: + return self._time_to_empty + return 0 def get_type(self): return 'battery' - def _battery_changed(self, num_changes, changes_list): - for change in changes_list: - if change[0] == _LEVEL_PROP: - self._level = self._get_level() - self.notify('level') - elif change[0] == _CHARGING_PROP: - self._charging = self._get_charging() - self.notify('charging') - elif change[0] == _DISCHARGING_PROP: - self._discharging = self._get_discharging() - self.notify('discharging') - elif change[0] == _PRESENT_PROP: - self._present = self._get_present() - self.notify('present') + def __battery_properties_changed_cb(self): + old_level = self._level + old_state = self._state + old_present = self._present + old_time = self.props.time_remaining + self._fetch_properties_from_upower() + if self._level != old_level: + self.notify('level') + if self._state != old_state: + self.notify('charging') + self.notify('discharging') + if self._present != old_present: + self.notify('present') + if self.props.time_remaining != old_time: + self.notify('time-remaining') + + self.emit('updated') + def setup(tray): bus = dbus.Bus(dbus.Bus.TYPE_SYSTEM) - proxy = bus.get_object('org.freedesktop.Hal', - '/org/freedesktop/Hal/Manager') - hal_manager = dbus.Interface(proxy, 'org.freedesktop.Hal.Manager') - - for udi in hal_manager.FindDeviceByCapability('battery'): - tray.add_device(DeviceView(udi)) + up_proxy = bus.get_object('org.freedesktop.UPower', + '/org/freedesktop/UPower') + upower = dbus.Interface(up_proxy, 'org.freedesktop.UPower') + + for device_path in upower.EnumerateDevices(): + device = bus.get_object('org.freedesktop.UPower', device_path) + device_prop_iface = dbus.Interface(device, dbus.PROPERTIES_IFACE) + device_type = device_prop_iface.Get(_UP_DEVICE_IFACE, 'Type') + if device_type == _UP_TYPE_BATTERY: + tray.add_device(DeviceView(device)) diff --git a/extensions/deviceicon/network.py b/extensions/deviceicon/network.py index 94a4293..4c4f339 100644 --- a/extensions/deviceicon/network.py +++ b/extensions/deviceicon/network.py @@ -23,10 +23,10 @@ import logging import hashlib import socket import struct -import re import datetime import time import gtk +import glib import gobject import gconf import dbus @@ -36,17 +36,17 @@ from sugar.graphics import style from sugar.graphics.palette import Palette from sugar.graphics.toolbutton import ToolButton from sugar.graphics.tray import TrayIcon +from sugar.graphics.menuitem import MenuItem +from sugar.graphics.icon import Icon from sugar.graphics import xocolor -from sugar.util import unique_id from sugar import profile from jarabe.model import network -from jarabe.model.network import Settings -from jarabe.model.network import IP4Config from jarabe.frame.frameinvoker import FrameWidgetInvoker from jarabe.view.pulsingicon import PulsingIcon -IP_ADDRESS_TEXT_TEMPLATE = _("IP address: %s") + +IP_ADDRESS_TEXT_TEMPLATE = _('IP address: %s') _NM_SERVICE = 'org.freedesktop.NetworkManager' _NM_IFACE = 'org.freedesktop.NetworkManager' @@ -63,26 +63,18 @@ _GSM_STATE_NOT_READY = 0 _GSM_STATE_DISCONNECTED = 1 _GSM_STATE_CONNECTING = 2 _GSM_STATE_CONNECTED = 3 -_GSM_STATE_NEED_AUTH = 4 +_GSM_STATE_FAILED = 4 -def frequency_to_channel(frequency): - ftoc = { 2412: 1, 2417: 2, 2422: 3, 2427: 4, - 2432: 5, 2437: 6, 2442: 7, 2447: 8, - 2452: 9, 2457: 10, 2462: 11, 2467: 12, - 2472: 13} - return ftoc[frequency] class WirelessPalette(Palette): __gtype_name__ = 'SugarWirelessPalette' __gsignals__ = { - 'deactivate-connection' : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([])), - 'create-connection' : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([])), + 'deactivate-connection': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([])), } - def __init__(self, primary_text, can_create=True): + def __init__(self, primary_text): Palette.__init__(self, label=primary_text) self._disconnect_item = None @@ -109,16 +101,13 @@ class WirelessPalette(Palette): self._info.pack_start(_padded(self._ip_address_label)) self._info.show_all() - self._disconnect_item = gtk.MenuItem(_('Disconnect...')) - self._disconnect_item.connect('activate', self.__disconnect_activate_cb) + self._disconnect_item = MenuItem(_('Disconnect...')) + icon = Icon(icon_size=gtk.ICON_SIZE_MENU, icon_name='media-eject') + self._disconnect_item.set_image(icon) + self._disconnect_item.connect('activate', + self.__disconnect_activate_cb) self.menu.append(self._disconnect_item) - if can_create: - self._adhoc_item = gtk.MenuItem(_('Create new wireless network')) - self._adhoc_item.connect('activate', self.__adhoc_activate_cb) - self.menu.append(self._adhoc_item) - self._adhoc_item.show() - def set_connecting(self): self.props.secondary_text = _('Connecting...') @@ -145,18 +134,12 @@ class WirelessPalette(Palette): def __disconnect_activate_cb(self, menuitem): self.emit('deactivate-connection') - def __adhoc_activate_cb(self, menuitem): - self.emit('create-connection') - def _set_frequency(self, frequency): - try: - channel = frequency_to_channel(frequency) - except KeyError: - channel = 0 + channel = network.frequency_to_channel(frequency) self._set_channel(channel) def _set_channel(self, channel): - self._channel_label.set_text("%s: %d" % (_("Channel"), channel)) + self._channel_label.set_text('%s: %d' % (_('Channel'), channel)) def _set_ip_address(self, ip_address): if ip_address is not None: @@ -204,7 +187,7 @@ class WiredPalette(Palette): def _inet_ntoa(self, iaddress): address = ['%s' % ((iaddress >> i) % 256) for i in [0, 8, 16, 24]] - return ".".join(address) + return '.'.join(address) def _set_ip_address(self, ip_address): if ip_address is not None: @@ -214,14 +197,13 @@ class WiredPalette(Palette): ip_address_text = "" self._ip_address_label.set_text(ip_address_text) + class GsmPalette(Palette): __gtype_name__ = 'SugarGsmPalette' __gsignals__ = { - 'gsm-connect' : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([])), - 'gsm-disconnect' : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([])), + 'gsm-connect': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), + 'gsm-disconnect': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), } def __init__(self): @@ -229,34 +211,53 @@ class GsmPalette(Palette): Palette.__init__(self, label=_('Wireless modem')) self._current_state = None + self._failed_connection = False - self._toggle_state_item = gtk.MenuItem('') + self._toggle_state_item = MenuItem('') self._toggle_state_item.connect('activate', self.__toggle_state_cb) self.menu.append(self._toggle_state_item) self._toggle_state_item.show() - self.set_state(_GSM_STATE_NOT_READY) - self.info_box = gtk.VBox() - self.data_label = gtk.Label() - self.data_label.props.xalign = 0.0 - label_alignment = self._add_widget_with_padding(self.data_label) - self.info_box.pack_start(label_alignment) - self.data_label.show() + self.error_title_label = gtk.Label("") + self.error_title_label.set_alignment(0, 0.5) + self.error_title_label.set_line_wrap(True) + self.info_box.pack_start(self.error_title_label) + self.error_description_label = gtk.Label("") + self.error_description_label.set_alignment(0, 0.5) + self.error_description_label.set_line_wrap(True) + self.info_box.pack_start(self.error_description_label) + + self.connection_info_box = gtk.HBox() + icon = Icon(icon_name='data-upload', icon_size=gtk.ICON_SIZE_MENU) + self.connection_info_box.pack_start(icon) + icon.show() + + self._data_label_up = gtk.Label() + self._data_label_up.props.xalign = 0.0 + label_alignment = self._add_widget_with_padding(self._data_label_up) + self.connection_info_box.pack_start(label_alignment) + self._data_label_up.show() label_alignment.show() - self.connection_time_label = gtk.Label() - self.connection_time_label.props.xalign = 0.0 - label_alignment = self._add_widget_with_padding( \ - self.connection_time_label) - self.info_box.pack_start(label_alignment) - self.connection_time_label.show() + icon = Icon(icon_name='data-download', icon_size=gtk.ICON_SIZE_MENU) + self.connection_info_box.pack_start(icon) + icon.show() + self._data_label_down = gtk.Label() + self._data_label_down.props.xalign = 0.0 + label_alignment = self._add_widget_with_padding(self._data_label_down) + self.connection_info_box.pack_start(label_alignment) + self._data_label_down.show() label_alignment.show() + self.info_box.pack_start(self.connection_info_box) + self.info_box.show() self.set_content(self.info_box) + self.set_state(_GSM_STATE_NOT_READY) + def _add_widget_with_padding(self, child, xalign=0, yalign=0.5): alignment = gtk.Alignment(xalign=xalign, yalign=yalign, xscale=1, yscale=0.33) @@ -267,31 +268,41 @@ class GsmPalette(Palette): alignment.add(child) return alignment - def set_state(self, state): + def update_state(self, state, reason=0): self._current_state = state - self._update_label_and_text() + self._update_label_and_text(reason) - def _update_label_and_text(self): + def _update_label_and_text(self, reason=0): if self._current_state == _GSM_STATE_NOT_READY: self._toggle_state_item.get_child().set_label('...') self.props.secondary_text = _('Please wait...') elif self._current_state == _GSM_STATE_DISCONNECTED: - self._toggle_state_item.get_child().set_label(_('Connect')) + if not self._failed_connection: + self._toggle_state_item.get_child().set_label(_('Connect')) self.props.secondary_text = _('Disconnected') + icon = Icon(icon_name='dialog-ok', \ + icon_size=gtk.ICON_SIZE_MENU) + self._toggle_state_item.set_image(icon) elif self._current_state == _GSM_STATE_CONNECTING: self._toggle_state_item.get_child().set_label(_('Cancel')) self.props.secondary_text = _('Connecting...') + icon = Icon(icon_name='dialog-cancel', \ + icon_size=gtk.ICON_SIZE_MENU) + self._toggle_state_item.set_image(icon) elif self._current_state == _GSM_STATE_CONNECTED: + self._failed_connection = False self._toggle_state_item.get_child().set_label(_('Disconnect')) - self.props.secondary_text = _('Connected') - - elif self._current_state == _GSM_STATE_NEED_AUTH: - self._toggle_state_item.get_child().set_label(_('Sim requires Pin/Puk')) - self.props.secondary_text = _('Authentication Error') - + self.update_connection_time() + icon = Icon(icon_name='media-eject', \ + icon_size=gtk.ICON_SIZE_MENU) + self._toggle_state_item.set_image(icon) + + elif self._current_state == _GSM_STATE_FAILED: + message_error = self._get_error_by_nm_reason(reason) + self.add_alert(message_error[0], message_error[1]) else: raise ValueError('Invalid GSM state while updating label and ' \ 'text, %s' % str(self._current_state)) @@ -300,21 +311,64 @@ class GsmPalette(Palette): if self._current_state == _GSM_STATE_NOT_READY: pass elif self._current_state == _GSM_STATE_DISCONNECTED: + self.error_title_label.hide() + self.error_description_label.hide() self.emit('gsm-connect') elif self._current_state == _GSM_STATE_CONNECTING: self.emit('gsm-disconnect') elif self._current_state == _GSM_STATE_CONNECTED: self.emit('gsm-disconnect') - elif self._current_state == _GSM_STATE_NEED_AUTH: - self.emit('gsm-disconnect') else: raise ValueError('Invalid GSM state while emitting signal, %s' % \ str(self._current_state)) + def add_alert(self, error, suggestion): + self._failed_connection = True + action = _('Try connection again') + self._toggle_state_item.get_child().set_label(action) + + title = _('Error: %s') % error + self.error_title_label.set_markup('<b>%s</b>' % title) + self.error_title_label.show() + + message = _('Suggestion: %s') % suggestion + self.error_description_label.set_text(message) + self.error_description_label.show() + + def update_connection_time(self, connection_time=None): + if connection_time is not None: + self.props.secondary_text = _('Connected for %s') % \ + connection_time.strftime('%H:%M:%S') + else: + self.props.secondary_text = _('Connected for %s') % '00:00:00' + + def update_stats(self, in_bytes, out_bytes): + in_KBytes = in_bytes / 1024 + out_KBytes = out_bytes / 1024 + self._data_label_up.set_text(_('%d KB') % (out_KBytes)) + self._data_label_down.set_text(_('%d KB') % (in_KBytes)) + + def _get_error_by_nm_reason(self, reason): + if reason in [network.NM_DEVICE_STATE_REASON_NO_SECRETS, + network.NM_DEVICE_STATE_REASON_GSM_PIN_CHECK_FAILED]: + message = _('Check your Pin/Puk configuration.') + elif reason in [network.NM_DEVICE_STATE_REASON_PPP_DISCONNECT, + network.NM_DEVICE_STATE_REASON_PPP_FAILED]: + message = _('Check your Access Point Name ' \ + '(APN) configuration') + elif reason in [network.NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER, + network.NM_DEVICE_STATE_REASON_MODEM_DIAL_TIMEOUT]: + message = _('Check the Number configuration.') + elif reason == network.NM_DEVICE_STATE_REASON_CONFIG_FAILED: + message = _('Check your configuration.') + else: + message = '' + message_tuple = (network.get_error_by_reason(reason), message) + return message_tuple + class WirelessDeviceView(ToolButton): - _ICON_NAME = 'network-wireless' FRAME_POSITION_RELATIVE = 302 def __init__(self, device): @@ -333,9 +387,9 @@ class WirelessDeviceView(ToolButton): self._active_ap_op = None self._icon = PulsingIcon() - self._icon.props.icon_name = get_icon_state(self._ICON_NAME, 0) - self._inactive_color = xocolor.XoColor( \ - "%s,%s" % (style.COLOR_BUTTON_GREY.get_svg(), + self._icon.props.icon_name = get_icon_state('network-wireless', 0) + self._inactive_color = xocolor.XoColor( + '%s,%s' % (style.COLOR_BUTTON_GREY.get_svg(), style.COLOR_TRANSPARENT.get_svg())) self._icon.props.pulse_color = self._inactive_color self._icon.props.base_color = self._inactive_color @@ -347,14 +401,12 @@ class WirelessDeviceView(ToolButton): self._palette = WirelessPalette(self._name) self._palette.connect('deactivate-connection', self.__deactivate_connection_cb) - self._palette.connect('create-connection', - self.__create_connection_cb) self.set_palette(self._palette) self._palette.set_group_id('frame') - self._device_props = dbus.Interface(self._device, - 'org.freedesktop.DBus.Properties') - self._device_props.GetAll(_NM_DEVICE_IFACE, byte_arrays=True, + self._device_props = dbus.Interface(self._device, + dbus.PROPERTIES_IFACE) + self._device_props.GetAll(_NM_DEVICE_IFACE, byte_arrays=True, reply_handler=self.__get_device_props_reply_cb, error_handler=self.__get_device_props_error_cb) @@ -394,7 +446,7 @@ class WirelessDeviceView(ToolButton): return self._active_ap_op = active_ap_op active_ap = self._bus.get_object(_NM_SERVICE, active_ap_op) - props = dbus.Interface(active_ap, 'org.freedesktop.DBus.Properties') + props = dbus.Interface(active_ap, dbus.PROPERTIES_IFACE) props.GetAll(_NM_ACCESSPOINT_IFACE, byte_arrays=True, reply_handler=self.__get_all_ap_props_reply_cb, @@ -418,11 +470,6 @@ class WirelessDeviceView(ToolButton): def __ap_properties_changed_cb(self, properties): self._update_properties(properties) - def _name_encodes_colors(self): - """Match #XXXXXX,#YYYYYY at the end of the network name""" - return self._name[-7] == '#' and self._name[-8] == ',' \ - and self._name[-15] == '#' - def _update_properties(self, properties): if 'Mode' in properties: self._mode = properties['Mode'] @@ -438,11 +485,9 @@ class WirelessDeviceView(ToolButton): self._frequency = properties['Frequency'] if self._color == None: - if self._mode == network.NM_802_11_MODE_ADHOC \ - and self._name_encodes_colors(): - 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_ADHOC and \ + network.is_sugar_adhoc_network(self._name): + self._color = profile.get_color() else: sha_hash = hashlib.sha1() data = self._name + hex(self._flags) @@ -450,7 +495,7 @@ class WirelessDeviceView(ToolButton): digest = hash(sha_hash.digest()) index = digest % len(xocolor.colors) - self._color = xocolor.XoColor('%s,%s' % + self._color = xocolor.XoColor('%s,%s' % (xocolor.colors[index][0], xocolor.colors[index][1])) self._update() @@ -463,11 +508,11 @@ class WirelessDeviceView(ToolButton): def _update(self): if self._flags == network.NM_802_11_AP_FLAGS_PRIVACY: - self._icon.props.badge_name = "emblem-locked" + self._icon.props.badge_name = 'emblem-locked' else: self._icon.props.badge_name = None - self._palette.props.primary_text = self._name + self._palette.props.primary_text = glib.markup_escape_text(self._name) self._update_state() self._update_color() @@ -478,14 +523,24 @@ class WirelessDeviceView(ToolButton): else: state = network.DEVICE_STATE_UNKNOWN - if state == network.DEVICE_STATE_ACTIVATED: - icon_name = '%s-connected' % self._ICON_NAME - else: - icon_name = self._ICON_NAME + if self._mode != network.NM_802_11_MODE_ADHOC and \ + network.is_sugar_adhoc_network(self._name) == False: + if state == network.DEVICE_STATE_ACTIVATED: + icon_name = '%s-connected' % 'network-wireless' + else: + icon_name = 'network-wireless' - icon_name = get_icon_state(icon_name, self._strength) - if icon_name: - self._icon.props.icon_name = icon_name + icon_name = get_icon_state(icon_name, self._strength) + if icon_name: + self._icon.props.icon_name = icon_name + else: + channel = network.frequency_to_channel(self._frequency) + if state == network.DEVICE_STATE_ACTIVATED: + self._icon.props.icon_name = 'network-adhoc-%s-connected' \ + % channel + else: + self._icon.props.icon_name = 'network-adhoc-%s' % channel + self._icon.props.base_color = profile.get_color() if state == network.DEVICE_STATE_PREPARE or \ state == network.DEVICE_STATE_CONFIG or \ @@ -495,7 +550,8 @@ class WirelessDeviceView(ToolButton): self._icon.props.pulsing = True elif state == network.DEVICE_STATE_ACTIVATED: address = self._device_props.Get(_NM_DEVICE_IFACE, 'Ip4Address') - self._palette.set_connected_with_frequency(self._frequency, address) + self._palette.set_connected_with_frequency(self._frequency, + address) self._icon.props.pulsing = False else: self._icon.props.badge_name = None @@ -508,69 +564,12 @@ class WirelessDeviceView(ToolButton): self._icon.props.base_color = self._color def __deactivate_connection_cb(self, palette, data=None): - if self._active_ap_op is not None: - obj = self._bus.get_object(_NM_SERVICE, _NM_PATH) - netmgr = dbus.Interface(obj, _NM_IFACE) - netmgr_props = dbus.Interface( - netmgr, 'org.freedesktop.DBus.Properties') - active_connections_o = netmgr_props.Get(_NM_IFACE, - 'ActiveConnections') - - for conn_o in active_connections_o: - obj = self._bus.get_object(_NM_IFACE, conn_o) - props = dbus.Interface(obj, 'org.freedesktop.DBus.Properties') - ap_op = props.Get(_NM_ACTIVE_CONN_IFACE, 'SpecificObject') - if ap_op == self._active_ap_op: - netmgr.DeactivateConnection(conn_o) - break - - def __create_connection_cb(self, palette, data=None): - """Create an 802.11 IBSS network. - - The user's color is encoded at the end of the network name. The network - name is truncated so that it does not exceed the 32 byte SSID limit. - """ - client = gconf.client_get_default() - nick = client.get_string('/desktop/sugar/user/nick').decode('utf-8') - color = client.get_string('/desktop/sugar/user/color') - color_suffix = ' %s' % color - - format = _('%s\'s network').encode('utf-8') - extra_length = (len(format) - len('%s')) + len(color_suffix) - name_limit = 32 - extra_length - - # truncate the nick and use a regex to drop any partial characters - # at the end - nick = nick.encode('utf-8')[:name_limit] - pattern = "([\xf6-\xf7][\x80-\xbf]{0,2}|[\xe0-\xef][\x80-\xbf]{0,1}|[\xc0-\xdf])$" - nick = re.sub(pattern, '', nick) - - connection_name = format % nick - connection_name += color_suffix - - connection = network.find_connection_by_ssid(connection_name) - if connection is None: - settings = Settings() - settings.connection.id = 'Auto ' + connection_name - uuid = settings.connection.uuid = unique_id() - settings.connection.type = '802-11-wireless' - settings.wireless.ssid = dbus.ByteArray(connection_name) - settings.wireless.band = 'bg' - settings.wireless.mode = 'adhoc' - settings.ip4_config = IP4Config() - settings.ip4_config.method = 'link-local' - - connection = network.add_connection(uuid, settings) + if self._mode == network.NM_802_11_MODE_INFRA: + connection = network.find_connection_by_ssid(self._name) + if connection: + connection.disable_autoconnect() - obj = self._bus.get_object(_NM_SERVICE, _NM_PATH) - netmgr = dbus.Interface(obj, _NM_IFACE) - - netmgr.ActivateConnection(network.SETTINGS_SERVICE, - connection.path, - self._device.object_path, - '/', - reply_handler=self.__activate_reply_cb, - error_handler=self.__activate_error_cb) + network.disconnect_access_points([self._active_ap_op]) def __activate_reply_cb(self, connection): logging.debug('Network created: %s', connection) @@ -583,7 +582,7 @@ class OlpcMeshDeviceView(ToolButton): _ICON_NAME = 'network-mesh' FRAME_POSITION_RELATIVE = 302 - def __init__(self, device): + def __init__(self, device, state): ToolButton.__init__(self) self._bus = dbus.SystemBus() @@ -593,57 +592,41 @@ class OlpcMeshDeviceView(ToolButton): self._channel = 0 self._icon = PulsingIcon(icon_name=self._ICON_NAME) - self._icon.props.pulse_color = xocolor.XoColor( \ - "%s,%s" % (style.COLOR_BUTTON_GREY.get_svg(), + self._inactive_color = xocolor.XoColor( + '%s,%s' % (style.COLOR_BUTTON_GREY.get_svg(), style.COLOR_TRANSPARENT.get_svg())) - self._icon.props.base_color = profile.get_color() + self._icon.props.pulse_color = profile.get_color() + self._icon.props.base_color = self._inactive_color self.set_icon_widget(self._icon) self._icon.show() self.set_palette_invoker(FrameWidgetInvoker(self)) - self._palette = WirelessPalette(_("Mesh Network")) + self._palette = WirelessPalette(_('Mesh Network')) self._palette.connect('deactivate-connection', self.__deactivate_connection) self.set_palette(self._palette) self._palette.set_group_id('frame') + self.update_state(state) + self._device_props = dbus.Interface(self._device, - 'org.freedesktop.DBus.Properties') - self._device_props.GetAll(_NM_DEVICE_IFACE, byte_arrays=True, - reply_handler=self.__get_device_props_reply_cb, - error_handler=self.__get_device_props_error_cb) + dbus.PROPERTIES_IFACE) self._device_props.Get(_NM_OLPC_MESH_IFACE, 'ActiveChannel', reply_handler=self.__get_active_channel_reply_cb, error_handler=self.__get_active_channel_error_cb) - self._bus.add_signal_receiver(self.__state_changed_cb, - signal_name='StateChanged', - path=self._device.object_path, - dbus_interface=_NM_DEVICE_IFACE) self._bus.add_signal_receiver(self.__wireless_properties_changed_cb, signal_name='PropertiesChanged', path=device.object_path, dbus_interface=_NM_OLPC_MESH_IFACE) def disconnect(self): - self._bus.remove_signal_receiver(self.__state_changed_cb, - signal_name='StateChanged', - path=self._device.object_path, - dbus_interface=_NM_DEVICE_IFACE) self._bus.remove_signal_receiver(self.__wireless_properties_changed_cb, signal_name='PropertiesChanged', path=self._device.object_path, dbus_interface=_NM_OLPC_MESH_IFACE) - def __get_device_props_reply_cb(self, properties): - if 'State' in properties: - self._device_state = properties['State'] - self._update() - - def __get_device_props_error_cb(self, err): - logging.error('Error getting the device properties: %s', err) - def __get_active_channel_reply_cb(self, channel): self._channel = channel self._update_text() @@ -661,7 +644,8 @@ class OlpcMeshDeviceView(ToolButton): self._update_text() def _update_text(self): - text = _("Mesh Network") + " " + str(self._channel) + channel = str(self._channel) + text = _('Mesh Network %s') % glib.markup_escape_text(channel) self._palette.props.primary_text = text def _update(self): @@ -671,31 +655,39 @@ class OlpcMeshDeviceView(ToolButton): network.DEVICE_STATE_CONFIG, network.DEVICE_STATE_NEED_AUTH, network.DEVICE_STATE_IP_CONFIG]: + self._icon.props.base_color = self._inactive_color + self._icon.props.pulse_color = profile.get_color() self._palette.set_connecting() self._icon.props.pulsing = True elif state == network.DEVICE_STATE_ACTIVATED: address = self._device_props.Get(_NM_DEVICE_IFACE, 'Ip4Address') self._palette.set_connected_with_channel(self._channel, address) + self._icon.props.base_color = profile.get_color() self._icon.props.pulsing = False + self._update_text() + + def update_state(self, state): + self._device_state = state + self._update() def __deactivate_connection(self, palette, data=None): obj = self._bus.get_object(_NM_SERVICE, _NM_PATH) netmgr = dbus.Interface(obj, _NM_IFACE) - netmgr_props = dbus.Interface(netmgr, 'org.freedesktop.DBus.Properties') + netmgr_props = dbus.Interface(netmgr, dbus.PROPERTIES_IFACE) active_connections_o = netmgr_props.Get(_NM_IFACE, 'ActiveConnections') for conn_o in active_connections_o: # The connection path for a mesh connection is the device itself. obj = self._bus.get_object(_NM_IFACE, conn_o) - props = dbus.Interface(obj, 'org.freedesktop.DBus.Properties') + props = dbus.Interface(obj, dbus.PROPERTIES_IFACE) ap_op = props.Get(_NM_ACTIVE_CONN_IFACE, 'SpecificObject') try: obj = self._bus.get_object(_NM_IFACE, ap_op) - props = dbus.Interface(obj, 'org.freedesktop.DBus.Properties') - type = props.Get(_NM_DEVICE_IFACE, 'DeviceType') - if type == network.DEVICE_TYPE_802_11_OLPC_MESH: + props = dbus.Interface(obj, dbus.PROPERTIES_IFACE) + device_type = props.Get(_NM_DEVICE_IFACE, 'DeviceType') + if device_type == network.DEVICE_TYPE_802_11_OLPC_MESH: netmgr.DeactivateConnection(conn_o) break except dbus.exceptions.DBusException: @@ -747,6 +739,7 @@ class GsmDeviceView(TrayIcon): signal_name='PppStats', path=self._device.object_path, dbus_interface=_NM_SERIAL_IFACE) + def create_palette(self): palette = GsmPalette() @@ -756,7 +749,7 @@ class GsmDeviceView(TrayIcon): self._palette = palette - props = dbus.Interface(self._device, 'org.freedesktop.DBus.Properties') + props = dbus.Interface(self._device, dbus.PROPERTIES_IFACE) props.GetAll(_NM_DEVICE_IFACE, byte_arrays=True, reply_handler=self.__current_state_check_cb, error_handler=self.__current_state_check_error_cb) @@ -774,6 +767,10 @@ class GsmDeviceView(TrayIcon): '/', reply_handler=self.__connect_cb, error_handler=self.__connect_error_cb) + else: + self._palette.add_alert(_('No GSM connection available.'), \ + _('Create a connection in the ' \ + 'control panel.')) def __connect_cb(self, active_connection): logging.debug('Connected successfully to gsm device, %s', @@ -785,12 +782,12 @@ class GsmDeviceView(TrayIcon): def __gsm_disconnect_cb(self, palette, data=None): obj = self._bus.get_object(_NM_SERVICE, _NM_PATH) netmgr = dbus.Interface(obj, _NM_IFACE) - netmgr_props = dbus.Interface(netmgr, 'org.freedesktop.DBus.Properties') + netmgr_props = dbus.Interface(netmgr, dbus.PROPERTIES_IFACE) active_connections_o = netmgr_props.Get(_NM_IFACE, 'ActiveConnections') for conn_o in active_connections_o: obj = self._bus.get_object(_NM_IFACE, conn_o) - props = dbus.Interface(obj, 'org.freedesktop.DBus.Properties') + props = dbus.Interface(obj, dbus.PROPERTIES_IFACE) devices = props.Get(_NM_ACTIVE_CONN_IFACE, 'Devices') if self._device.object_path in devices: netmgr.DeactivateConnection( @@ -806,16 +803,17 @@ class GsmDeviceView(TrayIcon): raise RuntimeError('Error when disconnecting gsm device, %s' % error) def __state_changed_cb(self, new_state, old_state, reason): - logging.debug('State: %s to %s, reason %s', old_state, new_state, reason) - self._update_state(int(new_state)) + logging.debug('State: %s to %s, reason %s', old_state, + new_state, reason) + self._update_state(int(new_state), int(old_state), int(reason)) def __current_state_check_cb(self, properties): - self._update_state(int(properties['State'])) + self._update_state(int(properties['State']), 0, 0) def __current_state_check_error_cb(self, error): raise RuntimeError('Error when checking gsm device state, %s' % error) - def _update_state(self, state): + def _update_state(self, state, old_state, reason): gsm_state = None if state is network.DEVICE_STATE_ACTIVATED: @@ -823,20 +821,22 @@ class GsmDeviceView(TrayIcon): connection = network.find_gsm_connection() if connection is not None: connection.set_connected() - self._connection_timestamp = time.time() - \ + self._connection_timestamp = time.time() - \ connection.get_settings().connection.timestamp self._connection_time_handler = gobject.timeout_add_seconds( \ 1, self.__connection_timecount_cb) - self._update_stats(0, 0) - self._update_connection_time() - self._palette.info_box.show() + self._palette.update_connection_time() + self._palette.update_stats(0, 0) + if self._palette is not None: + self._palette.connection_info_box.show() elif state is network.DEVICE_STATE_DISCONNECTED: gsm_state = _GSM_STATE_DISCONNECTED self._connection_timestamp = 0 if self._connection_time_handler is not None: gobject.source_remove(self._connection_time_handler) - self._palette.info_box.hide() + if self._palette is not None: + self._palette.connection_info_box.hide() elif state in [network.DEVICE_STATE_UNMANAGED, network.DEVICE_STATE_UNAVAILABLE, @@ -845,14 +845,15 @@ class GsmDeviceView(TrayIcon): elif state in [network.DEVICE_STATE_PREPARE, network.DEVICE_STATE_CONFIG, - network.DEVICE_STATE_IP_CONFIG]: + network.DEVICE_STATE_IP_CONFIG, + network.DEVICE_STATE_NEED_AUTH]: gsm_state = _GSM_STATE_CONNECTING - - elif state in [network.DEVICE_STATE_NEED_AUTH]: - gsm_state = _GSM_STATE_NEED_AUTH - + + elif state == network.DEVICE_STATE_FAILED: + gsm_state = _GSM_STATE_FAILED + if self._palette is not None: - self._palette.set_state(gsm_state) + self._palette.update_state(gsm_state, reason) def disconnect(self): self._bus.remove_signal_receiver(self.__state_changed_cb, @@ -861,38 +862,22 @@ class GsmDeviceView(TrayIcon): dbus_interface=_NM_DEVICE_IFACE) def __ppp_stats_changed_cb(self, in_bytes, out_bytes): - self._update_stats(in_bytes, out_bytes) - - def _update_stats(self, in_bytes, out_bytes): - in_KBytes = in_bytes / 1024 - out_KBytes = out_bytes / 1024 - text = _("Data sent %d KB / received %d KB") % (out_KBytes, in_KBytes) - self._palette.data_label.set_text(text) + self._palette.update_stats(in_bytes, out_bytes) def __connection_timecount_cb(self): self._connection_timestamp = self._connection_timestamp + 1 - self._update_connection_time() + connection_time = \ + datetime.datetime.fromtimestamp(self._connection_timestamp) + self._palette.update_connection_time(connection_time) return True - def _update_connection_time(self): - connection_time = datetime.datetime.fromtimestamp( \ - self._connection_timestamp) - text = _("Connection time ") + connection_time.strftime('%H : %M : %S') - self._palette.connection_time_label.set_text(text) class WirelessDeviceObserver(object): - def __init__(self, device, tray, device_type): + def __init__(self, device, tray): self._device = device self._device_view = None self._tray = tray - - if device_type == network.DEVICE_TYPE_802_11_WIRELESS: - self._device_view = WirelessDeviceView(self._device) - elif device_type == network.DEVICE_TYPE_802_11_OLPC_MESH: - self._device_view = OlpcMeshDeviceView(self._device) - else: - raise ValueError('Unimplemented device type %d' % device_type) - + self._device_view = WirelessDeviceView(self._device) self._tray.add_device(self._device_view) def disconnect(self): @@ -902,6 +887,63 @@ class WirelessDeviceObserver(object): self._device_view = None +class MeshDeviceObserver(object): + def __init__(self, device, tray): + self._bus = dbus.SystemBus() + self._device = device + self._device_view = None + self._tray = tray + + props = dbus.Interface(self._device, dbus.PROPERTIES_IFACE) + props.GetAll(_NM_DEVICE_IFACE, byte_arrays=True, + reply_handler=self.__get_device_props_reply_cb, + error_handler=self.__get_device_props_error_cb) + + self._bus.add_signal_receiver(self.__state_changed_cb, + signal_name='StateChanged', + path=self._device.object_path, + dbus_interface=_NM_DEVICE_IFACE) + + def _remove_device_view(self): + self._device_view.disconnect() + self._tray.remove_device(self._device_view) + self._device_view = None + + def disconnect(self): + if self._device_view is not None: + self._remove_device_view() + + self._bus.remove_signal_receiver(self.__state_changed_cb, + signal_name='StateChanged', + path=self._device.object_path, + dbus_interface=_NM_DEVICE_IFACE) + + def __get_device_props_reply_cb(self, properties): + if 'State' in properties: + self._update_state(properties['State']) + + def __get_device_props_error_cb(self, err): + logging.error('Error getting the device properties: %s', err) + + def __state_changed_cb(self, new_state, old_state, reason): + self._update_state(new_state) + + def _update_state(self, state): + if state in (network.DEVICE_STATE_PREPARE, network.DEVICE_STATE_CONFIG, + network.DEVICE_STATE_NEED_AUTH, + network.DEVICE_STATE_IP_CONFIG, + network.DEVICE_STATE_ACTIVATED): + if self._device_view is not None: + self._device_view.update_state(state) + return + + self._device_view = OlpcMeshDeviceView(self._device, state) + self._tray.add_device(self._device_view) + else: + if self._device_view is not None: + self._remove_device_view() + + class WiredDeviceObserver(object): def __init__(self, device, tray): self._bus = dbus.SystemBus() @@ -910,7 +952,7 @@ class WiredDeviceObserver(object): self._device_view = None self._tray = tray - props = dbus.Interface(self._device, 'org.freedesktop.DBus.Properties') + props = dbus.Interface(self._device, dbus.PROPERTIES_IFACE) props.GetAll(_NM_DEVICE_IFACE, byte_arrays=True, reply_handler=self.__get_device_props_reply_cb, error_handler=self.__get_device_props_error_cb) @@ -938,8 +980,7 @@ class WiredDeviceObserver(object): def _update_state(self, state): if state == network.DEVICE_STATE_ACTIVATED: - props = dbus.Interface(self._device, - 'org.freedesktop.DBus.Properties') + props = dbus.Interface(self._device, dbus.PROPERTIES_IFACE) address = props.Get(_NM_DEVICE_IFACE, 'Ip4Address') speed = props.Get(_NM_WIRED_IFACE, 'Speed') self._device_view = WiredDeviceView(speed, address) @@ -950,6 +991,7 @@ class WiredDeviceObserver(object): del self._device_view self._device_view = None + class GsmDeviceObserver(object): def __init__(self, device, tray): self._device = device @@ -964,6 +1006,7 @@ class GsmDeviceObserver(object): self._tray.remove_device(self._device_view) self._device_view = None + class NetworkManagerObserver(object): def __init__(self, tray): self._bus = dbus.SystemBus() @@ -997,15 +1040,17 @@ class NetworkManagerObserver(object): def _check_device(self, device_op): nm_device = self._bus.get_object(_NM_SERVICE, device_op) - props = dbus.Interface(nm_device, 'org.freedesktop.DBus.Properties') + props = dbus.Interface(nm_device, dbus.PROPERTIES_IFACE) device_type = props.Get(_NM_DEVICE_IFACE, 'DeviceType') if device_type == network.DEVICE_TYPE_802_3_ETHERNET: device = WiredDeviceObserver(nm_device, self._tray) self._devices[device_op] = device - elif device_type in [network.DEVICE_TYPE_802_11_WIRELESS, - network.DEVICE_TYPE_802_11_OLPC_MESH]: - device = WirelessDeviceObserver(nm_device, self._tray, device_type) + elif device_type == network.DEVICE_TYPE_802_11_WIRELESS: + device = WirelessDeviceObserver(nm_device, self._tray) + self._devices[device_op] = device + elif device_type == network.DEVICE_TYPE_802_11_OLPC_MESH: + device = MeshDeviceObserver(nm_device, self._tray) self._devices[device_op] = device elif device_type == network.DEVICE_TYPE_GSM_MODEM: device = GsmDeviceObserver(nm_device, self._tray) @@ -1020,5 +1065,6 @@ class NetworkManagerObserver(object): device.disconnect() del self._devices[device_op] + def setup(tray): device_observer = NetworkManagerObserver(tray) diff --git a/extensions/deviceicon/speaker.py b/extensions/deviceicon/speaker.py index 3a54464..d396bfb 100644 --- a/extensions/deviceicon/speaker.py +++ b/extensions/deviceicon/speaker.py @@ -32,12 +32,13 @@ from jarabe.model import sound _ICON_NAME = 'speaker' + class DeviceView(TrayIcon): FRAME_POSITION_RELATIVE = 103 def __init__(self): - client = gconf.client_get_default() + client = gconf.client_get_default() self._color = XoColor(client.get_string('/desktop/sugar/user/color')) TrayIcon.__init__(self, icon_name=_ICON_NAME, xo_color=self._color) @@ -70,22 +71,24 @@ class DeviceView(TrayIcon): xo_color = XoColor('%s,%s' % (style.COLOR_WHITE.get_svg(), style.COLOR_WHITE.get_svg())) - self.icon.props.icon_name = get_icon_state(name, current_level, step=-1) + self.icon.props.icon_name = get_icon_state(name, current_level, + step=-1) self.icon.props.xo_color = xo_color def __button_release_event_cb(self, widget, event): - if event.button == 1: - self._model.props.muted = not self._model.props.muted - return True - else: + if event.button != 1: return False + self.palette_invoker.notify_right_click() + return True + def __expose_event_cb(self, *args): self._update_info() def __speaker_status_changed_cb(self, pspec_, param_): self._update_info() + class SpeakerPalette(Palette): def __init__(self, primary_text, model): @@ -167,10 +170,11 @@ class SpeakerPalette(Palette): self._update_level() self._update_muted() + class DeviceModel(gobject.GObject): __gproperties__ = { - 'level' : (int, None, None, 0, 100, 0, gobject.PARAM_READWRITE), - 'muted' : (bool, None, None, False, gobject.PARAM_READWRITE), + 'level': (int, None, None, 0, 100, 0, gobject.PARAM_READWRITE), + 'muted': (bool, None, None, False, gobject.PARAM_READWRITE), } def __init__(self): @@ -201,16 +205,17 @@ class DeviceModel(gobject.GObject): return 'speaker' def do_get_property(self, pspec): - if pspec.name == "level": + if pspec.name == 'level': return self._get_level() - elif pspec.name == "muted": + elif pspec.name == 'muted': return self._get_muted() def do_set_property(self, pspec, value): - if pspec.name == "level": + if pspec.name == 'level': self._set_level(value) - elif pspec.name == "muted": + elif pspec.name == 'muted': self._set_muted(value) + def setup(tray): tray.add_device(DeviceView()) diff --git a/extensions/deviceicon/touchpad.py b/extensions/deviceicon/touchpad.py new file mode 100644 index 0000000..b3b34f5 --- /dev/null +++ b/extensions/deviceicon/touchpad.py @@ -0,0 +1,136 @@ +# Copyright (C) 2010, Walter Bender, 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 + + +from gettext import gettext as _ +import os + +import gtk +import gconf + +import logging + +from sugar.graphics.tray import TrayIcon +from sugar.graphics.xocolor import XoColor +from sugar.graphics.palette import Palette +from sugar.graphics import style + +from jarabe.frame.frameinvoker import FrameWidgetInvoker + +TOUCHPAD_MODE_CAPACITIVE = 'capacitive' +TOUCHPAD_MODE_RESISTIVE = 'resistive' +TOUCHPAD_MODES = [TOUCHPAD_MODE_CAPACITIVE, TOUCHPAD_MODE_RESISTIVE] +STATUS_TEXT = { + TOUCHPAD_MODE_CAPACITIVE: _('finger'), + TOUCHPAD_MODE_RESISTIVE: _('stylus'), +} +STATUS_ICON = { + TOUCHPAD_MODE_CAPACITIVE: 'touchpad-' + TOUCHPAD_MODE_CAPACITIVE, + TOUCHPAD_MODE_RESISTIVE: 'touchpad-' + TOUCHPAD_MODE_RESISTIVE, +} +# NODE_PATH is used to communicate with the touchpad device. +NODE_PATH = '/sys/devices/platform/i8042/serio1/ptmode' + + +class DeviceView(TrayIcon): + """ Manage the touchpad mode from the device palette on the Frame. """ + + FRAME_POSITION_RELATIVE = 500 + + def __init__(self): + """ Create the icon that represents the touchpad. """ + icon_name = STATUS_ICON[_read_touchpad_mode()] + + client = gconf.client_get_default() + color = XoColor(client.get_string('/desktop/sugar/user/color')) + TrayIcon.__init__(self, icon_name=icon_name, xo_color=color) + + self.set_palette_invoker(FrameWidgetInvoker(self)) + self.connect('button-release-event', self.__button_release_event_cb) + + def create_palette(self): + """ Create a palette for this icon; called by the Sugar framework + when a palette needs to be displayed. """ + self.palette = ResourcePalette(_('My touchpad'), self.icon) + self.palette.set_group_id('frame') + return self.palette + + def __button_release_event_cb(self, widget, event): + """ Callback for button release event; used to invoke touchpad-mode + change. """ + self.palette.toggle_mode() + return True + + +class ResourcePalette(Palette): + """ Palette attached to the decive icon that represents the touchpas. """ + + def __init__(self, primary_text, icon): + """ Create the palette and initilize with current touchpad status. """ + Palette.__init__(self, label=primary_text) + + self._icon = icon + + vbox = gtk.VBox() + self.set_content(vbox) + + self._status_text = gtk.Label() + vbox.pack_start(self._status_text, padding=style.DEFAULT_PADDING) + self._status_text.show() + + vbox.show() + + self._mode = _read_touchpad_mode() + self._update() + + def _update(self): + """ Update the label and icon based on the current mode. """ + self._status_text.set_label(STATUS_TEXT[self._mode]) + self._icon.props.icon_name = STATUS_ICON[self._mode] + + def toggle_mode(self): + """ Toggle the touchpad mode. """ + self._mode = TOUCHPAD_MODES[1 - TOUCHPAD_MODES.index(self._mode)] + _write_touchpad_mode(self._mode) + self._update() + + +def setup(tray): + """ Initialize the devic icon; called by the shell when initializing the + Frame. """ + if os.path.exists(NODE_PATH): + tray.add_device(DeviceView()) + _write_touchpad_mode(TOUCHPAD_MODE_CAPACITIVE) + + +def _read_touchpad_mode(): + """ Read the touchpad mode from the node path. """ + node_file_handle = open(NODE_PATH, 'r') + text = node_file_handle.read() + node_file_handle.close() + + return TOUCHPAD_MODES[int(text[0])] + + +def _write_touchpad_mode(touchpad): + """ Write the touchpad mode to the node path. """ + try: + node_file_handle = open(NODE_PATH, 'w') + except IOError, e: + logging.error('Error opening %s for writing: %s', NODE_PATH, e) + return + node_file_handle.write(str(TOUCHPAD_MODES.index(touchpad))) + node_file_handle.close() diff --git a/extensions/deviceicon/volume.py b/extensions/deviceicon/volume.py index e7f62a2..ea2377d 100644 --- a/extensions/deviceicon/volume.py +++ b/extensions/deviceicon/volume.py @@ -28,8 +28,10 @@ from jarabe.journal import journalactivity from jarabe.view.palettes import VolumePalette from jarabe.frame.frameinvoker import FrameWidgetInvoker + _icons = {} + class DeviceView(TrayIcon): FRAME_POSITION_RELATIVE = 500 @@ -70,9 +72,11 @@ class DeviceView(TrayIcon): journal.reveal() return True + def setup(tray): gobject.idle_add(_setup_volumes, tray) + def _setup_volumes(tray): volume_monitor = gio.volume_monitor_get() @@ -86,9 +90,11 @@ def _setup_volumes(tray): volume_monitor.connect('mount-added', _mount_added_cb, tray) volume_monitor.connect('mount-removed', _mount_removed_cb, tray) + def _volume_added_cb(volume_monitor, volume, tray): _mount(volume, tray) + def _mount(volume, tray): # Follow Nautilus behaviour here # since it has the same issue with removable device @@ -102,20 +108,23 @@ def _mount(volume, tray): #TODO: pass None as mount_operation, or better, SugarMountOperation volume.mount(gtk.MountOperation(tray.get_toplevel()), _mount_cb) + def _mount_cb(volume, result): logging.debug('_mount_cb %r %r', volume, result) volume.mount_finish(result) + def _mount_added_cb(volume_monitor, mount, tray): _add_device(mount, tray) + def _mount_removed_cb(volume_monitor, mount, tray): icon = _icons[mount] tray.remove_device(icon) del _icons[mount] + def _add_device(mount, tray): icon = DeviceView(mount) _icons[mount] = icon tray.add_device(icon) - diff --git a/extensions/globalkey/screenshot.py b/extensions/globalkey/screenshot.py index 8b4d4c2..0afe964 100644 --- a/extensions/globalkey/screenshot.py +++ b/extensions/globalkey/screenshot.py @@ -17,7 +17,6 @@ import os import tempfile -import time from gettext import gettext as _ import gtk @@ -31,6 +30,7 @@ from jarabe.model import shell BOUND_KEYS = ['<alt>1', 'Print'] + def handle_key_press(key): tmp_dir = os.path.join(env.get_profile_path(), 'data') fd, file_path = tempfile.mkstemp(dir=tmp_dir) @@ -45,7 +45,7 @@ def handle_key_press(key): height=height) screenshot.get_from_drawable(window, window.get_colormap(), x_orig, y_orig, 0, 0, width, height) - screenshot.save(file_path, "png") + screenshot.save(file_path, 'png') client = gconf.client_get_default() color = client.get_string('/desktop/sugar/user/color') @@ -87,14 +87,15 @@ def handle_key_press(key): jobject.destroy() del jobject + def _get_preview_data(screenshot): preview = screenshot.scale_simple(style.zoom(300), style.zoom(225), gtk.gdk.INTERP_BILINEAR) preview_data = [] + def save_func(buf, data): data.append(buf) preview.save_to_callback(save_func, 'png', user_data=preview_data) return dbus.ByteArray(''.join(preview_data)) - diff --git a/extensions/globalkey/viewsource.py b/extensions/globalkey/viewsource.py index df3cd9e..96e7c6b 100644 --- a/extensions/globalkey/viewsource.py +++ b/extensions/globalkey/viewsource.py @@ -18,8 +18,10 @@ from jarabe.view.viewsource import setup_view_source from jarabe.model import shell + BOUND_KEYS = ['0xEC', '<alt><shift>v'] + def handle_key_press(key): shell_model = shell.get_model() activity = shell_model.get_active_activity() diff --git a/po/Makevars b/po/Makevars new file mode 100644 index 0000000..da4dba6 --- /dev/null +++ b/po/Makevars @@ -0,0 +1 @@ +XGETTEXT_OPTIONS = --keyword=_ --keyword=N_ --keyword=C_:1c,2 --keyword=NC_:1c,2 --keyword=Q_ --keyword=g_dgettext:2 --keyword=g_dngettext:2,3 --keyword=g_dpgettext:2 --keyword=g_dpgettext2=2c,3 --keyword=pgettext:1c,2 diff --git a/po/POTFILES.in b/po/POTFILES.in index e9a7f1c..9e46831 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -28,6 +28,7 @@ extensions/cpsection/updater/view.py extensions/deviceicon/battery.py extensions/deviceicon/network.py extensions/deviceicon/speaker.py +extensions/deviceicon/touchpad.py extensions/deviceicon/volume.py extensions/globalkey/screenshot.py data/sugar.schemas.in @@ -41,6 +42,7 @@ src/jarabe/desktop/favoritesview.py src/jarabe/desktop/homebox.py src/jarabe/desktop/keydialog.py src/jarabe/desktop/meshbox.py +src/jarabe/desktop/networkviews.py src/jarabe/desktop/schoolserver.py src/jarabe/frame/activitiestray.py src/jarabe/frame/clipboardmenu.py @@ -52,15 +54,18 @@ src/jarabe/journal/detailview.py src/jarabe/journal/expandedentry.py src/jarabe/journal/journalactivity.py src/jarabe/journal/journaltoolbox.py +src/jarabe/journal/listmodel.py src/jarabe/journal/listview.py src/jarabe/journal/misc.py src/jarabe/journal/modalalert.py src/jarabe/journal/objectchooser.py src/jarabe/journal/palettes.py src/jarabe/journal/volumestoolbar.py +src/jarabe/model/network.py src/jarabe/view/buddymenu.py src/jarabe/view/keyhandler.py src/jarabe/view/launcher.py src/jarabe/view/palettes.py src/jarabe/view/viewsource.py +src/jarabe/util/emulator.py @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: sugar\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2010-01-30 00:31-0500\n" -"PO-Revision-Date: 2010-02-07 17:12+0200\n" +"PO-Revision-Date: 2011-01-24 00:25+0200\n" "Last-Translator: Khaled Hosny <khaledhosny@eglug.org>\n" "Language-Team: Arabic <doc@arabeyes.org>\n" "Language: ar\n" @@ -16,7 +16,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " "&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" -"X-Generator: Pootle 2.0.1\n" +"X-Generator: Pootle 2.0.3\n" "Nplurals=6; Plural=N==0 ? 0: n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 " ": n%100>=11 && n%100<=99 ? 4 : 5;\n" @@ -321,12 +321,12 @@ msgstr "برمجيات محدّثة" #, python-format msgid "You can install %s update" msgid_plural "You can install %s updates" -msgstr[0] "يمكنك تنزيل %s تحديث" -msgstr[1] "يمكنك تنزيل تحديث واحد" -msgstr[2] "يمكنك تنزيل تحديثين" -msgstr[3] "يمكنك تنزيل %s تحديثات" -msgstr[4] "يمكنك تنزيل %s تحديثا" -msgstr[5] "يمكنك تنزيل %s تحديث" +msgstr[0] "لا تحديثات لتثبيتها%.0s" +msgstr[1] "يمكنك تثبيت تحديث واحد%.0s" +msgstr[2] "يمكنك تثبيت تحديثين%.0s" +msgstr[3] "يمكنك تثبيت %s تحديثات" +msgstr[4] "يمكنك تثبيت %s تحديثا" +msgstr[5] "يمكنك تثبيت %s تحديث" #: ../extensions/cpsection/updater/view.py:159 msgid "Checking for updates..." @@ -340,9 +340,9 @@ msgstr "يُثبّت التحديثات..." #, python-format msgid "%s update was installed" msgid_plural "%s updates were installed" -msgstr[0] "لم تُثبّت أي تحديثات" -msgstr[1] "ثُبّت تحديث واحد" -msgstr[2] "ثُبّت تحديثين" +msgstr[0] "لم تُثبّت أي تحديثات%.0s" +msgstr[1] "ثُبّت تحديث واحد%.0s" +msgstr[2] "ثُبّت تحديثين%.0s" msgstr[3] "ثُبّتت %s تحديثات" msgstr[4] "ثُبّت %s تحديثا" msgstr[5] "ثُبّت %s تحديث" @@ -10,14 +10,42 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # This file is distributed under the same license as the PACKAGE package. # Fabian Affolter <fab@fedoraproject.org>, 2007. msgid "" msgstr "" "Project-Id-Version: sugar\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2010-03-11 00:31-0500\n" -"PO-Revision-Date: 2010-03-29 01:49+0200\n" +"POT-Creation-Date: 2010-12-23 01:15-0500\n" +"PO-Revision-Date: 2011-01-18 10:34+0200\n" "Last-Translator: Markus <m.slg@gmx.de>\n" "Language-Team: German <fedora-trans-de@redhat.com>\n" "Language: de\n" @@ -32,43 +60,39 @@ msgstr "" msgid "About Me" msgstr "Über mich" -#: ../extensions/cpsection/aboutme/model.py:43 +#: ../extensions/cpsection/aboutme/model.py:48 msgid "You must enter a name." msgstr "Bitte einen Namen eingeben." -#: ../extensions/cpsection/aboutme/model.py:68 +#: ../extensions/cpsection/aboutme/model.py:75 #, python-format msgid "stroke: color=%s hue=%s" msgstr "Linie: Farbe=%s Farbton=%s" -#: ../extensions/cpsection/aboutme/model.py:71 +#: ../extensions/cpsection/aboutme/model.py:78 #, python-format msgid "stroke: %s" msgstr "Linie: %s" -#: ../extensions/cpsection/aboutme/model.py:73 +#: ../extensions/cpsection/aboutme/model.py:80 #, python-format msgid "fill: color=%s hue=%s" -msgstr "Füllung: Farbe=%s Farbton=%s" +msgstr "Füllung: Farbe=%s Farbton=%s" -#: ../extensions/cpsection/aboutme/model.py:75 +#: ../extensions/cpsection/aboutme/model.py:82 #, python-format msgid "fill: %s" -msgstr "Füllung: %s" +msgstr "Füllung: %s" -#: ../extensions/cpsection/aboutme/model.py:86 +#: ../extensions/cpsection/aboutme/model.py:94 msgid "Error in specified color modifiers." msgstr "Fehler in den angegebenen Farbänderungen." -#: ../extensions/cpsection/aboutme/model.py:89 +#: ../extensions/cpsection/aboutme/model.py:97 msgid "Error in specified colors." msgstr "Fehler in den angegebenen Farben." -#: ../extensions/cpsection/aboutme/view.py:94 ../src/jarabe/intro/window.py:93 -msgid "Name:" -msgstr "Name:" - -#: ../extensions/cpsection/aboutme/view.py:128 +#: ../extensions/cpsection/aboutme/view.py:235 msgid "Click to change your color:" msgstr "Klicken, um deine Farbe zu wechseln:" @@ -76,43 +100,43 @@ msgstr "Klicken, um deine Farbe zu wechseln:" msgid "About my Computer" msgstr "Über meinen Computer" -#: ../extensions/cpsection/aboutcomputer/model.py:28 +#: ../extensions/cpsection/aboutcomputer/model.py:29 msgid "Not available" msgstr "Nicht verfügbar" -#: ../extensions/cpsection/aboutcomputer/view.py:60 +#: ../extensions/cpsection/aboutcomputer/view.py:61 msgid "Identity" msgstr "Identität" -#: ../extensions/cpsection/aboutcomputer/view.py:69 +#: ../extensions/cpsection/aboutcomputer/view.py:70 msgid "Serial Number:" msgstr "Seriennummer:" -#: ../extensions/cpsection/aboutcomputer/view.py:91 +#: ../extensions/cpsection/aboutcomputer/view.py:92 msgid "Software" msgstr "Software" -#: ../extensions/cpsection/aboutcomputer/view.py:100 +#: ../extensions/cpsection/aboutcomputer/view.py:101 msgid "Build:" msgstr "Version:" -#: ../extensions/cpsection/aboutcomputer/view.py:115 +#: ../extensions/cpsection/aboutcomputer/view.py:116 msgid "Sugar:" msgstr "Sugar:" -#: ../extensions/cpsection/aboutcomputer/view.py:131 +#: ../extensions/cpsection/aboutcomputer/view.py:132 msgid "Firmware:" msgstr "Firmware:" -#: ../extensions/cpsection/aboutcomputer/view.py:146 +#: ../extensions/cpsection/aboutcomputer/view.py:147 msgid "Wireless Firmware:" msgstr "WLAN-Firmware:" -#: ../extensions/cpsection/aboutcomputer/view.py:169 +#: ../extensions/cpsection/aboutcomputer/view.py:170 msgid "Copyright and License" msgstr "Copyright und Lizenz" -#: ../extensions/cpsection/aboutcomputer/view.py:184 +#: ../extensions/cpsection/aboutcomputer/view.py:188 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 " @@ -124,7 +148,7 @@ msgstr "" "darin festgelegten Bedingungen ist es erlaubt, die Software zu verändern " "und/oder Kopien davon zu erstellen und zu verteilen." -#: ../extensions/cpsection/aboutcomputer/view.py:196 +#: ../extensions/cpsection/aboutcomputer/view.py:200 msgid "Full license:" msgstr "Vollständige Lizenz:" @@ -132,11 +156,11 @@ msgstr "Vollständige Lizenz:" msgid "Date & Time" msgstr "Datum & Uhrzeit" -#: ../extensions/cpsection/datetime/model.py:87 +#: ../extensions/cpsection/datetime/model.py:92 msgid "Error timezone does not exist." msgstr "Fehler: unbekannte Zeitzone." -#: ../extensions/cpsection/datetime/view.py:68 ../data/sugar.schemas.in.h:47 +#: ../extensions/cpsection/datetime/view.py:70 ../data/sugar.schemas.in.h:52 msgid "Timezone" msgstr "Zeitzone" @@ -145,51 +169,51 @@ msgstr "Zeitzone" msgid "Frame" msgstr "Rahmen" -#: ../extensions/cpsection/frame/model.py:38 -#: ../extensions/cpsection/frame/model.py:60 +#: ../extensions/cpsection/frame/model.py:41 +#: ../extensions/cpsection/frame/model.py:66 msgid "Value must be an integer." msgstr "Der Wert muss ganzzahlig sein." -#: ../extensions/cpsection/frame/view.py:26 +#: ../extensions/cpsection/frame/view.py:27 msgid "never" msgstr "nie" # (Markus S.) ' unmittelbar'? -#: ../extensions/cpsection/frame/view.py:27 +#: ../extensions/cpsection/frame/view.py:28 msgid "instantaneous" msgstr "sofort" -#: ../extensions/cpsection/frame/view.py:28 +#: ../extensions/cpsection/frame/view.py:29 #, python-format msgid "%s seconds" msgstr "%s Sekunden" -#: ../extensions/cpsection/frame/view.py:52 +#: ../extensions/cpsection/frame/view.py:54 msgid "Activation Delay" msgstr "Aktivierungsverzögerung" -#: ../extensions/cpsection/frame/view.py:76 +#: ../extensions/cpsection/frame/view.py:78 msgid "Corner" msgstr "Ecke" -#: ../extensions/cpsection/frame/view.py:111 +#: ../extensions/cpsection/frame/view.py:113 msgid "Edge" -msgstr "Kante" +msgstr "Rand" #: ../extensions/cpsection/keyboard/__init__.py:21 -#: ../extensions/cpsection/keyboard/view.py:31 +#: ../extensions/cpsection/keyboard/view.py:32 msgid "Keyboard" msgstr "Tastatur" -#: ../extensions/cpsection/keyboard/view.py:189 +#: ../extensions/cpsection/keyboard/view.py:190 msgid "Keyboard Model" msgstr "Tastaturmodell" -#: ../extensions/cpsection/keyboard/view.py:248 +#: ../extensions/cpsection/keyboard/view.py:250 msgid "Key(s) to change layout" msgstr "Taste(n) zum Layoutwechsel" -#: ../extensions/cpsection/keyboard/view.py:318 +#: ../extensions/cpsection/keyboard/view.py:319 msgid "Keyboard Layout(s)" msgstr "Tastaturlayout(s)" @@ -198,21 +222,21 @@ msgstr "Tastaturlayout(s)" msgid "Language" msgstr "Sprache" -#: ../extensions/cpsection/language/model.py:28 +#: ../extensions/cpsection/language/model.py:30 msgid "Could not access ~/.i18n. Create standard settings." msgstr "Zugriff auf ~/.i18n nicht möglich. Erzeuge daher Standardeinstellungen." -#: ../extensions/cpsection/language/model.py:124 +#: ../extensions/cpsection/language/model.py:131 #, python-format msgid "Language for code=%s could not be determined." msgstr "Sprache für Code=%s konnte nicht ermittelt werden." -#: ../extensions/cpsection/language/model.py:144 +#: ../extensions/cpsection/language/model.py:152 #, python-format msgid "Sorry I do not speak '%s'." msgstr "Entschuldigung, ich spreche nicht '%s'." -#: ../extensions/cpsection/language/view.py:56 +#: ../extensions/cpsection/language/view.py:57 msgid "" "Add languages in the order you prefer. If a translation is not available, " "the next in the list will be used." @@ -224,31 +248,31 @@ msgstr "" msgid "Modem Configuration" msgstr "Modem-Konfiguration" -#: ../extensions/cpsection/modemconfiguration/view.py:91 +#: ../extensions/cpsection/modemconfiguration/view.py:94 msgid "Username:" msgstr "Benutzername:" -#: ../extensions/cpsection/modemconfiguration/view.py:102 +#: ../extensions/cpsection/modemconfiguration/view.py:106 msgid "Password:" msgstr "Passwort:" -#: ../extensions/cpsection/modemconfiguration/view.py:113 +#: ../extensions/cpsection/modemconfiguration/view.py:118 msgid "Number:" msgstr "Nummer:" -#: ../extensions/cpsection/modemconfiguration/view.py:124 +#: ../extensions/cpsection/modemconfiguration/view.py:130 msgid "Access Point Name (APN):" -msgstr "Access-Point-Name (APN):" +msgstr "Name des Zugangspunktes (APN):" -#: ../extensions/cpsection/modemconfiguration/view.py:135 +#: ../extensions/cpsection/modemconfiguration/view.py:142 msgid "Personal Identity Number (PIN):" msgstr "Persönliche Kennnumer (PIN):" -#: ../extensions/cpsection/modemconfiguration/view.py:146 +#: ../extensions/cpsection/modemconfiguration/view.py:154 msgid "Personal Unblocking Key (PUK):" msgstr "Persönlicher Freischaltschlüssel (PUK):" -#: ../extensions/cpsection/modemconfiguration/view.py:167 +#: ../extensions/cpsection/modemconfiguration/view.py:175 msgid "" "You will need to provide the following information to set up a mobile " "broadband connection to a cellular (3G) network." @@ -257,52 +281,52 @@ msgstr "" "Mobilfunknetz (3G) aufzubauen." #: ../extensions/cpsection/network/__init__.py:21 -#: ../extensions/cpsection/network/view.py:28 +#: ../extensions/cpsection/network/view.py:29 msgid "Network" msgstr "Netzwerk" -#: ../extensions/cpsection/network/model.py:79 +#: ../extensions/cpsection/network/model.py:68 msgid "State is unknown." msgstr "Status ist nicht bekannt." # (Markus S.) vgl. http://lists.laptop.org/pipermail/localization/2008-July/001232.html -#: ../extensions/cpsection/network/model.py:105 +#: ../extensions/cpsection/network/model.py:96 msgid "Error in specified radio argument use on/off." msgstr "Fehler im angegebenen Funknetzparameter -- on/off verwenden." # (Markus S.) vgl. http://lists.laptop.org/pipermail/localization/2008-July/001232.html -#: ../extensions/cpsection/network/model.py:137 +#: ../extensions/cpsection/network/model.py:133 msgid "Error in specified argument use 0/1." msgstr "Fehler im angegebenen Parameter -- 0/1 verwenden." -#: ../extensions/cpsection/network/view.py:59 +#: ../extensions/cpsection/network/view.py:61 msgid "Wireless" msgstr "Funknetzwerk" -#: ../extensions/cpsection/network/view.py:67 +#: ../extensions/cpsection/network/view.py:69 msgid "Turn off the wireless radio to save battery life" msgstr "Schalte das Funknetz aus, um die Lebensdauer der Batterie zu erhöhen." # (Markus S,) war 'Radio:' -#: ../extensions/cpsection/network/view.py:80 +#: ../extensions/cpsection/network/view.py:82 msgid "Radio" msgstr "Funknetz" -#: ../extensions/cpsection/network/view.py:96 +#: ../extensions/cpsection/network/view.py:98 msgid "Discard network history if you have trouble connecting to the network" msgstr "" "Verwirf die Netzwerk-Chronik, wenn du Schwierigkeiten hast, dich mit dem " "Netzwerk zu verbinden." -#: ../extensions/cpsection/network/view.py:105 +#: ../extensions/cpsection/network/view.py:107 msgid "Discard network history" msgstr "Netzwerk-Chronik verwerfen" -#: ../extensions/cpsection/network/view.py:118 +#: ../extensions/cpsection/network/view.py:120 msgid "Collaboration" msgstr "Zusammenarbeit" -#: ../extensions/cpsection/network/view.py:126 +#: ../extensions/cpsection/network/view.py:128 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 " @@ -312,7 +336,7 @@ msgstr "" "Server können einander sehen, selbst wenn sie sich nicht im selben Netzwerk " "befinden." -#: ../extensions/cpsection/network/view.py:136 +#: ../extensions/cpsection/network/view.py:138 msgid "Server:" msgstr "Server:" @@ -321,25 +345,25 @@ msgid "Power" msgstr "Energieversorgung" # (Markus S.) vgl. http://lists.laptop.org/pipermail/localization/2008-July/001232.html -#: ../extensions/cpsection/power/model.py:85 +#: ../extensions/cpsection/power/model.py:90 msgid "Error in automatic pm argument, use on/off." msgstr "" "Fehler im automatischen Energieverwaltungsparameter -- on/off verwenden." # (Markus S.) vgl. http://lists.laptop.org/pipermail/localization/2008-July/001232.html -#: ../extensions/cpsection/power/model.py:112 +#: ../extensions/cpsection/power/model.py:120 msgid "Error in extreme pm argument, use on/off." msgstr "Fehler im extremen Energieverwaltungsparameter -- on/off verwenden." -#: ../extensions/cpsection/power/view.py:47 +#: ../extensions/cpsection/power/view.py:48 msgid "Power management" msgstr "Energieverwaltung" -#: ../extensions/cpsection/power/view.py:57 +#: ../extensions/cpsection/power/view.py:58 msgid "Automatic power management (increases battery life)" msgstr "Automatische Energieverwaltung (erhöht die Lebensdauer der Batterie)" -#: ../extensions/cpsection/power/view.py:85 +#: ../extensions/cpsection/power/view.py:86 msgid "" "Extreme power management (disableswireless radio, increases battery life)" msgstr "" @@ -381,37 +405,37 @@ msgstr "Deine Software ist auf dem neuesten Stand." #, python-format msgid "You can install %s update" msgid_plural "You can install %s updates" -msgstr[0] "Du kannst %s Update installieren." -msgstr[1] "Du kannst %s Updates installieren." +msgstr[0] "Du kannst %s Aktualisierung installieren." +msgstr[1] "Du kannst %s Aktualisierungen installieren." #: ../extensions/cpsection/updater/view.py:159 msgid "Checking for updates..." -msgstr "Suche nach Updates..." +msgstr "Suche nach Aktualisierungen..." #: ../extensions/cpsection/updater/view.py:164 msgid "Installing updates..." -msgstr "Installiere Updates..." +msgstr "Installiere Aktualisierungen..." -#: ../extensions/cpsection/updater/view.py:172 +#: ../extensions/cpsection/updater/view.py:173 #, python-format msgid "%s update was installed" msgid_plural "%s updates were installed" -msgstr[0] "%s Update wurde installiert." -msgstr[1] "%s Updates wurden installiert." +msgstr[0] "%s Aktualisierung wurde installiert." +msgstr[1] "%s Aktualisierungen wurden installiert." -#: ../extensions/cpsection/updater/view.py:253 +#: ../extensions/cpsection/updater/view.py:255 msgid "Install selected" msgstr "Auswahl installieren" -#: ../extensions/cpsection/updater/view.py:274 +#: ../extensions/cpsection/updater/view.py:276 #, python-format msgid "Download size: %s" msgstr "Downloadgröße: %s" -#: ../extensions/cpsection/updater/view.py:362 +#: ../extensions/cpsection/updater/view.py:364 #, python-format -msgid "From version %(current)d to %(new)s (Size: %(size)s)" -msgstr "Von Version %(current)d auf %(new)s (Größe: %(size)s)" +msgid "From version %(current)s to %(new)s (Size: %(size)s)" +msgstr "Von Version %(current)s auf %(new)s (Größe: %(size)s)" #. TRANS: download size is 0 #: ../extensions/cpsection/updater/view.py:382 @@ -435,28 +459,28 @@ msgstr "%.0f KB" msgid "%.1f MB" msgstr "%.1f MB" -#: ../extensions/deviceicon/battery.py:58 +#: ../extensions/deviceicon/battery.py:60 msgid "My Battery" msgstr "Meine Batterie" -#: ../extensions/deviceicon/battery.py:137 +#: ../extensions/deviceicon/battery.py:141 msgid "Removed" msgstr "Entfernt" -#: ../extensions/deviceicon/battery.py:140 +#: ../extensions/deviceicon/battery.py:144 msgid "Charging" -msgstr "Aufladen" +msgstr "Wird aufgeladen" -#: ../extensions/deviceicon/battery.py:143 +#: ../extensions/deviceicon/battery.py:147 msgid "Very little power remaining" msgstr "Sehr wenig Ladung verbleibend" -#: ../extensions/deviceicon/battery.py:149 +#: ../extensions/deviceicon/battery.py:153 #, python-format msgid "%(hour)d:%(min).2d remaining" msgstr "%(hour)d:%(min).2d verbleibend" -#: ../extensions/deviceicon/battery.py:152 +#: ../extensions/deviceicon/battery.py:156 msgid "Charged" msgstr "Aufgeladen" @@ -469,134 +493,180 @@ msgstr "IP-Addresse: %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:112 +#: ../extensions/deviceicon/network.py:104 msgid "Disconnect..." msgstr "Verbindung trennen..." -#: ../extensions/deviceicon/network.py:117 -msgid "Create new wireless network" -msgstr "Neues Funknetzwerk erstellen" - # 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:123 -#: ../extensions/deviceicon/network.py:285 -#: ../src/jarabe/desktop/meshbox.py:247 ../src/jarabe/desktop/meshbox.py:536 +#: ../extensions/deviceicon/network.py:112 +#: ../extensions/deviceicon/network.py:290 +#: ../src/jarabe/desktop/networkviews.py:239 +#: ../src/jarabe/desktop/networkviews.py:538 +#: ../src/jarabe/desktop/networkviews.py:667 msgid "Connecting..." msgstr "Verbinde..." # TODO: show the channel number -#: ../extensions/deviceicon/network.py:127 -#: ../extensions/deviceicon/network.py:199 -#: ../extensions/deviceicon/network.py:289 -#: ../src/jarabe/desktop/meshbox.py:253 ../src/jarabe/desktop/meshbox.py:542 +#: ../extensions/deviceicon/network.py:116 +#: ../extensions/deviceicon/network.py:182 +#: ../src/jarabe/desktop/networkviews.py:249 +#: ../src/jarabe/desktop/networkviews.py:544 +#: ../src/jarabe/desktop/networkviews.py:673 msgid "Connected" msgstr "Verbunden" -#: ../extensions/deviceicon/network.py:159 +#: ../extensions/deviceicon/network.py:142 msgid "Channel" msgstr "Kanal" -#: ../extensions/deviceicon/network.py:174 +#: ../extensions/deviceicon/network.py:157 msgid "Wired Network" msgstr "Kabelnetzwerk" # (Markus S.) War 'Geschwindigkeit' -#: ../extensions/deviceicon/network.py:202 +#: ../extensions/deviceicon/network.py:185 msgid "Speed" msgstr "Übertragungsrate" -#: ../extensions/deviceicon/network.py:229 +#: ../extensions/deviceicon/network.py:211 msgid "Wireless modem" msgstr "Funkmodem" -#: ../extensions/deviceicon/network.py:277 +#: ../extensions/deviceicon/network.py:278 msgid "Please wait..." msgstr "Bitte warten..." -#: ../extensions/deviceicon/network.py:280 -#: ../src/jarabe/desktop/meshbox.py:163 ../src/jarabe/desktop/meshbox.py:493 +#: ../extensions/deviceicon/network.py:282 +#: ../src/jarabe/desktop/networkviews.py:149 +#: ../src/jarabe/desktop/networkviews.py:492 +#: ../src/jarabe/desktop/networkviews.py:624 msgid "Connect" msgstr "Verbinden" -#: ../extensions/deviceicon/network.py:281 +#: ../extensions/deviceicon/network.py:283 msgid "Disconnected" msgstr "Nicht verbunden" -#: ../extensions/deviceicon/network.py:284 -#: ../src/jarabe/controlpanel/toolbar.py:115 -#: ../src/jarabe/desktop/homebox.py:68 -#: ../src/jarabe/frame/activitiestray.py:700 -#: ../src/jarabe/frame/activitiestray.py:799 -#: ../src/jarabe/frame/activitiestray.py:827 +#: ../extensions/deviceicon/network.py:289 +#: ../src/jarabe/controlpanel/toolbar.py:119 +#: ../src/jarabe/desktop/homebox.py:70 +#: ../src/jarabe/frame/activitiestray.py:587 +#: ../src/jarabe/frame/activitiestray.py:687 +#: ../src/jarabe/frame/activitiestray.py:715 msgid "Cancel" msgstr "Abbrechen" # (mschlager) war 'Nicht verbunden', ich würde aber eher erwarten, dass das die Beschriftung eines Menüeintrags ist, mit dem man eine Verbindung trennt, was dann in der Folge die Meldung 'Disconnecting...' liefert. -#: ../extensions/deviceicon/network.py:288 -#: ../src/jarabe/desktop/meshbox.py:167 +#: ../extensions/deviceicon/network.py:297 +#: ../src/jarabe/desktop/networkviews.py:153 +#: ../src/jarabe/desktop/networkviews.py:496 msgid "Disconnect" msgstr "Verbindung trennen" -#: ../extensions/deviceicon/network.py:292 -msgid "Sim requires Pin/Puk" -msgstr "Sim benötigt PIN/PUK" +#: ../extensions/deviceicon/network.py:327 +msgid "Try connection again" +msgstr "Erneuter Verbindungsversuch" + +#: ../extensions/deviceicon/network.py:330 +#, python-format +msgid "Error: %s" +msgstr "Fehler: %s" + +#: ../extensions/deviceicon/network.py:334 +#, python-format +msgid "Suggestion: %s" +msgstr "Vorschlag: %s" -#: ../extensions/deviceicon/network.py:293 -msgid "Authentication Error" -msgstr "Authentifizierungsfehler" +#: ../extensions/deviceicon/network.py:340 +#: ../extensions/deviceicon/network.py:343 +#, python-format +msgid "Connected for %s" +msgstr "Verbunden für %s" -#: ../extensions/deviceicon/network.py:538 +#: ../extensions/deviceicon/network.py:348 +#: ../extensions/deviceicon/network.py:349 #, python-format -msgid "%s's network" -msgstr "Netzwerk von %s" +msgid "%d KB" +msgstr "%d KB" + +#: ../extensions/deviceicon/network.py:354 +msgid "Check your Pin/Puk configuration." +msgstr "Überprüfe deine Pin/Puk-Einstellungen." + +#: ../extensions/deviceicon/network.py:357 +msgid "Check your Access Point Name (APN) configuration" +msgstr "Überprüfe deine Einstellung für den Namen des Zugangspunktes (APN)." + +#: ../extensions/deviceicon/network.py:361 +msgid "Check the Number configuration." +msgstr "Überprüfe deine Einstellung der Nummer." -#: ../extensions/deviceicon/network.py:605 -#: ../extensions/deviceicon/network.py:664 +#: ../extensions/deviceicon/network.py:363 +msgid "Check your configuration." +msgstr "Überprüfe deine Einstellungen." + +#: ../extensions/deviceicon/network.py:615 msgid "Mesh Network" msgstr "Maschennetzwerk" -#: ../extensions/deviceicon/network.py:869 +#: ../extensions/deviceicon/network.py:658 #, python-format -msgid "Data sent %d KB / received %d KB" -msgstr "gesendet %d KB / empfangen %d KB" +msgid "Mesh Network %s" +msgstr "Maschennetzwerk %s" + +#: ../extensions/deviceicon/network.py:782 +msgid "No GSM connection available." +msgstr "Keine GSM-Verbindung verfügbar." -#: ../extensions/deviceicon/network.py:880 -msgid "Connection time " -msgstr "Verbindungsdauer " +#: ../extensions/deviceicon/network.py:783 +msgid "Create a connection in the control panel." +msgstr "Verbindung in der Menüleiste erstellen." -#: ../extensions/deviceicon/speaker.py:59 +#: ../extensions/deviceicon/speaker.py:60 msgid "My Speakers" msgstr "Meine Lautsprecher" -#: ../extensions/deviceicon/speaker.py:133 +#: ../extensions/deviceicon/speaker.py:136 msgid "Unmute" msgstr "Laut schalten" -#: ../extensions/deviceicon/speaker.py:136 +#: ../extensions/deviceicon/speaker.py:139 msgid "Mute" msgstr "Stumm schalten" +#: ../extensions/deviceicon/touchpad.py:37 +msgid "finger" +msgstr "Finger" + +#: ../extensions/deviceicon/touchpad.py:38 +msgid "stylus" +msgstr "Stift" + +#: ../extensions/deviceicon/touchpad.py:67 +msgid "My touchpad" +msgstr "Mein Touchpad" + # (Markus S.) 'Zelle'? #: ../extensions/globalkey/screenshot.py:59 msgid "Mesh" msgstr "Masche" #: ../extensions/globalkey/screenshot.py:61 -#: ../src/jarabe/frame/zoomtoolbar.py:39 +#: ../src/jarabe/frame/zoomtoolbar.py:40 msgid "Group" msgstr "Gruppe" # (Markus S.) war 'Zuhause', vgl. stuffer-sheet #: ../extensions/globalkey/screenshot.py:63 -#: ../src/jarabe/frame/zoomtoolbar.py:41 +#: ../src/jarabe/frame/zoomtoolbar.py:42 msgid "Home" msgstr "Startbildschirm" #: ../extensions/globalkey/screenshot.py:69 -#: ../src/jarabe/frame/zoomtoolbar.py:43 +#: ../src/jarabe/frame/zoomtoolbar.py:44 msgid "Activity" msgstr "Aktivität" @@ -627,6 +697,10 @@ msgid "Backup URL" msgstr "Backup-URL" #: ../data/sugar.schemas.in.h:4 +msgid "Bundle IDs of protected activities" +msgstr "IDs geschützter Aktivitäten bündeln" + +#: ../data/sugar.schemas.in.h:5 msgid "" "Color for the XO icon that is used throughout the desktop. The string is " "composed of the stroke color and fill color, format is that of rbg colors. " @@ -636,103 +710,103 @@ msgstr "" "Zeichenkette setzt sich aus der Linien- und der Füllfarbe zusammen, die " "jeweils als RGB-Farben angeben werden. Beispiel: #AC32FF,#9A5200" -#: ../data/sugar.schemas.in.h:5 +#: ../data/sugar.schemas.in.h:6 msgid "Corner Delay" msgstr "Eckenverzögerung" -#: ../data/sugar.schemas.in.h:6 +#: ../data/sugar.schemas.in.h:7 msgid "Default font face" msgstr "Standard-Schriftart" -#: ../data/sugar.schemas.in.h:7 +#: ../data/sugar.schemas.in.h:8 msgid "Default font size" msgstr "Standard-Schriftgröße" -#: ../data/sugar.schemas.in.h:8 +#: ../data/sugar.schemas.in.h:9 msgid "Default nick" msgstr "Standard-Benutzername" -#: ../data/sugar.schemas.in.h:9 +#: ../data/sugar.schemas.in.h:10 msgid "Delay for the activation of the frame using the corners." msgstr "Verzögerung bei der Aktivierung eines Rahmens über die Ecken." -#: ../data/sugar.schemas.in.h:10 +#: ../data/sugar.schemas.in.h:11 msgid "Delay for the activation of the frame using the edges." msgstr "Verzögerung bei der Aktivierung eines Rahmens über die Ränder." -#: ../data/sugar.schemas.in.h:11 +#: ../data/sugar.schemas.in.h:12 msgid "Directory to search for translations" msgstr "Verzeichnis zur Suche nach Übersetzungen" -#: ../data/sugar.schemas.in.h:12 +#: ../data/sugar.schemas.in.h:13 msgid "Edge Delay" msgstr "Randverzögerung" -#: ../data/sugar.schemas.in.h:13 +#: ../data/sugar.schemas.in.h:14 msgid "Favorites Layout" msgstr "Favoriten-Layout" -#: ../data/sugar.schemas.in.h:14 +#: ../data/sugar.schemas.in.h:15 msgid "Favorites resume mode" msgstr "Favoriten-Wiederaufnahmemodus" -#: ../data/sugar.schemas.in.h:15 +#: ../data/sugar.schemas.in.h:16 msgid "Font face that is used throughout the desktop." msgstr "Schriftart, die auf dem Desktop benutzt wird." -#: ../data/sugar.schemas.in.h:16 +#: ../data/sugar.schemas.in.h:17 msgid "Font size that is used throughout the desktop." msgstr "Schriftgröße, die auf dem Desktop benutzt wird." -#: ../data/sugar.schemas.in.h:17 +#: ../data/sugar.schemas.in.h:18 msgid "GSM network APN" msgstr "GSM-Netzwerk APN" -#: ../data/sugar.schemas.in.h:18 +#: ../data/sugar.schemas.in.h:19 msgid "GSM network PIN" msgstr "GSM-Netzwerk PIN" -#: ../data/sugar.schemas.in.h:19 +#: ../data/sugar.schemas.in.h:20 msgid "GSM network PUK" msgstr "GSM-Netzwerk PUK" -#: ../data/sugar.schemas.in.h:20 +#: ../data/sugar.schemas.in.h:21 msgid "GSM network access point name configuration" -msgstr "GSM-Netzwerk Namenseinstellung Zugriffspunkt" +msgstr "GSM-Netzwerk Einstellung des Namens des Zugangspunktes" -#: ../data/sugar.schemas.in.h:21 +#: ../data/sugar.schemas.in.h:22 msgid "GSM network number" msgstr "GSM-Netzwerk Nummer" -#: ../data/sugar.schemas.in.h:22 +#: ../data/sugar.schemas.in.h:23 msgid "GSM network password" msgstr "GSM-Netzwerk Passwort" -#: ../data/sugar.schemas.in.h:23 +#: ../data/sugar.schemas.in.h:24 msgid "GSM network password configuration" msgstr "GSM-Netzwerk Passworteinstellung" -#: ../data/sugar.schemas.in.h:24 +#: ../data/sugar.schemas.in.h:25 msgid "GSM network personal identification number configuration" msgstr "GSM-Netzwerk PIN-Einstellung" -#: ../data/sugar.schemas.in.h:25 +#: ../data/sugar.schemas.in.h:26 msgid "GSM network personal unlock key configuration" msgstr "GSM-Netzwerk PUK-Einstellung" -#: ../data/sugar.schemas.in.h:26 +#: ../data/sugar.schemas.in.h:27 msgid "GSM network telephone number configuration" -msgstr "GSM-Netzwerk Telefonnummereinstellung" +msgstr "GSM-Netzwerk Telefonnummerneinstellung" -#: ../data/sugar.schemas.in.h:27 +#: ../data/sugar.schemas.in.h:28 msgid "GSM network username" msgstr "GSM-Netzwerk Benutzername" -#: ../data/sugar.schemas.in.h:28 +#: ../data/sugar.schemas.in.h:29 msgid "GSM network username configuration" -msgstr "GSM-Netzwerk Benutzernameneinstellung" +msgstr "GSM-Netzwerk Benutzernamenseinstellung" -#: ../data/sugar.schemas.in.h:29 +#: ../data/sugar.schemas.in.h:30 msgid "" "If TRUE, Sugar will make us searchable for the other users of the Jabber " "server." @@ -740,112 +814,140 @@ msgstr "" "Falls WAHR, wird Sugar es für andere Nutzer des Jabber-Servers zulassen, uns " "zu suchen." -#: ../data/sugar.schemas.in.h:30 +#: ../data/sugar.schemas.in.h:31 msgid "If TRUE, Sugar will show a \"Log out\" option." msgstr "Falls WAHR, wird Sugar die Option \"Abmelden\" anzeigen." -#: ../data/sugar.schemas.in.h:31 +#: ../data/sugar.schemas.in.h:32 +msgid "If TRUE, Sugar will show a \"Restart\" option." +msgstr "Falls WAHR, wird Sugar die Option \"Neustart\" anzeigen." + +#: ../data/sugar.schemas.in.h:33 +msgid "" +"If TRUE, Sugar will show default Ad-hoc networks for channel 1,6 and 11. If " +"Sugar sees no \"known\" network when it starts, it does autoconnect to an Ad-" +"hoc network." +msgstr "" +"Falls WAHR, wird Sugar standardmäßige Ad-hoc-Netzwerke für Kanal 1, 6 und 11 " +"anzeigen. Wenn Sugar beim Start kein \"bekanntes\" Netzwerk erkennt, " +"verbindet es sich automatisch mit einem Ad-hoc-Netzwerk." + +#: ../data/sugar.schemas.in.h:34 msgid "Jabber Server" msgstr "Jabber-Server" -#: ../data/sugar.schemas.in.h:32 +#: ../data/sugar.schemas.in.h:35 msgid "Keyboard layouts" msgstr "Tastaturlayouts" -#: ../data/sugar.schemas.in.h:33 +#: ../data/sugar.schemas.in.h:36 msgid "Keyboard model" msgstr "Tastaturmodell" -#: ../data/sugar.schemas.in.h:34 +#: ../data/sugar.schemas.in.h:37 msgid "Keyboard options" msgstr "Tastatureinstellungen" -#: ../data/sugar.schemas.in.h:35 +#: ../data/sugar.schemas.in.h:38 msgid "Layout of the favorites view." msgstr "Layout der Favoriten-Ansicht." -#: ../data/sugar.schemas.in.h:36 +#: ../data/sugar.schemas.in.h:39 msgid "" "List of keyboard layouts. Each entry should be in the form layout(variant)" msgstr "" "Liste der Tastaturlayouts. Jeder Eintrag sollte von der Form " "Layout(Variante) sein." -#: ../data/sugar.schemas.in.h:37 +#: ../data/sugar.schemas.in.h:40 msgid "List of keyboard options." msgstr "Liste der Tastatureinstellungen." -#: ../data/sugar.schemas.in.h:38 +#: ../data/sugar.schemas.in.h:41 msgid "Power Automatic" msgstr "Automatische Energieverwaltung" -#: ../data/sugar.schemas.in.h:39 +#: ../data/sugar.schemas.in.h:42 msgid "Power Automatic." msgstr "Automatische Energieverwaltung." # (Markus S.) war 'Extreme Energieverwaltung' -#: ../data/sugar.schemas.in.h:40 +#: ../data/sugar.schemas.in.h:43 msgid "Power Extreme" msgstr "Extremes Energiesparen" # (Markus S.) war 'Extreme Energieverwaltung' -#: ../data/sugar.schemas.in.h:41 +#: ../data/sugar.schemas.in.h:44 msgid "Power Extreme." msgstr "Extremes Energiesparen." -#: ../data/sugar.schemas.in.h:42 +#: ../data/sugar.schemas.in.h:45 msgid "Publish to Gadget" msgstr "Veröffentlichen auf Gerät" -#: ../data/sugar.schemas.in.h:43 +#: ../data/sugar.schemas.in.h:46 msgid "Setting for muting the sound device." msgstr "Einstellung zum Stummschalten der Audio-Ausgabe." -#: ../data/sugar.schemas.in.h:44 +#: ../data/sugar.schemas.in.h:47 msgid "Show Log out" msgstr "Abmelden anzeigen" -#: ../data/sugar.schemas.in.h:45 +#: ../data/sugar.schemas.in.h:48 +msgid "Show Restart" +msgstr "Neustart anzeigen" + +#: ../data/sugar.schemas.in.h:49 +msgid "Show Sugar Ad-hoc networks" +msgstr "Sugar-Ad-hoc-Netzwerke anzeigen" + +#: ../data/sugar.schemas.in.h:50 msgid "Sound Muted" msgstr "Stummgeschaltet" -#: ../data/sugar.schemas.in.h:46 +#: ../data/sugar.schemas.in.h:51 msgid "The keyboard model to be used" msgstr "Das zu verwendende Tastaturmodell" -#: ../data/sugar.schemas.in.h:48 +#: ../data/sugar.schemas.in.h:53 msgid "Timezone setting for the system." msgstr "Zeitzoneneinstellung des Systems." -#: ../data/sugar.schemas.in.h:49 +#: ../data/sugar.schemas.in.h:54 msgid "Url of the jabber server to use." msgstr "URL des zu nutzenden Jabber-Servers." -#: ../data/sugar.schemas.in.h:50 +#: ../data/sugar.schemas.in.h:55 msgid "Url where the backup is saved to." msgstr "URL, unter der das Backup gespeichert wird." -#: ../data/sugar.schemas.in.h:51 +#: ../data/sugar.schemas.in.h:56 msgid "User Color" msgstr "Benutzerfarbe" -#: ../data/sugar.schemas.in.h:52 +#: ../data/sugar.schemas.in.h:57 msgid "User Name" msgstr "Benutzername" -#: ../data/sugar.schemas.in.h:53 +#: ../data/sugar.schemas.in.h:58 msgid "User name that is used throughout the desktop." msgstr "Benutzername, der überall auf dem Desktop benutzt wird." -#: ../data/sugar.schemas.in.h:54 +#: ../data/sugar.schemas.in.h:59 +msgid "" +"Users will not be allowed to erase these activities through the list view." +msgstr "" +"Benutzer werden diese Aktivitäten nicht in der Listenansicht löschen können." + +#: ../data/sugar.schemas.in.h:60 msgid "Volume Level" msgstr "Lautstärke" -#: ../data/sugar.schemas.in.h:55 +#: ../data/sugar.schemas.in.h:61 msgid "Volume level for the sound device." msgstr "Lautstärkepegel für die Audio-Ausgabe." -#: ../data/sugar.schemas.in.h:56 +#: ../data/sugar.schemas.in.h:62 msgid "" "When in resume mode, clicking on a favorite icon will cause the last entry " "for that activity to be resumed." @@ -877,7 +979,7 @@ msgstr "sugar-control-panel: %s" # which must appear in the translated string (msgstr) as well. #. TRANS: Translators, there's a empty line at the end of this string, #. which must appear in the translated string (msgstr) as well. -#: ../src/jarabe/controlpanel/cmd.py:37 +#: ../src/jarabe/controlpanel/cmd.py:38 msgid "" "Usage: sugar-control-panel [ option ] key [ args ... ] \n" " Control for the sugar environment. \n" @@ -899,52 +1001,54 @@ msgstr "" " -g Parameter Den aktuellen Wert für diesen Parameter auslesen\n" " -s Parameter Den aktuellen Wert für diesen Parameter festlegen\n" " -c Parameter Den aktuellen Wert für diesen Parameter zurücksetzen\n" -" " +" " -#: ../src/jarabe/controlpanel/cmd.py:50 +#: ../src/jarabe/controlpanel/cmd.py:52 msgid "To apply your changes you have to restart sugar.\n" msgstr "Um die Änderungen zu übernehmen, muss Sugar neu gestartet werden.\n" -#: ../src/jarabe/controlpanel/gui.py:281 +#: ../src/jarabe/controlpanel/gui.py:285 +#: ../src/jarabe/journal/journaltoolbox.py:437 +#: ../src/jarabe/journal/volumestoolbar.py:158 msgid "Warning" msgstr "Warnung" -#: ../src/jarabe/controlpanel/gui.py:282 -#: ../src/jarabe/controlpanel/sectionview.py:42 +#: ../src/jarabe/controlpanel/gui.py:286 +#: ../src/jarabe/controlpanel/sectionview.py:41 msgid "Changes require restart" msgstr "Neustart zur Übernahme der Änderungen notwendig" -#: ../src/jarabe/controlpanel/gui.py:285 +#: ../src/jarabe/controlpanel/gui.py:289 msgid "Cancel changes" msgstr "Änderungen verwerfen" -#: ../src/jarabe/controlpanel/gui.py:290 ../src/jarabe/desktop/homebox.py:70 +#: ../src/jarabe/controlpanel/gui.py:294 ../src/jarabe/desktop/homebox.py:72 msgid "Later" msgstr "Später" -#: ../src/jarabe/controlpanel/gui.py:294 +#: ../src/jarabe/controlpanel/gui.py:298 msgid "Restart now" msgstr "Jetzt neu starten" -#: ../src/jarabe/controlpanel/toolbar.py:61 ../src/jarabe/intro/window.py:206 +#: ../src/jarabe/controlpanel/toolbar.py:63 ../src/jarabe/intro/window.py:211 msgid "Done" msgstr "Fertig" -#: ../src/jarabe/controlpanel/toolbar.py:121 -#: ../src/jarabe/desktop/favoritesview.py:333 +#: ../src/jarabe/controlpanel/toolbar.py:125 +#: ../src/jarabe/desktop/favoritesview.py:336 msgid "Ok" msgstr "Ok" -#: ../src/jarabe/desktop/activitieslist.py:236 +#: ../src/jarabe/desktop/activitieslist.py:230 #, python-format msgid "Version %s" msgstr "Version %s" -#: ../src/jarabe/desktop/activitieslist.py:357 +#: ../src/jarabe/desktop/activitieslist.py:354 msgid "Confirm erase" msgstr "Löschen bestätigen" -#: ../src/jarabe/desktop/activitieslist.py:359 +#: ../src/jarabe/desktop/activitieslist.py:356 #, python-format msgid "Confirm erase: Do you want to permanently erase %s?" msgstr "Löschen bestätigen: Willst du %s wirklich dauerhaft löschen?" @@ -953,388 +1057,444 @@ msgstr "Löschen bestätigen: Willst du %s wirklich dauerhaft löschen?" # 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:363 -#: ../src/jarabe/frame/clipboardmenu.py:63 -#: ../src/jarabe/view/viewsource.py:218 +#: ../src/jarabe/desktop/activitieslist.py:360 +#: ../src/jarabe/frame/clipboardmenu.py:64 +#: ../src/jarabe/view/viewsource.py:221 msgid "Keep" msgstr "Behalten" -#: ../src/jarabe/desktop/activitieslist.py:366 -#: ../src/jarabe/desktop/activitieslist.py:409 -#: ../src/jarabe/journal/journaltoolbox.py:360 -#: ../src/jarabe/journal/palettes.py:106 +#: ../src/jarabe/desktop/activitieslist.py:363 +#: ../src/jarabe/desktop/activitieslist.py:417 +#: ../src/jarabe/journal/journaltoolbox.py:391 +#: ../src/jarabe/journal/palettes.py:112 msgid "Erase" msgstr "Löschen" -#: ../src/jarabe/desktop/activitieslist.py:430 +#: ../src/jarabe/desktop/activitieslist.py:432 msgid "Remove favorite" msgstr "Favorit entfernen" -#: ../src/jarabe/desktop/activitieslist.py:434 +#: ../src/jarabe/desktop/activitieslist.py:436 msgid "Make favorite" msgstr "Zum Favoriten machen" # 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 +#: ../src/jarabe/desktop/favoriteslayout.py:127 msgid "Freeform" msgstr "Freie Form" # 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:198 +#: ../src/jarabe/desktop/favoriteslayout.py:215 msgid "Ring" msgstr "Ring" # 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:337 +#: ../src/jarabe/desktop/favoriteslayout.py:402 msgid "Spiral" msgstr "Spirale" # 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:404 +#: ../src/jarabe/desktop/favoriteslayout.py:472 msgid "Box" msgstr "Rechteck" # 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:445 +#: ../src/jarabe/desktop/favoriteslayout.py:515 msgid "Triangle" msgstr "Dreieck" -#: ../src/jarabe/desktop/favoritesview.py:324 +#: ../src/jarabe/desktop/favoritesview.py:327 msgid "Registration Failed" msgstr "Anmeldung fehlgeschlagen" -#: ../src/jarabe/desktop/favoritesview.py:325 +#: ../src/jarabe/desktop/favoritesview.py:328 #, python-format msgid "%s" msgstr "%s" -#: ../src/jarabe/desktop/favoritesview.py:327 +#: ../src/jarabe/desktop/favoritesview.py:330 msgid "Registration Successful" msgstr "Anmeldung erfolgreich" -#: ../src/jarabe/desktop/favoritesview.py:328 +#: ../src/jarabe/desktop/favoritesview.py:331 msgid "You are now registered with your school server." msgstr "Du bist nun an deinem Schulserver angemeldet." -#: ../src/jarabe/desktop/favoritesview.py:631 +#: ../src/jarabe/desktop/favoritesview.py:626 msgid "Register" msgstr "Am Schulserver anmelden" -#: ../src/jarabe/desktop/homebox.py:63 +#: ../src/jarabe/desktop/homebox.py:65 msgid "Software Update" msgstr "Software-Aktualisierung" -#: ../src/jarabe/desktop/homebox.py:64 +#: ../src/jarabe/desktop/homebox.py:66 msgid "Update your activities to ensure compatibility with your new software" msgstr "" "Aktualisiere deine Aktivitäten, um die Kompatibilität mit deiner neuen " "Software sicherzustellen." -#: ../src/jarabe/desktop/homebox.py:73 +#: ../src/jarabe/desktop/homebox.py:75 msgid "Check now" msgstr "Jetzt prüfen" -#: ../src/jarabe/desktop/homebox.py:192 +#: ../src/jarabe/desktop/homebox.py:193 msgid "List view" msgstr "Listenansicht" -#: ../src/jarabe/desktop/homebox.py:193 +#: ../src/jarabe/desktop/homebox.py:194 msgid "<Ctrl>2" msgstr "<Ctrl>2" -#: ../src/jarabe/desktop/homebox.py:255 +#: ../src/jarabe/desktop/homebox.py:257 msgid "Favorites view" msgstr "Favoritenansicht" -#: ../src/jarabe/desktop/homebox.py:256 +#: ../src/jarabe/desktop/homebox.py:258 msgid "<Ctrl>1" msgstr "<Ctrl>1" -#: ../src/jarabe/desktop/keydialog.py:135 +#: ../src/jarabe/desktop/keydialog.py:143 msgid "Key Type:" msgstr "Schlüsseltyp:" -#: ../src/jarabe/desktop/keydialog.py:155 +#: ../src/jarabe/desktop/keydialog.py:163 msgid "Authentication Type:" msgstr "Authentifizierungstyp:" -#: ../src/jarabe/desktop/keydialog.py:220 +#: ../src/jarabe/desktop/keydialog.py:229 msgid "WPA & WPA2 Personal" msgstr "WPA & WPA2 Personal" -#: ../src/jarabe/desktop/keydialog.py:229 +#: ../src/jarabe/desktop/keydialog.py:238 msgid "Wireless Security:" msgstr "WLAN-Sicherheit:" -#: ../src/jarabe/desktop/meshbox.py:491 -#, python-format -msgid "Mesh Network %d" -msgstr "Maschennetzwerk %d" - # TRANS: Action label for resuming an activity. #. TRANS: Action label for resuming an activity. -#: ../src/jarabe/desktop/meshbox.py:628 -#: ../src/jarabe/frame/activitiestray.py:735 -#: ../src/jarabe/journal/journaltoolbox.py:428 -#: ../src/jarabe/journal/palettes.py:66 ../src/jarabe/view/palettes.py:67 +#: ../src/jarabe/desktop/meshbox.py:109 +#: ../src/jarabe/frame/activitiestray.py:622 +#: ../src/jarabe/journal/journaltoolbox.py:485 +#: ../src/jarabe/journal/palettes.py:66 ../src/jarabe/view/palettes.py:78 msgid "Resume" msgstr "Fortsetzen" -#: ../src/jarabe/desktop/meshbox.py:633 -#: ../src/jarabe/frame/activitiestray.py:233 +#: ../src/jarabe/desktop/meshbox.py:114 +#: ../src/jarabe/frame/activitiestray.py:173 msgid "Join" -msgstr "Mitmachen" +msgstr "Beitreten" + +#: ../src/jarabe/desktop/networkviews.py:489 +#, python-format +msgid "Ad-hoc Network %d" +msgstr "Ad-hoc-Netzwerk %d" + +#: ../src/jarabe/desktop/networkviews.py:622 +#, python-format +msgid "Mesh Network %d" +msgstr "Maschennetzwerk %d" -#: ../src/jarabe/desktop/schoolserver.py:104 +#: ../src/jarabe/desktop/schoolserver.py:131 msgid "Cannot connect to the server." msgstr "Kann nicht mit dem Server verbinden." -#: ../src/jarabe/desktop/schoolserver.py:109 +#: ../src/jarabe/desktop/schoolserver.py:136 msgid "The server could not complete the request." msgstr "Der Server konnte die Anforderung nicht erfüllen." -#: ../src/jarabe/frame/activitiestray.py:238 -#: ../src/jarabe/frame/activitiestray.py:672 +#: ../src/jarabe/frame/activitiestray.py:178 +#: ../src/jarabe/frame/activitiestray.py:559 msgid "Decline" msgstr "Ablehnen" -#: ../src/jarabe/frame/activitiestray.py:624 +#: ../src/jarabe/frame/activitiestray.py:509 #, python-format msgid "%dB" msgstr "%dB" -#: ../src/jarabe/frame/activitiestray.py:626 +#: ../src/jarabe/frame/activitiestray.py:511 #, python-format msgid "%dKB" msgstr "%dKB" -#: ../src/jarabe/frame/activitiestray.py:628 +#: ../src/jarabe/frame/activitiestray.py:513 #, python-format msgid "%dMB" msgstr "%dMB" -#: ../src/jarabe/frame/activitiestray.py:645 +#: ../src/jarabe/frame/activitiestray.py:530 #, python-format msgid "%s of %s" msgstr "%s von %s" -#: ../src/jarabe/frame/activitiestray.py:657 +#: ../src/jarabe/frame/activitiestray.py:544 #, python-format msgid "Transfer from %r" msgstr "Übertragung von %r" -#: ../src/jarabe/frame/activitiestray.py:667 +#: ../src/jarabe/frame/activitiestray.py:554 msgid "Accept" msgstr "Akzeptieren" -#: ../src/jarabe/frame/activitiestray.py:690 -#: ../src/jarabe/frame/activitiestray.py:817 +#: ../src/jarabe/frame/activitiestray.py:577 +#: ../src/jarabe/frame/activitiestray.py:705 #, python-format msgid "%s (%s)" msgstr "%s (%s)" -#: ../src/jarabe/frame/activitiestray.py:724 -#: ../src/jarabe/frame/activitiestray.py:852 +#: ../src/jarabe/frame/activitiestray.py:611 +#: ../src/jarabe/frame/activitiestray.py:740 msgid "Dismiss" msgstr "Verwerfen" -#: ../src/jarabe/frame/activitiestray.py:787 +#: ../src/jarabe/frame/activitiestray.py:675 #, python-format msgid "Transfer to %r" msgstr "Übertragung zu %r" -#: ../src/jarabe/frame/clipboardmenu.py:53 ../src/jarabe/view/palettes.py:221 +#: ../src/jarabe/frame/clipboardmenu.py:54 ../src/jarabe/view/palettes.py:220 msgid "Remove" msgstr "Entfernen" -#: ../src/jarabe/frame/clipboardmenu.py:58 -#: ../src/jarabe/frame/clipboardmenu.py:81 +#: ../src/jarabe/frame/clipboardmenu.py:59 +#: ../src/jarabe/frame/clipboardmenu.py:82 msgid "Open" msgstr "Öffnen" -#: ../src/jarabe/frame/clipboardmenu.py:86 +#: ../src/jarabe/frame/clipboardmenu.py:87 msgid "Open with" msgstr "Öffnen mit" # (Markus S.) 'clipping', nicht 'clipped' -#: ../src/jarabe/frame/clipboardobject.py:49 +#: ../src/jarabe/frame/clipboardobject.py:50 #, python-format msgid "%s clipping" msgstr "%s ausgeschnitten" -#: ../src/jarabe/frame/zoomtoolbar.py:37 +#: ../src/jarabe/frame/zoomtoolbar.py:38 msgid "Neighborhood" msgstr "Umgebung" -#: ../src/jarabe/frame/zoomtoolbar.py:37 +#: ../src/jarabe/frame/zoomtoolbar.py:38 msgid "F1" msgstr "F1" -#: ../src/jarabe/frame/zoomtoolbar.py:39 +#: ../src/jarabe/frame/zoomtoolbar.py:40 msgid "F2" msgstr "F2" -#: ../src/jarabe/frame/zoomtoolbar.py:41 +#: ../src/jarabe/frame/zoomtoolbar.py:42 msgid "F3" msgstr "F3" -#: ../src/jarabe/frame/zoomtoolbar.py:43 +#: ../src/jarabe/frame/zoomtoolbar.py:44 msgid "F4" msgstr "F4" -#: ../src/jarabe/intro/window.py:128 +#: ../src/jarabe/intro/window.py:96 +msgid "Name:" +msgstr "Name:" + +#: ../src/jarabe/intro/window.py:132 msgid "Click to change color:" msgstr "Klicken zum Wechseln der Farbe:" -#: ../src/jarabe/intro/window.py:192 ../src/jarabe/journal/detailview.py:103 +#: ../src/jarabe/intro/window.py:197 ../src/jarabe/journal/detailview.py:105 msgid "Back" msgstr "Zurück" # (Markus S.) war 'Nächste' -#: ../src/jarabe/intro/window.py:209 +#: ../src/jarabe/intro/window.py:214 msgid "Next" msgstr "Vor" -#: ../src/jarabe/journal/expandedentry.py:151 -#: ../src/jarabe/journal/palettes.py:60 +#: ../src/jarabe/journal/expandedentry.py:154 +#: ../src/jarabe/journal/listmodel.py:144 ../src/jarabe/journal/palettes.py:59 msgid "Untitled" msgstr "Ohne Titel" -#: ../src/jarabe/journal/expandedentry.py:242 +#: ../src/jarabe/journal/expandedentry.py:243 msgid "No preview" msgstr "Keine Vorschau" -#: ../src/jarabe/journal/expandedentry.py:261 +#: ../src/jarabe/journal/expandedentry.py:262 #, python-format msgid "Kind: %s" msgstr "Art: %s" -#: ../src/jarabe/journal/expandedentry.py:261 +#: ../src/jarabe/journal/expandedentry.py:262 +#: ../src/jarabe/journal/listmodel.py:150 +#: ../src/jarabe/journal/listmodel.py:157 +#: ../src/jarabe/journal/listmodel.py:165 msgid "Unknown" msgstr "Unbekannt" -#: ../src/jarabe/journal/expandedentry.py:262 +#: ../src/jarabe/journal/expandedentry.py:263 #, python-format msgid "Date: %s" msgstr "Datum: %s" -#: ../src/jarabe/journal/expandedentry.py:263 +#: ../src/jarabe/journal/expandedentry.py:264 #, python-format msgid "Size: %s" msgstr "Größe: %s" -#: ../src/jarabe/journal/expandedentry.py:285 ../src/jarabe/journal/misc.py:93 +#: ../src/jarabe/journal/expandedentry.py:292 +#: ../src/jarabe/journal/misc.py:108 msgid "No date" msgstr "Kein Datum" -#: ../src/jarabe/journal/expandedentry.py:292 +#: ../src/jarabe/journal/expandedentry.py:299 msgid "Participants:" msgstr "Teilnehmer:" -#: ../src/jarabe/journal/expandedentry.py:315 +#: ../src/jarabe/journal/expandedentry.py:321 msgid "Description:" msgstr "Beschreibung:" -#: ../src/jarabe/journal/expandedentry.py:340 +#: ../src/jarabe/journal/expandedentry.py:346 msgid "Tags:" msgstr "Stichwörter:" -#: ../src/jarabe/journal/journalactivity.py:108 -#: ../src/jarabe/journal/volumestoolbar.py:47 +#: ../src/jarabe/journal/journalactivity.py:115 +#: ../src/jarabe/journal/journaltoolbox.py:456 +#: ../src/jarabe/journal/volumestoolbar.py:50 msgid "Journal" msgstr "Tagebuch" -#: ../src/jarabe/journal/journaltoolbox.py:67 +#: ../src/jarabe/journal/journaltoolbox.py:69 msgid "Search" msgstr "Suchen" -#: ../src/jarabe/journal/journaltoolbox.py:126 +#: ../src/jarabe/journal/journaltoolbox.py:136 msgid "Anytime" msgstr "Beliebiges Datum" -#: ../src/jarabe/journal/journaltoolbox.py:128 +#: ../src/jarabe/journal/journaltoolbox.py:138 msgid "Today" msgstr "Heute" -#: ../src/jarabe/journal/journaltoolbox.py:130 +#: ../src/jarabe/journal/journaltoolbox.py:140 msgid "Since yesterday" msgstr "Seit gestern" # TRANS: Filter entries modified during the last 7 days. #. TRANS: Filter entries modified during the last 7 days. -#: ../src/jarabe/journal/journaltoolbox.py:132 +#: ../src/jarabe/journal/journaltoolbox.py:142 msgid "Past week" msgstr "Vergangene Woche" # TRANS: Filter entries modified during the last 30 days. #. TRANS: Filter entries modified during the last 30 days. -#: ../src/jarabe/journal/journaltoolbox.py:134 +#: ../src/jarabe/journal/journaltoolbox.py:144 msgid "Past month" msgstr "Vergangener Monat" # TRANS: Filter entries modified during the last 356 days. #. TRANS: Filter entries modified during the last 356 days. -#: ../src/jarabe/journal/journaltoolbox.py:136 +#: ../src/jarabe/journal/journaltoolbox.py:146 msgid "Past year" msgstr "Vergangenes Jahr" -#: ../src/jarabe/journal/journaltoolbox.py:143 +#: ../src/jarabe/journal/journaltoolbox.py:153 msgid "Anyone" msgstr "Alle" -#: ../src/jarabe/journal/journaltoolbox.py:145 +#: ../src/jarabe/journal/journaltoolbox.py:155 msgid "My friends" msgstr "Meine Freunde" -#: ../src/jarabe/journal/journaltoolbox.py:146 +#: ../src/jarabe/journal/journaltoolbox.py:156 msgid "My class" msgstr "Meine Klasse" # TRANS: Item in a combo box that filters by entry type. -#: ../src/jarabe/journal/journaltoolbox.py:274 +#: ../src/jarabe/journal/journaltoolbox.py:298 msgid "Anything" msgstr "Alles" # TODO: Add "Start with" menu item -#: ../src/jarabe/journal/journaltoolbox.py:350 -#: ../src/jarabe/journal/palettes.py:84 +#: ../src/jarabe/journal/journaltoolbox.py:381 +#: ../src/jarabe/journal/palettes.py:90 msgid "Copy" msgstr "Kopieren" +#: ../src/jarabe/journal/journaltoolbox.py:436 +#: ../src/jarabe/journal/volumestoolbar.py:157 +msgid "Entries without a file cannot be copied." +msgstr "Einträge ohne eine Datei lassen sich nicht kopieren." + +#: ../src/jarabe/journal/journaltoolbox.py:445 +#: ../src/jarabe/journal/volumestoolbar.py:166 +#, python-format +msgid "Error while copying the entry. %s" +msgstr "Fehler beim Kopieren des Eintrags. %s" + +#: ../src/jarabe/journal/journaltoolbox.py:446 +#: ../src/jarabe/journal/volumestoolbar.py:167 +msgid "Error" +msgstr "Fehler" + # TRANS: Action label for starting an entry. #. TRANS: Action label for starting an entry. -#: ../src/jarabe/journal/journaltoolbox.py:431 +#: ../src/jarabe/journal/journaltoolbox.py:488 #: ../src/jarabe/journal/palettes.py:69 msgid "Start" msgstr "Start" -#: ../src/jarabe/journal/listview.py:373 +#: ../src/jarabe/journal/journaltoolbox.py:516 +msgid "Sort by date modified" +msgstr "Nach Änderungsdatum sortieren" + +#: ../src/jarabe/journal/journaltoolbox.py:517 +msgid "Sort by date created" +msgstr "Nach Erstellungsdatum sortieren" + +#: ../src/jarabe/journal/journaltoolbox.py:518 +msgid "Sort by size" +msgstr "Nach Größe sortieren" + +#: ../src/jarabe/journal/journaltoolbox.py:527 +msgid "Sort view" +msgstr "Ansicht sortieren" + +#: ../src/jarabe/journal/listview.py:380 msgid "Your Journal is empty" msgstr "Dein Tagebuch ist leer." -#: ../src/jarabe/journal/listview.py:375 +#: ../src/jarabe/journal/listview.py:382 msgid "No matching entries" msgstr "Keine passenden Einträge" -#: ../src/jarabe/journal/listview.py:386 +#: ../src/jarabe/journal/listview.py:393 msgid "Clear search" msgstr "Suchfeld leeren" -#: ../src/jarabe/journal/modalalert.py:63 +#: ../src/jarabe/journal/misc.py:273 +#, python-format +msgid "Older Version Of %s Activity" +msgstr "Ältere Version von %s Aktivität" + +#: ../src/jarabe/journal/misc.py:274 +#, python-format +msgid "Do you want to downgrade to version %s " +msgstr "Möchtest Du auf Version %s zurücksetzen?" + +#: ../src/jarabe/journal/modalalert.py:64 msgid "Your Journal is full" msgstr "Dein Tagebuch ist voll." -#: ../src/jarabe/journal/modalalert.py:67 +#: ../src/jarabe/journal/modalalert.py:68 msgid "Please delete some old Journal entries to make space for new ones." msgstr "" "Lösche bitte einige alte Tagebucheinträge, um Platz für neue zu schaffen." -#: ../src/jarabe/journal/modalalert.py:79 +#: ../src/jarabe/journal/modalalert.py:80 msgid "Show Journal" msgstr "Tagebuch anzeigen" @@ -1343,7 +1503,7 @@ msgid "Choose an object" msgstr "Ein Objekt auswählen" #: ../src/jarabe/journal/objectchooser.py:151 -#: ../src/jarabe/view/viewsource.py:310 +#: ../src/jarabe/view/viewsource.py:311 msgid "Close" msgstr "Schließen" @@ -1355,102 +1515,306 @@ msgstr "Fortsetzen mit" msgid "Start with" msgstr "Beginnen mit" -#: ../src/jarabe/journal/palettes.py:92 +#: ../src/jarabe/journal/palettes.py:83 ../src/jarabe/journal/palettes.py:216 +msgid "No activity to start entry" +msgstr "Keine Aktivität, um den Eintrag zu beginnen" + +#: ../src/jarabe/journal/palettes.py:98 msgid "Send to" msgstr "Senden an" -#: ../src/jarabe/journal/palettes.py:101 +#: ../src/jarabe/journal/palettes.py:107 msgid "View Details" msgstr "Details betrachten" -#: ../src/jarabe/journal/palettes.py:179 +#: ../src/jarabe/journal/palettes.py:181 msgid "No friends present" msgstr "Keine Freunde anwesend" -#: ../src/jarabe/journal/palettes.py:184 +#: ../src/jarabe/journal/palettes.py:186 msgid "No valid connection found" msgstr "Keine gültige Verbindung gefunden" -#: ../src/jarabe/journal/palettes.py:212 +#: ../src/jarabe/journal/palettes.py:214 msgid "No activity to resume entry" msgstr "Keine Aktivität, um den Eintrag fortzusetzen" -#: ../src/jarabe/journal/palettes.py:214 -msgid "No activity to start entry" -msgstr "Keine Aktivität, um den Eintrag zu beginnen" +#: ../src/jarabe/model/network.py:158 +msgid "The reason for the device state change is unknown." +msgstr "Der Grund für die Zustandsänderung des Gerätes ist unbekannt." + +#: ../src/jarabe/model/network.py:160 +msgid "The state change is normal." +msgstr "Die Zustandsänderung ist normal." + +#: ../src/jarabe/model/network.py:162 +msgid "The device is now managed." +msgstr "Das Gerät wird nun verwaltet." + +#: ../src/jarabe/model/network.py:164 +msgid "The device is no longer managed." +msgstr "Das Gerät wird nicht länger verwaltet." + +#: ../src/jarabe/model/network.py:166 +msgid "The device could not be readied for configuration." +msgstr "Das Gerät konnte nicht für die Konfiguration bereitgestellt werden." + +#: ../src/jarabe/model/network.py:168 +msgid "" +"IP configuration could not be reserved (no available address, timeout, etc)." +msgstr "" +"Die IP-Konfiguration konnte nicht umgesetzt werden (keine Adresse verfügbar, " +"Zeitüberschreitung etc.)." + +#: ../src/jarabe/model/network.py:171 +msgid "The IP configuration is no longer valid." +msgstr "Die IP-Konfiguration ist nicht länger gültig." + +#: ../src/jarabe/model/network.py:173 +msgid "Secrets were required, but not provided." +msgstr "Geheimnisse wurden angefordert, aber nicht bereitgestellt." + +#: ../src/jarabe/model/network.py:175 +msgid "" +"The 802.1X supplicant disconnected from the access point or authentication " +"server." +msgstr "" +"Der 802.1X-Supplikant wurde vom Zugangspunkt oder Authentifizierungsserver " +"getrennt." + +#: ../src/jarabe/model/network.py:178 +msgid "Configuration of the 802.1X supplicant failed." +msgstr "Konfiguration des 802.1X-Supplikanten schlug fehl." + +#: ../src/jarabe/model/network.py:180 +msgid "The 802.1X supplicant quit or failed unexpectedly." +msgstr "Der 802.1X-Supplikant brach ab oder scheiterte unerwartet." + +#: ../src/jarabe/model/network.py:182 +msgid "The 802.1X supplicant took too long to authenticate." +msgstr "Der 802.1X-Supplikant benötigte zu lange für die Authentifizierung." + +#: ../src/jarabe/model/network.py:184 +msgid "The PPP service failed to start within the allowed time." +msgstr "Der PPP-Dienst startete nicht in der vorgegebenen Zeit." + +#: ../src/jarabe/model/network.py:186 +msgid "The PPP service disconnected unexpectedly." +msgstr "Der PPP-Dienst wurde unerwartet getrennt." + +#: ../src/jarabe/model/network.py:188 +msgid "The PPP service quit or failed unexpectedly." +msgstr "Der PPP-Dienst brach ab oder scheiterte unerwartet." + +#: ../src/jarabe/model/network.py:190 +msgid "The DHCP service failed to start within the allowed time." +msgstr "Der DHCP-Dienst startete nicht in der vorgegebenen Zeit." + +#: ../src/jarabe/model/network.py:192 +msgid "The DHCP service reported an unexpected error." +msgstr "Der DHCP-Dienst meldete einen unerwarteten Fehler." + +#: ../src/jarabe/model/network.py:194 +msgid "The DHCP service quit or failed unexpectedly." +msgstr "Der DHCP-Dienst brach ab oder scheiterte unerwartet." + +#: ../src/jarabe/model/network.py:196 +msgid "The shared connection service failed to start." +msgstr "Der Dienst für geteilte Verbindungen konnte nicht starten." + +#: ../src/jarabe/model/network.py:198 +msgid "The shared connection service quit or failed unexpectedly." +msgstr "" +"Der Dienst für geteilte Verbindungen brach ab oder scheiterte unerwartet." -#: ../src/jarabe/view/buddymenu.py:62 +#: ../src/jarabe/model/network.py:201 +msgid "The AutoIP service failed to start." +msgstr "Der AutoIP-Dienst konnte nicht starten." + +#: ../src/jarabe/model/network.py:203 +msgid "The AutoIP service reported an unexpected error." +msgstr "Der AutoIP-Dienst meldete einen unerwarteten Fehler." + +#: ../src/jarabe/model/network.py:205 +msgid "The AutoIP service quit or failed unexpectedly." +msgstr "Der AutoIP-Dienst brach ab oder scheiterte unerwartet." + +#: ../src/jarabe/model/network.py:207 +msgid "Dialing failed because the line was busy." +msgstr "Die Einwahl schlug fehl, weil die Leitung belegt war." + +#: ../src/jarabe/model/network.py:209 +msgid "Dialing failed because there was no dial tone." +msgstr "Die Einwahl schlug mangels Einwahlton fehl." + +#: ../src/jarabe/model/network.py:211 +msgid "Dialing failed because there was no carrier." +msgstr "Die Einwahl schlug mangels Leitung fehl." + +#: ../src/jarabe/model/network.py:213 +msgid "Dialing timed out." +msgstr "Zeitüberschreitung bei der Einwahl." + +#: ../src/jarabe/model/network.py:215 +msgid "Dialing failed." +msgstr "Einwahl fehlgeschlagen." + +#: ../src/jarabe/model/network.py:217 +msgid "Modem initialization failed." +msgstr "Initialisierung des Modems fehlgeschlagen." + +#: ../src/jarabe/model/network.py:219 +msgid "Failed to select the specified GSM APN" +msgstr "Konnte den angegebenen GSM-Zugangspunkt (APN) nicht anwählen." + +#: ../src/jarabe/model/network.py:221 +msgid "Not searching for networks." +msgstr "Suche nicht nach Netzwerken." + +#: ../src/jarabe/model/network.py:223 +msgid "Network registration was denied." +msgstr "Die Netzwerkregistrierung wurde verweigert." + +#: ../src/jarabe/model/network.py:225 +msgid "Network registration timed out." +msgstr "Zeitüberschreitung bei der Netzwerkregistrierung." + +#: ../src/jarabe/model/network.py:227 +msgid "Failed to register with the requested GSM network." +msgstr "Registrierung bei dem angeforderten GSM-Netzwerk fehlgeschlagen." + +#: ../src/jarabe/model/network.py:229 +msgid "PIN check failed." +msgstr "PIN-Überprüfung fehlgeschlagen." + +#: ../src/jarabe/model/network.py:231 +msgid "Necessary firmware for the device may be missing." +msgstr "Möglicherweise fehlt die nötige Firmware für das Gerät." + +#: ../src/jarabe/model/network.py:233 +msgid "The device was removed." +msgstr "Das Gerät wurde entfernt." + +#: ../src/jarabe/model/network.py:235 +msgid "NetworkManager went to sleep." +msgstr "Der Netzwerk-Manager ist nun im Ruhezustand." + +#: ../src/jarabe/model/network.py:237 +msgid "The device's active connection was removed or disappeared." +msgstr "Die aktive Verbindung des Gerätes wurde entfernt oder ist verschwunden." + +#: ../src/jarabe/model/network.py:240 +msgid "A user or client requested the disconnection." +msgstr "Ein Benutzer oder Client forderte die Verbindungstrennung." + +#: ../src/jarabe/model/network.py:242 +msgid "The device's carrier/link changed." +msgstr "Die Leitung/Verbindung des Gerätes hat sich geändert." + +#: ../src/jarabe/view/buddymenu.py:63 msgid "Remove friend" msgstr "Freund entfernen" -#: ../src/jarabe/view/buddymenu.py:65 +#: ../src/jarabe/view/buddymenu.py:66 msgid "Make friend" msgstr "Freunde werden" -#: ../src/jarabe/view/buddymenu.py:82 +#: ../src/jarabe/view/buddymenu.py:83 msgid "Shutdown" -msgstr "Rechner Ausschalten" +msgstr "Rechner ausschalten" -#: ../src/jarabe/view/buddymenu.py:90 +#: ../src/jarabe/view/buddymenu.py:91 +msgid "Restart" +msgstr "Rechner neu starten" + +#: ../src/jarabe/view/buddymenu.py:97 msgid "Logout" -msgstr "Benutzer Abmelden" +msgstr "Benutzer abmelden" -#: ../src/jarabe/view/buddymenu.py:95 +#: ../src/jarabe/view/buddymenu.py:102 msgid "My Settings" msgstr "Meine Einstellungen" -#: ../src/jarabe/view/buddymenu.py:130 +#: ../src/jarabe/view/buddymenu.py:137 #, python-format msgid "Invite to %s" msgstr "Einladen zu %s" -#: ../src/jarabe/view/launcher.py:192 +#: ../src/jarabe/view/launcher.py:190 #, python-format msgid "<b>%s</b> failed to start." msgstr "<b>%s</b> konnte nicht gestartet werden." -#: ../src/jarabe/view/palettes.py:45 +#: ../src/jarabe/view/palettes.py:46 msgid "Starting..." msgstr "Starte..." +#: ../src/jarabe/view/palettes.py:56 +msgid "Activity failed to start" +msgstr "Aktivität konnte nicht gestartet werden." + #. TODO: share-with, keep -#: ../src/jarabe/view/palettes.py:74 +#: ../src/jarabe/view/palettes.py:85 msgid "View Source" -msgstr "Quelltext betrachten" +msgstr "Quelltext anzeigen" -#: ../src/jarabe/view/palettes.py:85 +#: ../src/jarabe/view/palettes.py:96 msgid "Stop" msgstr "Beenden" -#: ../src/jarabe/view/palettes.py:125 +#: ../src/jarabe/view/palettes.py:132 msgid "Start new" msgstr "Neu beginnen" -#: ../src/jarabe/view/palettes.py:174 +#: ../src/jarabe/view/palettes.py:172 msgid "Show contents" msgstr "Inhalte anzeigen" -#: ../src/jarabe/view/palettes.py:196 ../src/jarabe/view/palettes.py:246 +#: ../src/jarabe/view/palettes.py:194 ../src/jarabe/view/palettes.py:245 #, python-format msgid "%(free_space)d MB Free" msgstr "%(free_space)d MB frei" -#: ../src/jarabe/view/viewsource.py:208 +#: ../src/jarabe/view/viewsource.py:211 msgid "Instance Source" msgstr "Quelltext der Instanz" -#: ../src/jarabe/view/viewsource.py:233 +#: ../src/jarabe/view/viewsource.py:236 msgid "Source" msgstr "Quelltext" -#: ../src/jarabe/view/viewsource.py:294 +#: ../src/jarabe/view/viewsource.py:295 msgid "Activity Bundle Source" msgstr "Quelltext des Aktivitätenbündels" -#: ../src/jarabe/view/viewsource.py:301 +#: ../src/jarabe/view/viewsource.py:302 #, python-format msgid "View source: %r" -msgstr "Quelltext betrachten: %r" +msgstr "Quelltext anzeigen: %r" + +#: ../src/jarabe/util/emulator.py:40 +msgid "Sugar in a window" +msgstr "Sugar in einem Fenster" + +#~ msgid "Create new wireless network" +#~ msgstr "Neues Funknetzwerk erstellen" + +#~ msgid "Sim requires Pin/Puk" +#~ msgstr "Sim benötigt PIN/PUK" + +#~ msgid "Authentication Error" +#~ msgstr "Authentifizierungsfehler" + +#, python-format +#~ msgid "%s's network" +#~ msgstr "Netzwerk von %s" + +#, python-format +#~ msgid "Data sent %d KB / received %d KB" +#~ msgstr "gesendet %d KB / empfangen %d KB" + +#~ msgid "Connection time " +#~ msgstr "Verbindungsdauer " #~ msgid "APN:" #~ msgstr "APN (Zugangspunkt):" @@ -1470,9 +1834,6 @@ msgstr "Quelltext betrachten: %r" #~ msgid "Unmount" #~ msgstr "Einbindung lösen" -#~ msgid "Restart" -#~ msgstr "Neustart" - #~ msgid "" #~ "© 2008 One Laptop per Child Association Inc; Red Hat Inc; and Contributors." #~ msgstr "" @@ -2,20 +2,40 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. msgid "" msgstr "" "Project-Id-Version: olpc-sugar\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2009-09-05 00:31-0400\n" -"PO-Revision-Date: 2010-01-18 19:18+0200\n" -"Last-Translator: Chris <cjl@laptop.org>\n" +"POT-Creation-Date: 2011-02-06 00:30-0500\n" +"PO-Revision-Date: 2011-02-10 18:26+0200\n" +"Last-Translator: Gonzalo <godiard@sugarlabs.org>\n" "Language-Team: Fedora Spanish <fedora-trans-es@redhat.com>\n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Pootle 2.0.1\n" +"X-Generator: Pootle 2.0.3\n" "X-Poedit-Language: Spanish\n" "X-Poedit-SourceCharset: utf-8\n" "X-Poedit-Basepath: .\n" @@ -24,43 +44,39 @@ msgstr "" msgid "About Me" msgstr "Acerca de mí" -#: ../extensions/cpsection/aboutme/model.py:43 +#: ../extensions/cpsection/aboutme/model.py:48 msgid "You must enter a name." msgstr "Debe ingresar un nombre." -#: ../extensions/cpsection/aboutme/model.py:68 +#: ../extensions/cpsection/aboutme/model.py:75 #, python-format msgid "stroke: color=%s hue=%s" msgstr "borde: color=%s tonalidad=%s" -#: ../extensions/cpsection/aboutme/model.py:71 +#: ../extensions/cpsection/aboutme/model.py:78 #, python-format msgid "stroke: %s" msgstr "borde: %s" -#: ../extensions/cpsection/aboutme/model.py:73 +#: ../extensions/cpsection/aboutme/model.py:80 #, python-format msgid "fill: color=%s hue=%s" msgstr "relleno: color=%s tonalidad=%s" -#: ../extensions/cpsection/aboutme/model.py:75 +#: ../extensions/cpsection/aboutme/model.py:82 #, python-format msgid "fill: %s" msgstr "relleno: %s" -#: ../extensions/cpsection/aboutme/model.py:86 +#: ../extensions/cpsection/aboutme/model.py:94 msgid "Error in specified color modifiers." msgstr "Error en modificadores de color especificados." -#: ../extensions/cpsection/aboutme/model.py:89 +#: ../extensions/cpsection/aboutme/model.py:97 msgid "Error in specified colors." msgstr "Error en colores especificados." -#: ../extensions/cpsection/aboutme/view.py:94 ../src/jarabe/intro/window.py:92 -msgid "Name:" -msgstr "Nombre:" - -#: ../extensions/cpsection/aboutme/view.py:128 +#: ../extensions/cpsection/aboutme/view.py:235 msgid "Click to change your color:" msgstr "Clic para cambiar su color:" @@ -68,56 +84,61 @@ msgstr "Clic para cambiar su color:" msgid "About my Computer" msgstr "Acerca de mi computadora" -#: ../extensions/cpsection/aboutcomputer/model.py:28 +#: ../extensions/cpsection/aboutcomputer/model.py:37 msgid "Not available" msgstr "No disponible" -#: ../extensions/cpsection/aboutcomputer/view.py:60 +#: ../extensions/cpsection/aboutcomputer/model.py:158 +#, python-format +msgid "%(interface)s: %(version)s" +msgstr "%(interface)s: %(version)s" + +#: ../extensions/cpsection/aboutcomputer/view.py:61 msgid "Identity" msgstr "Identidad" -#: ../extensions/cpsection/aboutcomputer/view.py:69 +#: ../extensions/cpsection/aboutcomputer/view.py:70 msgid "Serial Number:" msgstr "Número de serie:" -#: ../extensions/cpsection/aboutcomputer/view.py:91 +#: ../extensions/cpsection/aboutcomputer/view.py:92 msgid "Software" msgstr "Software" # Por ahora.. -#: ../extensions/cpsection/aboutcomputer/view.py:100 +#: ../extensions/cpsection/aboutcomputer/view.py:101 msgid "Build:" msgstr "Ensamble:" -#: ../extensions/cpsection/aboutcomputer/view.py:115 +#: ../extensions/cpsection/aboutcomputer/view.py:116 msgid "Sugar:" msgstr "Azúcar:" -#: ../extensions/cpsection/aboutcomputer/view.py:131 +#: ../extensions/cpsection/aboutcomputer/view.py:132 msgid "Firmware:" msgstr "Firmware:" -#: ../extensions/cpsection/aboutcomputer/view.py:146 +#: ../extensions/cpsection/aboutcomputer/view.py:147 msgid "Wireless Firmware:" -msgstr "Firmware Wireless:" +msgstr "Firmware de la red inalámbrica:" -#: ../extensions/cpsection/aboutcomputer/view.py:169 +#: ../extensions/cpsection/aboutcomputer/view.py:170 msgid "Copyright and License" -msgstr "Licencia y Copyright" +msgstr "Licencia y derechos de autor" -#: ../extensions/cpsection/aboutcomputer/view.py:184 +#: ../extensions/cpsection/aboutcomputer/view.py:188 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 " "change it and/or distribute copies of it under certain conditions described " "therein." msgstr "" -"Azucar es la interfaz gráfica de usuario que usted esta mirando. Azucar es " +"Azucar es la interfaz gráfica de usuario que usted esta mirando. Azucar es " "software libre, cubierto bajo la licencia GNU Licencia Publica General, y " "esta invitado a cambiarla y/o distribuir copias bajo ciertas condiciones que " "se describen en ella." -#: ../extensions/cpsection/aboutcomputer/view.py:196 +#: ../extensions/cpsection/aboutcomputer/view.py:200 msgid "Full license:" msgstr "Licencia completa:" @@ -125,11 +146,11 @@ msgstr "Licencia completa:" msgid "Date & Time" msgstr "Fecha y hora" -#: ../extensions/cpsection/datetime/model.py:87 +#: ../extensions/cpsection/datetime/model.py:92 msgid "Error timezone does not exist." msgstr "Error, zona horaria no existe." -#: ../extensions/cpsection/datetime/view.py:68 ../data/sugar.schemas.in.h:27 +#: ../extensions/cpsection/datetime/view.py:70 ../data/sugar.schemas.in.h:52 msgid "Timezone" msgstr "Zona horaria" @@ -137,50 +158,50 @@ msgstr "Zona horaria" msgid "Frame" msgstr "Cuadro" -#: ../extensions/cpsection/frame/model.py:38 -#: ../extensions/cpsection/frame/model.py:60 +#: ../extensions/cpsection/frame/model.py:41 +#: ../extensions/cpsection/frame/model.py:66 msgid "Value must be an integer." msgstr "El valor debe ser un número entero." -#: ../extensions/cpsection/frame/view.py:26 +#: ../extensions/cpsection/frame/view.py:27 msgid "never" msgstr "nunca" -#: ../extensions/cpsection/frame/view.py:27 +#: ../extensions/cpsection/frame/view.py:28 msgid "instantaneous" msgstr "instantáneo" -#: ../extensions/cpsection/frame/view.py:28 +#: ../extensions/cpsection/frame/view.py:29 #, python-format msgid "%s seconds" msgstr "%s segundos" -#: ../extensions/cpsection/frame/view.py:52 +#: ../extensions/cpsection/frame/view.py:54 msgid "Activation Delay" msgstr "Retraso de activación" -#: ../extensions/cpsection/frame/view.py:76 +#: ../extensions/cpsection/frame/view.py:78 msgid "Corner" msgstr "Esquina" -#: ../extensions/cpsection/frame/view.py:111 +#: ../extensions/cpsection/frame/view.py:113 msgid "Edge" msgstr "Borde" #: ../extensions/cpsection/keyboard/__init__.py:21 -#: ../extensions/cpsection/keyboard/view.py:31 +#: ../extensions/cpsection/keyboard/view.py:32 msgid "Keyboard" msgstr "Teclado" -#: ../extensions/cpsection/keyboard/view.py:187 +#: ../extensions/cpsection/keyboard/view.py:190 msgid "Keyboard Model" msgstr "Modelo de teclado" -#: ../extensions/cpsection/keyboard/view.py:243 +#: ../extensions/cpsection/keyboard/view.py:250 msgid "Key(s) to change layout" msgstr "Tecla(s) para cambiar el diseño" -#: ../extensions/cpsection/keyboard/view.py:311 +#: ../extensions/cpsection/keyboard/view.py:319 msgid "Keyboard Layout(s)" msgstr "Diseño(s) de teclado" @@ -189,22 +210,22 @@ msgstr "Diseño(s) de teclado" msgid "Language" msgstr "Idioma" -#: ../extensions/cpsection/language/model.py:28 +#: ../extensions/cpsection/language/model.py:30 msgid "Could not access ~/.i18n. Create standard settings." msgstr "" "No se puede acceder a ~/.i18n. Crear configuración internacional estándar." -#: ../extensions/cpsection/language/model.py:124 +#: ../extensions/cpsection/language/model.py:131 #, python-format msgid "Language for code=%s could not be determined." msgstr "El lenguaje del código=%s no pudo ser determinado." -#: ../extensions/cpsection/language/model.py:144 +#: ../extensions/cpsection/language/model.py:152 #, python-format msgid "Sorry I do not speak '%s'." msgstr "Lo siento, yo no hablo '%s'." -#: ../extensions/cpsection/language/view.py:56 +#: ../extensions/cpsection/language/view.py:57 msgid "" "Add languages in the order you prefer. If a translation is not available, " "the next in the list will be used." @@ -212,48 +233,84 @@ msgstr "" "Añade idiomas en el orden que prefieres. Si una traducción no se encuentra " "disponible, se usará la siguiente en la lista." +#: ../extensions/cpsection/modemconfiguration/__init__.py:21 +msgid "Modem Configuration" +msgstr "Configuración del módem" + +#: ../extensions/cpsection/modemconfiguration/view.py:94 +msgid "Username:" +msgstr "Nombre de usuario:" + +#: ../extensions/cpsection/modemconfiguration/view.py:106 +msgid "Password:" +msgstr "Contraseña:" + +#: ../extensions/cpsection/modemconfiguration/view.py:118 +msgid "Number:" +msgstr "Número:" + +#: ../extensions/cpsection/modemconfiguration/view.py:130 +msgid "Access Point Name (APN):" +msgstr "Nombre de punto de acceso (APN):" + +#: ../extensions/cpsection/modemconfiguration/view.py:142 +msgid "Personal Identity Number (PIN):" +msgstr "Número de identificación personal (PIN):" + +#: ../extensions/cpsection/modemconfiguration/view.py:154 +msgid "Personal Unblocking Key (PUK):" +msgstr "Clave personal de desbloqueo (PUK):" + +#: ../extensions/cpsection/modemconfiguration/view.py:175 +msgid "" +"You will need to provide the following information to set up a mobile " +"broadband connection to a cellular (3G) network." +msgstr "" +"Necesitará proveer la información siguiente para configurar una conexión de " +"banda ancha (3G) de red de telefonía celular." + #: ../extensions/cpsection/network/__init__.py:21 -#: ../extensions/cpsection/network/view.py:28 +#: ../extensions/cpsection/network/view.py:29 msgid "Network" msgstr "Red" -#: ../extensions/cpsection/network/model.py:79 +#: ../extensions/cpsection/network/model.py:71 msgid "State is unknown." msgstr "Estado desconocido." -#: ../extensions/cpsection/network/model.py:105 +#: ../extensions/cpsection/network/model.py:99 msgid "Error in specified radio argument use on/off." msgstr "Error en argumento especificado de radio use on/off." -#: ../extensions/cpsection/network/model.py:137 +#: ../extensions/cpsection/network/model.py:140 msgid "Error in specified argument use 0/1." msgstr "Error en argumento especificado use 0/1." -#: ../extensions/cpsection/network/view.py:59 +#: ../extensions/cpsection/network/view.py:61 msgid "Wireless" msgstr "Inalámbrica" -#: ../extensions/cpsection/network/view.py:67 +#: ../extensions/cpsection/network/view.py:69 msgid "Turn off the wireless radio to save battery life" msgstr "Apague la radio inalámbrica y ahorre vida de batería" -#: ../extensions/cpsection/network/view.py:80 +#: ../extensions/cpsection/network/view.py:82 msgid "Radio" msgstr "Radio" -#: ../extensions/cpsection/network/view.py:96 +#: ../extensions/cpsection/network/view.py:98 msgid "Discard network history if you have trouble connecting to the network" msgstr "Descarte el historial de la red si tiene problemas de conexión" -#: ../extensions/cpsection/network/view.py:105 +#: ../extensions/cpsection/network/view.py:107 msgid "Discard network history" msgstr "Descarte historial de la red" -#: ../extensions/cpsection/network/view.py:118 +#: ../extensions/cpsection/network/view.py:122 msgid "Collaboration" msgstr "Colaboración" -#: ../extensions/cpsection/network/view.py:126 +#: ../extensions/cpsection/network/view.py:130 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 " @@ -262,7 +319,7 @@ msgstr "" "El servidor es equivalente al cuarto en el cual se esta; la gente en el " "mismo servidor podrá verse entre ellos, aun cuando no esten en la misma red." -#: ../extensions/cpsection/network/view.py:136 +#: ../extensions/cpsection/network/view.py:140 msgid "Server:" msgstr "Servidor:" @@ -270,24 +327,24 @@ msgstr "Servidor:" msgid "Power" msgstr "Energía" -#: ../extensions/cpsection/power/model.py:54 +#: ../extensions/cpsection/power/model.py:90 msgid "Error in automatic pm argument, use on/off." -msgstr "Error en argumento automático de pm, use on/off." +msgstr "Error en argumento automático de manejo de energía, use on/off." -#: ../extensions/cpsection/power/model.py:81 +#: ../extensions/cpsection/power/model.py:120 msgid "Error in extreme pm argument, use on/off." -msgstr "Error en argumento extremo de pm, use on/off." +msgstr "Error en argumento extremo de manejo de energía, use on/off." -#: ../extensions/cpsection/power/view.py:47 +#: ../extensions/cpsection/power/view.py:48 msgid "Power management" msgstr "Manejo de energía" -#: ../extensions/cpsection/power/view.py:57 +#: ../extensions/cpsection/power/view.py:58 msgid "Automatic power management (increases battery life)" msgstr "Manejo automático de energía (incrementa la vida de la batería)" # best translationfor now -#: ../extensions/cpsection/power/view.py:85 +#: ../extensions/cpsection/power/view.py:86 msgid "" "Extreme power management (disableswireless radio, increases battery life)" msgstr "" @@ -296,9 +353,9 @@ msgstr "" #: ../extensions/cpsection/updater/__init__.py:21 msgid "Software update" -msgstr "Actualización de Software" +msgstr "Actualización de software" -#: ../extensions/cpsection/updater/view.py:62 +#: ../extensions/cpsection/updater/view.py:63 msgid "" "Software updates correct errors, eliminate security vulnerabilities, and " "provide new features." @@ -306,109 +363,109 @@ msgstr "" "Las actualizaciones de software corrigen errores, eliminan vulnerabilidades " "de seguridad y proveen nuevas características." -#: ../extensions/cpsection/updater/view.py:122 +#: ../extensions/cpsection/updater/view.py:125 #, python-format msgid "Checking %s..." msgstr "Probando %s..." -#: ../extensions/cpsection/updater/view.py:124 +#: ../extensions/cpsection/updater/view.py:127 #, python-format msgid "Downloading %s..." msgstr "Descargando %s..." -#: ../extensions/cpsection/updater/view.py:126 +#: ../extensions/cpsection/updater/view.py:129 #, python-format msgid "Updating %s..." msgstr "Actualizando %s..." -#: ../extensions/cpsection/updater/view.py:135 +#: ../extensions/cpsection/updater/view.py:139 msgid "Your software is up-to-date" msgstr "Tu software esta actualizado" -#: ../extensions/cpsection/updater/view.py:137 +#: ../extensions/cpsection/updater/view.py:141 #, python-format msgid "You can install %s update" msgid_plural "You can install %s updates" msgstr[0] "Puedes instalar %s actualización" msgstr[1] "Puedes instalar %s actualizaciones" -#: ../extensions/cpsection/updater/view.py:155 +#: ../extensions/cpsection/updater/view.py:159 msgid "Checking for updates..." msgstr "Buscando actualizaciones..." -#: ../extensions/cpsection/updater/view.py:160 +#: ../extensions/cpsection/updater/view.py:164 msgid "Installing updates..." msgstr "Instalando actualizaciones..." -#: ../extensions/cpsection/updater/view.py:165 +#: ../extensions/cpsection/updater/view.py:173 #, python-format msgid "%s update was installed" msgid_plural "%s updates were installed" msgstr[0] "%s actualización fue instalada" msgstr[1] "%s actualizaciones fueron instaladas" -#: ../extensions/cpsection/updater/view.py:244 +#: ../extensions/cpsection/updater/view.py:255 msgid "Install selected" msgstr "Instalación seleccionada" -#: ../extensions/cpsection/updater/view.py:265 +#: ../extensions/cpsection/updater/view.py:276 #, python-format msgid "Download size: %s" msgstr "Tamaño de descarga: %s" -#: ../extensions/cpsection/updater/view.py:353 +#: ../extensions/cpsection/updater/view.py:364 #, python-format -msgid "From version %(current)d to %(new)s (Size: %(size)s)" -msgstr "Desde la version %(current)d hacia %(new)s (Size: %(size)s)" +msgid "From version %(current)s to %(new)s (Size: %(size)s)" +msgstr "Desde la version %(current)s hacia %(new)s (Size: %(size)s)" #. TRANS: download size is 0 -#: ../extensions/cpsection/updater/view.py:373 +#: ../extensions/cpsection/updater/view.py:382 msgid "None" msgstr "Ninguno" #. TRANS: download size of very small updates -#: ../extensions/cpsection/updater/view.py:376 +#: ../extensions/cpsection/updater/view.py:385 msgid "1 KB" msgstr "1 KB" #. TRANS: download size of small updates, e.g. '250 KB' -#: ../extensions/cpsection/updater/view.py:379 +#: ../extensions/cpsection/updater/view.py:388 #, python-format msgid "%.0f KB" msgstr "%.0f KB" #. TRANS: download size of updates, e.g. '2.3 MB' -#: ../extensions/cpsection/updater/view.py:382 +#: ../extensions/cpsection/updater/view.py:391 #, python-format msgid "%.1f MB" msgstr "%.1f MB" -#: ../extensions/deviceicon/battery.py:58 +#: ../extensions/deviceicon/battery.py:60 msgid "My Battery" msgstr "Mi batería" -#: ../extensions/deviceicon/battery.py:137 +#: ../extensions/deviceicon/battery.py:141 msgid "Removed" msgstr "Eliminado" -#: ../extensions/deviceicon/battery.py:140 +#: ../extensions/deviceicon/battery.py:144 msgid "Charging" msgstr "Cargando" -#: ../extensions/deviceicon/battery.py:143 +#: ../extensions/deviceicon/battery.py:147 msgid "Very little power remaining" msgstr "Queda muy poca batería" -#: ../extensions/deviceicon/battery.py:149 +#: ../extensions/deviceicon/battery.py:153 #, python-format msgid "%(hour)d:%(min).2d remaining" msgstr "Quedan %(hour)d:%(min).2d" -#: ../extensions/deviceicon/battery.py:152 +#: ../extensions/deviceicon/battery.py:156 msgid "Charged" msgstr "Cargada" -#: ../extensions/deviceicon/network.py:44 +#: ../extensions/deviceicon/network.py:49 #, python-format msgid "IP address: %s" msgstr "Direccion IP: %s" @@ -417,125 +474,336 @@ msgstr "Direccion 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:110 +#: ../extensions/deviceicon/network.py:104 msgid "Disconnect..." msgstr "Desconectando..." -#: ../extensions/deviceicon/network.py:114 -msgid "Create new wireless network" -msgstr "Crear nueva red inalámbrica" - -#: ../extensions/deviceicon/network.py:120 -#: ../src/jarabe/desktop/meshbox.py:264 +#: ../extensions/deviceicon/network.py:112 +#: ../extensions/deviceicon/network.py:290 +#: ../src/jarabe/desktop/networkviews.py:241 +#: ../src/jarabe/desktop/networkviews.py:546 +#: ../src/jarabe/desktop/networkviews.py:673 msgid "Connecting..." msgstr "Conectando..." # TODO: show the channel number -#: ../extensions/deviceicon/network.py:124 -#: ../extensions/deviceicon/network.py:186 -#: ../src/jarabe/desktop/meshbox.py:270 +#: ../extensions/deviceicon/network.py:116 +#: ../extensions/deviceicon/network.py:182 +#: ../src/jarabe/desktop/networkviews.py:251 +#: ../src/jarabe/desktop/networkviews.py:552 +#: ../src/jarabe/desktop/networkviews.py:679 msgid "Connected" msgstr "Conectado" -#: ../extensions/deviceicon/network.py:146 +#: ../extensions/deviceicon/network.py:142 msgid "Channel" msgstr "Canal" -#: ../extensions/deviceicon/network.py:161 +#: ../extensions/deviceicon/network.py:157 msgid "Wired Network" msgstr "Red Cableada" -#: ../extensions/deviceicon/network.py:189 +#: ../extensions/deviceicon/network.py:185 msgid "Speed" msgstr "Velocidad" -#: ../extensions/deviceicon/network.py:415 +#: ../extensions/deviceicon/network.py:211 +msgid "Wireless modem" +msgstr "Módem inalámbrico" + +#: ../extensions/deviceicon/network.py:278 +msgid "Please wait..." +msgstr "Espere por favor..." + +#: ../extensions/deviceicon/network.py:282 +#: ../src/jarabe/desktop/networkviews.py:134 +#: ../src/jarabe/desktop/networkviews.py:500 +#: ../src/jarabe/desktop/networkviews.py:630 +msgid "Connect" +msgstr "Conectar" + +#: ../extensions/deviceicon/network.py:283 +msgid "Disconnected" +msgstr "Desconectado" + +#: ../extensions/deviceicon/network.py:289 +#: ../src/jarabe/controlpanel/toolbar.py:119 +#: ../src/jarabe/desktop/homebox.py:70 +#: ../src/jarabe/frame/activitiestray.py:587 +#: ../src/jarabe/frame/activitiestray.py:687 +#: ../src/jarabe/frame/activitiestray.py:715 +msgid "Cancel" +msgstr "Cancelar" + +#: ../extensions/deviceicon/network.py:297 +#: ../src/jarabe/desktop/networkviews.py:138 +#: ../src/jarabe/desktop/networkviews.py:504 +msgid "Disconnect" +msgstr "Desconectar" + +#: ../extensions/deviceicon/network.py:327 +msgid "Try connection again" +msgstr "Probar la conexión nuevamente" + +#: ../extensions/deviceicon/network.py:330 +#, python-format +msgid "Error: %s" +msgstr "Error: %s" + +#: ../extensions/deviceicon/network.py:334 +#, python-format +msgid "Suggestion: %s" +msgstr "Sugerencia: %s" + +#: ../extensions/deviceicon/network.py:340 +#: ../extensions/deviceicon/network.py:343 +#, python-format +msgid "Connected for %s" +msgstr "Conectado a %s" + +#: ../extensions/deviceicon/network.py:348 +#: ../extensions/deviceicon/network.py:349 +#, python-format +msgid "%d KB" +msgstr "%d KB" + +#: ../extensions/deviceicon/network.py:354 +msgid "Check your Pin/Puk configuration." +msgstr "Revise la configuración de su Pin/Puk." + +#: ../extensions/deviceicon/network.py:357 +msgid "Check your Access Point Name (APN) configuration" +msgstr "Revise la configuración del nombre de su punto de acceso (APN)" + +#: ../extensions/deviceicon/network.py:361 +msgid "Check the Number configuration." +msgstr "Revise el Número en la configuración." + +#: ../extensions/deviceicon/network.py:363 +msgid "Check your configuration." +msgstr "Revise su configuración." + +#: ../extensions/deviceicon/network.py:605 +msgid "Mesh Network" +msgstr "Red Malla" + +#: ../extensions/deviceicon/network.py:648 +#, python-format +msgid "Mesh Network %s" +msgstr "Red Malla %s" + +#: ../extensions/deviceicon/network.py:771 +msgid "No GSM connection available." +msgstr "No se dispone de conexión GSM." + +#: ../extensions/deviceicon/network.py:772 +msgid "Create a connection in the control panel." +msgstr "Crear una conexión en el panel de control." + +#: ../extensions/deviceicon/resources.py:48 +msgid "System resources" +msgstr "Recursos del sistema" + +#: ../extensions/deviceicon/resources.py:170 #, python-format -msgid "%s's network %s" -msgstr "%s's red %s" +msgid "CPU in use: %d%%" +msgstr "CPU en uso: %d%%" -#: ../extensions/deviceicon/speaker.py:59 +#: ../extensions/deviceicon/resources.py:172 +#, python-format +msgid "Memory in use: %d%%" +msgstr "Memoria en uso: %d%%" + +#: ../extensions/deviceicon/resources.py:202 +msgid "Cannot compute CPU and memory usage statistics!" +msgstr "No puedo calcular las estadísticas de uso de CPU y memoria!" + +#: ../extensions/deviceicon/speaker.py:60 msgid "My Speakers" msgstr "Mis parlantes" # la traducción la tome del AlsaMixer de Gnome. -#: ../extensions/deviceicon/speaker.py:133 +#: ../extensions/deviceicon/speaker.py:136 msgid "Unmute" msgstr "Dar voz" -#: ../extensions/deviceicon/speaker.py:136 +#: ../extensions/deviceicon/speaker.py:139 msgid "Mute" msgstr "Silenciar" -#: ../extensions/globalkey/screenshot.py:56 +#: ../extensions/deviceicon/touchpad.py:37 +msgid "finger" +msgstr "dedo" + +#: ../extensions/deviceicon/touchpad.py:38 +msgid "stylus" +msgstr "estilo" + +#: ../extensions/deviceicon/touchpad.py:67 +msgid "My touchpad" +msgstr "Mi superficie táctil" + +#: ../extensions/globalkey/screenshot.py:59 msgid "Mesh" msgstr "Malla" -#: ../extensions/globalkey/screenshot.py:58 -#: ../src/jarabe/frame/zoomtoolbar.py:39 +#: ../extensions/globalkey/screenshot.py:61 +#: ../src/jarabe/frame/zoomtoolbar.py:40 msgid "Group" msgstr "Grupo" -#: ../extensions/globalkey/screenshot.py:60 -#: ../src/jarabe/frame/zoomtoolbar.py:41 +#: ../extensions/globalkey/screenshot.py:63 +#: ../src/jarabe/frame/zoomtoolbar.py:42 msgid "Home" msgstr "Hogar" -#: ../extensions/globalkey/screenshot.py:66 -#: ../src/jarabe/frame/zoomtoolbar.py:43 +#: ../extensions/globalkey/screenshot.py:69 +#: ../src/jarabe/frame/zoomtoolbar.py:44 msgid "Activity" msgstr "Actividad" -#: ../extensions/globalkey/screenshot.py:69 +#: ../extensions/globalkey/screenshot.py:72 msgid "Screenshot" msgstr "Captura de pantalla" -#: ../extensions/globalkey/screenshot.py:71 +#: ../extensions/globalkey/screenshot.py:74 #, python-format msgid "Screenshot of \"%s\"" msgstr "Captura pantalla de \"%s\"" #: ../data/sugar.schemas.in.h:1 +msgid "" +"\"disabled\" to ask nick on initialization; \"system\" to reuse UNIX account " +"long name." +msgstr "" +"\"disabled\" (desactivado) para preguntar apodo al inicio; \"system\" (sistema) " +"para reutilizar el nombre largo de la cuenta UNIX." + +#: ../data/sugar.schemas.in.h:2 +msgid "Additional directories which can contain updated translations." +msgstr "Directorios adicionales que pueden contener traducciones actualizadas." + +#: ../data/sugar.schemas.in.h:3 msgid "Backup URL" msgstr "URL de Respaldo" -#: ../data/sugar.schemas.in.h:2 +#: ../data/sugar.schemas.in.h:4 +msgid "Bundle IDs of protected activities" +msgstr "Bundle IDs de actividades protegidas" + +#: ../data/sugar.schemas.in.h:5 msgid "" "Color for the XO icon that is used throughout the desktop. The string is " -"composed of the stroke color and fill color, format is that of rbg colors. " +"composed of the stroke color and fill color, format is that of rgb colors. " "Example: #AC32FF,#9A5200" msgstr "" "El color para el ícono del XO se utiliza en todo el escritorio. La cadena " "está compuesta por el trazo y color de relleno de color, el formato es el de " -"colores RBG. Ejemplo: #AC32FF, #9A5200" +"colores RGB. Ejemplo: #AC32FF, #9A5200" # es la mejor traduccion ? -#: ../data/sugar.schemas.in.h:3 +#: ../data/sugar.schemas.in.h:6 msgid "Corner Delay" msgstr "Retraso de las Esquinas" -#: ../data/sugar.schemas.in.h:4 +#: ../data/sugar.schemas.in.h:7 +msgid "Default font face" +msgstr "Tipo de letra predeterminado" + +#: ../data/sugar.schemas.in.h:8 +msgid "Default font size" +msgstr "Tamaño de letra predeterminado" + +#: ../data/sugar.schemas.in.h:9 +msgid "Default nick" +msgstr "Apodo predeterminado" + +#: ../data/sugar.schemas.in.h:10 msgid "Delay for the activation of the frame using the corners." msgstr "Retraso para la activación del cuadro utilizando las esquinas." -#: ../data/sugar.schemas.in.h:5 +#: ../data/sugar.schemas.in.h:11 msgid "Delay for the activation of the frame using the edges." msgstr "Retraso para la activación del cuadro utilizando los bordes." +#: ../data/sugar.schemas.in.h:12 +msgid "Directory to search for translations" +msgstr "Directorio a buscar para traducciones" + # es la mejor traduccion ? -#: ../data/sugar.schemas.in.h:6 +#: ../data/sugar.schemas.in.h:13 msgid "Edge Delay" msgstr "Retraso del Borde" -#: ../data/sugar.schemas.in.h:7 +#: ../data/sugar.schemas.in.h:14 msgid "Favorites Layout" msgstr "Diseño de favoritos" -#: ../data/sugar.schemas.in.h:8 +#: ../data/sugar.schemas.in.h:15 msgid "Favorites resume mode" msgstr "Modo de reanudar favoritos" -#: ../data/sugar.schemas.in.h:9 +#: ../data/sugar.schemas.in.h:16 +msgid "Font face that is used throughout the desktop." +msgstr "Tipo de letra que se utiliza en todo el escritorio." + +#: ../data/sugar.schemas.in.h:17 +msgid "Font size that is used throughout the desktop." +msgstr "Tamaño de letra que se utiliza en todo el escritorio." + +#: ../data/sugar.schemas.in.h:18 +msgid "GSM network APN" +msgstr "APN de la red GSM" + +#: ../data/sugar.schemas.in.h:19 +msgid "GSM network PIN" +msgstr "PIN de la red GSM" + +#: ../data/sugar.schemas.in.h:20 +msgid "GSM network PUK" +msgstr "PUK de la red GSM" + +# Puede evitarse el texto "(APN)"; pero es bueno ser preciso en detallarlo para usuarios no técnicos. +#: ../data/sugar.schemas.in.h:21 +msgid "GSM network access point name configuration" +msgstr "Configuración del nombre del punto de acceso (APN) de la red GSM" + +#: ../data/sugar.schemas.in.h:22 +msgid "GSM network number" +msgstr "Número de la red GSM" + +# Puede usarse el sinónimo "Clave" mas hay que usar el término conocido por el usuario. +#: ../data/sugar.schemas.in.h:23 +msgid "GSM network password" +msgstr "Contraseña de la red GSM" + +#: ../data/sugar.schemas.in.h:24 +msgid "GSM network password configuration" +msgstr "Configuración de la contraseña de la red GSM" + +# Puede excluirse el texto "(PIN)" mas es de ser precisos para usuarios no técnicos. +#: ../data/sugar.schemas.in.h:25 +msgid "GSM network personal identification number configuration" +msgstr "Configuración del numero de identificación personal (PIN) de la red GSM" + +# Puede no usarse el texto "(PUK)" pero es válido ser específico. +#: ../data/sugar.schemas.in.h:26 +msgid "GSM network personal unlock key configuration" +msgstr "Configuración de la clave de desbloqueo personal (PUK) de la red GSM" + +#: ../data/sugar.schemas.in.h:27 +msgid "GSM network telephone number configuration" +msgstr "Configuración del número de teléfono de la red GSM" + +#: ../data/sugar.schemas.in.h:28 +msgid "GSM network username" +msgstr "Nombre de usuario de la red GSM" + +#: ../data/sugar.schemas.in.h:29 +msgid "GSM network username configuration" +msgstr "Configuración del nombre de usuario de la red GSM" + +#: ../data/sugar.schemas.in.h:30 msgid "" "If TRUE, Sugar will make us searchable for the other users of the Jabber " "server." @@ -543,110 +811,139 @@ msgstr "" "Si es TRUE, Azúcar habilitará que otros usuarios nos busquen en el servidor " "Jabber." -#: ../data/sugar.schemas.in.h:10 +#: ../data/sugar.schemas.in.h:31 msgid "If TRUE, Sugar will show a \"Log out\" option." msgstr "Si es TRUE, Azúcar mostrará una opción \"Terminar Sesión\"." -#: ../data/sugar.schemas.in.h:11 +#: ../data/sugar.schemas.in.h:32 +msgid "If TRUE, Sugar will show a \"Restart\" option." +msgstr "Si es TRUE, Azúcar mostrará una opción \"Reiniciar Sesión\"." + +#: ../data/sugar.schemas.in.h:33 +msgid "" +"If TRUE, Sugar will show default Ad-hoc networks for channel 1,6 and 11. If " +"Sugar sees no \"known\" network when it starts, it does autoconnect to an Ad-" +"hoc network." +msgstr "" +"Sí es TRUE, Azúcar mostrara las redes ad-hoc predefinidas para canales 1,6, " +"y 11. Sí Azúcar no ve redes conocidas cuando inicia, se conectará " +"automáticamente a una red ad-hoc." + +#: ../data/sugar.schemas.in.h:34 msgid "Jabber Server" msgstr "Servidor Jabber" -#: ../data/sugar.schemas.in.h:12 +#: ../data/sugar.schemas.in.h:35 msgid "Keyboard layouts" msgstr "Distribuciones del teclado" -#: ../data/sugar.schemas.in.h:13 +#: ../data/sugar.schemas.in.h:36 msgid "Keyboard model" msgstr "Modelo del teclado" -#: ../data/sugar.schemas.in.h:14 +#: ../data/sugar.schemas.in.h:37 msgid "Keyboard options" msgstr "Opciones del teclado" -#: ../data/sugar.schemas.in.h:15 +#: ../data/sugar.schemas.in.h:38 msgid "Layout of the favorites view." msgstr "Distribución de las actividades favoritas." -#: ../data/sugar.schemas.in.h:16 +#: ../data/sugar.schemas.in.h:39 msgid "" "List of keyboard layouts. Each entry should be in the form layout(variant)" msgstr "" "Lista de las distribuciones de teclado. Cada entrada debe ser en la forma " "distribución(variante)" -#: ../data/sugar.schemas.in.h:17 +#: ../data/sugar.schemas.in.h:40 msgid "List of keyboard options." msgstr "Lista de las opciones del teclado." -#: ../data/sugar.schemas.in.h:18 +#: ../data/sugar.schemas.in.h:41 msgid "Power Automatic" msgstr "Manejo automática de energía" -#: ../data/sugar.schemas.in.h:19 +#: ../data/sugar.schemas.in.h:42 msgid "Power Automatic." msgstr "Manejo automática de energía." -#: ../data/sugar.schemas.in.h:20 +#: ../data/sugar.schemas.in.h:43 msgid "Power Extreme" msgstr "Manejo extremo de energía" -#: ../data/sugar.schemas.in.h:21 +#: ../data/sugar.schemas.in.h:44 msgid "Power Extreme." msgstr "Manejo extremo de energía." -#: ../data/sugar.schemas.in.h:22 +#: ../data/sugar.schemas.in.h:45 msgid "Publish to Gadget" msgstr "Publicar en Gadget" -#: ../data/sugar.schemas.in.h:23 +#: ../data/sugar.schemas.in.h:46 msgid "Setting for muting the sound device." msgstr "Configuración para silenciar el dispositivo de sonido." -#: ../data/sugar.schemas.in.h:24 +#: ../data/sugar.schemas.in.h:47 msgid "Show Log out" msgstr "Mostrar Terminar Sesión" -#: ../data/sugar.schemas.in.h:25 +#: ../data/sugar.schemas.in.h:48 +msgid "Show Restart" +msgstr "Mostrar Reiniciar" + +#: ../data/sugar.schemas.in.h:49 +msgid "Show Sugar Ad-hoc networks" +msgstr "Mostrar redes específicas de Azúcar" + +#: ../data/sugar.schemas.in.h:50 msgid "Sound Muted" msgstr "Sonido silenciado" -#: ../data/sugar.schemas.in.h:26 +#: ../data/sugar.schemas.in.h:51 msgid "The keyboard model to be used" msgstr "El modelo del teclado que se utilizará" -#: ../data/sugar.schemas.in.h:28 +#: ../data/sugar.schemas.in.h:53 msgid "Timezone setting for the system." msgstr "Configuración de zona horaria para el sistema." -#: ../data/sugar.schemas.in.h:29 +#: ../data/sugar.schemas.in.h:54 msgid "Url of the jabber server to use." msgstr "URL del servidor de Jabber para usar." -#: ../data/sugar.schemas.in.h:30 +#: ../data/sugar.schemas.in.h:55 msgid "Url where the backup is saved to." msgstr "URL donde se guarda el backup." -#: ../data/sugar.schemas.in.h:31 +#: ../data/sugar.schemas.in.h:56 msgid "User Color" msgstr "Color del usuario" -#: ../data/sugar.schemas.in.h:32 +#: ../data/sugar.schemas.in.h:57 msgid "User Name" msgstr "Nombre de usuario" -#: ../data/sugar.schemas.in.h:33 +#: ../data/sugar.schemas.in.h:58 msgid "User name that is used throughout the desktop." msgstr "Nombre de usuario que se utiliza en todo el escritorio." -#: ../data/sugar.schemas.in.h:34 +#: ../data/sugar.schemas.in.h:59 +msgid "" +"Users will not be allowed to erase these activities through the list view." +msgstr "" +"Usuarios que no se les permitirá borrar actividades a través de la vista de " +"lista." + +#: ../data/sugar.schemas.in.h:60 msgid "Volume Level" msgstr "Nivel de volumen" -#: ../data/sugar.schemas.in.h:35 +#: ../data/sugar.schemas.in.h:61 msgid "Volume level for the sound device." msgstr "Nivel de volumen para el dispositivo de sonido." -#: ../data/sugar.schemas.in.h:36 +#: ../data/sugar.schemas.in.h:62 msgid "" "When in resume mode, clicking on a favorite icon will cause the last entry " "for that activity to be resumed." @@ -677,7 +974,7 @@ msgstr "sugar-control-panel: %s" # which must appear in the translated string (msgstr) as well. #. TRANS: Translators, there's a empty line at the end of this string, #. which must appear in the translated string (msgstr) as well. -#: ../src/jarabe/controlpanel/cmd.py:37 +#: ../src/jarabe/controlpanel/cmd.py:38 msgid "" "Usage: sugar-control-panel [ option ] key [ args ... ] \n" " Control for the sugar environment. \n" @@ -701,72 +998,52 @@ msgstr "" " -c clave vaciar el valor actual de la clave \n" " " -#: ../src/jarabe/controlpanel/cmd.py:50 +#: ../src/jarabe/controlpanel/cmd.py:52 msgid "To apply your changes you have to restart sugar.\n" msgstr "Para aplicar sus cambios tiene que reiniciar Azúcar.\n" -#: ../src/jarabe/controlpanel/gui.py:280 +#: ../src/jarabe/controlpanel/gui.py:297 +#: ../src/jarabe/journal/journaltoolbox.py:437 +#: ../src/jarabe/journal/volumestoolbar.py:158 msgid "Warning" msgstr "Advertencia" -#: ../src/jarabe/controlpanel/gui.py:281 -#: ../src/jarabe/controlpanel/sectionview.py:42 +#: ../src/jarabe/controlpanel/gui.py:298 +#: ../src/jarabe/controlpanel/sectionview.py:41 msgid "Changes require restart" msgstr "Los cambios requieren reiniciar" -#: ../src/jarabe/controlpanel/gui.py:284 +#: ../src/jarabe/controlpanel/gui.py:301 msgid "Cancel changes" msgstr "Cancelar cambios" -#: ../src/jarabe/controlpanel/gui.py:289 ../src/jarabe/desktop/homebox.py:70 +#: ../src/jarabe/controlpanel/gui.py:306 ../src/jarabe/desktop/homebox.py:72 msgid "Later" msgstr "Después" -#: ../src/jarabe/controlpanel/gui.py:293 +#: ../src/jarabe/controlpanel/gui.py:310 msgid "Restart now" msgstr "Reiniciar ahora" -#: ../src/jarabe/controlpanel/toolbar.py:61 ../src/jarabe/intro/window.py:188 +#: ../src/jarabe/controlpanel/toolbar.py:63 ../src/jarabe/intro/window.py:212 msgid "Done" msgstr "Hecho" -#: ../src/jarabe/controlpanel/toolbar.py:115 -#: ../src/jarabe/desktop/homebox.py:68 -#: ../src/jarabe/frame/activitiestray.py:726 -#: ../src/jarabe/frame/activitiestray.py:822 -#: ../src/jarabe/frame/activitiestray.py:850 -msgid "Cancel" -msgstr "Cancelar" - -#: ../src/jarabe/controlpanel/toolbar.py:121 -#: ../src/jarabe/desktop/favoritesview.py:332 +#: ../src/jarabe/controlpanel/toolbar.py:125 +#: ../src/jarabe/desktop/favoritesview.py:336 msgid "Ok" -msgstr "Ok" - -#: ../src/jarabe/desktop/activitieslist.py:80 -#: ../src/jarabe/journal/listview.py:147 -msgid "Title" -msgstr "Título" - -#: ../src/jarabe/desktop/activitieslist.py:91 -msgid "Version" -msgstr "Versión" - -#: ../src/jarabe/desktop/activitieslist.py:105 -#: ../src/jarabe/journal/listview.py:178 -msgid "Date" -msgstr "Fecha" +msgstr "Aceptar" -#: ../src/jarabe/desktop/activitieslist.py:234 +#: ../src/jarabe/desktop/activitieslist.py:230 #, python-format msgid "Version %s" msgstr "Versión %s" -#: ../src/jarabe/desktop/activitieslist.py:355 +#: ../src/jarabe/desktop/activitieslist.py:354 msgid "Confirm erase" msgstr "Confirmar borrado" -#: ../src/jarabe/desktop/activitieslist.py:357 +#: ../src/jarabe/desktop/activitieslist.py:356 #, python-format msgid "Confirm erase: Do you want to permanently erase %s?" msgstr "Confirmar el borrado: ¿Quiere borrar %s de forma permanente?" @@ -775,390 +1052,448 @@ msgstr "Confirmar el borrado: ¿Quiere borrar %s de forma permanente?" # 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 +#: ../src/jarabe/desktop/activitieslist.py:360 +#: ../src/jarabe/frame/clipboardmenu.py:64 +#: ../src/jarabe/view/viewsource.py:221 msgid "Keep" msgstr "Guardar" -#: ../src/jarabe/desktop/activitieslist.py:364 -#: ../src/jarabe/desktop/activitieslist.py:407 -#: ../src/jarabe/journal/journaltoolbox.py:360 +#: ../src/jarabe/desktop/activitieslist.py:363 +#: ../src/jarabe/desktop/activitieslist.py:417 +#: ../src/jarabe/journal/journaltoolbox.py:391 #: ../src/jarabe/journal/palettes.py:112 msgid "Erase" msgstr "Borrar" -#: ../src/jarabe/desktop/activitieslist.py:428 +#: ../src/jarabe/desktop/activitieslist.py:432 msgid "Remove favorite" msgstr "Remover favorito" -#: ../src/jarabe/desktop/activitieslist.py:432 +#: ../src/jarabe/desktop/activitieslist.py:436 msgid "Make favorite" msgstr "Hacer favorito" # 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 +#: ../src/jarabe/desktop/favoriteslayout.py:127 msgid "Freeform" msgstr "Forma libre" # 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:198 +#: ../src/jarabe/desktop/favoriteslayout.py:215 msgid "Ring" msgstr "Anillo" # 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:337 +#: ../src/jarabe/desktop/favoriteslayout.py:402 msgid "Spiral" msgstr "Espiral" # 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:404 +#: ../src/jarabe/desktop/favoriteslayout.py:472 msgid "Box" msgstr "Caja" # 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:445 +#: ../src/jarabe/desktop/favoriteslayout.py:515 msgid "Triangle" msgstr "Triángulo" -#: ../src/jarabe/desktop/favoritesview.py:323 +#: ../src/jarabe/desktop/favoritesview.py:327 msgid "Registration Failed" -msgstr "Registro fallido" +msgstr "Error al registrar" -#: ../src/jarabe/desktop/favoritesview.py:324 +#: ../src/jarabe/desktop/favoritesview.py:328 #, python-format msgid "%s" msgstr "%s" -#: ../src/jarabe/desktop/favoritesview.py:326 +#: ../src/jarabe/desktop/favoritesview.py:330 msgid "Registration Successful" msgstr "Registro exitoso" -#: ../src/jarabe/desktop/favoritesview.py:327 +#: ../src/jarabe/desktop/favoritesview.py:331 msgid "You are now registered with your school server." msgstr "Ahora estás registrado en el servidor de colegio." -#: ../src/jarabe/desktop/favoritesview.py:671 +#: ../src/jarabe/desktop/favoritesview.py:627 msgid "Register" msgstr "Registro" -#: ../src/jarabe/desktop/homebox.py:63 +#: ../src/jarabe/desktop/favoritesview.py:629 +#: ../src/jarabe/desktop/favoritesview.py:646 +msgid "Register again" +msgstr "Registrar nuevamente" + +#: ../src/jarabe/desktop/homebox.py:65 msgid "Software Update" msgstr "Actualización de Software" -#: ../src/jarabe/desktop/homebox.py:64 +#: ../src/jarabe/desktop/homebox.py:66 msgid "Update your activities to ensure compatibility with your new software" msgstr "" "Actualice sus actividades para asegurar compatibilidad con su nuevo software" -#: ../src/jarabe/desktop/homebox.py:73 +#: ../src/jarabe/desktop/homebox.py:75 msgid "Check now" msgstr "Pruebe ahora" -#: ../src/jarabe/desktop/homebox.py:192 +#: ../src/jarabe/desktop/homebox.py:193 msgid "List view" msgstr "Vista en lista" -#: ../src/jarabe/desktop/homebox.py:193 +#: ../src/jarabe/desktop/homebox.py:194 msgid "<Ctrl>2" msgstr "<Ctrl>2" -#: ../src/jarabe/desktop/homebox.py:255 +#: ../src/jarabe/desktop/homebox.py:257 msgid "Favorites view" msgstr "Vista de favoritos" -#: ../src/jarabe/desktop/homebox.py:256 +#: ../src/jarabe/desktop/homebox.py:258 msgid "<Ctrl>1" msgstr "<Ctrl>1" # This is an encryption key type, not a keyboard key -#: ../src/jarabe/desktop/keydialog.py:131 +#: ../src/jarabe/desktop/keydialog.py:143 msgid "Key Type:" msgstr "Tipo de clave:" -#: ../src/jarabe/desktop/keydialog.py:151 +#: ../src/jarabe/desktop/keydialog.py:163 msgid "Authentication Type:" msgstr "Tipo de autenticación:" -#: ../src/jarabe/desktop/keydialog.py:215 +#: ../src/jarabe/desktop/keydialog.py:229 msgid "WPA & WPA2 Personal" msgstr "WPA y WPA2 Personal" -#: ../src/jarabe/desktop/keydialog.py:224 +#: ../src/jarabe/desktop/keydialog.py:238 msgid "Wireless Security:" msgstr "Seguridad inalámbrica:" -#: ../src/jarabe/desktop/meshbox.py:136 -msgid "Connect" -msgstr "Conectar" - -#: ../src/jarabe/desktop/meshbox.py:140 -msgid "Disconnect" -msgstr "Desconectar" - # TRANS: Action label for resuming an activity. #. TRANS: Action label for resuming an activity. -#: ../src/jarabe/desktop/meshbox.py:466 -#: ../src/jarabe/frame/activitiestray.py:761 -#: ../src/jarabe/journal/journaltoolbox.py:428 -#: ../src/jarabe/journal/palettes.py:72 ../src/jarabe/view/palettes.py:64 +#: ../src/jarabe/desktop/meshbox.py:109 +#: ../src/jarabe/frame/activitiestray.py:622 +#: ../src/jarabe/journal/journaltoolbox.py:485 +#: ../src/jarabe/journal/palettes.py:66 ../src/jarabe/view/palettes.py:78 msgid "Resume" msgstr "Retomar" -#: ../src/jarabe/desktop/meshbox.py:471 -#: ../src/jarabe/frame/activitiestray.py:235 +#: ../src/jarabe/desktop/meshbox.py:114 +#: ../src/jarabe/frame/activitiestray.py:173 msgid "Join" msgstr "Unirse" -#: ../src/jarabe/desktop/schoolserver.py:103 +#: ../src/jarabe/desktop/networkviews.py:497 +#, python-format +msgid "Ad-hoc Network %d" +msgstr "Red específica %d" + +#: ../src/jarabe/desktop/networkviews.py:628 +#, python-format +msgid "Mesh Network %d" +msgstr "Red Malla %d" + +#: ../src/jarabe/desktop/schoolserver.py:131 msgid "Cannot connect to the server." msgstr "No se puede conectar al servidor." -#: ../src/jarabe/desktop/schoolserver.py:108 +#: ../src/jarabe/desktop/schoolserver.py:136 msgid "The server could not complete the request." msgstr "El servidor no pudo completar el pedido." -#: ../src/jarabe/frame/activitiestray.py:240 -#: ../src/jarabe/frame/activitiestray.py:698 +#: ../src/jarabe/frame/activitiestray.py:178 +#: ../src/jarabe/frame/activitiestray.py:559 msgid "Decline" msgstr "Rechazar" -#: ../src/jarabe/frame/activitiestray.py:650 +#: ../src/jarabe/frame/activitiestray.py:509 #, python-format msgid "%dB" msgstr "%dB" -#: ../src/jarabe/frame/activitiestray.py:652 +#: ../src/jarabe/frame/activitiestray.py:511 #, python-format msgid "%dKB" msgstr "%dKB" -#: ../src/jarabe/frame/activitiestray.py:654 +#: ../src/jarabe/frame/activitiestray.py:513 #, python-format msgid "%dMB" msgstr "%dMB" -#: ../src/jarabe/frame/activitiestray.py:671 +#: ../src/jarabe/frame/activitiestray.py:530 #, python-format msgid "%s of %s" msgstr "%s de %s" -#: ../src/jarabe/frame/activitiestray.py:683 +#: ../src/jarabe/frame/activitiestray.py:544 #, python-format msgid "Transfer from %r" msgstr "Transferencia desde %r" -#: ../src/jarabe/frame/activitiestray.py:693 +#: ../src/jarabe/frame/activitiestray.py:554 msgid "Accept" msgstr "Aceptar" -#: ../src/jarabe/frame/activitiestray.py:716 -#: ../src/jarabe/frame/activitiestray.py:840 +#: ../src/jarabe/frame/activitiestray.py:577 +#: ../src/jarabe/frame/activitiestray.py:705 #, python-format msgid "%s (%s)" msgstr "%s (%s)" -#: ../src/jarabe/frame/activitiestray.py:750 -#: ../src/jarabe/frame/activitiestray.py:875 +#: ../src/jarabe/frame/activitiestray.py:611 +#: ../src/jarabe/frame/activitiestray.py:740 msgid "Dismiss" msgstr "Descartar" -#: ../src/jarabe/frame/activitiestray.py:810 +#: ../src/jarabe/frame/activitiestray.py:675 #, python-format msgid "Transfer to %r" msgstr "Transferencia a %r" -#: ../src/jarabe/frame/clipboardmenu.py:52 ../src/jarabe/view/palettes.py:218 +#: ../src/jarabe/frame/clipboardmenu.py:54 ../src/jarabe/view/palettes.py:220 msgid "Remove" msgstr "Eliminar" -#: ../src/jarabe/frame/clipboardmenu.py:57 -#: ../src/jarabe/frame/clipboardmenu.py:80 +#: ../src/jarabe/frame/clipboardmenu.py:59 +#: ../src/jarabe/frame/clipboardmenu.py:82 msgid "Open" msgstr "Abrir" -#: ../src/jarabe/frame/clipboardmenu.py:85 +#: ../src/jarabe/frame/clipboardmenu.py:87 msgid "Open with" msgstr "Abrir con" -#: ../src/jarabe/frame/clipboardobject.py:49 +#: ../src/jarabe/frame/clipboardobject.py:50 #, python-format msgid "%s clipping" msgstr "recorte de %s" -#: ../src/jarabe/frame/zoomtoolbar.py:37 +#: ../src/jarabe/frame/zoomtoolbar.py:38 msgid "Neighborhood" msgstr "Vecindario" -#: ../src/jarabe/frame/zoomtoolbar.py:37 +#: ../src/jarabe/frame/zoomtoolbar.py:38 msgid "F1" msgstr "F1" -#: ../src/jarabe/frame/zoomtoolbar.py:39 +#: ../src/jarabe/frame/zoomtoolbar.py:40 msgid "F2" msgstr "F2" -#: ../src/jarabe/frame/zoomtoolbar.py:41 +#: ../src/jarabe/frame/zoomtoolbar.py:42 msgid "F3" msgstr "F3" -#: ../src/jarabe/frame/zoomtoolbar.py:43 +#: ../src/jarabe/frame/zoomtoolbar.py:44 msgid "F4" msgstr "F4" -#: ../src/jarabe/intro/window.py:124 +#: ../src/jarabe/intro/window.py:97 +msgid "Name:" +msgstr "Nombre:" + +#: ../src/jarabe/intro/window.py:133 msgid "Click to change color:" msgstr "Clic para cambiar de color:" -#: ../src/jarabe/intro/window.py:174 ../src/jarabe/journal/detailview.py:103 +#: ../src/jarabe/intro/window.py:198 ../src/jarabe/journal/detailview.py:105 msgid "Back" msgstr "Atrás" -#: ../src/jarabe/intro/window.py:191 +#: ../src/jarabe/intro/window.py:215 msgid "Next" msgstr "Siguiente" -#: ../src/jarabe/journal/expandedentry.py:164 -#: ../src/jarabe/journal/palettes.py:66 +#: ../src/jarabe/journal/expandedentry.py:154 +#: ../src/jarabe/journal/listmodel.py:144 ../src/jarabe/journal/palettes.py:59 msgid "Untitled" msgstr "Sin título" -#: ../src/jarabe/journal/expandedentry.py:210 +#: ../src/jarabe/journal/expandedentry.py:243 msgid "No preview" msgstr "Sin vista previa" -#: ../src/jarabe/journal/expandedentry.py:229 +#: ../src/jarabe/journal/expandedentry.py:262 #, python-format msgid "Kind: %s" msgstr "Tipo: %s" -#: ../src/jarabe/journal/expandedentry.py:229 +#: ../src/jarabe/journal/expandedentry.py:262 +#: ../src/jarabe/journal/listmodel.py:150 +#: ../src/jarabe/journal/listmodel.py:158 +#: ../src/jarabe/journal/listmodel.py:166 msgid "Unknown" msgstr "Desconocido" -#: ../src/jarabe/journal/expandedentry.py:230 +#: ../src/jarabe/journal/expandedentry.py:263 #, python-format msgid "Date: %s" msgstr "Fecha: %s" -#: ../src/jarabe/journal/expandedentry.py:231 +#: ../src/jarabe/journal/expandedentry.py:264 #, python-format msgid "Size: %s" msgstr "Tamaño: %s" -#: ../src/jarabe/journal/expandedentry.py:253 ../src/jarabe/journal/misc.py:92 +#: ../src/jarabe/journal/expandedentry.py:292 +#: ../src/jarabe/journal/misc.py:108 msgid "No date" msgstr "Sin fecha" -#: ../src/jarabe/journal/expandedentry.py:260 +#: ../src/jarabe/journal/expandedentry.py:299 msgid "Participants:" msgstr "Participantes:" -#: ../src/jarabe/journal/expandedentry.py:283 +#: ../src/jarabe/journal/expandedentry.py:321 msgid "Description:" msgstr "Descripción:" -#: ../src/jarabe/journal/expandedentry.py:309 +#: ../src/jarabe/journal/expandedentry.py:346 msgid "Tags:" msgstr "Etiquetas:" -#: ../src/jarabe/journal/journalactivity.py:108 -#: ../src/jarabe/journal/volumestoolbar.py:47 +#: ../src/jarabe/journal/journalactivity.py:115 +#: ../src/jarabe/journal/journaltoolbox.py:456 +#: ../src/jarabe/journal/volumestoolbar.py:50 msgid "Journal" msgstr "Diario" -#: ../src/jarabe/journal/journaltoolbox.py:67 +#: ../src/jarabe/journal/journaltoolbox.py:69 msgid "Search" msgstr "Buscar" -#: ../src/jarabe/journal/journaltoolbox.py:126 +#: ../src/jarabe/journal/journaltoolbox.py:136 msgid "Anytime" msgstr "Cualquier momento" -#: ../src/jarabe/journal/journaltoolbox.py:128 +#: ../src/jarabe/journal/journaltoolbox.py:138 msgid "Today" msgstr "Hoy" -#: ../src/jarabe/journal/journaltoolbox.py:130 +#: ../src/jarabe/journal/journaltoolbox.py:140 msgid "Since yesterday" msgstr "Desde ayer" # TRANS: Filter entries modified during the last 7 days. #. TRANS: Filter entries modified during the last 7 days. -#: ../src/jarabe/journal/journaltoolbox.py:132 +#: ../src/jarabe/journal/journaltoolbox.py:142 msgid "Past week" msgstr "Última semana" # TRANS: Filter entries modified during the last 30 days. #. TRANS: Filter entries modified during the last 30 days. -#: ../src/jarabe/journal/journaltoolbox.py:134 +#: ../src/jarabe/journal/journaltoolbox.py:144 msgid "Past month" msgstr "Último mes" # TRANS: Filter entries modified during the last 356 days. #. TRANS: Filter entries modified during the last 356 days. -#: ../src/jarabe/journal/journaltoolbox.py:136 +#: ../src/jarabe/journal/journaltoolbox.py:146 msgid "Past year" msgstr "Último año" -#: ../src/jarabe/journal/journaltoolbox.py:143 +#: ../src/jarabe/journal/journaltoolbox.py:153 msgid "Anyone" msgstr "Cualquiera" -#: ../src/jarabe/journal/journaltoolbox.py:145 +#: ../src/jarabe/journal/journaltoolbox.py:155 msgid "My friends" msgstr "Mis amigos" -#: ../src/jarabe/journal/journaltoolbox.py:146 +#: ../src/jarabe/journal/journaltoolbox.py:156 msgid "My class" msgstr "Mi clase" # TRANS: Item in a combo box that filters by entry type. -#: ../src/jarabe/journal/journaltoolbox.py:274 +#: ../src/jarabe/journal/journaltoolbox.py:298 msgid "Anything" msgstr "Cualquiera" # TODO: Add "Start with" menu item -#: ../src/jarabe/journal/journaltoolbox.py:350 +#: ../src/jarabe/journal/journaltoolbox.py:381 #: ../src/jarabe/journal/palettes.py:90 msgid "Copy" msgstr "Copiar" +#: ../src/jarabe/journal/journaltoolbox.py:436 +#: ../src/jarabe/journal/volumestoolbar.py:157 +msgid "Entries without a file cannot be copied." +msgstr "Entradas sin un archivo no pueden ser copiadas." + +#: ../src/jarabe/journal/journaltoolbox.py:445 +#: ../src/jarabe/journal/volumestoolbar.py:166 +#, python-format +msgid "Error while copying the entry. %s" +msgstr "Error mientras se copiaba la entrada. %s" + +#: ../src/jarabe/journal/journaltoolbox.py:446 +#: ../src/jarabe/journal/volumestoolbar.py:167 +msgid "Error" +msgstr "Error" + # TRANS: Action label for starting an entry. #. TRANS: Action label for starting an entry. -#: ../src/jarabe/journal/journaltoolbox.py:431 -#: ../src/jarabe/journal/palettes.py:75 +#: ../src/jarabe/journal/journaltoolbox.py:488 +#: ../src/jarabe/journal/palettes.py:69 msgid "Start" msgstr "Iniciar" -#: ../src/jarabe/journal/listview.py:361 +#: ../src/jarabe/journal/journaltoolbox.py:516 +msgid "Sort by date modified" +msgstr "Ordenar por fecha de modificación" + +#: ../src/jarabe/journal/journaltoolbox.py:517 +msgid "Sort by date created" +msgstr "Ordenar por fecha de creación" + +#: ../src/jarabe/journal/journaltoolbox.py:518 +msgid "Sort by size" +msgstr "Ordenar por tamaño" + +#: ../src/jarabe/journal/journaltoolbox.py:527 +msgid "Sort view" +msgstr "Ordenar vista" + +#: ../src/jarabe/journal/listview.py:380 msgid "Your Journal is empty" msgstr "Su diario está vacío" -#: ../src/jarabe/journal/listview.py:363 +#: ../src/jarabe/journal/listview.py:382 msgid "No matching entries" msgstr "No hay entradas coincidentes" -#: ../src/jarabe/journal/listview.py:374 +#: ../src/jarabe/journal/listview.py:393 msgid "Clear search" msgstr "Limpiar búsqueda" -#: ../src/jarabe/journal/modalalert.py:63 +#: ../src/jarabe/journal/misc.py:273 +#, python-format +msgid "Older Version Of %s Activity" +msgstr "Versión más antigua de la actividad %s" + +#: ../src/jarabe/journal/misc.py:274 +#, python-format +msgid "Do you want to downgrade to version %s" +msgstr "¿Desea instalar la versión %s, mas antigua? " + +#: ../src/jarabe/journal/modalalert.py:64 msgid "Your Journal is full" -msgstr "Su diario está vacío" +msgstr "Su diario está lleno" -#: ../src/jarabe/journal/modalalert.py:67 +#: ../src/jarabe/journal/modalalert.py:68 msgid "Please delete some old Journal entries to make space for new ones." msgstr "" -"Por favor borre las entradas viejas del diario para hacer espacio a las " +"Por favor borre algunas entradas viejas del diario para hacer espacio a las " "nuevas entradas." -#: ../src/jarabe/journal/modalalert.py:79 +#: ../src/jarabe/journal/modalalert.py:80 msgid "Show Journal" msgstr "Mostrar diario" @@ -1167,18 +1502,22 @@ msgid "Choose an object" msgstr "Escoja un objeto" #: ../src/jarabe/journal/objectchooser.py:151 -#: ../src/jarabe/view/viewsource.py:308 +#: ../src/jarabe/view/viewsource.py:311 msgid "Close" msgstr "Cerrar" -#: ../src/jarabe/journal/palettes.py:73 +#: ../src/jarabe/journal/palettes.py:67 msgid "Resume with" msgstr "Reiniciar con" -#: ../src/jarabe/journal/palettes.py:76 +#: ../src/jarabe/journal/palettes.py:70 msgid "Start with" msgstr "Empezar con" +#: ../src/jarabe/journal/palettes.py:83 ../src/jarabe/journal/palettes.py:216 +msgid "No activity to start entry" +msgstr "No se encontró una actividad para iniciar la entrada" + #: ../src/jarabe/journal/palettes.py:98 msgid "Send to" msgstr "Enviar a" @@ -1187,103 +1526,313 @@ msgstr "Enviar a" msgid "View Details" msgstr "Ver detalles" -#: ../src/jarabe/journal/palettes.py:185 +#: ../src/jarabe/journal/palettes.py:181 msgid "No friends present" msgstr "No hay amigos presentes" # tildes -#: ../src/jarabe/journal/palettes.py:190 +#: ../src/jarabe/journal/palettes.py:186 msgid "No valid connection found" msgstr "No se encontró una conexión válida" # tildes... -#: ../src/jarabe/journal/palettes.py:218 +#: ../src/jarabe/journal/palettes.py:214 msgid "No activity to resume entry" msgstr "No se encontró una actividad para retomar la entrada" -#: ../src/jarabe/journal/palettes.py:220 -msgid "No activity to start entry" -msgstr "No se encontró una actividad para iniciar la entrada" +#: ../src/jarabe/model/network.py:163 +msgid "The reason for the device state change is unknown." +msgstr "La razón para el cambio de estado del dispositivo es desconocida." + +#: ../src/jarabe/model/network.py:165 +msgid "The state change is normal." +msgstr "El cambio de estado es normal." + +#: ../src/jarabe/model/network.py:167 +msgid "The device is now managed." +msgstr "El dispositivo está siendo administrado." + +#: ../src/jarabe/model/network.py:169 +msgid "The device is no longer managed." +msgstr "El dispositivo ya no está siendo administrado." + +#: ../src/jarabe/model/network.py:171 +msgid "The device could not be readied for configuration." +msgstr "El dispositivo no pudo ser preparado para su configuración." + +#: ../src/jarabe/model/network.py:173 +msgid "" +"IP configuration could not be reserved (no available address, timeout, etc)." +msgstr "" +"La configuración IP no pudo ser reservada (no hay dirección disponible, " +"tiempo fuera, etc)." + +#: ../src/jarabe/model/network.py:176 +msgid "The IP configuration is no longer valid." +msgstr "La configuración IP ya no es válida." + +#: ../src/jarabe/model/network.py:178 +msgid "Secrets were required, but not provided." +msgstr "Claves requeridas, pero no fueron suministradas." + +#: ../src/jarabe/model/network.py:180 +msgid "" +"The 802.1X supplicant disconnected from the access point or authentication " +"server." +msgstr "" +"El cliente 802.1X fué desconectado del punto de acceso o del server de " +"autenticación." + +#: ../src/jarabe/model/network.py:183 +msgid "Configuration of the 802.1X supplicant failed." +msgstr "Configuración del cliente 802.1X fallada." + +#: ../src/jarabe/model/network.py:185 +msgid "The 802.1X supplicant quit or failed unexpectedly." +msgstr "El cliente 802.1X ha abandonado o fallado inesperadamente." + +#: ../src/jarabe/model/network.py:187 +msgid "The 802.1X supplicant took too long to authenticate." +msgstr "El cliente 802.1X ha tomado demasiado tiempo para autenticar." + +#: ../src/jarabe/model/network.py:189 +msgid "The PPP service failed to start within the allowed time." +msgstr "El servicio PPP ha fallado en comenzar en el tiempo permitido." + +#: ../src/jarabe/model/network.py:191 +msgid "The PPP service disconnected unexpectedly." +msgstr "El servicio PPP se ha desconectado inesperadamente." + +#: ../src/jarabe/model/network.py:193 +msgid "The PPP service quit or failed unexpectedly." +msgstr "El servicio PPP ha abandonado o fallado inesperadamente." + +#: ../src/jarabe/model/network.py:195 +msgid "The DHCP service failed to start within the allowed time." +msgstr "El servicio DHCP ha fallado en comenzar en el tiempo permitido." + +#: ../src/jarabe/model/network.py:197 +msgid "The DHCP service reported an unexpected error." +msgstr "El servicio DHCP ha reportado un error inesperado." + +#: ../src/jarabe/model/network.py:199 +msgid "The DHCP service quit or failed unexpectedly." +msgstr "El servicio DHCP ha abandonado o fallado inesperadamente." + +#: ../src/jarabe/model/network.py:201 +msgid "The shared connection service failed to start." +msgstr "El servicio de conexión compartida ha fallado al iniciar." + +#: ../src/jarabe/model/network.py:203 +msgid "The shared connection service quit or failed unexpectedly." +msgstr "" +"El servicio de conexión compartida ha abandonado o fallado inesperadamente." + +#: ../src/jarabe/model/network.py:206 +msgid "The AutoIP service failed to start." +msgstr "El servicio AutoIP ha fallado al iniciar." + +#: ../src/jarabe/model/network.py:208 +msgid "The AutoIP service reported an unexpected error." +msgstr "El servicio AutoIP ha reportado un error inesperado." + +#: ../src/jarabe/model/network.py:210 +msgid "The AutoIP service quit or failed unexpectedly." +msgstr "El servicio AutoIP ha abandonado o fallado inesperadamente." + +#: ../src/jarabe/model/network.py:212 +msgid "Dialing failed because the line was busy." +msgstr "Conexión fallada porque la línea estaba ocupada." + +#: ../src/jarabe/model/network.py:214 +msgid "Dialing failed because there was no dial tone." +msgstr "Conexión fallada porque no había tono de llamada." + +#: ../src/jarabe/model/network.py:216 +msgid "Dialing failed because there was no carrier." +msgstr "Conexión fallada porque no había portadora." + +#: ../src/jarabe/model/network.py:218 +msgid "Dialing timed out." +msgstr "Llamada expiró por tiempo." + +#: ../src/jarabe/model/network.py:220 +msgid "Dialing failed." +msgstr "Llamada falló." + +#: ../src/jarabe/model/network.py:222 +msgid "Modem initialization failed." +msgstr "Fallo en inicialización de modem." + +#: ../src/jarabe/model/network.py:224 +msgid "Failed to select the specified GSM APN" +msgstr "Fallo al seleccionar el punto de acceso (APN) GSM" + +#: ../src/jarabe/model/network.py:226 +msgid "Not searching for networks." +msgstr "No se buscan redes." + +#: ../src/jarabe/model/network.py:228 +msgid "Network registration was denied." +msgstr "Registración en la red fué rechazada." + +#: ../src/jarabe/model/network.py:230 +msgid "Network registration timed out." +msgstr "Registración en la red expiró." + +#: ../src/jarabe/model/network.py:232 +msgid "Failed to register with the requested GSM network." +msgstr "Falló la registración con la red GSM solicitada." + +#: ../src/jarabe/model/network.py:234 +msgid "PIN check failed." +msgstr "Control de PIN fallado." + +#: ../src/jarabe/model/network.py:236 +msgid "Necessary firmware for the device may be missing." +msgstr "Firmware necesario para el dispositivo puede faltar." + +#: ../src/jarabe/model/network.py:238 +msgid "The device was removed." +msgstr "El dispositivo fué quitado." + +#: ../src/jarabe/model/network.py:240 +msgid "NetworkManager went to sleep." +msgstr "NetworkManager fue dormido." + +#: ../src/jarabe/model/network.py:242 +msgid "The device's active connection was removed or disappeared." +msgstr "" +"Las conexiones activas del dispositivo fueron removidas o desaparecieron." + +#: ../src/jarabe/model/network.py:245 +msgid "A user or client requested the disconnection." +msgstr "Un usuario o cliente solicitó la desconexión." + +#: ../src/jarabe/model/network.py:247 +msgid "The device's carrier/link changed." +msgstr "La portadora/link del dispositivo a cambiado." # "Eliminate friend"??? That's a bit harsh. Wouldn't "quitar amigo" be a better choice?-- # agree but i preffer remover :). that verbe has the exact meaning we are looking on here. -#: ../src/jarabe/view/buddymenu.py:62 +#: ../src/jarabe/view/buddymenu.py:63 msgid "Remove friend" msgstr "Remover amigo" -#: ../src/jarabe/view/buddymenu.py:65 +#: ../src/jarabe/view/buddymenu.py:66 msgid "Make friend" msgstr "Agregar amigo" -#: ../src/jarabe/view/buddymenu.py:82 +#: ../src/jarabe/view/buddymenu.py:83 msgid "Shutdown" msgstr "Apagar" -#: ../src/jarabe/view/buddymenu.py:90 +#: ../src/jarabe/view/buddymenu.py:91 +msgid "Restart" +msgstr "Reiniciar" + +#: ../src/jarabe/view/buddymenu.py:97 msgid "Logout" msgstr "Salir" -#: ../src/jarabe/view/buddymenu.py:95 +#: ../src/jarabe/view/buddymenu.py:102 msgid "My Settings" msgstr "Mis ajustes" -#: ../src/jarabe/view/buddymenu.py:130 +#: ../src/jarabe/view/buddymenu.py:137 #, python-format msgid "Invite to %s" msgstr "Invitar a %s" -#: ../src/jarabe/view/palettes.py:45 +#: ../src/jarabe/view/launcher.py:190 +#, python-format +msgid "<b>%s</b> failed to start." +msgstr "<b>%s</b> falló al iniciar." + +#: ../src/jarabe/view/palettes.py:46 msgid "Starting..." msgstr "Iniciando..." +#: ../src/jarabe/view/palettes.py:56 +msgid "Activity failed to start" +msgstr "Actividad falló al iniciar" + #. TODO: share-with, keep -#: ../src/jarabe/view/palettes.py:71 +#: ../src/jarabe/view/palettes.py:85 msgid "View Source" msgstr "Ver fuente" -#: ../src/jarabe/view/palettes.py:82 +#: ../src/jarabe/view/palettes.py:96 msgid "Stop" msgstr "Parar" -#: ../src/jarabe/view/palettes.py:122 +#: ../src/jarabe/view/palettes.py:132 msgid "Start new" msgstr "Empezar nuevo" -#: ../src/jarabe/view/palettes.py:171 +#: ../src/jarabe/view/palettes.py:172 msgid "Show contents" msgstr "Mostrar contenidos" -#: ../src/jarabe/view/palettes.py:193 ../src/jarabe/view/palettes.py:243 +#: ../src/jarabe/view/palettes.py:194 ../src/jarabe/view/palettes.py:245 #, python-format msgid "%(free_space)d MB Free" msgstr "%(free_space)d MB libres" -#: ../src/jarabe/view/viewsource.py:208 +#: ../src/jarabe/view/viewsource.py:211 msgid "Instance Source" msgstr "Fuente de la instancia" -#: ../src/jarabe/view/viewsource.py:233 +#: ../src/jarabe/view/viewsource.py:236 msgid "Source" msgstr "Fuente" -#: ../src/jarabe/view/viewsource.py:292 +#: ../src/jarabe/view/viewsource.py:295 msgid "Activity Bundle Source" msgstr "Fuente del paquete de la actividad" -#: ../src/jarabe/view/viewsource.py:299 +#: ../src/jarabe/view/viewsource.py:302 #, python-format msgid "View source: %r" msgstr "Ver código fuente: %r" +#: ../src/jarabe/util/emulator.py:40 +msgid "Sugar in a window" +msgstr "Sugar en una ventana" + +# Access point name for GPRS network +#~ msgid "APN:" +#~ msgstr "Nombre del Punto de Acceso:" + +#~ msgid "Create new wireless network" +#~ msgstr "Crear nueva red inalámbrica" + +#, python-format +#~ msgid "%s's network" +#~ msgstr "Red de %s" + +#, python-format +#~ msgid "Data sent %d kb / received %d kb" +#~ msgstr "Datos enviados %d kb / recibidos %d kb" + +#~ msgid "Connection time " +#~ msgstr "Tiempo de conexión " + +#~ msgid "Title" +#~ msgstr "Título" + +#~ msgid "Version" +#~ msgstr "Versión" + +#~ msgid "Date" +#~ msgstr "Fecha" + #~ msgid "Cannot obtain data needed for registration." #~ msgstr "No se puede obtener datos necesarios para el registro" #~ msgid "Unmount" #~ msgstr "Desmontar" -#~ msgid "Restart" -#~ msgstr "Reiniciar" - #~ msgid "" #~ "© 2008 One Laptop per Child Association Inc; Red Hat Inc; and Contributors." #~ msgstr "" @@ -1306,12 +1855,6 @@ msgstr "Ver código fuente: %r" #~ msgid "Disconnecting..." #~ msgstr "Desconectando..." -#~ msgid "Mesh Network" -#~ msgstr "Red Malla" - -#~ msgid "Disconnected" -#~ msgstr "Desconectado" - #~ msgid "About my XO" #~ msgstr "Acerca de mi XO" @@ -1,3 +1,15 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # translation of sugar.po to french # Copyright (C) 2007 the Package Owner # This file is distributed under the same license as the sugar graphical shell package. @@ -6,8 +18,8 @@ msgid "" msgstr "" "Project-Id-Version: sugar\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2009-09-05 00:31-0400\n" -"PO-Revision-Date: 2009-09-16 01:37-0400\n" +"POT-Creation-Date: 2011-02-06 00:30-0500\n" +"PO-Revision-Date: 2011-02-11 18:23+0200\n" "Last-Translator: samy boutayeb <s.boutayeb@free.fr>\n" "Language-Team: French <traduc@traduc.org>\n" "Language: fr\n" @@ -15,49 +27,45 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -"X-Generator: Pootle 1.2.1\n" +"X-Generator: Pootle 2.0.3\n" #: ../extensions/cpsection/aboutme/__init__.py:24 msgid "About Me" msgstr "Moi" -#: ../extensions/cpsection/aboutme/model.py:43 +#: ../extensions/cpsection/aboutme/model.py:48 msgid "You must enter a name." msgstr "Vous devez indiquer un nom." -#: ../extensions/cpsection/aboutme/model.py:68 +#: ../extensions/cpsection/aboutme/model.py:75 #, python-format msgid "stroke: color=%s hue=%s" msgstr "stroke: color=%s hue=%s" -#: ../extensions/cpsection/aboutme/model.py:71 +#: ../extensions/cpsection/aboutme/model.py:78 #, python-format msgid "stroke: %s" msgstr "stroke: %s" -#: ../extensions/cpsection/aboutme/model.py:73 +#: ../extensions/cpsection/aboutme/model.py:80 #, python-format msgid "fill: color=%s hue=%s" msgstr "fill: color=%s hue=%s" -#: ../extensions/cpsection/aboutme/model.py:75 +#: ../extensions/cpsection/aboutme/model.py:82 #, python-format msgid "fill: %s" msgstr "fill: %s" -#: ../extensions/cpsection/aboutme/model.py:86 +#: ../extensions/cpsection/aboutme/model.py:94 msgid "Error in specified color modifiers." msgstr "Erreur dans les modificateurs de couleur spécifiés." -#: ../extensions/cpsection/aboutme/model.py:89 +#: ../extensions/cpsection/aboutme/model.py:97 msgid "Error in specified colors." msgstr "Erreur dans les couleurs spécifiées." -#: ../extensions/cpsection/aboutme/view.py:94 ../src/jarabe/intro/window.py:92 -msgid "Name:" -msgstr "Nom :" - -#: ../extensions/cpsection/aboutme/view.py:128 +#: ../extensions/cpsection/aboutme/view.py:235 msgid "Click to change your color:" msgstr "Cliquer pour changer de couleur :" @@ -65,43 +73,48 @@ msgstr "Cliquer pour changer de couleur :" msgid "About my Computer" msgstr "Mon ordinateur" -#: ../extensions/cpsection/aboutcomputer/model.py:28 +#: ../extensions/cpsection/aboutcomputer/model.py:37 msgid "Not available" msgstr "Non disponible" -#: ../extensions/cpsection/aboutcomputer/view.py:60 +#: ../extensions/cpsection/aboutcomputer/model.py:158 +#, python-format +msgid "%(interface)s: %(version)s" +msgstr "%(interface)s : %(version)s" + +#: ../extensions/cpsection/aboutcomputer/view.py:61 msgid "Identity" msgstr "Identité" -#: ../extensions/cpsection/aboutcomputer/view.py:69 +#: ../extensions/cpsection/aboutcomputer/view.py:70 msgid "Serial Number:" msgstr "Numéro de série :" -#: ../extensions/cpsection/aboutcomputer/view.py:91 +#: ../extensions/cpsection/aboutcomputer/view.py:92 msgid "Software" msgstr "Logiciel" -#: ../extensions/cpsection/aboutcomputer/view.py:100 +#: ../extensions/cpsection/aboutcomputer/view.py:101 msgid "Build:" msgstr "Version :" -#: ../extensions/cpsection/aboutcomputer/view.py:115 +#: ../extensions/cpsection/aboutcomputer/view.py:116 msgid "Sugar:" msgstr "Sugar :" -#: ../extensions/cpsection/aboutcomputer/view.py:131 +#: ../extensions/cpsection/aboutcomputer/view.py:132 msgid "Firmware:" msgstr "Micrologiciel :" -#: ../extensions/cpsection/aboutcomputer/view.py:146 +#: ../extensions/cpsection/aboutcomputer/view.py:147 msgid "Wireless Firmware:" msgstr "Micrologiciel sans fil :" -#: ../extensions/cpsection/aboutcomputer/view.py:169 +#: ../extensions/cpsection/aboutcomputer/view.py:170 msgid "Copyright and License" msgstr "Copyright et licence" -#: ../extensions/cpsection/aboutcomputer/view.py:184 +#: ../extensions/cpsection/aboutcomputer/view.py:188 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 " @@ -113,7 +126,7 @@ msgstr "" "License). Vous êtes autorisé à le modifier et/ou à en distribuer des copies " "aux conditions spécifiées." -#: ../extensions/cpsection/aboutcomputer/view.py:196 +#: ../extensions/cpsection/aboutcomputer/view.py:200 msgid "Full license:" msgstr "Licence complète :" @@ -121,11 +134,11 @@ msgstr "Licence complète :" msgid "Date & Time" msgstr "Date & heure" -#: ../extensions/cpsection/datetime/model.py:87 +#: ../extensions/cpsection/datetime/model.py:92 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:27 +#: ../extensions/cpsection/datetime/view.py:70 ../data/sugar.schemas.in.h:52 msgid "Timezone" msgstr "Fuseau horaire" @@ -133,50 +146,50 @@ msgstr "Fuseau horaire" msgid "Frame" msgstr "Cadre" -#: ../extensions/cpsection/frame/model.py:38 -#: ../extensions/cpsection/frame/model.py:60 +#: ../extensions/cpsection/frame/model.py:41 +#: ../extensions/cpsection/frame/model.py:66 msgid "Value must be an integer." msgstr "La valeur doit être un entier." -#: ../extensions/cpsection/frame/view.py:26 +#: ../extensions/cpsection/frame/view.py:27 msgid "never" msgstr "jamais" -#: ../extensions/cpsection/frame/view.py:27 +#: ../extensions/cpsection/frame/view.py:28 msgid "instantaneous" msgstr "immédiat" -#: ../extensions/cpsection/frame/view.py:28 +#: ../extensions/cpsection/frame/view.py:29 #, python-format msgid "%s seconds" msgstr "%s secondes" -#: ../extensions/cpsection/frame/view.py:52 +#: ../extensions/cpsection/frame/view.py:54 msgid "Activation Delay" msgstr "Délai d'activation" -#: ../extensions/cpsection/frame/view.py:76 +#: ../extensions/cpsection/frame/view.py:78 msgid "Corner" msgstr "Coin" -#: ../extensions/cpsection/frame/view.py:111 +#: ../extensions/cpsection/frame/view.py:113 msgid "Edge" msgstr "Bord" #: ../extensions/cpsection/keyboard/__init__.py:21 -#: ../extensions/cpsection/keyboard/view.py:31 +#: ../extensions/cpsection/keyboard/view.py:32 msgid "Keyboard" msgstr "Clavier" -#: ../extensions/cpsection/keyboard/view.py:187 +#: ../extensions/cpsection/keyboard/view.py:190 msgid "Keyboard Model" msgstr "Modèle de clavier" -#: ../extensions/cpsection/keyboard/view.py:243 +#: ../extensions/cpsection/keyboard/view.py:250 msgid "Key(s) to change layout" msgstr "Touche(s) de modification de la disposition" -#: ../extensions/cpsection/keyboard/view.py:311 +#: ../extensions/cpsection/keyboard/view.py:319 msgid "Keyboard Layout(s)" msgstr "Disposition(s) du clavier" @@ -185,21 +198,21 @@ msgstr "Disposition(s) du clavier" msgid "Language" msgstr "Langue" -#: ../extensions/cpsection/language/model.py:28 +#: ../extensions/cpsection/language/model.py:30 msgid "Could not access ~/.i18n. Create standard settings." msgstr "Accès impossible à ~/.i18n. Création de paramètres par défaut." -#: ../extensions/cpsection/language/model.py:124 +#: ../extensions/cpsection/language/model.py:131 #, python-format msgid "Language for code=%s could not be determined." msgstr "La langue associée au code = %s n'a pas pu être déterminée." -#: ../extensions/cpsection/language/model.py:144 +#: ../extensions/cpsection/language/model.py:152 #, python-format msgid "Sorry I do not speak '%s'." msgstr "Désolé je ne parle pas '%s'." -#: ../extensions/cpsection/language/view.py:56 +#: ../extensions/cpsection/language/view.py:57 msgid "" "Add languages in the order you prefer. If a translation is not available, " "the next in the list will be used." @@ -207,50 +220,86 @@ 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/modemconfiguration/__init__.py:21 +msgid "Modem Configuration" +msgstr "Configuration du modem" + +#: ../extensions/cpsection/modemconfiguration/view.py:94 +msgid "Username:" +msgstr "Identifiant :" + +#: ../extensions/cpsection/modemconfiguration/view.py:106 +msgid "Password:" +msgstr "Mot de passe :" + +#: ../extensions/cpsection/modemconfiguration/view.py:118 +msgid "Number:" +msgstr "Numéro :" + +#: ../extensions/cpsection/modemconfiguration/view.py:130 +msgid "Access Point Name (APN):" +msgstr "Nom du Point d'Accès (APN) :" + +#: ../extensions/cpsection/modemconfiguration/view.py:142 +msgid "Personal Identity Number (PIN):" +msgstr "Numéro d'Identité Personnel (PIN) :" + +#: ../extensions/cpsection/modemconfiguration/view.py:154 +msgid "Personal Unblocking Key (PUK):" +msgstr "Clé de Déblocage Personnelle (PUK) :" + +#: ../extensions/cpsection/modemconfiguration/view.py:175 +msgid "" +"You will need to provide the following information to set up a mobile " +"broadband connection to a cellular (3G) network." +msgstr "" +"Fournir les informations ci-après pour configurer une connexion large bande " +"à un réseau cellulaire (3G)." + #: ../extensions/cpsection/network/__init__.py:21 -#: ../extensions/cpsection/network/view.py:28 +#: ../extensions/cpsection/network/view.py:29 msgid "Network" msgstr "Réseau" -#: ../extensions/cpsection/network/model.py:79 +#: ../extensions/cpsection/network/model.py:71 msgid "State is unknown." msgstr "État inconnu." -#: ../extensions/cpsection/network/model.py:105 +#: ../extensions/cpsection/network/model.py:99 msgid "Error in specified radio argument use on/off." msgstr "Argument 'radio' spécifié incorrect. Utiliser marche/arrêt." -#: ../extensions/cpsection/network/model.py:137 +#: ../extensions/cpsection/network/model.py:140 msgid "Error in specified argument use 0/1." msgstr "Argument spécifié incorrect. Utiliser 0/1." -#: ../extensions/cpsection/network/view.py:59 +#: ../extensions/cpsection/network/view.py:61 msgid "Wireless" msgstr "Réseau sans fil" -#: ../extensions/cpsection/network/view.py:67 +#: ../extensions/cpsection/network/view.py:69 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:80 +#: ../extensions/cpsection/network/view.py:82 msgid "Radio" msgstr "Radio" -#: ../extensions/cpsection/network/view.py:96 +#: ../extensions/cpsection/network/view.py:98 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:105 +#: ../extensions/cpsection/network/view.py:107 msgid "Discard network history" msgstr "Ignorer l'historique du réseau" -#: ../extensions/cpsection/network/view.py:118 +#: ../extensions/cpsection/network/view.py:122 msgid "Collaboration" msgstr "Collaboration" -#: ../extensions/cpsection/network/view.py:126 +#: ../extensions/cpsection/network/view.py:130 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 " @@ -260,7 +309,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:136 +#: ../extensions/cpsection/network/view.py:140 msgid "Server:" msgstr "Serveur :" @@ -268,23 +317,23 @@ msgstr "Serveur :" msgid "Power" msgstr "Alimentation" -#: ../extensions/cpsection/power/model.py:54 +#: ../extensions/cpsection/power/model.py:90 msgid "Error in automatic pm argument, use on/off." -msgstr "Erreur dans l'argument gestion de l'alimentation automatique" +msgstr "Erreur dans l'argument gestion de l'alimentation automatique." -#: ../extensions/cpsection/power/model.py:81 +#: ../extensions/cpsection/power/model.py:120 msgid "Error in extreme pm argument, use on/off." -msgstr "Erreur dans l'argument gestion de l'alimentation extrême" +msgstr "Erreur dans l'argument gestion de l'alimentation extrême." -#: ../extensions/cpsection/power/view.py:47 +#: ../extensions/cpsection/power/view.py:48 msgid "Power management" msgstr "Gestion de l'alimentation" -#: ../extensions/cpsection/power/view.py:57 +#: ../extensions/cpsection/power/view.py:58 msgid "Automatic power management (increases battery life)" msgstr "Gestion automatique de l'alimentation (prolonge la batterie)" -#: ../extensions/cpsection/power/view.py:85 +#: ../extensions/cpsection/power/view.py:86 msgid "" "Extreme power management (disableswireless radio, increases battery life)" msgstr "" @@ -295,7 +344,7 @@ msgstr "" msgid "Software update" msgstr "Mise à jour logicielle" -#: ../extensions/cpsection/updater/view.py:62 +#: ../extensions/cpsection/updater/view.py:63 msgid "" "Software updates correct errors, eliminate security vulnerabilities, and " "provide new features." @@ -303,109 +352,109 @@ msgstr "" "Les mises à jour logicielles corrigent les erreurs, éliminent les failles de " "sécurité et apportent de nouvelles fonctionnalités." -#: ../extensions/cpsection/updater/view.py:122 +#: ../extensions/cpsection/updater/view.py:125 #, python-format msgid "Checking %s..." msgstr "Vérification de %s..." -#: ../extensions/cpsection/updater/view.py:124 +#: ../extensions/cpsection/updater/view.py:127 #, python-format msgid "Downloading %s..." msgstr "Téléchargement de %s..." -#: ../extensions/cpsection/updater/view.py:126 +#: ../extensions/cpsection/updater/view.py:129 #, python-format msgid "Updating %s..." msgstr "Mise à jour de %s..." -#: ../extensions/cpsection/updater/view.py:135 +#: ../extensions/cpsection/updater/view.py:139 msgid "Your software is up-to-date" msgstr "Vos logiciels sont à jour" -#: ../extensions/cpsection/updater/view.py:137 +#: ../extensions/cpsection/updater/view.py:141 #, python-format msgid "You can install %s update" msgid_plural "You can install %s updates" msgstr[0] "Vous pouvez installer %s mise à jour" msgstr[1] "Vous pouvez installer %s mises à jour" -#: ../extensions/cpsection/updater/view.py:155 +#: ../extensions/cpsection/updater/view.py:159 msgid "Checking for updates..." msgstr "Vérification des mises à jour..." -#: ../extensions/cpsection/updater/view.py:160 +#: ../extensions/cpsection/updater/view.py:164 msgid "Installing updates..." msgstr "Installation des mises à jour..." -#: ../extensions/cpsection/updater/view.py:165 +#: ../extensions/cpsection/updater/view.py:173 #, python-format msgid "%s update was installed" msgid_plural "%s updates were installed" msgstr[0] "%s mise à jour a été installée" msgstr[1] "%s mises à jour ont été installées" -#: ../extensions/cpsection/updater/view.py:244 +#: ../extensions/cpsection/updater/view.py:255 msgid "Install selected" msgstr "Installer les activités sélectionnées" -#: ../extensions/cpsection/updater/view.py:265 +#: ../extensions/cpsection/updater/view.py:276 #, python-format msgid "Download size: %s" msgstr "Taille du téléchargement : %s" -#: ../extensions/cpsection/updater/view.py:353 +#: ../extensions/cpsection/updater/view.py:364 #, python-format -msgid "From version %(current)d to %(new)s (Size: %(size)s)" -msgstr "De la version %(current)d à %(new)s (taille : %(size)s)" +msgid "From version %(current)s to %(new)s (Size: %(size)s)" +msgstr "De la version %(current)s à %(new)s (taille : %(size)s)" #. TRANS: download size is 0 -#: ../extensions/cpsection/updater/view.py:373 +#: ../extensions/cpsection/updater/view.py:382 msgid "None" msgstr "Zéro" #. TRANS: download size of very small updates -#: ../extensions/cpsection/updater/view.py:376 +#: ../extensions/cpsection/updater/view.py:385 msgid "1 KB" msgstr "1 Ko" #. TRANS: download size of small updates, e.g. '250 KB' -#: ../extensions/cpsection/updater/view.py:379 +#: ../extensions/cpsection/updater/view.py:388 #, python-format msgid "%.0f KB" msgstr "%.0f Ko" #. TRANS: download size of updates, e.g. '2.3 MB' -#: ../extensions/cpsection/updater/view.py:382 +#: ../extensions/cpsection/updater/view.py:391 #, python-format msgid "%.1f MB" msgstr "%.1f Mo" -#: ../extensions/deviceicon/battery.py:58 +#: ../extensions/deviceicon/battery.py:60 msgid "My Battery" msgstr "Ma batterie" -#: ../extensions/deviceicon/battery.py:137 +#: ../extensions/deviceicon/battery.py:141 msgid "Removed" msgstr "Retiré" -#: ../extensions/deviceicon/battery.py:140 +#: ../extensions/deviceicon/battery.py:144 msgid "Charging" msgstr "En charge" -#: ../extensions/deviceicon/battery.py:143 +#: ../extensions/deviceicon/battery.py:147 msgid "Very little power remaining" msgstr "La batterie est pratiquement déchargée" -#: ../extensions/deviceicon/battery.py:149 +#: ../extensions/deviceicon/battery.py:153 #, python-format msgid "%(hour)d:%(min).2d remaining" msgstr "%(hour)d:%(min).2d restantes" -#: ../extensions/deviceicon/battery.py:152 +#: ../extensions/deviceicon/battery.py:156 msgid "Charged" msgstr "Charge complète" -#: ../extensions/deviceicon/network.py:44 +#: ../extensions/deviceicon/network.py:49 #, python-format msgid "IP address: %s" msgstr "Adresse IP : %s" @@ -414,122 +463,332 @@ 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:110 +#: ../extensions/deviceicon/network.py:104 msgid "Disconnect..." msgstr "Déconnexion..." -#: ../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:264 +#: ../extensions/deviceicon/network.py:112 +#: ../extensions/deviceicon/network.py:290 +#: ../src/jarabe/desktop/networkviews.py:241 +#: ../src/jarabe/desktop/networkviews.py:546 +#: ../src/jarabe/desktop/networkviews.py:673 msgid "Connecting..." msgstr "Connexion..." # TODO: show the channel number -#: ../extensions/deviceicon/network.py:124 -#: ../extensions/deviceicon/network.py:186 -#: ../src/jarabe/desktop/meshbox.py:270 +#: ../extensions/deviceicon/network.py:116 +#: ../extensions/deviceicon/network.py:182 +#: ../src/jarabe/desktop/networkviews.py:251 +#: ../src/jarabe/desktop/networkviews.py:552 +#: ../src/jarabe/desktop/networkviews.py:679 msgid "Connected" msgstr "Connecté" -#: ../extensions/deviceicon/network.py:146 +#: ../extensions/deviceicon/network.py:142 msgid "Channel" msgstr "Canal" -#: ../extensions/deviceicon/network.py:161 +#: ../extensions/deviceicon/network.py:157 msgid "Wired Network" msgstr "Réseau filaire" -#: ../extensions/deviceicon/network.py:189 +#: ../extensions/deviceicon/network.py:185 msgid "Speed" msgstr "Vitesse" -#: ../extensions/deviceicon/network.py:415 +#: ../extensions/deviceicon/network.py:211 +msgid "Wireless modem" +msgstr "Modem sans fil" + +#: ../extensions/deviceicon/network.py:278 +msgid "Please wait..." +msgstr "Patienter..." + +#: ../extensions/deviceicon/network.py:282 +#: ../src/jarabe/desktop/networkviews.py:134 +#: ../src/jarabe/desktop/networkviews.py:500 +#: ../src/jarabe/desktop/networkviews.py:630 +msgid "Connect" +msgstr "Connecter" + +#: ../extensions/deviceicon/network.py:283 +msgid "Disconnected" +msgstr "Déconnecté" + +#: ../extensions/deviceicon/network.py:289 +#: ../src/jarabe/controlpanel/toolbar.py:119 +#: ../src/jarabe/desktop/homebox.py:70 +#: ../src/jarabe/frame/activitiestray.py:587 +#: ../src/jarabe/frame/activitiestray.py:687 +#: ../src/jarabe/frame/activitiestray.py:715 +msgid "Cancel" +msgstr "Annuler" + +#: ../extensions/deviceicon/network.py:297 +#: ../src/jarabe/desktop/networkviews.py:138 +#: ../src/jarabe/desktop/networkviews.py:504 +msgid "Disconnect" +msgstr "Déconnecter" + +#: ../extensions/deviceicon/network.py:327 +msgid "Try connection again" +msgstr "Essayez de vous reconnecter" + +#: ../extensions/deviceicon/network.py:330 +#, python-format +msgid "Error: %s" +msgstr "Erreur : %s" + +#: ../extensions/deviceicon/network.py:334 +#, python-format +msgid "Suggestion: %s" +msgstr "Suggestion : %s" + +#: ../extensions/deviceicon/network.py:340 +#: ../extensions/deviceicon/network.py:343 +#, python-format +msgid "Connected for %s" +msgstr "Connecté depuis %s" + +#: ../extensions/deviceicon/network.py:348 +#: ../extensions/deviceicon/network.py:349 +#, python-format +msgid "%d KB" +msgstr "%d Ko" + +#: ../extensions/deviceicon/network.py:354 +msgid "Check your Pin/Puk configuration." +msgstr "Vérifiez votre configuration Pin/Puk." + +#: ../extensions/deviceicon/network.py:357 +msgid "Check your Access Point Name (APN) configuration" +msgstr "Vérifiez la configuration de votre nom de point d'accès (APN)." + +#: ../extensions/deviceicon/network.py:361 +msgid "Check the Number configuration." +msgstr "Vérifiez la configuration du numéro." + +#: ../extensions/deviceicon/network.py:363 +msgid "Check your configuration." +msgstr "Vérifiez votre configuration." + +#: ../extensions/deviceicon/network.py:605 +msgid "Mesh Network" +msgstr "Réseau maillé" + +#: ../extensions/deviceicon/network.py:648 #, python-format -msgid "%s's network %s" -msgstr "Réseau %s %s" +msgid "Mesh Network %s" +msgstr "Réseau maillé %s" + +#: ../extensions/deviceicon/network.py:771 +msgid "No GSM connection available." +msgstr "Aucune connexion GSM disponible." + +#: ../extensions/deviceicon/network.py:772 +msgid "Create a connection in the control panel." +msgstr "Créez une connexion dans le panneau de contrôle." -#: ../extensions/deviceicon/speaker.py:59 +#: ../extensions/deviceicon/resources.py:48 +msgid "System resources" +msgstr "Ressources système" + +#: ../extensions/deviceicon/resources.py:170 +#, python-format +msgid "CPU in use: %d%%" +msgstr "Processeur utilisé : %d%%" + +#: ../extensions/deviceicon/resources.py:172 +#, python-format +msgid "Memory in use: %d%%" +msgstr "Mémoire utilisée : %d%%" + +#: ../extensions/deviceicon/resources.py:202 +msgid "Cannot compute CPU and memory usage statistics!" +msgstr "" +"Calcul des statistiques d'utilisation du processeur et de la mémoire " +"impossible !" + +#: ../extensions/deviceicon/speaker.py:60 msgid "My Speakers" msgstr "Haut-parleurs" -#: ../extensions/deviceicon/speaker.py:133 +#: ../extensions/deviceicon/speaker.py:136 msgid "Unmute" msgstr "Activer le son" -#: ../extensions/deviceicon/speaker.py:136 +#: ../extensions/deviceicon/speaker.py:139 msgid "Mute" msgstr "Mettre en sourdine" -#: ../extensions/globalkey/screenshot.py:56 +#: ../extensions/deviceicon/touchpad.py:37 +msgid "finger" +msgstr "doigt" + +#: ../extensions/deviceicon/touchpad.py:38 +msgid "stylus" +msgstr "stylet" + +#: ../extensions/deviceicon/touchpad.py:67 +msgid "My touchpad" +msgstr "Mon pavé tactile" + +#: ../extensions/globalkey/screenshot.py:59 msgid "Mesh" msgstr "Réseau maillé" -#: ../extensions/globalkey/screenshot.py:58 -#: ../src/jarabe/frame/zoomtoolbar.py:39 +#: ../extensions/globalkey/screenshot.py:61 +#: ../src/jarabe/frame/zoomtoolbar.py:40 msgid "Group" msgstr "Groupe" -#: ../extensions/globalkey/screenshot.py:60 -#: ../src/jarabe/frame/zoomtoolbar.py:41 +#: ../extensions/globalkey/screenshot.py:63 +#: ../src/jarabe/frame/zoomtoolbar.py:42 msgid "Home" msgstr "Accueil" -#: ../extensions/globalkey/screenshot.py:66 -#: ../src/jarabe/frame/zoomtoolbar.py:43 +#: ../extensions/globalkey/screenshot.py:69 +#: ../src/jarabe/frame/zoomtoolbar.py:44 msgid "Activity" msgstr "Activité" -#: ../extensions/globalkey/screenshot.py:69 +#: ../extensions/globalkey/screenshot.py:72 msgid "Screenshot" msgstr "Capture d'écran" -#: ../extensions/globalkey/screenshot.py:71 +#: ../extensions/globalkey/screenshot.py:74 #, python-format msgid "Screenshot of \"%s\"" msgstr "Capture d'écran de \"%s\"" #: ../data/sugar.schemas.in.h:1 +msgid "" +"\"disabled\" to ask nick on initialization; \"system\" to reuse UNIX account " +"long name." +msgstr "" +"\"désactivé\" pour demander un pseudo lors de l'initialisation ; \"système\" " +"pour réutiliser l'identifiant long du compte UNIX." + +#: ../data/sugar.schemas.in.h:2 +msgid "Additional directories which can contain updated translations." +msgstr "" +"Les dossiers supplémentaires peuvent comporter des traductions mises à jour." + +#: ../data/sugar.schemas.in.h:3 msgid "Backup URL" msgstr "Sauvegarde de l'URL" -#: ../data/sugar.schemas.in.h:2 +#: ../data/sugar.schemas.in.h:4 +msgid "Bundle IDs of protected activities" +msgstr "Identifiant des bundles des activités protégées" + +#: ../data/sugar.schemas.in.h:5 msgid "" "Color for the XO icon that is used throughout the desktop. The string is " -"composed of the stroke color and fill color, format is that of rbg colors. " +"composed of the stroke color and fill color, format is that of rgb colors. " "Example: #AC32FF,#9A5200" msgstr "" -"Couleur du XO utilisée sur le Bureau. La chaîne indique la couleur du trait " -"et du remplissage. Le format correspond aux couleurs RVB. Exemple : " -"#AC32FF,#9A5200" +"Couleur de l'icône du XO utilisée sur le Bureau. La chaîne indique la " +"couleur du trait et du remplissage. Le format correspond aux couleurs RVB. " +"Exemple : #AC32FF,#9A5200" -#: ../data/sugar.schemas.in.h:3 +#: ../data/sugar.schemas.in.h:6 msgid "Corner Delay" msgstr "Délai des coins" -#: ../data/sugar.schemas.in.h:4 +#: ../data/sugar.schemas.in.h:7 +msgid "Default font face" +msgstr "Police par défaut" + +#: ../data/sugar.schemas.in.h:8 +msgid "Default font size" +msgstr "Corps de la police par défaut" + +#: ../data/sugar.schemas.in.h:9 +msgid "Default nick" +msgstr "Pseudo par défaut" + +#: ../data/sugar.schemas.in.h:10 msgid "Delay for the activation of the frame using the corners." msgstr "Délai d'activation du cadre à l'aide des coins." -#: ../data/sugar.schemas.in.h:5 +#: ../data/sugar.schemas.in.h:11 msgid "Delay for the activation of the frame using the edges." msgstr "Délai d'activation du cadre à l'aide des bords." -#: ../data/sugar.schemas.in.h:6 +#: ../data/sugar.schemas.in.h:12 +msgid "Directory to search for translations" +msgstr "Répertoire de recherche des traductions" + +#: ../data/sugar.schemas.in.h:13 msgid "Edge Delay" msgstr "Délai des bords" -#: ../data/sugar.schemas.in.h:7 +#: ../data/sugar.schemas.in.h:14 msgid "Favorites Layout" msgstr "Disposition favorite" -#: ../data/sugar.schemas.in.h:8 +#: ../data/sugar.schemas.in.h:15 msgid "Favorites resume mode" -msgstr "Mode de reprise favori " +msgstr "Mode de reprise favori" -#: ../data/sugar.schemas.in.h:9 +#: ../data/sugar.schemas.in.h:16 +msgid "Font face that is used throughout the desktop." +msgstr "Police utilisée sur le bureau." + +#: ../data/sugar.schemas.in.h:17 +msgid "Font size that is used throughout the desktop." +msgstr "Corps de la police utilisée sur le bureau." + +#: ../data/sugar.schemas.in.h:18 +msgid "GSM network APN" +msgstr "Code APN du réseau GSM" + +#: ../data/sugar.schemas.in.h:19 +msgid "GSM network PIN" +msgstr "Code PIN du réseau GSM" + +#: ../data/sugar.schemas.in.h:20 +msgid "GSM network PUK" +msgstr "Code PUK du réseau GSM" + +#: ../data/sugar.schemas.in.h:21 +msgid "GSM network access point name configuration" +msgstr "Configuration du nom du point d'accès au réseau GSM" + +#: ../data/sugar.schemas.in.h:22 +msgid "GSM network number" +msgstr "Numéro du réseau GSM" + +#: ../data/sugar.schemas.in.h:23 +msgid "GSM network password" +msgstr "Mot de passe du réseau GSM" + +#: ../data/sugar.schemas.in.h:24 +msgid "GSM network password configuration" +msgstr "Configuration du mot de passe du réseau GSM" + +#: ../data/sugar.schemas.in.h:25 +msgid "GSM network personal identification number configuration" +msgstr "Configuration du numéro d'identification personnel du réseau GSM" + +#: ../data/sugar.schemas.in.h:26 +msgid "GSM network personal unlock key configuration" +msgstr "Configuration de la clé de déblocage personnelle du réseau GSM" + +#: ../data/sugar.schemas.in.h:27 +msgid "GSM network telephone number configuration" +msgstr "Configuration du numéro de téléphone du réseau GSM" + +#: ../data/sugar.schemas.in.h:28 +msgid "GSM network username" +msgstr "Nom d'utilisateur du réseau GSM" + +#: ../data/sugar.schemas.in.h:29 +msgid "GSM network username configuration" +msgstr "Configuration du nom d'utilisateur du réseau GSM" + +#: ../data/sugar.schemas.in.h:30 msgid "" "If TRUE, Sugar will make us searchable for the other users of the Jabber " "server." @@ -537,110 +796,139 @@ msgstr "" "Si VRAI, Sugar permettra aux autres utilisateurs du serveur Jabber de nous " "retrouver." -#: ../data/sugar.schemas.in.h:10 +#: ../data/sugar.schemas.in.h:31 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 +#: ../data/sugar.schemas.in.h:32 +msgid "If TRUE, Sugar will show a \"Restart\" option." +msgstr "Si VRAI, Sugar affichera une option \"Redémarrer\"." + +#: ../data/sugar.schemas.in.h:33 +msgid "" +"If TRUE, Sugar will show default Ad-hoc networks for channel 1,6 and 11. If " +"Sugar sees no \"known\" network when it starts, it does autoconnect to an Ad-" +"hoc network." +msgstr "" +"Si VRAI, Sugar affichera les réseaux ad-hoc des canaux 1,6 et 11. si Sugar " +"ne voit aucun réseau \"connu\" au démarrage, il se connecte automatiquement à " +"un réseau ad-hoc." + +#: ../data/sugar.schemas.in.h:34 msgid "Jabber Server" msgstr "Serveur Jabber" -#: ../data/sugar.schemas.in.h:12 +#: ../data/sugar.schemas.in.h:35 msgid "Keyboard layouts" msgstr "Dispositions du clavier" -#: ../data/sugar.schemas.in.h:13 +#: ../data/sugar.schemas.in.h:36 msgid "Keyboard model" msgstr "Modèle de clavier" -#: ../data/sugar.schemas.in.h:14 +#: ../data/sugar.schemas.in.h:37 msgid "Keyboard options" msgstr "Options du clavier" -#: ../data/sugar.schemas.in.h:15 +#: ../data/sugar.schemas.in.h:38 msgid "Layout of the favorites view." msgstr "Disposition de la vue favorite." -#: ../data/sugar.schemas.in.h:16 +#: ../data/sugar.schemas.in.h:39 msgid "" "List of keyboard layouts. Each entry should be in the form layout(variant)" msgstr "" "Liste des dispositions de claviers. Chaque ligne doit avoir la forme " "disposition(variante)" -#: ../data/sugar.schemas.in.h:17 +#: ../data/sugar.schemas.in.h:40 msgid "List of keyboard options." msgstr "Liste des options de clavier." -#: ../data/sugar.schemas.in.h:18 +#: ../data/sugar.schemas.in.h:41 msgid "Power Automatic" msgstr "Alimentation automatique" -#: ../data/sugar.schemas.in.h:19 +#: ../data/sugar.schemas.in.h:42 msgid "Power Automatic." msgstr "Alimentation automatique." -#: ../data/sugar.schemas.in.h:20 +#: ../data/sugar.schemas.in.h:43 msgid "Power Extreme" msgstr "Alimentation extrême" -#: ../data/sugar.schemas.in.h:21 +#: ../data/sugar.schemas.in.h:44 msgid "Power Extreme." msgstr "Alimentation extrême." -#: ../data/sugar.schemas.in.h:22 +#: ../data/sugar.schemas.in.h:45 msgid "Publish to Gadget" msgstr "Publication vers Gadget" -#: ../data/sugar.schemas.in.h:23 +#: ../data/sugar.schemas.in.h:46 msgid "Setting for muting the sound device." msgstr "Configuration de la mise en sourdine du périphérique audio." -#: ../data/sugar.schemas.in.h:24 +#: ../data/sugar.schemas.in.h:47 msgid "Show Log out" msgstr "Afficher Déconnexion" -#: ../data/sugar.schemas.in.h:25 +#: ../data/sugar.schemas.in.h:48 +msgid "Show Restart" +msgstr "Afficher Redémarrer" + +#: ../data/sugar.schemas.in.h:49 +msgid "Show Sugar Ad-hoc networks" +msgstr "Afficher les réseaux ad-hoc Sugar" + +#: ../data/sugar.schemas.in.h:50 msgid "Sound Muted" msgstr "Audio désactivé" -#: ../data/sugar.schemas.in.h:26 +#: ../data/sugar.schemas.in.h:51 msgid "The keyboard model to be used" msgstr "Modèle de clavier à utiliser" -#: ../data/sugar.schemas.in.h:28 +#: ../data/sugar.schemas.in.h:53 msgid "Timezone setting for the system." msgstr "Configuration du fuseau horaire du système." -#: ../data/sugar.schemas.in.h:29 +#: ../data/sugar.schemas.in.h:54 msgid "Url of the jabber server to use." msgstr "URL du serveur Jabber à utiliser." -#: ../data/sugar.schemas.in.h:30 +#: ../data/sugar.schemas.in.h:55 msgid "Url where the backup is saved to." msgstr "URL d'enregistrement de la sauvegarde." -#: ../data/sugar.schemas.in.h:31 +#: ../data/sugar.schemas.in.h:56 msgid "User Color" msgstr "Couleurs de l'utilisateur" -#: ../data/sugar.schemas.in.h:32 +#: ../data/sugar.schemas.in.h:57 msgid "User Name" msgstr "Nom de l'utilisateur" -#: ../data/sugar.schemas.in.h:33 +#: ../data/sugar.schemas.in.h:58 msgid "User name that is used throughout the desktop." msgstr "Nom identifiant l'utilisateur sur le bureau." -#: ../data/sugar.schemas.in.h:34 +#: ../data/sugar.schemas.in.h:59 +msgid "" +"Users will not be allowed to erase these activities through the list view." +msgstr "" +"Les utilisateurs ne sont pas autorisés à supprimer ces activités depuis la " +"vue liste." + +#: ../data/sugar.schemas.in.h:60 msgid "Volume Level" msgstr "Niveau de volume" -#: ../data/sugar.schemas.in.h:35 +#: ../data/sugar.schemas.in.h:61 msgid "Volume level for the sound device." msgstr "Niveau de volume du périphérique audio." -#: ../data/sugar.schemas.in.h:36 +#: ../data/sugar.schemas.in.h:62 msgid "" "When in resume mode, clicking on a favorite icon will cause the last entry " "for that activity to be resumed." @@ -671,7 +959,7 @@ msgstr "sugar-control-panel: %s" # which must appear in the translated string (msgstr) as well. #. TRANS: Translators, there's a empty line at the end of this string, #. which must appear in the translated string (msgstr) as well. -#: ../src/jarabe/controlpanel/cmd.py:37 +#: ../src/jarabe/controlpanel/cmd.py:38 msgid "" "Usage: sugar-control-panel [ option ] key [ args ... ] \n" " Control for the sugar environment. \n" @@ -694,73 +982,53 @@ msgstr "" " -s clé définir la valeur actuelle de cette clé \n" " " -#: ../src/jarabe/controlpanel/cmd.py:50 +#: ../src/jarabe/controlpanel/cmd.py:52 msgid "To apply your changes you have to restart sugar.\n" msgstr "Redémarrer sugar pour que les changements prennent effet.\n" -#: ../src/jarabe/controlpanel/gui.py:280 +#: ../src/jarabe/controlpanel/gui.py:297 +#: ../src/jarabe/journal/journaltoolbox.py:437 +#: ../src/jarabe/journal/volumestoolbar.py:158 msgid "Warning" msgstr "Attention" -#: ../src/jarabe/controlpanel/gui.py:281 -#: ../src/jarabe/controlpanel/sectionview.py:42 +#: ../src/jarabe/controlpanel/gui.py:298 +#: ../src/jarabe/controlpanel/sectionview.py:41 msgid "Changes require restart" msgstr "Relancer pour valider" -#: ../src/jarabe/controlpanel/gui.py:284 +#: ../src/jarabe/controlpanel/gui.py:301 msgid "Cancel changes" msgstr "Abandonner" -#: ../src/jarabe/controlpanel/gui.py:289 ../src/jarabe/desktop/homebox.py:70 +#: ../src/jarabe/controlpanel/gui.py:306 ../src/jarabe/desktop/homebox.py:72 msgid "Later" msgstr "Plus tard" -#: ../src/jarabe/controlpanel/gui.py:293 +#: ../src/jarabe/controlpanel/gui.py:310 msgid "Restart now" msgstr "Maintenant" -#: ../src/jarabe/controlpanel/toolbar.py:61 ../src/jarabe/intro/window.py:188 +#: ../src/jarabe/controlpanel/toolbar.py:63 ../src/jarabe/intro/window.py:212 msgid "Done" msgstr "Accepter" -#: ../src/jarabe/controlpanel/toolbar.py:115 -#: ../src/jarabe/desktop/homebox.py:68 -#: ../src/jarabe/frame/activitiestray.py:726 -#: ../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:332 +#: ../src/jarabe/controlpanel/toolbar.py:125 +#: ../src/jarabe/desktop/favoritesview.py:336 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 +#: ../src/jarabe/desktop/activitieslist.py:230 #, python-format msgid "Version %s" msgstr "Version %s" -#: ../src/jarabe/desktop/activitieslist.py:355 +#: ../src/jarabe/desktop/activitieslist.py:354 msgid "Confirm erase" msgstr "Confirmer la suppression" # Conformer la suppression : faut-il supprimer %s définitivement ? -#: ../src/jarabe/desktop/activitieslist.py:357 +#: ../src/jarabe/desktop/activitieslist.py:356 #, python-format msgid "Confirm erase: Do you want to permanently erase %s?" msgstr "Confirmer la suppression : faut-il supprimer %s définitivement ?" @@ -769,388 +1037,446 @@ msgstr "Confirmer la suppression : faut-il supprimer %s définitivement ?" # 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 +#: ../src/jarabe/desktop/activitieslist.py:360 +#: ../src/jarabe/frame/clipboardmenu.py:64 +#: ../src/jarabe/view/viewsource.py:221 msgid "Keep" msgstr "Conserver" -#: ../src/jarabe/desktop/activitieslist.py:364 -#: ../src/jarabe/desktop/activitieslist.py:407 -#: ../src/jarabe/journal/journaltoolbox.py:360 +#: ../src/jarabe/desktop/activitieslist.py:363 +#: ../src/jarabe/desktop/activitieslist.py:417 +#: ../src/jarabe/journal/journaltoolbox.py:391 #: ../src/jarabe/journal/palettes.py:112 msgid "Erase" msgstr "Supprimer" -#: ../src/jarabe/desktop/activitieslist.py:428 +#: ../src/jarabe/desktop/activitieslist.py:432 msgid "Remove favorite" msgstr "Retirer le favori" -#: ../src/jarabe/desktop/activitieslist.py:432 +#: ../src/jarabe/desktop/activitieslist.py:436 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 +#: ../src/jarabe/desktop/favoriteslayout.py:127 msgid "Freeform" msgstr "Libre" # 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:198 +#: ../src/jarabe/desktop/favoriteslayout.py:215 msgid "Ring" msgstr "Concentrique" # 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:337 +#: ../src/jarabe/desktop/favoriteslayout.py:402 msgid "Spiral" msgstr "Spirale" # 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:404 +#: ../src/jarabe/desktop/favoriteslayout.py:472 msgid "Box" msgstr "Boîte" # 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:445 +#: ../src/jarabe/desktop/favoriteslayout.py:515 msgid "Triangle" msgstr "Triangle" -#: ../src/jarabe/desktop/favoritesview.py:323 +#: ../src/jarabe/desktop/favoritesview.py:327 msgid "Registration Failed" msgstr "Echec de l'enregistrement" -#: ../src/jarabe/desktop/favoritesview.py:324 +#: ../src/jarabe/desktop/favoritesview.py:328 #, python-format msgid "%s" msgstr "%s" -#: ../src/jarabe/desktop/favoritesview.py:326 +#: ../src/jarabe/desktop/favoritesview.py:330 msgid "Registration Successful" msgstr "Enregistrement réussi" -#: ../src/jarabe/desktop/favoritesview.py:327 +#: ../src/jarabe/desktop/favoritesview.py:331 msgid "You are now registered with your school server." -msgstr "Vous êtes maintenant enregistré sur le serveur de l'école" +msgstr "Vous êtes maintenant enregistré sur le serveur de l'école." -#: ../src/jarabe/desktop/favoritesview.py:671 +#: ../src/jarabe/desktop/favoritesview.py:627 msgid "Register" msgstr "S'enregistrer" -#: ../src/jarabe/desktop/homebox.py:63 +#: ../src/jarabe/desktop/favoritesview.py:629 +#: ../src/jarabe/desktop/favoritesview.py:646 +msgid "Register again" +msgstr "S'enregistrer à nouveau" + +#: ../src/jarabe/desktop/homebox.py:65 msgid "Software Update" msgstr "Mise à jour logicielle" -#: ../src/jarabe/desktop/homebox.py:64 +#: ../src/jarabe/desktop/homebox.py:66 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:73 +#: ../src/jarabe/desktop/homebox.py:75 msgid "Check now" msgstr "Vérifier maintenant" -#: ../src/jarabe/desktop/homebox.py:192 +#: ../src/jarabe/desktop/homebox.py:193 msgid "List view" msgstr "Écran liste" -#: ../src/jarabe/desktop/homebox.py:193 +#: ../src/jarabe/desktop/homebox.py:194 msgid "<Ctrl>2" msgstr "<Ctrl>2" -#: ../src/jarabe/desktop/homebox.py:255 +#: ../src/jarabe/desktop/homebox.py:257 msgid "Favorites view" msgstr "Écran favoris" -#: ../src/jarabe/desktop/homebox.py:256 +#: ../src/jarabe/desktop/homebox.py:258 msgid "<Ctrl>1" msgstr "<Ctrl>1" -#: ../src/jarabe/desktop/keydialog.py:131 +#: ../src/jarabe/desktop/keydialog.py:143 msgid "Key Type:" msgstr "Type de clé :" -#: ../src/jarabe/desktop/keydialog.py:151 +#: ../src/jarabe/desktop/keydialog.py:163 msgid "Authentication Type:" msgstr "Type d'authentification :" -#: ../src/jarabe/desktop/keydialog.py:215 +#: ../src/jarabe/desktop/keydialog.py:229 msgid "WPA & WPA2 Personal" msgstr "WPA & WPA2 Personal" -#: ../src/jarabe/desktop/keydialog.py:224 +#: ../src/jarabe/desktop/keydialog.py:238 msgid "Wireless Security:" msgstr "Sécurité sans fil :" -#: ../src/jarabe/desktop/meshbox.py:136 -msgid "Connect" -msgstr "Connecter" - -#: ../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:466 -#: ../src/jarabe/frame/activitiestray.py:761 -#: ../src/jarabe/journal/journaltoolbox.py:428 -#: ../src/jarabe/journal/palettes.py:72 ../src/jarabe/view/palettes.py:64 +#: ../src/jarabe/desktop/meshbox.py:109 +#: ../src/jarabe/frame/activitiestray.py:622 +#: ../src/jarabe/journal/journaltoolbox.py:485 +#: ../src/jarabe/journal/palettes.py:66 ../src/jarabe/view/palettes.py:78 msgid "Resume" msgstr "Reprendre" -#: ../src/jarabe/desktop/meshbox.py:471 -#: ../src/jarabe/frame/activitiestray.py:235 +#: ../src/jarabe/desktop/meshbox.py:114 +#: ../src/jarabe/frame/activitiestray.py:173 msgid "Join" msgstr "Rejoindre" -#: ../src/jarabe/desktop/schoolserver.py:103 +#: ../src/jarabe/desktop/networkviews.py:497 +#, python-format +msgid "Ad-hoc Network %d" +msgstr "Réseau ad-hoc %d" + +#: ../src/jarabe/desktop/networkviews.py:628 +#, python-format +msgid "Mesh Network %d" +msgstr "Réseau maillé %d" + +#: ../src/jarabe/desktop/schoolserver.py:131 msgid "Cannot connect to the server." msgstr "Impossible de se connecter au serveur." -#: ../src/jarabe/desktop/schoolserver.py:108 +#: ../src/jarabe/desktop/schoolserver.py:136 msgid "The server could not complete the request." msgstr "Le serveur n'a pas pu achever la requête." -#: ../src/jarabe/frame/activitiestray.py:240 -#: ../src/jarabe/frame/activitiestray.py:698 +#: ../src/jarabe/frame/activitiestray.py:178 +#: ../src/jarabe/frame/activitiestray.py:559 msgid "Decline" msgstr "Refuser" -#: ../src/jarabe/frame/activitiestray.py:650 +#: ../src/jarabe/frame/activitiestray.py:509 #, python-format msgid "%dB" msgstr "%do" -#: ../src/jarabe/frame/activitiestray.py:652 +#: ../src/jarabe/frame/activitiestray.py:511 #, python-format msgid "%dKB" msgstr "%dKo" -#: ../src/jarabe/frame/activitiestray.py:654 +#: ../src/jarabe/frame/activitiestray.py:513 #, python-format msgid "%dMB" msgstr "%dMo" -#: ../src/jarabe/frame/activitiestray.py:671 +#: ../src/jarabe/frame/activitiestray.py:530 #, python-format msgid "%s of %s" msgstr "%s sur %s" -#: ../src/jarabe/frame/activitiestray.py:683 +#: ../src/jarabe/frame/activitiestray.py:544 #, python-format msgid "Transfer from %r" msgstr "Transfert depuis %r" -#: ../src/jarabe/frame/activitiestray.py:693 +#: ../src/jarabe/frame/activitiestray.py:554 msgid "Accept" msgstr "Accepter" -#: ../src/jarabe/frame/activitiestray.py:716 -#: ../src/jarabe/frame/activitiestray.py:840 +#: ../src/jarabe/frame/activitiestray.py:577 +#: ../src/jarabe/frame/activitiestray.py:705 #, python-format msgid "%s (%s)" msgstr "%s (%s)" -#: ../src/jarabe/frame/activitiestray.py:750 -#: ../src/jarabe/frame/activitiestray.py:875 +#: ../src/jarabe/frame/activitiestray.py:611 +#: ../src/jarabe/frame/activitiestray.py:740 msgid "Dismiss" msgstr "Refuser" -#: ../src/jarabe/frame/activitiestray.py:810 +#: ../src/jarabe/frame/activitiestray.py:675 #, python-format msgid "Transfer to %r" msgstr "Transfert vers %r" -#: ../src/jarabe/frame/clipboardmenu.py:52 ../src/jarabe/view/palettes.py:218 +#: ../src/jarabe/frame/clipboardmenu.py:54 ../src/jarabe/view/palettes.py:220 msgid "Remove" msgstr "Retirer" -#: ../src/jarabe/frame/clipboardmenu.py:57 -#: ../src/jarabe/frame/clipboardmenu.py:80 +#: ../src/jarabe/frame/clipboardmenu.py:59 +#: ../src/jarabe/frame/clipboardmenu.py:82 msgid "Open" msgstr "Ouvrir" -#: ../src/jarabe/frame/clipboardmenu.py:85 +#: ../src/jarabe/frame/clipboardmenu.py:87 msgid "Open with" msgstr "Ouvrir avec" -#: ../src/jarabe/frame/clipboardobject.py:49 +#: ../src/jarabe/frame/clipboardobject.py:50 #, python-format msgid "%s clipping" msgstr "%s coupure" -#: ../src/jarabe/frame/zoomtoolbar.py:37 +#: ../src/jarabe/frame/zoomtoolbar.py:38 msgid "Neighborhood" msgstr "Voisinage" -#: ../src/jarabe/frame/zoomtoolbar.py:37 +#: ../src/jarabe/frame/zoomtoolbar.py:38 msgid "F1" msgstr "F1" -#: ../src/jarabe/frame/zoomtoolbar.py:39 +#: ../src/jarabe/frame/zoomtoolbar.py:40 msgid "F2" msgstr "F2" -#: ../src/jarabe/frame/zoomtoolbar.py:41 +#: ../src/jarabe/frame/zoomtoolbar.py:42 msgid "F3" msgstr "F3" -#: ../src/jarabe/frame/zoomtoolbar.py:43 +#: ../src/jarabe/frame/zoomtoolbar.py:44 msgid "F4" msgstr "F4" -#: ../src/jarabe/intro/window.py:124 +#: ../src/jarabe/intro/window.py:97 +msgid "Name:" +msgstr "Nom :" + +#: ../src/jarabe/intro/window.py:133 msgid "Click to change color:" msgstr "Cliquer pour changer de couleur :" -#: ../src/jarabe/intro/window.py:174 ../src/jarabe/journal/detailview.py:103 +#: ../src/jarabe/intro/window.py:198 ../src/jarabe/journal/detailview.py:105 msgid "Back" msgstr "Précédent" -#: ../src/jarabe/intro/window.py:191 +#: ../src/jarabe/intro/window.py:215 msgid "Next" msgstr "Suivant" -#: ../src/jarabe/journal/expandedentry.py:164 -#: ../src/jarabe/journal/palettes.py:66 +#: ../src/jarabe/journal/expandedentry.py:154 +#: ../src/jarabe/journal/listmodel.py:144 ../src/jarabe/journal/palettes.py:59 msgid "Untitled" msgstr "Sans titre" -#: ../src/jarabe/journal/expandedentry.py:210 +#: ../src/jarabe/journal/expandedentry.py:243 msgid "No preview" msgstr "Pas de prévisualisation" -#: ../src/jarabe/journal/expandedentry.py:229 +#: ../src/jarabe/journal/expandedentry.py:262 #, python-format msgid "Kind: %s" msgstr "Variante : %s" -#: ../src/jarabe/journal/expandedentry.py:229 +#: ../src/jarabe/journal/expandedentry.py:262 +#: ../src/jarabe/journal/listmodel.py:150 +#: ../src/jarabe/journal/listmodel.py:158 +#: ../src/jarabe/journal/listmodel.py:166 msgid "Unknown" msgstr "Inconnu" -#: ../src/jarabe/journal/expandedentry.py:230 +#: ../src/jarabe/journal/expandedentry.py:263 #, python-format msgid "Date: %s" msgstr "Date : %s" -#: ../src/jarabe/journal/expandedentry.py:231 +#: ../src/jarabe/journal/expandedentry.py:264 #, python-format msgid "Size: %s" msgstr "Taille : %s" -#: ../src/jarabe/journal/expandedentry.py:253 ../src/jarabe/journal/misc.py:92 +#: ../src/jarabe/journal/expandedentry.py:292 +#: ../src/jarabe/journal/misc.py:108 msgid "No date" msgstr "Sans date" -#: ../src/jarabe/journal/expandedentry.py:260 +#: ../src/jarabe/journal/expandedentry.py:299 msgid "Participants:" msgstr "Participants :" -#: ../src/jarabe/journal/expandedentry.py:283 +#: ../src/jarabe/journal/expandedentry.py:321 msgid "Description:" msgstr "Description :" -#: ../src/jarabe/journal/expandedentry.py:309 +#: ../src/jarabe/journal/expandedentry.py:346 msgid "Tags:" msgstr "Étiquettes :" -#: ../src/jarabe/journal/journalactivity.py:108 -#: ../src/jarabe/journal/volumestoolbar.py:47 +#: ../src/jarabe/journal/journalactivity.py:115 +#: ../src/jarabe/journal/journaltoolbox.py:456 +#: ../src/jarabe/journal/volumestoolbar.py:50 msgid "Journal" msgstr "Journal" -#: ../src/jarabe/journal/journaltoolbox.py:67 +#: ../src/jarabe/journal/journaltoolbox.py:69 msgid "Search" msgstr "Rechercher" -#: ../src/jarabe/journal/journaltoolbox.py:126 +#: ../src/jarabe/journal/journaltoolbox.py:136 msgid "Anytime" msgstr "N'importe quand" -#: ../src/jarabe/journal/journaltoolbox.py:128 +#: ../src/jarabe/journal/journaltoolbox.py:138 msgid "Today" msgstr "Aujourd'hui" -#: ../src/jarabe/journal/journaltoolbox.py:130 +#: ../src/jarabe/journal/journaltoolbox.py:140 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:132 +#: ../src/jarabe/journal/journaltoolbox.py:142 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:134 +#: ../src/jarabe/journal/journaltoolbox.py:144 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:136 +#: ../src/jarabe/journal/journaltoolbox.py:146 msgid "Past year" msgstr "Depuis une année" -#: ../src/jarabe/journal/journaltoolbox.py:143 +#: ../src/jarabe/journal/journaltoolbox.py:153 msgid "Anyone" msgstr "Tout le monde" -#: ../src/jarabe/journal/journaltoolbox.py:145 +#: ../src/jarabe/journal/journaltoolbox.py:155 msgid "My friends" msgstr "Mes amis" -#: ../src/jarabe/journal/journaltoolbox.py:146 +#: ../src/jarabe/journal/journaltoolbox.py:156 msgid "My class" msgstr "Ma classe" # TRANS: Item in a combo box that filters by entry type. -#: ../src/jarabe/journal/journaltoolbox.py:274 +#: ../src/jarabe/journal/journaltoolbox.py:298 msgid "Anything" msgstr "Tout" # TODO: Add "Start with" menu item -#: ../src/jarabe/journal/journaltoolbox.py:350 +#: ../src/jarabe/journal/journaltoolbox.py:381 #: ../src/jarabe/journal/palettes.py:90 msgid "Copy" msgstr "Copier" +#: ../src/jarabe/journal/journaltoolbox.py:436 +#: ../src/jarabe/journal/volumestoolbar.py:157 +msgid "Entries without a file cannot be copied." +msgstr "Impossible de copier les entrées sans fichier." + +#: ../src/jarabe/journal/journaltoolbox.py:445 +#: ../src/jarabe/journal/volumestoolbar.py:166 +#, python-format +msgid "Error while copying the entry. %s" +msgstr "Erreur lors de la copie de l'entrée. %s" + +#: ../src/jarabe/journal/journaltoolbox.py:446 +#: ../src/jarabe/journal/volumestoolbar.py:167 +msgid "Error" +msgstr "Erreur" + # TRANS: Action label for starting an entry. #. TRANS: Action label for starting an entry. -#: ../src/jarabe/journal/journaltoolbox.py:431 -#: ../src/jarabe/journal/palettes.py:75 +#: ../src/jarabe/journal/journaltoolbox.py:488 +#: ../src/jarabe/journal/palettes.py:69 msgid "Start" msgstr "Lancer" -#: ../src/jarabe/journal/listview.py:361 +#: ../src/jarabe/journal/journaltoolbox.py:516 +msgid "Sort by date modified" +msgstr "Trier sur la date de modification" + +#: ../src/jarabe/journal/journaltoolbox.py:517 +msgid "Sort by date created" +msgstr "Trier sur la date de création" + +#: ../src/jarabe/journal/journaltoolbox.py:518 +msgid "Sort by size" +msgstr "Trier sur la taille" + +#: ../src/jarabe/journal/journaltoolbox.py:527 +msgid "Sort view" +msgstr "Trier l'affichage" + +#: ../src/jarabe/journal/listview.py:380 msgid "Your Journal is empty" msgstr "Le journal est vide" -#: ../src/jarabe/journal/listview.py:363 +#: ../src/jarabe/journal/listview.py:382 msgid "No matching entries" msgstr "Aucune entrée correspondante" -#: ../src/jarabe/journal/listview.py:374 +#: ../src/jarabe/journal/listview.py:393 msgid "Clear search" msgstr "Effacer la recherche" -#: ../src/jarabe/journal/modalalert.py:63 +#: ../src/jarabe/journal/misc.py:273 +#, python-format +msgid "Older Version Of %s Activity" +msgstr "Ancienne version de l'activité %s" + +#: ../src/jarabe/journal/misc.py:274 +#, python-format +msgid "Do you want to downgrade to version %s" +msgstr "Voulez-vous revenir à la version %s" + +#: ../src/jarabe/journal/modalalert.py:64 msgid "Your Journal is full" msgstr "Votre journal est plein" -#: ../src/jarabe/journal/modalalert.py:67 +#: ../src/jarabe/journal/modalalert.py:68 msgid "Please delete some old Journal entries to make space for new ones." msgstr "" "Effacer des entrées anciennes du Journal pour libérer de la place pour les " "nouvelles entrées." -#: ../src/jarabe/journal/modalalert.py:79 +#: ../src/jarabe/journal/modalalert.py:80 msgid "Show Journal" msgstr "Montre le Journal" @@ -1159,18 +1485,22 @@ msgid "Choose an object" msgstr "Choisir un objet" #: ../src/jarabe/journal/objectchooser.py:151 -#: ../src/jarabe/view/viewsource.py:308 +#: ../src/jarabe/view/viewsource.py:311 msgid "Close" msgstr "Fermer" -#: ../src/jarabe/journal/palettes.py:73 +#: ../src/jarabe/journal/palettes.py:67 msgid "Resume with" msgstr "Reprendre avec" -#: ../src/jarabe/journal/palettes.py:76 +#: ../src/jarabe/journal/palettes.py:70 msgid "Start with" msgstr "Commencer avec" +#: ../src/jarabe/journal/palettes.py:83 ../src/jarabe/journal/palettes.py:216 +msgid "No activity to start entry" +msgstr "Acune activité pour démarrer l'entrée" + #: ../src/jarabe/journal/palettes.py:98 msgid "Send to" msgstr "Envoyer à" @@ -1179,99 +1509,306 @@ msgstr "Envoyer à" msgid "View Details" msgstr "Afficher les détails" -#: ../src/jarabe/journal/palettes.py:185 +#: ../src/jarabe/journal/palettes.py:181 msgid "No friends present" msgstr "Aucun ami présent" -#: ../src/jarabe/journal/palettes.py:190 +#: ../src/jarabe/journal/palettes.py:186 msgid "No valid connection found" msgstr "Aucune connexion valide trouvée" -#: ../src/jarabe/journal/palettes.py:218 +#: ../src/jarabe/journal/palettes.py:214 msgid "No activity to resume entry" msgstr "Aucune activité pour reprendre l'entrée" -#: ../src/jarabe/journal/palettes.py:220 -msgid "No activity to start entry" -msgstr "Acune activité pour démarrer l'entrée" +#: ../src/jarabe/model/network.py:163 +msgid "The reason for the device state change is unknown." +msgstr "La raison du changement d'état du périphérique est inconnue." + +#: ../src/jarabe/model/network.py:165 +msgid "The state change is normal." +msgstr "Le changement d'état est normal." + +#: ../src/jarabe/model/network.py:167 +msgid "The device is now managed." +msgstr "Le périphérique est géré." + +#: ../src/jarabe/model/network.py:169 +msgid "The device is no longer managed." +msgstr "Le périphérique n'est plus géré." + +#: ../src/jarabe/model/network.py:171 +msgid "The device could not be readied for configuration." +msgstr "Le périphérique n'est pas disponible pour être configuré." + +#: ../src/jarabe/model/network.py:173 +msgid "" +"IP configuration could not be reserved (no available address, timeout, etc)." +msgstr "" +"Impossible de réserver la configuration IP (adresse indisponible, " +"dépassement de délai, etc.)." + +#: ../src/jarabe/model/network.py:176 +msgid "The IP configuration is no longer valid." +msgstr "La configuration IP n'est plus valide." + +#: ../src/jarabe/model/network.py:178 +msgid "Secrets were required, but not provided." +msgstr "Secrets requis mais non fournis." + +#: ../src/jarabe/model/network.py:180 +msgid "" +"The 802.1X supplicant disconnected from the access point or authentication " +"server." +msgstr "" +"Déconnexion du supplicant 802.1X du point d'accès ou du serveur " +"d'authentification." + +#: ../src/jarabe/model/network.py:183 +msgid "Configuration of the 802.1X supplicant failed." +msgstr "Echec de la configuration du supplicant 802.1X." + +#: ../src/jarabe/model/network.py:185 +msgid "The 802.1X supplicant quit or failed unexpectedly." +msgstr "Arrêt ou échec inattendu du supplicant 802.1X." + +#: ../src/jarabe/model/network.py:187 +msgid "The 802.1X supplicant took too long to authenticate." +msgstr "Délai d'authentification trop long du supplicant 802.1X." + +#: ../src/jarabe/model/network.py:189 +msgid "The PPP service failed to start within the allowed time." +msgstr "Echec du démarrage du service PPP dans le délai autorisé." + +#: ../src/jarabe/model/network.py:191 +msgid "The PPP service disconnected unexpectedly." +msgstr "Déconnexion inattendue du service PPP." + +#: ../src/jarabe/model/network.py:193 +msgid "The PPP service quit or failed unexpectedly." +msgstr "Arrêt ou échec inattendu du service PPP." + +#: ../src/jarabe/model/network.py:195 +msgid "The DHCP service failed to start within the allowed time." +msgstr "Echec du démarrage du service DHCP dans le délai autorisé." + +#: ../src/jarabe/model/network.py:197 +msgid "The DHCP service reported an unexpected error." +msgstr "Le service DHCP a signalé une erreur inattendue." + +#: ../src/jarabe/model/network.py:199 +msgid "The DHCP service quit or failed unexpectedly." +msgstr "Arrêt ou échec inattendu du service DHCP." + +#: ../src/jarabe/model/network.py:201 +msgid "The shared connection service failed to start." +msgstr "Echec du démarrage du service de connexion partagée." + +#: ../src/jarabe/model/network.py:203 +msgid "The shared connection service quit or failed unexpectedly." +msgstr "Arrêt ou échec inattendu du service de connexion partagée." + +#: ../src/jarabe/model/network.py:206 +msgid "The AutoIP service failed to start." +msgstr "Echec du démarrage du service AutoIP." + +#: ../src/jarabe/model/network.py:208 +msgid "The AutoIP service reported an unexpected error." +msgstr "Le service AutoIP a signalé une erreur inattendue." + +#: ../src/jarabe/model/network.py:210 +msgid "The AutoIP service quit or failed unexpectedly." +msgstr "Arrêt ou échec inattendu du service AutoIP." + +#: ../src/jarabe/model/network.py:212 +msgid "Dialing failed because the line was busy." +msgstr "Echec de la numérotation car la ligne était occupée." + +#: ../src/jarabe/model/network.py:214 +msgid "Dialing failed because there was no dial tone." +msgstr "Echec de la numérotation en l'absence de tonalité." + +#: ../src/jarabe/model/network.py:216 +msgid "Dialing failed because there was no carrier." +msgstr "Echec de la numérotation résultant de l'absence de porteuse." -#: ../src/jarabe/view/buddymenu.py:62 +#: ../src/jarabe/model/network.py:218 +msgid "Dialing timed out." +msgstr "Délai de la numérotation dépassé." + +#: ../src/jarabe/model/network.py:220 +msgid "Dialing failed." +msgstr "Echec de la numérotation." + +#: ../src/jarabe/model/network.py:222 +msgid "Modem initialization failed." +msgstr "Echec de l'initialisation du modem." + +#: ../src/jarabe/model/network.py:224 +msgid "Failed to select the specified GSM APN" +msgstr "Echec de la sélection de l'APN du GSM spécifié." + +#: ../src/jarabe/model/network.py:226 +msgid "Not searching for networks." +msgstr "Pas de recherche de réseaux." + +#: ../src/jarabe/model/network.py:228 +msgid "Network registration was denied." +msgstr "Enregistrement du réseau refusé." + +#: ../src/jarabe/model/network.py:230 +msgid "Network registration timed out." +msgstr "Enregistrement du réseau expiré." + +#: ../src/jarabe/model/network.py:232 +msgid "Failed to register with the requested GSM network." +msgstr "Echec de l'enregistrement avec le réseau GSM demandé." + +#: ../src/jarabe/model/network.py:234 +msgid "PIN check failed." +msgstr "Echec de la vérification du code PIN." + +#: ../src/jarabe/model/network.py:236 +msgid "Necessary firmware for the device may be missing." +msgstr "Le firmware requis pour le périphérique est peut-être absent." + +#: ../src/jarabe/model/network.py:238 +msgid "The device was removed." +msgstr "Le périphérique a été retiré." + +#: ../src/jarabe/model/network.py:240 +msgid "NetworkManager went to sleep." +msgstr "NetworkManager est en veille." + +#: ../src/jarabe/model/network.py:242 +msgid "The device's active connection was removed or disappeared." +msgstr "La connexion active du périphérique a été supprimée ou a disparu." + +#: ../src/jarabe/model/network.py:245 +msgid "A user or client requested the disconnection." +msgstr "Un utilisateur ou un client a demandé la déconnexion." + +#: ../src/jarabe/model/network.py:247 +msgid "The device's carrier/link changed." +msgstr "La porteuse/le lien du périphérique a changé." + +#: ../src/jarabe/view/buddymenu.py:63 msgid "Remove friend" msgstr "Retirer de mes amis" -#: ../src/jarabe/view/buddymenu.py:65 +#: ../src/jarabe/view/buddymenu.py:66 msgid "Make friend" msgstr "Ajouter à mes amis" -#: ../src/jarabe/view/buddymenu.py:82 +#: ../src/jarabe/view/buddymenu.py:83 msgid "Shutdown" msgstr "Éteindre" -#: ../src/jarabe/view/buddymenu.py:90 +#: ../src/jarabe/view/buddymenu.py:91 +msgid "Restart" +msgstr "Redémarrer" + +#: ../src/jarabe/view/buddymenu.py:97 msgid "Logout" msgstr "Se déconnecter" -#: ../src/jarabe/view/buddymenu.py:95 +#: ../src/jarabe/view/buddymenu.py:102 msgid "My Settings" msgstr "Mes paramètres" -#: ../src/jarabe/view/buddymenu.py:130 +#: ../src/jarabe/view/buddymenu.py:137 #, python-format msgid "Invite to %s" msgstr "Inviter à %s" -#: ../src/jarabe/view/palettes.py:45 +#: ../src/jarabe/view/launcher.py:190 +#, python-format +msgid "<b>%s</b> failed to start." +msgstr "<b>%s</b> n'a pas pu être démarré." + +#: ../src/jarabe/view/palettes.py:46 msgid "Starting..." msgstr "Démarrage..." +#: ../src/jarabe/view/palettes.py:56 +msgid "Activity failed to start" +msgstr "Echec du démarrage de l'activité" + #. TODO: share-with, keep -#: ../src/jarabe/view/palettes.py:71 +#: ../src/jarabe/view/palettes.py:85 msgid "View Source" msgstr "Afficher la source" -#: ../src/jarabe/view/palettes.py:82 +#: ../src/jarabe/view/palettes.py:96 msgid "Stop" msgstr "Arrêter" -#: ../src/jarabe/view/palettes.py:122 +#: ../src/jarabe/view/palettes.py:132 msgid "Start new" msgstr "Commencer un nouveau" -#: ../src/jarabe/view/palettes.py:171 +#: ../src/jarabe/view/palettes.py:172 msgid "Show contents" msgstr "Afficher les contenus" -#: ../src/jarabe/view/palettes.py:193 ../src/jarabe/view/palettes.py:243 +#: ../src/jarabe/view/palettes.py:194 ../src/jarabe/view/palettes.py:245 #, python-format msgid "%(free_space)d MB Free" msgstr "%(free_space)d Mo de libre" -#: ../src/jarabe/view/viewsource.py:208 +#: ../src/jarabe/view/viewsource.py:211 msgid "Instance Source" msgstr "Instancie source" -#: ../src/jarabe/view/viewsource.py:233 +#: ../src/jarabe/view/viewsource.py:236 msgid "Source" msgstr "Source" -#: ../src/jarabe/view/viewsource.py:292 +#: ../src/jarabe/view/viewsource.py:295 msgid "Activity Bundle Source" msgstr "Source du paquet activité" -#: ../src/jarabe/view/viewsource.py:299 +#: ../src/jarabe/view/viewsource.py:302 #, python-format msgid "View source: %r" msgstr "Afficher le code source : %r" +#: ../src/jarabe/util/emulator.py:40 +msgid "Sugar in a window" +msgstr "Sugar dans une fenêtre" + +#~ msgid "APN:" +#~ msgstr "APN :" + +#~ msgid "Create new wireless network" +#~ msgstr "Créer un nouveau réseau sans fil" + +#, python-format +#~ msgid "%s's network" +#~ msgstr "Réseau %s" + +#, python-format +#~ msgid "Data sent %d kb / received %d kb" +#~ msgstr "Données envoyées %d ko / reçues %d ko" + +#~ msgid "Connection time " +#~ msgstr "Durée de connexion" + +#~ msgid "Title" +#~ msgstr "Titre" + +#~ msgid "Version" +#~ msgstr "Version" + +#~ msgid "Date" +#~ msgstr "Date" + #~ msgid "Cannot obtain data needed for registration." #~ msgstr "Impossible d'obtenir les données nécessaires à l'enregistrement." #~ msgid "Unmount" #~ msgstr "Démonter" -#~ msgid "Restart" -#~ msgstr "Redémarrer" - #~ msgid "" #~ "© 2008 One Laptop per Child Association Inc; Red Hat Inc; and Contributors." #~ msgstr "" @@ -1294,12 +1831,6 @@ msgstr "Afficher le code source : %r" #~ msgid "Disconnecting..." #~ msgstr "Déconnexion..." -#~ msgid "Mesh Network" -#~ msgstr "Réseau maillé" - -#~ msgid "Disconnected" -#~ msgstr "Déconnecté" - #~ msgid "About my XO" #~ msgstr "Mon XO" @@ -2,12 +2,20 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2010-02-11 00:32-0500\n" -"PO-Revision-Date: 2010-03-17 17:21+0200\n" +"POT-Creation-Date: 2010-12-23 01:15-0500\n" +"PO-Revision-Date: 2011-02-09 17:49+0200\n" "Last-Translator: Carlo Falciola <cfalciola@yahoo.it>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "Language: it\n" @@ -21,43 +29,39 @@ msgstr "" msgid "About Me" msgstr "Informazioni su" -#: ../extensions/cpsection/aboutme/model.py:43 +#: ../extensions/cpsection/aboutme/model.py:48 msgid "You must enter a name." msgstr "Devi inserire un nome." -#: ../extensions/cpsection/aboutme/model.py:68 +#: ../extensions/cpsection/aboutme/model.py:75 #, python-format msgid "stroke: color=%s hue=%s" msgstr "linea: colore=%s tinta=%s" -#: ../extensions/cpsection/aboutme/model.py:71 +#: ../extensions/cpsection/aboutme/model.py:78 #, python-format msgid "stroke: %s" msgstr "linea: %s" -#: ../extensions/cpsection/aboutme/model.py:73 +#: ../extensions/cpsection/aboutme/model.py:80 #, python-format msgid "fill: color=%s hue=%s" msgstr "riempimento: colore=%s tinta=%s" -#: ../extensions/cpsection/aboutme/model.py:75 +#: ../extensions/cpsection/aboutme/model.py:82 #, python-format msgid "fill: %s" msgstr "riempimento: %s" -#: ../extensions/cpsection/aboutme/model.py:86 +#: ../extensions/cpsection/aboutme/model.py:94 msgid "Error in specified color modifiers." -msgstr "Errore nella variazione dei colori richiesta" +msgstr "Errore nella variazione dei colori richiesta." -#: ../extensions/cpsection/aboutme/model.py:89 +#: ../extensions/cpsection/aboutme/model.py:97 msgid "Error in specified colors." msgstr "Errore nella definizione dei colori." -#: ../extensions/cpsection/aboutme/view.py:94 ../src/jarabe/intro/window.py:93 -msgid "Name:" -msgstr "Nome:" - -#: ../extensions/cpsection/aboutme/view.py:128 +#: ../extensions/cpsection/aboutme/view.py:235 msgid "Click to change your color:" msgstr "Seleziona per cambiare il tuo colore:" @@ -65,43 +69,43 @@ msgstr "Seleziona per cambiare il tuo colore:" msgid "About my Computer" msgstr "Il mio Computer" -#: ../extensions/cpsection/aboutcomputer/model.py:28 +#: ../extensions/cpsection/aboutcomputer/model.py:29 msgid "Not available" msgstr "Non disponibile" -#: ../extensions/cpsection/aboutcomputer/view.py:60 +#: ../extensions/cpsection/aboutcomputer/view.py:61 msgid "Identity" msgstr "Identità" -#: ../extensions/cpsection/aboutcomputer/view.py:69 +#: ../extensions/cpsection/aboutcomputer/view.py:70 msgid "Serial Number:" msgstr "Numero di Serie:" -#: ../extensions/cpsection/aboutcomputer/view.py:91 +#: ../extensions/cpsection/aboutcomputer/view.py:92 msgid "Software" msgstr "Software" -#: ../extensions/cpsection/aboutcomputer/view.py:100 +#: ../extensions/cpsection/aboutcomputer/view.py:101 msgid "Build:" msgstr "Build:" -#: ../extensions/cpsection/aboutcomputer/view.py:115 +#: ../extensions/cpsection/aboutcomputer/view.py:116 msgid "Sugar:" msgstr "Sugar:" -#: ../extensions/cpsection/aboutcomputer/view.py:131 +#: ../extensions/cpsection/aboutcomputer/view.py:132 msgid "Firmware:" msgstr "Firmware:" -#: ../extensions/cpsection/aboutcomputer/view.py:146 +#: ../extensions/cpsection/aboutcomputer/view.py:147 msgid "Wireless Firmware:" msgstr "Wireless Firmware:" -#: ../extensions/cpsection/aboutcomputer/view.py:169 +#: ../extensions/cpsection/aboutcomputer/view.py:170 msgid "Copyright and License" msgstr "Copyright e Licenza" -#: ../extensions/cpsection/aboutcomputer/view.py:184 +#: ../extensions/cpsection/aboutcomputer/view.py:188 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 " @@ -113,7 +117,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:196 +#: ../extensions/cpsection/aboutcomputer/view.py:200 msgid "Full license:" msgstr "Testo della Licenza:" @@ -121,11 +125,11 @@ msgstr "Testo della Licenza:" msgid "Date & Time" msgstr "Data e Ora" -#: ../extensions/cpsection/datetime/model.py:87 +#: ../extensions/cpsection/datetime/model.py:92 msgid "Error timezone does not exist." msgstr "Errore, timezone non esistente." -#: ../extensions/cpsection/datetime/view.py:68 ../data/sugar.schemas.in.h:33 +#: ../extensions/cpsection/datetime/view.py:70 ../data/sugar.schemas.in.h:52 msgid "Timezone" msgstr "Timezone" @@ -133,50 +137,50 @@ msgstr "Timezone" msgid "Frame" msgstr "Cornice" -#: ../extensions/cpsection/frame/model.py:38 -#: ../extensions/cpsection/frame/model.py:60 +#: ../extensions/cpsection/frame/model.py:41 +#: ../extensions/cpsection/frame/model.py:66 msgid "Value must be an integer." msgstr "Valore deve essere un intero." -#: ../extensions/cpsection/frame/view.py:26 +#: ../extensions/cpsection/frame/view.py:27 msgid "never" msgstr "mai" -#: ../extensions/cpsection/frame/view.py:27 +#: ../extensions/cpsection/frame/view.py:28 msgid "instantaneous" msgstr "istantaneamente" -#: ../extensions/cpsection/frame/view.py:28 +#: ../extensions/cpsection/frame/view.py:29 #, python-format msgid "%s seconds" msgstr "%s secondi" -#: ../extensions/cpsection/frame/view.py:52 +#: ../extensions/cpsection/frame/view.py:54 msgid "Activation Delay" msgstr "Ritardo attivazione" -#: ../extensions/cpsection/frame/view.py:76 +#: ../extensions/cpsection/frame/view.py:78 msgid "Corner" msgstr "Angolo" -#: ../extensions/cpsection/frame/view.py:111 +#: ../extensions/cpsection/frame/view.py:113 msgid "Edge" msgstr "Margine" #: ../extensions/cpsection/keyboard/__init__.py:21 -#: ../extensions/cpsection/keyboard/view.py:31 +#: ../extensions/cpsection/keyboard/view.py:32 msgid "Keyboard" msgstr "Tastiera" -#: ../extensions/cpsection/keyboard/view.py:189 +#: ../extensions/cpsection/keyboard/view.py:190 msgid "Keyboard Model" msgstr "Modello Tastiera" -#: ../extensions/cpsection/keyboard/view.py:248 +#: ../extensions/cpsection/keyboard/view.py:250 msgid "Key(s) to change layout" msgstr "Tasto/i per cambiare la disposizione" -#: ../extensions/cpsection/keyboard/view.py:318 +#: ../extensions/cpsection/keyboard/view.py:319 msgid "Keyboard Layout(s)" msgstr "Disposizione Tastiera" @@ -185,21 +189,21 @@ msgstr "Disposizione Tastiera" msgid "Language" msgstr "Lingua" -#: ../extensions/cpsection/language/model.py:28 +#: ../extensions/cpsection/language/model.py:30 msgid "Could not access ~/.i18n. Create standard settings." msgstr "Impossibile accedere a ~/.i18n. Creazione configurazione standard." -#: ../extensions/cpsection/language/model.py:124 +#: ../extensions/cpsection/language/model.py:131 #, python-format msgid "Language for code=%s could not be determined." msgstr "Linguaggio con codice=%s sconosciuto." -#: ../extensions/cpsection/language/model.py:144 +#: ../extensions/cpsection/language/model.py:152 #, python-format msgid "Sorry I do not speak '%s'." msgstr "Spiacente, ma non parlo '%s'." -#: ../extensions/cpsection/language/view.py:56 +#: ../extensions/cpsection/language/view.py:57 msgid "" "Add languages in the order you prefer. If a translation is not available, " "the next in the list will be used." @@ -211,66 +215,82 @@ msgstr "" msgid "Modem Configuration" msgstr "Configurazione del modem" -#: ../extensions/cpsection/modemconfiguration/view.py:90 +#: ../extensions/cpsection/modemconfiguration/view.py:94 msgid "Username:" msgstr "Utente:" -#: ../extensions/cpsection/modemconfiguration/view.py:101 +#: ../extensions/cpsection/modemconfiguration/view.py:106 msgid "Password:" msgstr "Password:" -#: ../extensions/cpsection/modemconfiguration/view.py:112 +#: ../extensions/cpsection/modemconfiguration/view.py:118 msgid "Number:" msgstr "Numero:" -#: ../extensions/cpsection/modemconfiguration/view.py:123 -msgid "APN:" -msgstr "APN:" +#: ../extensions/cpsection/modemconfiguration/view.py:130 +msgid "Access Point Name (APN):" +msgstr "Nome Access Point (APN):" + +#: ../extensions/cpsection/modemconfiguration/view.py:142 +msgid "Personal Identity Number (PIN):" +msgstr "Personal Identity Number (PIN):" + +#: ../extensions/cpsection/modemconfiguration/view.py:154 +msgid "Personal Unblocking Key (PUK):" +msgstr "Personal Unblocking Key (PUK):" + +#: ../extensions/cpsection/modemconfiguration/view.py:175 +msgid "" +"You will need to provide the following information to set up a mobile " +"broadband connection to a cellular (3G) network." +msgstr "" +"Dovresti fornire le informazioni richieste per configurare una connessione " +"mobile in banda larga ad una rete cellulare (3G)." #: ../extensions/cpsection/network/__init__.py:21 -#: ../extensions/cpsection/network/view.py:28 +#: ../extensions/cpsection/network/view.py:29 msgid "Network" msgstr "Network" -#: ../extensions/cpsection/network/model.py:79 +#: ../extensions/cpsection/network/model.py:68 msgid "State is unknown." msgstr "Stato sconosciuto." -#: ../extensions/cpsection/network/model.py:105 +#: ../extensions/cpsection/network/model.py:96 msgid "Error in specified radio argument use on/off." msgstr "Errore nel campo specificato, utilizzare on/off." -#: ../extensions/cpsection/network/model.py:137 +#: ../extensions/cpsection/network/model.py:133 msgid "Error in specified argument use 0/1." msgstr "Errore nel campo specificato, utilizzare 0/1." -#: ../extensions/cpsection/network/view.py:59 +#: ../extensions/cpsection/network/view.py:61 msgid "Wireless" msgstr "Wireless" -#: ../extensions/cpsection/network/view.py:67 +#: ../extensions/cpsection/network/view.py:69 msgid "Turn off the wireless radio to save battery life" msgstr "Spegni il trasmettitore wireless per risparmiare carica della batteria" -#: ../extensions/cpsection/network/view.py:80 +#: ../extensions/cpsection/network/view.py:82 msgid "Radio" msgstr "Radio" -#: ../extensions/cpsection/network/view.py:96 +#: ../extensions/cpsection/network/view.py:98 msgid "Discard network history if you have trouble connecting to the network" msgstr "" "Elimina la storia delle connessioni di rete effettuate nel caso di problemi " "di connessione" -#: ../extensions/cpsection/network/view.py:105 +#: ../extensions/cpsection/network/view.py:107 msgid "Discard network history" msgstr "Elimina la storia delle connessioni di rete" -#: ../extensions/cpsection/network/view.py:118 +#: ../extensions/cpsection/network/view.py:120 msgid "Collaboration" msgstr "Collaborazione" -#: ../extensions/cpsection/network/view.py:126 +#: ../extensions/cpsection/network/view.py:128 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 " @@ -280,7 +300,7 @@ msgstr "" "stesso server sono in grado di vedersi fra loro, anche se collegate a reti " "differenti." -#: ../extensions/cpsection/network/view.py:136 +#: ../extensions/cpsection/network/view.py:138 msgid "Server:" msgstr "Server:" @@ -288,26 +308,27 @@ msgstr "Server:" msgid "Power" msgstr "Energia" -#: ../extensions/cpsection/power/model.py:54 +#: ../extensions/cpsection/power/model.py:90 msgid "Error in automatic pm argument, use on/off." msgstr "" -"Errore nel campo Gestione Automatica Risparmio Energetico, utilizza on/off" +"Errore nel campo Gestione Automatica Risparmio Energetico, utilizza on/off." -#: ../extensions/cpsection/power/model.py:81 +#: ../extensions/cpsection/power/model.py:120 msgid "Error in extreme pm argument, use on/off." -msgstr "Errore nel campo Gestione Estrema Risparmio Energetico, utilizza on/off" +msgstr "" +"Errore nel campo Gestione Estrema Risparmio Energetico, utilizza on/off." -#: ../extensions/cpsection/power/view.py:47 +#: ../extensions/cpsection/power/view.py:48 msgid "Power management" msgstr "Gestione Risparmio Energetico (power management)" -#: ../extensions/cpsection/power/view.py:57 +#: ../extensions/cpsection/power/view.py:58 msgid "Automatic power management (increases battery life)" msgstr "" "Gestione Automatica Risparmio Energetico (incrementa la durata delle " "batterie)" -#: ../extensions/cpsection/power/view.py:85 +#: ../extensions/cpsection/power/view.py:86 msgid "" "Extreme power management (disableswireless radio, increases battery life)" msgstr "" @@ -324,7 +345,7 @@ msgid "" "provide new features." msgstr "" "Gli aggiornamenti software correggono errori, eliminano vulnerabilità del " -"codice ed offrono nuove funzioni. " +"codice ed offrono nuove funzioni." #: ../extensions/cpsection/updater/view.py:125 #, python-format @@ -360,26 +381,26 @@ msgstr "Ricerca aggiornamenti..." msgid "Installing updates..." msgstr "Installazione aggiornamenti..." -#: ../extensions/cpsection/updater/view.py:172 +#: ../extensions/cpsection/updater/view.py:173 #, python-format msgid "%s update was installed" msgid_plural "%s updates were installed" msgstr[0] "%s aggiornamento è stato installato" msgstr[1] "%s aggiornamenti sono stati installati" -#: ../extensions/cpsection/updater/view.py:253 +#: ../extensions/cpsection/updater/view.py:255 msgid "Install selected" msgstr "Installa selezionati" -#: ../extensions/cpsection/updater/view.py:274 +#: ../extensions/cpsection/updater/view.py:276 #, python-format msgid "Download size: %s" msgstr "Dimensione dati da scaricare: %s" -#: ../extensions/cpsection/updater/view.py:362 +#: ../extensions/cpsection/updater/view.py:364 #, python-format -msgid "From version %(current)d to %(new)s (Size: %(size)s)" -msgstr "Dalla versione %(current)d alla %(new)s (Dimensione: %(size)s)" +msgid "From version %(current)s to %(new)s (Size: %(size)s)" +msgstr "Dalla versione %(current)s alla %(new)s (Dimensione: %(size)s)" #. TRANS: download size is 0 #: ../extensions/cpsection/updater/view.py:382 @@ -403,28 +424,28 @@ msgstr "%.0f KB" msgid "%.1f MB" msgstr "%.1f MB" -#: ../extensions/deviceicon/battery.py:58 +#: ../extensions/deviceicon/battery.py:60 msgid "My Battery" msgstr "La mia Batteria" -#: ../extensions/deviceicon/battery.py:137 +#: ../extensions/deviceicon/battery.py:141 msgid "Removed" msgstr "Rimosso" -#: ../extensions/deviceicon/battery.py:140 +#: ../extensions/deviceicon/battery.py:144 msgid "Charging" msgstr "Caricando" -#: ../extensions/deviceicon/battery.py:143 +#: ../extensions/deviceicon/battery.py:147 msgid "Very little power remaining" msgstr "Pochissima carica rimanente" -#: ../extensions/deviceicon/battery.py:149 +#: ../extensions/deviceicon/battery.py:153 #, python-format msgid "%(hour)d:%(min).2d remaining" msgstr "%(hour)d:%(min).2d rimanenti" -#: ../extensions/deviceicon/battery.py:152 +#: ../extensions/deviceicon/battery.py:156 msgid "Charged" msgstr "Carica" @@ -437,119 +458,174 @@ 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:111 +#: ../extensions/deviceicon/network.py:104 msgid "Disconnect..." msgstr "Disconnessione..." -#: ../extensions/deviceicon/network.py:116 -msgid "Create new wireless network" -msgstr "Crea una nuova rete wireless" - -#: ../extensions/deviceicon/network.py:122 -#: ../extensions/deviceicon/network.py:284 -#: ../src/jarabe/desktop/meshbox.py:248 ../src/jarabe/desktop/meshbox.py:537 +#: ../extensions/deviceicon/network.py:112 +#: ../extensions/deviceicon/network.py:290 +#: ../src/jarabe/desktop/networkviews.py:239 +#: ../src/jarabe/desktop/networkviews.py:538 +#: ../src/jarabe/desktop/networkviews.py:667 msgid "Connecting..." msgstr "Connessione..." # TODO: show the channel number -#: ../extensions/deviceicon/network.py:126 -#: ../extensions/deviceicon/network.py:198 -#: ../extensions/deviceicon/network.py:288 -#: ../src/jarabe/desktop/meshbox.py:254 ../src/jarabe/desktop/meshbox.py:543 +#: ../extensions/deviceicon/network.py:116 +#: ../extensions/deviceicon/network.py:182 +#: ../src/jarabe/desktop/networkviews.py:249 +#: ../src/jarabe/desktop/networkviews.py:544 +#: ../src/jarabe/desktop/networkviews.py:673 msgid "Connected" msgstr "Connesso" -#: ../extensions/deviceicon/network.py:158 +#: ../extensions/deviceicon/network.py:142 msgid "Channel" msgstr "Canale" -#: ../extensions/deviceicon/network.py:173 +#: ../extensions/deviceicon/network.py:157 msgid "Wired Network" msgstr "Rete su cavo" -#: ../extensions/deviceicon/network.py:201 +#: ../extensions/deviceicon/network.py:185 msgid "Speed" msgstr "Velocità" -#: ../extensions/deviceicon/network.py:228 +#: ../extensions/deviceicon/network.py:211 msgid "Wireless modem" msgstr "Modem Wireless" -#: ../extensions/deviceicon/network.py:276 +#: ../extensions/deviceicon/network.py:278 msgid "Please wait..." msgstr "Attendi..." -#: ../extensions/deviceicon/network.py:279 -#: ../src/jarabe/desktop/meshbox.py:164 ../src/jarabe/desktop/meshbox.py:494 +#: ../extensions/deviceicon/network.py:282 +#: ../src/jarabe/desktop/networkviews.py:149 +#: ../src/jarabe/desktop/networkviews.py:492 +#: ../src/jarabe/desktop/networkviews.py:624 msgid "Connect" msgstr "Connetti" -#: ../extensions/deviceicon/network.py:280 +#: ../extensions/deviceicon/network.py:283 msgid "Disconnected" msgstr "Disconnesso" -#: ../extensions/deviceicon/network.py:283 -#: ../src/jarabe/controlpanel/toolbar.py:115 -#: ../src/jarabe/desktop/homebox.py:68 -#: ../src/jarabe/frame/activitiestray.py:700 -#: ../src/jarabe/frame/activitiestray.py:799 -#: ../src/jarabe/frame/activitiestray.py:827 +#: ../extensions/deviceicon/network.py:289 +#: ../src/jarabe/controlpanel/toolbar.py:119 +#: ../src/jarabe/desktop/homebox.py:70 +#: ../src/jarabe/frame/activitiestray.py:587 +#: ../src/jarabe/frame/activitiestray.py:687 +#: ../src/jarabe/frame/activitiestray.py:715 msgid "Cancel" msgstr "Cancella" -#: ../extensions/deviceicon/network.py:287 -#: ../src/jarabe/desktop/meshbox.py:168 +#: ../extensions/deviceicon/network.py:297 +#: ../src/jarabe/desktop/networkviews.py:153 +#: ../src/jarabe/desktop/networkviews.py:496 msgid "Disconnect" msgstr "Disconnetti" -#: ../extensions/deviceicon/network.py:530 +#: ../extensions/deviceicon/network.py:327 +msgid "Try connection again" +msgstr "Prova nuovamente a connetterti" + +#: ../extensions/deviceicon/network.py:330 #, python-format -msgid "%s's network" -msgstr "rete di %s" +msgid "Error: %s" +msgstr "Errore: %s" + +#: ../extensions/deviceicon/network.py:334 +#, python-format +msgid "Suggestion: %s" +msgstr "Suggerimento: %s" + +#: ../extensions/deviceicon/network.py:340 +#: ../extensions/deviceicon/network.py:343 +#, python-format +msgid "Connected for %s" +msgstr "Connesso per %s" + +#: ../extensions/deviceicon/network.py:348 +#: ../extensions/deviceicon/network.py:349 +#, python-format +msgid "%d KB" +msgstr "%d KB" + +#: ../extensions/deviceicon/network.py:354 +msgid "Check your Pin/Puk configuration." +msgstr "Verifica la configurazione di Pin/Puk ." + +#: ../extensions/deviceicon/network.py:357 +msgid "Check your Access Point Name (APN) configuration" +msgstr "Verifica la configurazione dell'Access Point Name (APN)" + +#: ../extensions/deviceicon/network.py:361 +msgid "Check the Number configuration." +msgstr "Verifica la configurazione del Numero" + +#: ../extensions/deviceicon/network.py:363 +msgid "Check your configuration." +msgstr "Verifica la configurazione." # A complete translation in italian: "rete a maglie" becames a tautology -#: ../extensions/deviceicon/network.py:597 -#: ../extensions/deviceicon/network.py:656 +#: ../extensions/deviceicon/network.py:615 msgid "Mesh Network" msgstr "Rete Mesh" -#: ../extensions/deviceicon/network.py:857 +# A complete translation in italian: "rete a maglie" becames a tautology +#: ../extensions/deviceicon/network.py:658 #, python-format -msgid "Data sent %d kb / received %d kb" -msgstr "Dati %d kb inviati / %d kb ricevuti" +msgid "Mesh Network %s" +msgstr "Rete Mesh %s" + +#: ../extensions/deviceicon/network.py:782 +msgid "No GSM connection available." +msgstr "Connessione GSM non disponibile." -#: ../extensions/deviceicon/network.py:868 -msgid "Connection time " -msgstr "Tempo di connessione" +#: ../extensions/deviceicon/network.py:783 +msgid "Create a connection in the control panel." +msgstr "Definisci una connessione nel Pannello di Controllo." -#: ../extensions/deviceicon/speaker.py:59 +#: ../extensions/deviceicon/speaker.py:60 msgid "My Speakers" msgstr "I miei Altoparlanti" -#: ../extensions/deviceicon/speaker.py:133 +#: ../extensions/deviceicon/speaker.py:136 msgid "Unmute" msgstr "Attiva" -#: ../extensions/deviceicon/speaker.py:136 +#: ../extensions/deviceicon/speaker.py:139 msgid "Mute" msgstr "Silenzia" +#: ../extensions/deviceicon/touchpad.py:37 +msgid "finger" +msgstr "dito" + +#: ../extensions/deviceicon/touchpad.py:38 +msgid "stylus" +msgstr "stilo" + +#: ../extensions/deviceicon/touchpad.py:67 +msgid "My touchpad" +msgstr "Il mio Touchpad" + #: ../extensions/globalkey/screenshot.py:59 msgid "Mesh" msgstr "Mesh" #: ../extensions/globalkey/screenshot.py:61 -#: ../src/jarabe/frame/zoomtoolbar.py:39 +#: ../src/jarabe/frame/zoomtoolbar.py:40 msgid "Group" msgstr "Gruppo" #: ../extensions/globalkey/screenshot.py:63 -#: ../src/jarabe/frame/zoomtoolbar.py:41 +#: ../src/jarabe/frame/zoomtoolbar.py:42 msgid "Home" msgstr "Casa" #: ../extensions/globalkey/screenshot.py:69 -#: ../src/jarabe/frame/zoomtoolbar.py:43 +#: ../src/jarabe/frame/zoomtoolbar.py:44 msgid "Activity" msgstr "Attività" @@ -568,13 +644,21 @@ msgid "" "long name." msgstr "" "se \"disabled\" viene richiesto il nome alla inizializzazione; se \"system\" " -"verrà utilizzato il nome presente nell'account UNIX." +"verrà utilizzato il nome presente nell'account UNIX." #: ../data/sugar.schemas.in.h:2 +msgid "Additional directories which can contain updated translations." +msgstr "Ulteriori directory contenenti traduzioni aggiornate." + +#: ../data/sugar.schemas.in.h:3 msgid "Backup URL" msgstr "URL di salvataggio" -#: ../data/sugar.schemas.in.h:3 +#: ../data/sugar.schemas.in.h:4 +msgid "Bundle IDs of protected activities" +msgstr "ID dei pacchetti di attività protette" + +#: ../data/sugar.schemas.in.h:5 msgid "" "Color for the XO icon that is used throughout the desktop. The string is " "composed of the stroke color and fill color, format is that of rbg colors. " @@ -584,52 +668,105 @@ msgstr "" "composta dal colore della linea e dal colore del riempimento, il formato è " "quello dei colori rbg. Esempio: #AC32FF,#9A5200" -#: ../data/sugar.schemas.in.h:4 +#: ../data/sugar.schemas.in.h:6 msgid "Corner Delay" msgstr "Ritardo dell'angolo" -#: ../data/sugar.schemas.in.h:5 +#: ../data/sugar.schemas.in.h:7 msgid "Default font face" msgstr "Tipo di carattere di default" -#: ../data/sugar.schemas.in.h:6 +#: ../data/sugar.schemas.in.h:8 msgid "Default font size" msgstr "Dimensione del carattere di default" -#: ../data/sugar.schemas.in.h:7 +#: ../data/sugar.schemas.in.h:9 msgid "Default nick" msgstr "Nome di default" -#: ../data/sugar.schemas.in.h:8 +#: ../data/sugar.schemas.in.h:10 msgid "Delay for the activation of the frame using the corners." msgstr "" "Ritardo nella attivazione della cornice portando il puntatore in un angolo." -#: ../data/sugar.schemas.in.h:9 +#: ../data/sugar.schemas.in.h:11 msgid "Delay for the activation of the frame using the edges." -msgstr "Ritardo nella attivazione della cornice portando il puntatore sui bordi" +msgstr "" +"Ritardo nella attivazione della cornice portando il puntatore sui bordi." -#: ../data/sugar.schemas.in.h:10 +#: ../data/sugar.schemas.in.h:12 +msgid "Directory to search for translations" +msgstr "Directory in cui cercare traduzioni" + +#: ../data/sugar.schemas.in.h:13 msgid "Edge Delay" msgstr "Ritardo del bordo" -#: ../data/sugar.schemas.in.h:11 +#: ../data/sugar.schemas.in.h:14 msgid "Favorites Layout" msgstr "Disposizione delle attività preferite" -#: ../data/sugar.schemas.in.h:12 +#: ../data/sugar.schemas.in.h:15 msgid "Favorites resume mode" msgstr "Modalità di richiamo delle attività preferite" -#: ../data/sugar.schemas.in.h:13 +#: ../data/sugar.schemas.in.h:16 msgid "Font face that is used throughout the desktop." msgstr "Tipo di carattere di default utilizzato in tutto il sistema." -#: ../data/sugar.schemas.in.h:14 +#: ../data/sugar.schemas.in.h:17 msgid "Font size that is used throughout the desktop." msgstr "Dimensione del carattere utilizzato in tutto il sistema." -#: ../data/sugar.schemas.in.h:15 +#: ../data/sugar.schemas.in.h:18 +msgid "GSM network APN" +msgstr " APN rete GSM" + +#: ../data/sugar.schemas.in.h:19 +msgid "GSM network PIN" +msgstr "PIN rete GSM" + +#: ../data/sugar.schemas.in.h:20 +msgid "GSM network PUK" +msgstr "PUK rete GSM" + +#: ../data/sugar.schemas.in.h:21 +msgid "GSM network access point name configuration" +msgstr "configurazione di access point name rete GSM" + +#: ../data/sugar.schemas.in.h:22 +msgid "GSM network number" +msgstr "numero di rete GSM" + +#: ../data/sugar.schemas.in.h:23 +msgid "GSM network password" +msgstr "password di rete GSM" + +#: ../data/sugar.schemas.in.h:24 +msgid "GSM network password configuration" +msgstr "configurazione della password di rete GSM" + +#: ../data/sugar.schemas.in.h:25 +msgid "GSM network personal identification number configuration" +msgstr "configurazione del personal identification number (PIN) della rete GSM" + +#: ../data/sugar.schemas.in.h:26 +msgid "GSM network personal unlock key configuration" +msgstr "configurazione della chiave personale di sblocco (PUK) della rete GSM" + +#: ../data/sugar.schemas.in.h:27 +msgid "GSM network telephone number configuration" +msgstr "configurazione del numero telefonico della rete GSM" + +#: ../data/sugar.schemas.in.h:28 +msgid "GSM network username" +msgstr "username dela rete GSM" + +#: ../data/sugar.schemas.in.h:29 +msgid "GSM network username configuration" +msgstr "configurazione dello username della rete GSM" + +#: ../data/sugar.schemas.in.h:30 msgid "" "If TRUE, Sugar will make us searchable for the other users of the Jabber " "server." @@ -637,110 +774,136 @@ msgstr "" "Se TRUE, Sugar renderà il computer rintracciabile dagli altri utenti del " "server Jabber." -#: ../data/sugar.schemas.in.h:16 +#: ../data/sugar.schemas.in.h:31 msgid "If TRUE, Sugar will show a \"Log out\" option." -msgstr "Se TRUE, Sugar mostrerà una opzione di \"Disconnessione\"." +msgstr "Se TRUE, Sugar mostrerà una opzione di \"Disconnessione\"." -#: ../data/sugar.schemas.in.h:17 +#: ../data/sugar.schemas.in.h:32 +msgid "If TRUE, Sugar will show a \"Restart\" option." +msgstr "Se TRUE, Sugar mostrerà una opzione di \"Riavvia\"." + +#: ../data/sugar.schemas.in.h:33 +msgid "" +"If TRUE, Sugar will show default Ad-hoc networks for channel 1,6 and 11. If " +"Sugar sees no \"known\" network when it starts, it does autoconnect to an Ad-" +"hoc network." +msgstr "" + +#: ../data/sugar.schemas.in.h:34 msgid "Jabber Server" msgstr "Jabber Server" -#: ../data/sugar.schemas.in.h:18 +#: ../data/sugar.schemas.in.h:35 msgid "Keyboard layouts" msgstr "Disposizioni tastiera" -#: ../data/sugar.schemas.in.h:19 +#: ../data/sugar.schemas.in.h:36 msgid "Keyboard model" msgstr "Modello tastiera" -#: ../data/sugar.schemas.in.h:20 +#: ../data/sugar.schemas.in.h:37 msgid "Keyboard options" msgstr "Opzioni tastiera" -#: ../data/sugar.schemas.in.h:21 +#: ../data/sugar.schemas.in.h:38 msgid "Layout of the favorites view." msgstr "Disposizione della vista dei favoriti." -#: ../data/sugar.schemas.in.h:22 +#: ../data/sugar.schemas.in.h:39 msgid "" "List of keyboard layouts. Each entry should be in the form layout(variant)" msgstr "" "Elenco di disposizioni della tastiera. Ogni voce deve essere nel formato " -"layout(variant) " +"layout(variant)" -#: ../data/sugar.schemas.in.h:23 +#: ../data/sugar.schemas.in.h:40 msgid "List of keyboard options." msgstr "Lista di opzioni per la tastiera." -#: ../data/sugar.schemas.in.h:24 +#: ../data/sugar.schemas.in.h:41 msgid "Power Automatic" msgstr "Economizzazione Automatica" -#: ../data/sugar.schemas.in.h:25 +#: ../data/sugar.schemas.in.h:42 msgid "Power Automatic." msgstr "Economizzazione Automatica." -#: ../data/sugar.schemas.in.h:26 +#: ../data/sugar.schemas.in.h:43 msgid "Power Extreme" msgstr "Economizzazione Massima" -#: ../data/sugar.schemas.in.h:27 +#: ../data/sugar.schemas.in.h:44 msgid "Power Extreme." msgstr "Economizzazione Massima." -#: ../data/sugar.schemas.in.h:28 +#: ../data/sugar.schemas.in.h:45 msgid "Publish to Gadget" msgstr "Pubblica su Gadget" -#: ../data/sugar.schemas.in.h:29 +#: ../data/sugar.schemas.in.h:46 msgid "Setting for muting the sound device." msgstr "Selezione per silenziare il riproduttore sonoro." -#: ../data/sugar.schemas.in.h:30 +#: ../data/sugar.schemas.in.h:47 msgid "Show Log out" msgstr "Mostra Disconnessione" -#: ../data/sugar.schemas.in.h:31 +#: ../data/sugar.schemas.in.h:48 +msgid "Show Restart" +msgstr "Mostra Riavvia" + +#: ../data/sugar.schemas.in.h:49 +msgid "Show Sugar Ad-hoc networks" +msgstr "Mostra rete ad-hoc di Sugar" + +#: ../data/sugar.schemas.in.h:50 msgid "Sound Muted" msgstr "Suono Silenziato" -#: ../data/sugar.schemas.in.h:32 +#: ../data/sugar.schemas.in.h:51 msgid "The keyboard model to be used" msgstr "Modello di tastiera da utilizzare" -#: ../data/sugar.schemas.in.h:34 +#: ../data/sugar.schemas.in.h:53 msgid "Timezone setting for the system." -msgstr "Selezione per il Timezone del sistema" +msgstr "Selezione per il Timezone del sistema." -#: ../data/sugar.schemas.in.h:35 +#: ../data/sugar.schemas.in.h:54 msgid "Url of the jabber server to use." msgstr "Url del server jabber da utilizzare." -#: ../data/sugar.schemas.in.h:36 +#: ../data/sugar.schemas.in.h:55 msgid "Url where the backup is saved to." msgstr "Url su cui effettuare i salvataggi." -#: ../data/sugar.schemas.in.h:37 +#: ../data/sugar.schemas.in.h:56 msgid "User Color" -msgstr "Colore dell'Utente " +msgstr "Colore dell'Utente" -#: ../data/sugar.schemas.in.h:38 +#: ../data/sugar.schemas.in.h:57 msgid "User Name" msgstr "Nome Utente" -#: ../data/sugar.schemas.in.h:39 +#: ../data/sugar.schemas.in.h:58 msgid "User name that is used throughout the desktop." msgstr "Nome Utente utilizzato in tutto il sistema." -#: ../data/sugar.schemas.in.h:40 +#: ../data/sugar.schemas.in.h:59 +msgid "" +"Users will not be allowed to erase these activities through the list view." +msgstr "" +"Gli utenti non saranno abilitati alla cancellazione di queste attività dalla " +"Visualizzazione a lista" + +#: ../data/sugar.schemas.in.h:60 msgid "Volume Level" msgstr "Livello Volume" -#: ../data/sugar.schemas.in.h:41 +#: ../data/sugar.schemas.in.h:61 msgid "Volume level for the sound device." msgstr "Livello del volume del riproduttore di suoni." -#: ../data/sugar.schemas.in.h:42 +#: ../data/sugar.schemas.in.h:62 msgid "" "When in resume mode, clicking on a favorite icon will cause the last entry " "for that activity to be resumed." @@ -773,7 +936,7 @@ msgstr "sugar-control-panel: %s" # which must appear in the translated string (msgstr) as well. #. TRANS: Translators, there's a empty line at the end of this string, #. which must appear in the translated string (msgstr) as well. -#: ../src/jarabe/controlpanel/cmd.py:37 +#: ../src/jarabe/controlpanel/cmd.py:38 msgid "" "Usage: sugar-control-panel [ option ] key [ args ... ] \n" " Control for the sugar environment. \n" @@ -796,50 +959,52 @@ msgstr "" " -s key assegna il valore corrente alla \"key\" \n" " " -#: ../src/jarabe/controlpanel/cmd.py:50 +#: ../src/jarabe/controlpanel/cmd.py:52 msgid "To apply your changes you have to restart sugar.\n" msgstr "Per applicare le modifiche è necessario riavviare sugar.\n" -#: ../src/jarabe/controlpanel/gui.py:281 +#: ../src/jarabe/controlpanel/gui.py:285 +#: ../src/jarabe/journal/journaltoolbox.py:437 +#: ../src/jarabe/journal/volumestoolbar.py:158 msgid "Warning" msgstr "Attenzione" -#: ../src/jarabe/controlpanel/gui.py:282 -#: ../src/jarabe/controlpanel/sectionview.py:42 +#: ../src/jarabe/controlpanel/gui.py:286 +#: ../src/jarabe/controlpanel/sectionview.py:41 msgid "Changes require restart" msgstr "Le modifiche rendono necessario un riavvio" -#: ../src/jarabe/controlpanel/gui.py:285 +#: ../src/jarabe/controlpanel/gui.py:289 msgid "Cancel changes" msgstr "Annulla modifiche" -#: ../src/jarabe/controlpanel/gui.py:290 ../src/jarabe/desktop/homebox.py:70 +#: ../src/jarabe/controlpanel/gui.py:294 ../src/jarabe/desktop/homebox.py:72 msgid "Later" msgstr "Dopo" -#: ../src/jarabe/controlpanel/gui.py:294 +#: ../src/jarabe/controlpanel/gui.py:298 msgid "Restart now" msgstr "Riavvia adesso" -#: ../src/jarabe/controlpanel/toolbar.py:61 ../src/jarabe/intro/window.py:206 +#: ../src/jarabe/controlpanel/toolbar.py:63 ../src/jarabe/intro/window.py:211 msgid "Done" msgstr "Fatto" -#: ../src/jarabe/controlpanel/toolbar.py:121 -#: ../src/jarabe/desktop/favoritesview.py:333 +#: ../src/jarabe/controlpanel/toolbar.py:125 +#: ../src/jarabe/desktop/favoritesview.py:336 msgid "Ok" msgstr "Ok" -#: ../src/jarabe/desktop/activitieslist.py:236 +#: ../src/jarabe/desktop/activitieslist.py:230 #, python-format msgid "Version %s" msgstr "Versione %s" -#: ../src/jarabe/desktop/activitieslist.py:357 +#: ../src/jarabe/desktop/activitieslist.py:354 msgid "Confirm erase" msgstr "Conferma cancellazione" -#: ../src/jarabe/desktop/activitieslist.py:359 +#: ../src/jarabe/desktop/activitieslist.py:356 #, python-format msgid "Confirm erase: Do you want to permanently erase %s?" msgstr "" @@ -849,255 +1014,264 @@ msgstr "" # 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:363 -#: ../src/jarabe/frame/clipboardmenu.py:63 -#: ../src/jarabe/view/viewsource.py:218 +#: ../src/jarabe/desktop/activitieslist.py:360 +#: ../src/jarabe/frame/clipboardmenu.py:64 +#: ../src/jarabe/view/viewsource.py:221 msgid "Keep" msgstr "Memorizza" -#: ../src/jarabe/desktop/activitieslist.py:366 -#: ../src/jarabe/desktop/activitieslist.py:409 -#: ../src/jarabe/journal/journaltoolbox.py:360 -#: ../src/jarabe/journal/palettes.py:105 +#: ../src/jarabe/desktop/activitieslist.py:363 +#: ../src/jarabe/desktop/activitieslist.py:417 +#: ../src/jarabe/journal/journaltoolbox.py:391 +#: ../src/jarabe/journal/palettes.py:112 msgid "Erase" msgstr "Elimina" -#: ../src/jarabe/desktop/activitieslist.py:430 +#: ../src/jarabe/desktop/activitieslist.py:432 msgid "Remove favorite" msgstr "Rimuovi preferito" -#: ../src/jarabe/desktop/activitieslist.py:434 +#: ../src/jarabe/desktop/activitieslist.py:436 msgid "Make favorite" msgstr "Definisci preferito" # 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 +#: ../src/jarabe/desktop/favoriteslayout.py:127 msgid "Freeform" msgstr "Formato libero" # 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:198 +#: ../src/jarabe/desktop/favoriteslayout.py:215 msgid "Ring" msgstr "Anello" # 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:337 +#: ../src/jarabe/desktop/favoriteslayout.py:402 msgid "Spiral" msgstr "Spirale" # 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:404 +#: ../src/jarabe/desktop/favoriteslayout.py:472 msgid "Box" msgstr "Scatola" # 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:445 +#: ../src/jarabe/desktop/favoriteslayout.py:515 msgid "Triangle" msgstr "Triangolo" -#: ../src/jarabe/desktop/favoritesview.py:324 +#: ../src/jarabe/desktop/favoritesview.py:327 msgid "Registration Failed" msgstr "Registrazione Fallita" -#: ../src/jarabe/desktop/favoritesview.py:325 +#: ../src/jarabe/desktop/favoritesview.py:328 #, python-format msgid "%s" msgstr "%s" -#: ../src/jarabe/desktop/favoritesview.py:327 +#: ../src/jarabe/desktop/favoritesview.py:330 msgid "Registration Successful" msgstr "Registrazione Effettuata" -#: ../src/jarabe/desktop/favoritesview.py:328 +#: ../src/jarabe/desktop/favoritesview.py:331 msgid "You are now registered with your school server." -msgstr "Ora sei registrato sul tuo server di scuola" +msgstr "Ora sei registrato sul tuo server di scuola." -#: ../src/jarabe/desktop/favoritesview.py:630 +#: ../src/jarabe/desktop/favoritesview.py:626 msgid "Register" msgstr "Registra" -#: ../src/jarabe/desktop/homebox.py:63 +#: ../src/jarabe/desktop/homebox.py:65 msgid "Software Update" msgstr "Aggiornamento software" -#: ../src/jarabe/desktop/homebox.py:64 +#: ../src/jarabe/desktop/homebox.py:66 msgid "Update your activities to ensure compatibility with your new software" msgstr "" "Aggiorna le tue attività perchè siano compatibili con il tuo sistema " -"aggiornato." +"aggiornato" -#: ../src/jarabe/desktop/homebox.py:73 +#: ../src/jarabe/desktop/homebox.py:75 msgid "Check now" msgstr "Verifica adesso" -#: ../src/jarabe/desktop/homebox.py:192 +#: ../src/jarabe/desktop/homebox.py:193 msgid "List view" msgstr "Vista Elenco" -#: ../src/jarabe/desktop/homebox.py:193 +#: ../src/jarabe/desktop/homebox.py:194 msgid "<Ctrl>2" msgstr "<Ctrl>2" -#: ../src/jarabe/desktop/homebox.py:255 +#: ../src/jarabe/desktop/homebox.py:257 msgid "Favorites view" msgstr "Visualizza i Preferiti" -#: ../src/jarabe/desktop/homebox.py:256 +#: ../src/jarabe/desktop/homebox.py:258 msgid "<Ctrl>1" msgstr "<Ctrl>1" -#: ../src/jarabe/desktop/keydialog.py:135 +#: ../src/jarabe/desktop/keydialog.py:143 msgid "Key Type:" msgstr "Tipo Chiave:" -#: ../src/jarabe/desktop/keydialog.py:155 +#: ../src/jarabe/desktop/keydialog.py:163 msgid "Authentication Type:" msgstr "Tipo di Autenticazione:" -#: ../src/jarabe/desktop/keydialog.py:220 +#: ../src/jarabe/desktop/keydialog.py:229 msgid "WPA & WPA2 Personal" msgstr "WPA & WPA2 Personal" -#: ../src/jarabe/desktop/keydialog.py:229 +#: ../src/jarabe/desktop/keydialog.py:238 msgid "Wireless Security:" msgstr "Wireless Security:" -# A complete translation in italian: "rete a maglie" becames a tautology -#: ../src/jarabe/desktop/meshbox.py:492 -#, python-format -msgid "Mesh Network %d" -msgstr "Rete Mesh %d" - # TRANS: Action label for resuming an activity. #. TRANS: Action label for resuming an activity. -#: ../src/jarabe/desktop/meshbox.py:629 -#: ../src/jarabe/frame/activitiestray.py:735 -#: ../src/jarabe/journal/journaltoolbox.py:428 -#: ../src/jarabe/journal/palettes.py:65 ../src/jarabe/view/palettes.py:67 +#: ../src/jarabe/desktop/meshbox.py:109 +#: ../src/jarabe/frame/activitiestray.py:622 +#: ../src/jarabe/journal/journaltoolbox.py:485 +#: ../src/jarabe/journal/palettes.py:66 ../src/jarabe/view/palettes.py:78 msgid "Resume" msgstr "Riprendi" -#: ../src/jarabe/desktop/meshbox.py:634 -#: ../src/jarabe/frame/activitiestray.py:233 +#: ../src/jarabe/desktop/meshbox.py:114 +#: ../src/jarabe/frame/activitiestray.py:173 msgid "Join" msgstr "Associa" -#: ../src/jarabe/desktop/schoolserver.py:103 +#: ../src/jarabe/desktop/networkviews.py:489 +#, python-format +msgid "Ad-hoc Network %d" +msgstr "Rete Ad-hoc %d" + +# A complete translation in italian: "rete a maglie" becames a tautology +#: ../src/jarabe/desktop/networkviews.py:622 +#, python-format +msgid "Mesh Network %d" +msgstr "Rete Mesh %d" + +#: ../src/jarabe/desktop/schoolserver.py:131 msgid "Cannot connect to the server." msgstr "Impossibile connettersi al server." -#: ../src/jarabe/desktop/schoolserver.py:108 +#: ../src/jarabe/desktop/schoolserver.py:136 msgid "The server could not complete the request." msgstr "Il server non può completare la richiesta." -#: ../src/jarabe/frame/activitiestray.py:238 -#: ../src/jarabe/frame/activitiestray.py:672 +#: ../src/jarabe/frame/activitiestray.py:178 +#: ../src/jarabe/frame/activitiestray.py:559 msgid "Decline" msgstr "Rinuncia" -#: ../src/jarabe/frame/activitiestray.py:624 +#: ../src/jarabe/frame/activitiestray.py:509 #, python-format msgid "%dB" msgstr "%dB" -#: ../src/jarabe/frame/activitiestray.py:626 +#: ../src/jarabe/frame/activitiestray.py:511 #, python-format msgid "%dKB" msgstr "%dKB" -#: ../src/jarabe/frame/activitiestray.py:628 +#: ../src/jarabe/frame/activitiestray.py:513 #, python-format msgid "%dMB" msgstr "%dMB" -#: ../src/jarabe/frame/activitiestray.py:645 +#: ../src/jarabe/frame/activitiestray.py:530 #, python-format msgid "%s of %s" msgstr "%s di %s" -#: ../src/jarabe/frame/activitiestray.py:657 +#: ../src/jarabe/frame/activitiestray.py:544 #, python-format msgid "Transfer from %r" msgstr "Trasferisci da %r" -#: ../src/jarabe/frame/activitiestray.py:667 +#: ../src/jarabe/frame/activitiestray.py:554 msgid "Accept" msgstr "Accetta" -#: ../src/jarabe/frame/activitiestray.py:690 -#: ../src/jarabe/frame/activitiestray.py:817 +#: ../src/jarabe/frame/activitiestray.py:577 +#: ../src/jarabe/frame/activitiestray.py:705 #, python-format msgid "%s (%s)" msgstr "%s (%s)" -#: ../src/jarabe/frame/activitiestray.py:724 -#: ../src/jarabe/frame/activitiestray.py:852 +#: ../src/jarabe/frame/activitiestray.py:611 +#: ../src/jarabe/frame/activitiestray.py:740 msgid "Dismiss" msgstr "Abbandona" -#: ../src/jarabe/frame/activitiestray.py:787 +#: ../src/jarabe/frame/activitiestray.py:675 #, python-format msgid "Transfer to %r" msgstr "Trasferisci verso %r" -#: ../src/jarabe/frame/clipboardmenu.py:53 ../src/jarabe/view/palettes.py:221 +#: ../src/jarabe/frame/clipboardmenu.py:54 ../src/jarabe/view/palettes.py:220 msgid "Remove" msgstr "Rimuovi" -#: ../src/jarabe/frame/clipboardmenu.py:58 -#: ../src/jarabe/frame/clipboardmenu.py:81 +#: ../src/jarabe/frame/clipboardmenu.py:59 +#: ../src/jarabe/frame/clipboardmenu.py:82 msgid "Open" msgstr "Apri" -#: ../src/jarabe/frame/clipboardmenu.py:86 +#: ../src/jarabe/frame/clipboardmenu.py:87 msgid "Open with" msgstr "Apri con" -#: ../src/jarabe/frame/clipboardobject.py:49 +#: ../src/jarabe/frame/clipboardobject.py:50 #, python-format msgid "%s clipping" msgstr "ritaglio %s" # Letterale "Vicinato", sperimentale: I miei vicini -#: ../src/jarabe/frame/zoomtoolbar.py:37 +#: ../src/jarabe/frame/zoomtoolbar.py:38 msgid "Neighborhood" msgstr "I miei vicini" -#: ../src/jarabe/frame/zoomtoolbar.py:37 +#: ../src/jarabe/frame/zoomtoolbar.py:38 msgid "F1" msgstr "F1" -#: ../src/jarabe/frame/zoomtoolbar.py:39 +#: ../src/jarabe/frame/zoomtoolbar.py:40 msgid "F2" msgstr "F2" -#: ../src/jarabe/frame/zoomtoolbar.py:41 +#: ../src/jarabe/frame/zoomtoolbar.py:42 msgid "F3" msgstr "F3" -#: ../src/jarabe/frame/zoomtoolbar.py:43 +#: ../src/jarabe/frame/zoomtoolbar.py:44 msgid "F4" msgstr "F4" -#: ../src/jarabe/intro/window.py:128 +#: ../src/jarabe/intro/window.py:96 +msgid "Name:" +msgstr "Nome:" + +#: ../src/jarabe/intro/window.py:132 msgid "Click to change color:" msgstr "Seleziona per cambiare colore:" -#: ../src/jarabe/intro/window.py:192 ../src/jarabe/journal/detailview.py:103 +#: ../src/jarabe/intro/window.py:197 ../src/jarabe/journal/detailview.py:105 msgid "Back" msgstr "Indietro" -#: ../src/jarabe/intro/window.py:209 +#: ../src/jarabe/intro/window.py:214 msgid "Next" msgstr "Prossimo" -#: ../src/jarabe/journal/expandedentry.py:152 -#: ../src/jarabe/journal/palettes.py:59 +#: ../src/jarabe/journal/expandedentry.py:154 +#: ../src/jarabe/journal/listmodel.py:144 ../src/jarabe/journal/palettes.py:59 msgid "Untitled" msgstr "Senza titolo" @@ -1111,6 +1285,9 @@ msgid "Kind: %s" msgstr "Tipo: %s" #: ../src/jarabe/journal/expandedentry.py:262 +#: ../src/jarabe/journal/listmodel.py:150 +#: ../src/jarabe/journal/listmodel.py:157 +#: ../src/jarabe/journal/listmodel.py:165 msgid "Unknown" msgstr "Sconosciuto" @@ -1124,114 +1301,158 @@ msgstr "Data: %s" msgid "Size: %s" msgstr "Dimensione: %s" -#: ../src/jarabe/journal/expandedentry.py:286 ../src/jarabe/journal/misc.py:93 +#: ../src/jarabe/journal/expandedentry.py:292 +#: ../src/jarabe/journal/misc.py:108 msgid "No date" msgstr "Nessuna data" -#: ../src/jarabe/journal/expandedentry.py:293 +#: ../src/jarabe/journal/expandedentry.py:299 msgid "Participants:" msgstr "Partecipanti:" -#: ../src/jarabe/journal/expandedentry.py:316 +#: ../src/jarabe/journal/expandedentry.py:321 msgid "Description:" msgstr "Descrizione:" -#: ../src/jarabe/journal/expandedentry.py:341 +#: ../src/jarabe/journal/expandedentry.py:346 msgid "Tags:" msgstr "Etichette:" -#: ../src/jarabe/journal/journalactivity.py:108 -#: ../src/jarabe/journal/volumestoolbar.py:47 +#: ../src/jarabe/journal/journalactivity.py:115 +#: ../src/jarabe/journal/journaltoolbox.py:456 +#: ../src/jarabe/journal/volumestoolbar.py:50 msgid "Journal" msgstr "Diario" -#: ../src/jarabe/journal/journaltoolbox.py:67 +#: ../src/jarabe/journal/journaltoolbox.py:69 msgid "Search" msgstr "Cerca" -#: ../src/jarabe/journal/journaltoolbox.py:126 +#: ../src/jarabe/journal/journaltoolbox.py:136 msgid "Anytime" msgstr "Sempre" -#: ../src/jarabe/journal/journaltoolbox.py:128 +#: ../src/jarabe/journal/journaltoolbox.py:138 msgid "Today" msgstr "Oggi" -#: ../src/jarabe/journal/journaltoolbox.py:130 +#: ../src/jarabe/journal/journaltoolbox.py:140 msgid "Since yesterday" msgstr "Da ieri" # TRANS: Filter entries modified during the last 7 days. #. TRANS: Filter entries modified during the last 7 days. -#: ../src/jarabe/journal/journaltoolbox.py:132 +#: ../src/jarabe/journal/journaltoolbox.py:142 msgid "Past week" msgstr "Settimana scorsa" # TRANS: Filter entries modified during the last 30 days. #. TRANS: Filter entries modified during the last 30 days. -#: ../src/jarabe/journal/journaltoolbox.py:134 +#: ../src/jarabe/journal/journaltoolbox.py:144 msgid "Past month" msgstr "Mese scorso" # TRANS: Filter entries modified during the last 356 days. #. TRANS: Filter entries modified during the last 356 days. -#: ../src/jarabe/journal/journaltoolbox.py:136 +#: ../src/jarabe/journal/journaltoolbox.py:146 msgid "Past year" msgstr "Anno scorso" -#: ../src/jarabe/journal/journaltoolbox.py:143 +#: ../src/jarabe/journal/journaltoolbox.py:153 msgid "Anyone" msgstr "Tutti" -#: ../src/jarabe/journal/journaltoolbox.py:145 +#: ../src/jarabe/journal/journaltoolbox.py:155 msgid "My friends" msgstr "I miei amici" -#: ../src/jarabe/journal/journaltoolbox.py:146 +#: ../src/jarabe/journal/journaltoolbox.py:156 msgid "My class" msgstr "La mia classe" # TRANS: Item in a combo box that filters by entry type. -#: ../src/jarabe/journal/journaltoolbox.py:274 +#: ../src/jarabe/journal/journaltoolbox.py:298 msgid "Anything" msgstr "Qualsiasi" # TODO: Add "Start with" menu item -#: ../src/jarabe/journal/journaltoolbox.py:350 -#: ../src/jarabe/journal/palettes.py:83 +#: ../src/jarabe/journal/journaltoolbox.py:381 +#: ../src/jarabe/journal/palettes.py:90 msgid "Copy" msgstr "Copia" +#: ../src/jarabe/journal/journaltoolbox.py:436 +#: ../src/jarabe/journal/volumestoolbar.py:157 +msgid "Entries without a file cannot be copied." +msgstr "Elementi senza file non possono essere copiati" + +#: ../src/jarabe/journal/journaltoolbox.py:445 +#: ../src/jarabe/journal/volumestoolbar.py:166 +#, python-format +msgid "Error while copying the entry. %s" +msgstr "Errore durante la copia dell'elemento. %s" + +#: ../src/jarabe/journal/journaltoolbox.py:446 +#: ../src/jarabe/journal/volumestoolbar.py:167 +msgid "Error" +msgstr "Errore" + # TRANS: Action label for starting an entry. #. TRANS: Action label for starting an entry. -#: ../src/jarabe/journal/journaltoolbox.py:431 -#: ../src/jarabe/journal/palettes.py:68 +#: ../src/jarabe/journal/journaltoolbox.py:488 +#: ../src/jarabe/journal/palettes.py:69 msgid "Start" msgstr "Avvia" -#: ../src/jarabe/journal/listview.py:373 +#: ../src/jarabe/journal/journaltoolbox.py:516 +msgid "Sort by date modified" +msgstr "" + +#: ../src/jarabe/journal/journaltoolbox.py:517 +msgid "Sort by date created" +msgstr "" + +#: ../src/jarabe/journal/journaltoolbox.py:518 +msgid "Sort by size" +msgstr "" + +#: ../src/jarabe/journal/journaltoolbox.py:527 +msgid "Sort view" +msgstr "" + +#: ../src/jarabe/journal/listview.py:380 msgid "Your Journal is empty" msgstr "Il tuo Diario è vuoto" -#: ../src/jarabe/journal/listview.py:375 +#: ../src/jarabe/journal/listview.py:382 msgid "No matching entries" msgstr "Non ci sono registrazioni corrispondenti" -#: ../src/jarabe/journal/listview.py:386 +#: ../src/jarabe/journal/listview.py:393 msgid "Clear search" msgstr "Annulla ricerca" -#: ../src/jarabe/journal/modalalert.py:63 +#: ../src/jarabe/journal/misc.py:273 +#, python-format +msgid "Older Version Of %s Activity" +msgstr "" + +#: ../src/jarabe/journal/misc.py:274 +#, python-format +msgid "Do you want to downgrade to version %s " +msgstr "" + +#: ../src/jarabe/journal/modalalert.py:64 msgid "Your Journal is full" msgstr "Il tuo Diario è pieno" -#: ../src/jarabe/journal/modalalert.py:67 +#: ../src/jarabe/journal/modalalert.py:68 msgid "Please delete some old Journal entries to make space for new ones." msgstr "" "Per favore cancella alcune registrazioni vecchie dal Diario per far spazio " "alle nuove." -#: ../src/jarabe/journal/modalalert.py:79 +#: ../src/jarabe/journal/modalalert.py:80 msgid "Show Journal" msgstr "Apri il Diario" @@ -1240,110 +1461,311 @@ msgid "Choose an object" msgstr "Scegli un oggetto" #: ../src/jarabe/journal/objectchooser.py:151 -#: ../src/jarabe/view/viewsource.py:308 +#: ../src/jarabe/view/viewsource.py:311 msgid "Close" msgstr "Chiudi" -#: ../src/jarabe/journal/palettes.py:66 +#: ../src/jarabe/journal/palettes.py:67 msgid "Resume with" msgstr "Riprendi con" -#: ../src/jarabe/journal/palettes.py:69 +#: ../src/jarabe/journal/palettes.py:70 msgid "Start with" msgstr "Inizia con" -#: ../src/jarabe/journal/palettes.py:91 +#: ../src/jarabe/journal/palettes.py:83 ../src/jarabe/journal/palettes.py:216 +msgid "No activity to start entry" +msgstr "Attività per riprendere la sessione non presente" + +#: ../src/jarabe/journal/palettes.py:98 msgid "Send to" msgstr "Invia a" -#: ../src/jarabe/journal/palettes.py:100 +#: ../src/jarabe/journal/palettes.py:107 msgid "View Details" msgstr "Visualizza Dettagli" -#: ../src/jarabe/journal/palettes.py:178 +#: ../src/jarabe/journal/palettes.py:181 msgid "No friends present" msgstr "Non ci sono amici presenti" -#: ../src/jarabe/journal/palettes.py:183 +#: ../src/jarabe/journal/palettes.py:186 msgid "No valid connection found" msgstr "Connessione non trovata" -#: ../src/jarabe/journal/palettes.py:211 +#: ../src/jarabe/journal/palettes.py:214 msgid "No activity to resume entry" msgstr "Sessione dell'Attività da riprendere non presente" -#: ../src/jarabe/journal/palettes.py:213 -msgid "No activity to start entry" -msgstr "Attività per riprendere la sessione non presente" +#: ../src/jarabe/model/network.py:158 +msgid "The reason for the device state change is unknown." +msgstr "" + +#: ../src/jarabe/model/network.py:160 +msgid "The state change is normal." +msgstr "" + +#: ../src/jarabe/model/network.py:162 +msgid "The device is now managed." +msgstr "" + +#: ../src/jarabe/model/network.py:164 +msgid "The device is no longer managed." +msgstr "" + +#: ../src/jarabe/model/network.py:166 +msgid "The device could not be readied for configuration." +msgstr "" + +#: ../src/jarabe/model/network.py:168 +msgid "" +"IP configuration could not be reserved (no available address, timeout, etc)." +msgstr "" + +#: ../src/jarabe/model/network.py:171 +msgid "The IP configuration is no longer valid." +msgstr "" + +#: ../src/jarabe/model/network.py:173 +msgid "Secrets were required, but not provided." +msgstr "" + +#: ../src/jarabe/model/network.py:175 +msgid "" +"The 802.1X supplicant disconnected from the access point or authentication " +"server." +msgstr "" + +#: ../src/jarabe/model/network.py:178 +msgid "Configuration of the 802.1X supplicant failed." +msgstr "" + +#: ../src/jarabe/model/network.py:180 +msgid "The 802.1X supplicant quit or failed unexpectedly." +msgstr "" + +#: ../src/jarabe/model/network.py:182 +msgid "The 802.1X supplicant took too long to authenticate." +msgstr "" -#: ../src/jarabe/view/buddymenu.py:62 +#: ../src/jarabe/model/network.py:184 +msgid "The PPP service failed to start within the allowed time." +msgstr "" + +#: ../src/jarabe/model/network.py:186 +msgid "The PPP service disconnected unexpectedly." +msgstr "" + +#: ../src/jarabe/model/network.py:188 +msgid "The PPP service quit or failed unexpectedly." +msgstr "" + +#: ../src/jarabe/model/network.py:190 +msgid "The DHCP service failed to start within the allowed time." +msgstr "" + +#: ../src/jarabe/model/network.py:192 +msgid "The DHCP service reported an unexpected error." +msgstr "" + +#: ../src/jarabe/model/network.py:194 +msgid "The DHCP service quit or failed unexpectedly." +msgstr "" + +#: ../src/jarabe/model/network.py:196 +msgid "The shared connection service failed to start." +msgstr "" + +#: ../src/jarabe/model/network.py:198 +msgid "The shared connection service quit or failed unexpectedly." +msgstr "" + +#: ../src/jarabe/model/network.py:201 +msgid "The AutoIP service failed to start." +msgstr "" + +#: ../src/jarabe/model/network.py:203 +msgid "The AutoIP service reported an unexpected error." +msgstr "" + +#: ../src/jarabe/model/network.py:205 +msgid "The AutoIP service quit or failed unexpectedly." +msgstr "" + +#: ../src/jarabe/model/network.py:207 +msgid "Dialing failed because the line was busy." +msgstr "" + +#: ../src/jarabe/model/network.py:209 +msgid "Dialing failed because there was no dial tone." +msgstr "" + +#: ../src/jarabe/model/network.py:211 +msgid "Dialing failed because there was no carrier." +msgstr "" + +#: ../src/jarabe/model/network.py:213 +msgid "Dialing timed out." +msgstr "" + +#: ../src/jarabe/model/network.py:215 +msgid "Dialing failed." +msgstr "" + +#: ../src/jarabe/model/network.py:217 +msgid "Modem initialization failed." +msgstr "" + +#: ../src/jarabe/model/network.py:219 +msgid "Failed to select the specified GSM APN" +msgstr "" + +#: ../src/jarabe/model/network.py:221 +msgid "Not searching for networks." +msgstr "" + +#: ../src/jarabe/model/network.py:223 +msgid "Network registration was denied." +msgstr "" + +#: ../src/jarabe/model/network.py:225 +msgid "Network registration timed out." +msgstr "" + +#: ../src/jarabe/model/network.py:227 +msgid "Failed to register with the requested GSM network." +msgstr "" + +#: ../src/jarabe/model/network.py:229 +msgid "PIN check failed." +msgstr "" + +#: ../src/jarabe/model/network.py:231 +msgid "Necessary firmware for the device may be missing." +msgstr "" + +#: ../src/jarabe/model/network.py:233 +msgid "The device was removed." +msgstr "" + +#: ../src/jarabe/model/network.py:235 +msgid "NetworkManager went to sleep." +msgstr "" + +#: ../src/jarabe/model/network.py:237 +msgid "The device's active connection was removed or disappeared." +msgstr "" + +#: ../src/jarabe/model/network.py:240 +msgid "A user or client requested the disconnection." +msgstr "" + +#: ../src/jarabe/model/network.py:242 +msgid "The device's carrier/link changed." +msgstr "" + +#: ../src/jarabe/view/buddymenu.py:63 msgid "Remove friend" msgstr "Rimuovi l'amico" -#: ../src/jarabe/view/buddymenu.py:65 +#: ../src/jarabe/view/buddymenu.py:66 msgid "Make friend" msgstr "Aggiungi agli amici" -#: ../src/jarabe/view/buddymenu.py:82 +#: ../src/jarabe/view/buddymenu.py:83 msgid "Shutdown" msgstr "Spegni" -#: ../src/jarabe/view/buddymenu.py:90 +#: ../src/jarabe/view/buddymenu.py:91 +msgid "Restart" +msgstr "Riavvia" + +#: ../src/jarabe/view/buddymenu.py:97 msgid "Logout" msgstr "Disconnessione" -#: ../src/jarabe/view/buddymenu.py:95 +#: ../src/jarabe/view/buddymenu.py:102 msgid "My Settings" msgstr "Le mie Preferenze" -#: ../src/jarabe/view/buddymenu.py:130 +#: ../src/jarabe/view/buddymenu.py:137 #, python-format msgid "Invite to %s" msgstr "Invito per %s" -#: ../src/jarabe/view/palettes.py:45 +#: ../src/jarabe/view/launcher.py:190 +#, python-format +msgid "<b>%s</b> failed to start." +msgstr "" + +#: ../src/jarabe/view/palettes.py:46 msgid "Starting..." msgstr "Inizio..." +#: ../src/jarabe/view/palettes.py:56 +msgid "Activity failed to start" +msgstr "" + #. TODO: share-with, keep -#: ../src/jarabe/view/palettes.py:74 +#: ../src/jarabe/view/palettes.py:85 msgid "View Source" msgstr "Visualizza Sorgente" -#: ../src/jarabe/view/palettes.py:85 +#: ../src/jarabe/view/palettes.py:96 msgid "Stop" msgstr "Chiudi" -#: ../src/jarabe/view/palettes.py:125 +#: ../src/jarabe/view/palettes.py:132 msgid "Start new" msgstr "Inizia nuovo" -#: ../src/jarabe/view/palettes.py:174 +#: ../src/jarabe/view/palettes.py:172 msgid "Show contents" msgstr "Mostra i contenuti" -#: ../src/jarabe/view/palettes.py:196 ../src/jarabe/view/palettes.py:246 +#: ../src/jarabe/view/palettes.py:194 ../src/jarabe/view/palettes.py:245 #, python-format msgid "%(free_space)d MB Free" msgstr "%(free_space)d MB Liberi" -#: ../src/jarabe/view/viewsource.py:208 +#: ../src/jarabe/view/viewsource.py:211 msgid "Instance Source" msgstr "Sorgente Istanza" -#: ../src/jarabe/view/viewsource.py:233 +#: ../src/jarabe/view/viewsource.py:236 msgid "Source" msgstr "Sorgente" -#: ../src/jarabe/view/viewsource.py:292 +#: ../src/jarabe/view/viewsource.py:295 msgid "Activity Bundle Source" msgstr "Sorgente della Attività" -#: ../src/jarabe/view/viewsource.py:299 +#: ../src/jarabe/view/viewsource.py:302 #, python-format msgid "View source: %r" msgstr "Vedi codice sorgente: %r" +#: ../src/jarabe/util/emulator.py:40 +msgid "Sugar in a window" +msgstr "" + +#~ msgid "APN:" +#~ msgstr "APN:" + +#~ msgid "Create new wireless network" +#~ msgstr "Crea una nuova rete wireless" + +#, python-format +#~ msgid "%s's network" +#~ msgstr "rete di %s" + +#, python-format +#~ msgid "Data sent %d kb / received %d kb" +#~ msgstr "Dati %d kb inviati / %d kb ricevuti" + +#~ msgid "Connection time " +#~ msgstr "Tempo di connessione" + #~ msgid "Title" #~ msgstr "Titolo" @@ -1359,9 +1781,6 @@ msgstr "Vedi codice sorgente: %r" #~ msgid "Unmount" #~ msgstr "Rimuovi" -#~ msgid "Restart" -#~ msgstr "Riavvia" - #~ msgid "" #~ "© 2008 One Laptop per Child Association Inc; Red Hat Inc; and Contributors." #~ msgstr "" @@ -2,12 +2,16 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2009-08-26 00:31-0400\n" -"PO-Revision-Date: 2009-09-05 07:25-0400\n" +"POT-Creation-Date: 2010-06-08 00:31-0400\n" +"PO-Revision-Date: 2010-08-07 21:55+0200\n" "Last-Translator: Myckel Habets <myckel@sdf.lonestar.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "Language: nl\n" @@ -15,7 +19,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Pootle 1.2.1\n" +"X-Generator: Pootle 2.0.3\n" #: ../extensions/cpsection/aboutme/__init__.py:24 msgid "About Me" @@ -53,7 +57,7 @@ msgstr "Fout in opgegeven kleurenmodificaties." msgid "Error in specified colors." msgstr "Fout in opgegeven kleuren." -#: ../extensions/cpsection/aboutme/view.py:94 ../src/jarabe/intro/window.py:92 +#: ../extensions/cpsection/aboutme/view.py:94 ../src/jarabe/intro/window.py:93 msgid "Name:" msgstr "Naam:" @@ -125,7 +129,7 @@ msgstr "Datum en Tijd" msgid "Error timezone does not exist." msgstr "Fout tijdzone bestaat niet." -#: ../extensions/cpsection/datetime/view.py:68 ../data/sugar.schemas.in.h:27 +#: ../extensions/cpsection/datetime/view.py:68 ../data/sugar.schemas.in.h:47 msgid "Timezone" msgstr "Tijdzone" @@ -161,24 +165,24 @@ msgstr "Hoek" #: ../extensions/cpsection/frame/view.py:111 msgid "Edge" -msgstr "Ribbe" +msgstr "Rand" #: ../extensions/cpsection/keyboard/__init__.py:21 #: ../extensions/cpsection/keyboard/view.py:31 msgid "Keyboard" msgstr "Toetsenbord" -#: ../extensions/cpsection/keyboard/view.py:187 +#: ../extensions/cpsection/keyboard/view.py:189 msgid "Keyboard Model" msgstr "Toetsenbordmodel" -#: ../extensions/cpsection/keyboard/view.py:243 +#: ../extensions/cpsection/keyboard/view.py:248 msgid "Key(s) to change layout" -msgstr "Toets(en) om de layout te veranderen" +msgstr "Toets(en) om de indeling te veranderen" -#: ../extensions/cpsection/keyboard/view.py:311 +#: ../extensions/cpsection/keyboard/view.py:318 msgid "Keyboard Layout(s)" -msgstr "Toetsenbordlayout(s)" +msgstr "Toetsenbordindeling(en)" #: ../extensions/cpsection/language/__init__.py:21 #: ../extensions/cpsection/language/view.py:33 @@ -197,7 +201,7 @@ msgstr "Taal voor code=%s kon niet bepaald worden." #: ../extensions/cpsection/language/model.py:144 #, python-format msgid "Sorry I do not speak '%s'." -msgstr "Sorry, I spreek geen '%s'." +msgstr "Sorry ik spreek geen '%s'." #: ../extensions/cpsection/language/view.py:56 msgid "" @@ -207,6 +211,42 @@ msgstr "" "Voeg talen toe in de volgorde die je wenst. Als een vertaling niet " "beschikbaar is, zal de volgende in de lijst gebruikt worden." +#: ../extensions/cpsection/modemconfiguration/__init__.py:21 +msgid "Modem Configuration" +msgstr "Modem configuratie" + +#: ../extensions/cpsection/modemconfiguration/view.py:91 +msgid "Username:" +msgstr "Gebruikersnaam:" + +#: ../extensions/cpsection/modemconfiguration/view.py:102 +msgid "Password:" +msgstr "Wachtwoord:" + +#: ../extensions/cpsection/modemconfiguration/view.py:113 +msgid "Number:" +msgstr "Getal:" + +#: ../extensions/cpsection/modemconfiguration/view.py:124 +msgid "Access Point Name (APN):" +msgstr "Access Point Naam (APN):" + +#: ../extensions/cpsection/modemconfiguration/view.py:135 +msgid "Personal Identity Number (PIN):" +msgstr "Persoonlijk Identiteitsnummer (PIN):" + +#: ../extensions/cpsection/modemconfiguration/view.py:146 +msgid "Personal Unblocking Key (PUK):" +msgstr "Persoonlijke Ontsluitingssleutel (PUK):" + +#: ../extensions/cpsection/modemconfiguration/view.py:167 +msgid "" +"You will need to provide the following information to set up a mobile " +"broadband connection to a cellular (3G) network." +msgstr "" +"Je moet de volgende informatie opgeven om een mobiele breedbandverbinding " +"via een mobiel (3G) netwerk op te zetten." + #: ../extensions/cpsection/network/__init__.py:21 #: ../extensions/cpsection/network/view.py:28 msgid "Network" @@ -256,9 +296,9 @@ msgid "" "server will be able to see each other, even when they aren't on the same " "network." msgstr "" -"De server is het equivalent van in welke ruimte je je bevindt; mensen op " +"De server is hetzelfde als de ruimte waar jij je in bevindt; mensen op " "dezelfde server kunnen elkaar zien, ook als ze niet op hetzelfde netwerk " -"zitten" +"zitten." #: ../extensions/cpsection/network/view.py:136 msgid "Server:" @@ -268,11 +308,11 @@ msgstr "Server:" msgid "Power" msgstr "Energie" -#: ../extensions/cpsection/power/model.py:54 +#: ../extensions/cpsection/power/model.py:85 msgid "Error in automatic pm argument, use on/off." msgstr "Fout in automatisch energiebeheer argument, gebruik on/off." -#: ../extensions/cpsection/power/model.py:81 +#: ../extensions/cpsection/power/model.py:112 msgid "Error in extreme pm argument, use on/off." msgstr "Fout in extreem energiebeheer argument, gebruik on/off." @@ -295,7 +335,7 @@ msgstr "" msgid "Software update" msgstr "Software-update" -#: ../extensions/cpsection/updater/view.py:62 +#: ../extensions/cpsection/updater/view.py:63 msgid "" "Software updates correct errors, eliminate security vulnerabilities, and " "provide new features." @@ -303,79 +343,79 @@ msgstr "" "Software-updates verbeteren fouten, verhelpen beveiligingsproblemen en " "bieden nieuwe mogelijkheden." -#: ../extensions/cpsection/updater/view.py:122 +#: ../extensions/cpsection/updater/view.py:125 #, python-format msgid "Checking %s..." msgstr "Controleren van %s..." -#: ../extensions/cpsection/updater/view.py:124 +#: ../extensions/cpsection/updater/view.py:127 #, python-format msgid "Downloading %s..." msgstr "Downloaden van %s..." -#: ../extensions/cpsection/updater/view.py:126 +#: ../extensions/cpsection/updater/view.py:129 #, python-format msgid "Updating %s..." msgstr "Updaten van %s..." -#: ../extensions/cpsection/updater/view.py:135 +#: ../extensions/cpsection/updater/view.py:139 msgid "Your software is up-to-date" msgstr "Je software is bijgewerkt" -#: ../extensions/cpsection/updater/view.py:137 +#: ../extensions/cpsection/updater/view.py:141 #, python-format msgid "You can install %s update" msgid_plural "You can install %s updates" msgstr[0] "Je kan %s update installeren" msgstr[1] "Je kan %s updates installeren" -#: ../extensions/cpsection/updater/view.py:155 +#: ../extensions/cpsection/updater/view.py:159 msgid "Checking for updates..." msgstr "Controleren op updates..." -#: ../extensions/cpsection/updater/view.py:160 +#: ../extensions/cpsection/updater/view.py:164 msgid "Installing updates..." msgstr "Updates installeren..." -#: ../extensions/cpsection/updater/view.py:165 +#: ../extensions/cpsection/updater/view.py:172 #, python-format msgid "%s update was installed" msgid_plural "%s updates were installed" msgstr[0] "%s update is geïnstalleerd" msgstr[1] "%s updates zijn geïnstalleerd" -#: ../extensions/cpsection/updater/view.py:244 +#: ../extensions/cpsection/updater/view.py:253 msgid "Install selected" msgstr "Installatie geselecteerd" -#: ../extensions/cpsection/updater/view.py:265 +#: ../extensions/cpsection/updater/view.py:274 #, python-format msgid "Download size: %s" msgstr "Downloadgrootte: %s" -#: ../extensions/cpsection/updater/view.py:353 +#: ../extensions/cpsection/updater/view.py:362 #, python-format msgid "From version %(current)d to %(new)s (Size: %(size)s)" msgstr "Van versie %(current)d naar %(new)s (Grootte: %(size)s)" #. TRANS: download size is 0 -#: ../extensions/cpsection/updater/view.py:373 +#: ../extensions/cpsection/updater/view.py:382 msgid "None" msgstr "Niets" #. TRANS: download size of very small updates -#: ../extensions/cpsection/updater/view.py:376 +#: ../extensions/cpsection/updater/view.py:385 msgid "1 KB" msgstr "1 KB" #. TRANS: download size of small updates, e.g. '250 KB' -#: ../extensions/cpsection/updater/view.py:379 +#: ../extensions/cpsection/updater/view.py:388 #, python-format msgid "%.0f KB" msgstr "%.0f KB" #. TRANS: download size of updates, e.g. '2.3 MB' -#: ../extensions/cpsection/updater/view.py:382 +#: ../extensions/cpsection/updater/view.py:391 #, python-format msgid "%.1f MB" msgstr "%.1f MB" @@ -405,47 +445,102 @@ msgstr "%(hour)d:%(min).2d over" msgid "Charged" msgstr "Opgeladen" -#: ../extensions/deviceicon/network.py:44 +#: ../extensions/deviceicon/network.py:49 #, python-format msgid "IP address: %s" msgstr "IP adres: %s" -#: ../extensions/deviceicon/network.py:110 +#: ../extensions/deviceicon/network.py:112 msgid "Disconnect..." msgstr "Verbinding verbreken..." -#: ../extensions/deviceicon/network.py:114 +#: ../extensions/deviceicon/network.py:117 msgid "Create new wireless network" msgstr "Nieuw draadloos netwerk aanmaken" -#: ../extensions/deviceicon/network.py:120 -#: ../src/jarabe/desktop/meshbox.py:261 +#: ../extensions/deviceicon/network.py:123 +#: ../extensions/deviceicon/network.py:285 +#: ../src/jarabe/desktop/meshbox.py:247 ../src/jarabe/desktop/meshbox.py:536 msgid "Connecting..." msgstr "Verbinden..." # TODO: show the channel number -#: ../extensions/deviceicon/network.py:124 -#: ../extensions/deviceicon/network.py:186 -#: ../src/jarabe/desktop/meshbox.py:267 +#: ../extensions/deviceicon/network.py:127 +#: ../extensions/deviceicon/network.py:199 +#: ../extensions/deviceicon/network.py:289 +#: ../src/jarabe/desktop/meshbox.py:253 ../src/jarabe/desktop/meshbox.py:542 msgid "Connected" msgstr "Verbonden" -#: ../extensions/deviceicon/network.py:146 +#: ../extensions/deviceicon/network.py:159 msgid "Channel" msgstr "Kanaal" -#: ../extensions/deviceicon/network.py:161 +#: ../extensions/deviceicon/network.py:174 msgid "Wired Network" msgstr "Bedraad netwerk" -#: ../extensions/deviceicon/network.py:189 +#: ../extensions/deviceicon/network.py:202 msgid "Speed" msgstr "Snelheid" -#: ../extensions/deviceicon/network.py:415 +#: ../extensions/deviceicon/network.py:229 +msgid "Wireless modem" +msgstr "Draadloze modem" + +#: ../extensions/deviceicon/network.py:277 +msgid "Please wait..." +msgstr "Even geduld aub..." + +#: ../extensions/deviceicon/network.py:280 +#: ../src/jarabe/desktop/meshbox.py:163 ../src/jarabe/desktop/meshbox.py:493 +msgid "Connect" +msgstr "Verbinden" + +#: ../extensions/deviceicon/network.py:281 +msgid "Disconnected" +msgstr "Verbinding verbroken" + +#: ../extensions/deviceicon/network.py:284 +#: ../src/jarabe/controlpanel/toolbar.py:115 +#: ../src/jarabe/desktop/homebox.py:68 +#: ../src/jarabe/frame/activitiestray.py:708 +#: ../src/jarabe/frame/activitiestray.py:807 +#: ../src/jarabe/frame/activitiestray.py:835 +msgid "Cancel" +msgstr "Annuleren" + +#: ../extensions/deviceicon/network.py:288 +#: ../src/jarabe/desktop/meshbox.py:167 +msgid "Disconnect" +msgstr "Verbinding verbreken" + +#: ../extensions/deviceicon/network.py:292 +msgid "Sim requires Pin/Puk" +msgstr "Sim vereist Pin/Puk" + +#: ../extensions/deviceicon/network.py:293 +msgid "Authentication Error" +msgstr "Authenticatiefout" + +#: ../extensions/deviceicon/network.py:538 #, python-format -msgid "%s's network %s" -msgstr "%s's netwerk %s" +msgid "%s's network" +msgstr "%s's netwerk" + +#: ../extensions/deviceicon/network.py:605 +#: ../extensions/deviceicon/network.py:664 +msgid "Mesh Network" +msgstr "Mesh netwerk" + +#: ../extensions/deviceicon/network.py:869 +#, python-format +msgid "Data sent %d KB / received %d KB" +msgstr "Gegevens verstuurd %d KB / ontvangen %d KB" + +#: ../extensions/deviceicon/network.py:880 +msgid "Connection time " +msgstr "Tijd verbonden " #: ../extensions/deviceicon/speaker.py:59 msgid "My Speakers" @@ -459,39 +554,51 @@ msgstr "Ontdempen" msgid "Mute" msgstr "Dempen" -#: ../extensions/globalkey/screenshot.py:56 +#: ../extensions/globalkey/screenshot.py:59 msgid "Mesh" msgstr "Mesh" -#: ../extensions/globalkey/screenshot.py:58 +#: ../extensions/globalkey/screenshot.py:61 #: ../src/jarabe/frame/zoomtoolbar.py:39 msgid "Group" msgstr "Groep" -#: ../extensions/globalkey/screenshot.py:60 +#: ../extensions/globalkey/screenshot.py:63 #: ../src/jarabe/frame/zoomtoolbar.py:41 msgid "Home" msgstr "Thuis" -#: ../extensions/globalkey/screenshot.py:66 +#: ../extensions/globalkey/screenshot.py:69 #: ../src/jarabe/frame/zoomtoolbar.py:43 msgid "Activity" msgstr "Activiteit" -#: ../extensions/globalkey/screenshot.py:69 +#: ../extensions/globalkey/screenshot.py:72 msgid "Screenshot" msgstr "Schermafdruk" -#: ../extensions/globalkey/screenshot.py:71 +#: ../extensions/globalkey/screenshot.py:74 #, python-format msgid "Screenshot of \"%s\"" msgstr "Schermafdruk van \"%s\"" #: ../data/sugar.schemas.in.h:1 -msgid "Backup URL" -msgstr "Backup URL" +msgid "" +"\"disabled\" to ask nick on initialization; \"system\" to reuse UNIX account " +"long name." +msgstr "" +"\"uitgezet\" om bijnaam te vragen bij initialisatie; \"systeem\" hergebruikt de " +"UNIX gebruiker lange naam." #: ../data/sugar.schemas.in.h:2 +msgid "Additional directories which can contain updated translations." +msgstr "Additionele directories die bijgewerkte vertalingen kunnen bevatten." + +#: ../data/sugar.schemas.in.h:3 +msgid "Backup URL" +msgstr "Back-up URL" + +#: ../data/sugar.schemas.in.h:4 msgid "" "Color for the XO icon that is used throughout the desktop. The string is " "composed of the stroke color and fill color, format is that of rbg colors. " @@ -501,148 +608,220 @@ msgstr "" "uit een lijnkleur en een vulkleur, formaat is gelijk aan rbg kleuren. " "Bijvoorbeeld: #AC32FF,#9A5200" -#: ../data/sugar.schemas.in.h:3 +#: ../data/sugar.schemas.in.h:5 msgid "Corner Delay" msgstr "Hoekvertraging" -#: ../data/sugar.schemas.in.h:4 +#: ../data/sugar.schemas.in.h:6 +msgid "Default font face" +msgstr "Standaard lettertype" + +#: ../data/sugar.schemas.in.h:7 +msgid "Default font size" +msgstr "Standaard lettergrootte" + +#: ../data/sugar.schemas.in.h:8 +msgid "Default nick" +msgstr "Standaard bijnaam" + +#: ../data/sugar.schemas.in.h:9 msgid "Delay for the activation of the frame using the corners." msgstr "Vertraging voor de activatie van het kader door de hoeken te gebruiken." -#: ../data/sugar.schemas.in.h:5 +#: ../data/sugar.schemas.in.h:10 msgid "Delay for the activation of the frame using the edges." msgstr "Vertraging voor de activatie van het kader door de randen te gebruiken." -#: ../data/sugar.schemas.in.h:6 +#: ../data/sugar.schemas.in.h:11 +msgid "Directory to search for translations" +msgstr "Directory om naar vertalingen te zoeken" + +#: ../data/sugar.schemas.in.h:12 msgid "Edge Delay" msgstr "Randvertraging" -#: ../data/sugar.schemas.in.h:7 +#: ../data/sugar.schemas.in.h:13 msgid "Favorites Layout" msgstr "Favorietenlayout" -#: ../data/sugar.schemas.in.h:8 +#: ../data/sugar.schemas.in.h:14 msgid "Favorites resume mode" msgstr "Favorieten hervatmodus" -#: ../data/sugar.schemas.in.h:9 +#: ../data/sugar.schemas.in.h:15 +msgid "Font face that is used throughout the desktop." +msgstr "Lettertype dat overal gebruikt wordt op het bureaublad." + +#: ../data/sugar.schemas.in.h:16 +msgid "Font size that is used throughout the desktop." +msgstr "Lettergrootte die overal gebruikt wordt op het bureaublad." + +#: ../data/sugar.schemas.in.h:17 +msgid "GSM network APN" +msgstr "GSM netwerk APN" + +#: ../data/sugar.schemas.in.h:18 +msgid "GSM network PIN" +msgstr "GSM netwerk PIN" + +#: ../data/sugar.schemas.in.h:19 +msgid "GSM network PUK" +msgstr "GSM netwerk PUK" + +#: ../data/sugar.schemas.in.h:20 +msgid "GSM network access point name configuration" +msgstr "GSM netwerk access point naam configuratie" + +#: ../data/sugar.schemas.in.h:21 +msgid "GSM network number" +msgstr "GSM netwerknummer" + +#: ../data/sugar.schemas.in.h:22 +msgid "GSM network password" +msgstr "GSM netwerkwachtwoord" + +#: ../data/sugar.schemas.in.h:23 +msgid "GSM network password configuration" +msgstr "GSM netwerkwachtwoord configuratie" + +#: ../data/sugar.schemas.in.h:24 +msgid "GSM network personal identification number configuration" +msgstr "GSM netwerk persoonlijk identificatienummer configuratie" + +#: ../data/sugar.schemas.in.h:25 +msgid "GSM network personal unlock key configuration" +msgstr "GSM netwerk persoonlijk ontsluitsleutel configuratie" + +#: ../data/sugar.schemas.in.h:26 +msgid "GSM network telephone number configuration" +msgstr "GSM netwerk telefoonnummer configuratie" + +#: ../data/sugar.schemas.in.h:27 +msgid "GSM network username" +msgstr "GSM netwerkgebruikersnaam" + +#: ../data/sugar.schemas.in.h:28 +msgid "GSM network username configuration" +msgstr "GSM netwerkgebruikersnaam configuratie" + +#: ../data/sugar.schemas.in.h:29 msgid "" "If TRUE, Sugar will make us searchable for the other users of the Jabber " "server." msgstr "" -"Indien WAAR, Sugar zal ons opzoekbaar maken voor andere gebruikers op de " +"Indien WAAR, dan zal Sugar ons opzoekbaar maken voor andere gebruikers op de " "Jabber server." -#: ../data/sugar.schemas.in.h:10 +#: ../data/sugar.schemas.in.h:30 msgid "If TRUE, Sugar will show a \"Log out\" option." msgstr "Indien WAAR, zal Sugar een \"Afmelden\" optie geven." -#: ../data/sugar.schemas.in.h:11 +#: ../data/sugar.schemas.in.h:31 msgid "Jabber Server" -msgstr "Jabber server" +msgstr "Jabber-server" -#: ../data/sugar.schemas.in.h:12 +#: ../data/sugar.schemas.in.h:32 msgid "Keyboard layouts" -msgstr "Toetsenbordlayouts" +msgstr "Toetsenbordindelingen" -#: ../data/sugar.schemas.in.h:13 +#: ../data/sugar.schemas.in.h:33 msgid "Keyboard model" msgstr "Toetsenbordmodel" -#: ../data/sugar.schemas.in.h:14 +#: ../data/sugar.schemas.in.h:34 msgid "Keyboard options" msgstr "Toetsenbordopties" -#: ../data/sugar.schemas.in.h:15 +#: ../data/sugar.schemas.in.h:35 msgid "Layout of the favorites view." -msgstr "Layout van de Favorieten weergave" +msgstr "Layout van de favorieten weergave." -#: ../data/sugar.schemas.in.h:16 +#: ../data/sugar.schemas.in.h:36 msgid "" "List of keyboard layouts. Each entry should be in the form layout(variant)" msgstr "" -"Lijst van toetsenbordlayouts. Elke ingang moet in de vorm layout(variant) " -"zijn." +"Lijst met toetsenbordindelingen. Elke invoer moet in de " +"vormindeling(variant) zijn" -#: ../data/sugar.schemas.in.h:17 +#: ../data/sugar.schemas.in.h:37 msgid "List of keyboard options." -msgstr "Lijst van toetsenbordopties." +msgstr "Lijst met toetsenbordopties." -#: ../data/sugar.schemas.in.h:18 +#: ../data/sugar.schemas.in.h:38 msgid "Power Automatic" msgstr "Energiebeheer: automatisch" -#: ../data/sugar.schemas.in.h:19 +#: ../data/sugar.schemas.in.h:39 msgid "Power Automatic." msgstr "Energiebeheer: automatisch." -#: ../data/sugar.schemas.in.h:20 +#: ../data/sugar.schemas.in.h:40 msgid "Power Extreme" msgstr "Energiebeheer: extreem" -#: ../data/sugar.schemas.in.h:21 +#: ../data/sugar.schemas.in.h:41 msgid "Power Extreme." msgstr "Energiebeheer: extreem." -#: ../data/sugar.schemas.in.h:22 +#: ../data/sugar.schemas.in.h:42 msgid "Publish to Gadget" msgstr "Publiceren naar gadget" -#: ../data/sugar.schemas.in.h:23 +#: ../data/sugar.schemas.in.h:43 msgid "Setting for muting the sound device." msgstr "Instelling voor dempen van het audio-apparaat." -#: ../data/sugar.schemas.in.h:24 +#: ../data/sugar.schemas.in.h:44 msgid "Show Log out" msgstr "Afmelden weergeven" -#: ../data/sugar.schemas.in.h:25 +#: ../data/sugar.schemas.in.h:45 msgid "Sound Muted" msgstr "Geluid gedempt" -#: ../data/sugar.schemas.in.h:26 +#: ../data/sugar.schemas.in.h:46 msgid "The keyboard model to be used" msgstr "Het toetsenbordmodel om te gebruiken" -#: ../data/sugar.schemas.in.h:28 +#: ../data/sugar.schemas.in.h:48 msgid "Timezone setting for the system." msgstr "Tijdzone-instelling voor het systeem." -#: ../data/sugar.schemas.in.h:29 +#: ../data/sugar.schemas.in.h:49 msgid "Url of the jabber server to use." -msgstr "Url van de te gebruiken jabber server." +msgstr "Url van de te gebruiken jabber-server." -#: ../data/sugar.schemas.in.h:30 +#: ../data/sugar.schemas.in.h:50 msgid "Url where the backup is saved to." -msgstr "Url waar de backup opgeslagen wordt." +msgstr "Url waar de back-up opgeslagen wordt." -#: ../data/sugar.schemas.in.h:31 +#: ../data/sugar.schemas.in.h:51 msgid "User Color" msgstr "Gebruikerskleur" -#: ../data/sugar.schemas.in.h:32 +#: ../data/sugar.schemas.in.h:52 msgid "User Name" msgstr "Gebruikersnaam" -#: ../data/sugar.schemas.in.h:33 +#: ../data/sugar.schemas.in.h:53 msgid "User name that is used throughout the desktop." msgstr "Gebruikersnaam die op de desktop gebruikt wordt." -#: ../data/sugar.schemas.in.h:34 +#: ../data/sugar.schemas.in.h:54 msgid "Volume Level" msgstr "Volumeniveau" -#: ../data/sugar.schemas.in.h:35 +#: ../data/sugar.schemas.in.h:55 msgid "Volume level for the sound device." -msgstr "Volumeniveau voor het audio-apparaat" +msgstr "Volumeniveau voor het audio-apparaat." -#: ../data/sugar.schemas.in.h:36 +#: ../data/sugar.schemas.in.h:56 msgid "" "When in resume mode, clicking on a favorite icon will cause the last entry " "for that activity to be resumed." msgstr "" -"Indien in hervatmodus, klikken op een favoriet icoon zal ervoor zorgen dat " -"de laatste ingang voor die activiteit hervat wordt." +"Indien in hervatmodus, kun je door te klikken op een favorietenicoon ervoor " +"zorgen dat de laatste invoer van die activiteit hervat wordt." #: ../src/jarabe/controlpanel/cmd.py:28 #, python-format @@ -650,7 +829,7 @@ msgid "" "sugar-control-panel: WARNING, found more than one option with the same name: " "%s module: %r" msgstr "" -"sugar-control-panel: WAARSCHUWING, meer dan een optie met dezelfde naam " +"sugar-stuur-paneel: WAARSCHUWING, meer dan één optie met dezelfde naam " "gevonden: %s module: %r" #: ../src/jarabe/controlpanel/cmd.py:30 @@ -661,7 +840,7 @@ msgstr "sugar-control-panel: sleutel=%s is geen beschikbare optie" #: ../src/jarabe/controlpanel/cmd.py:31 #, python-format msgid "sugar-control-panel: %s" -msgstr "sugar-control-panel: %s" +msgstr "sugar-stuur-paneel: %s" # TRANS: Translators, there's a empty line at the end of this string, # which must appear in the translated string (msgstr) as well. @@ -680,11 +859,11 @@ msgid "" " -c key clear the current value for the key \n" " " msgstr "" -"Gebruik: sugar-control-panel [ optie ] sleutel [ args ... ] \n" +"Gebruik: sugar-stuur-paneel [ optie ] sleutel [ args ... ] \n" " Configuratie voor de sugar omgeving. \n" " Opties: \n" " -h geef dit helpbericht weer en sluit af \n" -" -l lijst van alle beschikbare opties \n" +" -l lijst van alle beschikbare opties \n" " -h sleutel geef informatie over deze sleutel \n" " -g sleutel verkrijg de huidige waarde van de sleutel \n" " -s sleutel zet de huidige waarde naar de sleutel \n" @@ -695,68 +874,46 @@ msgstr "" msgid "To apply your changes you have to restart sugar.\n" msgstr "Om je veranderingen toe te passen moet je sugar herstarten.\n" -#: ../src/jarabe/controlpanel/gui.py:280 +#: ../src/jarabe/controlpanel/gui.py:281 msgid "Warning" msgstr "Waarschuwing" -#: ../src/jarabe/controlpanel/gui.py:281 +#: ../src/jarabe/controlpanel/gui.py:282 #: ../src/jarabe/controlpanel/sectionview.py:42 msgid "Changes require restart" msgstr "Verandering vereist een herstart" -#: ../src/jarabe/controlpanel/gui.py:284 +#: ../src/jarabe/controlpanel/gui.py:285 msgid "Cancel changes" msgstr "Veranderingen annuleren" -#: ../src/jarabe/controlpanel/gui.py:289 ../src/jarabe/desktop/homebox.py:70 +#: ../src/jarabe/controlpanel/gui.py:290 ../src/jarabe/desktop/homebox.py:70 msgid "Later" msgstr "Later" -#: ../src/jarabe/controlpanel/gui.py:293 +#: ../src/jarabe/controlpanel/gui.py:294 msgid "Restart now" msgstr "Herstart nu" -#: ../src/jarabe/controlpanel/toolbar.py:61 ../src/jarabe/intro/window.py:188 +#: ../src/jarabe/controlpanel/toolbar.py:61 ../src/jarabe/intro/window.py:206 msgid "Done" msgstr "Klaar" -#: ../src/jarabe/controlpanel/toolbar.py:115 -#: ../src/jarabe/desktop/homebox.py:68 -#: ../src/jarabe/frame/activitiestray.py:726 -#: ../src/jarabe/frame/activitiestray.py:822 -#: ../src/jarabe/frame/activitiestray.py:850 -msgid "Cancel" -msgstr "Annuleren" - #: ../src/jarabe/controlpanel/toolbar.py:121 -#: ../src/jarabe/desktop/favoritesview.py:332 +#: ../src/jarabe/desktop/favoritesview.py:333 msgid "Ok" msgstr "Ok" -#: ../src/jarabe/desktop/activitieslist.py:80 -#: ../src/jarabe/journal/listview.py:147 -msgid "Title" -msgstr "Titel" - -#: ../src/jarabe/desktop/activitieslist.py:91 -msgid "Version" -msgstr "Versie" - -#: ../src/jarabe/desktop/activitieslist.py:105 -#: ../src/jarabe/journal/listview.py:178 -msgid "Date" -msgstr "Datum" - -#: ../src/jarabe/desktop/activitieslist.py:234 +#: ../src/jarabe/desktop/activitieslist.py:236 #, python-format msgid "Version %s" msgstr "Versie %s" -#: ../src/jarabe/desktop/activitieslist.py:355 +#: ../src/jarabe/desktop/activitieslist.py:357 msgid "Confirm erase" msgstr "Bevestig wissen" -#: ../src/jarabe/desktop/activitieslist.py:357 +#: ../src/jarabe/desktop/activitieslist.py:359 #, python-format msgid "Confirm erase: Do you want to permanently erase %s?" msgstr "Bevestig wissen: Wilt u permanent %s wissen?" @@ -766,24 +923,24 @@ msgstr "Bevestig wissen: Wilt u permanent %s wissen?" # 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/desktop/activitieslist.py:363 +#: ../src/jarabe/frame/clipboardmenu.py:63 #: ../src/jarabe/view/viewsource.py:218 msgid "Keep" msgstr "Bewaar" -#: ../src/jarabe/desktop/activitieslist.py:364 -#: ../src/jarabe/desktop/activitieslist.py:406 -#: ../src/jarabe/journal/journaltoolbox.py:360 -#: ../src/jarabe/journal/palettes.py:112 +#: ../src/jarabe/desktop/activitieslist.py:366 +#: ../src/jarabe/desktop/activitieslist.py:409 +#: ../src/jarabe/journal/journaltoolbox.py:361 +#: ../src/jarabe/journal/palettes.py:106 msgid "Erase" msgstr "Wissen" -#: ../src/jarabe/desktop/activitieslist.py:427 +#: ../src/jarabe/desktop/activitieslist.py:430 msgid "Remove favorite" msgstr "Verwijder favoriet" -#: ../src/jarabe/desktop/activitieslist.py:431 +#: ../src/jarabe/desktop/activitieslist.py:434 msgid "Make favorite" msgstr "Maak favoriet" @@ -791,7 +948,7 @@ msgstr "Maak favoriet" #. TRANS: label for the freeform layout in the favorites view #: ../src/jarabe/desktop/favoriteslayout.py:116 msgid "Freeform" -msgstr "VrijeVorm" +msgstr "Vrijevorm" # TRANS: label for the ring layout in the favorites view #. TRANS: label for the ring layout in the favorites view @@ -801,52 +958,52 @@ msgstr "Ring" # 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:334 +#: ../src/jarabe/desktop/favoriteslayout.py:337 msgid "Spiral" msgstr "Spiraal" # 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:401 +#: ../src/jarabe/desktop/favoriteslayout.py:404 msgid "Box" msgstr "Vierkant" # 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:442 +#: ../src/jarabe/desktop/favoriteslayout.py:445 msgid "Triangle" msgstr "Driehoek" -#: ../src/jarabe/desktop/favoritesview.py:323 +#: ../src/jarabe/desktop/favoritesview.py:324 msgid "Registration Failed" msgstr "Registratie mislukt" -#: ../src/jarabe/desktop/favoritesview.py:324 +#: ../src/jarabe/desktop/favoritesview.py:325 #, python-format msgid "%s" msgstr "%s" -#: ../src/jarabe/desktop/favoritesview.py:326 +#: ../src/jarabe/desktop/favoritesview.py:327 msgid "Registration Successful" msgstr "Registratie succesvol uitgevoerd" -#: ../src/jarabe/desktop/favoritesview.py:327 +#: ../src/jarabe/desktop/favoritesview.py:328 msgid "You are now registered with your school server." -msgstr "Je bent nu geregistreerd bij je schoolserver." +msgstr "Je bent nu geregistreerd bij je school-server." -#: ../src/jarabe/desktop/favoritesview.py:671 +#: ../src/jarabe/desktop/favoritesview.py:631 msgid "Register" msgstr "Registreren" #: ../src/jarabe/desktop/homebox.py:63 msgid "Software Update" -msgstr "Software update" +msgstr "Software Bijwerken" #: ../src/jarabe/desktop/homebox.py:64 msgid "Update your activities to ensure compatibility with your new software" msgstr "" "Update je activiteiten om zeker ervan te zijn dat ze met je nieuwe software " -"compatibel zijn." +"compatibel zijn" #: ../src/jarabe/desktop/homebox.py:73 msgid "Check now" @@ -868,117 +1025,109 @@ msgstr "Favorietenweergave" msgid "<Ctrl>1" msgstr "<Ctrl>1" -#: ../src/jarabe/desktop/keydialog.py:131 +#: ../src/jarabe/desktop/keydialog.py:135 msgid "Key Type:" msgstr "Sleutel type:" -#: ../src/jarabe/desktop/keydialog.py:151 +#: ../src/jarabe/desktop/keydialog.py:155 msgid "Authentication Type:" msgstr "Authenticatie type:" -#: ../src/jarabe/desktop/keydialog.py:215 +#: ../src/jarabe/desktop/keydialog.py:220 msgid "WPA & WPA2 Personal" -msgstr "WPA en WPA 2 Persoonlijk" +msgstr "WPA & WPA2 Persoonlijk" -#: ../src/jarabe/desktop/keydialog.py:224 +#: ../src/jarabe/desktop/keydialog.py:229 msgid "Wireless Security:" msgstr "Draadloze netwerkbeveiliging:" -#: ../src/jarabe/desktop/meshbox.py:136 -msgid "Connect" -msgstr "Verbinden" - -#: ../src/jarabe/desktop/meshbox.py:140 -msgid "Disconnect" -msgstr "Verbinding verbreken" +#: ../src/jarabe/desktop/meshbox.py:491 +#, python-format +msgid "Mesh Network %d" +msgstr "Mesh Netwerk %d" # TRANS: Action label for resuming an activity. #. TRANS: Action label for resuming an activity. -#: ../src/jarabe/desktop/meshbox.py:463 -#: ../src/jarabe/frame/activitiestray.py:761 -#: ../src/jarabe/journal/journaltoolbox.py:428 -#: ../src/jarabe/journal/palettes.py:72 ../src/jarabe/view/palettes.py:64 +#: ../src/jarabe/desktop/meshbox.py:628 +#: ../src/jarabe/frame/activitiestray.py:743 +#: ../src/jarabe/journal/journaltoolbox.py:440 +#: ../src/jarabe/journal/palettes.py:66 ../src/jarabe/view/palettes.py:79 msgid "Resume" msgstr "Hervatten" -#: ../src/jarabe/desktop/meshbox.py:468 -#: ../src/jarabe/frame/activitiestray.py:235 +#: ../src/jarabe/desktop/meshbox.py:633 +#: ../src/jarabe/frame/activitiestray.py:241 msgid "Join" msgstr "Bijvoegen" -#: ../src/jarabe/desktop/schoolserver.py:34 -msgid "Cannot obtain data needed for registration." -msgstr "" -"Kan vereiste gegevens die nodig zijn voor de registratie niet verkrijgen." - -#: ../src/jarabe/desktop/schoolserver.py:51 +#: ../src/jarabe/desktop/schoolserver.py:104 msgid "Cannot connect to the server." msgstr "Kan niet met de server verbinden." -#: ../src/jarabe/desktop/schoolserver.py:56 +#: ../src/jarabe/desktop/schoolserver.py:109 msgid "The server could not complete the request." msgstr "De server kon de aanvraag niet voltooien." -#: ../src/jarabe/frame/activitiestray.py:240 -#: ../src/jarabe/frame/activitiestray.py:698 +#: ../src/jarabe/frame/activitiestray.py:246 +#: ../src/jarabe/frame/activitiestray.py:680 msgid "Decline" msgstr "Weigeren" -#: ../src/jarabe/frame/activitiestray.py:650 +#: ../src/jarabe/frame/activitiestray.py:632 #, python-format msgid "%dB" msgstr "%dB" -#: ../src/jarabe/frame/activitiestray.py:652 +#: ../src/jarabe/frame/activitiestray.py:634 #, python-format msgid "%dKB" msgstr "%dKB" -#: ../src/jarabe/frame/activitiestray.py:654 +#: ../src/jarabe/frame/activitiestray.py:636 #, python-format msgid "%dMB" msgstr "%dMB" -#: ../src/jarabe/frame/activitiestray.py:671 +#: ../src/jarabe/frame/activitiestray.py:653 #, python-format msgid "%s of %s" msgstr "%s van %s" -#: ../src/jarabe/frame/activitiestray.py:683 +#: ../src/jarabe/frame/activitiestray.py:665 #, python-format msgid "Transfer from %r" msgstr "Overdragen van %r" -#: ../src/jarabe/frame/activitiestray.py:693 +#: ../src/jarabe/frame/activitiestray.py:675 msgid "Accept" msgstr "Accepteren" -#: ../src/jarabe/frame/activitiestray.py:716 -#: ../src/jarabe/frame/activitiestray.py:840 +#: ../src/jarabe/frame/activitiestray.py:698 +#: ../src/jarabe/frame/activitiestray.py:825 #, python-format msgid "%s (%s)" msgstr "%s (%s)" -#: ../src/jarabe/frame/activitiestray.py:750 -#: ../src/jarabe/frame/activitiestray.py:875 +#: ../src/jarabe/frame/activitiestray.py:732 +#: ../src/jarabe/frame/activitiestray.py:860 msgid "Dismiss" msgstr "Wegdoen" -#: ../src/jarabe/frame/activitiestray.py:810 +#: ../src/jarabe/frame/activitiestray.py:795 #, python-format msgid "Transfer to %r" msgstr "Overdragen naar %r" -#: ../src/jarabe/frame/clipboardmenu.py:52 +#: ../src/jarabe/frame/clipboardmenu.py:53 ../src/jarabe/view/palettes.py:233 msgid "Remove" msgstr "Verwijderen" -#: ../src/jarabe/frame/clipboardmenu.py:57 -#: ../src/jarabe/frame/clipboardmenu.py:80 +#: ../src/jarabe/frame/clipboardmenu.py:58 +#: ../src/jarabe/frame/clipboardmenu.py:81 msgid "Open" msgstr "Openen" -#: ../src/jarabe/frame/clipboardmenu.py:85 +#: ../src/jarabe/frame/clipboardmenu.py:86 msgid "Open with" msgstr "Openen met" @@ -1007,140 +1156,141 @@ msgstr "F3" msgid "F4" msgstr "F4" -#: ../src/jarabe/intro/window.py:124 +#: ../src/jarabe/intro/window.py:128 msgid "Click to change color:" msgstr "Klik om de kleur te veranderen:" -#: ../src/jarabe/intro/window.py:174 ../src/jarabe/journal/detailview.py:103 +#: ../src/jarabe/intro/window.py:192 ../src/jarabe/journal/detailview.py:103 msgid "Back" msgstr "Terug" -#: ../src/jarabe/intro/window.py:191 +#: ../src/jarabe/intro/window.py:209 msgid "Next" msgstr "Volgende" -#: ../src/jarabe/journal/expandedentry.py:164 -#: ../src/jarabe/journal/palettes.py:66 +#: ../src/jarabe/journal/expandedentry.py:151 +#: ../src/jarabe/journal/palettes.py:60 msgid "Untitled" msgstr "Naamloos" -#: ../src/jarabe/journal/expandedentry.py:210 +#: ../src/jarabe/journal/expandedentry.py:242 msgid "No preview" msgstr "Geen voorbeeld" -#: ../src/jarabe/journal/expandedentry.py:229 +#: ../src/jarabe/journal/expandedentry.py:261 #, python-format msgid "Kind: %s" msgstr "Type: %s" -#: ../src/jarabe/journal/expandedentry.py:229 +#: ../src/jarabe/journal/expandedentry.py:261 msgid "Unknown" msgstr "Onbekend" -#: ../src/jarabe/journal/expandedentry.py:230 +#: ../src/jarabe/journal/expandedentry.py:262 #, python-format msgid "Date: %s" msgstr "Datum: %s" -#: ../src/jarabe/journal/expandedentry.py:231 +#: ../src/jarabe/journal/expandedentry.py:263 #, python-format msgid "Size: %s" msgstr "Grootte: %s" -#: ../src/jarabe/journal/expandedentry.py:253 ../src/jarabe/journal/misc.py:92 +#: ../src/jarabe/journal/expandedentry.py:285 ../src/jarabe/journal/misc.py:93 msgid "No date" msgstr "Geen datum" -#: ../src/jarabe/journal/expandedentry.py:260 +#: ../src/jarabe/journal/expandedentry.py:292 msgid "Participants:" msgstr "Deelnemers:" -#: ../src/jarabe/journal/expandedentry.py:283 +#: ../src/jarabe/journal/expandedentry.py:315 msgid "Description:" msgstr "Omschrijving:" -#: ../src/jarabe/journal/expandedentry.py:309 +#: ../src/jarabe/journal/expandedentry.py:340 msgid "Tags:" msgstr "Labels:" #: ../src/jarabe/journal/journalactivity.py:108 +#: ../src/jarabe/journal/journaltoolbox.py:411 #: ../src/jarabe/journal/volumestoolbar.py:47 msgid "Journal" msgstr "Dagboek" -#: ../src/jarabe/journal/journaltoolbox.py:67 +#: ../src/jarabe/journal/journaltoolbox.py:68 msgid "Search" msgstr "Zoeken" -#: ../src/jarabe/journal/journaltoolbox.py:126 +#: ../src/jarabe/journal/journaltoolbox.py:127 msgid "Anytime" msgstr "Ieder tijdstip" -#: ../src/jarabe/journal/journaltoolbox.py:128 +#: ../src/jarabe/journal/journaltoolbox.py:129 msgid "Today" msgstr "Vandaag" -#: ../src/jarabe/journal/journaltoolbox.py:130 +#: ../src/jarabe/journal/journaltoolbox.py:131 msgid "Since yesterday" msgstr "Sinds gisteren" # TRANS: Filter entries modified during the last 7 days. #. TRANS: Filter entries modified during the last 7 days. -#: ../src/jarabe/journal/journaltoolbox.py:132 +#: ../src/jarabe/journal/journaltoolbox.py:133 msgid "Past week" msgstr "Afgelopen week" # TRANS: Filter entries modified during the last 30 days. #. TRANS: Filter entries modified during the last 30 days. -#: ../src/jarabe/journal/journaltoolbox.py:134 +#: ../src/jarabe/journal/journaltoolbox.py:135 msgid "Past month" msgstr "Afgelopen maand" # TRANS: Filter entries modified during the last 356 days. #. TRANS: Filter entries modified during the last 356 days. -#: ../src/jarabe/journal/journaltoolbox.py:136 +#: ../src/jarabe/journal/journaltoolbox.py:137 msgid "Past year" msgstr "Afgelopen jaar" -#: ../src/jarabe/journal/journaltoolbox.py:143 +#: ../src/jarabe/journal/journaltoolbox.py:144 msgid "Anyone" msgstr "Iedereen" -#: ../src/jarabe/journal/journaltoolbox.py:145 +#: ../src/jarabe/journal/journaltoolbox.py:146 msgid "My friends" msgstr "Mijn vrienden" -#: ../src/jarabe/journal/journaltoolbox.py:146 +#: ../src/jarabe/journal/journaltoolbox.py:147 msgid "My class" msgstr "Mijn klas" # TRANS: Item in a combo box that filters by entry type. -#: ../src/jarabe/journal/journaltoolbox.py:274 +#: ../src/jarabe/journal/journaltoolbox.py:275 msgid "Anything" msgstr "Alles" # TODO: Add "Start with" menu item -#: ../src/jarabe/journal/journaltoolbox.py:350 -#: ../src/jarabe/journal/palettes.py:90 +#: ../src/jarabe/journal/journaltoolbox.py:351 +#: ../src/jarabe/journal/palettes.py:84 msgid "Copy" msgstr "Kopieer" # TRANS: Action label for starting an entry. #. TRANS: Action label for starting an entry. -#: ../src/jarabe/journal/journaltoolbox.py:431 -#: ../src/jarabe/journal/palettes.py:75 +#: ../src/jarabe/journal/journaltoolbox.py:443 +#: ../src/jarabe/journal/palettes.py:69 msgid "Start" msgstr "Start" -#: ../src/jarabe/journal/listview.py:361 +#: ../src/jarabe/journal/listview.py:373 msgid "Your Journal is empty" msgstr "Je dagboek is leeg" -#: ../src/jarabe/journal/listview.py:363 +#: ../src/jarabe/journal/listview.py:375 msgid "No matching entries" msgstr "Geen overeenkomende ingangen" -#: ../src/jarabe/journal/listview.py:374 +#: ../src/jarabe/journal/listview.py:386 msgid "Clear search" msgstr "Zoekopdracht wissen" @@ -1161,41 +1311,41 @@ msgid "Choose an object" msgstr "Kies een object" #: ../src/jarabe/journal/objectchooser.py:151 -#: ../src/jarabe/view/viewsource.py:308 +#: ../src/jarabe/view/viewsource.py:310 msgid "Close" msgstr "Sluiten" -#: ../src/jarabe/journal/palettes.py:73 +#: ../src/jarabe/journal/palettes.py:67 msgid "Resume with" msgstr "Hervatten met" -#: ../src/jarabe/journal/palettes.py:76 +#: ../src/jarabe/journal/palettes.py:70 msgid "Start with" msgstr "Starten met" -#: ../src/jarabe/journal/palettes.py:98 +#: ../src/jarabe/journal/palettes.py:92 msgid "Send to" msgstr "Verstuur naar" -#: ../src/jarabe/journal/palettes.py:107 +#: ../src/jarabe/journal/palettes.py:101 msgid "View Details" msgstr "Details weergeven" -#: ../src/jarabe/journal/palettes.py:185 +#: ../src/jarabe/journal/palettes.py:179 msgid "No friends present" msgstr "Geen vrienden aanwezig" -#: ../src/jarabe/journal/palettes.py:190 +#: ../src/jarabe/journal/palettes.py:184 msgid "No valid connection found" msgstr "Geen bruikbare verbinding gevonden" -#: ../src/jarabe/journal/palettes.py:218 +#: ../src/jarabe/journal/palettes.py:212 msgid "No activity to resume entry" -msgstr "Geen activiteit om ingang mee te hervatten" +msgstr "Geen activiteit om invoer mee te hervatten" -#: ../src/jarabe/journal/palettes.py:220 +#: ../src/jarabe/journal/palettes.py:214 msgid "No activity to start entry" -msgstr "Geen activiteit om ingang mee te starten" +msgstr "Geen activiteit om invoer mee te starten" #: ../src/jarabe/view/buddymenu.py:62 msgid "Remove friend" @@ -1222,36 +1372,41 @@ msgstr "Mijn instellingen" msgid "Invite to %s" msgstr "Nodig uit voor %s" -#: ../src/jarabe/view/palettes.py:45 +#: ../src/jarabe/view/launcher.py:190 +#, python-format +msgid "<b>%s</b> failed to start." +msgstr "<b>%s</b> kon niet starten." + +#: ../src/jarabe/view/palettes.py:47 msgid "Starting..." msgstr "Beginnen..." +#: ../src/jarabe/view/palettes.py:57 +msgid "Activity failed to start" +msgstr "Activiteit kon niet starten" + #. TODO: share-with, keep -#: ../src/jarabe/view/palettes.py:71 +#: ../src/jarabe/view/palettes.py:86 msgid "View Source" msgstr "Bron weergeven" -#: ../src/jarabe/view/palettes.py:82 +#: ../src/jarabe/view/palettes.py:97 msgid "Stop" msgstr "Stop" -#: ../src/jarabe/view/palettes.py:122 +#: ../src/jarabe/view/palettes.py:137 msgid "Start new" msgstr "Begin nieuw" -#: ../src/jarabe/view/palettes.py:171 +#: ../src/jarabe/view/palettes.py:186 msgid "Show contents" msgstr "Inhoud weergeven" -#: ../src/jarabe/view/palettes.py:193 ../src/jarabe/view/palettes.py:243 +#: ../src/jarabe/view/palettes.py:208 ../src/jarabe/view/palettes.py:258 #, python-format msgid "%(free_space)d MB Free" msgstr "%(free_space)d MB vrij" -#: ../src/jarabe/view/palettes.py:218 -msgid "Unmount" -msgstr "Loskoppelen" - #: ../src/jarabe/view/viewsource.py:208 msgid "Instance Source" msgstr "Werkkopie van bron maken" @@ -1260,15 +1415,34 @@ msgstr "Werkkopie van bron maken" msgid "Source" msgstr "Bron" -#: ../src/jarabe/view/viewsource.py:292 +#: ../src/jarabe/view/viewsource.py:294 msgid "Activity Bundle Source" msgstr "Activiteitsbundel Bron" -#: ../src/jarabe/view/viewsource.py:299 +#: ../src/jarabe/view/viewsource.py:301 #, python-format msgid "View source: %r" msgstr "Bron weergeven: %r" +#~ msgid "APN:" +#~ msgstr "APN:" + +#~ msgid "Title" +#~ msgstr "Titel" + +#~ msgid "Version" +#~ msgstr "Versie" + +#~ msgid "Date" +#~ msgstr "Datum" + +#~ msgid "Cannot obtain data needed for registration." +#~ msgstr "" +#~ "Kan vereiste gegevens die nodig zijn voor de registratie niet verkrijgen." + +#~ msgid "Unmount" +#~ msgstr "Loskoppelen" + #~ msgid "Restart" #~ msgstr "Herstarten" @@ -1290,12 +1464,6 @@ msgstr "Bron weergeven: %r" #~ msgid "Disconnecting..." #~ msgstr "Verbinding verbreken..." -#~ msgid "Mesh Network" -#~ msgstr "Mesh netwerk" - -#~ msgid "Disconnected" -#~ msgstr "Verbinding verbroken" - #~ msgid "About my XO" #~ msgstr "Over mijn XO" diff --git a/src/jarabe/__init__.py b/src/jarabe/__init__.py index 41b4b1c..ed2f639 100644 --- a/src/jarabe/__init__.py +++ b/src/jarabe/__init__.py @@ -23,4 +23,3 @@ refer to a command-line "shell" interface. # 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/config.py.in b/src/jarabe/config.py.in index 6c418e9..d22ee9a 100644 --- a/src/jarabe/config.py.in +++ b/src/jarabe/config.py.in @@ -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 -# pylint: disable-msg=C0301 +# pylint: disable=C0301 prefix = '@prefix@' data_path = '@prefix@/share/sugar/data' diff --git a/src/jarabe/controlpanel/__init__.py b/src/jarabe/controlpanel/__init__.py index a9dd95a..85f6a24 100644 --- a/src/jarabe/controlpanel/__init__.py +++ b/src/jarabe/controlpanel/__init__.py @@ -13,4 +13,3 @@ # 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/controlpanel/cmd.py b/src/jarabe/controlpanel/cmd.py index 7144b33..fe8f1a4 100644 --- a/src/jarabe/controlpanel/cmd.py +++ b/src/jarabe/controlpanel/cmd.py @@ -18,20 +18,21 @@ import sys import getopt import os from gettext import gettext as _ -import traceback import logging from jarabe import config + _RESTART = 1 -_same_option_warning = _("sugar-control-panel: WARNING, found more than" - " one option with the same name: %s module: %r") -_no_option_error = _("sugar-control-panel: key=%s not an available option") -_general_error = _("sugar-control-panel: %s") +_same_option_warning = _('sugar-control-panel: WARNING, found more than one' + ' option with the same name: %s module: %r') +_no_option_error = _('sugar-control-panel: key=%s not an available option') +_general_error = _('sugar-control-panel: %s') + def cmd_help(): - '''Print the help to the screen''' + """Print the help to the screen""" # TRANS: Translators, there's a empty line at the end of this string, # which must appear in the translated string (msgstr) as well. print _('Usage: sugar-control-panel [ option ] key [ args ... ] \n\ @@ -45,14 +46,16 @@ def cmd_help(): -c key clear the current value for the key \n\ ') + def note_restart(): - '''Instructions how to restart sugar''' + """Instructions how to restart sugar""" print _('To apply your changes you have to restart sugar.\n' + 'Hit ctrl+alt+erase on the keyboard to trigger a restart.') + def load_modules(): - '''Build a list of pointers to available modules and import them. - ''' + """Build a list of pointers to available modules and import them. + """ modules = [] path = os.path.join(config.ext_path, 'cpsection') @@ -65,16 +68,16 @@ def load_modules(): module = __import__('.'.join(('cpsection', item, 'model')), globals(), locals(), ['model']) except Exception: - logging.error('Exception while loading extension:\n' + \ - ''.join(traceback.format_exception(*sys.exc_info()))) + logging.exception('Exception while loading extension:') else: modules.append(module) return modules + def main(): try: - options, args = getopt.getopt(sys.argv[1:], "h:s:g:c:l", []) + options, args = getopt.getopt(sys.argv[1:], 'h:s:g:c:l', []) except getopt.GetoptError: cmd_help() sys.exit(2) @@ -87,7 +90,7 @@ def main(): for option, key in options: found = 0 - if option in ("-h"): + if option in ('-h'): for module in modules: method = getattr(module, 'set_' + key, None) if method: @@ -98,7 +101,7 @@ def main(): print _(_same_option_warning % (key, module)) if found == 0: print _(_no_option_error % key) - if option in ("-l"): + if option in ('-l'): for module in modules: methods = dir(module) print '%s:' % module.__name__.split('.')[1] @@ -106,9 +109,9 @@ def main(): if method.startswith('get_'): print ' %s' % method[4:] elif method.startswith('clear_'): - print " %s (use the -c argument with this option)" \ + print ' %s (use the -c argument with this option)' \ % method[6:] - if option in ("-g"): + if option in ('-g'): for module in modules: method = getattr(module, 'print_' + key, None) if method: @@ -122,7 +125,7 @@ def main(): print _(_same_option_warning % (key, module)) if found == 0: print _(_no_option_error % key) - if option in ("-s"): + if option in ('-s'): for module in modules: method = getattr(module, 'set_' + key, None) if method: @@ -139,7 +142,7 @@ def main(): print _(_same_option_warning % (key, module)) if found == 0: print _(_no_option_error % key) - if option in ("-c"): + if option in ('-c'): for module in modules: method = getattr(module, 'clear_' + key, None) if method: diff --git a/src/jarabe/controlpanel/gui.py b/src/jarabe/controlpanel/gui.py index 51d9820..2f55951 100644 --- a/src/jarabe/controlpanel/gui.py +++ b/src/jarabe/controlpanel/gui.py @@ -17,8 +17,6 @@ import os import logging from gettext import gettext as _ -import sys -import traceback import gobject import gtk @@ -32,8 +30,9 @@ from jarabe.controlpanel.toolbar import MainToolbar from jarabe.controlpanel.toolbar import SectionToolbar from jarabe import config + _logger = logging.getLogger('ControlPanel') -_MAX_COLUMNS = 5 + class ControlPanel(gtk.Window): __gtype_name__ = 'SugarControlPanel' @@ -41,6 +40,9 @@ class ControlPanel(gtk.Window): def __init__(self): gtk.Window.__init__(self) + self._max_columns = int(0.285 * (float(gtk.gdk.screen_width()) / + style.GRID_CELL_SIZE - 3)) + self.set_border_width(style.LINE_WIDTH) offset = style.GRID_CELL_SIZE width = gtk.gdk.screen_width() - offset * 2 @@ -74,7 +76,7 @@ class ControlPanel(gtk.Window): self.add(self._vbox) self._vbox.show() - self.connect("realize", self.__realize_cb) + self.connect('realize', self.__realize_cb) self._options = self._get_options() self._current_option = None @@ -110,6 +112,7 @@ class ControlPanel(gtk.Window): self._table = gtk.Table() self._table.set_col_spacings(style.GRID_CELL_SIZE) + self._table.set_row_spacings(style.GRID_CELL_SIZE) self._table.set_border_width(style.GRID_CELL_SIZE) self._scrolledwindow = gtk.ScrolledWindow() @@ -134,8 +137,17 @@ class ControlPanel(gtk.Window): except ImportError: del self._options['keyboard'] - row = 0 - column = 2 + # If the screen width only supports two columns, start + # placing from the second row. + if self._max_columns == 2: + row = 1 + column = 0 + else: + # About Me and About my computer are hardcoded below to use the + # first two slots so we need to leave them free. + row = 0 + column = 2 + options = self._options.keys() options.sort() @@ -157,7 +169,7 @@ class ControlPanel(gtk.Window): column, column + 1, row, row + 1) column += 1 - if column == _MAX_COLUMNS: + if column == self._max_columns: column = 0 row += 1 @@ -214,11 +226,16 @@ class ControlPanel(gtk.Window): globals(), locals(), ['model']) model = ModelWrapper(mod) - self._section_view = view_class(model, - self._options[option]['alerts']) + try: + self.get_window().set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH)) + self._section_view = view_class(model, + self._options[option]['alerts']) + + self._set_canvas(self._section_view) + self._section_view.show() + finally: + self.get_window().set_cursor(None) - self._set_canvas(self._section_view) - self._section_view.show() self._section_view.connect('notify::is-valid', self.__valid_section_cb) self._section_view.connect('request-close', @@ -227,13 +244,13 @@ class ControlPanel(gtk.Window): style.COLOR_WHITE.get_gdk_color()) def set_section_view_auto_close(self): - '''Automatically close the control panel if there is "nothing to do" - ''' + """Automatically close the control panel if there is "nothing to do" + """ self._section_view.auto_close = True def _get_options(self): - '''Get the available option information from the extensions - ''' + """Get the available option information from the extensions + """ options = {} path = os.path.join(config.ext_path, 'cpsection') @@ -259,11 +276,9 @@ class ControlPanel(gtk.Window): keywords.append(item) options[item]['keywords'] = keywords else: - _logger.error('There is no CLASS constant specifieds ' \ - 'in the view file \'%s\'.' % item) + _logger.error('no CLASS attribute in %r', item) except Exception: - logging.error('Exception while loading extension:\n' + \ - ''.join(traceback.format_exception(*sys.exc_info()))) + logging.exception('Exception while loading extension:') return options @@ -333,6 +348,7 @@ class ControlPanel(gtk.Window): section_is_valid = section_view.props.is_valid self._section_toolbar.accept_button.set_sensitive(section_is_valid) + class ModelWrapper(object): def __init__(self, module): self._module = module @@ -360,18 +376,15 @@ class ModelWrapper(object): except Exception, detail: _logger.debug('Error undo option: %s', detail) + class _SectionIcon(gtk.EventBox): - __gtype_name__ = "SugarSectionIcon" + __gtype_name__ = 'SugarSectionIcon' __gproperties__ = { - 'icon-name' : (str, None, None, None, - gobject.PARAM_READWRITE), - 'pixel-size' : (object, None, None, - gobject.PARAM_READWRITE), - 'xo-color' : (object, None, None, - gobject.PARAM_READWRITE), - 'title' : (str, None, None, None, - gobject.PARAM_READWRITE) + 'icon-name': (str, None, None, None, gobject.PARAM_READWRITE), + 'pixel-size': (object, None, None, gobject.PARAM_READWRITE), + 'xo-color': (object, None, None, gobject.PARAM_READWRITE), + 'title': (str, None, None, None, gobject.PARAM_READWRITE), } def __init__(self, **kwargs): diff --git a/src/jarabe/controlpanel/inlinealert.py b/src/jarabe/controlpanel/inlinealert.py index b1880da..f970af4 100644 --- a/src/jarabe/controlpanel/inlinealert.py +++ b/src/jarabe/controlpanel/inlinealert.py @@ -21,6 +21,7 @@ import pango from sugar.graphics import style from sugar.graphics.icon import Icon + class InlineAlert(gtk.HBox): """UI interface for Inline alerts @@ -36,11 +37,9 @@ class InlineAlert(gtk.HBox): __gtype_name__ = 'SugarInlineAlert' __gproperties__ = { - 'msg' : (str, None, None, None, - gobject.PARAM_READWRITE), - 'icon' : (object, None, None, - gobject.PARAM_WRITABLE) - } + 'msg': (str, None, None, None, gobject.PARAM_READWRITE), + 'icon': (object, None, None, gobject.PARAM_WRITABLE), + } def __init__(self, **kwargs): @@ -80,4 +79,3 @@ class InlineAlert(gtk.HBox): def do_get_property(self, pspec): if pspec.name == 'msg': return self._msg - diff --git a/src/jarabe/controlpanel/sectionview.py b/src/jarabe/controlpanel/sectionview.py index 4de27a2..4b5751f 100644 --- a/src/jarabe/controlpanel/sectionview.py +++ b/src/jarabe/controlpanel/sectionview.py @@ -18,18 +18,17 @@ import gobject import gtk from gettext import gettext as _ + class SectionView(gtk.VBox): __gtype_name__ = 'SugarSectionView' __gsignals__ = { - 'request-close': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([])) - } + 'request-close': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), + } __gproperties__ = { - 'is_valid' : (bool, None, None, True, - gobject.PARAM_READWRITE) - } + 'is_valid': (bool, None, None, True, gobject.PARAM_READWRITE), + } _APPLY_TIMEOUT = 1000 @@ -51,5 +50,5 @@ class SectionView(gtk.VBox): return self._is_valid def undo(self): - '''Undo here the changes that have been made in this section.''' + """Undo here the changes that have been made in this section.""" pass diff --git a/src/jarabe/controlpanel/toolbar.py b/src/jarabe/controlpanel/toolbar.py index 320a8eb..fca34a0 100644 --- a/src/jarabe/controlpanel/toolbar.py +++ b/src/jarabe/controlpanel/toolbar.py @@ -25,6 +25,7 @@ from sugar.graphics.toolbutton import ToolButton from sugar.graphics import iconentry from sugar.graphics import style + class MainToolbar(gtk.Toolbar): """ Main toolbar of the control panel """ @@ -36,8 +37,9 @@ class MainToolbar(gtk.Toolbar): ([])), 'search-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([str])) + ([str])), } + def __init__(self): gtk.Toolbar.__init__(self) @@ -83,6 +85,7 @@ class MainToolbar(gtk.Toolbar): def __stop_clicked_cb(self, button): self.emit('stop-clicked') + class SectionToolbar(gtk.Toolbar): """ Toolbar of the sections of the control panel """ @@ -94,8 +97,9 @@ class SectionToolbar(gtk.Toolbar): ([])), 'accept-clicked': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([])) + ([])), } + def __init__(self): gtk.Toolbar.__init__(self) @@ -154,4 +158,3 @@ class SectionToolbar(gtk.Toolbar): def __accept_button_clicked_cb(self, widget, data=None): self.emit('accept-clicked') - diff --git a/src/jarabe/desktop/Makefile.am b/src/jarabe/desktop/Makefile.am index 5b47455..25fb0b4 100644 --- a/src/jarabe/desktop/Makefile.am +++ b/src/jarabe/desktop/Makefile.am @@ -11,7 +11,7 @@ sugar_PYTHON = \ homewindow.py \ keydialog.py \ meshbox.py \ - myicon.py \ + networkviews.py \ schoolserver.py \ snowflakelayout.py \ spreadlayout.py \ diff --git a/src/jarabe/desktop/__init__.py b/src/jarabe/desktop/__init__.py index a9dd95a..85f6a24 100644 --- a/src/jarabe/desktop/__init__.py +++ b/src/jarabe/desktop/__init__.py @@ -13,4 +13,3 @@ # 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/desktop/activitieslist.py b/src/jarabe/desktop/activitieslist.py index 87f2af0..0370ef3 100644 --- a/src/jarabe/desktop/activitieslist.py +++ b/src/jarabe/desktop/activitieslist.py @@ -30,19 +30,18 @@ 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 from jarabe.model import bundleregistry from jarabe.view.palettes import ActivityPalette -from jarabe.view import launcher +from jarabe.journal import misc + class ActivitiesTreeView(gtk.TreeView): __gtype_name__ = 'SugarActivitiesTreeView' __gsignals__ = { - 'erase-activated' : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([str])) + 'erase-activated': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([str])), } def __init__(self): @@ -143,13 +142,7 @@ class ActivitiesTreeView(gtk.TreeView): registry = bundleregistry.get_registry() bundle = registry.get_bundle(row[ListModel.COLUMN_BUNDLE_ID]) - activity_id = activityfactory.create_activity_id() - - client = gconf.client_get_default() - xo_color = XoColor(client.get_string('/desktop/sugar/user/color')) - - launcher.add_launcher(activity_id, bundle.get_icon(), xo_color) - activityfactory.create(bundle, ActivityHandle(activity_id)) + misc.launch(bundle) def set_filter(self, query): self._query = query.lower() @@ -159,6 +152,7 @@ class ActivitiesTreeView(gtk.TreeView): title = model[tree_iter][ListModel.COLUMN_TITLE] return title is not None and title.lower().find(self._query) > -1 + class ListModel(gtk.TreeModelSort): __gtype_name__ = 'SugarListModel' @@ -172,7 +166,7 @@ class ListModel(gtk.TreeModelSort): COLUMN_DATE_TEXT = 7 def __init__(self): - self._model = gtk.ListStore(str, bool, str, str, int, str, int, str) + self._model = gtk.ListStore(str, bool, str, str, str, str, int, str) self._model_filter = self._model.filter_new() gtk.TreeModelSort.__init__(self, self._model_filter) @@ -243,6 +237,7 @@ class ListModel(gtk.TreeModelSort): def refilter(self): self._model_filter.refilter() + class CellRendererFavorite(CellRendererIcon): __gtype_name__ = 'SugarCellRendererFavorite' @@ -257,12 +252,13 @@ class CellRendererFavorite(CellRendererIcon): self.props.prelit_stroke_color = style.COLOR_BUTTON_GREY.get_svg() self.props.prelit_fill_color = style.COLOR_BUTTON_GREY.get_svg() + class CellRendererActivityIcon(CellRendererIcon): __gtype_name__ = 'SugarCellRendererActivityIcon' __gsignals__ = { - 'erase-activated' : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([str])) + 'erase-activated': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([str])), } def __init__(self, tree_view): @@ -295,6 +291,7 @@ class CellRendererActivityIcon(CellRendererIcon): def __erase_activated_cb(self, palette, bundle_id): self.emit('erase-activated', bundle_id) + class ActivitiesList(gtk.VBox): __gtype_name__ = 'SugarActivitiesList' @@ -376,14 +373,15 @@ class ActivitiesList(gtk.VBox): if response_id == gtk.RESPONSE_OK: registry = bundleregistry.get_registry() bundle = registry.get_bundle(bundle_id) - registry.uninstall(bundle) + registry.uninstall(bundle, delete_profile=True) + class ActivityListPalette(ActivityPalette): __gtype_name__ = 'SugarActivityListPalette' __gsignals__ = { - 'erase-activated' : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([str])) + 'erase-activated': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([str])), } def __init__(self, activity_info): @@ -406,13 +404,7 @@ class ActivityListPalette(ActivityPalette): self._favorite_item.show() if activity_info.is_user_activity(): - 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 + self._add_erase_option(registry, activity_info) registry = bundleregistry.get_registry() self._activity_changed_sid = registry.connect('bundle_changed', @@ -421,6 +413,16 @@ class ActivityListPalette(ActivityPalette): self.connect('destroy', self.__destroy_cb) + def _add_erase_option(self, registry, activity_info): + 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) or \ + registry.is_activity_protected(self._bundle_id): + menu_item.props.sensitive = False + def __destroy_cb(self, palette): self.disconnect(self._activity_changed_sid) @@ -433,7 +435,7 @@ class ActivityListPalette(ActivityPalette): else: label.set_text(_('Make favorite')) client = gconf.client_get_default() - xo_color = XoColor(client.get_string("/desktop/sugar/user/color")) + xo_color = XoColor(client.get_string('/desktop/sugar/user/color')) self._favorite_icon.props.xo_color = xo_color @@ -453,4 +455,3 @@ class ActivityListPalette(ActivityPalette): 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 85e1b59..360c147 100644 --- a/src/jarabe/desktop/favoriteslayout.py +++ b/src/jarabe/desktop/favoriteslayout.py @@ -1,4 +1,5 @@ # Copyright (C) 2008 One Laptop Per Child +# Copyright (C) 2010 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 @@ -28,10 +29,18 @@ from sugar.graphics import style from jarabe.model import bundleregistry from jarabe.desktop.grid import Grid + _logger = logging.getLogger('FavoritesLayout') _CELL_SIZE = 4 _BASE_SCALE = 1000 +_INTERMEDIATE_B = (style.STANDARD_ICON_SIZE + style.SMALL_ICON_SIZE) / 2 +_INTERMEDIATE_A = (style.STANDARD_ICON_SIZE + _INTERMEDIATE_B) / 2 +_INTERMEDIATE_C = (_INTERMEDIATE_B + style.SMALL_ICON_SIZE) / 2 +_ICON_SIZES = [style.MEDIUM_ICON_SIZE, style.STANDARD_ICON_SIZE, + _INTERMEDIATE_A, _INTERMEDIATE_B, _INTERMEDIATE_C, + style.SMALL_ICON_SIZE] + class FavoritesLayout(gobject.GObject, hippo.CanvasLayout): """Base class of the different layout types.""" @@ -81,7 +90,8 @@ class FavoritesLayout(gobject.GObject, hippo.CanvasLayout): if icon not in self.box.get_children(): raise ValueError('Child not in box.') - if not(hasattr(icon, 'get_bundle_id') and hasattr(icon, 'get_version')): + if not (hasattr(icon, 'get_bundle_id') and + hasattr(icon, 'get_version')): logging.debug('Not an activity icon %r', icon) return @@ -101,6 +111,7 @@ class FavoritesLayout(gobject.GObject, hippo.CanvasLayout): def allow_dnd(self): return False + class RandomLayout(FavoritesLayout): """Lay out icons randomly; try to nudge them around to resolve overlaps.""" @@ -181,13 +192,19 @@ class RandomLayout(FavoritesLayout): def allow_dnd(self): return True + _MINIMUM_RADIUS = style.XLARGE_ICON_SIZE / 2 + style.DEFAULT_SPACING + \ style.STANDARD_ICON_SIZE * 2 _MAXIMUM_RADIUS = (gtk.gdk.screen_height() - style.GRID_CELL_SIZE) / 2 - \ style.STANDARD_ICON_SIZE - style.DEFAULT_SPACING +_ICON_SPACING_FACTORS = [1.5, 1.4, 1.3, 1.2, 1.1, 1.0] +_SPIRAL_SPACING_FACTORS = [1.5, 1.5, 1.5, 1.4, 1.3, 1.2] +_MIMIMUM_RADIUS_ENCROACHMENT = 0.75 +_INITIAL_ANGLE = math.pi + class RingLayout(FavoritesLayout): - """Lay out icons in a ring around the XO man.""" + """Lay out icons in a ring or spiral around the XO man.""" __gtype_name__ = 'RingLayout' icon_name = 'view-radial' @@ -201,6 +218,7 @@ class RingLayout(FavoritesLayout): def __init__(self): FavoritesLayout.__init__(self) self._locked_children = {} + self._spiral_mode = False def append(self, icon, locked=False): FavoritesLayout.append(self, icon, locked) @@ -221,33 +239,78 @@ class RingLayout(FavoritesLayout): self._locked_children[child] = (x, y) def _calculate_radius_and_icon_size(self, children_count): - # what's the radius required without downscaling? - distance = style.STANDARD_ICON_SIZE + style.DEFAULT_SPACING + """ Adjust the ring or spiral radius and icon size as needed. """ + self._spiral_mode = False + distance = style.MEDIUM_ICON_SIZE + style.DEFAULT_SPACING * \ + _ICON_SPACING_FACTORS[_ICON_SIZES.index(style.MEDIUM_ICON_SIZE)] + radius = max(children_count * distance / (2 * math.pi), + _MINIMUM_RADIUS) + if radius < _MAXIMUM_RADIUS: + return radius, style.MEDIUM_ICON_SIZE + + distance = style.STANDARD_ICON_SIZE + style.DEFAULT_SPACING * \ + _ICON_SPACING_FACTORS[_ICON_SIZES.index(style.STANDARD_ICON_SIZE)] + radius = max(children_count * distance / (2 * math.pi), + _MINIMUM_RADIUS) + if radius < _MAXIMUM_RADIUS: + return radius, style.STANDARD_ICON_SIZE + + self._spiral_mode = True icon_size = style.STANDARD_ICON_SIZE - # circumference is 2*pi*r; we want this to be at least - # 'children_count * distance' - radius = children_count * distance / (2 * math.pi) - # limit computed radius to reasonable bounds. - radius = max(radius, _MINIMUM_RADIUS) - radius = min(radius, _MAXIMUM_RADIUS) - # recompute icon size from limited radius - if children_count > 0: - icon_size = (2 * math.pi * radius / children_count) \ - - style.DEFAULT_SPACING - # limit adjusted icon size. - icon_size = max(icon_size, style.SMALL_ICON_SIZE) - icon_size = min(icon_size, style.MEDIUM_ICON_SIZE) + angle_, radius = self._calculate_angle_and_radius(children_count, + icon_size) + while radius > _MAXIMUM_RADIUS: + i = _ICON_SIZES.index(icon_size) + if i < len(_ICON_SIZES) - 1: + icon_size = _ICON_SIZES[i + 1] + angle_, radius = self._calculate_angle_and_radius( + children_count, icon_size) + else: + break return radius, icon_size - def _calculate_position(self, radius, icon_size, index, children_count, - sin=math.sin, cos=math.cos): + def _calculate_position(self, radius, icon_size, icon_index, + children_count, sin=math.sin, cos=math.cos): + """ Calculate an icon position on a circle or a spiral. """ width, height = self.box.get_allocation() - angle = index * (2 * math.pi / children_count) - math.pi / 2 - x = radius * cos(angle) + (width - icon_size) / 2 - y = radius * sin(angle) + (height - icon_size - - (style.GRID_CELL_SIZE/2) ) / 2 + if self._spiral_mode: + min_width_, box_width = self.box.get_width_request() + min_height_, box_height = self.box.get_height_request(box_width) + angle, radius = self._calculate_angle_and_radius(icon_index, + icon_size) + x, y = self._convert_from_polar_to_cartesian(angle, radius, + icon_size, + width, height) + else: + angle = icon_index * (2 * math.pi / children_count) - math.pi / 2 + x = radius * cos(angle) + (width - icon_size) / 2 + y = radius * sin(angle) + (height - icon_size - \ + (style.GRID_CELL_SIZE / 2)) / 2 + return x, y + + def _convert_from_polar_to_cartesian(self, angle, radius, icon_size, width, + height): + """ Convert angle, radius to x, y """ + x = int(math.sin(angle) * radius) + y = int(math.cos(angle) * radius) + x = - x + (width - icon_size) / 2 + y = y + (height - icon_size - (style.GRID_CELL_SIZE / 2)) / 2 return x, y + def _calculate_angle_and_radius(self, icon_count, icon_size): + """ Based on icon_count and icon_size, calculate radius and angle. """ + spiral_spacing = _SPIRAL_SPACING_FACTORS[_ICON_SIZES.index(icon_size)] + icon_spacing = icon_size + style.DEFAULT_SPACING * \ + _ICON_SPACING_FACTORS[_ICON_SIZES.index(icon_size)] + angle = _INITIAL_ANGLE + radius = _MINIMUM_RADIUS - (icon_size * _MIMIMUM_RADIUS_ENCROACHMENT) + for i_ in range(icon_count): + circumference = radius * 2 * math.pi + n = circumference / icon_spacing + angle += (2 * math.pi / n) + radius += (float(icon_spacing) * spiral_spacing / n) + return angle, radius + def _get_children_in_ring(self): children_in_ring = [child for child in self.box.get_layout_children() \ if child not in self._locked_children] @@ -294,6 +357,7 @@ class RingLayout(FavoritesLayout): else: return 0 + _SUNFLOWER_CONSTANT = style.STANDARD_ICON_SIZE * .75 """Chose a constant such that STANDARD_ICON_SIZE icons are nicely spaced.""" @@ -319,6 +383,7 @@ This is the golden angle: http://en.wikipedia.org/wiki/Golden_angle Calculation: math.radians(360) / ( _GOLDEN_RATIO * _GOLDEN_RATIO ) """ + class SunflowerLayout(RingLayout): """Spiral layout based on Fibonacci ratio in phyllotaxis. @@ -346,7 +411,8 @@ class SunflowerLayout(RingLayout): return None, style.STANDARD_ICON_SIZE def adjust_index(self, i): - """Skip floret indices which end up outside the desired bounding box.""" + """Skip floret indices which end up outside the desired bounding box. + """ for idx in self.skipped_indices: if i < idx: break @@ -377,7 +443,7 @@ class SunflowerLayout(RingLayout): # removed to make room for the "active activity" icon. x = r * cos(phi) + (width - icon_size) / 2 y = r * sin(phi) + (height - icon_size - \ - (style.GRID_CELL_SIZE / 2) ) / 2 + (style.GRID_CELL_SIZE / 2)) / 2 # skip allocations outside the allocation box. # give up once we can't fit @@ -385,10 +451,12 @@ class SunflowerLayout(RingLayout): if y < 0 or y > (height - icon_size) or \ x < 0 or x > (width - icon_size): self.skipped_indices.append(index) - continue # try again + # try again + continue return x, y + class BoxLayout(RingLayout): """Lay out icons in a square around the XO man.""" @@ -421,14 +489,16 @@ class BoxLayout(RingLayout): return (90 - d) / 45. if d < 225: return -1 - return cos_d(360 - d) # mirror around 180 + # mirror around 180 + return cos_d(360 - d) cos = lambda r: cos_d(math.degrees(r)) sin = lambda r: cos_d(math.degrees(r) - 90) - return RingLayout._calculate_position\ - (self, radius, icon_size, index, children_count, - sin=sin, cos=cos) + return RingLayout._calculate_position(self, radius, icon_size, index, + children_count, sin=sin, + cos=cos) + class TriangleLayout(RingLayout): """Lay out icons in a triangle around the XO man.""" @@ -467,7 +537,8 @@ class TriangleLayout(RingLayout): return (d + 90) / 120. if d <= 90: return (90 - d) / 60. - return -cos_d(180 - d) # mirror around 90 + # mirror around 90 + return -cos_d(180 - d) sqrt_3 = math.sqrt(3) @@ -478,11 +549,12 @@ class TriangleLayout(RingLayout): return ((d + 90) / 120.) * sqrt_3 - 1 if d <= 90: return sqrt_3 - 1 - return sin_d(180 - d) # mirror around 90 + # mirror around 90 + return sin_d(180 - d) cos = lambda r: cos_d(math.degrees(r)) sin = lambda r: sin_d(math.degrees(r)) - return RingLayout._calculate_position\ - (self, radius, icon_size, index, children_count, - sin=sin, cos=cos) + return RingLayout._calculate_position(self, radius, icon_size, index, + children_count, sin=sin, + cos=cos) diff --git a/src/jarabe/desktop/favoritesview.py b/src/jarabe/desktop/favoritesview.py index aca945a..b4a4e75 100644 --- a/src/jarabe/desktop/favoritesview.py +++ b/src/jarabe/desktop/favoritesview.py @@ -30,25 +30,23 @@ from sugar.graphics.menuitem import MenuItem from sugar.graphics.alert import Alert from sugar.graphics.xocolor import XoColor from sugar.activity import activityfactory -from sugar.activity.activityhandle import ActivityHandle -from sugar.presence import presenceservice from sugar import dispatch from sugar.datastore import datastore from jarabe.view.palettes import JournalPalette from jarabe.view.palettes import CurrentActivityPalette, ActivityPalette +from jarabe.view.buddyicon import BuddyIcon from jarabe.view.buddymenu import BuddyMenu -from jarabe.view import launcher -from jarabe.model.buddy import BuddyModel +from jarabe.model.buddy import get_owner_instance from jarabe.model import shell from jarabe.model import bundleregistry from jarabe.journal import misc from jarabe.desktop import schoolserver from jarabe.desktop.schoolserver import RegisterError -from jarabe.desktop.myicon import MyIcon from jarabe.desktop import favoriteslayout + _logger = logging.getLogger('FavoritesView') _ICON_DND_TARGET = ('activity-icon', gtk.TARGET_SAME_WIDGET, 0) @@ -62,6 +60,9 @@ LAYOUT_MAP = {favoriteslayout.RingLayout.key: favoriteslayout.RingLayout, `FavoritesLayout` which implement the layouts. Additional information about the layout can be accessed with fields of the class.""" +_favorites_settings = None + + class FavoritesView(hippo.Canvas): __gtype_name__ = 'SugarFavoritesView' @@ -82,7 +83,7 @@ class FavoritesView(hippo.Canvas): self._box.props.background_color = style.COLOR_WHITE.get_int() self.set_root(self._box) - self._my_icon = _MyIcon(style.XLARGE_ICON_SIZE) + self._my_icon = OwnerIcon(style.XLARGE_ICON_SIZE) self._my_icon.connect('register-activate', self.__register_activate_cb) self._box.append(self._my_icon) @@ -172,7 +173,8 @@ class FavoritesView(hippo.Canvas): height = allocation.height min_w_, my_icon_width = self._my_icon.get_width_request() - min_h_, my_icon_height = self._my_icon.get_height_request(my_icon_width) + min_h_, my_icon_height = self._my_icon.get_height_request( + my_icon_width) x = (width - my_icon_width) / 2 y = (height - my_icon_height - style.GRID_CELL_SIZE) / 2 self._layout.move_icon(self._my_icon, x, y, locked=True) @@ -190,7 +192,8 @@ class FavoritesView(hippo.Canvas): # TODO: Dnd methods. This should be merged somehow inside hippo-canvas. def __button_press_event_cb(self, widget, event): if event.button == 1 and event.type == gtk.gdk.BUTTON_PRESS: - self._last_clicked_icon = self._get_icon_at_coords(event.x, event.y) + self._last_clicked_icon = self._get_icon_at_coords(event.x, + event.y) if self._last_clicked_icon is not None: self._pressed_button = event.button self._press_start_x = event.x @@ -203,9 +206,9 @@ class FavoritesView(hippo.Canvas): icon_x, icon_y = icon.get_context().translate_to_widget(icon) icon_width, icon_height = icon.get_allocation() - if (x >= icon_x ) and (x <= icon_x + icon_width) and \ - (y >= icon_y ) and (y <= icon_y + icon_height) and \ - isinstance(icon, ActivityIcon): + if (x >= icon_x) and (x <= icon_x + icon_width) and \ + (y >= icon_y) and (y <= icon_y + icon_height) and \ + isinstance(icon, ActivityIcon): return icon return None @@ -274,7 +277,7 @@ class FavoritesView(hippo.Canvas): def _set_layout(self, layout): if layout not in LAYOUT_MAP: - logging.warn('Unknown favorites layout: %r' % layout) + logging.warn('Unknown favorites layout: %r', layout) layout = favoriteslayout.RingLayout.key assert layout in LAYOUT_MAP @@ -327,7 +330,7 @@ class FavoritesView(hippo.Canvas): alert.props.title = _('Registration Successful') alert.props.msg = _('You are now registered ' \ 'with your school server.') - self._my_icon.remove_register_menu() + self._my_icon.set_registered() ok_icon = Icon(icon_name='dialog-ok') alert.add_button(gtk.RESPONSE_OK, _('Ok'), ok_icon) @@ -387,7 +390,7 @@ class ActivityIcon(CanvasIcon): break def _get_last_activity_async(self, bundle_id, properties): - query = {'activity': bundle_id} + query = {'activity': bundle_id} datastore.find(query, sorting=['+timestamp'], limit=self._MAX_RESUME_ENTRIES, properties=properties, @@ -395,8 +398,8 @@ class ActivityIcon(CanvasIcon): error_handler=self.__get_last_activity_error_handler_cb) def __get_last_activity_reply_handler_cb(self, entries, total_count): - # If there's a problem with the DS index, we may get entries not related - # to this activity. + # If there's a problem with the DS index, we may get entries not + # related to this activity. checked_entries = [] for entry in entries: if entry['activity'] == self.bundle_id: @@ -484,16 +487,7 @@ class ActivityIcon(CanvasIcon): if self._resume_mode and self._journal_entries: self._resume(self._journal_entries[0]) else: - client = gconf.client_get_default() - xo_color = XoColor(client.get_string('/desktop/sugar/user/color')) - - activity_id = activityfactory.create_activity_id() - launcher.add_launcher(activity_id, - self._activity_info.get_icon(), - xo_color) - - handle = ActivityHandle(activity_id) - activityfactory.create(self._activity_info, handle) + misc.launch(self._activity_info) def get_bundle_id(self): return self._activity_info.get_bundle_id() @@ -516,6 +510,7 @@ class ActivityIcon(CanvasIcon): self._resume_mode = resume_mode self._update() + class FavoritePalette(ActivityPalette): __gtype_name__ = 'SugarFavoritePalette' @@ -564,6 +559,7 @@ class FavoritePalette(ActivityPalette): if entry is not None: self.emit('entry-activate', entry) + class CurrentActivityIcon(CanvasIcon, hippo.CanvasItem): def __init__(self): CanvasIcon.__init__(self, cache=True) @@ -602,17 +598,18 @@ class CurrentActivityIcon(CanvasIcon, hippo.CanvasItem): self._home_activity = home_activity self._update() -class _MyIcon(MyIcon): - __gtype_name__ = 'SugarFavoritesMyIcon' + +class OwnerIcon(BuddyIcon): + __gtype_name__ = 'SugarFavoritesOwnerIcon' __gsignals__ = { - 'register-activate' : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([])) + 'register-activate': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([])), } - def __init__(self, scale): - MyIcon.__init__(self, scale) - self._power_manager = None + def __init__(self, size): + BuddyIcon.__init__(self, buddy=get_owner_instance(), size=size) + self._palette_enabled = False self._register_menu = None @@ -621,17 +618,20 @@ class _MyIcon(MyIcon): self._palette_enabled = True return - presence_service = presenceservice.get_instance() - owner = BuddyModel(buddy=presence_service.get_owner()) - palette = BuddyMenu(owner) + palette = BuddyMenu(get_owner_instance()) client = gconf.client_get_default() backup_url = client.get_string('/desktop/sugar/backup_url') + if not backup_url: self._register_menu = MenuItem(_('Register'), 'media-record') - self._register_menu.connect('activate', self.__register_activate_cb) - palette.menu.append(self._register_menu) - self._register_menu.show() + else: + self._register_menu = MenuItem(_('Register again'), + 'media-record') + + self._register_menu.connect('activate', self.__register_activate_cb) + palette.menu.append(self._register_menu) + self._register_menu.show() return palette @@ -641,12 +641,17 @@ class _MyIcon(MyIcon): def __register_activate_cb(self, menuitem): self.emit('register-activate') - def remove_register_menu(self): + def set_registered(self): self.palette.menu.remove(self._register_menu) + self._register_menu = MenuItem(_('Register again'), 'media-record') + self._register_menu.connect('activate', self.__register_activate_cb) + self.palette.menu.append(self._register_menu) + self._register_menu.show() + class FavoritesSetting(object): - _FAVORITES_KEY = "/desktop/sugar/desktop/favorites_layout" + _FAVORITES_KEY = '/desktop/sugar/desktop/favorites_layout' def __init__(self): client = gconf.client_get_default() @@ -672,7 +677,6 @@ class FavoritesSetting(object): layout = property(get_layout, set_layout) -_favorites_settings = None def get_settings(): global _favorites_settings diff --git a/src/jarabe/desktop/friendview.py b/src/jarabe/desktop/friendview.py index 4c5f1c8..8dab35f 100644 --- a/src/jarabe/desktop/friendview.py +++ b/src/jarabe/desktop/friendview.py @@ -1,4 +1,5 @@ # Copyright (C) 2006-2007 Red Hat, Inc. +# Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> # # 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 @@ -18,17 +19,15 @@ import hippo from sugar.graphics.icon import CanvasIcon from sugar.graphics import style -from sugar.presence import presenceservice from jarabe.view.buddyicon import BuddyIcon from jarabe.model import bundleregistry + class FriendView(hippo.CanvasBox): def __init__(self, buddy, **kwargs): hippo.CanvasBox.__init__(self, **kwargs) - self._pservice = presenceservice.get_instance() - self._buddy = buddy self._buddy_icon = BuddyIcon(buddy) self._buddy_icon.props.size = style.LARGE_ICON_SIZE @@ -37,14 +36,12 @@ class FriendView(hippo.CanvasBox): self._activity_icon = CanvasIcon(size=style.LARGE_ICON_SIZE) self._activity_icon_visible = False - if self._buddy.is_present(): - self._buddy_appeared_cb(buddy) + self._update_activity() - self._buddy.connect('current-activity-changed', - self._buddy_activity_changed_cb) - self._buddy.connect('appeared', self._buddy_appeared_cb) - self._buddy.connect('disappeared', self._buddy_disappeared_cb) - self._buddy.connect('color-changed', self._buddy_color_changed_cb) + self._buddy.connect('notify::current-activity', + self.__buddy_notify_current_activity_cb) + self._buddy.connect('notify::present', self.__buddy_notify_present_cb) + self._buddy.connect('notify::color', self.__buddy_notify_color_cb) def _get_new_icon_name(self, ps_activity): registry = bundleregistry.get_registry() @@ -58,30 +55,30 @@ class FriendView(hippo.CanvasBox): self.remove(self._activity_icon) self._activity_icon_visible = False - def _buddy_activity_changed_cb(self, buddy, ps_activity=None): - if not ps_activity: + def __buddy_notify_current_activity_cb(self, buddy, pspec): + self._update_activity() + + def _update_activity(self): + if not self._buddy.props.present or \ + not self._buddy.props.current_activity: self._remove_activity_icon() return # FIXME: use some sort of "unknown activity" icon rather # than hiding the icon? - name = self._get_new_icon_name(ps_activity) + name = self._get_new_icon_name(self._buddy.current_activity) if name: self._activity_icon.props.file_name = name - self._activity_icon.props.xo_color = buddy.get_color() + self._activity_icon.props.xo_color = self._buddy.props.color if not self._activity_icon_visible: self.append(self._activity_icon, hippo.PACK_EXPAND) self._activity_icon_visible = True else: self._remove_activity_icon() - def _buddy_appeared_cb(self, buddy): - home_activity = self._buddy.get_current_activity() - self._buddy_activity_changed_cb(buddy, home_activity) - - def _buddy_disappeared_cb(self, buddy): - self._buddy_activity_changed_cb(buddy, None) + def __buddy_notify_present_cb(self, buddy, pspec): + self._update_activity() - def _buddy_color_changed_cb(self, buddy, color): + def __buddy_notify_color_cb(self, buddy, pspec): # TODO: shouldn't this change self._buddy_icon instead? - self._activity_icon.props.xo_color = buddy.get_color() + self._activity_icon.props.xo_color = buddy.props.color diff --git a/src/jarabe/desktop/grid.py b/src/jarabe/desktop/grid.py index f3412c9..eab4033 100644 --- a/src/jarabe/desktop/grid.py +++ b/src/jarabe/desktop/grid.py @@ -22,17 +22,19 @@ import gtk from sugar import _sugarext + _PLACE_TRIALS = 20 _MAX_WEIGHT = 255 _REFRESH_RATE = 200 _MAX_COLLISIONS_PER_REFRESH = 20 + class Grid(_sugarext.Grid): __gsignals__ = { - 'child-changed' : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])) + 'child-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])), } + def __init__(self, width, height): gobject.GObject.__init__(self) @@ -185,7 +187,8 @@ class Grid(_sugarext.Grid): for c in self._children: intersection = child_rect.intersect(self._child_rects[c]) if c != child and intersection.width > 0: - if c not in self._locked_children and c not in self._collisions: + if (c not in self._locked_children and + c not in self._collisions): collision_found = True self._collisions.append(c) diff --git a/src/jarabe/desktop/groupbox.py b/src/jarabe/desktop/groupbox.py index 76c2981..ed8f8ae 100644 --- a/src/jarabe/desktop/groupbox.py +++ b/src/jarabe/desktop/groupbox.py @@ -23,18 +23,19 @@ import gconf from sugar.graphics import style from sugar.graphics.icon import CanvasIcon from sugar.graphics.xocolor import XoColor -from sugar.presence import presenceservice from jarabe.view.buddymenu import BuddyMenu -from jarabe.model.buddy import BuddyModel +from jarabe.model.buddy import get_owner_instance from jarabe.model import friends from jarabe.desktop.friendview import FriendView from jarabe.desktop.spreadlayout import SpreadLayout + class GroupBox(hippo.Canvas): __gtype_name__ = 'SugarGroupBox' + def __init__(self): - logging.debug("STARTUP: Loading the group view") + logging.debug('STARTUP: Loading the group view') gobject.GObject.__init__(self) @@ -48,15 +49,13 @@ class GroupBox(hippo.Canvas): self._box.set_layout(self._layout) client = gconf.client_get_default() - color = XoColor(client.get_string("/desktop/sugar/user/color")) + color = XoColor(client.get_string('/desktop/sugar/user/color')) self._owner_icon = CanvasIcon(icon_name='computer-xo', cache=True, xo_color=color) self._owner_icon.props.size = style.LARGE_ICON_SIZE - presence_service = presenceservice.get_instance() - owner = BuddyModel(buddy=presence_service.get_owner()) - self._owner_icon.set_palette(BuddyMenu(owner)) + self._owner_icon.set_palette(BuddyMenu(get_owner_instance())) self._layout.add(self._owner_icon) friends_model = friends.get_model() diff --git a/src/jarabe/desktop/homebox.py b/src/jarabe/desktop/homebox.py index 85279ff..661326e 100644 --- a/src/jarabe/desktop/homebox.py +++ b/src/jarabe/desktop/homebox.py @@ -30,16 +30,18 @@ from sugar.graphics.icon import Icon from jarabe.desktop import favoritesview from jarabe.desktop.activitieslist import ActivitiesList + _FAVORITES_VIEW = 0 _LIST_VIEW = 1 _AUTOSEARCH_TIMEOUT = 1000 + class HomeBox(gtk.VBox): __gtype_name__ = 'SugarHomeBox' def __init__(self): - logging.debug("STARTUP: Loading the home view") + logging.debug('STARTUP: Loading the home view') gobject.GObject.__init__(self) @@ -57,7 +59,7 @@ class HomeBox(gtk.VBox): def show_software_updates_alert(self): alert = Alert() updater_icon = Icon(icon_name='module-updater', - pixel_size = style.STANDARD_ICON_SIZE) + pixel_size=style.STANDARD_ICON_SIZE) alert.props.icon = updater_icon updater_icon.show() alert.props.title = _('Software Update') @@ -125,7 +127,7 @@ class HomeBox(gtk.VBox): else: raise ValueError('Invalid view: %r' % view) - _REDRAW_TIMEOUT = 5 * 60 * 1000 # 5 minutes + _REDRAW_TIMEOUT = 5 * 60 * 1000 # 5 minutes def resume(self): pass @@ -144,16 +146,15 @@ class HomeBox(gtk.VBox): def set_resume_mode(self, resume_mode): self._favorites_view.set_resume_mode(resume_mode) + class HomeToolbar(gtk.Toolbar): __gtype_name__ = 'SugarHomeToolbar' __gsignals__ = { - 'query-changed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, + 'query-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([str])), - 'view-changed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([object])) + 'view-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object])), } def __init__(self): @@ -239,13 +240,14 @@ class HomeToolbar(gtk.Toolbar): if self._autosearch_timer: gobject.source_remove(self._autosearch_timer) self._autosearch_timer = gobject.timeout_add(_AUTOSEARCH_TIMEOUT, - self.__autosearch_timer_cb) + self.__autosearch_timer_cb) def __autosearch_timer_cb(self): self._autosearch_timer = None self.search_entry.activate() return False + class FavoritesButton(RadioToolButton): __gtype_name__ = 'SugarFavoritesButton' @@ -295,4 +297,3 @@ class FavoritesButton(RadioToolButton): def _update_icon(self): self.props.named_icon = favoritesview.LAYOUT_MAP[self._layout]\ .icon_name - diff --git a/src/jarabe/desktop/homewindow.py b/src/jarabe/desktop/homewindow.py index d830ed0..07deff7 100644 --- a/src/jarabe/desktop/homewindow.py +++ b/src/jarabe/desktop/homewindow.py @@ -16,6 +16,7 @@ import logging +import gobject import gtk from sugar.graphics import style @@ -28,11 +29,15 @@ from jarabe.desktop.transitionbox import TransitionBox from jarabe.model.shell import ShellModel from jarabe.model import shell -_HOME_PAGE = 0 -_GROUP_PAGE = 1 -_MESH_PAGE = 2 + +_HOME_PAGE = 0 +_GROUP_PAGE = 1 +_MESH_PAGE = 2 _TRANSITION_PAGE = 3 +_instance = None + + class HomeWindow(gtk.Window): def __init__(self): logging.debug('STARTUP: Loading the desktop window') @@ -45,8 +50,10 @@ class HomeWindow(gtk.Window): self._active = False self._fully_obscured = True - self.set_default_size(gtk.gdk.screen_width(), - gtk.gdk.screen_height()) + screen = self.get_screen() + screen.connect('size-changed', self.__screen_size_change_cb) + self.set_default_size(screen.get_width(), + screen.get_height()) self.realize() self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DESKTOP) @@ -73,13 +80,16 @@ class HomeWindow(gtk.Window): self.__zoom_level_changed_cb) def _deactivate_view(self, level): - group = palettegroup.get_group("default") + group = palettegroup.get_group('default') group.popdown() if level == ShellModel.ZOOM_HOME: self._home_box.suspend() elif level == ShellModel.ZOOM_MESH: self._mesh_box.suspend() + def __screen_size_change_cb(self, screen): + self.resize(screen.get_width(), screen.get_height()) + def _activate_view(self, level): if level == ShellModel.ZOOM_HOME: self._home_box.resume() @@ -178,11 +188,22 @@ class HomeWindow(gtk.Window): def get_home_box(self): return self._home_box -_instance = None + def busy_during_delayed_action(self, action): + """Use busy cursor during execution of action, scheduled via idle_add. + """ + def action_wrapper(old_cursor): + try: + action() + finally: + self.get_window().set_cursor(old_cursor) + + old_cursor = self.get_window().get_cursor() + self.get_window().set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH)) + gobject.idle_add(action_wrapper, old_cursor) + 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 1e6d17a..6241b9b 100644 --- a/src/jarabe/desktop/keydialog.py +++ b/src/jarabe/desktop/keydialog.py @@ -24,8 +24,14 @@ import dbus from jarabe.model import network from jarabe.model.network import Secrets + IW_AUTH_ALG_OPEN_SYSTEM = 'open' -IW_AUTH_ALG_SHARED_KEY = 'shared' +IW_AUTH_ALG_SHARED_KEY = 'shared' + +WEP_PASSPHRASE = 1 +WEP_HEX = 2 +WEP_ASCII = 3 + def string_is_hex(key): is_hex = True @@ -34,6 +40,7 @@ def string_is_hex(key): is_hex = False return is_hex + def string_is_ascii(string): try: string.encode('ascii') @@ -41,12 +48,14 @@ def string_is_ascii(string): except UnicodeEncodeError: return False + def string_to_hex(passphrase): key = '' for c in passphrase: key += '%02x' % ord(c) return key + def hash_passphrase(passphrase): # passphrase must have a length of 64 if len(passphrase) > 64: @@ -57,16 +66,18 @@ def hash_passphrase(passphrase): passphrase = hashlib.md5(passphrase).digest() return string_to_hex(passphrase)[:26] + class CanceledKeyRequestError(dbus.DBusException): def __init__(self): dbus.DBusException.__init__(self) self._dbus_error_name = network.NM_SETTINGS_IFACE + '.CanceledError' + class KeyDialog(gtk.Dialog): def __init__(self, ssid, flags, wpa_flags, rsn_flags, dev_caps, settings, response): gtk.Dialog.__init__(self, flags=gtk.DIALOG_MODAL) - self.set_title("Wireless Key Required") + self.set_title('Wireless Key Required') self._settings = settings self._response = response @@ -108,9 +119,6 @@ class KeyDialog(gtk.Dialog): def get_response_object(self): return self._response -WEP_PASSPHRASE = 1 -WEP_HEX = 2 -WEP_ASCII = 3 class WEPKeyDialog(KeyDialog): def __init__(self, ssid, flags, wpa_flags, rsn_flags, dev_caps, settings, @@ -120,9 +128,9 @@ class WEPKeyDialog(KeyDialog): # WEP key type self.key_store = gtk.ListStore(str, int) - self.key_store.append(["Passphrase (128-bit)", WEP_PASSPHRASE]) - self.key_store.append(["Hex (40/128-bit)", WEP_HEX]) - self.key_store.append(["ASCII (40/128-bit)", WEP_ASCII]) + self.key_store.append(['Passphrase (128-bit)', WEP_PASSPHRASE]) + self.key_store.append(['Hex (40/128-bit)', WEP_HEX]) + self.key_store.append(['ASCII (40/128-bit)', WEP_ASCII]) self.key_combo = gtk.ComboBox(self.key_store) cell = gtk.CellRendererText() @@ -132,7 +140,7 @@ class WEPKeyDialog(KeyDialog): self.key_combo.connect('changed', self._key_combo_changed_cb) hbox = gtk.HBox() - hbox.pack_start(gtk.Label(_("Key Type:"))) + hbox.pack_start(gtk.Label(_('Key Type:'))) hbox.pack_start(self.key_combo) hbox.show_all() self.vbox.pack_start(hbox) @@ -142,8 +150,8 @@ class WEPKeyDialog(KeyDialog): # WEP authentication mode self.auth_store = gtk.ListStore(str, str) - self.auth_store.append(["Open System", IW_AUTH_ALG_OPEN_SYSTEM]) - self.auth_store.append(["Shared Key", IW_AUTH_ALG_SHARED_KEY]) + self.auth_store.append(['Open System', IW_AUTH_ALG_OPEN_SYSTEM]) + self.auth_store.append(['Shared Key', IW_AUTH_ALG_SHARED_KEY]) self.auth_combo = gtk.ComboBox(self.auth_store) cell = gtk.CellRendererText() @@ -152,7 +160,7 @@ class WEPKeyDialog(KeyDialog): self.auth_combo.set_active(0) hbox = gtk.HBox() - hbox.pack_start(gtk.Label(_("Authentication Type:"))) + hbox.pack_start(gtk.Label(_('Authentication Type:'))) hbox.pack_start(self.auth_combo) hbox.show_all() @@ -179,8 +187,8 @@ class WEPKeyDialog(KeyDialog): def print_security(self): (key, auth_alg) = self._get_security() - print "Key: %s" % key - print "Auth: %d" % auth_alg + print 'Key: %s' % key + print 'Auth: %d' % auth_alg def create_security(self): (key, auth_alg) = self._get_security() @@ -209,6 +217,7 @@ class WEPKeyDialog(KeyDialog): self.set_response_sensitive(gtk.RESPONSE_OK, valid) + class WPAKeyDialog(KeyDialog): def __init__(self, ssid, flags, wpa_flags, rsn_flags, dev_caps, settings, response): @@ -217,7 +226,7 @@ class WPAKeyDialog(KeyDialog): self.add_key_entry() self.store = gtk.ListStore(str) - self.store.append([_("WPA & WPA2 Personal")]) + self.store.append([_('WPA & WPA2 Personal')]) self.combo = gtk.ComboBox(self.store) cell = gtk.CellRendererText() @@ -226,7 +235,7 @@ class WPAKeyDialog(KeyDialog): self.combo.set_active(0) self.hbox = gtk.HBox() - self.hbox.pack_start(gtk.Label(_("Wireless Security:"))) + self.hbox.pack_start(gtk.Label(_('Wireless Security:'))) self.hbox.pack_start(self.combo) self.hbox.show_all() @@ -246,21 +255,21 @@ class WPAKeyDialog(KeyDialog): from subprocess import Popen, PIPE p = Popen(['wpa_passphrase', ssid, key], stdout=PIPE) for line in p.stdout: - if line.strip().startswith("psk="): + if line.strip().startswith('psk='): real_key = line.strip()[4:] if p.wait() != 0: - raise RuntimeError("Error hashing passphrase") + raise RuntimeError('Error hashing passphrase') if real_key and len(real_key) != 64: real_key = None if not real_key: - raise RuntimeError("Invalid key") + raise RuntimeError('Invalid key') return real_key def print_security(self): key = self._get_security() - print "Key: %s" % key + print 'Key: %s' % key def create_security(self): secrets = Secrets(self._settings) @@ -281,6 +290,7 @@ class WPAKeyDialog(KeyDialog): self.set_response_sensitive(gtk.RESPONSE_OK, valid) return False + def create(ssid, flags, wpa_flags, rsn_flags, dev_caps, settings, response): if wpa_flags == network.NM_802_11_AP_SEC_NONE and \ rsn_flags == network.NM_802_11_AP_SEC_NONE: @@ -290,13 +300,15 @@ def create(ssid, flags, wpa_flags, rsn_flags, dev_caps, settings, response): key_dialog = WPAKeyDialog(ssid, flags, wpa_flags, rsn_flags, dev_caps, settings, response) - key_dialog.connect("response", _key_dialog_response_cb) - key_dialog.connect("destroy", _key_dialog_destroy_cb) + key_dialog.connect('response', _key_dialog_response_cb) + key_dialog.connect('destroy', _key_dialog_destroy_cb) key_dialog.show_all() + def _key_dialog_destroy_cb(key_dialog, data=None): _key_dialog_response_cb(key_dialog, gtk.RESPONSE_CANCEL) + def _key_dialog_response_cb(key_dialog, response_id): response = key_dialog.get_response_object() secrets = None @@ -308,10 +320,9 @@ def _key_dialog_response_cb(key_dialog, response_id): response.set_error(CanceledKeyRequestError()) elif response_id == gtk.RESPONSE_OK: if not secrets: - raise RuntimeError("Invalid security arguments.") + raise RuntimeError('Invalid security arguments.') response.set_secrets(secrets) else: - raise RuntimeError("Unhandled key dialog response %d" % response_id) + raise RuntimeError('Unhandled key dialog response %d' % response_id) key_dialog.destroy() - diff --git a/src/jarabe/desktop/meshbox.py b/src/jarabe/desktop/meshbox.py index a04922b..ad4b873 100644 --- a/src/jarabe/desktop/meshbox.py +++ b/src/jarabe/desktop/meshbox.py @@ -1,6 +1,7 @@ # Copyright (C) 2006-2007 Red Hat, Inc. # Copyright (C) 2009 Tomeu Vizoso, Simon Schampijer -# Copyright (C) 2009 One Laptop per Child +# Copyright (C) 2009-2010 One Laptop per Child +# Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> # # 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 @@ -18,41 +19,34 @@ from gettext import gettext as _ import logging -import hashlib import dbus import hippo +import glib import gobject import gtk +import gconf from sugar.graphics.icon import CanvasIcon, Icon -from sugar.graphics.xocolor import XoColor -from sugar.graphics import xocolor from sugar.graphics import style -from sugar.graphics.icon import get_icon_state from sugar.graphics import palette from sugar.graphics import iconentry from sugar.graphics.menuitem import MenuItem -from sugar.activity.activityhandle import ActivityHandle -from sugar.activity import activityfactory -from sugar.util import unique_id -from sugar import profile from jarabe.model import neighborhood +from jarabe.model.buddy import get_owner_instance from jarabe.view.buddyicon import BuddyIcon -from jarabe.view.pulsingicon import CanvasPulsingIcon -from jarabe.view import launcher from jarabe.desktop.snowflakelayout import SnowflakeLayout from jarabe.desktop.spreadlayout import SpreadLayout -from jarabe.desktop import keydialog -from jarabe.model import bundleregistry +from jarabe.desktop.networkviews import WirelessNetworkView +from jarabe.desktop.networkviews import OlpcMeshView +from jarabe.desktop.networkviews import SugarAdhocView 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 from jarabe.model.network import AccessPoint from jarabe.model.olpcmesh import OlpcMeshManager +from jarabe.model.adhoc import get_adhoc_manager_instance +from jarabe.journal import misc + _NM_SERVICE = 'org.freedesktop.NetworkManager' _NM_IFACE = 'org.freedesktop.NetworkManager' @@ -66,522 +60,7 @@ _NM_ACTIVE_CONN_IFACE = 'org.freedesktop.NetworkManager.Connection.Active' _AP_ICON_NAME = 'network-wireless' _OLPC_MESH_ICON_NAME = 'network-mesh' -class WirelessNetworkView(CanvasPulsingIcon): - def __init__(self, initial_ap): - CanvasPulsingIcon.__init__(self, size=style.STANDARD_ICON_SIZE, - cache=True) - self._bus = dbus.SystemBus() - self._access_points = {initial_ap.model.object_path: initial_ap} - self._active_ap = None - self._device = initial_ap.device - self._palette_icon = None - self._disconnect_item = None - self._connect_item = None - self._greyed_out = False - self._name = initial_ap.name - self._mode = initial_ap.mode - self._strength = initial_ap.strength - self._flags = initial_ap.flags - self._wpa_flags = initial_ap.wpa_flags - self._rsn_flags = initial_ap.rsn_flags - self._device_caps = 0 - self._device_state = None - self._connection = None - self._color = None - - if self._mode == network.NM_802_11_MODE_ADHOC \ - and self._name_encodes_colors(): - encoded_color = self._name.split("#", 1) - if len(encoded_color) == 2: - self._color = xocolor.XoColor('#' + encoded_color[1]) - else: - 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.connect('button-release-event', self.__button_release_event_cb) - - pulse_color = XoColor('%s,%s' % (style.COLOR_BUTTON_GREY.get_svg(), - style.COLOR_TRANSPARENT.get_svg())) - self.props.pulse_color = pulse_color - - self._palette = self._create_palette() - self.set_palette(self._palette) - self._palette_icon.props.xo_color = self._color - - if network.find_connection_by_ssid(self._name) is not None: - self.props.badge_name = "emblem-favorite" - self._palette_icon.props.badge_name = "emblem-favorite" - elif initial_ap.flags == network.NM_802_11_AP_FLAGS_PRIVACY: - self.props.badge_name = "emblem-locked" - self._palette_icon.props.badge_name = "emblem-locked" - else: - self.props.badge_name = None - self._palette_icon.props.badge_name = None - - interface_props = dbus.Interface(self._device, - 'org.freedesktop.DBus.Properties') - interface_props.Get(_NM_DEVICE_IFACE, 'State', - reply_handler=self.__get_device_state_reply_cb, - error_handler=self.__get_device_state_error_cb) - interface_props.Get(_NM_WIRELESS_IFACE, 'WirelessCapabilities', - reply_handler=self.__get_device_caps_reply_cb, - error_handler=self.__get_device_caps_error_cb) - interface_props.Get(_NM_WIRELESS_IFACE, 'ActiveAccessPoint', - reply_handler=self.__get_active_ap_reply_cb, - error_handler=self.__get_active_ap_error_cb) - - self._bus.add_signal_receiver(self.__device_state_changed_cb, - signal_name='StateChanged', - path=self._device.object_path, - dbus_interface=_NM_DEVICE_IFACE) - self._bus.add_signal_receiver(self.__wireless_properties_changed_cb, - signal_name='PropertiesChanged', - path=self._device.object_path, - dbus_interface=_NM_WIRELESS_IFACE) - - def _name_encodes_colors(self): - """Match #XXXXXX,#YYYYYY at the end of the network name""" - return self._name[-7] == '#' and self._name[-8] == ',' \ - and self._name[-15] == '#' - - def _create_palette(self): - icon_name = get_icon_state(_AP_ICON_NAME, self._strength) - self._palette_icon = Icon(icon_name=icon_name, - icon_size=style.STANDARD_ICON_SIZE, - badge_name=self.props.badge_name) - - p = palette.Palette(primary_text=self._name, - icon=self._palette_icon) - - self._connect_item = MenuItem(_('Connect'), 'dialog-ok') - self._connect_item.connect('activate', self.__connect_activate_cb) - p.menu.append(self._connect_item) - - self._disconnect_item = MenuItem(_('Disconnect'), 'media-eject') - self._disconnect_item.connect('activate', - self._disconnect_activate_cb) - p.menu.append(self._disconnect_item) - - return p - - def __device_state_changed_cb(self, new_state, old_state, reason): - self._device_state = new_state - self._update_state() - - def __update_active_ap(self, ap_path): - if ap_path in self._access_points: - # save reference to active AP, so that we always display the - # strength of that one - self._active_ap = self._access_points[ap_path] - self.update_strength() - self._update_state() - elif self._active_ap is not None: - # revert to showing state of strongest AP again - self._active_ap = None - self.update_strength() - self._update_state() - - def __wireless_properties_changed_cb(self, properties): - if 'ActiveAccessPoint' in properties: - self.__update_active_ap(properties['ActiveAccessPoint']) - - def __get_active_ap_reply_cb(self, ap_path): - self.__update_active_ap(ap_path) - - def __get_active_ap_error_cb(self, err): - logging.error('Error getting the active access point: %s', err) - - def __get_device_caps_reply_cb(self, caps): - self._device_caps = caps - - def __get_device_caps_error_cb(self, err): - logging.error('Error getting the wireless device properties: %s', err) - - def __get_device_state_reply_cb(self, state): - self._device_state = state - self._update() - - def __get_device_state_error_cb(self, err): - logging.error('Error getting the device state: %s', err) - - def _update(self): - self._update_state() - self._update_color() - - def _update_state(self): - if self._active_ap is not None: - state = self._device_state - else: - state = network.DEVICE_STATE_UNKNOWN - - if state == network.DEVICE_STATE_ACTIVATED: - connection = network.find_connection_by_ssid(self._name) - if connection: - if self._mode == network.NM_802_11_MODE_INFRA: - connection.set_connected() - - icon_name = '%s-connected' % _AP_ICON_NAME - else: - icon_name = _AP_ICON_NAME - - icon_name = get_icon_state(icon_name, self._strength) - if icon_name: - self.props.icon_name = icon_name - icon = self._palette.props.icon - icon.props.icon_name = icon_name - - if state == network.DEVICE_STATE_PREPARE or \ - state == network.DEVICE_STATE_CONFIG or \ - state == network.DEVICE_STATE_NEED_AUTH or \ - state == network.DEVICE_STATE_IP_CONFIG: - if self._disconnect_item: - self._disconnect_item.show() - self._connect_item.hide() - self._palette.props.secondary_text = _('Connecting...') - self.props.pulsing = True - elif state == network.DEVICE_STATE_ACTIVATED: - if self._disconnect_item: - self._disconnect_item.show() - self._connect_item.hide() - self._palette.props.secondary_text = _('Connected') - self.props.pulsing = False - else: - if self._disconnect_item: - self._disconnect_item.hide() - self._connect_item.show() - self._palette.props.secondary_text = None - self.props.pulsing = False - - def _update_color(self): - if self._greyed_out: - self.props.pulsing = False - self.props.base_color = XoColor('#D5D5D5,#D5D5D5') - else: - self.props.base_color = self._color - - def _disconnect_activate_cb(self, item): - pass - - def _add_ciphers_from_flags(self, flags, pairwise): - ciphers = [] - if pairwise: - if flags & network.NM_802_11_AP_SEC_PAIR_TKIP: - ciphers.append("tkip") - if flags & network.NM_802_11_AP_SEC_PAIR_CCMP: - ciphers.append("ccmp") - else: - if flags & network.NM_802_11_AP_SEC_GROUP_WEP40: - ciphers.append("wep40") - if flags & network.NM_802_11_AP_SEC_GROUP_WEP104: - ciphers.append("wep104") - if flags & network.NM_802_11_AP_SEC_GROUP_TKIP: - ciphers.append("tkip") - if flags & network.NM_802_11_AP_SEC_GROUP_CCMP: - ciphers.append("ccmp") - return ciphers - - def _get_security(self): - if not (self._flags & network.NM_802_11_AP_FLAGS_PRIVACY) and \ - (self._wpa_flags == network.NM_802_11_AP_SEC_NONE) and \ - (self._rsn_flags == network.NM_802_11_AP_SEC_NONE): - # No security - return None - - if (self._flags & network.NM_802_11_AP_FLAGS_PRIVACY) and \ - (self._wpa_flags == network.NM_802_11_AP_SEC_NONE) and \ - (self._rsn_flags == network.NM_802_11_AP_SEC_NONE): - # Static WEP, Dynamic WEP, or LEAP - wireless_security = WirelessSecurity() - wireless_security.key_mgmt = 'none' - return wireless_security - - if (self._mode != network.NM_802_11_MODE_INFRA): - # Stuff after this point requires infrastructure - logging.error('The infrastructure mode is not supoorted' - ' by your wireless device.') - return None - - if (self._rsn_flags & network.NM_802_11_AP_SEC_KEY_MGMT_PSK) and \ - (self._device_caps & network.NM_802_11_DEVICE_CAP_RSN): - # WPA2 PSK first - pairwise = self._add_ciphers_from_flags(self._rsn_flags, True) - group = self._add_ciphers_from_flags(self._rsn_flags, False) - wireless_security = WirelessSecurity() - wireless_security.key_mgmt = 'wpa-psk' - wireless_security.proto = 'rsn' - wireless_security.pairwise = pairwise - wireless_security.group = group - return wireless_security - - if (self._wpa_flags & network.NM_802_11_AP_SEC_KEY_MGMT_PSK) and \ - (self._device_caps & network.NM_802_11_DEVICE_CAP_WPA): - # WPA PSK - pairwise = self._add_ciphers_from_flags(self._wpa_flags, True) - group = self._add_ciphers_from_flags(self._wpa_flags, False) - wireless_security = WirelessSecurity() - wireless_security.key_mgmt = 'wpa-psk' - wireless_security.proto = 'wpa' - wireless_security.pairwise = pairwise - wireless_security.group = group - return wireless_security - - def __connect_activate_cb(self, icon): - self._connect() - - def __button_release_event_cb(self, icon, event): - self._connect() - - def _connect(self): - connection = network.find_connection_by_ssid(self._name) - if connection is None: - settings = Settings() - settings.connection.id = 'Auto ' + self._name - uuid = settings.connection.uuid = unique_id() - settings.connection.type = '802-11-wireless' - settings.wireless.ssid = self._name - - if self._mode == network.NM_802_11_MODE_INFRA: - 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 = 'link-local' - - wireless_security = self._get_security() - settings.wireless_security = wireless_security - - if wireless_security is not None: - settings.wireless.security = '802-11-wireless-security' - - connection = network.add_connection(uuid, settings) - - obj = self._bus.get_object(_NM_SERVICE, _NM_PATH) - netmgr = dbus.Interface(obj, _NM_IFACE) - - netmgr.ActivateConnection(network.SETTINGS_SERVICE, connection.path, - self._device.object_path, - "/", - reply_handler=self.__activate_reply_cb, - error_handler=self.__activate_error_cb) - - def __activate_reply_cb(self, connection): - logging.debug('Connection activated: %s', connection) - - def __activate_error_cb(self, err): - logging.error('Failed to activate connection: %s', err) - - def set_filter(self, query): - self._greyed_out = self._name.lower().find(query) == -1 - self._update_state() - self._update_color() - - def create_keydialog(self, settings, response): - keydialog.create(self._name, self._flags, self._wpa_flags, - self._rsn_flags, self._device_caps, settings, response) - - def update_strength(self): - if self._active_ap is not None: - # display strength of AP that we are connected to - new_strength = self._active_ap.strength - else: - # display the strength of the strongest AP that makes up this - # network, also considering that there may be no APs - new_strength = max([0] + [ap.strength for ap in - self._access_points.values()]) - - if new_strength != self._strength: - self._strength = new_strength - self._update_state() - - def add_ap(self, ap): - self._access_points[ap.model.object_path] = ap - self.update_strength() - - def remove_ap(self, ap): - path = ap.model.object_path - if path not in self._access_points: - return - del self._access_points[path] - if self._active_ap == ap: - self._active_ap = None - self.update_strength() - - def num_aps(self): - return len(self._access_points) - - def find_ap(self, ap_path): - if ap_path not in self._access_points: - return None - return self._access_points[ap_path] - - def is_olpc_mesh(self): - return self._mode == network.NM_802_11_MODE_ADHOC \ - and self.name == "olpc-mesh" - - def remove_all_aps(self): - for ap in self._access_points.values(): - ap.disconnect() - self._access_points = {} - self._active_ap = None - self.update_strength() - - def disconnect(self): - self._bus.remove_signal_receiver(self.__device_state_changed_cb, - signal_name='StateChanged', - path=self._device.object_path, - dbus_interface=_NM_DEVICE_IFACE) - self._bus.remove_signal_receiver(self.__wireless_properties_changed_cb, - signal_name='PropertiesChanged', - path=self._device.object_path, - dbus_interface=_NM_WIRELESS_IFACE) - - -class OlpcMeshView(CanvasPulsingIcon): - def __init__(self, mesh_mgr, channel): - CanvasPulsingIcon.__init__(self, icon_name=_OLPC_MESH_ICON_NAME, - size=style.STANDARD_ICON_SIZE, cache=True) - self._bus = dbus.SystemBus() - self._channel = channel - self._mesh_mgr = mesh_mgr - self._disconnect_item = None - self._connect_item = None - self._greyed_out = False - self._name = '' - self._device_state = None - self._connection = None - self._active = False - device = mesh_mgr.mesh_device - - self.connect('button-release-event', self.__button_release_event_cb) - - interface_props = dbus.Interface(device, - 'org.freedesktop.DBus.Properties') - interface_props.Get(_NM_DEVICE_IFACE, 'State', - reply_handler=self.__get_device_state_reply_cb, - error_handler=self.__get_device_state_error_cb) - interface_props.Get(_NM_OLPC_MESH_IFACE, 'ActiveChannel', - reply_handler=self.__get_active_channel_reply_cb, - error_handler=self.__get_active_channel_error_cb) - - self._bus.add_signal_receiver(self.__device_state_changed_cb, - signal_name='StateChanged', - path=device.object_path, - dbus_interface=_NM_DEVICE_IFACE) - self._bus.add_signal_receiver(self.__wireless_properties_changed_cb, - signal_name='PropertiesChanged', - path=device.object_path, - dbus_interface=_NM_OLPC_MESH_IFACE) - - pulse_color = XoColor('%s,%s' % (style.COLOR_BUTTON_GREY.get_svg(), - style.COLOR_TRANSPARENT.get_svg())) - self.props.pulse_color = pulse_color - self.props.base_color = profile.get_color() - self._palette = self._create_palette() - self.set_palette(self._palette) - - def _create_palette(self): - _palette = palette.Palette(_("Mesh Network %d") % self._channel) - - self._connect_item = MenuItem(_('Connect'), 'dialog-ok') - self._connect_item.connect('activate', self.__connect_activate_cb) - _palette.menu.append(self._connect_item) - - return _palette - - def __get_device_state_reply_cb(self, state): - self._device_state = state - self._update() - - def __get_device_state_error_cb(self, err): - logging.error('Error getting the device state: %s', err) - - def __device_state_changed_cb(self, new_state, old_state, reason): - self._device_state = new_state - self._update() - - def __get_active_channel_reply_cb(self, channel): - self._active = (channel == self._channel) - self._update() - - def __get_active_channel_error_cb(self, err): - logging.error('Error getting the active channel: %s', err) - - def __wireless_properties_changed_cb(self, properties): - if 'ActiveChannel' in properties: - channel = properties['ActiveChannel'] - self._active = (channel == self._channel) - self._update() - - def _update(self): - if self._active: - state = self._device_state - else: - state = network.DEVICE_STATE_UNKNOWN - - if state in [network.DEVICE_STATE_PREPARE, - network.DEVICE_STATE_CONFIG, - network.DEVICE_STATE_NEED_AUTH, - network.DEVICE_STATE_IP_CONFIG]: - if self._disconnect_item: - self._disconnect_item.show() - self._connect_item.hide() - self._palette.props.secondary_text = _('Connecting...') - self.props.pulsing = True - elif state == network.DEVICE_STATE_ACTIVATED: - if self._disconnect_item: - self._disconnect_item.show() - self._connect_item.hide() - self._palette.props.secondary_text = _('Connected') - self.props.pulsing = False - else: - if self._disconnect_item: - self._disconnect_item.hide() - self._connect_item.show() - self._palette.props.secondary_text = None - self.props.pulsing = False - - def _update_color(self): - if self._greyed_out: - self.props.base_color = XoColor('#D5D5D5,#D5D5D5') - else: - self.props.base_color = profile.get_color() - - def __connect_activate_cb(self, icon): - self._connect() - - def __button_release_event_cb(self, icon, event): - self._connect() - - def _connect(self): - self._mesh_mgr.user_activate_channel(self._channel) - - def __activate_reply_cb(self, connection): - logging.debug('Connection activated: %s', connection) - - def __activate_error_cb(self, err): - logging.error('Failed to activate connection: %s', err) - - def set_filter(self, query): - self._greyed_out = (query != '') - self._update_color() - - def disconnect(self): - self._bus.remove_signal_receiver(self.__device_state_changed_cb, - signal_name='StateChanged', - path=self._device.object_path, - dbus_interface=_NM_DEVICE_IFACE) - self._bus.remove_signal_receiver(self.__wireless_properties_changed_cb, - signal_name='PropertiesChanged', - path=self._device.object_path, - dbus_interface=_NM_OLPC_MESH_IFACE) +_AUTOSEARCH_TIMEOUT = 1000 class ActivityView(hippo.CanvasBox): @@ -589,6 +68,9 @@ class ActivityView(hippo.CanvasBox): hippo.CanvasBox.__init__(self) self._model = model + self._model.connect('current-buddy-added', self.__buddy_added_cb) + self._model.connect('current-buddy-removed', self.__buddy_removed_cb) + self._icons = {} self._palette = None @@ -598,31 +80,30 @@ class ActivityView(hippo.CanvasBox): self._icon = self._create_icon() self._layout.add(self._icon, center=True) - self._update_palette() + self._palette = self._create_palette() + self._icon.set_palette(self._palette) - activity = self._model.activity - activity.connect('notify::name', self._name_changed_cb) - activity.connect('notify::color', self._color_changed_cb) - activity.connect('notify::private', self._private_changed_cb) - activity.connect('joined', self._joined_changed_cb) - #FIXME: 'joined' signal not working, see #5032 + for buddy in self._model.props.current_buddies: + self._add_buddy(buddy) def _create_icon(self): - icon = CanvasIcon(file_name=self._model.get_icon_name(), + icon = CanvasIcon(file_name=self._model.bundle.get_icon(), xo_color=self._model.get_color(), cache=True, size=style.STANDARD_ICON_SIZE) icon.connect('activated', self._clicked_cb) return icon def _create_palette(self): - p_icon = Icon(file=self._model.get_icon_name(), + p_text = glib.markup_escape_text(self._model.bundle.get_name()) + p_icon = Icon(file=self._model.bundle.get_icon(), xo_color=self._model.get_color()) p_icon.props.icon_size = gtk.ICON_SIZE_LARGE_TOOLBAR - p = palette.Palette(None, primary_text=self._model.activity.props.name, + p = palette.Palette(None, + primary_text=p_text, icon=p_icon) - private = self._model.activity.props.private - joined = self._model.activity.props.joined + private = self._model.props.private + joined = get_owner_instance() in self._model.props.buddies if joined: item = MenuItem(_('Resume'), 'activity-start') @@ -637,42 +118,30 @@ class ActivityView(hippo.CanvasBox): return p - def _update_palette(self): - self._palette = self._create_palette() - self._icon.set_palette(self._palette) - def has_buddy_icon(self, key): - return self._icons.has_key(key) + return key in self._icons + + def __buddy_added_cb(self, activity, buddy): + self._add_buddy(buddy) - def add_buddy_icon(self, key, icon): - self._icons[key] = icon + def _add_buddy(self, buddy): + icon = BuddyIcon(buddy, style.STANDARD_ICON_SIZE) + self._icons[buddy.props.key] = icon self._layout.add(icon) - def remove_buddy_icon(self, key): - icon = self._icons[key] - del self._icons[key] + def __buddy_removed_cb(self, activity, buddy): + icon = self._icons[buddy.props.key] + del self._icons[buddy.props.key] icon.destroy() def _clicked_cb(self, item): - shell_model = shell.get_model() - activity = shell_model.get_activity_by_id(self._model.get_id()) - if activity: - activity.get_window().activate(gtk.get_current_event_time()) - return - - bundle_id = self._model.get_bundle_id() - bundle = bundleregistry.get_registry().get_bundle(bundle_id) - - launcher.add_launcher(self._model.get_id(), - bundle.get_icon(), - self._model.get_color()) - - handle = ActivityHandle(self._model.get_id()) - activityfactory.create(bundle, handle) + bundle = self._model.get_bundle() + misc.launch(bundle, activity_id=self._model.activity_id, + color=self._model.get_color()) def set_filter(self, query): - text_to_check = self._model.activity.props.name.lower() + \ - self._model.activity.props.type.lower() + text_to_check = self._model.bundle.get_name().lower() + \ + self._model.bundle.get_bundle_id().lower() if text_to_check.find(query) == -1: self._icon.props.stroke_color = '#D5D5D5' self._icon.props.fill_color = style.COLOR_TRANSPARENT.get_svg() @@ -683,31 +152,13 @@ class ActivityView(hippo.CanvasBox): if hasattr(icon, 'set_filter'): icon.set_filter(query) - def _name_changed_cb(self, activity, pspec): - self._update_palette() - - def _color_changed_cb(self, activity, pspec): - self._layout.remove(self._icon) - self._icon = self._create_icon() - self._layout.add(self._icon, center=True) - self._icon.set_palette(self._palette) - - def _private_changed_cb(self, activity, pspec): - self._update_palette() - - def _joined_changed_cb(self, widget, event): - logging.debug('ActivityView._joined_changed_cb') - -_AUTOSEARCH_TIMEOUT = 1000 - class MeshToolbar(gtk.Toolbar): __gtype_name__ = 'MeshToolbar' __gsignals__ = { - 'query-changed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([str])) + 'query-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([str])), } def __init__(self): @@ -770,15 +221,23 @@ class MeshToolbar(gtk.Toolbar): return False -class DeviceObserver(object): - def __init__(self, box, device): - self._box = box +class DeviceObserver(gobject.GObject): + __gsignals__ = { + 'access-point-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])), + 'access-point-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])), + } + + def __init__(self, device): + gobject.GObject.__init__(self) self._bus = dbus.SystemBus() - self._device = device + self.device = device - wireless = dbus.Interface(self._device, _NM_WIRELESS_IFACE) - wireless.GetAccessPoints(reply_handler=self._get_access_points_reply_cb, - error_handler=self._get_access_points_error_cb) + wireless = dbus.Interface(device, _NM_WIRELESS_IFACE) + wireless.GetAccessPoints( + reply_handler=self._get_access_points_reply_cb, + error_handler=self._get_access_points_error_cb) self._bus.add_signal_receiver(self.__access_point_added_cb, signal_name='AccessPointAdded', @@ -792,35 +251,42 @@ class DeviceObserver(object): def _get_access_points_reply_cb(self, access_points_o): for ap_o in access_points_o: ap = self._bus.get_object(_NM_SERVICE, ap_o) - self._box.add_access_point(self._device, ap) + self.emit('access-point-added', ap) def _get_access_points_error_cb(self, err): logging.error('Failed to get access points: %s', err) def __access_point_added_cb(self, access_point_o): ap = self._bus.get_object(_NM_SERVICE, access_point_o) - self._box.add_access_point(self._device, ap) + self.emit('access-point-added', ap) def __access_point_removed_cb(self, access_point_o): - self._box.remove_access_point(access_point_o) + self.emit('access-point-removed', access_point_o) def disconnect(self): self._bus.remove_signal_receiver(self.__access_point_added_cb, signal_name='AccessPointAdded', - path=self._device.object_path, + path=self.device.object_path, dbus_interface=_NM_WIRELESS_IFACE) self._bus.remove_signal_receiver(self.__access_point_removed_cb, signal_name='AccessPointRemoved', - path=self._device.object_path, + path=self.device.object_path, dbus_interface=_NM_WIRELESS_IFACE) class NetworkManagerObserver(object): + + _SHOW_ADHOC_GCONF_KEY = '/desktop/sugar/network/adhoc' + def __init__(self, box): self._box = box self._bus = None self._devices = {} self._netmgr = None + self._olpc_mesh_device_o = None + + client = gconf.client_get_default() + self._have_adhoc_networks = client.get_bool(self._SHOW_ADHOC_GCONF_KEY) def listen(self): try: @@ -840,6 +306,9 @@ class NetworkManagerObserver(object): self._bus.add_signal_receiver(self.__device_removed_cb, signal_name='DeviceRemoved', dbus_interface=_NM_IFACE) + self._bus.add_signal_receiver(self.__properties_changed_cb, + signal_name='PropertiesChanged', + dbus_interface=_NM_IFACE) settings = network.get_settings() if settings is not None: @@ -849,13 +318,12 @@ class NetworkManagerObserver(object): # FIXME It would be better to do all of this async, but I cannot think # of a good way to. NM could really use some love here. - netmgr_props = dbus.Interface( - self._netmgr, 'org.freedesktop.DBus.Properties') + netmgr_props = dbus.Interface(self._netmgr, dbus.PROPERTIES_IFACE) active_connections_o = netmgr_props.Get(_NM_IFACE, 'ActiveConnections') for conn_o in active_connections_o: obj = self._bus.get_object(_NM_IFACE, conn_o) - props = dbus.Interface(obj, 'org.freedesktop.DBus.Properties') + props = dbus.Interface(obj, dbus.PROPERTIES_IFACE) state = props.Get(_NM_ACTIVE_CONN_IFACE, 'State') if state == network.NM_ACTIVE_CONNECTION_STATE_ACTIVATING: ap_o = props.Get(_NM_ACTIVE_CONN_IFACE, 'SpecificObject') @@ -867,8 +335,8 @@ class NetworkManagerObserver(object): settings = kwargs['connection'].get_settings() net.create_keydialog(settings, kwargs['response']) if not found: - logging.error('Could not determine AP for' - ' specific object %s' % conn_o) + logging.error('Could not determine AP for specific object' + ' %s', conn_o) def __get_devices_reply_cb(self, devices_o): for dev_o in devices_o: @@ -879,12 +347,19 @@ class NetworkManagerObserver(object): def _check_device(self, device_o): device = self._bus.get_object(_NM_SERVICE, device_o) - props = dbus.Interface(device, 'org.freedesktop.DBus.Properties') + props = dbus.Interface(device, dbus.PROPERTIES_IFACE) device_type = props.Get(_NM_DEVICE_IFACE, 'DeviceType') if device_type == network.DEVICE_TYPE_802_11_WIRELESS: - self._devices[device_o] = DeviceObserver(self._box, device) + self._devices[device_o] = DeviceObserver(device) + self._devices[device_o].connect('access-point-added', + self.__ap_added_cb) + self._devices[device_o].connect('access-point-removed', + self.__ap_removed_cb) + if self._have_adhoc_networks: + self._box.add_adhoc_networks(device) elif device_type == network.DEVICE_TYPE_802_11_OLPC_MESH: + self._olpc_mesh_device_o = device_o self._box.enable_olpc_mesh(device) def _get_device_path_error_cb(self, err): @@ -898,23 +373,41 @@ class NetworkManagerObserver(object): observer = self._devices[device_o] observer.disconnect() del self._devices[device_o] + if self._have_adhoc_networks: + self._box.remove_adhoc_networks() return - - device = self._bus.get_object(_NM_SERVICE, device_o) - props = dbus.Interface(device, 'org.freedesktop.DBus.Properties') - device_type = props.Get(_NM_DEVICE_IFACE, 'DeviceType') - if device_type == network.DEVICE_TYPE_802_11_OLPC_MESH: - self._box.disable_olpc_mesh(device) + + if self._olpc_mesh_device_o == device_o: + self._box.disable_olpc_mesh(device_o) + + def __ap_added_cb(self, device_observer, access_point): + self._box.add_access_point(device_observer.device, access_point) + + def __ap_removed_cb(self, device_observer, access_point_o): + self._box.remove_access_point(access_point_o) + + def __properties_changed_cb(self, properties): + if 'WirelessHardwareEnabled' in properties: + if properties['WirelessHardwareEnabled']: + if not self._have_adhoc_networks: + self._box.remove_adhoc_networks() + elif properties['WirelessHardwareEnabled']: + for device in self._devices: + if self._have_adhoc_networks: + self._box.add_adhoc_networks(device) + class MeshBox(gtk.VBox): __gtype_name__ = 'SugarMeshBox' def __init__(self): - logging.debug("STARTUP: Loading the mesh view") + logging.debug('STARTUP: Loading the mesh view') gobject.GObject.__init__(self) self.wireless_networks = {} + self._adhoc_manager = None + self._adhoc_networks = [] self._model = neighborhood.get_model() self._buddies = {} @@ -942,11 +435,10 @@ class MeshBox(gtk.VBox): self._layout_box.set_layout(self._layout) for buddy_model in self._model.get_buddies(): - self._add_alone_buddy(buddy_model) + self._add_buddy(buddy_model) self._model.connect('buddy-added', self._buddy_added_cb) self._model.connect('buddy-removed', self._buddy_removed_cb) - self._model.connect('buddy-moved', self._buddy_moved_cb) for activity_model in self._model.get_activities(): self._add_activity(activity_model) @@ -970,24 +462,22 @@ class MeshBox(gtk.VBox): gtk.VBox.do_size_allocate(self, allocation) def _buddy_added_cb(self, model, buddy_model): - self._add_alone_buddy(buddy_model) + self._add_buddy(buddy_model) def _buddy_removed_cb(self, model, buddy_model): self._remove_buddy(buddy_model) - def _buddy_moved_cb(self, model, buddy_model, activity_model): - # Owner doesn't move from the center - if buddy_model.is_owner(): - return - self._move_buddy(buddy_model, activity_model) - def _activity_added_cb(self, model, activity_model): self._add_activity(activity_model) def _activity_removed_cb(self, model, activity_model): self._remove_activity(activity_model) - def _add_alone_buddy(self, buddy_model): + def _add_buddy(self, buddy_model): + buddy_model.connect('notify::current-activity', + self.__buddy_notify_current_activity_cb) + if buddy_model.props.current_activity is not None: + return icon = BuddyIcon(buddy_model) if buddy_model.is_owner(): self._owner_icon = icon @@ -996,36 +486,23 @@ class MeshBox(gtk.VBox): if hasattr(icon, 'set_filter'): icon.set_filter(self._query) - self._buddies[buddy_model.get_buddy().object_path()] = icon + self._buddies[buddy_model.props.key] = icon - def _remove_alone_buddy(self, buddy_model): - icon = self._buddies[buddy_model.get_buddy().object_path()] + def _remove_buddy(self, buddy_model): + logging.debug('MeshBox._remove_buddy') + icon = self._buddies[buddy_model.props.key] self._layout.remove(icon) - del self._buddies[buddy_model.get_buddy().object_path()] + del self._buddies[buddy_model.props.key] icon.destroy() - def _remove_buddy(self, buddy_model): - object_path = buddy_model.get_buddy().object_path() - if self._buddies.has_key(object_path): - self._remove_alone_buddy(buddy_model) - else: - for activity in self._activities.values(): - if activity.has_buddy_icon(object_path): - activity.remove_buddy_icon(object_path) - - def _move_buddy(self, buddy_model, activity_model): - self._remove_buddy(buddy_model) - - if activity_model == None: - self._add_alone_buddy(buddy_model) - elif activity_model.get_id() in self._activities: - activity = self._activities[activity_model.get_id()] - - icon = BuddyIcon(buddy_model, style.STANDARD_ICON_SIZE) - activity.add_buddy_icon(buddy_model.get_buddy().object_path(), icon) - - if hasattr(icon, 'set_filter'): - icon.set_filter(self._query) + def __buddy_notify_current_activity_cb(self, buddy_model, pspec): + logging.debug('MeshBox.__buddy_notify_current_activity_cb %s', + buddy_model.props.current_activity) + if buddy_model.props.current_activity is None: + if not buddy_model.props.key in self._buddies: + self._add_buddy(buddy_model) + elif buddy_model.props.key in self._buddies: + self._remove_buddy(buddy_model) def _add_activity(self, activity_model): icon = ActivityView(activity_model) @@ -1034,58 +511,70 @@ class MeshBox(gtk.VBox): if hasattr(icon, 'set_filter'): icon.set_filter(self._query) - self._activities[activity_model.get_id()] = icon + self._activities[activity_model.activity_id] = icon def _remove_activity(self, activity_model): - icon = self._activities[activity_model.get_id()] + icon = self._activities[activity_model.activity_id] self._layout.remove(icon) - del self._activities[activity_model.get_id()] + del self._activities[activity_model.activity_id] icon.destroy() # add AP to its corresponding network icon on the desktop, # creating one if it doesn't already exist def _add_ap_to_network(self, ap): - hash = ap.network_hash() - if hash in self.wireless_networks: - self.wireless_networks[hash].add_ap(ap) + hash_value = ap.network_hash() + if hash_value in self.wireless_networks: + self.wireless_networks[hash_value].add_ap(ap) else: # this is a new network icon = WirelessNetworkView(ap) - self.wireless_networks[hash] = icon + self.wireless_networks[hash_value] = icon self._layout.add(icon) if hasattr(icon, 'set_filter'): icon.set_filter(self._query) - def _remove_net_if_empty(self, net, hash): + def _remove_net_if_empty(self, net, hash_value): # remove a network if it has no APs left if net.num_aps() == 0: net.disconnect() self._layout.remove(net) - del self.wireless_networks[hash] + del self.wireless_networks[hash_value] - def _ap_props_changed_cb(self, ap, old_hash): + def _ap_props_changed_cb(self, ap, old_hash_value): # if we have mesh hardware, ignore OLPC mesh networks that appear as # normal wifi networks if len(self._mesh) > 0 and ap.mode == network.NM_802_11_MODE_ADHOC \ - and ap.name == "olpc-mesh": - logging.debug("ignoring OLPC mesh IBSS") + and ap.name == 'olpc-mesh': + logging.debug('ignoring OLPC mesh IBSS') ap.disconnect() return - if old_hash is None: # new AP finished initializing + if self._adhoc_manager is not None and \ + network.is_sugar_adhoc_network(ap.name) and \ + ap.mode == network.NM_802_11_MODE_ADHOC: + if old_hash_value is None: + # new Ad-hoc network finished initializing + self._adhoc_manager.add_access_point(ap) + # we are called as well in other cases but we do not need to + # act here as we don't display signal strength for Ad-hoc networks + return + + if old_hash_value is None: + # new AP finished initializing self._add_ap_to_network(ap) return - hash = ap.network_hash() - if old_hash == hash: + hash_value = ap.network_hash() + if old_hash_value == hash_value: # no change in network identity, so just update signal strengths - self.wireless_networks[hash].update_strength() + self.wireless_networks[hash_value].update_strength() return # properties change includes a change of the identity of the network # that it is on. so create this as a new network. - self.wireless_networks[old_hash].remove_ap(ap) - self._remove_net_if_empty(self.wireless_networks[old_hash], old_hash) + self.wireless_networks[old_hash_value].remove_ap(ap) + self._remove_net_if_empty(self.wireless_networks[old_hash_value], + old_hash_value) self._add_ap_to_network(ap) def add_access_point(self, device, ap_o): @@ -1094,6 +583,11 @@ class MeshBox(gtk.VBox): ap.initialize() def remove_access_point(self, ap_o): + if self._adhoc_manager is not None: + if self._adhoc_manager.is_sugar_adhoc_access_point(ap_o): + self._adhoc_manager.remove_access_point(ap_o) + return + # we don't keep an index of ap object path to network, but since # we'll only ever have a handful of networks, just try them all... for net in self.wireless_networks.values(): @@ -1108,7 +602,26 @@ class MeshBox(gtk.VBox): # it's not an error if the AP isn't found, since we might have ignored # it (e.g. olpc-mesh adhoc network) - logging.debug('Can not remove access point %s' % ap_o) + logging.debug('Can not remove access point %s', ap_o) + + def add_adhoc_networks(self, device): + if self._adhoc_manager is None: + self._adhoc_manager = get_adhoc_manager_instance() + self._adhoc_manager.start_listening(device) + self._add_adhoc_network_icon(1) + self._add_adhoc_network_icon(6) + self._add_adhoc_network_icon(11) + self._adhoc_manager.autoconnect() + + def remove_adhoc_networks(self): + for icon in self._adhoc_networks: + self._layout.remove(icon) + self._adhoc_networks = [] + + def _add_adhoc_network_icon(self, channel): + icon = SugarAdhocView(channel) + self._layout.add(icon) + self._adhoc_networks.append(icon) def _add_olpc_mesh_icon(self, mesh_mgr, channel): icon = OlpcMeshView(mesh_mgr, channel) @@ -1123,15 +636,15 @@ class MeshBox(gtk.VBox): # the OLPC mesh can be recognised as a "normal" wifi network. remove # any such normal networks if they have been created - for hash, net in self.wireless_networks.iteritems(): + for hash_value, net in self.wireless_networks.iteritems(): if not net.is_olpc_mesh(): continue - logging.debug("removing OLPC mesh IBSS") + logging.debug('removing OLPC mesh IBSS') net.remove_all_aps() net.disconnect() self._layout.remove(net) - del self.wireless_networks[hash] + del self.wireless_networks[hash_value] def disable_olpc_mesh(self, mesh_device): for icon in self._mesh: diff --git a/src/jarabe/desktop/networkviews.py b/src/jarabe/desktop/networkviews.py new file mode 100644 index 0000000..99d46b6 --- /dev/null +++ b/src/jarabe/desktop/networkviews.py @@ -0,0 +1,721 @@ +# Copyright (C) 2006-2007 Red Hat, Inc. +# Copyright (C) 2009 Tomeu Vizoso, Simon Schampijer +# Copyright (C) 2009-2010 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 + +from gettext import gettext as _ +import logging +import hashlib + +import dbus +import glib + +from sugar.graphics.icon import Icon +from sugar.graphics.xocolor import XoColor +from sugar.graphics import xocolor +from sugar.graphics import style +from sugar.graphics.icon import get_icon_state +from sugar.graphics import palette +from sugar.graphics.menuitem import MenuItem +from sugar.util import unique_id +from sugar import profile + +from jarabe.view.pulsingicon import CanvasPulsingIcon +from jarabe.desktop import keydialog +from jarabe.model import network +from jarabe.model.network import Settings +from jarabe.model.network import IP4Config +from jarabe.model.network import WirelessSecurity +from jarabe.model.adhoc import get_adhoc_manager_instance + + +_NM_SERVICE = 'org.freedesktop.NetworkManager' +_NM_IFACE = 'org.freedesktop.NetworkManager' +_NM_PATH = '/org/freedesktop/NetworkManager' +_NM_DEVICE_IFACE = 'org.freedesktop.NetworkManager.Device' +_NM_WIRELESS_IFACE = 'org.freedesktop.NetworkManager.Device.Wireless' +_NM_OLPC_MESH_IFACE = 'org.freedesktop.NetworkManager.Device.OlpcMesh' +_NM_ACCESSPOINT_IFACE = 'org.freedesktop.NetworkManager.AccessPoint' +_NM_ACTIVE_CONN_IFACE = 'org.freedesktop.NetworkManager.Connection.Active' + +_AP_ICON_NAME = 'network-wireless' +_OLPC_MESH_ICON_NAME = 'network-mesh' + + +class WirelessNetworkView(CanvasPulsingIcon): + def __init__(self, initial_ap): + CanvasPulsingIcon.__init__(self, size=style.STANDARD_ICON_SIZE, + cache=True) + self._bus = dbus.SystemBus() + self._access_points = {initial_ap.model.object_path: initial_ap} + self._active_ap = None + self._device = initial_ap.device + self._palette_icon = None + self._disconnect_item = None + self._connect_item = None + self._greyed_out = False + self._name = initial_ap.name + self._mode = initial_ap.mode + self._strength = initial_ap.strength + self._flags = initial_ap.flags + self._wpa_flags = initial_ap.wpa_flags + self._rsn_flags = initial_ap.rsn_flags + self._device_caps = 0 + self._device_state = None + self._color = None + + if self._mode == network.NM_802_11_MODE_ADHOC and \ + network.is_sugar_adhoc_network(self._name): + self._color = profile.get_color() + else: + 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.connect('button-release-event', self.__button_release_event_cb) + + pulse_color = XoColor('%s,%s' % (style.COLOR_BUTTON_GREY.get_svg(), + style.COLOR_TRANSPARENT.get_svg())) + self.props.pulse_color = pulse_color + + self._palette = self._create_palette() + self.set_palette(self._palette) + self._palette_icon.props.xo_color = self._color + self._update_badge() + + interface_props = dbus.Interface(self._device, dbus.PROPERTIES_IFACE) + interface_props.Get(_NM_DEVICE_IFACE, 'State', + reply_handler=self.__get_device_state_reply_cb, + error_handler=self.__get_device_state_error_cb) + interface_props.Get(_NM_WIRELESS_IFACE, 'WirelessCapabilities', + reply_handler=self.__get_device_caps_reply_cb, + error_handler=self.__get_device_caps_error_cb) + interface_props.Get(_NM_WIRELESS_IFACE, 'ActiveAccessPoint', + reply_handler=self.__get_active_ap_reply_cb, + error_handler=self.__get_active_ap_error_cb) + + self._bus.add_signal_receiver(self.__device_state_changed_cb, + signal_name='StateChanged', + path=self._device.object_path, + dbus_interface=_NM_DEVICE_IFACE) + self._bus.add_signal_receiver(self.__wireless_properties_changed_cb, + signal_name='PropertiesChanged', + path=self._device.object_path, + dbus_interface=_NM_WIRELESS_IFACE) + + def _create_palette(self): + icon_name = get_icon_state(_AP_ICON_NAME, self._strength) + self._palette_icon = Icon(icon_name=icon_name, + icon_size=style.STANDARD_ICON_SIZE, + badge_name=self.props.badge_name) + + p = palette.Palette(primary_text=glib.markup_escape_text(self._name), + icon=self._palette_icon) + + self._connect_item = MenuItem(_('Connect'), 'dialog-ok') + self._connect_item.connect('activate', self.__connect_activate_cb) + p.menu.append(self._connect_item) + + self._disconnect_item = MenuItem(_('Disconnect'), 'media-eject') + self._disconnect_item.connect('activate', + self._disconnect_activate_cb) + p.menu.append(self._disconnect_item) + + return p + + def __device_state_changed_cb(self, new_state, old_state, reason): + self._device_state = new_state + self._update_state() + self._update_icon() + self._update_badge() + + def __update_active_ap(self, ap_path): + if ap_path in self._access_points: + # save reference to active AP, so that we always display the + # strength of that one + self._active_ap = self._access_points[ap_path] + self.update_strength() + elif self._active_ap is not None: + # revert to showing state of strongest AP again + self._active_ap = None + self.update_strength() + + def __wireless_properties_changed_cb(self, properties): + if 'ActiveAccessPoint' in properties: + self.__update_active_ap(properties['ActiveAccessPoint']) + + def __get_active_ap_reply_cb(self, ap_path): + self.__update_active_ap(ap_path) + + def __get_active_ap_error_cb(self, err): + logging.error('Error getting the active access point: %s', err) + + def __get_device_caps_reply_cb(self, caps): + self._device_caps = caps + + def __get_device_caps_error_cb(self, err): + logging.error('Error getting the wireless device properties: %s', err) + + def __get_device_state_reply_cb(self, state): + self._device_state = state + self._update_state() + self._update_color() + self._update_badge() + + def __get_device_state_error_cb(self, err): + logging.error('Error getting the device state: %s', err) + + def _update_icon(self): + if self._mode == network.NM_802_11_MODE_ADHOC and \ + network.is_sugar_adhoc_network(self._name): + channel = max([1] + [ap.channel for ap in + self._access_points.values()]) + if self._device_state == network.DEVICE_STATE_ACTIVATED and \ + self._active_ap is not None: + icon_name = 'network-adhoc-%s-connected' % channel + else: + icon_name = 'network-adhoc-%s' % channel + self.props.icon_name = icon_name + icon = self._palette.props.icon + icon.props.icon_name = icon_name + else: + if self._device_state == network.DEVICE_STATE_ACTIVATED and \ + self._active_ap is not None: + icon_name = '%s-connected' % _AP_ICON_NAME + else: + icon_name = _AP_ICON_NAME + + icon_name = get_icon_state(icon_name, self._strength) + if icon_name: + self.props.icon_name = icon_name + icon = self._palette.props.icon + icon.props.icon_name = icon_name + + def _update_badge(self): + if self._mode != network.NM_802_11_MODE_ADHOC: + if network.find_connection_by_ssid(self._name) is not None: + self.props.badge_name = 'emblem-favorite' + self._palette_icon.props.badge_name = 'emblem-favorite' + elif self._flags == network.NM_802_11_AP_FLAGS_PRIVACY: + self.props.badge_name = 'emblem-locked' + self._palette_icon.props.badge_name = 'emblem-locked' + else: + self.props.badge_name = None + self._palette_icon.props.badge_name = None + else: + self.props.badge_name = None + self._palette_icon.props.badge_name = None + + def _update_state(self): + if self._active_ap is not None: + state = self._device_state + else: + state = network.DEVICE_STATE_UNKNOWN + + if state == network.DEVICE_STATE_PREPARE or \ + state == network.DEVICE_STATE_CONFIG or \ + state == network.DEVICE_STATE_NEED_AUTH or \ + state == network.DEVICE_STATE_IP_CONFIG: + if self._disconnect_item: + self._disconnect_item.show() + self._connect_item.hide() + self._palette.props.secondary_text = _('Connecting...') + self.props.pulsing = True + elif state == network.DEVICE_STATE_ACTIVATED: + connection = network.find_connection_by_ssid(self._name) + if connection is not None: + if self._mode == network.NM_802_11_MODE_INFRA: + connection.set_connected() + if self._disconnect_item: + self._disconnect_item.show() + self._connect_item.hide() + self._palette.props.secondary_text = _('Connected') + self.props.pulsing = False + else: + if self._disconnect_item: + self._disconnect_item.hide() + self._connect_item.show() + self._palette.props.secondary_text = None + self.props.pulsing = False + + def _update_color(self): + if self._greyed_out: + self.props.pulsing = False + self.props.base_color = XoColor('#D5D5D5,#D5D5D5') + else: + self.props.base_color = self._color + + def _disconnect_activate_cb(self, item): + if self._mode == network.NM_802_11_MODE_INFRA: + connection = network.find_connection_by_ssid(self._name) + if connection: + connection.disable_autoconnect() + + ap_paths = self._access_points.keys() + network.disconnect_access_points(ap_paths) + + def _add_ciphers_from_flags(self, flags, pairwise): + ciphers = [] + if pairwise: + if flags & network.NM_802_11_AP_SEC_PAIR_TKIP: + ciphers.append('tkip') + if flags & network.NM_802_11_AP_SEC_PAIR_CCMP: + ciphers.append('ccmp') + else: + if flags & network.NM_802_11_AP_SEC_GROUP_WEP40: + ciphers.append('wep40') + if flags & network.NM_802_11_AP_SEC_GROUP_WEP104: + ciphers.append('wep104') + if flags & network.NM_802_11_AP_SEC_GROUP_TKIP: + ciphers.append('tkip') + if flags & network.NM_802_11_AP_SEC_GROUP_CCMP: + ciphers.append('ccmp') + return ciphers + + def _get_security(self): + if not (self._flags & network.NM_802_11_AP_FLAGS_PRIVACY) and \ + (self._wpa_flags == network.NM_802_11_AP_SEC_NONE) and \ + (self._rsn_flags == network.NM_802_11_AP_SEC_NONE): + # No security + return None + + if (self._flags & network.NM_802_11_AP_FLAGS_PRIVACY) and \ + (self._wpa_flags == network.NM_802_11_AP_SEC_NONE) and \ + (self._rsn_flags == network.NM_802_11_AP_SEC_NONE): + # Static WEP, Dynamic WEP, or LEAP + wireless_security = WirelessSecurity() + wireless_security.key_mgmt = 'none' + return wireless_security + + if (self._mode != network.NM_802_11_MODE_INFRA): + # Stuff after this point requires infrastructure + logging.error('The infrastructure mode is not supoorted' + ' by your wireless device.') + return None + + if (self._rsn_flags & network.NM_802_11_AP_SEC_KEY_MGMT_PSK) and \ + (self._device_caps & network.NM_802_11_DEVICE_CAP_RSN): + # WPA2 PSK first + pairwise = self._add_ciphers_from_flags(self._rsn_flags, True) + group = self._add_ciphers_from_flags(self._rsn_flags, False) + wireless_security = WirelessSecurity() + wireless_security.key_mgmt = 'wpa-psk' + wireless_security.proto = 'rsn' + wireless_security.pairwise = pairwise + wireless_security.group = group + return wireless_security + + if (self._wpa_flags & network.NM_802_11_AP_SEC_KEY_MGMT_PSK) and \ + (self._device_caps & network.NM_802_11_DEVICE_CAP_WPA): + # WPA PSK + pairwise = self._add_ciphers_from_flags(self._wpa_flags, True) + group = self._add_ciphers_from_flags(self._wpa_flags, False) + wireless_security = WirelessSecurity() + wireless_security.key_mgmt = 'wpa-psk' + wireless_security.proto = 'wpa' + wireless_security.pairwise = pairwise + wireless_security.group = group + return wireless_security + + def __connect_activate_cb(self, icon): + self._connect() + + def __button_release_event_cb(self, icon, event): + self._connect() + + def _connect(self): + connection = network.find_connection_by_ssid(self._name) + if connection is None: + settings = Settings() + settings.connection.id = 'Auto ' + self._name + uuid = settings.connection.uuid = unique_id() + settings.connection.type = '802-11-wireless' + settings.wireless.ssid = self._name + + if self._mode == network.NM_802_11_MODE_INFRA: + 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 = 'link-local' + + wireless_security = self._get_security() + settings.wireless_security = wireless_security + + if wireless_security is not None: + settings.wireless.security = '802-11-wireless-security' + + connection = network.add_connection(uuid, settings) + + obj = self._bus.get_object(_NM_SERVICE, _NM_PATH) + netmgr = dbus.Interface(obj, _NM_IFACE) + + netmgr.ActivateConnection(network.SETTINGS_SERVICE, connection.path, + self._device.object_path, + '/', + reply_handler=self.__activate_reply_cb, + error_handler=self.__activate_error_cb) + + def __activate_reply_cb(self, connection): + logging.debug('Connection activated: %s', connection) + + def __activate_error_cb(self, err): + logging.error('Failed to activate connection: %s', err) + + def set_filter(self, query): + self._greyed_out = self._name.lower().find(query) == -1 + self._update_icon() + self._update_color() + + def create_keydialog(self, settings, response): + keydialog.create(self._name, self._flags, self._wpa_flags, + self._rsn_flags, self._device_caps, settings, + response) + + def update_strength(self): + if self._active_ap is not None: + # display strength of AP that we are connected to + new_strength = self._active_ap.strength + else: + # display the strength of the strongest AP that makes up this + # network, also considering that there may be no APs + new_strength = max([0] + [ap.strength for ap in + self._access_points.values()]) + + if new_strength != self._strength: + self._strength = new_strength + self._update_icon() + + def add_ap(self, ap): + self._access_points[ap.model.object_path] = ap + self.update_strength() + + def remove_ap(self, ap): + path = ap.model.object_path + if path not in self._access_points: + return + del self._access_points[path] + if self._active_ap == ap: + self._active_ap = None + self.update_strength() + + def num_aps(self): + return len(self._access_points) + + def find_ap(self, ap_path): + if ap_path not in self._access_points: + return None + return self._access_points[ap_path] + + def is_olpc_mesh(self): + return self._mode == network.NM_802_11_MODE_ADHOC \ + and self.name == 'olpc-mesh' + + def remove_all_aps(self): + for ap in self._access_points.values(): + ap.disconnect() + self._access_points = {} + self._active_ap = None + self.update_strength() + + def disconnect(self): + self._bus.remove_signal_receiver(self.__device_state_changed_cb, + signal_name='StateChanged', + path=self._device.object_path, + dbus_interface=_NM_DEVICE_IFACE) + self._bus.remove_signal_receiver(self.__wireless_properties_changed_cb, + signal_name='PropertiesChanged', + path=self._device.object_path, + dbus_interface=_NM_WIRELESS_IFACE) + + +class SugarAdhocView(CanvasPulsingIcon): + """To mimic the mesh behavior on devices where mesh hardware is + not available we support the creation of an Ad-hoc network on + three channels 1, 6, 11. This is the class for an icon + representing a channel in the neighborhood view. + + """ + + _ICON_NAME = 'network-adhoc-' + _NAME = 'Ad-hoc Network ' + + def __init__(self, channel): + CanvasPulsingIcon.__init__(self, + icon_name=self._ICON_NAME + str(channel), + size=style.STANDARD_ICON_SIZE, cache=True) + self._bus = dbus.SystemBus() + self._channel = channel + self._disconnect_item = None + self._connect_item = None + self._palette_icon = None + self._greyed_out = False + + get_adhoc_manager_instance().connect('members-changed', + self.__members_changed_cb) + get_adhoc_manager_instance().connect('state-changed', + self.__state_changed_cb) + + self.connect('button-release-event', self.__button_release_event_cb) + + pulse_color = XoColor('%s,%s' % (style.COLOR_BUTTON_GREY.get_svg(), + style.COLOR_TRANSPARENT.get_svg())) + self.props.pulse_color = pulse_color + self._state_color = XoColor('%s,%s' % \ + (profile.get_color().get_stroke_color(), + style.COLOR_TRANSPARENT.get_svg())) + self.props.base_color = self._state_color + self._palette = self._create_palette() + self.set_palette(self._palette) + self._palette_icon.props.xo_color = self._state_color + + def _create_palette(self): + self._palette_icon = Icon( \ + icon_name=self._ICON_NAME + str(self._channel), + icon_size=style.STANDARD_ICON_SIZE) + + palette_ = palette.Palette(_('Ad-hoc Network %d') % self._channel, + icon=self._palette_icon) + + self._connect_item = MenuItem(_('Connect'), 'dialog-ok') + self._connect_item.connect('activate', self.__connect_activate_cb) + palette_.menu.append(self._connect_item) + + self._disconnect_item = MenuItem(_('Disconnect'), 'media-eject') + self._disconnect_item.connect('activate', + self.__disconnect_activate_cb) + palette_.menu.append(self._disconnect_item) + + return palette_ + + def __button_release_event_cb(self, icon, event): + get_adhoc_manager_instance().activate_channel(self._channel) + + def __connect_activate_cb(self, icon): + get_adhoc_manager_instance().activate_channel(self._channel) + + def __disconnect_activate_cb(self, icon): + get_adhoc_manager_instance().deactivate_active_channel() + + def __state_changed_cb(self, adhoc_manager, channel, device_state): + if self._channel == channel: + state = device_state + else: + state = network.DEVICE_STATE_UNKNOWN + + if state == network.DEVICE_STATE_ACTIVATED: + icon_name = '%s-connected' % (self._ICON_NAME + str(self._channel)) + else: + icon_name = self._ICON_NAME + str(self._channel) + + if icon_name is not None: + self.props.icon_name = icon_name + icon = self._palette.props.icon + icon.props.icon_name = icon_name + + if state in [network.DEVICE_STATE_PREPARE, + network.DEVICE_STATE_CONFIG, + network.DEVICE_STATE_NEED_AUTH, + network.DEVICE_STATE_IP_CONFIG]: + if self._disconnect_item: + self._disconnect_item.show() + self._connect_item.hide() + self._palette.props.secondary_text = _('Connecting...') + self.props.pulsing = True + elif state == network.DEVICE_STATE_ACTIVATED: + if self._disconnect_item: + self._disconnect_item.show() + self._connect_item.hide() + self._palette.props.secondary_text = _('Connected') + self.props.pulsing = False + else: + if self._disconnect_item: + self._disconnect_item.hide() + self._connect_item.show() + self._palette.props.secondary_text = None + self.props.pulsing = False + + def _update_color(self): + if self._greyed_out: + self.props.pulsing = False + self.props.base_color = XoColor('#D5D5D5,#D5D5D5') + else: + self.props.base_color = self._state_color + + def __members_changed_cb(self, adhoc_manager, channel, has_members): + if channel == self._channel: + if has_members == True: + self._state_color = profile.get_color() + else: + color = '%s,%s' % (profile.get_color().get_stroke_color(), + style.COLOR_TRANSPARENT.get_svg()) + self._state_color = XoColor(color) + + if not self._greyed_out: + self.props.base_color = self._state_color + self._palette_icon.props.xo_color = self._state_color + + def set_filter(self, query): + name = self._NAME + str(self._channel) + self._greyed_out = name.lower().find(query) == -1 + self._update_color() + + +class OlpcMeshView(CanvasPulsingIcon): + def __init__(self, mesh_mgr, channel): + CanvasPulsingIcon.__init__(self, icon_name=_OLPC_MESH_ICON_NAME, + size=style.STANDARD_ICON_SIZE, cache=True) + self._bus = dbus.SystemBus() + self._channel = channel + self._mesh_mgr = mesh_mgr + self._disconnect_item = None + self._connect_item = None + self._greyed_out = False + self._name = '' + self._device_state = None + self._active = False + device = mesh_mgr.mesh_device + + self.connect('button-release-event', self.__button_release_event_cb) + + interface_props = dbus.Interface(device, dbus.PROPERTIES_IFACE) + interface_props.Get(_NM_DEVICE_IFACE, 'State', + reply_handler=self.__get_device_state_reply_cb, + error_handler=self.__get_device_state_error_cb) + interface_props.Get(_NM_OLPC_MESH_IFACE, 'ActiveChannel', + reply_handler=self.__get_active_channel_reply_cb, + error_handler=self.__get_active_channel_error_cb) + + self._bus.add_signal_receiver(self.__device_state_changed_cb, + signal_name='StateChanged', + path=device.object_path, + dbus_interface=_NM_DEVICE_IFACE) + self._bus.add_signal_receiver(self.__wireless_properties_changed_cb, + signal_name='PropertiesChanged', + path=device.object_path, + dbus_interface=_NM_OLPC_MESH_IFACE) + + pulse_color = XoColor('%s,%s' % (style.COLOR_BUTTON_GREY.get_svg(), + style.COLOR_TRANSPARENT.get_svg())) + self.props.pulse_color = pulse_color + self.props.base_color = profile.get_color() + self._palette = self._create_palette() + self.set_palette(self._palette) + + def _create_palette(self): + _palette = palette.Palette(_('Mesh Network %d') % self._channel) + + self._connect_item = MenuItem(_('Connect'), 'dialog-ok') + self._connect_item.connect('activate', self.__connect_activate_cb) + _palette.menu.append(self._connect_item) + + return _palette + + def __get_device_state_reply_cb(self, state): + self._device_state = state + self._update() + + def __get_device_state_error_cb(self, err): + logging.error('Error getting the device state: %s', err) + + def __device_state_changed_cb(self, new_state, old_state, reason): + self._device_state = new_state + self._update() + + def __get_active_channel_reply_cb(self, channel): + self._active = (channel == self._channel) + self._update() + + def __get_active_channel_error_cb(self, err): + logging.error('Error getting the active channel: %s', err) + + def __wireless_properties_changed_cb(self, properties): + if 'ActiveChannel' in properties: + channel = properties['ActiveChannel'] + self._active = (channel == self._channel) + self._update() + + def _update(self): + if self._active: + state = self._device_state + else: + state = network.DEVICE_STATE_UNKNOWN + + if state in [network.DEVICE_STATE_PREPARE, + network.DEVICE_STATE_CONFIG, + network.DEVICE_STATE_NEED_AUTH, + network.DEVICE_STATE_IP_CONFIG]: + if self._disconnect_item: + self._disconnect_item.show() + self._connect_item.hide() + self._palette.props.secondary_text = _('Connecting...') + self.props.pulsing = True + elif state == network.DEVICE_STATE_ACTIVATED: + if self._disconnect_item: + self._disconnect_item.show() + self._connect_item.hide() + self._palette.props.secondary_text = _('Connected') + self.props.pulsing = False + else: + if self._disconnect_item: + self._disconnect_item.hide() + self._connect_item.show() + self._palette.props.secondary_text = None + self.props.pulsing = False + + def _update_color(self): + if self._greyed_out: + self.props.base_color = XoColor('#D5D5D5,#D5D5D5') + else: + self.props.base_color = profile.get_color() + + def __connect_activate_cb(self, icon): + self._connect() + + def __button_release_event_cb(self, icon, event): + self._connect() + + def _connect(self): + self._mesh_mgr.user_activate_channel(self._channel) + + def __activate_reply_cb(self, connection): + logging.debug('Connection activated: %s', connection) + + def __activate_error_cb(self, err): + logging.error('Failed to activate connection: %s', err) + + def set_filter(self, query): + self._greyed_out = (query != '') + self._update_color() + + def disconnect(self): + device_object_path = self._mesh_mgr.mesh_device.object_path + + self._bus.remove_signal_receiver(self.__device_state_changed_cb, + signal_name='StateChanged', + path=device_object_path, + dbus_interface=_NM_DEVICE_IFACE) + self._bus.remove_signal_receiver(self.__wireless_properties_changed_cb, + signal_name='PropertiesChanged', + path=device_object_path, + dbus_interface=_NM_OLPC_MESH_IFACE) diff --git a/src/jarabe/desktop/schoolserver.py b/src/jarabe/desktop/schoolserver.py index fc9ddeb..aea2357 100644 --- a/src/jarabe/desktop/schoolserver.py +++ b/src/jarabe/desktop/schoolserver.py @@ -16,8 +16,9 @@ import logging from gettext import gettext as _ -from xmlrpclib import ServerProxy, Error +import xmlrpclib import socket +import httplib import os from string import ascii_uppercase import random @@ -29,15 +30,17 @@ import gconf from sugar import env from sugar.profile import get_profile -REGISTER_URL = 'http://schoolserver:8080/' +_REGISTER_URL = 'http://schoolserver:8080/' +_REGISTER_TIMEOUT = 8 -def generate_serial_number(): + +def _generate_serial_number(): """ Generates a serial number based on 3 random uppercase letters and the last 8 digits of the current unix seconds. """ serial_part1 = [] - for y_ in range(3) : + for y_ in range(3): serial_part1.append(random.choice(ascii_uppercase)) serial_part1 = ''.join(serial_part1) @@ -46,7 +49,8 @@ def generate_serial_number(): return serial -def store_identifiers(serial_number, uuid, backup_url): + +def _store_identifiers(serial_number, uuid_, backup_url): """ Stores the serial number, uuid and backup_url in the identifier folder inside the profile directory so that these identifiers can be used for backup. """ @@ -64,7 +68,7 @@ def store_identifiers(serial_number, uuid, backup_url): if os.path.exists(os.path.join(identifier_path, 'uuid')): os.remove(os.path.join(identifier_path, 'uuid')) uuid_file = open(os.path.join(identifier_path, 'uuid'), 'w') - uuid_file.write(uuid) + uuid_file.write(uuid_) uuid_file.close() if os.path.exists(os.path.join(identifier_path, 'backup_url')): @@ -73,33 +77,56 @@ def store_identifiers(serial_number, uuid, backup_url): backup_url_file.write(backup_url) backup_url_file.close() + class RegisterError(Exception): pass -def register_laptop(url=REGISTER_URL): + +class _TimeoutHTTP(httplib.HTTP): + + def __init__(self, host='', port=None, strict=None, timeout=None): + if port == 0: + port = None + # FIXME: Depending on undocumented internals that can break between + # Python releases. Please have a look at SL #2350 + self._setup(self._connection_class(host, + port, strict, timeout=_REGISTER_TIMEOUT)) + + +class _TimeoutTransport(xmlrpclib.Transport): + + def make_connection(self, host): + host, extra_headers, x509_ = self.get_host_info(host) + return _TimeoutHTTP(host, timeout=_REGISTER_TIMEOUT) + + +def register_laptop(url=_REGISTER_URL): profile = get_profile() client = gconf.client_get_default() - if have_ofw_tree(): - sn = read_ofw('mfg-data/SN') - uuid_ = read_ofw('mfg-data/U#') + if _have_ofw_tree(): + sn = _read_ofw('mfg-data/SN') + uuid_ = _read_ofw('mfg-data/U#') sn = sn or 'SHF00000000' uuid_ = uuid_ or '00000000-0000-0000-0000-000000000000' else: - sn = generate_serial_number() + sn = _generate_serial_number() uuid_ = str(uuid.uuid1()) - setting_name = '/desktop/sugar/collaboration/jabber_server' - jabber_server = client.get_string(setting_name) - store_identifiers(sn, uuid_, jabber_server) + + setting_name = '/desktop/sugar/collaboration/jabber_server' + jabber_server = client.get_string(setting_name) + _store_identifiers(sn, uuid_, jabber_server) + + if jabber_server: url = 'http://' + jabber_server + ':8080/' nick = client.get_string('/desktop/sugar/user/nick') - server = ServerProxy(url) + server = xmlrpclib.ServerProxy(url, _TimeoutTransport()) try: data = server.register(sn, nick, uuid_, profile.pubkey) - except (Error, socket.error): + except (xmlrpclib.Error, TypeError, socket.error): logging.exception('Registration: cannot connect to server') raise RegisterError(_('Cannot connect to the server.')) @@ -114,10 +141,12 @@ def register_laptop(url=REGISTER_URL): return True -def have_ofw_tree(): + +def _have_ofw_tree(): return os.path.exists('/ofw') -def read_ofw(path): + +def _read_ofw(path): path = os.path.join('/ofw', path) if not os.path.exists(path): return None diff --git a/src/jarabe/desktop/snowflakelayout.py b/src/jarabe/desktop/snowflakelayout.py index 5782cff..e4963ba 100644 --- a/src/jarabe/desktop/snowflakelayout.py +++ b/src/jarabe/desktop/snowflakelayout.py @@ -21,11 +21,14 @@ import hippo from sugar.graphics import style + _BASE_DISTANCE = style.zoom(25) _CHILDREN_FACTOR = style.zoom(3) + class SnowflakeLayout(gobject.GObject, hippo.CanvasLayout): __gtype_name__ = 'SugarSnowflakeLayout' + def __init__(self): gobject.GObject.__init__(self) self._nflakes = 0 diff --git a/src/jarabe/desktop/spreadlayout.py b/src/jarabe/desktop/spreadlayout.py index ffc5bc7..9200361 100644 --- a/src/jarabe/desktop/spreadlayout.py +++ b/src/jarabe/desktop/spreadlayout.py @@ -22,10 +22,13 @@ from sugar.graphics import style from jarabe.desktop.grid import Grid + _CELL_SIZE = 4.0 + class SpreadLayout(gobject.GObject, hippo.CanvasLayout): __gtype_name__ = 'SugarSpreadLayout' + def __init__(self): gobject.GObject.__init__(self) self._box = None @@ -80,4 +83,3 @@ class SpreadLayout(gobject.GObject, hippo.CanvasLayout): def _grid_child_changed_cb(self, grid, child): child.emit_request_changed() - diff --git a/src/jarabe/desktop/transitionbox.py b/src/jarabe/desktop/transitionbox.py index af17cfb..4042044 100644 --- a/src/jarabe/desktop/transitionbox.py +++ b/src/jarabe/desktop/transitionbox.py @@ -20,7 +20,9 @@ import gobject from sugar.graphics import style from sugar.graphics import animator -from jarabe.desktop.myicon import MyIcon +from jarabe.model.buddy import get_owner_instance +from jarabe.view.buddyicon import BuddyIcon + class _Animation(animator.Animation): def __init__(self, icon, start_size, end_size): @@ -34,8 +36,10 @@ class _Animation(animator.Animation): d = (self.end_size - self.start_size) * current self._icon.props.size = self.start_size + d + class _Layout(gobject.GObject, hippo.CanvasLayout): __gtype_name__ = 'SugarTransitionBoxLayout' + def __init__(self): gobject.GObject.__init__(self) self._box = None @@ -59,12 +63,12 @@ class _Layout(gobject.GObject, hippo.CanvasLayout): y + (height - child_height) / 2, child_width, child_height, origin_changed) + class TransitionBox(hippo.Canvas): __gtype_name__ = 'SugarTransitionBox' __gsignals__ = { - 'completed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([])) + 'completed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), } def __init__(self): @@ -77,7 +81,8 @@ class TransitionBox(hippo.Canvas): self._layout = _Layout() self._box.set_layout(self._layout) - self._my_icon = MyIcon(style.XLARGE_ICON_SIZE) + self._my_icon = BuddyIcon(buddy=get_owner_instance(), + size=style.XLARGE_ICON_SIZE) self._box.append(self._my_icon) self._animator = animator.Animator(0.3) diff --git a/src/jarabe/frame/__init__.py b/src/jarabe/frame/__init__.py index d7aec3d..b3e4b80 100644 --- a/src/jarabe/frame/__init__.py +++ b/src/jarabe/frame/__init__.py @@ -16,8 +16,10 @@ from jarabe.frame.frame import Frame + _view = None + def get_view(): global _view if not _view: diff --git a/src/jarabe/frame/activitiestray.py b/src/jarabe/frame/activitiestray.py index b5762ee..6e08fc0 100644 --- a/src/jarabe/frame/activitiestray.py +++ b/src/jarabe/frame/activitiestray.py @@ -1,5 +1,6 @@ # Copyright (C) 2006-2007 Red Hat, Inc. # Copyright (C) 2008 One Laptop Per Child +# Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> # # 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,20 +34,16 @@ from sugar.graphics.toolbutton import ToolButton from sugar.graphics.icon import Icon, get_icon_file_name from sugar.graphics.palette import Palette, WidgetInvoker from sugar.graphics.menuitem import MenuItem -from sugar.activity.activityhandle import ActivityHandle -from sugar.activity import activityfactory from sugar.datastore import datastore from sugar import mime from sugar import env from jarabe.model import shell -from jarabe.model import neighborhood -from jarabe.model import owner +from jarabe.model import invites from jarabe.model import bundleregistry from jarabe.model import filetransfer from jarabe.view.palettes import JournalPalette, CurrentActivityPalette from jarabe.view.pulsingicon import PulsingIcon -from jarabe.view import launcher from jarabe.frame.frameinvoker import FrameWidgetInvoker from jarabe.frame.notification import NotificationIcon import jarabe.frame @@ -59,6 +56,7 @@ class ActivityButton(RadioToolButton): self.set_palette_invoker(FrameWidgetInvoker(self)) self._home_activity = home_activity + self._notify_launch_hid = None self._icon = PulsingIcon() self._icon.props.base_color = home_activity.get_icon_color() @@ -72,13 +70,12 @@ class ActivityButton(RadioToolButton): self.set_icon_widget(self._icon) self._icon.show() - if home_activity.props.launching: + if home_activity.props.launch_status == shell.Activity.LAUNCHING: self._icon.props.pulsing = True - self._notify_launching_hid = home_activity.connect( \ - 'notify::launching', self.__notify_launching_cb) - else: - self._notify_launching_hid = None - self._notif_icon = None + self._notify_launch_hid = home_activity.connect( \ + 'notify::launch-status', self.__notify_launch_status_cb) + elif home_activity.props.launch_status == shell.Activity.LAUNCH_FAILED: + self._on_failed_launch() def create_palette(self): if self._home_activity.is_journal(): @@ -88,26 +85,64 @@ class ActivityButton(RadioToolButton): palette.set_group_id('frame') self.set_palette(palette) - def __notify_launching_cb(self, home_activity, pspec): - if not home_activity.props.launching: + def _on_failed_launch(self): + # TODO http://bugs.sugarlabs.org/ticket/2007 + pass + + def __notify_launch_status_cb(self, home_activity, pspec): + home_activity.disconnect(self._notify_launch_hid) + self._notify_launch_hid = None + if home_activity.props.launch_status == shell.Activity.LAUNCH_FAILED: + self._on_failed_launch() + else: self._icon.props.pulsing = False - home_activity.disconnect(self._notify_launching_hid) -class BaseInviteButton(ToolButton): + +class InviteButton(ToolButton): + """Invite to shared activity""" def __init__(self, invite): ToolButton.__init__(self) + self._invite = invite + self.connect('clicked', self.__clicked_cb) + self.connect('destroy', self.__destroy_cb) + + bundle_registry = bundleregistry.get_registry() + bundle = bundle_registry.get_bundle(invite.get_bundle_id()) + self._icon = Icon() + self._icon.props.xo_color = invite.get_color() + if bundle is not None: + self._icon.props.file = bundle.get_icon() + else: + self._icon.props.icon_name = 'image-missing' self.set_icon_widget(self._icon) self._icon.show() - self.connect('clicked', self.__clicked_cb) - self.connect('destroy', self.__destroy_cb) + palette = InvitePalette(invite) + palette.props.invoker = FrameWidgetInvoker(self) + palette.set_group_id('frame') + self.set_palette(palette) + self._notif_icon = NotificationIcon() self._notif_icon.connect('button-release-event', self.__button_release_event_cb) + self._notif_icon.props.xo_color = invite.get_color() + if bundle is not None: + self._notif_icon.props.icon_filename = bundle.get_icon() + else: + self._notif_icon.props.icon_name = 'image-missing' + + palette = InvitePalette(invite) + palette.props.invoker = WidgetInvoker(self._notif_icon) + palette.set_group_id('frame') + self._notif_icon.palette = palette + + frame = jarabe.frame.get_view() + frame.add_notification(self._notif_icon, gtk.CORNER_TOP_LEFT) + def __button_release_event_cb(self, icon, event): self.emit('clicked') @@ -118,118 +153,23 @@ class BaseInviteButton(ToolButton): self._notif_icon = None self._launch() - def _launch(self): - """Launch the target of the invite""" - raise NotImplementedError - def __destroy_cb(self, button): frame = jarabe.frame.get_view() frame.remove_notification(self._notif_icon) -class ActivityInviteButton(BaseInviteButton): - """Invite to shared activity""" - def __init__(self, invite): - BaseInviteButton.__init__(self, invite) - mesh = neighborhood.get_model() - activity_model = mesh.get_activity(invite.get_activity_id()) - self._activity_model = activity_model - self._bundle_id = activity_model.get_bundle_id() - - self._icon.props.xo_color = activity_model.get_color() - if activity_model.get_icon_name(): - self._icon.props.file = activity_model.get_icon_name() - else: - self._icon.props.icon_name = 'image-missing' - - palette = ActivityInvitePalette(invite) - palette.props.invoker = FrameWidgetInvoker(self) - palette.set_group_id('frame') - self.set_palette(palette) - - self._notif_icon.props.xo_color = activity_model.get_color() - if activity_model.get_icon_name(): - icon_name = activity_model.get_icon_name() - self._notif_icon.props.icon_filename = icon_name - else: - self._notif_icon.props.icon_name = 'image-missing' - - palette = ActivityInvitePalette(invite) - palette.props.invoker = WidgetInvoker(self._notif_icon) - palette.set_group_id('frame') - self._notif_icon.palette = palette - - frame = jarabe.frame.get_view() - frame.add_notification(self._notif_icon, - gtk.CORNER_TOP_LEFT) - def _launch(self): """Join the activity in the invite.""" + self._invite.join() - shell_model = shell.get_model() - activity = shell_model.get_activity_by_id(self._activity_model.get_id()) - if activity: - activity.get_window().activate(gtk.get_current_event_time()) - return - - registry = bundleregistry.get_registry() - bundle = registry.get_bundle(self._bundle_id) - - launcher.add_launcher(self._activity_model.get_id(), - bundle.get_icon(), - self._activity_model.get_color()) - handle = ActivityHandle(self._activity_model.get_id()) - activityfactory.create(bundle, handle) +class InvitePalette(Palette): + """Palette for frame or notification icon for invites.""" -class PrivateInviteButton(BaseInviteButton): - """Invite to a private one to one channel""" def __init__(self, invite): - BaseInviteButton.__init__(self, invite) - self._private_channel = invite.get_private_channel() - self._bundle_id = invite.get_bundle_id() - - client = gconf.client_get_default() - color = XoColor(client.get_string('/desktop/sugar/user/color')) - - self._icon.props.xo_color = color - registry = bundleregistry.get_registry() - self._bundle = registry.get_bundle(self._bundle_id) - - if self._bundle: - self._icon.props.file = self._bundle.get_icon() - else: - self._icon.props.icon_name = 'image-missing' - - palette = PrivateInvitePalette(invite) - palette.props.invoker = FrameWidgetInvoker(self) - palette.set_group_id('frame') - self.set_palette(palette) - - self._notif_icon.props.xo_color = color - - if self._bundle: - self._notif_icon.props.icon_filename = self._bundle.get_icon() - else: - self._notif_icon.props.icon_name = 'image-missing' - - palette = PrivateInvitePalette(invite) - palette.props.invoker = WidgetInvoker(self._notif_icon) - palette.set_group_id('frame') - self._notif_icon.palette = palette - - frame = jarabe.frame.get_view() - frame.add_notification(self._notif_icon, - gtk.CORNER_TOP_LEFT) - - def _launch(self): - """Start the activity with private channel.""" - activityfactory.create_with_uri(self._bundle, self._private_channel) - -class BaseInvitePalette(Palette): - """Palette for frame or notification icon for invites.""" - def __init__(self): Palette.__init__(self, '') + self._invite = invite + menu_item = MenuItem(_('Join'), icon_name='dialog-ok') menu_item.connect('activate', self.__join_activate_cb) self.menu.append(menu_item) @@ -240,72 +180,22 @@ class BaseInvitePalette(Palette): self.menu.append(menu_item) menu_item.show() - def __join_activate_cb(self, menu_item): - self._join() - - def __decline_activate_cb(self, menu_item): - self._decline() - - def _join(self): - raise NotImplementedError - - def _decline(self): - raise NotImplementedError - - -class ActivityInvitePalette(BaseInvitePalette): - """Palette for shared activity invites.""" - - def __init__(self, invite): - BaseInvitePalette.__init__(self) - - mesh = neighborhood.get_model() - activity_model = mesh.get_activity(invite.get_activity_id()) - self._activity_model = activity_model - self._bundle_id = activity_model.get_bundle_id() + bundle_id = invite.get_bundle_id() registry = bundleregistry.get_registry() - self._bundle = registry.get_bundle(self._bundle_id) + self._bundle = registry.get_bundle(bundle_id) if self._bundle: self.set_primary_text(self._bundle.get_name()) else: - self.set_primary_text(self._bundle_id) + self.set_primary_text(bundle_id) - def _join(self): - handle = ActivityHandle(self._activity_model.get_id()) - activityfactory.create(self._bundle, handle) + def __join_activate_cb(self, menu_item): + self._invite.join() - def _decline(self): - invites = owner.get_model().get_invites() + def __decline_activate_cb(self, menu_item): + invites_model = invites.get_instance() activity_id = self._activity_model.get_id() - invites.remove_activity(activity_id) - - -class PrivateInvitePalette(BaseInvitePalette): - """Palette for private channel invites.""" - - def __init__(self, invite): - BaseInvitePalette.__init__(self) - - self._private_channel = invite.get_private_channel() - self._bundle_id = invite.get_bundle_id() - - registry = bundleregistry.get_registry() - self._bundle = registry.get_bundle(self._bundle_id) - if self._bundle: - self.set_primary_text(self._bundle.get_name()) - else: - self.set_primary_text(self._bundle_id) - - def _join(self): - activityfactory.create_with_uri(self._bundle, self._private_channel) - - invites = owner.get_model().get_invites() - invites.remove_private_channel(self._private_channel) - - def _decline(self): - invites = owner.get_model().get_invites() - invites.remove_private_channel(self._private_channel) + invites_model.remove_activity(activity_id) class ActivitiesTray(HTray): @@ -318,13 +208,14 @@ class ActivitiesTray(HTray): self._home_model = shell.get_model() self._home_model.connect('activity-added', self.__activity_added_cb) - self._home_model.connect('activity-removed', self.__activity_removed_cb) + self._home_model.connect('activity-removed', + self.__activity_removed_cb) self._home_model.connect('active-activity-changed', self.__activity_changed_cb) self._home_model.connect('tabbing-activity-changed', self.__tabbing_activity_changed_cb) - self._invites = owner.get_model().get_invites() + self._invites = invites.get_instance() for invite in self._invites: self._add_invite(invite) self._invites.connect('invite-added', self.__invite_added_cb) @@ -388,32 +279,22 @@ class ActivitiesTray(HTray): window.activate(gtk.get_current_event_time()) def __invite_clicked_cb(self, icon, invite): - if hasattr(invite, 'get_activity_id'): - self._invites.remove_invite(invite) - else: - self._invites.remove_private_invite(invite) + self._invites.remove_invite(invite) - def __invite_added_cb(self, invites, invite): + def __invite_added_cb(self, invites_model, invite): self._add_invite(invite) - def __invite_removed_cb(self, invites, invite): + def __invite_removed_cb(self, invites_model, invite): self._remove_invite(invite) def _add_invite(self, invite): - """Add an invite (SugarInvite or PrivateInvite)""" - item = None - if hasattr(invite, 'get_activity_id'): - mesh = neighborhood.get_model() - activity_model = mesh.get_activity(invite.get_activity_id()) - if activity_model is not None: - item = ActivityInviteButton(invite) - else: - item = PrivateInviteButton(invite) - if item is not None: - item.connect('clicked', self.__invite_clicked_cb, invite) - self.add_item(item) - item.show() - self._invite_to_item[invite] = item + """Add an invite""" + item = InviteButton(invite) + item.connect('clicked', self.__invite_clicked_cb, invite) + self.add_item(item) + item.show() + + self._invite_to_item[invite] = item def _remove_invite(self, invite): self.remove_item(self._invite_to_item[invite]) @@ -432,6 +313,7 @@ class ActivitiesTray(HTray): self.add_item(button) button.show() + class BaseTransferButton(ToolButton): """Button with a notification attached """ @@ -468,6 +350,7 @@ class BaseTransferButton(ToolButton): filetransfer.FT_REASON_LOCAL_STOPPED: self.remove() + class IncomingTransferButton(BaseTransferButton): """UI element representing an ongoing incoming file transfer """ @@ -490,7 +373,7 @@ class IncomingTransferButton(BaseTransferButton): self.notif_icon.props.icon_name = icon_name break - icon_color = XoColor(file_transfer.buddy.props.color) + icon_color = file_transfer.buddy.props.color self.props.icon_widget.props.xo_color = icon_color self.notif_icon.props.xo_color = icon_color @@ -515,18 +398,18 @@ class IncomingTransferButton(BaseTransferButton): self._ds_object.metadata['buddies'] = '' self._ds_object.metadata['preview'] = '' self._ds_object.metadata['icon-color'] = \ - file_transfer.buddy.props.color + file_transfer.buddy.props.color.to_string() self._ds_object.metadata['mime_type'] = file_transfer.mime_type elif file_transfer.props.state == filetransfer.FT_STATE_COMPLETED: logging.debug('__notify_state_cb COMPLETED') self._ds_object.metadata['progress'] = '100' self._ds_object.file_path = file_transfer.destination_path - datastore.write(self._ds_jobject, transfer_ownership=True, + datastore.write(self._ds_object, transfer_ownership=True, reply_handler=self.__reply_handler_cb, error_handler=self.__error_handler_cb) elif file_transfer.props.state == filetransfer.FT_STATE_CANCELLED: logging.debug('__notify_state_cb CANCELLED') - object_id = self._jobject.object_id + object_id = self._ds_object.object_id if object_id is not None: self._ds_object.destroy() datastore.delete(object_id) @@ -536,17 +419,19 @@ class IncomingTransferButton(BaseTransferButton): progress = file_transfer.props.transferred_bytes / \ file_transfer.file_size self._ds_object.metadata['progress'] = str(progress * 100) - datastore.write(self._ds_object.object_id, update_mtime=False) + datastore.write(self._ds_object, update_mtime=False) def __reply_handler_cb(self): - logging.debug('__reply_handler_cb %r', self._object_id) + logging.debug('__reply_handler_cb %r', self._ds_object.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._ds_object.object_id, + error) def __dismiss_clicked_cb(self, palette): self.remove() + class OutgoingTransferButton(BaseTransferButton): """UI element representing an ongoing outgoing file transfer """ @@ -564,7 +449,7 @@ class OutgoingTransferButton(BaseTransferButton): break client = gconf.client_get_default() - icon_color = XoColor(client.get_string("/desktop/sugar/user/color")) + icon_color = XoColor(client.get_string('/desktop/sugar/user/color')) self.props.icon_widget.props.xo_color = icon_color self.notif_icon.props.xo_color = icon_color @@ -582,14 +467,14 @@ class OutgoingTransferButton(BaseTransferButton): def __dismiss_clicked_cb(self, palette): self.remove() + class BaseTransferPalette(Palette): """Base palette class for frame or notification icon for file transfers """ - __gtype_name__ = "SugarBaseTransferPalette" + __gtype_name__ = 'SugarBaseTransferPalette' __gsignals__ = { - 'dismiss-clicked': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([])), + 'dismiss-clicked': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), } def __init__(self, file_transfer): @@ -644,10 +529,12 @@ class BaseTransferPalette(Palette): total = self._format_size(self.file_transfer.file_size) self.progress_label.props.label = _('%s of %s') % (transferred, total) + class IncomingTransferPalette(BaseTransferPalette): """Palette for frame or notification icon for incoming file transfers """ - __gtype_name__ = "SugarIncomingTransferPalette" + __gtype_name__ = 'SugarIncomingTransferPalette' + def __init__(self, file_transfer): BaseTransferPalette.__init__(self, file_transfer) @@ -770,10 +657,11 @@ class IncomingTransferPalette(BaseTransferPalette): def __dismiss_activate_cb(self, menu_item): self.emit('dismiss-clicked') + class OutgoingTransferPalette(BaseTransferPalette): """Palette for frame or notification icon for outgoing file transfers """ - __gtype_name__ = "SugarOutgoingTransferPalette" + __gtype_name__ = 'SugarOutgoingTransferPalette' def __init__(self, file_transfer): BaseTransferPalette.__init__(self, file_transfer) diff --git a/src/jarabe/frame/clipboard.py b/src/jarabe/frame/clipboard.py index 3b9f745..be2b902 100644 --- a/src/jarabe/frame/clipboard.py +++ b/src/jarabe/frame/clipboard.py @@ -26,6 +26,10 @@ from sugar import mime from jarabe.frame.clipboardobject import ClipboardObject, Format + +_instance = None + + class Clipboard(gobject.GObject): __gsignals__ = { @@ -34,7 +38,7 @@ class Clipboard(gobject.GObject): 'object-deleted': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([int])), 'object-state-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([object])) + ([object])), } def __init__(self): @@ -69,7 +73,7 @@ class Clipboard(gobject.GObject): + ' with path at ' + new_uri) else: cb_object.add_format(Format(format_type, data, on_disk)) - logging.debug('Added in-memory format of type ' + format_type + '.') + logging.debug('Added in-memory format of type %s.', format_type) self.emit('object-state-changed', cb_object) @@ -82,9 +86,9 @@ class Clipboard(gobject.GObject): def set_object_percent(self, object_id, percent): cb_object = self._objects[object_id] if percent < 0 or percent > 100: - raise ValueError("invalid percentage") + raise ValueError('invalid percentage') if cb_object.get_percent() > percent: - raise ValueError("invalid percentage; less than current percent") + raise ValueError('invalid percentage; less than current percent') if cb_object.get_percent() == percent: # ignore setting same percentage return @@ -126,21 +130,21 @@ class Clipboard(gobject.GObject): def _copy_file(self, original_uri): uri = urlparse.urlparse(original_uri) - path_, file_name = os.path.split(uri.path) + path = uri.path # pylint: disable=E1101 + directory_, file_name = os.path.split(path) root, ext = os.path.splitext(file_name) if not ext or ext == '.': - mime_type = mime.get_for_file(uri.path) + mime_type = mime.get_for_file(path) ext = '.' + mime.get_primary_extension(mime_type) f_, new_file_path = tempfile.mkstemp(ext, root) del f_ - shutil.copyfile(uri.path, new_file_path) + shutil.copyfile(path, new_file_path) os.chmod(new_file_path, 0644) return 'file://' + new_file_path -_instance = None def get_instance(): global _instance diff --git a/src/jarabe/frame/clipboardicon.py b/src/jarabe/frame/clipboardicon.py index 279db08..aa72d8a 100644 --- a/src/jarabe/frame/clipboardicon.py +++ b/src/jarabe/frame/clipboardicon.py @@ -31,6 +31,7 @@ from jarabe.frame.frameinvoker import FrameWidgetInvoker from jarabe.frame.notification import NotificationIcon import jarabe.frame + class ClipboardIcon(RadioToolButton): __gtype_name__ = 'SugarClipboardIcon' @@ -71,7 +72,8 @@ class ClipboardIcon(RadioToolButton): def _drag_data_get_cb(self, widget, context, selection, target_type, event_time): - logging.debug('_drag_data_get_cb: requested target ' + selection.target) + logging.debug('_drag_data_get_cb: requested target %s', + selection.target) data = self._cb_object.get_formats()[selection.target].get_data() selection.set(selection.target, 8, data) @@ -79,8 +81,8 @@ class ClipboardIcon(RadioToolButton): logging.debug('ClipboardIcon._put_in_clipboard') if self._cb_object.get_percent() < 100: - raise ValueError('Object is not complete,' \ - ' cannot be put into the clipboard.') + raise ValueError('Object is not complete, cannot be put into the' + ' clipboard.') targets = self._get_targets() if targets: diff --git a/src/jarabe/frame/clipboardmenu.py b/src/jarabe/frame/clipboardmenu.py index b998110..d11538d 100644 --- a/src/jarabe/frame/clipboardmenu.py +++ b/src/jarabe/frame/clipboardmenu.py @@ -35,6 +35,7 @@ from jarabe.frame import clipboard from jarabe.journal import misc from jarabe.model import bundleregistry + class ClipboardMenu(Palette): def __init__(self, cb_object): @@ -212,7 +213,8 @@ class ClipboardMenu(Palette): if most_significant_mime_type == 'text/uri-list': uris = mime.split_uri_list(format_.get_data()) if len(uris) == 1 and uris[0].startswith('file://'): - file_path = urlparse.urlparse(uris[0]).path + parsed_url = urlparse.urlparse(uris[0]) + file_path = parsed_url.path # pylint: disable=E1101 transfer_ownership = False mime_type = mime.get_for_file(file_path) else: @@ -221,7 +223,8 @@ class ClipboardMenu(Palette): mime_type = 'text/uri-list' else: if format_.is_on_disk(): - file_path = urlparse.urlparse(format_.get_data()).path + parsed_url = urlparse.urlparse(format_.get_data()) + file_path = parsed_url.path # pylint: disable=E1101 transfer_ownership = False mime_type = mime.get_for_file(file_path) else: diff --git a/src/jarabe/frame/clipboardobject.py b/src/jarabe/frame/clipboardobject.py index e9403f9..407af2f 100644 --- a/src/jarabe/frame/clipboardobject.py +++ b/src/jarabe/frame/clipboardobject.py @@ -24,6 +24,7 @@ from gettext import gettext as _ from sugar import mime from sugar.bundle.activitybundle import ActivityBundle + class ClipboardObject(object): def __init__(self, object_path, name): @@ -105,15 +106,18 @@ class ClipboardObject(object): 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) + scheme = uri.scheme # pylint: disable=E1101 + if scheme == 'file': + path = uri.path # pylint: disable=E1101 + if os.path.exists(path): + format_ = mime.get_for_file(path) else: - format_ = mime.get_from_file_name(uri.path) + format_ = mime.get_from_file_name(path) logging.debug('Chose %r!', format_) return format_ + class Format(object): def __init__(self, mime_type, data, on_disk): @@ -126,8 +130,9 @@ class Format(object): def destroy(self): if self._on_disk: uri = urlparse.urlparse(self._data) - if os.path.isfile(uri.path): - os.remove(uri.path) + path = uri.path # pylint: disable=E1101 + if os.path.isfile(path): + os.remove(path) def get_type(self): return self._type diff --git a/src/jarabe/frame/clipboardpanelwindow.py b/src/jarabe/frame/clipboardpanelwindow.py index ac324f4..f5d537c 100644 --- a/src/jarabe/frame/clipboardpanelwindow.py +++ b/src/jarabe/frame/clipboardpanelwindow.py @@ -25,6 +25,7 @@ from jarabe.frame.clipboardtray import ClipboardTray from jarabe.frame import clipboard + class ClipboardPanelWindow(FrameWindow): def __init__(self, frame, orientation): FrameWindow.__init__(self, orientation) @@ -35,7 +36,7 @@ class ClipboardPanelWindow(FrameWindow): # NOTE: we need to keep a reference to gtk.Clipboard in order to keep # listening to it. self._clipboard = gtk.Clipboard() - self._clipboard.connect("owner-change", self._owner_change_cb) + self._clipboard.connect('owner-change', self._owner_change_cb) self._clipboard_tray = ClipboardTray() canvas_widget = hippo.CanvasWidget(widget=self._clipboard_tray) @@ -43,14 +44,14 @@ class ClipboardPanelWindow(FrameWindow): # Receiving dnd drops self.drag_dest_set(0, [], 0) - self.connect("drag_motion", self._clipboard_tray.drag_motion_cb) - self.connect("drag_leave", self._clipboard_tray.drag_leave_cb) - self.connect("drag_drop", self._clipboard_tray.drag_drop_cb) - self.connect("drag_data_received", + self.connect('drag_motion', self._clipboard_tray.drag_motion_cb) + self.connect('drag_leave', self._clipboard_tray.drag_leave_cb) + self.connect('drag_drop', self._clipboard_tray.drag_drop_cb) + self.connect('drag_data_received', self._clipboard_tray.drag_data_received_cb) def _owner_change_cb(self, x_clipboard, event): - logging.debug("owner_change_cb") + logging.debug('owner_change_cb') if self._clipboard_tray.owns_clipboard(): return @@ -100,4 +101,3 @@ class ClipboardPanelWindow(FrameWindow): selection.type, selection.data, on_disk=False) - diff --git a/src/jarabe/frame/clipboardtray.py b/src/jarabe/frame/clipboardtray.py index 8beb6a8..f49b799 100644 --- a/src/jarabe/frame/clipboardtray.py +++ b/src/jarabe/frame/clipboardtray.py @@ -27,6 +27,7 @@ from sugar.graphics import style from jarabe.frame import clipboard from jarabe.frame.clipboardicon import ClipboardIcon + class _ContextMap(object): """Maps a drag context to the clipboard object involved in the dragging.""" def __init__(self): @@ -40,8 +41,8 @@ class _ContextMap(object): def get_object_id(self, context): """Retrieves the object_id associated with context. - Will release the association when this function was called as many times - as the number of data_types that this clipboard object contains. + Will release the association when this function was called as many + times as the number of data_types that this clipboard object contains. """ [object_id, data_types_left] = self._context_map[context] @@ -56,6 +57,7 @@ class _ContextMap(object): def has_context(self, context): return context in self._context_map + class ClipboardTray(tray.VTray): MAX_ITEMS = gtk.gdk.screen_height() / style.GRID_CELL_SIZE - 2 @@ -154,7 +156,7 @@ class ClipboardTray(tray.VTray): if 'XdndDirectSave0' in context.targets: window = context.source_window prop_type, format_, filename = \ - window.property_get('XdndDirectSave0','text/plain') + window.property_get('XdndDirectSave0', 'text/plain') # FIXME query the clipboard service for a filename? base_dir = tempfile.gettempdir() @@ -192,12 +194,13 @@ class ClipboardTray(tray.VTray): if selection.data == 'S': window = context.source_window - prop_type, format_, dest = \ - window.property_get('XdndDirectSave0', 'text/plain') + prop_type, format_, dest = window.property_get( + 'XdndDirectSave0', 'text/plain') clipboardservice = clipboard.get_instance() - clipboardservice.add_object_format( \ - object_id, 'XdndDirectSave0', dest, on_disk=True) + clipboardservice.add_object_format(object_id, + 'XdndDirectSave0', + dest, on_disk=True) else: self._add_selection(object_id, selection) @@ -213,4 +216,3 @@ class ClipboardTray(tray.VTray): return True else: return False - diff --git a/src/jarabe/frame/devicestray.py b/src/jarabe/frame/devicestray.py index 72affe3..c5db639 100644 --- a/src/jarabe/frame/devicestray.py +++ b/src/jarabe/frame/devicestray.py @@ -15,14 +15,13 @@ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA import os -import sys -import traceback import logging from sugar.graphics import tray from jarabe import config + class DevicesTray(tray.HTray): def __init__(self): tray.HTray.__init__(self, align=tray.ALIGN_TO_END) @@ -35,14 +34,14 @@ class DevicesTray(tray.HTray): locals(), [module_name]) mod.setup(self) except Exception: - logging.error('Exception while loading extension:\n' + \ - ''.join(traceback.format_exception(*sys.exc_info()))) + logging.exception('Exception while loading extension:') def add_device(self, view): index = 0 - relative_index = getattr(view, "FRAME_POSITION_RELATIVE", -1) + relative_index = getattr(view, 'FRAME_POSITION_RELATIVE', -1) for item in self.get_children(): - current_relative_index = getattr(item, "FRAME_POSITION_RELATIVE", 0) + current_relative_index = getattr(item, 'FRAME_POSITION_RELATIVE', + 0) if current_relative_index >= relative_index: index += 1 else: diff --git a/src/jarabe/frame/eventarea.py b/src/jarabe/frame/eventarea.py index 166aaf5..1b5bf86 100644 --- a/src/jarabe/frame/eventarea.py +++ b/src/jarabe/frame/eventarea.py @@ -19,14 +19,14 @@ import gobject import wnck import gconf + _MAX_DELAY = 1000 + class EventArea(gobject.GObject): __gsignals__ = { - 'enter': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([])), - 'leave': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([])) + 'enter': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), + 'leave': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), } def __init__(self): @@ -37,10 +37,11 @@ class EventArea(gobject.GObject): self._sids = {} client = gconf.client_get_default() self._edge_delay = client.get_int('/desktop/sugar/frame/edge_delay') - self._corner_delay = client.get_int('/desktop/sugar/frame/corner_delay') + self._corner_delay = client.get_int('/desktop/sugar/frame' + '/corner_delay') right = gtk.gdk.screen_width() - 1 - bottom = gtk.gdk.screen_height() -1 + bottom = gtk.gdk.screen_height() - 1 width = gtk.gdk.screen_width() - 2 height = gtk.gdk.screen_height() - 2 @@ -94,6 +95,7 @@ class EventArea(gobject.GObject): invisible.connect('drag_leave', self._drag_leave_cb) invisible.realize() + # pylint: disable=E1101 invisible.window.set_events(gtk.gdk.POINTER_MOTION_MASK | gtk.gdk.ENTER_NOTIFY_MASK | gtk.gdk.LEAVE_NOTIFY_MASK) diff --git a/src/jarabe/frame/frame.py b/src/jarabe/frame/frame.py index 55f866f..079eeeb 100644 --- a/src/jarabe/frame/frame.py +++ b/src/jarabe/frame/frame.py @@ -35,6 +35,7 @@ from jarabe.frame.clipboardpanelwindow import ClipboardPanelWindow from jarabe.frame.notification import NotificationIcon, NotificationWindow from jarabe.model import notifications + TOP_RIGHT = 0 TOP_LEFT = 1 BOTTOM_RIGHT = 2 @@ -43,6 +44,7 @@ BOTTOM_LEFT = 3 _FRAME_HIDING_DELAY = 500 _NOTIFICATION_DURATION = 5000 + class _Animation(animator.Animation): def __init__(self, frame, end): start = frame.current_position @@ -52,6 +54,7 @@ class _Animation(animator.Animation): def next_frame(self, current): self._frame.move(current) + class _MouseListener(object): def __init__(self, frame): self._frame = frame @@ -79,6 +82,7 @@ class _MouseListener(object): self._hide_sid = gobject.timeout_add( _FRAME_HIDING_DELAY, self._hide_frame_timeout_cb) + class _KeyListener(object): def __init__(self, frame): self._frame = frame @@ -90,13 +94,14 @@ class _KeyListener(object): else: self._frame.show(Frame.MODE_KEYBOARD) + class Frame(object): - MODE_MOUSE = 0 + MODE_MOUSE = 0 MODE_KEYBOARD = 1 MODE_NON_INTERACTIVE = 2 def __init__(self): - logging.debug("STARTUP: Loading the frame") + logging.debug('STARTUP: Loading the frame') self.mode = None self._palette_group = palettegroup.get_group('frame') @@ -173,12 +178,12 @@ class Frame(object): def _create_top_panel(self): panel = self._create_panel(gtk.POS_TOP) - # TODO: setting box_width and hippo.PACK_EXPAND looks like a hack to me. - # Why hippo isn't respecting the request size of these controls? + # TODO: setting box_width and hippo.PACK_EXPAND looks like a hack to + # me. Why hippo isn't respecting the request size of these controls? zoom_toolbar = ZoomToolbar() panel.append(hippo.CanvasWidget(widget=zoom_toolbar, - box_width=4*style.GRID_CELL_SIZE)) + box_width=4 * style.GRID_CELL_SIZE)) zoom_toolbar.show() activities_tray = ActivitiesTray() @@ -193,7 +198,8 @@ class Frame(object): # TODO: same issue as in _create_top_panel() devices_tray = DevicesTray() - panel.append(hippo.CanvasWidget(widget=devices_tray), hippo.PACK_EXPAND) + panel.append(hippo.CanvasWidget(widget=devices_tray), + hippo.PACK_EXPAND) devices_tray.show() return panel @@ -322,7 +328,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') icon = NotificationIcon() hints = kwargs['hints'] @@ -348,4 +354,3 @@ class Frame(object): # Do nothing for now. Our notification UI is so simple, there's no # point yet. pass - diff --git a/src/jarabe/frame/frameinvoker.py b/src/jarabe/frame/frameinvoker.py index e4a13e1..a4abfa8 100644 --- a/src/jarabe/frame/frameinvoker.py +++ b/src/jarabe/frame/frameinvoker.py @@ -19,6 +19,7 @@ import gtk from sugar.graphics import style from sugar.graphics.palette import WidgetInvoker + def _get_screen_area(): frame_thickness = style.GRID_CELL_SIZE @@ -28,6 +29,7 @@ def _get_screen_area(): return gtk.gdk.Rectangle(x, y, width, height) + class FrameWidgetInvoker(WidgetInvoker): def __init__(self, widget): WidgetInvoker.__init__(self, widget, widget.child) diff --git a/src/jarabe/frame/framewindow.py b/src/jarabe/frame/framewindow.py index a7d8fe7..c77e76c 100644 --- a/src/jarabe/frame/framewindow.py +++ b/src/jarabe/frame/framewindow.py @@ -19,6 +19,7 @@ import hippo from sugar.graphics import style + class FrameWindow(gtk.Window): __gtype_name__ = 'SugarFrameWindow' diff --git a/src/jarabe/frame/friendstray.py b/src/jarabe/frame/friendstray.py index b5437e5..31a9809 100644 --- a/src/jarabe/frame/friendstray.py +++ b/src/jarabe/frame/friendstray.py @@ -14,13 +14,16 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -from sugar.presence import presenceservice +import logging + from sugar.graphics.tray import VTray, TrayIcon from jarabe.view.buddymenu import BuddyMenu from jarabe.frame.frameinvoker import FrameWidgetInvoker from jarabe.model import shell -from jarabe.model.buddy import BuddyModel +from jarabe.model.buddy import get_owner_instance +from jarabe.model import neighborhood + class FriendIcon(TrayIcon): def __init__(self, buddy): @@ -32,46 +35,32 @@ class FriendIcon(TrayIcon): self.palette.props.icon_visible = False self.palette.set_group_id('frame') + class FriendsTray(VTray): def __init__(self): VTray.__init__(self) - self._activity_ps = None - self._joined_hid = -1 - self._left_hid = -1 + self._shared_activity = None self._buddies = {} - self._pservice = presenceservice.get_instance() - self._pservice.connect('activity-appeared', - self.__activity_appeared_cb) - - self._owner = self._pservice.get_owner() - - # Add initial activities the PS knows about - self._pservice.get_activities_async( \ - reply_handler=self._get_activities_cb) - shell.get_model().connect('active-activity-changed', - self._active_activity_changed_cb) + self.__active_activity_changed_cb) - def _get_activities_cb(self, activities_list): - for act in activities_list: - self.__activity_appeared_cb(self._pservice, act) + neighborhood.get_model().connect('activity-added', + self.__neighborhood_activity_added_cb) def add_buddy(self, buddy): - if self._buddies.has_key(buddy.props.key): + if buddy.props.key in self._buddies: return - model = BuddyModel(buddy=buddy) - - icon = FriendIcon(model) + icon = FriendIcon(buddy) self.add_item(icon) icon.show() self._buddies[buddy.props.key] = icon def remove_buddy(self, buddy): - if not self._buddies.has_key(buddy.props.key): + if buddy.props.key not in self._buddies: return self.remove_item(self._buddies[buddy.props.key]) @@ -83,39 +72,23 @@ class FriendsTray(VTray): item.destroy() self._buddies = {} - def __activity_appeared_cb(self, pservice, activity_ps): - activity = shell.get_model().get_active_activity() - if activity and activity_ps.props.id == activity.get_activity_id(): - self._set_activity_ps(activity_ps, True) - - def _set_activity_ps(self, activity_ps, shared_activity): - if self._activity_ps == activity_ps: - return + def __neighborhood_activity_added_cb(self, neighborhood_model, + shared_activity): + logging.debug('FriendsTray.__neighborhood_activity_added_cb') + self.clear() - if self._joined_hid > 0: - self._activity_ps.disconnect(self._joined_hid) - self._joined_hid = -1 - if self._left_hid > 0: - self._activity_ps.disconnect(self._left_hid) - self._left_hid = -1 + # always display ourselves + self.add_buddy(get_owner_instance()) - self._activity_ps = activity_ps + self._set_current_activity(shared_activity.activity_id) + def __active_activity_changed_cb(self, home_model, home_activity): + logging.debug('FriendsTray.__active_activity_changed_cb') self.clear() # always display ourselves - self.add_buddy(self._owner) - - if shared_activity is True: - for buddy in activity_ps.get_joined_buddies(): - self.add_buddy(buddy) + self.add_buddy(get_owner_instance()) - self._joined_hid = activity_ps.connect( - 'buddy-joined', self.__buddy_joined_cb) - self._left_hid = activity_ps.connect( - 'buddy-left', self.__buddy_left_cb) - - def _active_activity_changed_cb(self, home_model, home_activity): if home_activity is None: return @@ -123,19 +96,25 @@ class FriendsTray(VTray): if activity_id is None: return - # check if activity is shared - activity = None - for act in self._pservice.get_activities(): - if activity_id == act.props.id: - activity = act - break - if activity: - self._set_activity_ps(activity, True) - else: - self._set_activity_ps(home_activity, False) - - def __buddy_joined_cb(self, activity, buddy): + self._set_current_activity(activity_id) + + def _set_current_activity(self, activity_id): + logging.debug('FriendsTray._set_current_activity') + neighborhood_model = neighborhood.get_model() + self._shared_activity = neighborhood_model.get_activity(activity_id) + if self._shared_activity is None: + return + + for buddy in self._shared_activity.get_buddies(): + self.add_buddy(buddy) + + self._shared_activity.connect('buddy-added', self.__buddy_added_cb) + self._shared_activity.connect('buddy-removed', self.__buddy_removed_cb) + + def __buddy_added_cb(self, activity, buddy): + logging.debug('FriendsTray.__buddy_added_cb') self.add_buddy(buddy) - def __buddy_left_cb(self, activity, buddy): + def __buddy_removed_cb(self, activity, buddy): + logging.debug('FriendsTray.__buddy_removed_cb') self.remove_buddy(buddy) diff --git a/src/jarabe/frame/notification.py b/src/jarabe/frame/notification.py index 83dc27e..3471e2c 100644 --- a/src/jarabe/frame/notification.py +++ b/src/jarabe/frame/notification.py @@ -22,13 +22,14 @@ from sugar.graphics.xocolor import XoColor from jarabe.view.pulsingicon import PulsingIcon + class NotificationIcon(gtk.EventBox): __gtype_name__ = 'SugarNotificationIcon' __gproperties__ = { - 'xo-color' : (object, None, None, gobject.PARAM_READWRITE), - 'icon-name' : (str, None, None, None, gobject.PARAM_READWRITE), - 'icon-filename' : (str, None, None, None, gobject.PARAM_READWRITE) + 'xo-color': (object, None, None, gobject.PARAM_READWRITE), + 'icon-name': (str, None, None, None, gobject.PARAM_READWRITE), + 'icon-filename': (str, None, None, None, gobject.PARAM_READWRITE), } _PULSE_TIMEOUT = 3 @@ -45,7 +46,8 @@ class NotificationIcon(gtk.EventBox): self.add(self._icon) self._icon.show() - gobject.timeout_add_seconds(self._PULSE_TIMEOUT, self.__stop_pulsing_cb) + gobject.timeout_add_seconds(self._PULSE_TIMEOUT, + self.__stop_pulsing_cb) self.set_size_request(style.GRID_CELL_SIZE, style.GRID_CELL_SIZE) @@ -80,6 +82,7 @@ class NotificationIcon(gtk.EventBox): palette = property(_get_palette, _set_palette) + class NotificationWindow(gtk.Window): __gtype_name__ = 'SugarNotificationWindow' @@ -97,4 +100,3 @@ class NotificationWindow(gtk.Window): color = gtk.gdk.color_parse(style.COLOR_TOOLBAR_GREY.get_html()) self.modify_bg(gtk.STATE_NORMAL, color) - diff --git a/src/jarabe/frame/zoomtoolbar.py b/src/jarabe/frame/zoomtoolbar.py index 2ed3c54..6c10c61 100644 --- a/src/jarabe/frame/zoomtoolbar.py +++ b/src/jarabe/frame/zoomtoolbar.py @@ -26,6 +26,7 @@ from sugar.graphics.radiotoolbutton import RadioToolButton from jarabe.frame.frameinvoker import FrameWidgetInvoker from jarabe.model import shell + class ZoomToolbar(gtk.Toolbar): def __init__(self): gtk.Toolbar.__init__(self) @@ -86,4 +87,3 @@ class ZoomToolbar(gtk.Toolbar): self._activity_button.props.active = True else: raise ValueError('Invalid zoom level: %r' % (new_level)) - diff --git a/src/jarabe/intro/Makefile.am b/src/jarabe/intro/Makefile.am index a9fb96b..2ea7cea 100644 --- a/src/jarabe/intro/Makefile.am +++ b/src/jarabe/intro/Makefile.am @@ -1,7 +1,3 @@ -imagedir = $(pythondir)/jarabe/intro -image_DATA = default-picture.png - -EXTRA_DIST = $(conf_DATA) $(image_DATA) sugardir = $(pythondir)/jarabe/intro sugar_PYTHON = \ __init__.py \ diff --git a/src/jarabe/intro/__init__.py b/src/jarabe/intro/__init__.py index ca4f64d..d2932f1 100644 --- a/src/jarabe/intro/__init__.py +++ b/src/jarabe/intro/__init__.py @@ -8,6 +8,7 @@ from sugar.profile import get_profile from jarabe.intro.window import IntroWindow from jarabe.intro.window import create_profile + def check_profile(): profile = get_profile() diff --git a/src/jarabe/intro/colorpicker.py b/src/jarabe/intro/colorpicker.py index a939857..997199b 100644 --- a/src/jarabe/intro/colorpicker.py +++ b/src/jarabe/intro/colorpicker.py @@ -20,6 +20,7 @@ from sugar.graphics.icon import CanvasIcon from sugar.graphics import style from sugar.graphics.xocolor import XoColor + class ColorPicker(hippo.CanvasBox, hippo.CanvasItem): def __init__(self, **kwargs): hippo.CanvasBox.__init__(self, **kwargs) diff --git a/src/jarabe/intro/default-picture.png b/src/jarabe/intro/default-picture.png Binary files differdeleted file mode 100644 index e26b9b0..0000000 --- a/src/jarabe/intro/default-picture.png +++ /dev/null diff --git a/src/jarabe/intro/window.py b/src/jarabe/intro/window.py index 35c0cda..df19fbf 100644 --- a/src/jarabe/intro/window.py +++ b/src/jarabe/intro/window.py @@ -32,38 +32,34 @@ from sugar.graphics.xocolor import XoColor from jarabe.intro import colorpicker + _BACKGROUND_COLOR = style.COLOR_WHITE -def create_profile(name, color=None, pixbuf=None): - if not pixbuf: - path = os.path.join(os.path.dirname(__file__), 'default-picture.png') - pixbuf = gtk.gdk.pixbuf_new_from_file(path) +def create_profile(name, color=None): if not color: color = XoColor() - icon_path = os.path.join(env.get_profile_path(), "buddy-icon.jpg") - pixbuf.save(icon_path, "jpeg", {"quality":"85"}) - client = gconf.client_get_default() - client.set_string("/desktop/sugar/user/nick", name) - client.set_string("/desktop/sugar/user/color", color.to_string()) + client.set_string('/desktop/sugar/user/nick', name) + client.set_string('/desktop/sugar/user/color', color.to_string()) + client.suggest_sync() # Generate keypair import commands - keypath = os.path.join(env.get_profile_path(), "owner.key") + keypath = os.path.join(env.get_profile_path(), 'owner.key') if not os.path.isfile(keypath): 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.") + logging.error('Keypair exists, skip generation.') + class _Page(hippo.CanvasBox): __gproperties__ = { - 'valid' : (bool, None, None, False, - gobject.PARAM_READABLE) + 'valid': (bool, None, None, False, gobject.PARAM_READABLE), } def __init__(self, **kwargs): @@ -81,6 +77,7 @@ class _Page(hippo.CanvasBox): def activate(self): pass + class _NamePage(_Page): def __init__(self, intro): _Page.__init__(self, xalign=hippo.ALIGNMENT_CENTER, @@ -90,7 +87,7 @@ class _NamePage(_Page): self._intro = intro - label = hippo.CanvasText(text=_("Name:")) + label = hippo.CanvasText(text=_('Name:')) self.append(label) self._entry = CanvasEntry(box_width=style.zoom(300)) @@ -118,6 +115,7 @@ class _NamePage(_Page): def activate(self): self._entry.props.widget.grab_focus() + class _ColorPage(_Page): def __init__(self, **kwargs): _Page.__init__(self, xalign=hippo.ALIGNMENT_CENTER, @@ -125,7 +123,7 @@ class _ColorPage(_Page): spacing=style.DEFAULT_SPACING, yalign=hippo.ALIGNMENT_CENTER, **kwargs) - self._label = hippo.CanvasText(text=_("Click to change color:"), + self._label = hippo.CanvasText(text=_('Click to change color:'), xalign=hippo.ALIGNMENT_CENTER) self.append(self._label) @@ -138,10 +136,11 @@ class _ColorPage(_Page): def get_color(self): return self._cp.get_color() + class _IntroBox(hippo.CanvasBox): __gsignals__ = { 'done': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])) + ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])), } PAGE_NAME = 0 @@ -166,13 +165,9 @@ class _IntroBox(hippo.CanvasBox): self._page = self.PAGE_COLOR if default_nick == 'system': pwd_entry = pwd.getpwuid(os.getuid()) - if pwd_entry.pw_gecos: - nick = pwd_entry.pw_gecos.split(',')[0] - self._name_page.set_name(nick) - else: - self._name_page.set_name(pwd_entry.pw_name) - else: - self._name_page.set_name(default_nick) + default_nick = (pwd_entry.pw_gecos.split(',')[0] or + pwd_entry.pw_name) + self._name_page.set_name(default_nick) self._setup_page() @@ -255,6 +250,7 @@ class _IntroBox(hippo.CanvasBox): self.emit('done', name, color) + class IntroWindow(gtk.Window): def __init__(self): gtk.Window.__init__(self) @@ -282,16 +278,16 @@ class IntroWindow(gtk.Window): return False def __key_press_cb(self, widget, event): - if gtk.gdk.keyval_name(event.keyval) == "Return": + if gtk.gdk.keyval_name(event.keyval) == 'Return': self._intro_box.next() return True - elif gtk.gdk.keyval_name(event.keyval) == "Escape": + elif gtk.gdk.keyval_name(event.keyval) == 'Escape': self._intro_box.back() return True return False -if __name__ == "__main__": +if __name__ == '__main__': w = IntroWindow() w.show() w.connect('destroy', gtk.main_quit) diff --git a/src/jarabe/journal/Makefile.am b/src/jarabe/journal/Makefile.am index f4bf273..ba29062 100644 --- a/src/jarabe/journal/Makefile.am +++ b/src/jarabe/journal/Makefile.am @@ -6,6 +6,7 @@ sugar_PYTHON = \ journalactivity.py \ journalentrybundle.py \ journaltoolbox.py \ + journalwindow.py \ keepicon.py \ listmodel.py \ listview.py \ diff --git a/src/jarabe/journal/detailview.py b/src/jarabe/journal/detailview.py index b4a2339..aa8c039 100644 --- a/src/jarabe/journal/detailview.py +++ b/src/jarabe/journal/detailview.py @@ -27,11 +27,12 @@ from sugar.graphics.icon import CanvasIcon from jarabe.journal.expandedentry import ExpandedEntry from jarabe.journal import model + class DetailView(gtk.VBox): __gtype_name__ = 'DetailView' __gsignals__ = { - 'go-back-clicked': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])) + 'go-back-clicked': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), } def __init__(self, **kwargs): @@ -84,6 +85,7 @@ class DetailView(gtk.VBox): metadata = gobject.property( type=object, getter=get_metadata, setter=set_metadata) + class BackBar(hippo.CanvasBox): def __init__(self): hippo.CanvasBox.__init__(self, diff --git a/src/jarabe/journal/expandedentry.py b/src/jarabe/journal/expandedentry.py index c8e40c1..fe2f320 100644 --- a/src/jarabe/journal/expandedentry.py +++ b/src/jarabe/journal/expandedentry.py @@ -36,6 +36,7 @@ from jarabe.journal.palettes import ObjectPalette, BuddyPalette from jarabe.journal import misc from jarabe.journal import model + class Separator(hippo.CanvasBox, hippo.CanvasItem): def __init__(self, orientation): hippo.CanvasBox.__init__(self, @@ -46,6 +47,7 @@ class Separator(hippo.CanvasBox, hippo.CanvasItem): else: self.props.box_height = style.LINE_WIDTH + class BuddyList(hippo.CanvasBox): def __init__(self, buddies): hippo.CanvasBox.__init__(self, xalign=hippo.ALIGNMENT_START, @@ -61,6 +63,7 @@ class BuddyList(hippo.CanvasBox): hbox.append(icon) self.append(hbox) + class ExpandedEntry(hippo.CanvasBox): def __init__(self): hippo.CanvasBox.__init__(self) @@ -209,9 +212,7 @@ class ExpandedEntry(hippo.CanvasBox): height = style.zoom(240) box = hippo.CanvasBox() - if self._metadata.has_key('preview') and \ - len(self._metadata['preview']) > 4: - + if len(self._metadata.get('preview', '')) > 4: if self._metadata['preview'][1:4] == 'PNG': preview_data = self._metadata['preview'] else: @@ -260,8 +261,9 @@ class ExpandedEntry(hippo.CanvasBox): 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'])),)] + _('Size: %s') % (format_size(int(self._metadata.get('filesize', + model.get_file_size(self._metadata['uid']))))), + ] for line in lines: text = hippo.CanvasText(text=line, @@ -279,10 +281,15 @@ class ExpandedEntry(hippo.CanvasBox): 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') + try: + timestamp = float(self._metadata['timestamp']) + except (ValueError, TypeError): + logging.warning('Invalid timestamp for %r: %r', + self._metadata['uid'], + self._metadata['timestamp']) + else: + return time.strftime('%x', time.localtime(timestamp)) + return _('No date') def _create_buddy_list(self): @@ -300,8 +307,7 @@ class ExpandedEntry(hippo.CanvasBox): vbox.append(text) - if self._metadata.has_key('buddies') and \ - self._metadata['buddies']: + if self._metadata.get('buddies'): buddies = simplejson.loads(self._metadata['buddies']).values() vbox.append(BuddyList(buddies)) return vbox diff --git a/src/jarabe/journal/journalactivity.py b/src/jarabe/journal/journalactivity.py index 0559560..a33038a 100644 --- a/src/jarabe/journal/journalactivity.py +++ b/src/jarabe/journal/journalactivity.py @@ -17,8 +17,6 @@ import logging from gettext import gettext as _ -import sys -import traceback import uuid import gtk @@ -27,6 +25,8 @@ import statvfs import os from sugar.graphics.window import Window +from sugar.graphics.alert import ErrorAlert + from sugar.bundle.bundle import ZipExtractException, RegistrationException from sugar import env from sugar.activity import activityfactory @@ -42,6 +42,8 @@ from jarabe.journal.journalentrybundle import JournalEntryBundle from jarabe.journal.objectchooser import ObjectChooser from jarabe.journal.modalalert import ModalAlert from jarabe.journal import model +from jarabe.journal.journalwindow import JournalWindow + J_DBUS_SERVICE = 'org.laptop.Journal' J_DBUS_INTERFACE = 'org.laptop.Journal' @@ -50,6 +52,9 @@ J_DBUS_PATH = '/org/laptop/Journal' _SPACE_TRESHOLD = 52428800 _BUNDLE_ID = 'org.laptop.JournalActivity' +_journal = None + + class JournalActivityDBusService(dbus.service.Object): def __init__(self, parent): self._parent = parent @@ -79,7 +84,8 @@ class JournalActivityDBusService(dbus.service.Object): chooser.destroy() del chooser - @dbus.service.method(J_DBUS_INTERFACE, in_signature='is', out_signature='s') + @dbus.service.method(J_DBUS_INTERFACE, in_signature='is', + out_signature='s') def ChooseObject(self, parent_xid, what_filter=''): chooser_id = uuid.uuid4().hex if parent_xid > 0: @@ -92,18 +98,19 @@ class JournalActivityDBusService(dbus.service.Object): return chooser_id - @dbus.service.signal(J_DBUS_INTERFACE, signature="ss") + @dbus.service.signal(J_DBUS_INTERFACE, signature='ss') def ObjectChooserResponse(self, chooser_id, object_id): pass - @dbus.service.signal(J_DBUS_INTERFACE, signature="s") + @dbus.service.signal(J_DBUS_INTERFACE, signature='s') def ObjectChooserCancelled(self, chooser_id): pass -class JournalActivity(Window): + +class JournalActivity(JournalWindow): def __init__(self): - logging.debug("STARTUP: Loading the journal") - Window.__init__(self) + logging.debug('STARTUP: Loading the journal') + JournalWindow.__init__(self) self.set_title(_('Journal')) @@ -138,6 +145,15 @@ class JournalActivity(Window): self._critical_space_alert = None self._check_available_space() + def __volume_error_cb(self, gobject, message, severity): + alert = ErrorAlert(title=severity, msg=message) + alert.connect('response', self.__alert_response_cb) + self.add_alert(alert) + alert.show() + + def __alert_response_cb(self, alert, response_id): + self.remove_alert(alert) + def __realize_cb(self, window): wm.set_bundle_id(window.window, _BUNDLE_ID) activity_id = activityfactory.create_activity_id() @@ -161,6 +177,7 @@ class JournalActivity(Window): self._volumes_toolbar = VolumesToolbar() self._volumes_toolbar.connect('volume-changed', self.__volume_changed_cb) + self._volumes_toolbar.connect('volume-error', self.__volume_error_cb) self._main_view.pack_start(self._volumes_toolbar, expand=False) search_toolbar = self._main_toolbox.search_toolbar @@ -171,7 +188,8 @@ class JournalActivity(Window): self._secondary_view = gtk.VBox() self._detail_toolbox = DetailToolbox() - entry_toolbar = self._detail_toolbox.entry_toolbar + self._detail_toolbox.entry_toolbar.connect('volume-error', + self.__volume_error_cb) self._detail_view = DetailView() self._detail_view.connect('go-back-clicked', self.__go_back_clicked_cb) @@ -210,8 +228,7 @@ class JournalActivity(Window): try: self._detail_toolbox.entry_toolbar.set_metadata(metadata) except Exception: - logging.error('Exception while displaying entry:\n' + \ - ''.join(traceback.format_exception(*sys.exc_info()))) + logging.exception('Exception while displaying entry:') self.set_toolbar_box(self._detail_toolbox) self._detail_toolbox.show() @@ -219,8 +236,7 @@ class JournalActivity(Window): try: self._detail_view.props.metadata = metadata except Exception: - logging.error('Exception while displaying entry:\n' + \ - ''.join(traceback.format_exception(*sys.exc_info()))) + logging.exception('Exception while displaying entry:') self.set_canvas(self._secondary_view) self._secondary_view.show() @@ -314,11 +330,11 @@ class JournalActivity(Window): self._list_view.set_is_visible(visible) def _check_available_space(self): - ''' Check available space on device + """Check available space on device If the available space is below 50MB an alert will be shown which encourages to delete old journal entries. - ''' + """ if self._critical_space_alert: return @@ -345,7 +361,6 @@ class JournalActivity(Window): self.show_main_view() self.search_grab_focus() -_journal = None def get_journal(): global _journal @@ -354,6 +369,6 @@ def get_journal(): _journal.show() return _journal + def start(): get_journal() - diff --git a/src/jarabe/journal/journalentrybundle.py b/src/jarabe/journal/journalentrybundle.py index 41777c7..c220c09 100644 --- a/src/jarabe/journal/journalentrybundle.py +++ b/src/jarabe/journal/journalentrybundle.py @@ -25,6 +25,7 @@ from sugar.bundle.bundle import Bundle, MalformedBundleException from jarabe.journal import model + class JournalEntryBundle(Bundle): """A Journal entry bundle @@ -41,7 +42,7 @@ class JournalEntryBundle(Bundle): Bundle.__init__(self, path) def install(self, uid=''): - if os.environ.has_key('SUGAR_ACTIVITY_ROOT'): + if 'SUGAR_ACTIVITY_ROOT' in os.environ: install_dir = os.path.join(os.environ['SUGAR_ACTIVITY_ROOT'], 'data') else: @@ -91,4 +92,3 @@ class JournalEntryBundle(Bundle): def is_installed(self): # These bundles can be reinstalled as many times as desired. return False - diff --git a/src/jarabe/journal/journaltoolbox.py b/src/jarabe/journal/journaltoolbox.py index 61671bc..cdf998d 100644 --- a/src/jarabe/journal/journaltoolbox.py +++ b/src/jarabe/journal/journaltoolbox.py @@ -43,6 +43,7 @@ from jarabe.model import bundleregistry from jarabe.journal import misc from jarabe.journal import model + _AUTOSEARCH_TIMEOUT = 1000 _ACTION_ANYTIME = 0 @@ -68,13 +69,13 @@ class MainToolbox(Toolbox): self.add_toolbar(_('Search'), self.search_toolbar) self.search_toolbar.show() + class SearchToolbar(gtk.Toolbar): __gtype_name__ = 'SearchToolbar' __gsignals__ = { - 'query-changed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([object])) + 'query-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object])), } def __init__(self): @@ -109,6 +110,14 @@ class SearchToolbar(gtk.Toolbar): self.insert(tool_item, -1) tool_item.show() + self._sorting_button = SortingButton() + self._sorting_button.connect('clicked', + self.__sorting_button_clicked_cb) + self.insert(self._sorting_button, -1) + self._sorting_button.connect('sort-property-changed', + self.__sort_changed_cb) + self._sorting_button.show() + # TODO: enable it when the DS supports saving the buddies. #self._with_search_combo = self._get_with_search_combo() #tool_item = ToolComboBox(self._with_search_combo) @@ -191,6 +200,14 @@ class SearchToolbar(gtk.Toolbar): if text: query['query'] = text + property_, order = self._sorting_button.get_current_sort() + + if order == gtk.SORT_ASCENDING: + sign = '+' + else: + sign = '-' + query['order_by'] = [sign + property_] + return query def _get_date_range(self): @@ -213,6 +230,12 @@ class SearchToolbar(gtk.Toolbar): def _combo_changed_cb(self, combo): self._update_if_needed() + def __sort_changed_cb(self, button): + self._update_if_needed() + + def __sorting_button_clicked_cb(self, button): + self._sorting_button.palette.popup(immediate=True, state=1) + def _update_if_needed(self): new_query = self._build_query() if self._query != new_query: @@ -320,6 +343,7 @@ class SearchToolbar(gtk.Toolbar): self._when_search_combo.set_active(0) self._favorite_button.props.active = False + class DetailToolbox(Toolbox): def __init__(self): Toolbox.__init__(self) @@ -328,7 +352,13 @@ class DetailToolbox(Toolbox): self.add_toolbar('', self.entry_toolbar) self.entry_toolbar.show() + class EntryToolbar(gtk.Toolbar): + __gsignals__ = { + 'volume-error': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([str, str])), + } + def __init__(self): gtk.Toolbar.__init__(self) @@ -398,7 +428,22 @@ class EntryToolbar(gtk.Toolbar): misc.resume(self._metadata, service_name) def _copy_menu_item_activate_cb(self, menu_item, mount_point): - model.copy(self._metadata, mount_point) + file_path = model.get_file(self._metadata['uid']) + + if not file_path or not os.path.exists(file_path): + logging.warn('Entries without a file cannot be copied.') + self.emit('volume-error', + _('Entries without a file cannot be copied.'), + _('Warning')) + return + + try: + model.copy(self._metadata, mount_point) + except IOError, e: + logging.exception('Error while copying the entry. %s', e.strerror) + self.emit('volume-error', + _('Error while copying the entry. %s') % e.strerror, + _('Error')) def _refresh_copy_palette(self): palette = self._copy.get_palette() @@ -456,3 +501,48 @@ class EntryToolbar(gtk.Toolbar): activity_info.get_bundle_id()) palette.menu.append(menu_item) menu_item.show() + + +class SortingButton(ToolButton): + __gtype_name__ = 'JournalSortingButton' + + __gsignals__ = { + 'sort-property-changed': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([])), + } + + _SORT_OPTIONS = [ + ('timestamp', 'view-lastedit', _('Sort by date modified')), + ('creation_time', 'view-created', _('Sort by date created')), + ('filesize', 'view-size', _('Sort by size')), + ] + + def __init__(self): + ToolButton.__init__(self) + + self._property = 'timestamp' + self._order = gtk.SORT_ASCENDING + + self.props.tooltip = _('Sort view') + self.props.icon_name = 'view-lastedit' + + for property_, icon, label in self._SORT_OPTIONS: + button = MenuItem(icon_name=icon, text_label=label) + button.connect('activate', + self.__sort_type_changed_cb, + property_, + icon) + button.show() + self.props.palette.menu.insert(button, -1) + + def __sort_type_changed_cb(self, widget, property_, icon_name): + self._property = property_ + #FIXME: Implement sorting order + self._order = gtk.SORT_ASCENDING + self.emit('sort-property-changed') + + self.props.icon_name = icon_name + + def get_current_sort(self): + return (self._property, self._order) diff --git a/src/jarabe/desktop/myicon.py b/src/jarabe/journal/journalwindow.py index 4a4ad95..31bc790 100644 --- a/src/jarabe/desktop/myicon.py +++ b/src/jarabe/journal/journalwindow.py @@ -1,4 +1,5 @@ -# Copyright (C) 2006-2007 Red Hat, Inc. +#Copyright (C) 2010 Software for Education, Entertainment and Training +#Activities # # 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 @@ -14,15 +15,19 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -import gconf +from sugar.graphics.window import Window -from sugar.graphics.icon import CanvasIcon -from sugar.graphics.xocolor import XoColor +_journal_window = None -class MyIcon(CanvasIcon): - def __init__(self, size): - client = gconf.client_get_default() - color = XoColor(client.get_string("/desktop/sugar/user/color")) - CanvasIcon.__init__(self, size=size, - icon_name='computer-xo', - xo_color=color) + +class JournalWindow(Window): + + def __init__(self): + + global _journal_window + Window.__init__(self) + _journal_window = self + + +def get_journal_window(): + return _journal_window diff --git a/src/jarabe/journal/keepicon.py b/src/jarabe/journal/keepicon.py index 2c692c6..1253afc 100644 --- a/src/jarabe/journal/keepicon.py +++ b/src/jarabe/journal/keepicon.py @@ -22,6 +22,7 @@ from sugar.graphics.icon import CanvasIcon from sugar.graphics import style from sugar.graphics.xocolor import XoColor + class KeepIcon(CanvasIcon): def __init__(self, keep): CanvasIcon.__init__(self, icon_name='emblem-favorite', diff --git a/src/jarabe/journal/listmodel.py b/src/jarabe/journal/listmodel.py index 07f8544..3902eba 100644 --- a/src/jarabe/journal/listmodel.py +++ b/src/jarabe/journal/listmodel.py @@ -19,6 +19,7 @@ import logging import simplejson import gobject import gtk +from gettext import gettext as _ from sugar.graphics.xocolor import XoColor from sugar.graphics import style @@ -27,20 +28,18 @@ 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, - ([])), + 'ready': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), + 'progress': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), } COLUMN_UID = 0 @@ -48,22 +47,28 @@ class ListModel(gtk.GenericTreeModel, gtk.TreeDragSource): 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} + COLUMN_TIMESTAMP = 5 + COLUMN_CREATION_TIME = 6 + COLUMN_FILESIZE = 7 + COLUMN_PROGRESS = 8 + COLUMN_BUDDY_1 = 9 + COLUMN_BUDDY_2 = 10 + COLUMN_BUDDY_3 = 11 + + _COLUMN_TYPES = { + COLUMN_UID: str, + COLUMN_FAVORITE: bool, + COLUMN_ICON: str, + COLUMN_ICON_COLOR: object, + COLUMN_TITLE: str, + COLUMN_TIMESTAMP: str, + COLUMN_CREATION_TIME: str, + COLUMN_FILESIZE: str, + COLUMN_PROGRESS: int, + COLUMN_BUDDY_1: object, + COLUMN_BUDDY_3: object, + COLUMN_BUDDY_2: object, + } _PAGE_SIZE = 10 @@ -135,25 +140,64 @@ class ListModel(gtk.GenericTreeModel, gtk.TreeDragSource): xo_color = misc.get_icon_color(metadata) self._cached_row.append(xo_color) - title = gobject.markup_escape_text(metadata.get('title', None)) - self._cached_row.append('<b>%s</b>' % title) + title = gobject.markup_escape_text(metadata.get('title', + _('Untitled'))) + self._cached_row.append('<b>%s</b>' % (title, )) - timestamp = int(metadata.get('timestamp', 0)) - self._cached_row.append(util.timestamp_to_elapsed_string(timestamp)) + try: + timestamp = float(metadata.get('timestamp', 0)) + except (TypeError, ValueError): + timestamp_content = _('Unknown') + else: + timestamp_content = util.timestamp_to_elapsed_string(timestamp) + self._cached_row.append(timestamp_content) - self._cached_row.append(int(metadata.get('progress', 100))) + try: + creation_time = float(metadata.get('creation_time')) + except (TypeError, ValueError): + self._cached_row.append(_('Unknown')) + else: + self._cached_row.append( + util.timestamp_to_elapsed_string(float(creation_time))) - if metadata.get('buddies', ''): - buddies = simplejson.loads(metadata['buddies']).values() + try: + size = int(metadata.get('filesize')) + except (TypeError, ValueError): + self._cached_row.append(_('Unknown')) else: + self._cached_row.append(util.format_size(size)) + + try: + progress = int(float(metadata.get('progress', 100))) + except (TypeError, ValueError): + progress = 100 + self._cached_row.append(progress) + + buddies = [] + if metadata.get('buddies'): + try: + buddies = simplejson.loads(metadata['buddies']).values() + except simplejson.decoder.JSONDecodeError, exception: + logging.warning('Cannot decode buddies for %r: %s', + metadata['uid'], exception) + + if not isinstance(buddies, list): + logging.warning('Content of buddies for %r is not a list: %r', + metadata['uid'], buddies) 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) + try: + nick, color = buddies.pop(0) + except (AttributeError, ValueError), exception: + logging.warning('Malformed buddies for %r: %s', + metadata['uid'], exception) + else: + self._cached_row.append((nick, XoColor(color))) + continue + + self._cached_row.append(None) return self._cached_row[column] @@ -198,4 +242,3 @@ class ListModel(gtk.GenericTreeModel, gtk.TreeDragSource): return True return False - diff --git a/src/jarabe/journal/listview.py b/src/jarabe/journal/listview.py index 9e19f70..0aee1b7 100644 --- a/src/jarabe/journal/listview.py +++ b/src/jarabe/journal/listview.py @@ -34,11 +34,13 @@ from jarabe.journal.palettes import ObjectPalette, BuddyPalette from jarabe.journal import model from jarabe.journal import misc + UPDATE_INTERVAL = 300 MESSAGE_EMPTY_JOURNAL = 0 MESSAGE_NO_MATCH = 1 + class TreeView(gtk.TreeView): __gtype_name__ = 'JournalTreeView' @@ -47,8 +49,8 @@ class TreeView(gtk.TreeView): self.set_headers_visible(False) 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. + # 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 @@ -58,13 +60,12 @@ class TreeView(gtk.TreeView): 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, - gobject.TYPE_NONE, - ([])) + 'clear-clicked': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), } def __init__(self): @@ -81,7 +82,8 @@ class BaseListView(gtk.Bin): self.connect('destroy', self.__destroy_cb) self._scrolled_window = gtk.ScrolledWindow() - self._scrolled_window.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) + self._scrolled_window.set_policy(gtk.POLICY_NEVER, + gtk.POLICY_AUTOMATIC) self.add(self._scrolled_window) self._scrolled_window.show() @@ -97,7 +99,7 @@ class BaseListView(gtk.Bin): self.cell_title = None self.cell_icon = None self._title_column = None - self.date_column = None + self.sort_column = None self._add_columns() self.tree_view.enable_model_drag_source(gtk.gdk.BUTTON1_MASK, @@ -141,7 +143,8 @@ class BaseListView(gtk.Bin): 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, 'file-name', + ListModel.COLUMN_ICON) column.add_attribute(self.cell_icon, 'xo-color', ListModel.COLUMN_ICON_COLOR) self.tree_view.append_column(column) @@ -163,7 +166,8 @@ class BaseListView(gtk.Bin): 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, + 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) @@ -190,15 +194,16 @@ class BaseListView(gtk.Bin): date = util.timestamp_to_elapsed_string(timestamp) date_width = self._get_width_for_string(date) - self.date_column = gtk.TreeViewColumn() - 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.pack_start(cell_text) - self.date_column.add_attribute(cell_text, 'text', ListModel.COLUMN_DATE) - self.tree_view.append_column(self.date_column) + self.sort_column = gtk.TreeViewColumn() + self.sort_column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED + self.sort_column.props.fixed_width = date_width + self.sort_column.set_alignment(1) + self.sort_column.props.resizable = True + self.sort_column.props.clickable = True + self.sort_column.pack_start(cell_text) + self.sort_column.add_attribute(cell_text, 'text', + ListModel.COLUMN_TIMESTAMP) + self.tree_view.append_column(self.sort_column) def _get_width_for_string(self, text): # Add some extra margin @@ -252,11 +257,16 @@ class BaseListView(gtk.Bin): def update_with_query(self, query_dict): logging.debug('ListView.update_with_query') + if 'order_by' not in query_dict: + query_dict['order_by'] = ['+timestamp'] + if query_dict['order_by'] != self._query.get('order_by'): + property_ = query_dict['order_by'][0][1:] + cell_text = self.sort_column.get_cell_renderers()[0] + self.sort_column.set_attributes(cell_text, + text=getattr(ListModel, 'COLUMN_' + property_.upper(), + ListModel.COLUMN_TIMESTAMP)) self._query = query_dict - if 'order_by' not in self._query: - self._query['order_by'] = ['+timestamp'] - self.refresh() def refresh(self): @@ -316,12 +326,9 @@ class BaseListView(gtk.Bin): def _is_query_empty(self): # FIXME: This is a hack, we shouldn't have to update this every time # a new search term is added. - if self._query.get('query', '') or self._query.get('mime_type', '') or \ - self._query.get('keep', '') or self._query.get('mtime', '') or \ - self._query.get('activity', ''): - return False - else: - return True + return not (self._query.get('query') or self._query.get('mime_type') or + self._query.get('keep') or self._query.get('mtime') or + self._query.get('activity')) def __model_progress_cb(self, tree_model): if self._progress_bar is None: @@ -365,8 +372,8 @@ class BaseListView(gtk.Bin): icon = CanvasIcon(size=style.LARGE_ICON_SIZE, icon_name='activity-journal', - stroke_color = style.COLOR_BUTTON_GREY.get_svg(), - fill_color = style.COLOR_TRANSPARENT.get_svg()) + stroke_color=style.COLOR_BUTTON_GREY.get_svg(), + fill_color=style.COLOR_TRANSPARENT.get_svg()) box.append(icon) if message == MESSAGE_EMPTY_JOURNAL: @@ -379,7 +386,7 @@ class BaseListView(gtk.Bin): text = hippo.CanvasText(text=text, xalign=hippo.ALIGNMENT_CENTER, font_desc=style.FONT_BOLD.get_pango_desc(), - color = style.COLOR_BUTTON_GREY.get_int()) + color=style.COLOR_BUTTON_GREY.get_int()) box.append(text) if message == MESSAGE_NO_MATCH: @@ -415,7 +422,7 @@ class BaseListView(gtk.Bin): while True: x, y, width, height = self.tree_view.get_cell_area(path, - self.date_column) + self.sort_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: @@ -455,13 +462,13 @@ class BaseListView(gtk.Bin): self.update_dates() return True + class ListView(BaseListView): __gtype_name__ = 'JournalListView' __gsignals__ = { - 'detail-clicked': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([object])) + 'detail-clicked': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object])), } def __init__(self): @@ -533,6 +540,7 @@ class ListView(BaseListView): def __editing_canceled_cb(self, cell): self.cell_title.props.editable = False + class CellRendererFavorite(CellRendererIcon): __gtype_name__ = 'JournalCellRendererFavorite' @@ -547,6 +555,7 @@ class CellRendererFavorite(CellRendererIcon): 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' @@ -563,12 +572,12 @@ class CellRendererDetail(CellRendererIcon): 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, + 'detail-clicked': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([str])), } @@ -605,6 +614,7 @@ class CellRendererActivityIcon(CellRendererIcon): show_palette = gobject.property(type=bool, default=True, setter=set_show_palette) + class CellRendererBuddy(CellRendererIcon): __gtype_name__ = 'JournalCellRendererBuddy' @@ -638,4 +648,3 @@ class CellRendererBuddy(CellRendererIcon): self.props.xo_color = xo_color buddy = gobject.property(type=object, setter=set_buddy) - diff --git a/src/jarabe/journal/misc.py b/src/jarabe/journal/misc.py index 24ad216..1431d5f 100644 --- a/src/jarabe/journal/misc.py +++ b/src/jarabe/journal/misc.py @@ -27,8 +27,10 @@ from sugar.activity import activityfactory from sugar.activity.activityhandle import ActivityHandle from sugar.graphics.icon import get_icon_file_name from sugar.graphics.xocolor import XoColor +from sugar.graphics.alert import ConfirmationAlert from sugar import mime from sugar.bundle.activitybundle import ActivityBundle +from sugar.bundle.bundle import AlreadyInstalledException from sugar.bundle.contentbundle import ContentBundle from sugar import util @@ -36,6 +38,8 @@ from jarabe.view import launcher from jarabe.model import bundleregistry, shell from jarabe.journal.journalentrybundle import JournalEntryBundle from jarabe.journal import model +from jarabe.journal import journalwindow + def _get_icon_for_mime(mime_type): generic_types = mime.get_all_generic_types() @@ -52,6 +56,7 @@ def _get_icon_for_mime(mime_type): if file_name is not None: return file_name + def get_icon_name(metadata): file_name = None @@ -81,35 +86,46 @@ def get_icon_name(metadata): return file_name + def get_date(metadata): """ Convert from a string in iso format to a more human-like format. """ - if metadata.has_key('timestamp'): - timestamp = float(metadata['timestamp']) - return util.timestamp_to_elapsed_string(timestamp) - elif metadata.has_key('mtime'): - ti = time.strptime(metadata['mtime'], "%Y-%m-%dT%H:%M:%S") - return util.timestamp_to_elapsed_string(time.mktime(ti)) - else: - return _('No date') + if 'timestamp' in metadata: + try: + timestamp = float(metadata['timestamp']) + except (TypeError, ValueError): + logging.warning('Invalid timestamp: %r', metadata['timestamp']) + else: + return util.timestamp_to_elapsed_string(timestamp) + + if 'mtime' in metadata: + try: + ti = time.strptime(metadata['mtime'], '%Y-%m-%dT%H:%M:%S') + except (TypeError, ValueError): + logging.warning('Invalid mtime: %r', metadata['mtime']) + else: + return util.timestamp_to_elapsed_string(time.mktime(ti)) + + return _('No date') + def get_bundle(metadata): try: if is_activity_bundle(metadata): - file_path = util.TempFilePath(model.get_file(metadata['uid'])) + file_path = model.get_file(metadata['uid']) if not os.path.exists(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'])) + file_path = model.get_file(metadata['uid']) if not os.path.exists(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'])) + file_path = model.get_file(metadata['uid']) if not os.path.exists(file_path): logging.warning('Invalid path: %r', file_path) return None @@ -120,6 +136,7 @@ def get_bundle(metadata): logging.exception('Incorrect bundle') return None + def _get_activities_for_mime(mime_type): registry = bundleregistry.get_registry() result = registry.get_activities_for_type(mime_type) @@ -130,6 +147,7 @@ def _get_activities_for_mime(mime_type): result.append(activity) return result + def get_activities(metadata): activities = [] @@ -148,6 +166,7 @@ def get_activities(metadata): return activities + def resume(metadata, bundle_id=None): registry = bundleregistry.get_registry() @@ -159,19 +178,16 @@ def resume(metadata, bundle_id=None): bundle = ActivityBundle(file_path) if not registry.is_installed(bundle): logging.debug('Installing activity bundle') - registry.install(bundle) + try: + registry.install(bundle) + except AlreadyInstalledException: + _downgrade_option_alert(bundle) + return else: logging.debug('Upgrading activity bundle') registry.upgrade(bundle) - logging.debug('activityfactory.creating bundle with id %r', - bundle.get_bundle_id()) - installed_bundle = registry.get_bundle(bundle.get_bundle_id()) - if installed_bundle: - activityfactory.create(installed_bundle) - else: - logging.error('Bundle %r is not installed.', - bundle.get_bundle_id()) + _launch_bundle(bundle) elif is_content_bundle(metadata) and bundle_id is None: @@ -192,17 +208,10 @@ def resume(metadata, bundle_id=None): logging.debug('activityfactory.creating with uri %s', uri) activity_bundle = registry.get_bundle(activities[0].get_bundle_id()) - activityfactory.create_with_uri(activity_bundle, bundle.get_start_uri()) + launch(activity_bundle, uri=uri) else: activity_id = metadata.get('activity_id', '') - if activity_id: - shell_model = shell.get_model() - activity = shell_model.get_activity_by_id(activity_id) - if activity: - activity.get_window().activate(gtk.get_current_event_time()) - return - if bundle_id is None: activities = get_activities(metadata) if not activities: @@ -213,36 +222,91 @@ def resume(metadata, bundle_id=None): bundle = registry.get_bundle(bundle_id) - if metadata.get('mountpoint', '/') == '/': object_id = metadata['uid'] else: object_id = model.copy(metadata, '/') - if activity_id: - launcher.add_launcher(activity_id, bundle.get_icon(), - get_icon_color(metadata)) - handle = ActivityHandle(object_id=object_id, - activity_id=activity_id) - activityfactory.create(bundle, handle) - else: - activityfactory.create_with_object_id(bundle, object_id) + launch(bundle, activity_id=activity_id, object_id=object_id, + color=get_icon_color(metadata)) + + +def _launch_bundle(bundle): + registry = bundleregistry.get_registry() + logging.debug('activityfactory.creating bundle with id %r', + bundle.get_bundle_id()) + installed_bundle = registry.get_bundle(bundle.get_bundle_id()) + if installed_bundle: + launch(installed_bundle) + else: + logging.error('Bundle %r is not installed.', + bundle.get_bundle_id()) + + +def launch(bundle, activity_id=None, object_id=None, uri=None, color=None, + invited=False): + if activity_id is None or not activity_id: + activity_id = activityfactory.create_activity_id() + + logging.debug('launch bundle_id=%s activity_id=%s object_id=%s uri=%s', + bundle.get_bundle_id(), activity_id, object_id, uri) + + shell_model = shell.get_model() + activity = shell_model.get_activity_by_id(activity_id) + if activity is not None: + logging.debug('re-launch %r', activity.get_window()) + activity.get_window().activate(gtk.get_current_event_time()) + return + + if color is None: + client = gconf.client_get_default() + color = XoColor(client.get_string('/desktop/sugar/user/color')) + + launcher.add_launcher(activity_id, bundle.get_icon(), color) + activity_handle = ActivityHandle(activity_id=activity_id, + object_id=object_id, uri=uri, invited=invited) + activityfactory.create(bundle, activity_handle) + + +def _downgrade_option_alert(bundle): + alert = ConfirmationAlert() + alert.props.title = _('Older Version Of %s Activity') % (bundle.get_name()) + alert.props.msg = _('Do you want to downgrade to version %s') % \ + bundle.get_activity_version() + alert.connect('response', _downgrade_alert_response_cb, bundle) + journalwindow.get_journal_window().add_alert(alert) + alert.show() + + +def _downgrade_alert_response_cb(alert, response_id, bundle): + if response_id is gtk.RESPONSE_OK: + journalwindow.get_journal_window().remove_alert(alert) + registry = bundleregistry.get_registry() + registry.install(bundle, force_downgrade=True) + _launch_bundle(bundle) + elif response_id is gtk.RESPONSE_CANCEL: + journalwindow.get_journal_window().remove_alert(alert) + def is_activity_bundle(metadata): mime_type = metadata.get('mime_type', '') return mime_type == ActivityBundle.MIME_TYPE or \ mime_type == ActivityBundle.DEPRECATED_MIME_TYPE + def is_content_bundle(metadata): return metadata.get('mime_type', '') == ContentBundle.MIME_TYPE + def is_journal_bundle(metadata): return metadata.get('mime_type', '') == JournalEntryBundle.MIME_TYPE + def is_bundle(metadata): return is_activity_bundle(metadata) or is_content_bundle(metadata) or \ is_journal_bundle(metadata) + def get_icon_color(metadata): if metadata is None or not 'icon-color' in metadata: client = gconf.client_get_default() diff --git a/src/jarabe/journal/modalalert.py b/src/jarabe/journal/modalalert.py index c7c6a0a..6880941 100644 --- a/src/jarabe/journal/modalalert.py +++ b/src/jarabe/journal/modalalert.py @@ -22,6 +22,7 @@ from sugar.graphics.icon import Icon from sugar.graphics import style from sugar.graphics.xocolor import XoColor + class ModalAlert(gtk.Window): __gtype_name__ = 'SugarModalAlert' @@ -84,14 +85,12 @@ class ModalAlert(gtk.Window): self.add(self._main_view) self._main_view.show() - self.connect("realize", self.__realize_cb) + self.connect('realize', self.__realize_cb) def __realize_cb(self, widget): self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG) self.window.set_accept_focus(True) def __show_journal_cb(self, button): - '''The opener will listen on the destroy signal - ''' + """The opener will listen on the destroy signal""" self.destroy() - diff --git a/src/jarabe/journal/model.py b/src/jarabe/journal/model.py index ffc62e0..320e577 100644 --- a/src/jarabe/journal/model.py +++ b/src/jarabe/journal/model.py @@ -1,4 +1,4 @@ -# Copyright (C) 2007-2008, One Laptop Per Child +# Copyright (C) 2007-2010, 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 @@ -16,11 +16,13 @@ import logging import os +import errno from datetime import datetime import time import shutil -from stat import S_IFMT, S_IFDIR, S_IFREG +from stat import S_IFLNK, S_IFMT, S_IFDIR, S_IFREG import re +from operator import itemgetter import gobject import dbus @@ -31,18 +33,25 @@ from sugar import dispatch from sugar import mime from sugar import util + DS_DBUS_SERVICE = 'org.laptop.sugar.DataStore' DS_DBUS_INTERFACE = 'org.laptop.sugar.DataStore' DS_DBUS_PATH = '/org/laptop/sugar/DataStore' # Properties the journal cares about. -PROPERTIES = ['uid', 'title', 'mtime', 'timestamp', 'keep', 'buddies', - 'icon-color', 'mime_type', 'progress', 'activity', 'mountpoint', - 'activity_id', 'bundle_id'] +PROPERTIES = ['activity', 'activity_id', 'buddies', 'bundle_id', + 'creation_time', 'filesize', 'icon-color', 'keep', 'mime_type', + 'mountpoint', 'mtime', 'progress', 'timestamp', 'title', 'uid'] MIN_PAGES_TO_CACHE = 3 MAX_PAGES_TO_CACHE = 5 +_datastore = None +created = dispatch.Signal() +updated = dispatch.Signal() +deleted = dispatch.Signal() + + class _Cache(object): __gtype_name__ = 'model_Cache' @@ -73,7 +82,7 @@ class BaseResultSet(object): """ def __init__(self, query, page_size): - self._total_count = -1 + self._total_count = -1 self._position = -1 self._query = query self._page_size = page_size @@ -108,8 +117,6 @@ class BaseResultSet(object): self._position = position def read(self): - logging.debug('ResultSet.read position: %r', self._position) - if self._position == -1: self.seek(0) @@ -142,7 +149,8 @@ class BaseResultSet(object): self._cache.append_all(entries) self._offset = offset - elif remaining_forward_entries <= 0 and remaining_backwards_entries > 0: + 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', @@ -184,11 +192,10 @@ class BaseResultSet(object): objects_excess = len(self._cache) - cache_limit if objects_excess > 0: del self._cache[-objects_excess:] - else: - logging.debug('cache hit and no need to grow the cache') return self._cache[self._position - self._offset] + class DatastoreResultSet(BaseResultSet): """Encapsulates the result of a query on the datastore """ @@ -216,6 +223,7 @@ class DatastoreResultSet(BaseResultSet): return entries, total_count + class InplaceResultSet(BaseResultSet): """Encapsulates the result of a query on a mount point """ @@ -223,7 +231,9 @@ class InplaceResultSet(BaseResultSet): BaseResultSet.__init__(self, query, page_size) self._mount_point = mount_point self._file_list = None - self._pending_directories = 0 + self._pending_directories = [] + self._visited_directories = [] + self._pending_files = [] self._stopped = False query_text = query.get('query', '') @@ -246,15 +256,27 @@ class InplaceResultSet(BaseResultSet): self._mime_types = query.get('mime_type', []) + self._sort = query.get('order_by', ['+timestamp'])[0] + def setup(self): self._file_list = [] - self._recurse_dir(self._mount_point) + self._pending_directories = [self._mount_point] + self._visited_directories = [] + self._pending_files = [] + gobject.idle_add(self._scan) def stop(self): self._stopped = True def setup_ready(self): - self._file_list.sort(lambda a, b: b[2] - a[2]) + if self._sort[1:] == 'filesize': + keygetter = itemgetter(3) + else: + # timestamp + keygetter = itemgetter(2) + self._file_list.sort(lambda a, b: cmp(b, a), + key=keygetter, + reverse=(self._sort[0] == '-')) self.ready.send(self) def find(self, query): @@ -267,13 +289,13 @@ class InplaceResultSet(BaseResultSet): t = time.time() offset = int(query.get('offset', 0)) - limit = int(query.get('limit', len(self._file_list))) + limit = int(query.get('limit', len(self._file_list))) total_count = len(self._file_list) files = self._file_list[offset:offset + limit] entries = [] - for file_path, stat, mtime_ in files: + for file_path, stat, mtime_, size_ in files: metadata = _get_file_metadata(file_path, stat) metadata['mountpoint'] = self._mount_point entries.append(metadata) @@ -282,75 +304,115 @@ class InplaceResultSet(BaseResultSet): return entries, total_count - def _recurse_dir(self, dir_path): - self._pending_directories += 1 - gobject.idle_add(self._idle_recurse_dir, dir_path) + def _scan(self): + if self._stopped: + return False - def _idle_recurse_dir(self, dir_path): - try: - self._real_recurse_dir(dir_path) - finally: - self._pending_directories -= 1 - if self._pending_directories == 0: - self.setup_ready() + self.progress.send(self) - def _real_recurse_dir(self, dir_path): - if self._stopped: - return + if self._pending_files: + self._scan_a_file() + return True + + if self._pending_directories: + self._scan_a_directory() + return True + + self.setup_ready() + self._visited_directories = [] + return False + + def _scan_a_file(self): + full_path = self._pending_files.pop(0) try: - dirs = os.listdir(dir_path) - except Exception: - logging.exception('Error reading directory %r', dir_path) - dirs = [] + stat = os.lstat(full_path) + except OSError, e: + if e.errno != errno.ENOENT: + logging.exception( + 'Error reading metadata of file %r', full_path) + return + + if S_IFMT(stat.st_mode) == S_IFLNK: + try: + link = os.readlink(full_path) + except OSError, e: + logging.exception( + 'Error reading target of link %r', full_path) + return + + if not os.path.abspath(link).startswith(self._mount_point): + return - for entry in dirs: - if entry.startswith('.'): - continue - full_path = dir_path + '/' + entry try: stat = os.stat(full_path) - if S_IFMT(stat.st_mode) == S_IFDIR: - self._recurse_dir(full_path) - elif S_IFMT(stat.st_mode) == S_IFREG: - add_to_list = True + except OSError, e: + if e.errno != errno.ENOENT: + logging.exception( + 'Error reading metadata of linked file %r', full_path) + return + + if S_IFMT(stat.st_mode) == S_IFDIR: + id_tuple = stat.st_ino, stat.st_dev + if not id_tuple in self._visited_directories: + self._visited_directories.append(id_tuple) + self._pending_directories.append(full_path) + return + + if S_IFMT(stat.st_mode) != S_IFREG: + return - if self._regex is not None and \ - not self._regex.match(full_path): - add_to_list = False + if self._regex is not None and \ + not self._regex.match(full_path): + return - if None not in [self._date_start, self._date_end] and \ - (stat.st_mtime < self._date_start or - stat.st_mtime > self._date_end): - add_to_list = False + if self._date_start is not None and stat.st_mtime < self._date_start: + return - if self._mime_types: - mime_type = gio.content_type_guess(filename=full_path) - if mime_type not in self._mime_types: - add_to_list = False + if self._date_end is not None and stat.st_mtime > self._date_end: + return - if add_to_list: - file_info = (full_path, stat, int(stat.st_mtime)) - self._file_list.append(file_info) + if self._mime_types: + mime_type = gio.content_type_guess(filename=full_path) + if mime_type not in self._mime_types: + return - self.progress.send(self) + file_info = (full_path, stat, int(stat.st_mtime), stat.st_size) + self._file_list.append(file_info) + + return + + def _scan_a_directory(self): + dir_path = self._pending_directories.pop(0) + + try: + entries = os.listdir(dir_path) + except OSError, e: + if e.errno != errno.EACCES: + logging.exception('Error reading directory %r', dir_path) + return + + for entry in entries: + if entry.startswith('.'): + continue + self._pending_files.append(dir_path + '/' + entry) + return - except Exception: - logging.exception('Error reading file %r', full_path) def _get_file_metadata(path, stat): client = gconf.client_get_default() return {'uid': path, 'title': os.path.basename(path), 'timestamp': stat.st_mtime, + 'filesize': stat.st_size, 'mime_type': gio.content_type_guess(filename=path), 'activity': '', 'activity_id': '', 'icon-color': client.get_string('/desktop/sugar/user/color'), 'description': path} -_datastore = None + def _get_datastore(): global _datastore if _datastore is None: @@ -364,15 +426,19 @@ def _get_datastore(): return _datastore + def _datastore_created_cb(object_id): created.send(None, object_id=object_id) + def _datastore_updated_cb(object_id): updated.send(None, object_id=object_id) + def _datastore_deleted_cb(object_id): deleted.send(None, object_id=object_id) + def find(query_, page_size): """Returns a ResultSet """ @@ -387,6 +453,7 @@ def find(query_, page_size): else: return InplaceResultSet(query, page_size, mount_points[0]) + def _get_mount_point(path): dir_path = os.path.dirname(path) while True: @@ -395,6 +462,7 @@ def _get_mount_point(path): else: dir_path = dir_path.rsplit(os.sep, 1)[0] + def get(object_id): """Returns the metadata for an object """ @@ -407,6 +475,7 @@ def get(object_id): metadata['mountpoint'] = '/' return metadata + def get_file(object_id): """Returns the file for an object """ @@ -421,6 +490,7 @@ def get_file(object_id): else: return None + def get_file_size(object_id): """Return the file size for an object """ @@ -436,12 +506,14 @@ def get_file_size(object_id): return 0 + def get_unique_values(key): """Returns a list with the different values a property has taken """ empty_dict = dbus.Dictionary({}, signature='ss') return _get_datastore().get_uniquevaluesfor(key, empty_dict) + def delete(object_id): """Removes an object from persistent storage """ @@ -451,6 +523,7 @@ def delete(object_id): else: _get_datastore().delete(object_id) + def copy(metadata, mount_point): """Copies an object to another mount point """ @@ -462,6 +535,7 @@ def copy(metadata, mount_point): return write(metadata, file_path, transfer_ownership=False) + def write(metadata, file_path='', update_mtime=True, transfer_ownership=True): """Creates or updates an entry for that id """ @@ -496,6 +570,7 @@ def write(metadata, file_path='', update_mtime=True, transfer_ownership=True): return object_id + def _get_file_name(title, mime_type): file_name = title @@ -520,11 +595,12 @@ def _get_file_name(title, mime_type): return file_name + def _get_unique_file_name(mount_point, file_name): if os.path.exists(os.path.join(mount_point, file_name)): i = 1 + name, extension = os.path.splitext(file_name) while len(file_name) <= 255: - name, extension = os.path.splitext(file_name) file_name = name + '_' + str(i) + extension if not os.path.exists(os.path.join(mount_point, file_name)): break @@ -532,10 +608,7 @@ def _get_unique_file_name(mount_point, file_name): return file_name + def is_editable(metadata): mountpoint = metadata.get('mountpoint', '/') return mountpoint == '/' - -created = dispatch.Signal() -updated = dispatch.Signal() -deleted = dispatch.Signal() diff --git a/src/jarabe/journal/objectchooser.py b/src/jarabe/journal/objectchooser.py index 49af3e6..ecb8ecf 100644 --- a/src/jarabe/journal/objectchooser.py +++ b/src/jarabe/journal/objectchooser.py @@ -29,14 +29,13 @@ from jarabe.journal.listmodel import ListModel from jarabe.journal.journaltoolbox import SearchToolbar from jarabe.journal.volumestoolbar import VolumesToolbar + class ObjectChooser(gtk.Window): __gtype_name__ = 'ObjectChooser' __gsignals__ = { - 'response': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([int])) + 'response': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([int])), } def __init__(self, parent=None, what_filter=''): @@ -136,6 +135,7 @@ class ObjectChooser(gtk.Window): visible = event.state == gtk.gdk.VISIBILITY_FULLY_OBSCURED self._list_view.set_is_visible(visible) + class TitleBox(VolumesToolbar): __gtype_name__ = 'TitleBox' @@ -162,6 +162,7 @@ class TitleBox(VolumesToolbar): self.insert(tool_item, -1) tool_item.show() + class ChooserListView(BaseListView): __gtype_name__ = 'ChooserListView' @@ -187,7 +188,7 @@ class ChooserListView(BaseListView): if event.window != tree_view.get_bin_window(): return False - pos = tree_view.get_path_at_pos(event.x, event.y) + pos = tree_view.get_path_at_pos(int(event.x), int(event.y)) if pos is None: return False @@ -196,4 +197,3 @@ class ChooserListView(BaseListView): self.emit('entry-activated', uid) return False - diff --git a/src/jarabe/journal/palettes.py b/src/jarabe/journal/palettes.py index 0e7702d..9ae1afb 100644 --- a/src/jarabe/journal/palettes.py +++ b/src/jarabe/journal/palettes.py @@ -28,20 +28,19 @@ from sugar.graphics.icon import Icon from sugar.graphics.xocolor import XoColor from sugar import mime -from jarabe.model import bundleregistry from jarabe.model import friends from jarabe.model import filetransfer from jarabe.model import mimeregistry from jarabe.journal import misc from jarabe.journal import model + class ObjectPalette(Palette): __gtype_name__ = 'ObjectPalette' __gsignals__ = { - 'detail-clicked': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, + 'detail-clicked': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([str])), } @@ -54,7 +53,7 @@ class ObjectPalette(Palette): activity_icon.props.file = misc.get_icon_name(metadata) activity_icon.props.xo_color = misc.get_icon_color(metadata) - if metadata.has_key('title'): + if 'title' in metadata: title = gobject.markup_escape_text(metadata['title']) else: title = _('Untitled') @@ -62,22 +61,29 @@ class ObjectPalette(Palette): Palette.__init__(self, primary_text=title, icon=activity_icon) - if metadata.get('activity_id', ''): - resume_label = _('Resume') - resume_with_label = _('Resume with') - else: - resume_label = _('Start') - resume_with_label = _('Start with') - menu_item = MenuItem(resume_label, 'activity-start') - menu_item.connect('activate', self.__start_activate_cb) - self.menu.append(menu_item) - menu_item.show() + if misc.get_activities(metadata) or misc.is_bundle(metadata): + if metadata.get('activity_id', ''): + resume_label = _('Resume') + resume_with_label = _('Resume with') + else: + resume_label = _('Start') + resume_with_label = _('Start with') + menu_item = MenuItem(resume_label, 'activity-start') + menu_item.connect('activate', self.__start_activate_cb) + self.menu.append(menu_item) + menu_item.show() - menu_item = MenuItem(resume_with_label, 'activity-start') - self.menu.append(menu_item) - menu_item.show() - start_with_menu = StartWithMenu(self._metadata) - menu_item.set_submenu(start_with_menu) + menu_item = MenuItem(resume_with_label, 'activity-start') + self.menu.append(menu_item) + menu_item.show() + start_with_menu = StartWithMenu(self._metadata) + menu_item.set_submenu(start_with_menu) + + else: + menu_item = MenuItem(_('No activity to start entry')) + menu_item.set_sensitive(False) + self.menu.append(menu_item) + menu_item.show() client = gconf.client_get_default() color = XoColor(client.get_string('/desktop/sugar/user/color')) @@ -128,11 +134,6 @@ class ObjectPalette(Palette): self._temp_file_path = None def __erase_activate_cb(self, menu_item): - registry = bundleregistry.get_registry() - - bundle = misc.get_bundle(self._metadata) - if bundle is not None and registry.is_installed(bundle): - registry.uninstall(bundle) model.delete(self._metadata['uid']) def __detail_activate_cb(self, menu_item): @@ -152,12 +153,13 @@ class ObjectPalette(Palette): filetransfer.start_transfer(buddy, file_name, title, description, mime_type) + class FriendsMenu(gtk.Menu): __gtype_name__ = 'JournalFriendsMenu' __gsignals__ = { - 'friend-selected' : (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([object])), + 'friend-selected': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object])), } def __init__(self): diff --git a/src/jarabe/journal/volumestoolbar.py b/src/jarabe/journal/volumestoolbar.py index 74b974c..2d842f1 100644 --- a/src/jarabe/journal/volumestoolbar.py +++ b/src/jarabe/journal/volumestoolbar.py @@ -15,6 +15,8 @@ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA import logging +import os +import statvfs from gettext import gettext as _ import gobject @@ -25,17 +27,20 @@ import gconf from sugar.graphics.radiotoolbutton import RadioToolButton from sugar.graphics.palette import Palette from sugar.graphics.xocolor import XoColor +from sugar import env from jarabe.journal import model from jarabe.view.palettes import VolumePalette + class VolumesToolbar(gtk.Toolbar): __gtype_name__ = 'VolumesToolbar' __gsignals__ = { - 'volume-changed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([str])) + 'volume-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([str])), + 'volume-error': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([str, str])), } def __init__(self): @@ -44,7 +49,6 @@ class VolumesToolbar(gtk.Toolbar): self._mount_removed_hid = None button = JournalButton() - button.set_palette(Palette(_('Journal'))) button.connect('toggled', self._button_toggled_cb) self.insert(button, 0) button.show() @@ -61,10 +65,10 @@ class VolumesToolbar(gtk.Toolbar): def _set_up_volumes(self): volume_monitor = gio.volume_monitor_get() - self._mount_added_hid = \ - volume_monitor.connect('mount-added', self.__mount_added_cb) - self._mount_removed_hid = \ - volume_monitor.connect('mount-removed', self.__mount_removed_cb) + self._mount_added_hid = volume_monitor.connect('mount-added', + self.__mount_added_cb) + self._mount_removed_hid = volume_monitor.connect('mount-removed', + self.__mount_removed_cb) for mount in volume_monitor.get_mounts(): self._add_button(mount) @@ -81,6 +85,7 @@ class VolumesToolbar(gtk.Toolbar): button = VolumeButton(mount) button.props.group = self._volume_buttons[0] button.connect('toggled', self._button_toggled_cb) + button.connect('volume-error', self.__volume_error_cb) position = self.get_item_index(self._volume_buttons[-1]) + 1 self.insert(button, position) button.show() @@ -90,6 +95,9 @@ class VolumesToolbar(gtk.Toolbar): if len(self.get_children()) > 1: self.show() + def __volume_error_cb(self, button, strerror, severity): + self.emit('volume-error', strerror, severity) + def _button_toggled_cb(self, button): if button.props.active: self.emit('volume-changed', button.mount_point) @@ -122,7 +130,13 @@ class VolumesToolbar(gtk.Toolbar): button = self._get_button_for_mount(mount) button.props.active = True + class BaseButton(RadioToolButton): + __gsignals__ = { + 'volume-error': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([str, str])), + } + def __init__(self, mount_point): RadioToolButton.__init__(self) @@ -133,11 +147,26 @@ class BaseButton(RadioToolButton): gtk.gdk.ACTION_COPY) self.connect('drag-data-received', self._drag_data_received_cb) - def _drag_data_received_cb(self, widget, drag_context, x, y, selection_data, - info, timestamp): + def _drag_data_received_cb(self, widget, drag_context, x, y, + selection_data, info, timestamp): object_id = selection_data.data metadata = model.get(object_id) - model.copy(metadata, self.mount_point) + file_path = model.get_file(metadata['uid']) + if not file_path or not os.path.exists(file_path): + logging.warn('Entries without a file cannot be copied.') + self.emit('volume-error', + _('Entries without a file cannot be copied.'), + _('Warning')) + return + + try: + model.copy(metadata, self.mount_point) + except IOError, e: + logging.exception('Error while copying the entry. %s', e.strerror) + self.emit('volume-error', + _('Error while copying the entry. %s') % e.strerror, + _('Error')) + class VolumeButton(BaseButton): def __init__(self, mount): @@ -169,6 +198,7 @@ class VolumeButton(BaseButton): #palette.set_group_id('frame') return palette + class JournalButton(BaseButton): def __init__(self): BaseButton.__init__(self, mount_point='/') @@ -179,3 +209,36 @@ class JournalButton(BaseButton): color = XoColor(client.get_string('/desktop/sugar/user/color')) self.props.xo_color = color + def create_palette(self): + palette = JournalButtonPalette(self) + return palette + + +class JournalButtonPalette(Palette): + + def __init__(self, mount): + Palette.__init__(self, _('Journal')) + vbox = gtk.VBox() + self.set_content(vbox) + vbox.show() + + self._progress_bar = gtk.ProgressBar() + vbox.add(self._progress_bar) + self._progress_bar.show() + + self._free_space_label = gtk.Label() + self._free_space_label.set_alignment(0.5, 0.5) + vbox.add(self._free_space_label) + self._free_space_label.show() + + self.connect('popup', self.__popup_cb) + + def __popup_cb(self, palette): + stat = os.statvfs(env.get_profile_path()) + free_space = stat[statvfs.F_BSIZE] * stat[statvfs.F_BAVAIL] + total_space = stat[statvfs.F_BSIZE] * stat[statvfs.F_BLOCKS] + + fraction = (total_space - free_space) / float(total_space) + self._progress_bar.props.fraction = fraction + self._free_space_label.props.label = _('%(free_space)d MB Free') % \ + {'free_space': free_space / (1024 * 1024)} diff --git a/src/jarabe/model/Makefile.am b/src/jarabe/model/Makefile.am index e9f0700..92e8712 100644 --- a/src/jarabe/model/Makefile.am +++ b/src/jarabe/model/Makefile.am @@ -1,5 +1,6 @@ sugardir = $(pythondir)/jarabe/model sugar_PYTHON = \ + adhoc.py \ __init__.py \ buddy.py \ bundleregistry.py \ @@ -7,12 +8,12 @@ sugar_PYTHON = \ friends.py \ invites.py \ olpcmesh.py \ - owner.py \ - mimeregistry.py \ + mimeregistry.py \ neighborhood.py \ network.py \ notifications.py \ shell.py \ screen.py \ session.py \ - sound.py + sound.py \ + telepathyclient.py diff --git a/src/jarabe/model/__init__.py b/src/jarabe/model/__init__.py index a9dd95a..85f6a24 100644 --- a/src/jarabe/model/__init__.py +++ b/src/jarabe/model/__init__.py @@ -13,4 +13,3 @@ # 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/model/adhoc.py b/src/jarabe/model/adhoc.py new file mode 100644 index 0000000..8842a5c --- /dev/null +++ b/src/jarabe/model/adhoc.py @@ -0,0 +1,294 @@ +# Copyright (C) 2010 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 + +import dbus +import gobject + +from jarabe.model import network +from jarabe.model.network import Settings +from sugar.util import unique_id +from jarabe.model.network import IP4Config + + +_NM_SERVICE = 'org.freedesktop.NetworkManager' +_NM_IFACE = 'org.freedesktop.NetworkManager' +_NM_PATH = '/org/freedesktop/NetworkManager' +_NM_DEVICE_IFACE = 'org.freedesktop.NetworkManager.Device' +_NM_WIRELESS_IFACE = 'org.freedesktop.NetworkManager.Device.Wireless' +_NM_ACCESSPOINT_IFACE = 'org.freedesktop.NetworkManager.AccessPoint' +_NM_ACTIVE_CONN_IFACE = 'org.freedesktop.NetworkManager.Connection.Active' + +_adhoc_manager_instance = None + + +def get_adhoc_manager_instance(): + global _adhoc_manager_instance + if _adhoc_manager_instance is None: + _adhoc_manager_instance = AdHocManager() + return _adhoc_manager_instance + + +class AdHocManager(gobject.GObject): + """To mimic the mesh behavior on devices where mesh hardware is + not available we support the creation of an Ad-hoc network on + three channels 1, 6, 11. If Sugar sees no "known" network when it + starts, it does autoconnect to an Ad-hoc network. + + """ + + __gsignals__ = { + 'members-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])), + 'state-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])), + } + + _AUTOCONNECT_TIMEOUT = 30 + _CHANNEL_1 = 1 + _CHANNEL_6 = 6 + _CHANNEL_11 = 11 + + def __init__(self): + gobject.GObject.__init__(self) + + self._bus = dbus.SystemBus() + self._device = None + self._idle_source = 0 + self._listening_called = 0 + self._device_state = network.DEVICE_STATE_UNKNOWN + + self._current_channel = None + self._networks = {self._CHANNEL_1: None, + self._CHANNEL_6: None, + self._CHANNEL_11: None} + + def start_listening(self, device): + self._listening_called += 1 + if self._listening_called > 1: + raise RuntimeError('The start listening method can' \ + ' only be called once.') + + self._device = device + props = dbus.Interface(device, dbus.PROPERTIES_IFACE) + self._device_state = props.Get(_NM_DEVICE_IFACE, 'State') + + self._bus.add_signal_receiver(self.__device_state_changed_cb, + signal_name='StateChanged', + path=self._device.object_path, + dbus_interface=_NM_DEVICE_IFACE) + + self._bus.add_signal_receiver(self.__wireless_properties_changed_cb, + signal_name='PropertiesChanged', + path=self._device.object_path, + dbus_interface=_NM_WIRELESS_IFACE) + + def stop_listening(self): + self._bus.remove_signal_receiver(self.__device_state_changed_cb, + signal_name='StateChanged', + path=self._device.object_path, + dbus_interface=_NM_DEVICE_IFACE) + self._bus.remove_signal_receiver(self.__wireless_properties_changed_cb, + signal_name='PropertiesChanged', + path=self._device.object_path, + dbus_interface=_NM_WIRELESS_IFACE) + + def __device_state_changed_cb(self, new_state, old_state, reason): + self._device_state = new_state + self._update_state() + + def __wireless_properties_changed_cb(self, properties): + if 'ActiveAccessPoint' in properties and \ + properties['ActiveAccessPoint'] != '/': + active_ap = self._bus.get_object(_NM_SERVICE, + properties['ActiveAccessPoint']) + props = dbus.Interface(active_ap, dbus.PROPERTIES_IFACE) + props.GetAll(_NM_ACCESSPOINT_IFACE, byte_arrays=True, + reply_handler=self.__get_all_ap_props_reply_cb, + error_handler=self.__get_all_ap_props_error_cb) + + def __get_all_ap_props_reply_cb(self, properties): + if properties['Mode'] == network.NM_802_11_MODE_ADHOC and \ + 'Frequency' in properties: + frequency = properties['Frequency'] + self._current_channel = network.frequency_to_channel(frequency) + else: + self._current_channel = None + self._update_state() + + def __get_all_ap_props_error_cb(self, err): + logging.error('Error getting the access point properties: %s', err) + + def _update_state(self): + self.emit('state-changed', self._current_channel, self._device_state) + + def _have_configured_connections(self): + return len(network.get_settings().connections) > 0 + + def autoconnect(self): + """Autoconnect to an Ad-hoc network""" + if self._device_state != network.DEVICE_STATE_DISCONNECTED: + return + elif self._have_configured_connections(): + self._autoconnect_adhoc_timer() + else: + self._autoconnect_adhoc() + + def _autoconnect_adhoc_timer(self): + """Start a timer which basically looks for 30 seconds of inactivity + on the device, then does autoconnect to an Ad-hoc network. + + """ + if self._idle_source != 0: + gobject.source_remove(self._idle_source) + self._idle_source = gobject.timeout_add_seconds( \ + self._AUTOCONNECT_TIMEOUT, self.__idle_check_cb) + + def __idle_check_cb(self): + if self._device_state == network.DEVICE_STATE_DISCONNECTED: + logging.debug('Connect to Ad-hoc network due to inactivity.') + self._autoconnect_adhoc() + return False + + def _autoconnect_adhoc(self): + """First we try if there is an Ad-hoc network that is used by other + learners in the area, if not we default to channel 1. + + """ + if self._networks[self._CHANNEL_1] is not None: + self._connect(self._CHANNEL_1) + elif self._networks[self._CHANNEL_6] is not None: + self._connect(self._CHANNEL_6) + elif self._networks[self._CHANNEL_11] is not None: + self._connect(self._CHANNEL_11) + else: + self._connect(self._CHANNEL_1) + + def activate_channel(self, channel): + """Activate a sugar Ad-hoc network. + + Keyword arguments: + channel -- Channel to connect to (should be 1, 6, 11) + + """ + self._connect(channel) + + def _connect(self, channel): + name = 'Ad-hoc Network %d' % channel + connection = network.find_connection_by_ssid(name) + if connection is None: + settings = Settings() + settings.connection.id = name + settings.connection.uuid = unique_id() + settings.connection.type = '802-11-wireless' + settings.wireless.ssid = dbus.ByteArray(name) + settings.wireless.band = 'bg' + settings.wireless.channel = channel + settings.wireless.mode = 'adhoc' + settings.ip4_config = IP4Config() + settings.ip4_config.method = 'link-local' + + connection = network.add_connection(name, settings) + + obj = self._bus.get_object(_NM_SERVICE, _NM_PATH) + netmgr = dbus.Interface(obj, _NM_IFACE) + + netmgr.ActivateConnection(network.SETTINGS_SERVICE, + connection.path, + self._device.object_path, + '/', + reply_handler=self.__activate_reply_cb, + error_handler=self.__activate_error_cb) + + def deactivate_active_channel(self): + """Deactivate the current active channel.""" + obj = self._bus.get_object(_NM_SERVICE, _NM_PATH) + netmgr = dbus.Interface(obj, _NM_IFACE) + + netmgr_props = dbus.Interface(netmgr, dbus.PROPERTIES_IFACE) + netmgr_props.Get(_NM_IFACE, 'ActiveConnections', \ + reply_handler=self.__get_active_connections_reply_cb, + error_handler=self.__get_active_connections_error_cb) + + def __get_active_connections_reply_cb(self, active_connections_o): + for connection_o in active_connections_o: + obj = self._bus.get_object(_NM_IFACE, connection_o) + props = dbus.Interface(obj, dbus.PROPERTIES_IFACE) + state = props.Get(_NM_ACTIVE_CONN_IFACE, 'State') + if state == network.NM_ACTIVE_CONNECTION_STATE_ACTIVATED: + access_point_o = props.Get(_NM_ACTIVE_CONN_IFACE, + 'SpecificObject') + if access_point_o != '/': + obj = self._bus.get_object(_NM_SERVICE, _NM_PATH) + netmgr = dbus.Interface(obj, _NM_IFACE) + netmgr.DeactivateConnection(connection_o) + + def __get_active_connections_error_cb(self, err): + logging.error('Error getting the active connections: %s', err) + + def __activate_reply_cb(self, connection): + logging.debug('Ad-hoc network created: %s', connection) + + def __activate_error_cb(self, err): + logging.error('Failed to create Ad-hoc network: %s', err) + + def add_access_point(self, access_point): + """Add an access point to a network and notify the view to idicate + the member change. + + Keyword arguments: + access_point -- Access Point + + """ + if access_point.name.endswith(' 1'): + self._networks[self._CHANNEL_1] = access_point + self.emit('members-changed', self._CHANNEL_1, True) + elif access_point.name.endswith(' 6'): + self._networks[self._CHANNEL_6] = access_point + self.emit('members-changed', self._CHANNEL_6, True) + elif access_point.name.endswith('11'): + self._networks[self._CHANNEL_11] = access_point + self.emit('members-changed', self._CHANNEL_11, True) + + def is_sugar_adhoc_access_point(self, ap_object_path): + """Checks whether an access point is part of a sugar Ad-hoc network. + + Keyword arguments: + ap_object_path -- Access Point object path + + Return: Boolean + + """ + for access_point in self._networks.values(): + if access_point is not None: + if access_point.model.object_path == ap_object_path: + return True + return False + + def remove_access_point(self, ap_object_path): + """Remove an access point from a sugar Ad-hoc network. + + Keyword arguments: + ap_object_path -- Access Point object path + + """ + for channel in self._networks: + if self._networks[channel] is not None: + if self._networks[channel].model.object_path == ap_object_path: + self.emit('members-changed', channel, False) + self._networks[channel] = None + break diff --git a/src/jarabe/model/buddy.py b/src/jarabe/model/buddy.py index 5978bae..c580e68 100644 --- a/src/jarabe/model/buddy.py +++ b/src/jarabe/model/buddy.py @@ -1,4 +1,5 @@ # Copyright (C) 2006-2007 Red Hat, Inc. +# Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> # # 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 @@ -14,169 +15,220 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -from sugar.presence import presenceservice -from sugar.graphics.xocolor import XoColor +import logging + import gobject +import gconf +import dbus +from telepathy.client import Connection +from telepathy.interfaces import CONNECTION + +from sugar.graphics.xocolor import XoColor +from sugar.profile import get_profile + +from jarabe.util.telepathy import connection_watcher + -_NOT_PRESENT_COLOR = "#d5d5d5,#FFFFFF" - -class BuddyModel(gobject.GObject): - __gsignals__ = { - 'appeared': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([])), - 'disappeared': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([])), - 'nick-changed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])), - 'color-changed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])), - '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])) - } - - def __init__(self, key=None, buddy=None, nick=None): - if (key and buddy) or (not key and not buddy): - raise RuntimeError("Must specify only _one_ of key or buddy.") - - gobject.GObject.__init__(self) +CONNECTION_INTERFACE_BUDDY_INFO = 'org.laptop.Telepathy.BuddyInfo' +_owner_instance = None + + +class BaseBuddyModel(gobject.GObject): + __gtype_name__ = 'SugarBaseBuddyModel' + + def __init__(self, **kwargs): + self._key = None + self._nick = None self._color = None self._tags = None - self._ba_handler = None - self._pc_handler = None - self._dis_handler = None - self._bic_handler = None - self._cac_handler = None - - self._pservice = presenceservice.get_instance() - - self._buddy = None - - if not buddy: - self._key = key - # connect to the PS's buddy-appeared signal and - # wait for the buddy to appear - self._ba_handler = self._pservice.connect('buddy-appeared', - self._buddy_appeared_cb) - # Set color to 'inactive'/'disconnected' - self._set_color_from_string(_NOT_PRESENT_COLOR) - self._nick = nick - - self._pservice.get_buddies_async(reply_handler=self._get_buddies_cb) - else: - self._update_buddy(buddy) - - def _get_buddies_cb(self, buddy_list): - buddy = None - for iter_buddy in buddy_list: - if iter_buddy.props.key == self._key: - buddy = iter_buddy - break - - if buddy: - if self._ba_handler: - # Once we have the buddy, we no longer need to - # monitor buddy-appeared events - self._pservice.disconnect(self._ba_handler) - self._ba_handler = None - - self._update_buddy(buddy) - - def _set_color_from_string(self, color_string): - self._color = XoColor(color_string) + self._current_activity = None - def get_key(self): - return self._key + gobject.GObject.__init__(self, **kwargs) def get_nick(self): return self._nick + def set_nick(self, nick): + self._nick = nick + + nick = gobject.property(type=object, getter=get_nick, setter=set_nick) + + def get_key(self): + return self._key + + def set_key(self, key): + self._key = key + + key = gobject.property(type=object, getter=get_key, setter=set_key) + def get_color(self): return self._color + def set_color(self, color): + self._color = color + + color = gobject.property(type=object, getter=get_color, setter=set_color) + def get_tags(self): return self._tags - def get_buddy(self): - return self._buddy + tags = gobject.property(type=object, getter=get_tags) + + def get_current_activity(self): + return self._current_activity + + def set_current_activity(self, current_activity): + if self._current_activity != current_activity: + self._current_activity = current_activity + self.notify('current-activity') + + current_activity = gobject.property(type=object, + getter=get_current_activity, + setter=set_current_activity) + + def is_owner(self): + raise NotImplementedError + + +class OwnerBuddyModel(BaseBuddyModel): + __gtype_name__ = 'SugarOwnerBuddyModel' + + def __init__(self): + BaseBuddyModel.__init__(self) + + client = gconf.client_get_default() + self.props.nick = client.get_string('/desktop/sugar/user/nick') + color = client.get_string('/desktop/sugar/user/color') + self.props.color = XoColor(color) + + self.props.key = get_profile().pubkey + + self.connect('notify::nick', self.__property_changed_cb) + self.connect('notify::color', self.__property_changed_cb) + self.connect('notify::current-activity', + self.__current_activity_changed_cb) + + bus = dbus.SessionBus() + bus.add_signal_receiver( + self.__name_owner_changed_cb, + signal_name='NameOwnerChanged', + dbus_interface='org.freedesktop.DBus') + + bus_object = bus.get_object(dbus.BUS_DAEMON_NAME, dbus.BUS_DAEMON_PATH) + for service in bus_object.ListNames( + dbus_interface=dbus.BUS_DAEMON_IFACE): + if service.startswith(CONNECTION + '.'): + path = '/%s' % service.replace('.', '/') + Connection(service, path, bus, + ready_handler=self.__connection_ready_cb) + + def __connection_ready_cb(self, connection): + self._sync_properties_on_connection(connection) + + def __name_owner_changed_cb(self, name, old, new): + if name.startswith(CONNECTION + '.') and not old and new: + path = '/' + name.replace('.', '/') + Connection(name, path, ready_handler=self.__connection_ready_cb) + + def __property_changed_cb(self, buddy, pspec): + self._sync_properties() + + def __current_activity_changed_cb(self, buddy, pspec): + conn_watcher = connection_watcher.get_instance() + for connection in conn_watcher.get_connections(): + if self.props.current_activity is not None: + activity_id = self.props.current_activity.activity_id + room_handle = self.props.current_activity.room_handle + else: + activity_id = '' + room_handle = 0 + + connection[CONNECTION_INTERFACE_BUDDY_INFO].SetCurrentActivity( + activity_id, + room_handle, + reply_handler=self.__set_current_activity_cb, + error_handler=self.__error_handler_cb) + + def __set_current_activity_cb(self): + logging.debug('__set_current_activity_cb') + + def _sync_properties(self): + conn_watcher = connection_watcher.get_instance() + for connection in conn_watcher.get_connections(): + self._sync_properties_on_connection(connection) + + def _sync_properties_on_connection(self, connection): + if CONNECTION_INTERFACE_BUDDY_INFO in connection: + properties = {} + if self.props.key is not None: + properties['key'] = dbus.ByteArray(self.props.key) + if self.props.color is not None: + properties['color'] = self.props.color.to_string() + + logging.debug('calling SetProperties with %r', properties) + connection[CONNECTION_INTERFACE_BUDDY_INFO].SetProperties( + properties, + reply_handler=self.__set_properties_cb, + error_handler=self.__error_handler_cb) + + def __set_properties_cb(self): + logging.debug('__set_properties_cb') + + def __error_handler_cb(self, error): + raise RuntimeError(error) + + def __connection_added_cb(self, conn_watcher, connection): + self._sync_properties_on_connection(connection) def is_owner(self): - if not self._buddy: - return False - return self._buddy.props.owner + return True + + +def get_owner_instance(): + global _owner_instance + if _owner_instance is None: + _owner_instance = OwnerBuddyModel() + return _owner_instance + + +class BuddyModel(BaseBuddyModel): + __gtype_name__ = 'SugarBuddyModel' + + def __init__(self, **kwargs): + + self._account = None + self._contact_id = None + self._handle = None + + BaseBuddyModel.__init__(self, **kwargs) - def is_present(self): - if self._buddy: - return True + def is_owner(self): return False - def get_current_activity(self): - if self._buddy: - return self._buddy.props.current_activity - return None - - def _update_buddy(self, buddy): - if not buddy: - raise ValueError("Buddy cannot be None.") - - 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', - self._buddy_property_changed_cb) - self._bic_handler = self._buddy.connect('icon-changed', - self._buddy_icon_changed_cb) - - def _buddy_appeared_cb(self, pservice, buddy): - if self._buddy or buddy.props.key != self._key: - return - - if self._ba_handler: - # Once we have the buddy, we no longer need to - # monitor buddy-appeared events - self._pservice.disconnect(self._ba_handler) - self._ba_handler = None - - self._update_buddy(buddy) - self.emit('appeared') - - def _buddy_property_changed_cb(self, buddy, keys): - if not self._buddy: - return - if 'color' in keys: - self._set_color_from_string(self._buddy.props.color) - self.emit('color-changed', self.get_color()) - if 'current-activity' in keys: - self.emit('current-activity-changed', buddy.props.current_activity) - 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: - return - self._buddy.disconnect(self._pc_handler) - self._buddy.disconnect(self._dis_handler) - self._buddy.disconnect(self._bic_handler) - self._buddy.disconnect(self._cac_handler) - self._set_color_from_string(_NOT_PRESENT_COLOR) - self.emit('disappeared') - self._buddy = None - - def _buddy_icon_changed_cb(self, buddy): - self.emit('icon-changed') + def get_account(self): + return self._account + + def set_account(self, account): + self._account = account + + account = gobject.property(type=object, getter=get_account, + setter=set_account) + + def get_contact_id(self): + return self._contact_id + + def set_contact_id(self, contact_id): + self._contact_id = contact_id + + contact_id = gobject.property(type=object, getter=get_contact_id, + setter=set_contact_id) + + def get_handle(self): + return self._handle + + def set_handle(self, handle): + self._handle = handle + + handle = gobject.property(type=object, getter=get_handle, + setter=set_handle) diff --git a/src/jarabe/model/bundleregistry.py b/src/jarabe/model/bundleregistry.py index 86a2738..84d55c0 100644 --- a/src/jarabe/model/bundleregistry.py +++ b/src/jarabe/model/bundleregistry.py @@ -17,14 +17,15 @@ import os import logging -import traceback +import gconf import gobject import gio import simplejson from sugar.bundle.activitybundle import ActivityBundle from sugar.bundle.contentbundle import ContentBundle +from sugar.bundle.bundleversion import NormalizedVersion from jarabe.journal.journalentrybundle import JournalEntryBundle from sugar.bundle.bundle import MalformedBundleException, \ AlreadyInstalledException, RegistrationException @@ -33,16 +34,20 @@ from sugar import env from jarabe import config from jarabe.model import mimeregistry + +_instance = None + + class BundleRegistry(gobject.GObject): """Tracks the available activity bundles""" __gsignals__ = { - 'bundle-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])), + 'bundle-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])), 'bundle-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT])), 'bundle-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])) + ([gobject.TYPE_PYOBJECT])), } def __init__(self): @@ -66,6 +71,14 @@ class BundleRegistry(gobject.GObject): self._last_defaults_mtime = -1 self._favorite_bundles = {} + client = gconf.client_get_default() + self._protected_activities = client.get_list( + '/desktop/sugar/protected_activities', + gconf.VALUE_STRING) + + if self._protected_activities is None: + self._protected_activities = [] + try: self._load_favorites() except Exception: @@ -144,14 +157,16 @@ class BundleRegistry(gobject.GObject): return for bundle_id in default_activities: - max_version = -1 + max_version = '0' for bundle in self._bundles: if bundle.get_bundle_id() == bundle_id and \ - max_version < bundle.get_activity_version(): + NormalizedVersion(max_version) < \ + NormalizedVersion(bundle.get_activity_version()): max_version = bundle.get_activity_version() key = self._get_favorite_key(bundle_id, max_version) - if max_version > -1 and key not in self._favorite_bundles: + if NormalizedVersion(max_version) > NormalizedVersion('0') and \ + key not in self._favorite_bundles: self._favorite_bundles[key] = None logging.debug('After merging: %r', self._favorite_bundles) @@ -164,7 +179,7 @@ class BundleRegistry(gobject.GObject): if bundle.get_bundle_id() == bundle_id: return bundle return None - + def __iter__(self): return self._bundles.__iter__() @@ -185,19 +200,18 @@ class BundleRegistry(gobject.GObject): if os.path.isdir(bundle_dir): bundles[bundle_dir] = os.stat(bundle_dir).st_mtime except Exception: - logging.error('Error while processing installed activity ' \ - 'bundle %s:\n%s' % \ - (bundle_dir, traceback.format_exc())) + logging.exception('Error while processing installed activity' + ' bundle %s:', bundle_dir) bundle_dirs = bundles.keys() bundle_dirs.sort(lambda d1, d2: cmp(bundles[d1], bundles[d2])) for folder in bundle_dirs: try: self._add_bundle(folder) - except Exception, e: - logging.error('Error while processing installed activity ' \ - 'bundle %s:\n%s' % \ - (folder, traceback.format_exc())) + except: + # pylint: disable=W0702 + logging.exception('Error while processing installed activity' + ' bundle %s:', folder) def add_bundle(self, bundle_path, install_mime_type=False): bundle = self._add_bundle(bundle_path, install_mime_type) @@ -224,8 +238,8 @@ class BundleRegistry(gobject.GObject): installed = self.get_bundle(bundle_id) if installed is not None: - if installed.get_activity_version() >= \ - bundle.get_activity_version(): + if NormalizedVersion(installed.get_activity_version()) >= \ + NormalizedVersion(bundle.get_activity_version()): logging.debug('Skip old version for %s', bundle_id) return None else: @@ -251,8 +265,7 @@ class BundleRegistry(gobject.GObject): default_bundle = None for bundle in self._bundles: - if bundle.get_mime_types() and mime_type in bundle.get_mime_types(): - + if mime_type in (bundle.get_mime_types() or []): if bundle.get_bundle_id() == default_bundle_id: default_bundle = bundle elif self.get_default_for_type(mime_type) == \ @@ -267,10 +280,7 @@ class BundleRegistry(gobject.GObject): return result def get_default_for_type(self, mime_type): - if self._mime_defaults.has_key(mime_type): - return self._mime_defaults[mime_type] - else: - return None + return self._mime_defaults.get(mime_type) def _find_bundle(self, bundle_id, version): for bundle in self._bundles: @@ -302,10 +312,14 @@ class BundleRegistry(gobject.GObject): key = self._get_favorite_key(bundle_id, version) return key in self._favorite_bundles + def is_activity_protected(self, bundle_id): + return bundle_id in self._protected_activities + def set_bundle_position(self, bundle_id, version, x, y): key = self._get_favorite_key(bundle_id, version) if key not in self._favorite_bundles: - raise ValueError('Bundle %s %s not favorite' % (bundle_id, version)) + raise ValueError('Bundle %s %s not favorite' % + (bundle_id, version)) if self._favorite_bundles[key] is None: self._favorite_bundles[key] = {} @@ -346,19 +360,22 @@ class BundleRegistry(gobject.GObject): for installed_bundle in self._bundles: if bundle.get_bundle_id() == installed_bundle.get_bundle_id() and \ - bundle.get_activity_version() == \ - installed_bundle.get_activity_version(): + NormalizedVersion(bundle.get_activity_version()) <= \ + NormalizedVersion(installed_bundle.get_activity_version()): return True return False - def install(self, bundle, uid=None): + def install(self, bundle, uid=None, force_downgrade=False): activities_path = env.get_user_activities_path() for installed_bundle in self._bundles: if bundle.get_bundle_id() == installed_bundle.get_bundle_id() and \ - bundle.get_activity_version() <= \ - installed_bundle.get_activity_version(): - raise AlreadyInstalledException + NormalizedVersion(bundle.get_activity_version()) <= \ + NormalizedVersion(installed_bundle.get_activity_version()): + if not force_downgrade: + raise AlreadyInstalledException + else: + self.uninstall(installed_bundle, force=True) elif bundle.get_bundle_id() == installed_bundle.get_bundle_id(): self.uninstall(installed_bundle, force=True) @@ -376,7 +393,7 @@ class BundleRegistry(gobject.GObject): elif not self.add_bundle(install_path): raise RegistrationException - def uninstall(self, bundle, force=False): + def uninstall(self, bundle, force=False, delete_profile=False): # TODO treat ContentBundle in special way # needs rethinking while fixing ContentBundle support if isinstance(bundle, ContentBundle) or \ @@ -399,7 +416,7 @@ class BundleRegistry(gobject.GObject): install_path = act.get_path() - bundle.uninstall(install_path, force) + bundle.uninstall(install_path, force, delete_profile) if not self.remove_bundle(install_path): raise RegistrationException @@ -415,20 +432,17 @@ class BundleRegistry(gobject.GObject): try: self.uninstall(bundle, force=True) except Exception: - logging.error('Uninstall failed, still trying to install ' \ - 'newer bundle:\n' + \ - traceback.format_exc()) + logging.exception('Uninstall failed, still trying to install' + ' newer bundle:') else: - logging.warning('Unable to uninstall system activity, ' \ + logging.warning('Unable to uninstall system activity, ' 'installing upgraded version in user activities') self.install(bundle) -_instance = None def get_registry(): global _instance if not _instance: _instance = BundleRegistry() return _instance - diff --git a/src/jarabe/model/filetransfer.py b/src/jarabe/model/filetransfer.py index 46b246d..710c3a4 100644 --- a/src/jarabe/model/filetransfer.py +++ b/src/jarabe/model/filetransfer.py @@ -31,6 +31,8 @@ from sugar.presence import presenceservice from sugar import dispatch from jarabe.util.telepathy import connection_watcher +from jarabe.model import neighborhood + FT_STATE_NONE = 0 FT_STATE_PENDING = 1 @@ -51,14 +53,18 @@ FT_REASON_REMOTE_ERROR = 6 CHANNEL_TYPE_FILE_TRANSFER = \ 'org.freedesktop.Telepathy.Channel.Type.FileTransfer' +new_file_transfer = dispatch.Signal() + + # TODO Move to use splice_async() in Sugar 0.88 class StreamSplicer(gobject.GObject): - _CHUNK_SIZE = 10240 # 10K + _CHUNK_SIZE = 10240 # 10K __gsignals__ = { 'finished': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), } + def __init__(self, input_stream, output_stream): gobject.GObject.__init__(self) @@ -104,6 +110,7 @@ class StreamSplicer(gobject.GObject): gobject.PRIORITY_LOW, user_data=data) + class BaseFileTransfer(gobject.GObject): def __init__(self, connection): @@ -140,11 +147,7 @@ class BaseFileTransfer(gobject.GObject): self.mime_type = props['ContentType'] handle = channel_properties.Get(CHANNEL, 'TargetHandle') - presence_service = presenceservice.get_instance() - self.buddy = presence_service.get_buddy_by_telepathy_handle( - self._connection.service_name, - self._connection.object_path, - handle) + self.buddy = neighborhood.get_model().get_buddy_by_handle(handle) def __transferred_bytes_changed_cb(self, transferred_bytes): logging.debug('__transferred_bytes_changed_cb %r', transferred_bytes) @@ -179,6 +182,7 @@ class BaseFileTransfer(gobject.GObject): def cancel(self): self.channel[CHANNEL].Close() + class IncomingFileTransfer(BaseFileTransfer): def __init__(self, connection, object_path, props): BaseFileTransfer.__init__(self, connection) @@ -223,6 +227,7 @@ class IncomingFileTransfer(BaseFileTransfer): self._splicer = StreamSplicer(input_stream, output_stream) self._splicer.start() + class OutgoingFileTransfer(BaseFileTransfer): def __init__(self, buddy, file_name, title, description, mime_type): @@ -240,20 +245,18 @@ class OutgoingFileTransfer(BaseFileTransfer): self._splicer = None self._output_stream = None - self.buddy = buddy.get_buddy() + self.buddy = buddy self.title = title self.file_size = os.stat(file_name).st_size self.description = description self.mime_type = mime_type def __connection_ready_cb(self, connection): - handle = self._get_buddy_handle() - requests = connection[CONNECTION_INTERFACE_REQUESTS] object_path, properties_ = requests.CreateChannel({ CHANNEL + '.ChannelType': CHANNEL_TYPE_FILE_TRANSFER, CHANNEL + '.TargetHandleType': CONNECTION_HANDLE_TYPE_CONTACT, - CHANNEL + '.TargetHandle': handle, + CHANNEL + '.TargetHandle': self.buddy.handle, CHANNEL_TYPE_FILE_TRANSFER + '.ContentType': self.mime_type, CHANNEL_TYPE_FILE_TRANSFER + '.Filename': self.title, CHANNEL_TYPE_FILE_TRANSFER + '.Size': self.file_size, @@ -267,21 +270,6 @@ class OutgoingFileTransfer(BaseFileTransfer): SOCKET_ADDRESS_TYPE_UNIX, SOCKET_ACCESS_CONTROL_LOCALHOST, '', byte_arrays=True) - def _get_buddy_handle(self): - object_path = self.buddy.object_path() - - bus = dbus.SessionBus() - remote_object = bus.get_object('org.laptop.Sugar.Presence', object_path) - ps_buddy = dbus.Interface(remote_object, - 'org.laptop.Sugar.Presence.Buddy') - - handles = ps_buddy.GetTelepathyHandles() - 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) if self.props.state == FT_STATE_OPEN: @@ -303,6 +291,7 @@ class OutgoingFileTransfer(BaseFileTransfer): def cancel(self): self.channel[CHANNEL].Close() + def _new_channels_cb(connection, channels): for object_path, props in channels: if props[CHANNEL + '.ChannelType'] == CHANNEL_TYPE_FILE_TRANSFER and \ @@ -314,35 +303,39 @@ def _new_channels_cb(connection, channels): object_path, props) new_file_transfer.send(None, file_transfer=incoming_file_transfer) + def _monitor_connection(connection): logging.debug('connection added %r', connection) connection[CONNECTION_INTERFACE_REQUESTS].connect_to_signal('NewChannels', lambda channels: _new_channels_cb(connection, channels)) -def _connection_addded_cb(conn_watcher, connection): + +def _connection_added_cb(conn_watcher, connection): _monitor_connection(connection) + def _connection_removed_cb(conn_watcher, connection): logging.debug('connection removed %r', connection) -_conn_watcher = None def init(): - global _conn_watcher - _conn_watcher = connection_watcher.ConnectionWatcher() - _conn_watcher.connect('connection-added', _connection_addded_cb) - _conn_watcher.connect('connection-removed', _connection_removed_cb) + conn_watcher = connection_watcher.get_instance() + conn_watcher.connect('connection-added', _connection_added_cb) + conn_watcher.connect('connection-removed', _connection_removed_cb) - for connection in _conn_watcher.get_connections(): + for connection in conn_watcher.get_connections(): _monitor_connection(connection) + def start_transfer(buddy, file_name, title, description, mime_type): outgoing_file_transfer = OutgoingFileTransfer(buddy, file_name, title, description, mime_type) new_file_transfer.send(None, file_transfer=outgoing_file_transfer) + def file_transfer_available(): - for connection in _conn_watcher.get_connections(): + conn_watcher = connection_watcher.get_instance() + for connection in conn_watcher.get_connections(): properties_iface = connection[dbus.PROPERTIES_IFACE] properties = properties_iface.GetAll(CONNECTION_INTERFACE_REQUESTS) @@ -359,18 +352,17 @@ def file_transfer_available(): return False -new_file_transfer = dispatch.Signal() if __name__ == '__main__': import tempfile - input_stream = gio.File('/home/tomeu/isos/Soas2-200904031934.iso').read() - output_stream = gio.File(tempfile.mkstemp()[1]).append_to() + test_file_name = '/home/tomeu/isos/Soas2-200904031934.iso' + test_input_stream = gio.File(test_file_name).read() + test_output_stream = gio.File(tempfile.mkstemp()[1]).append_to() # TODO: Use splice_async when it gets implemented - splicer = StreamSplicer(input_stream, output_stream) + splicer = StreamSplicer(test_input_stream, test_output_stream) splicer.start() loop = gobject.MainLoop() loop.run() - diff --git a/src/jarabe/model/friends.py b/src/jarabe/model/friends.py index b7bf7f1..192f683 100644 --- a/src/jarabe/model/friends.py +++ b/src/jarabe/model/friends.py @@ -21,15 +21,83 @@ from ConfigParser import ConfigParser import gobject import dbus -from jarabe.model.buddy import BuddyModel from sugar import env +from sugar.graphics.xocolor import XoColor + +from jarabe.model.buddy import BuddyModel +from jarabe.model import neighborhood + + +_model = None + + +class FriendBuddyModel(BuddyModel): + __gtype_name__ = 'SugarFriendBuddyModel' + + _NOT_PRESENT_COLOR = '#D5D5D5,#FFFFFF' + + def __init__(self, nick, key): + self._online_buddy = None + + BuddyModel.__init__(self, nick=nick, key=key) + + neighborhood_model = neighborhood.get_model() + neighborhood_model.connect('buddy-added', self.__buddy_added_cb) + neighborhood_model.connect('buddy-removed', self.__buddy_removed_cb) + + buddy = neighborhood_model.get_buddy_by_key(key) + if buddy is not None: + self._set_online_buddy(buddy) + + def __buddy_added_cb(self, model_, buddy): + if buddy.key != self.key: + return + self._set_online_buddy(buddy) + + def _set_online_buddy(self, buddy): + self._online_buddy = buddy + self._online_buddy.connect('notify::color', self.__notify_color_cb) + self.notify('color') + self.notify('present') + + def __buddy_removed_cb(self, model_, buddy): + if buddy.key != self.key: + return + self._online_buddy = None + self.notify('color') + self.notify('present') + + def __notify_color_cb(self, buddy, pspec): + self.notify('color') + + def is_present(self): + return self._online_buddy is not None + + present = gobject.property(type=bool, default=False, getter=is_present) + + def get_color(self): + if self._online_buddy is not None: + return self._online_buddy.color + else: + return XoColor(FriendBuddyModel._NOT_PRESENT_COLOR) + + color = gobject.property(type=object, getter=get_color) + + def get_handle(self): + if self._online_buddy is not None: + return self._online_buddy.handle + else: + return None + + handle = gobject.property(type=object, getter=get_handle) + class Friends(gobject.GObject): __gsignals__ = { - 'friend-added': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([object])), - 'friend-removed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([str])) + 'friend-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object])), + 'friend-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([str])), } def __init__(self): @@ -41,7 +109,7 @@ class Friends(gobject.GObject): self.load() def has_buddy(self, buddy): - return self._friends.has_key(buddy.get_key()) + return buddy.get_key() in self._friends def add_friend(self, buddy_info): self._friends[buddy_info.get_key()] = buddy_info @@ -49,6 +117,7 @@ class Friends(gobject.GObject): def make_friend(self, buddy): if not self.has_buddy(buddy): + buddy = FriendBuddyModel(key=buddy.key, nick=buddy.nick) self.add_friend(buddy) self.save() @@ -70,7 +139,7 @@ class Friends(gobject.GObject): # HACK: don't screw up on old friends files if len(key) < 20: continue - buddy = BuddyModel(key=key, nick=cp.get(key, 'nick')) + buddy = FriendBuddyModel(key=key, nick=cp.get(key, 'nick')) self.add_friend(buddy) except Exception: logging.exception('Error parsing friends file') @@ -82,7 +151,6 @@ class Friends(gobject.GObject): section = friend.get_key() cp.add_section(section) cp.set(section, 'nick', friend.get_nick()) - cp.set(section, 'color', friend.get_color().to_string()) fileobject = open(self._path, 'w') cp.write(fileobject) @@ -113,7 +181,6 @@ class Friends(gobject.GObject): reply_handler=friends_synced, error_handler=friends_synced_error) -_model = None def get_model(): global _model diff --git a/src/jarabe/model/invites.py b/src/jarabe/model/invites.py index c918308..d2a2e0c 100644 --- a/src/jarabe/model/invites.py +++ b/src/jarabe/model/invites.py @@ -1,4 +1,5 @@ # Copyright (C) 2006-2007 Red Hat, Inc. +# Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> # # 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 @@ -14,110 +15,227 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -import gobject -from sugar.presence import presenceservice - - -class BaseInvite: - """Invitation to shared activity or private 1-1 Telepathy channel""" - def __init__(self, bundle_id): - """init for BaseInvite. +import logging +from functools import partial - bundle_id: string, e.g. 'org.laptop.Chat' - """ - self._bundle_id = bundle_id +import gobject +import dbus +from telepathy.interfaces import CHANNEL, \ + CHANNEL_DISPATCHER, \ + CHANNEL_DISPATCH_OPERATION, \ + CHANNEL_TYPE_CONTACT_LIST, \ + CHANNEL_TYPE_DBUS_TUBE, \ + CHANNEL_TYPE_STREAMED_MEDIA, \ + CHANNEL_TYPE_STREAM_TUBE, \ + CHANNEL_TYPE_TEXT, \ + CLIENT +from telepathy.constants import HANDLE_TYPE_ROOM - def get_bundle_id(self): - return self._bundle_id +from sugar.graphics.xocolor import XoColor +from jarabe.model import telepathyclient +from jarabe.model import bundleregistry +from jarabe.model import neighborhood +from jarabe.journal import misc -class ActivityInvite(BaseInvite): - """Invitation to a shared activity.""" - def __init__(self, bundle_id, activity_id): - BaseInvite.__init__(self, bundle_id) - self._activity_id = activity_id - def get_activity_id(self): - return self._activity_id +CONNECTION_INTERFACE_ACTIVITY_PROPERTIES = \ + 'org.laptop.Telepathy.ActivityProperties' +_instance = None -class PrivateInvite(BaseInvite): - """Invitation to a private 1-1 Telepathy channel. - This includes text chat or streaming media. - """ - def __init__(self, bundle_id, private_channel): - """init for PrivateInvite. +class ActivityInvite(object): + """Invitation to a shared activity.""" + def __init__(self, dispatch_operation_path, handle, handler, + activity_properties): + self.dispatch_operation_path = dispatch_operation_path + self._handle = handle + self._handler = handler - bundle_id: string, e.g. 'org.laptop.Chat' - private_channel: string containing simplejson dump of Telepathy - bus, connection and channel - """ - BaseInvite.__init__(self, bundle_id) - self._private_channel = private_channel + if activity_properties is not None: + self._activity_properties = activity_properties + else: + self._activity_properties = {} - def get_private_channel(self): - """Telepathy channel info from private invitation""" - return self._private_channel + def get_bundle_id(self): + if CLIENT in self._handler: + return self._handler[len(CLIENT + '.'):] + else: + return None + + def get_color(self): + color = self._activity_properties.get('color', None) + return XoColor(color) + + def join(self): + logging.error('ActivityInvite.join handler %r', self._handler) + + registry = bundleregistry.get_registry() + bundle_id = self.get_bundle_id() + bundle = registry.get_bundle(bundle_id) + if bundle is None: + self._call_handle_with() + else: + bus = dbus.SessionBus() + bus.add_signal_receiver(self.__name_owner_changed_cb, + 'NameOwnerChanged', + 'org.freedesktop.DBus', + arg0=self._handler) + + model = neighborhood.get_model() + activity_id = model.get_activity_by_room(self._handle).activity_id + misc.launch(bundle, color=self.get_color(), invited=True, + activity_id=activity_id) + + def __name_owner_changed_cb(self, name, old_owner, new_owner): + logging.debug('ActivityInvite.__name_owner_changed_cb %r %r %r', name, + new_owner, old_owner) + if name == self._handler and new_owner and not old_owner: + self._call_handle_with() + + def _call_handle_with(self): + bus = dbus.Bus() + obj = bus.get_object(CHANNEL_DISPATCHER, self.dispatch_operation_path) + dispatch_operation = dbus.Interface(obj, CHANNEL_DISPATCH_OPERATION) + dispatch_operation.HandleWith(self._handler, + reply_handler=self.__handle_with_reply_cb, + error_handler=self.__handle_with_reply_cb) + + def __handle_with_reply_cb(self, error=None): + if error is not None: + raise error + else: + logging.debug('__handle_with_reply_cb') class Invites(gobject.GObject): __gsignals__ = { - 'invite-added': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([object])), - 'invite-removed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([object])) + 'invite-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object])), + 'invite-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object])), } def __init__(self): gobject.GObject.__init__(self) - self._dict = {} - - ps = presenceservice.get_instance() - owner = ps.get_owner() - owner.connect('joined-activity', self._owner_joined_cb) - - def add_invite(self, bundle_id, activity_id): - if activity_id in self._dict: - # there is no point to add more than one time - # an invite for the same activity + self._dispatch_operations = {} + + client_handler = telepathyclient.get_instance() + client_handler.got_dispatch_operation.connect( + self.__got_dispatch_operation_cb) + + def __got_dispatch_operation_cb(self, **kwargs): + logging.debug('__got_dispatch_operation_cb') + dispatch_operation_path = kwargs['dispatch_operation_path'] + channel_path, channel_properties = kwargs['channels'][0] + properties = kwargs['properties'] + channel_type = channel_properties[CHANNEL + '.ChannelType'] + handle_type = channel_properties[CHANNEL + '.TargetHandleType'] + handle = channel_properties[CHANNEL + '.TargetHandle'] + + if handle_type == HANDLE_TYPE_ROOM and \ + channel_type == CHANNEL_TYPE_TEXT: + logging.debug('May be an activity, checking its properties') + connection_path = properties[CHANNEL_DISPATCH_OPERATION + + '.Connection'] + connection_name = connection_path.replace('/', '.')[1:] + + bus = dbus.Bus() + connection = bus.get_object(connection_name, connection_path) + connection.GetProperties( + channel_properties[CHANNEL + '.TargetHandle'], + dbus_interface=CONNECTION_INTERFACE_ACTIVITY_PROPERTIES, + reply_handler=partial(self.__get_properties_cb, + handle, + dispatch_operation_path), + error_handler=partial(self.__error_handler_cb, + handle, + channel_properties, + dispatch_operation_path)) + else: + self._dispatch_non_sugar_invitation(channel_path, + channel_properties, + dispatch_operation_path) + + def __get_properties_cb(self, handle, dispatch_operation_path, properties): + logging.debug('__get_properties_cb %r', properties) + handler = '%s.%s' % (CLIENT, properties['type']) + self._add_invite(dispatch_operation_path, handle, handler, properties) + + def __error_handler_cb(self, handle, channel_properties, + dispatch_operation_path, error): + logging.debug('__error_handler_cb %r', error) + exception_name = 'org.freedesktop.Telepathy.Error.NotAvailable' + if error.get_dbus_name() == exception_name: + self._dispatch_non_sugar_invitation(handle, + channel_properties, + dispatch_operation_path) + else: + raise error + + def _dispatch_non_sugar_invitation(self, handle, channel_properties, + dispatch_operation_path): + handler = None + channel_type = channel_properties[CHANNEL + '.ChannelType'] + if channel_type == CHANNEL_TYPE_CONTACT_LIST: + self._handle_with(dispatch_operation_path, CLIENT + '.Sugar') + elif channel_type == CHANNEL_TYPE_TEXT: + handler = CLIENT + '.org.laptop.Chat' + elif channel_type == CHANNEL_TYPE_STREAMED_MEDIA: + handler = CLIENT + '.org.laptop.VideoChat' + elif channel_type == CHANNEL_TYPE_DBUS_TUBE: + handler = channel_properties[CHANNEL_TYPE_DBUS_TUBE + + '.ServiceName'] + elif channel_type == CHANNEL_TYPE_STREAM_TUBE: + handler = channel_properties[CHANNEL_TYPE_STREAM_TUBE + '.Service'] + else: + self._handle_with(dispatch_operation_path, '') + + if handler is not None: + logging.debug('Adding an invite from a non-Sugar client') + self._add_invite(dispatch_operation_path, handle, handler) + + def _handle_with(self, dispatch_operation_path, handler): + logging.debug('_handle_with %r %r', dispatch_operation_path, handler) + bus = dbus.Bus() + obj = bus.get_object(CHANNEL_DISPATCHER, dispatch_operation_path) + dispatch_operation = dbus.Interface(obj, CHANNEL_DISPATCH_OPERATION) + dispatch_operation.HandleWith(handler, + reply_handler=self.__handle_with_reply_cb, + error_handler=self.__handle_with_reply_cb) + + def __handle_with_reply_cb(self, error=None): + if error is not None: + logging.error('__handle_with_reply_cb %r', error) + else: + logging.debug('__handle_with_reply_cb') + + def _add_invite(self, dispatch_operation_path, handle, handler, + activity_properties=None): + logging.debug('_add_invite %r %r %r', dispatch_operation_path, handle, + handler) + if dispatch_operation_path in self._dispatch_operations: + # there is no point to have more than one invite for the same + # dispatch operation return - invite = ActivityInvite(bundle_id, activity_id) - self._dict[activity_id] = invite - self.emit('invite-added', invite) - - def add_private_invite(self, private_channel, bundle_id): - if private_channel in self._dict: - # there is no point to add more than one invite for the - # same incoming connection - return - - invite = PrivateInvite(bundle_id, private_channel) - self._dict[private_channel] = invite + invite = ActivityInvite(dispatch_operation_path, handle, handler, + activity_properties) + self._dispatch_operations[dispatch_operation_path] = invite self.emit('invite-added', invite) def remove_invite(self, invite): - del self._dict[invite.get_activity_id()] + del self._dispatch_operations[invite.dispatch_operation_path] self.emit('invite-removed', invite) - def remove_private_invite(self, invite): - del self._dict[invite.get_private_channel()] - self.emit('invite-removed', invite) - - def remove_activity(self, activity_id): - invite = self._dict.get(activity_id) - if invite is not None: - self.remove_invite(invite) - - def remove_private_channel(self, private_channel): - invite = self._dict.get(private_channel) - if invite is not None: - self.remove_private_invite(invite) + def __iter__(self): + return self._dispatch_operations.values().__iter__() - def _owner_joined_cb(self, owner, activity): - self.remove_activity(activity.props.id) - def __iter__(self): - return self._dict.values().__iter__() +def get_instance(): + global _instance + if not _instance: + _instance = Invites() + return _instance diff --git a/src/jarabe/model/mimeregistry.py b/src/jarabe/model/mimeregistry.py index 537f6f3..7fb5bcf 100644 --- a/src/jarabe/model/mimeregistry.py +++ b/src/jarabe/model/mimeregistry.py @@ -21,6 +21,7 @@ import gconf _DEFAULTS_KEY = '/desktop/sugar/journal/defaults' _GCONF_INVALID_CHARS = re.compile('[^a-zA-Z0-9-_/.]') + _instance = None diff --git a/src/jarabe/model/neighborhood.py b/src/jarabe/model/neighborhood.py index 53e5581..ca4c5bf 100644 --- a/src/jarabe/model/neighborhood.py +++ b/src/jarabe/model/neighborhood.py @@ -1,4 +1,4 @@ -# Copyright (C) 2006-2007 Red Hat, Inc. +# Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> # # 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 @@ -14,257 +14,992 @@ # 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 functools import partial +from hashlib import sha1 + import gobject import gconf -import logging +import dbus +from dbus import PROPERTIES_IFACE +from telepathy.interfaces import ACCOUNT, \ + ACCOUNT_MANAGER, \ + CHANNEL, \ + CHANNEL_INTERFACE_GROUP, \ + CHANNEL_TYPE_CONTACT_LIST, \ + CHANNEL_TYPE_FILE_TRANSFER, \ + CLIENT, \ + CONNECTION, \ + CONNECTION_INTERFACE_ALIASING, \ + CONNECTION_INTERFACE_CONTACTS, \ + CONNECTION_INTERFACE_CONTACT_CAPABILITIES, \ + CONNECTION_INTERFACE_REQUESTS, \ + CONNECTION_INTERFACE_SIMPLE_PRESENCE +from telepathy.constants import HANDLE_TYPE_CONTACT, \ + HANDLE_TYPE_LIST, \ + CONNECTION_PRESENCE_TYPE_OFFLINE, \ + CONNECTION_STATUS_CONNECTED, \ + CONNECTION_STATUS_DISCONNECTED +from telepathy.client import Connection, Channel from sugar.graphics.xocolor import XoColor -from sugar.presence import presenceservice -from sugar import activity +from sugar.profile import get_profile -from jarabe.model.buddy import BuddyModel +from jarabe.model.buddy import BuddyModel, get_owner_instance from jarabe.model import bundleregistry -from jarabe.util.telepathy import connection_watcher -from dbus import PROPERTIES_IFACE -from telepathy.interfaces import CONNECTION_INTERFACE_REQUESTS -from telepathy.interfaces import CHANNEL_INTERFACE -from telepathy.client import Channel -CONN_INTERFACE_GADGET = 'org.laptop.Telepathy.Gadget' -CHAN_INTERFACE_VIEW = 'org.laptop.Telepathy.Channel.Interface.View' -CHAN_INTERFACE_BUDBY_VIEW = 'org.laptop.Telepathy.Channel.Type.BuddyView' -CHAN_INTERFACE_ACTIVITY_VIEW = 'org.laptop.Telepathy.Channel.Type.ActivityView' +ACCOUNT_MANAGER_SERVICE = 'org.freedesktop.Telepathy.AccountManager' +ACCOUNT_MANAGER_PATH = '/org/freedesktop/Telepathy/AccountManager' +CHANNEL_DISPATCHER_SERVICE = 'org.freedesktop.Telepathy.ChannelDispatcher' +CHANNEL_DISPATCHER_PATH = '/org/freedesktop/Telepathy/ChannelDispatcher' +SUGAR_CLIENT_SERVICE = 'org.freedesktop.Telepathy.Client.Sugar' +SUGAR_CLIENT_PATH = '/org/freedesktop/Telepathy/Client/Sugar' + +CONNECTION_INTERFACE_BUDDY_INFO = 'org.laptop.Telepathy.BuddyInfo' +CONNECTION_INTERFACE_ACTIVITY_PROPERTIES = \ + 'org.laptop.Telepathy.ActivityProperties' -NB_RANDOM_BUDDIES = 20 -NB_RANDOM_ACTIVITIES = 40 +_QUERY_DBUS_TIMEOUT = 200 +""" +Time in seconds to wait when querying contact properties. Some jabber servers +will be very slow in returning these queries, so just be patient. +""" -class ActivityModel: - def __init__(self, act, bundle): - self.activity = act - self.bundle = bundle +_model = None + + +class ActivityModel(gobject.GObject): + __gsignals__ = { + 'current-buddy-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object])), + 'current-buddy-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object])), + 'buddy-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object])), + 'buddy-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object])), + } - def get_id(self): - return self.activity.props.id + def __init__(self, activity_id, room_handle): + gobject.GObject.__init__(self) - def get_icon_name(self): - return self.bundle.get_icon() + self.activity_id = activity_id + self.room_handle = room_handle + self._bundle = None + self._color = None + self._private = True + self._name = None + self._current_buddies = [] + self._buddies = [] def get_color(self): - return XoColor(self.activity.props.color) + return self._color + + def set_color(self, color): + self._color = color + + color = gobject.property(type=object, getter=get_color, setter=set_color) + + def get_bundle(self): + return self._bundle + + def set_bundle(self, bundle): + self._bundle = bundle + + bundle = gobject.property(type=object, getter=get_bundle, + setter=set_bundle) + + def get_name(self): + return self._name + + def set_name(self, name): + self._name = name + + name = gobject.property(type=object, getter=get_name, setter=set_name) + + def is_private(self): + return self._private + + def set_private(self, private): + self._private = private + + private = gobject.property(type=object, getter=is_private, + setter=set_private) + + def get_buddies(self): + return self._buddies + + def add_buddy(self, buddy): + self._buddies.append(buddy) + self.notify('buddies') + self.emit('buddy-added', buddy) + + def remove_buddy(self, buddy): + self._buddies.remove(buddy) + self.notify('buddies') + self.emit('buddy-removed', buddy) + + buddies = gobject.property(type=object, getter=get_buddies) + + def get_current_buddies(self): + return self._current_buddies + + def add_current_buddy(self, buddy): + self._current_buddies.append(buddy) + self.notify('current-buddies') + self.emit('current-buddy-added', buddy) + + def remove_current_buddy(self, buddy): + self._current_buddies.remove(buddy) + self.notify('current-buddies') + self.emit('current-buddy-removed', buddy) + + current_buddies = gobject.property(type=object, getter=get_current_buddies) + + +class _Account(gobject.GObject): + __gsignals__ = { + 'activity-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object, object])), + 'activity-updated': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object, object])), + 'activity-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object])), + 'buddy-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object, object, object])), + 'buddy-updated': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object, object])), + 'buddy-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object])), + 'buddy-joined-activity': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object, object])), + 'buddy-left-activity': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object, object])), + 'current-activity-updated': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, ([object, object])), + 'connected': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), + 'disconnected': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), + } + + def __init__(self, account_path): + gobject.GObject.__init__(self) + + self.object_path = account_path + + self._connection = None + self._buddy_handles = {} + self._activity_handles = {} + self._self_handle = None + + self._buddies_per_activity = {} + self._activities_per_buddy = {} + + self._start_listening() + + def _start_listening(self): + bus = dbus.Bus() + obj = bus.get_object(ACCOUNT_MANAGER_SERVICE, self.object_path) + obj.Get(ACCOUNT, 'Connection', + reply_handler=self.__got_connection_cb, + error_handler=partial(self.__error_handler_cb, + 'Account.GetConnection')) + obj.connect_to_signal( + 'AccountPropertyChanged', self.__account_property_changed_cb) + + def __error_handler_cb(self, function_name, error): + raise RuntimeError('Error when calling %s: %s' % (function_name, + error)) + + def __got_connection_cb(self, connection_path): + logging.debug('_Account.__got_connection_cb %r', connection_path) + + if connection_path == '/': + self._check_registration_error() + return + + self._prepare_connection(connection_path) + + def _check_registration_error(self): + """ + See if a previous connection attempt failed and we need to unset + the register flag. + """ + bus = dbus.Bus() + obj = bus.get_object(ACCOUNT_MANAGER_SERVICE, self.object_path) + obj.Get(ACCOUNT, 'ConnectionError', + reply_handler=self.__got_connection_error_cb, + error_handler=partial(self.__error_handler_cb, + 'Account.GetConnectionError')) + + def __got_connection_error_cb(self, error): + logging.debug('_Account.__got_connection_error_cb %r', error) + if error == 'org.freedesktop.Telepathy.Error.RegistrationExists': + bus = dbus.Bus() + obj = bus.get_object(ACCOUNT_MANAGER_SERVICE, self.object_path) + obj.UpdateParameters({'register': False}, [], + dbus_interface=ACCOUNT) + + def __account_property_changed_cb(self, properties): + logging.debug('_Account.__account_property_changed_cb %r %r %r', + self.object_path, properties.get('Connection', None), + self._connection) + if 'Connection' not in properties: + return + if properties['Connection'] == '/': + self._check_registration_error() + self._connection = None + elif self._connection is None: + self._prepare_connection(properties['Connection']) + + def _prepare_connection(self, connection_path): + connection_name = connection_path.replace('/', '.')[1:] + + self._connection = Connection(connection_name, connection_path, + ready_handler=self.__connection_ready_cb) + + def __connection_ready_cb(self, connection): + logging.debug('_Account.__connection_ready_cb %r', + connection.object_path) + connection.connect_to_signal('StatusChanged', + self.__status_changed_cb) + + connection[PROPERTIES_IFACE].Get(CONNECTION, + 'Status', + reply_handler=self.__get_status_cb, + error_handler=partial(self.__error_handler_cb, + 'Connection.GetStatus')) + + def __get_status_cb(self, status): + logging.debug('_Account.__get_status_cb %r %r', + self._connection.object_path, status) + self._update_status(status) + + def __status_changed_cb(self, status, reason): + logging.debug('_Account.__status_changed_cb %r %r', status, reason) + self._update_status(status) + + def _update_status(self, status): + if status == CONNECTION_STATUS_CONNECTED: + self._connection[PROPERTIES_IFACE].Get(CONNECTION, + 'SelfHandle', + reply_handler=self.__get_self_handle_cb, + error_handler=partial(self.__error_handler_cb, + 'Connection.GetSelfHandle')) + self.emit('connected') + else: + for contact_handle, contact_id in self._buddy_handles.items(): + if contact_id is not None: + self.emit('buddy-removed', contact_id) + + for room_handle, activity_id in self._activity_handles.items(): + self.emit('activity-removed', activity_id) + + self._buddy_handles = {} + self._activity_handles = {} + self._buddies_per_activity = {} + self._activities_per_buddy = {} + + self.emit('disconnected') + + if status == CONNECTION_STATUS_DISCONNECTED: + self._connection = None + + def __get_self_handle_cb(self, self_handle): + self._self_handle = self_handle + + if CONNECTION_INTERFACE_CONTACT_CAPABILITIES in self._connection: + interface = CONNECTION_INTERFACE_CONTACT_CAPABILITIES + connection = self._connection[interface] + client_name = CLIENT + '.Sugar.FileTransfer' + file_transfer_channel_class = { + CHANNEL + '.ChannelType': CHANNEL_TYPE_FILE_TRANSFER, + CHANNEL + '.TargetHandleType': HANDLE_TYPE_CONTACT} + capabilities = [] + connection.UpdateCapabilities( + [(client_name, [file_transfer_channel_class], capabilities)], + reply_handler=self.__update_capabilities_cb, + error_handler=partial(self.__error_handler_cb, + 'Connection.UpdateCapabilities')) + + connection = self._connection[CONNECTION_INTERFACE_ALIASING] + connection.connect_to_signal('AliasesChanged', + self.__aliases_changed_cb) + + connection = self._connection[CONNECTION_INTERFACE_SIMPLE_PRESENCE] + connection.connect_to_signal('PresencesChanged', + self.__presences_changed_cb) + + if CONNECTION_INTERFACE_BUDDY_INFO in self._connection: + connection = self._connection[CONNECTION_INTERFACE_BUDDY_INFO] + connection.connect_to_signal('PropertiesChanged', + self.__buddy_info_updated_cb, + byte_arrays=True) + + connection.connect_to_signal('ActivitiesChanged', + self.__buddy_activities_changed_cb) + + connection.connect_to_signal('CurrentActivityChanged', + self.__current_activity_changed_cb) + else: + logging.warning('Connection %s does not support OLPC buddy ' + 'properties', self._connection.object_path) + + if CONNECTION_INTERFACE_ACTIVITY_PROPERTIES in self._connection: + connection = self._connection[ + CONNECTION_INTERFACE_ACTIVITY_PROPERTIES] + connection.connect_to_signal( + 'ActivityPropertiesChanged', + self.__activity_properties_changed_cb) + else: + logging.warning('Connection %s does not support OLPC activity ' + 'properties', self._connection.object_path) + + properties = { + CHANNEL + '.ChannelType': CHANNEL_TYPE_CONTACT_LIST, + CHANNEL + '.TargetHandleType': HANDLE_TYPE_LIST, + CHANNEL + '.TargetID': 'subscribe', + } + properties = dbus.Dictionary(properties, signature='sv') + connection = self._connection[CONNECTION_INTERFACE_REQUESTS] + is_ours, channel_path, properties = \ + connection.EnsureChannel(properties) + + channel = Channel(self._connection.service_name, channel_path) + channel[CHANNEL_INTERFACE_GROUP].connect_to_signal( + 'MembersChanged', self.__members_changed_cb) + + channel[PROPERTIES_IFACE].Get(CHANNEL_INTERFACE_GROUP, + 'Members', + reply_handler=self.__get_members_ready_cb, + error_handler=partial(self.__error_handler_cb, + 'Connection.GetMembers')) + + def __update_capabilities_cb(self): + pass + + def __aliases_changed_cb(self, aliases): + logging.debug('_Account.__aliases_changed_cb') + for handle, alias in aliases: + if handle in self._buddy_handles: + logging.debug('Got handle %r with nick %r, going to update', + handle, alias) + properties = {CONNECTION_INTERFACE_ALIASING + '/alias': alias} + self.emit('buddy-updated', self._buddy_handles[handle], + properties) + + def __presences_changed_cb(self, presences): + logging.debug('_Account.__presences_changed_cb %r', presences) + for handle, presence in presences.iteritems(): + if handle in self._buddy_handles: + presence_type, status_, message_ = presence + if presence_type == CONNECTION_PRESENCE_TYPE_OFFLINE: + contact_id = self._buddy_handles[handle] + del self._buddy_handles[handle] + self.emit('buddy-removed', contact_id) + + def __buddy_info_updated_cb(self, handle, properties): + logging.debug('_Account.__buddy_info_updated_cb %r', handle) + self.emit('buddy-updated', self._buddy_handles[handle], properties) + + def __current_activity_changed_cb(self, contact_handle, activity_id, + room_handle): + logging.debug('_Account.__current_activity_changed_cb %r %r %r', + contact_handle, activity_id, room_handle) + if contact_handle in self._buddy_handles: + contact_id = self._buddy_handles[contact_handle] + if not activity_id and room_handle: + activity_id = self._activity_handles.get(room_handle, '') + self.emit('current-activity-updated', contact_id, activity_id) + + def __get_current_activity_cb(self, contact_handle, activity_id, + room_handle): + logging.debug('_Account.__get_current_activity_cb %r %r %r', + contact_handle, activity_id, room_handle) + contact_id = self._buddy_handles[contact_handle] + self.emit('current-activity-updated', contact_id, activity_id) + + def __buddy_activities_changed_cb(self, buddy_handle, activities): + self._update_buddy_activities(buddy_handle, activities) + + def _update_buddy_activities(self, buddy_handle, activities): + logging.debug('_Account._update_buddy_activities') + if not buddy_handle in self._buddy_handles: + self._buddy_handles[buddy_handle] = None + + if not buddy_handle in self._activities_per_buddy: + self._activities_per_buddy[buddy_handle] = set() + + for activity_id, room_handle in activities: + if room_handle not in self._activity_handles: + self._activity_handles[room_handle] = activity_id + self.emit('activity-added', room_handle, activity_id) + + connection = self._connection[ + CONNECTION_INTERFACE_ACTIVITY_PROPERTIES] + connection.GetProperties(room_handle, + reply_handler=partial(self.__get_properties_cb, + room_handle), + error_handler=partial(self.__error_handler_cb, + 'ActivityProperties.GetProperties')) + + # Sometimes we'll get CurrentActivityChanged before we get to + # know about the activity so we miss the event. In that case, + # request again the current activity for this buddy. + connection = self._connection[CONNECTION_INTERFACE_BUDDY_INFO] + connection.GetCurrentActivity( + buddy_handle, + reply_handler=partial(self.__get_current_activity_cb, + buddy_handle), + error_handler=partial(self.__error_handler_cb, + 'BuddyInfo.GetCurrentActivity')) + + if not activity_id in self._buddies_per_activity: + self._buddies_per_activity[activity_id] = set() + self._buddies_per_activity[activity_id].add(buddy_handle) + if activity_id not in self._activities_per_buddy[buddy_handle]: + self._activities_per_buddy[buddy_handle].add(activity_id) + if self._buddy_handles[buddy_handle] is not None: + self.emit('buddy-joined-activity', + self._buddy_handles[buddy_handle], + activity_id) + + current_activity_ids = \ + [activity_id for activity_id, room_handle in activities] + for activity_id in self._activities_per_buddy[buddy_handle].copy(): + if not activity_id in current_activity_ids: + self._remove_buddy_from_activity(buddy_handle, activity_id) + + def __get_properties_cb(self, room_handle, properties): + logging.debug('_Account.__get_properties_cb %r %r', room_handle, + properties) + if properties: + self._update_activity(room_handle, properties) + + def _remove_buddy_from_activity(self, buddy_handle, activity_id): + if buddy_handle in self._buddies_per_activity[activity_id]: + self._buddies_per_activity[activity_id].remove(buddy_handle) + + if activity_id in self._activities_per_buddy[buddy_handle]: + self._activities_per_buddy[buddy_handle].remove(activity_id) + + if self._buddy_handles[buddy_handle] is not None: + self.emit('buddy-left-activity', + self._buddy_handles[buddy_handle], + activity_id) + + if not self._buddies_per_activity[activity_id]: + del self._buddies_per_activity[activity_id] + + for room_handle in self._activity_handles.copy(): + if self._activity_handles[room_handle] == activity_id: + del self._activity_handles[room_handle] + break + + self.emit('activity-removed', activity_id) + + def __activity_properties_changed_cb(self, room_handle, properties): + logging.debug('_Account.__activity_properties_changed_cb %r %r', + room_handle, properties) + self._update_activity(room_handle, properties) + + def _update_activity(self, room_handle, properties): + if room_handle in self._activity_handles: + self.emit('activity-updated', self._activity_handles[room_handle], + properties) + else: + logging.debug('_Account.__activity_properties_changed_cb unknown ' + 'activity') + # We don't get ActivitiesChanged for the owner of the connection, + # so we query for its activities in order to find out. + if CONNECTION_INTERFACE_BUDDY_INFO in self._connection: + handle = self._self_handle + connection = self._connection[CONNECTION_INTERFACE_BUDDY_INFO] + connection.GetActivities( + handle, + reply_handler=partial(self.__got_activities_cb, handle), + error_handler=partial(self.__error_handler_cb, + 'BuddyInfo.Getactivities')) + + def __members_changed_cb(self, message, added, removed, local_pending, + remote_pending, actor, reason): + self._add_buddy_handles(added) + + def __get_members_ready_cb(self, handles): + logging.debug('_Account.__get_members_ready_cb %r', handles) + if not handles: + return + + self._add_buddy_handles(handles) + + def _add_buddy_handles(self, handles): + logging.debug('_Account._add_buddy_handles %r', handles) + interfaces = [CONNECTION, CONNECTION_INTERFACE_ALIASING] + self._connection[CONNECTION_INTERFACE_CONTACTS].GetContactAttributes( + handles, interfaces, False, + reply_handler=self.__get_contact_attributes_cb, + error_handler=partial(self.__error_handler_cb, + 'Contacts.GetContactAttributes')) + + def __got_buddy_info_cb(self, handle, nick, properties): + logging.debug('_Account.__got_buddy_info_cb %r', handle) + self.emit('buddy-updated', self._buddy_handles[handle], properties) + + def __get_contact_attributes_cb(self, attributes): + logging.debug('_Account.__get_contact_attributes_cb %r', + attributes.keys()) + + for handle in attributes.keys(): + nick = attributes[handle][CONNECTION_INTERFACE_ALIASING + '/alias'] + + if handle in self._buddy_handles and \ + not self._buddy_handles[handle] is None: + logging.debug('Got handle %r with nick %r, going to update', + handle, nick) + self.emit('buddy-updated', self._buddy_handles[handle], + attributes[handle]) + else: + logging.debug('Got handle %r with nick %r, going to add', + handle, nick) + + contact_id = attributes[handle][CONNECTION + '/contact-id'] + self._buddy_handles[handle] = contact_id + + if CONNECTION_INTERFACE_BUDDY_INFO in self._connection: + connection = \ + self._connection[CONNECTION_INTERFACE_BUDDY_INFO] + + connection.GetProperties( + handle, + reply_handler=partial(self.__got_buddy_info_cb, handle, + nick), + error_handler=partial(self.__error_handler_cb, + 'BuddyInfo.GetProperties'), + byte_arrays=True, + timeout=_QUERY_DBUS_TIMEOUT) + + connection.GetActivities( + handle, + reply_handler=partial(self.__got_activities_cb, + handle), + error_handler=partial(self.__error_handler_cb, + 'BuddyInfo.GetActivities'), + timeout=_QUERY_DBUS_TIMEOUT) + + connection.GetCurrentActivity( + handle, + reply_handler=partial(self.__get_current_activity_cb, + handle), + error_handler=partial(self.__error_handler_cb, + 'BuddyInfo.GetCurrentActivity'), + timeout=_QUERY_DBUS_TIMEOUT) + + self.emit('buddy-added', contact_id, nick, handle) + + def __got_activities_cb(self, buddy_handle, activities): + logging.debug('_Account.__got_activities_cb %r %r', buddy_handle, + activities) + self._update_buddy_activities(buddy_handle, activities) + + def enable(self): + logging.debug('_Account.enable %s', self.object_path) + self._set_enabled(True) + + def disable(self): + logging.debug('_Account.disable %s', self.object_path) + self._set_enabled(False) + self._connection = None + + def _set_enabled(self, value): + bus = dbus.Bus() + obj = bus.get_object(ACCOUNT_MANAGER_SERVICE, self.object_path) + obj.Set(ACCOUNT, 'Enabled', value, + reply_handler=self.__set_enabled_cb, + error_handler=partial(self.__error_handler_cb, + 'Account.SetEnabled'), + dbus_interface=dbus.PROPERTIES_IFACE) + + def __set_enabled_cb(self): + logging.debug('_Account.__set_enabled_cb success') - def get_bundle_id(self): - return self.bundle.get_bundle_id() class Neighborhood(gobject.GObject): __gsignals__ = { - 'activity-added': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT])), - 'activity-removed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT])), - 'buddy-added': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT])), - 'buddy-moved': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT, - gobject.TYPE_PYOBJECT])), - 'buddy-removed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT])) + 'activity-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object])), + 'activity-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object])), + 'buddy-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object])), + 'buddy-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([object])), } def __init__(self): gobject.GObject.__init__(self) + self._buddies = {None: get_owner_instance()} self._activities = {} - self._buddies = {} - - self._pservice = presenceservice.get_instance() - self._pservice.connect("activity-appeared", - self._activity_appeared_cb) - self._pservice.connect('activity-disappeared', - self._activity_disappeared_cb) - self._pservice.connect("buddy-appeared", - self._buddy_appeared_cb) - self._pservice.connect("buddy-disappeared", - self._buddy_disappeared_cb) - - # Add any buddies the PS knows about already - self._pservice.get_buddies_async(reply_handler=self._get_buddies_cb) - - self._pservice.get_activities_async( - reply_handler=self._get_activities_cb) - - self._conn_watcher = connection_watcher.ConnectionWatcher() - self._conn_watcher.connect('connection-added', self.__conn_addded_cb) - - for conn in self._conn_watcher.get_connections(): - self.__conn_addded_cb(self._conn_watcher, conn) - - gconf_client = gconf.client_get_default() - gconf_client.add_dir('/desktop/sugar/collaboration', - gconf.CLIENT_PRELOAD_NONE) - gconf_client.notify_add('/desktop/sugar/collaboration/publish_gadget', - self.__publish_gadget_changed_cb) - - def __conn_addded_cb(self, watcher, conn): - if CONN_INTERFACE_GADGET not in conn: + self._link_local_account = None + self._server_account = None + + client = gconf.client_get_default() + client.add_dir('/desktop/sugar/collaboration', + gconf.CLIENT_PRELOAD_NONE) + client.notify_add('/desktop/sugar/collaboration/jabber_server', + self.__jabber_server_changed_cb) + client.add_dir('/desktop/sugar/user/nick', gconf.CLIENT_PRELOAD_NONE) + client.notify_add('/desktop/sugar/user/nick', self.__nick_changed_cb) + + bus = dbus.Bus() + obj = bus.get_object(ACCOUNT_MANAGER_SERVICE, ACCOUNT_MANAGER_PATH) + account_manager = dbus.Interface(obj, ACCOUNT_MANAGER) + account_manager.Get(ACCOUNT_MANAGER, 'ValidAccounts', + dbus_interface=PROPERTIES_IFACE, + reply_handler=self.__got_accounts_cb, + error_handler=self.__error_handler_cb) + + def __got_accounts_cb(self, account_paths): + self._link_local_account = \ + self._ensure_link_local_account(account_paths) + self._connect_to_account(self._link_local_account) + + self._server_account = self._ensure_server_account(account_paths) + self._connect_to_account(self._server_account) + + def __error_handler_cb(self, error): + raise RuntimeError(error) + + def _connect_to_account(self, account): + account.connect('buddy-added', self.__buddy_added_cb) + account.connect('buddy-updated', self.__buddy_updated_cb) + account.connect('buddy-removed', self.__buddy_removed_cb) + account.connect('buddy-joined-activity', + self.__buddy_joined_activity_cb) + account.connect('buddy-left-activity', self.__buddy_left_activity_cb) + account.connect('activity-added', self.__activity_added_cb) + account.connect('activity-updated', self.__activity_updated_cb) + account.connect('activity-removed', self.__activity_removed_cb) + account.connect('current-activity-updated', + self.__current_activity_updated_cb) + account.connect('connected', self.__account_connected_cb) + account.connect('disconnected', self.__account_disconnected_cb) + + def __account_connected_cb(self, account): + logging.debug('__account_connected_cb %s', account.object_path) + if account == self._server_account: + self._link_local_account.disable() + + def __account_disconnected_cb(self, account): + logging.debug('__account_disconnected_cb %s', account.object_path) + if account == self._server_account: + self._link_local_account.enable() + + def _ensure_link_local_account(self, account_paths): + for account_path in account_paths: + if 'salut' in account_path: + logging.debug('Already have a Salut account') + account = _Account(account_path) + account.enable() + return account + + logging.debug('Still dont have a Salut account, creating one') + + client = gconf.client_get_default() + nick = client.get_string('/desktop/sugar/user/nick') + + params = { + 'nickname': nick, + 'first-name': '', + 'last-name': '', + 'jid': self._get_jabber_account_id(), + 'published-name': nick, + } + + properties = { + ACCOUNT + '.Enabled': True, + ACCOUNT + '.Nickname': nick, + ACCOUNT + '.ConnectAutomatically': True, + } + + bus = dbus.Bus() + obj = bus.get_object(ACCOUNT_MANAGER_SERVICE, ACCOUNT_MANAGER_PATH) + account_manager = dbus.Interface(obj, ACCOUNT_MANAGER) + account_path = account_manager.CreateAccount('salut', 'local-xmpp', + 'salut', params, + properties) + return _Account(account_path) + + def _ensure_server_account(self, account_paths): + for account_path in account_paths: + if 'gabble' in account_path: + logging.debug('Already have a Gabble account') + account = _Account(account_path) + account.enable() + return account + + logging.debug('Still dont have a Gabble account, creating one') + + client = gconf.client_get_default() + nick = client.get_string('/desktop/sugar/user/nick') + server = client.get_string('/desktop/sugar/collaboration' + '/jabber_server') + key_hash = get_profile().privkey_hash + + params = { + 'account': self._get_jabber_account_id(), + 'password': key_hash, + 'server': server, + 'resource': 'sugar', + 'require-encryption': True, + 'ignore-ssl-errors': True, + 'register': True, + 'old-ssl': True, + 'port': dbus.UInt32(5223), + } + + properties = { + ACCOUNT + '.Enabled': True, + ACCOUNT + '.Nickname': nick, + ACCOUNT + '.ConnectAutomatically': True, + } + + bus = dbus.Bus() + obj = bus.get_object(ACCOUNT_MANAGER_SERVICE, ACCOUNT_MANAGER_PATH) + account_manager = dbus.Interface(obj, ACCOUNT_MANAGER) + account_path = account_manager.CreateAccount('gabble', 'jabber', + 'jabber', params, + properties) + return _Account(account_path) + + def _get_jabber_account_id(self): + public_key_hash = sha1(get_profile().pubkey).hexdigest() + client = gconf.client_get_default() + server = client.get_string('/desktop/sugar/collaboration' + '/jabber_server') + return '%s@%s' % (public_key_hash, server) + + def __jabber_server_changed_cb(self, client, timestamp, entry, *extra): + logging.debug('__jabber_server_changed_cb') + + bus = dbus.Bus() + account = bus.get_object(ACCOUNT_MANAGER_SERVICE, + self._server_account.object_path) + + server = client.get_string( + '/desktop/sugar/collaboration/jabber_server') + account_id = self._get_jabber_account_id() + needs_reconnect = account.UpdateParameters({'server': server, + 'account': account_id, + 'register': True}, + dbus.Array([], 's'), + dbus_interface=ACCOUNT) + if needs_reconnect: + account.Reconnect() + + self._update_jid() + + def __nick_changed_cb(self, client, timestamp, entry, *extra): + logging.debug('__nick_changed_cb') + + nick = client.get_string('/desktop/sugar/user/nick') + for account in self._server_account, self._link_local_account: + bus = dbus.Bus() + obj = bus.get_object(ACCOUNT_MANAGER_SERVICE, account.object_path) + obj.Set(ACCOUNT, 'Nickname', nick, dbus_interface=PROPERTIES_IFACE) + + self._update_jid() + + def _update_jid(self): + bus = dbus.Bus() + account = bus.get_object(ACCOUNT_MANAGER_SERVICE, + self._link_local_account.object_path) + + account_id = self._get_jabber_account_id() + needs_reconnect = account.UpdateParameters({'jid': account_id}, + dbus.Array([], 's'), + dbus_interface=ACCOUNT) + if needs_reconnect: + account.Reconnect() + + def __buddy_added_cb(self, account, contact_id, nick, handle): + logging.debug('__buddy_added_cb %r', contact_id) + + if contact_id in self._buddies: + logging.debug('__buddy_added_cb buddy already tracked') return - conn[CONN_INTERFACE_GADGET].connect_to_signal('GadgetDiscovered', - lambda: self._gadget_discovered(conn)) - - gadget_discovered = conn[PROPERTIES_IFACE].Get(CONN_INTERFACE_GADGET, - 'GadgetAvailable') - if gadget_discovered: - self._gadget_discovered(conn) - - def _gadget_discovered(self, conn): - 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) - 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) - - path, props_ = conn[CONNECTION_INTERFACE_REQUESTS].CreateChannel( - { 'org.freedesktop.Telepathy.Channel.ChannelType': - 'org.laptop.Telepathy.Channel.Type.BuddyView', - 'org.laptop.Telepathy.Channel.Interface.View.MaxSize': nb - }) - - view = Channel(conn.service_name, path) - view[CHANNEL_INTERFACE].connect_to_signal('Closed', - lambda: self.__respawnable_view_closed_cb( - lambda: self._request_random_buddies(conn, nb))) - - def _request_random_activities(self, conn, nb): - logging.debug('Request %d random activities', nb) - - path, props_ = conn[CONNECTION_INTERFACE_REQUESTS].CreateChannel( - { 'org.freedesktop.Telepathy.Channel.ChannelType': - 'org.laptop.Telepathy.Channel.Type.ActivityView', - 'org.laptop.Telepathy.Channel.Interface.View.MaxSize': nb - }) - - view = Channel(conn.service_name, path) - view[CHANNEL_INTERFACE].connect_to_signal('Closed', - lambda: self.__respawnable_view_closed_cb( - lambda: self._request_random_activities(conn, nb))) - - def __publish_gadget_changed_cb(self, client_, cnxn_id_, entry, - user_data=None): - if entry.value.type == gconf.VALUE_BOOL: - publish = entry.value.get_bool() - - for conn in self._conn_watcher.get_connections(): - if CONN_INTERFACE_GADGET not in conn: - continue - - gadget_discovered = conn[PROPERTIES_IFACE].Get( - CONN_INTERFACE_GADGET, 'GadgetAvailable') - if gadget_discovered: - logging.debug("publish_gadget gconf key changed." - " Publish our status on %s: %r" % - (conn.service_name.split('.')[-1], publish)) - conn[CONN_INTERFACE_GADGET].Publish(publish) - - def __respawnable_view_closed_cb(self, request_fct): - # Views are closed if the Gadget component is restarted. As we always - # want to have the random views opened, we re-request them if they are - # closed. - logging.debug('View closed. Re-request it') - request_fct() - - def _get_buddies_cb(self, buddy_list): - for buddy in buddy_list: - self._buddy_appeared_cb(self._pservice, buddy) - - def _get_activities_cb(self, activity_list): - for act in activity_list: - self._check_activity(act) + buddy = BuddyModel( + nick=nick, + account=account.object_path, + contact_id=contact_id, + handle=handle) + self._buddies[contact_id] = buddy + + def __buddy_updated_cb(self, account, contact_id, properties): + logging.debug('__buddy_updated_cb %r', contact_id) + if contact_id is None: + # Don't know the contact-id yet, will get the full state later + return - def get_activities(self): - return self._activities.values() + if contact_id not in self._buddies: + logging.debug('__buddy_updated_cb Unknown buddy with contact_id' + ' %r', contact_id) + return - def get_buddies(self): - return self._buddies.values() + buddy = self._buddies[contact_id] - def _buddy_activity_changed_cb(self, model, cur_activity): - if not self._buddies.has_key(model.get_buddy().object_path()): - return - if cur_activity and self._activities.has_key(cur_activity.props.id): - activity_model = self._activities[cur_activity.props.id] - self.emit('buddy-moved', model, activity_model) - else: - self.emit('buddy-moved', model, None) + is_new = buddy.props.key is None and 'key' in properties + + if 'color' in properties: + buddy.props.color = XoColor(properties['color']) + + if 'key' in properties: + buddy.props.key = properties['key'] + + if 'nick' in properties: + buddy.props.nick = properties['nick'] - def _buddy_appeared_cb(self, pservice, buddy): - if self._buddies.has_key(buddy.object_path()): + if is_new: + self.emit('buddy-added', buddy) + + def __buddy_removed_cb(self, account, contact_id): + logging.debug('Neighborhood.__buddy_removed_cb %r', contact_id) + if contact_id not in self._buddies: + logging.debug('Neighborhood.__buddy_removed_cb Unknown buddy with ' + 'contact_id %r', contact_id) return - model = BuddyModel(buddy=buddy) - model.connect('current-activity-changed', - self._buddy_activity_changed_cb) - self._buddies[buddy.object_path()] = model - self.emit('buddy-added', model) + buddy = self._buddies[contact_id] + del self._buddies[contact_id] - cur_activity = buddy.props.current_activity - if cur_activity: - self._buddy_activity_changed_cb(model, cur_activity) + if buddy.props.key is not None: + self.emit('buddy-removed', buddy) - def _buddy_disappeared_cb(self, pservice, buddy): - if not self._buddies.has_key(buddy.object_path()): + def __activity_added_cb(self, account, room_handle, activity_id): + logging.debug('__activity_added_cb %r %r', room_handle, activity_id) + if activity_id in self._activities: + logging.debug('__activity_added_cb activity already tracked') return - self.emit('buddy-removed', self._buddies[buddy.object_path()]) - del self._buddies[buddy.object_path()] - def _activity_appeared_cb(self, pservice, act): - self._check_activity(act) + activity = ActivityModel(activity_id, room_handle) + self._activities[activity_id] = activity + + def __activity_updated_cb(self, account, activity_id, properties): + logging.debug('__activity_updated_cb %r %r', activity_id, properties) + if activity_id not in self._activities: + logging.debug('__activity_updated_cb Unknown activity with ' + 'activity_id %r', activity_id) + return - def _check_activity(self, presence_activity): registry = bundleregistry.get_registry() - bundle = registry.get_bundle(presence_activity.props.type) + bundle = registry.get_bundle(properties['type']) if not bundle: + logging.warning('Ignoring shared activity we don''t have') + return + + activity = self._activities[activity_id] + + is_new = activity.props.bundle is None + + activity.props.color = XoColor(properties['color']) + activity.props.bundle = bundle + activity.props.name = properties['name'] + activity.props.private = properties['private'] + + if is_new: + self.emit('activity-added', activity) + + def __activity_removed_cb(self, account, activity_id): + logging.debug('__activity_removed_cb %r', activity_id) + if activity_id not in self._activities: + logging.debug('Unknown activity with id %s. Already removed?', + activity_id) + return + activity = self._activities[activity_id] + del self._activities[activity_id] + + if activity.props.bundle is not None: + self.emit('activity-removed', activity) + + def __current_activity_updated_cb(self, account, contact_id, activity_id): + logging.debug('__current_activity_updated_cb %r %r', contact_id, + activity_id) + if contact_id not in self._buddies: + logging.debug('__current_activity_updated_cb Unknown buddy with ' + 'contact_id %r', contact_id) + return + if activity_id and activity_id not in self._activities: + logging.debug('__current_activity_updated_cb Unknown activity with' + ' id %s', activity_id) + activity_id = '' + + buddy = self._buddies[contact_id] + if buddy.props.current_activity is not None: + if buddy.props.current_activity.activity_id == activity_id: + return + buddy.props.current_activity.remove_current_buddy(buddy) + + if activity_id: + activity = self._activities[activity_id] + buddy.props.current_activity = activity + activity.add_current_buddy(buddy) + else: + buddy.props.current_activity = None + + def __buddy_joined_activity_cb(self, account, contact_id, activity_id): + if contact_id not in self._buddies: + logging.debug('__buddy_joined_activity_cb Unknown buddy with ' + 'contact_id %r', contact_id) + return + + if activity_id not in self._activities: + logging.debug('__buddy_joined_activity_cb Unknown activity with ' + 'activity_id %r', activity_id) + return + + self._activities[activity_id].add_buddy(self._buddies[contact_id]) + + def __buddy_left_activity_cb(self, account, contact_id, activity_id): + if contact_id not in self._buddies: + logging.debug('__buddy_left_activity_cb Unknown buddy with ' + 'contact_id %r', contact_id) return - if self.has_activity(presence_activity.props.id): + + if activity_id not in self._activities: + logging.debug('__buddy_left_activity_cb Unknown activity with ' + 'activity_id %r', activity_id) return - self.add_activity(bundle, presence_activity) - def has_activity(self, activity_id): - return self._activities.has_key(activity_id) + self._activities[activity_id].remove_buddy(self._buddies[contact_id]) + + def get_buddies(self): + return self._buddies.values() + + def get_buddy_by_key(self, key): + for buddy in self._buddies.values(): + if buddy.key == key: + return buddy + return None + + def get_buddy_by_handle(self, contact_handle): + for buddy in self._buddies.values(): + if not buddy.is_owner() and buddy.handle == contact_handle: + return buddy + return None def get_activity(self, activity_id): - if self.has_activity(activity_id): - return self._activities[activity_id] - else: - return None - - def add_activity(self, bundle, act): - model = ActivityModel(act, bundle) - self._activities[model.get_id()] = model - self.emit('activity-added', model) - - for buddy in self._pservice.get_buddies(): - cur_activity = buddy.props.current_activity - object_path = buddy.object_path() - if cur_activity == activity and object_path in self._buddies: - buddy_model = self._buddies[object_path] - self.emit('buddy-moved', buddy_model, model) - - def _activity_disappeared_cb(self, pservice, act): - if self._activities.has_key(act.props.id): - activity_model = self._activities[act.props.id] - self.emit('activity-removed', activity_model) - del self._activities[act.props.id] + return self._activities.get(activity_id, None) + + def get_activity_by_room(self, room_handle): + for activity in self._activities.values(): + if activity.room_handle == room_handle: + return activity + return None + + def get_activities(self): + return self._activities.values() -_model = None def get_model(): global _model diff --git a/src/jarabe/model/network.py b/src/jarabe/model/network.py index 3a949da..f265ae4 100644 --- a/src/jarabe/model/network.py +++ b/src/jarabe/model/network.py @@ -1,6 +1,6 @@ # Copyright (C) 2008 Red Hat, Inc. # Copyright (C) 2009 Tomeu Vizoso, Simon Schampijer -# Copyright (C) 2009 One Laptop per Child +# Copyright (C) 2009-2010 One Laptop per Child # Copyright (C) 2009 Paraguay Educa, Martin Abente # Copyright (C) 2010 Plan Ceibal, Daniel Castelo # @@ -18,19 +18,23 @@ # 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 _ import logging import os import time import dbus +import dbus.service import gobject import ConfigParser import gconf +import ctypes from sugar import dispatch from sugar import env from sugar.util import unique_id + DEVICE_TYPE_802_3_ETHERNET = 1 DEVICE_TYPE_802_11_WIRELESS = 2 DEVICE_TYPE_GSM_MODEM = 3 @@ -54,6 +58,49 @@ NM_ACTIVE_CONNECTION_STATE_UNKNOWN = 0 NM_ACTIVE_CONNECTION_STATE_ACTIVATING = 1 NM_ACTIVE_CONNECTION_STATE_ACTIVATED = 2 + +NM_DEVICE_STATE_REASON_UNKNOWN = 0 +NM_DEVICE_STATE_REASON_NONE = 1 +NM_DEVICE_STATE_REASON_NOW_MANAGED = 2 +NM_DEVICE_STATE_REASON_NOW_UNMANAGED = 3 +NM_DEVICE_STATE_REASON_CONFIG_FAILED = 4 +NM_DEVICE_STATE_REASON_CONFIG_UNAVAILABLE = 5 +NM_DEVICE_STATE_REASON_CONFIG_EXPIRED = 6 +NM_DEVICE_STATE_REASON_NO_SECRETS = 7 +NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT = 8 +NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED = 9 +NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED = 10 +NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT = 11 +NM_DEVICE_STATE_REASON_PPP_START_FAILED = 12 +NM_DEVICE_STATE_REASON_PPP_DISCONNECT = 13 +NM_DEVICE_STATE_REASON_PPP_FAILED = 14 +NM_DEVICE_STATE_REASON_DHCP_START_FAILED = 15 +NM_DEVICE_STATE_REASON_DHCP_ERROR = 16 +NM_DEVICE_STATE_REASON_DHCP_FAILED = 17 +NM_DEVICE_STATE_REASON_SHARED_START_FAILED = 18 +NM_DEVICE_STATE_REASON_SHARED_FAILED = 19 +NM_DEVICE_STATE_REASON_AUTOIP_START_FAILED = 20 +NM_DEVICE_STATE_REASON_AUTOIP_ERROR = 21 +NM_DEVICE_STATE_REASON_AUTOIP_FAILED = 22 +NM_DEVICE_STATE_REASON_MODEM_BUSY = 23 +NM_DEVICE_STATE_REASON_MODEM_NO_DIAL_TONE = 24 +NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER = 25 +NM_DEVICE_STATE_REASON_MODEM_DIAL_TIMEOUT = 26 +NM_DEVICE_STATE_REASON_MODEM_DIAL_FAILED = 27 +NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED = 28 +NM_DEVICE_STATE_REASON_GSM_APN_FAILED = 29 +NM_DEVICE_STATE_REASON_GSM_REGISTRATION_NOT_SEARCHING = 30 +NM_DEVICE_STATE_REASON_GSM_REGISTRATION_DENIED = 31 +NM_DEVICE_STATE_REASON_GSM_REGISTRATION_TIMEOUT = 32 +NM_DEVICE_STATE_REASON_GSM_REGISTRATION_FAILED = 33 +NM_DEVICE_STATE_REASON_GSM_PIN_CHECK_FAILED = 34 +NM_DEVICE_STATE_REASON_FIRMWARE_MISSING = 35 +NM_DEVICE_STATE_REASON_REMOVED = 36 +NM_DEVICE_STATE_REASON_SLEEPING = 37 +NM_DEVICE_STATE_REASON_CONNECTION_REMOVED = 38 +NM_DEVICE_STATE_REASON_USER_REQUESTED = 39 +NM_DEVICE_STATE_REASON_CARRIER = 40 + NM_802_11_AP_FLAGS_NONE = 0x00000000 NM_802_11_AP_FLAGS_PRIVACY = 0x00000001 @@ -83,11 +130,16 @@ NM_802_11_DEVICE_CAP_RSN = 0x00000020 SETTINGS_SERVICE = 'org.freedesktop.NetworkManagerUserSettings' +NM_SERVICE = 'org.freedesktop.NetworkManager' +NM_IFACE = 'org.freedesktop.NetworkManager' +NM_PATH = '/org/freedesktop/NetworkManager' +NM_DEVICE_IFACE = 'org.freedesktop.NetworkManager.Device' NM_SETTINGS_PATH = '/org/freedesktop/NetworkManagerSettings' NM_SETTINGS_IFACE = 'org.freedesktop.NetworkManagerSettings' NM_CONNECTION_IFACE = 'org.freedesktop.NetworkManagerSettings.Connection' NM_SECRETS_IFACE = 'org.freedesktop.NetworkManagerSettings.Connection.Secrets' NM_ACCESSPOINT_IFACE = 'org.freedesktop.NetworkManager.AccessPoint' +NM_ACTIVE_CONN_IFACE = 'org.freedesktop.NetworkManager.Connection.Active' GSM_USERNAME_PATH = '/desktop/sugar/network/gsm/username' GSM_PASSWORD_PATH = '/desktop/sugar/network/gsm/password' @@ -99,6 +151,137 @@ GSM_PUK_PATH = '/desktop/sugar/network/gsm/puk' _nm_settings = None _conn_counter = 0 +_nm_device_state_reason_description = None + + +def get_error_by_reason(reason): + global _nm_device_state_reason_description + + if _nm_device_state_reason_description is None: + _nm_device_state_reason_description = { + NM_DEVICE_STATE_REASON_UNKNOWN: + _('The reason for the device state change is unknown.'), + NM_DEVICE_STATE_REASON_NONE: + _('The state change is normal.'), + NM_DEVICE_STATE_REASON_NOW_MANAGED: + _('The device is now managed.'), + NM_DEVICE_STATE_REASON_NOW_UNMANAGED: + _('The device is no longer managed.'), + NM_DEVICE_STATE_REASON_CONFIG_FAILED: + _('The device could not be readied for configuration.'), + NM_DEVICE_STATE_REASON_CONFIG_UNAVAILABLE: + _('IP configuration could not be reserved ' + '(no available address, timeout, etc).'), + NM_DEVICE_STATE_REASON_CONFIG_EXPIRED: + _('The IP configuration is no longer valid.'), + NM_DEVICE_STATE_REASON_NO_SECRETS: + _('Secrets were required, but not provided.'), + NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT: + _('The 802.1X supplicant disconnected from ' + 'the access point or authentication server.'), + NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED: + _('Configuration of the 802.1X supplicant failed.'), + NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED: + _('The 802.1X supplicant quit or failed unexpectedly.'), + NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT: + _('The 802.1X supplicant took too long to authenticate.'), + NM_DEVICE_STATE_REASON_PPP_START_FAILED: + _('The PPP service failed to start within the allowed time.'), + NM_DEVICE_STATE_REASON_PPP_DISCONNECT: + _('The PPP service disconnected unexpectedly.'), + NM_DEVICE_STATE_REASON_PPP_FAILED: + _('The PPP service quit or failed unexpectedly.'), + NM_DEVICE_STATE_REASON_DHCP_START_FAILED: + _('The DHCP service failed to start within the allowed time.'), + NM_DEVICE_STATE_REASON_DHCP_ERROR: + _('The DHCP service reported an unexpected error.'), + NM_DEVICE_STATE_REASON_DHCP_FAILED: + _('The DHCP service quit or failed unexpectedly.'), + NM_DEVICE_STATE_REASON_SHARED_START_FAILED: + _('The shared connection service failed to start.'), + NM_DEVICE_STATE_REASON_SHARED_FAILED: + _('The shared connection service quit or failed' + ' unexpectedly.'), + NM_DEVICE_STATE_REASON_AUTOIP_START_FAILED: + _('The AutoIP service failed to start.'), + NM_DEVICE_STATE_REASON_AUTOIP_ERROR: + _('The AutoIP service reported an unexpected error.'), + NM_DEVICE_STATE_REASON_AUTOIP_FAILED: + _('The AutoIP service quit or failed unexpectedly.'), + NM_DEVICE_STATE_REASON_MODEM_BUSY: + _('Dialing failed because the line was busy.'), + NM_DEVICE_STATE_REASON_MODEM_NO_DIAL_TONE: + _('Dialing failed because there was no dial tone.'), + NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER: + _('Dialing failed because there was no carrier.'), + NM_DEVICE_STATE_REASON_MODEM_DIAL_TIMEOUT: + _('Dialing timed out.'), + NM_DEVICE_STATE_REASON_MODEM_DIAL_FAILED: + _('Dialing failed.'), + NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED: + _('Modem initialization failed.'), + NM_DEVICE_STATE_REASON_GSM_APN_FAILED: + _('Failed to select the specified GSM APN'), + NM_DEVICE_STATE_REASON_GSM_REGISTRATION_NOT_SEARCHING: + _('Not searching for networks.'), + NM_DEVICE_STATE_REASON_GSM_REGISTRATION_DENIED: + _('Network registration was denied.'), + NM_DEVICE_STATE_REASON_GSM_REGISTRATION_TIMEOUT: + _('Network registration timed out.'), + NM_DEVICE_STATE_REASON_GSM_REGISTRATION_FAILED: + _('Failed to register with the requested GSM network.'), + NM_DEVICE_STATE_REASON_GSM_PIN_CHECK_FAILED: + _('PIN check failed.'), + NM_DEVICE_STATE_REASON_FIRMWARE_MISSING: + _('Necessary firmware for the device may be missing.'), + NM_DEVICE_STATE_REASON_REMOVED: + _('The device was removed.'), + NM_DEVICE_STATE_REASON_SLEEPING: + _('NetworkManager went to sleep.'), + NM_DEVICE_STATE_REASON_CONNECTION_REMOVED: + _("The device's active connection was removed " + "or disappeared."), + NM_DEVICE_STATE_REASON_USER_REQUESTED: + _('A user or client requested the disconnection.'), + NM_DEVICE_STATE_REASON_CARRIER: + _("The device's carrier/link changed.")} + + return _nm_device_state_reason_description[reason] + + +def frequency_to_channel(frequency): + """Returns the channel matching a given radio channel frequency. If a + frequency is not in the dictionary channel 1 will be returned. + + Keyword arguments: + frequency -- The radio channel frequency in MHz. + + Return: Channel + + """ + ftoc = {2412: 1, 2417: 2, 2422: 3, 2427: 4, + 2432: 5, 2437: 6, 2442: 7, 2447: 8, + 2452: 9, 2457: 10, 2462: 11, 2467: 12, + 2472: 13} + if frequency not in ftoc: + logging.warning('The frequency %s can not be mapped to a channel, ' + 'defaulting to channel 1.', frequency) + return 1 + return ftoc[frequency] + + +def is_sugar_adhoc_network(ssid): + """Checks whether an access point is a sugar Ad-hoc network. + + Keyword arguments: + ssid -- Ssid of the access point. + + Return: Boolean + + """ + return ssid.startswith('Ad-hoc Network') + + class WirelessSecurity(object): def __init__(self): self.key_mgmt = None @@ -118,14 +301,16 @@ class WirelessSecurity(object): wireless_security['group'] = self.group return wireless_security + class Wireless(object): - nm_name = "802-11-wireless" + nm_name = '802-11-wireless' def __init__(self): self.ssid = None self.security = None self.mode = None self.band = None + self.channel = None def get_dict(self): wireless = {'ssid': self.ssid} @@ -135,11 +320,13 @@ class Wireless(object): wireless['mode'] = self.mode if self.band: wireless['band'] = self.band + if self.channel: + wireless['channel'] = self.channel return wireless class OlpcMesh(object): - nm_name = "802-11-olpc-mesh" + nm_name = '802-11-olpc-mesh' def __init__(self, channel, anycast_addr): self.channel = channel @@ -147,12 +334,12 @@ class OlpcMesh(object): def get_dict(self): ret = { - "ssid": dbus.ByteArray("olpc-mesh"), - "channel": self.channel, + 'ssid': dbus.ByteArray('olpc-mesh'), + 'channel': self.channel, } if self.anycast_addr: - ret["dhcp-anycast-address"] = dbus.ByteArray(self.anycast_addr) + ret['dhcp-anycast-address'] = dbus.ByteArray(self.anycast_addr) return ret @@ -173,6 +360,7 @@ class Connection(object): connection['timestamp'] = self.timestamp return connection + class IP4Config(object): def __init__(self): self.method = None @@ -183,6 +371,7 @@ class IP4Config(object): ip4_config['method'] = self.method return ip4_config + class Serial(object): def __init__(self): self.baud = None @@ -195,6 +384,7 @@ class Serial(object): return serial + class Ppp(object): def __init__(self): pass @@ -203,6 +393,7 @@ class Ppp(object): ppp = {} return ppp + class Gsm(object): def __init__(self): self.apn = None @@ -221,6 +412,7 @@ class Gsm(object): return gsm + class Settings(object): def __init__(self, wireless_cfg=None): self.connection = Connection() @@ -243,6 +435,7 @@ class Settings(object): settings['ipv4'] = self.ip4_config.get_dict() return settings + class Secrets(object): def __init__(self, settings): self.settings = settings @@ -268,6 +461,7 @@ class Secrets(object): return settings + class SettingsGsm(object): def __init__(self): self.connection = Connection() @@ -287,22 +481,24 @@ class SettingsGsm(object): return settings + class SecretsGsm(object): def __init__(self): self.password = None self.pin = None self.puk = None - + def get_dict(self): secrets = {} if self.password is not None: secrets['password'] = self.password if self.pin is not None: secrets['pin'] = self.pin - if self.puk is not None: + if self.puk is not None: secrets['puk'] = self.puk return {'gsm': secrets} + class NMSettings(dbus.service.Object): def __init__(self): bus = dbus.SystemBus() @@ -330,10 +526,19 @@ class NMSettings(dbus.service.Object): self.secrets_request.send(self, connection=sender, response=kwargs['response']) + def clear_wifi_connections(self): + for uuid in self.connections.keys(): + conn = self.connections[uuid] + if conn._settings.connection.type == \ + NM_CONNECTION_TYPE_802_11_WIRELESS: + conn.Removed() + self.connections.pop(uuid) + + class SecretsResponse(object): - ''' Intermediate object to report the secrets from the dialog + """Intermediate object to report the secrets from the dialog back to the connection object and which will inform NM - ''' + """ def __init__(self, connection, reply_cb, error_cb): self._connection = connection self._reply_cb = reply_cb @@ -346,6 +551,7 @@ class SecretsResponse(object): def set_error(self, error): self._error_cb(error) + class NMSettingsConnection(dbus.service.Object): def __init__(self, path, settings, secrets): bus = dbus.SystemBus() @@ -358,27 +564,57 @@ class NMSettingsConnection(dbus.service.Object): self._settings = settings self._secrets = secrets + @dbus.service.signal(dbus_interface=NM_CONNECTION_IFACE, + signature='') + def Removed(self): + pass + + @dbus.service.signal(dbus_interface=NM_CONNECTION_IFACE, + signature='a{sa{sv}}') + def Updated(self, settings): + pass + def set_connected(self): if self._settings.connection.type == NM_CONNECTION_TYPE_GSM: self._settings.connection.timestamp = int(time.time()) - else: - if not self._settings.connection.autoconnect: - self._settings.connection.autoconnect = True - self._settings.connection.timestamp = int(time.time()) - if self._settings.connection.type == NM_CONNECTION_TYPE_802_11_WIRELESS: - self.save() + elif not self._settings.connection.autoconnect: + self._settings.connection.autoconnect = True + self._settings.connection.timestamp = int(time.time()) + if (self._settings.connection.type == + NM_CONNECTION_TYPE_802_11_WIRELESS): + self.Updated(self._settings.get_dict()) + self.save() + + try: + # try to flush resolver cache - SL#1940 + # ctypes' syntactic sugar does not work + # so we must get the func ptr explicitly + libc = ctypes.CDLL('libc.so.6') + res_init = getattr(libc, '__res_init') + res_init(None) + except: + # pylint: disable=W0702 + logging.exception('Error calling libc.__res_init') + + def disable_autoconnect(self): + if self._settings.connection.type != NM_CONNECTION_TYPE_GSM and \ + self._settings.connection.autoconnect: + self._settings.connection.autoconnect = False + self._settings.connection.timestamp = None + self.Updated(self._settings.get_dict()) + self.save() def set_secrets(self, secrets): self._secrets = secrets - if self._settings.connection.type == NM_CONNECTION_TYPE_802_11_WIRELESS: + if self._settings.connection.type == \ + NM_CONNECTION_TYPE_802_11_WIRELESS: self.save() def get_settings(self): return self._settings def save(self): - profile_path = env.get_profile_path() - config_path = os.path.join(profile_path, 'nm', 'connections.cfg') + config_path = _get_wifi_connections_path() config = ConfigParser.ConfigParser() try: @@ -443,22 +679,31 @@ class NMSettingsConnection(dbus.service.Object): 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) - 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: - logging.exception('Error requesting the secrets via dialog') + self.path, request_new) + if self._settings.connection.type is not NM_CONNECTION_TYPE_GSM: + 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: + logging.exception('Error requesting the secrets via' + ' dialog') + else: + reply(self._secrets.get_dict()) else: - reply(self._secrets.get_dict()) + if not request_new: + reply(self._secrets.get_dict()) + else: + raise Exception('The stored GSM secret has already been' + ' supplied') class AccessPoint(gobject.GObject): __gsignals__ = { 'props-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])) + ([gobject.TYPE_PYOBJECT])), } def __init__(self, device, model): @@ -475,10 +720,10 @@ class AccessPoint(gobject.GObject): self.wpa_flags = 0 self.rsn_flags = 0 self.mode = 0 + self.channel = 0 def initialize(self): - model_props = dbus.Interface(self.model, - 'org.freedesktop.DBus.Properties') + model_props = dbus.Interface(self.model, dbus.PROPERTIES_IFACE) model_props.GetAll(NM_ACCESSPOINT_IFACE, byte_arrays=True, reply_handler=self._ap_properties_changed_cb, error_handler=self._get_all_props_error_cb) @@ -523,7 +768,7 @@ class AccessPoint(gobject.GObject): else: fl |= 1 << 6 - hashstr = str(fl) + "@" + self.name + hashstr = str(fl) + '@' + self.name return hash(hashstr) def _update_properties(self, properties): @@ -544,6 +789,9 @@ class AccessPoint(gobject.GObject): self.rsn_flags = properties['RsnFlags'] if 'Mode' in properties: self.mode = properties['Mode'] + if 'Frequency' in properties: + self.channel = frequency_to_channel(properties['Frequency']) + self._initialized = True self.emit('props-changed', old_hash) @@ -559,6 +807,7 @@ class AccessPoint(gobject.GObject): path=self.model.object_path, dbus_interface=NM_ACCESSPOINT_IFACE) + def get_settings(): global _nm_settings if _nm_settings is None: @@ -569,17 +818,20 @@ def get_settings(): load_connections() return _nm_settings + def find_connection_by_ssid(ssid): connections = get_settings().connections for conn_index in connections: connection = connections[conn_index] - if connection._settings.connection.type == NM_CONNECTION_TYPE_802_11_WIRELESS: - if connection._settings.wireless.ssid == ssid: - return connection + if connection._settings.connection.type == \ + NM_CONNECTION_TYPE_802_11_WIRELESS and \ + connection._settings.wireless.ssid == ssid: + return connection return None + def add_connection(uuid, settings, secrets=None): global _conn_counter @@ -590,19 +842,26 @@ def add_connection(uuid, settings, secrets=None): _nm_settings.add_connection(uuid, conn) return conn -def load_wifi_connections(): + +def _get_wifi_connections_path(): profile_path = env.get_profile_path() - config_path = os.path.join(profile_path, 'nm', 'connections.cfg') + return os.path.join(profile_path, 'nm', 'connections.cfg') - config = ConfigParser.ConfigParser() + +def _create_wifi_connections(config_path): + if not os.path.exists(os.path.dirname(config_path)): + os.makedirs(os.path.dirname(config_path), 0755) + f = open(config_path, 'w') + f.close() + + +def load_wifi_connections(): + config_path = _get_wifi_connections_path() if not os.path.exists(config_path): - if not os.path.exists(os.path.dirname(config_path)): - os.makedirs(os.path.dirname(config_path), 0755) - f = open(config_path, 'w') - config.write(f) - f.close() + _create_wifi_connections(config_path) + config = ConfigParser.ConfigParser() try: if not config.read(config_path): logging.error('Error reading the nm config file') @@ -672,10 +931,10 @@ def load_gsm_connection(): if username and number and apn: settings = SettingsGsm() - settings.gsm.username = username + settings.gsm.username = username settings.gsm.number = number settings.gsm.apn = apn - + secrets = SecretsGsm() secrets.pin = pin secrets.puk = puk @@ -693,12 +952,14 @@ def load_gsm_connection(): except Exception: logging.exception('Error adding gsm connection to NMSettings.') else: - logging.exception("No gsm connection was set in GConf.") + logging.warning('No gsm connection was set in GConf.') + def load_connections(): load_wifi_connections() load_gsm_connection() + def find_gsm_connection(): connections = get_settings().connections @@ -708,3 +969,39 @@ def find_gsm_connection(): logging.debug('There is no gsm connection in the NMSettings.') return None + + +def have_wifi_connections(): + return bool(get_settings().connections) + + +def clear_wifi_connections(): + if _nm_settings is not None: + _nm_settings.clear_wifi_connections() + + config_path = _get_wifi_connections_path() + _create_wifi_connections(config_path) + + +def disconnect_access_points(ap_paths): + """ + Disconnect all devices connected to any of the given access points. + """ + bus = dbus.SystemBus() + netmgr_obj = bus.get_object(NM_SERVICE, NM_PATH) + netmgr = dbus.Interface(netmgr_obj, NM_IFACE) + netmgr_props = dbus.Interface(netmgr, dbus.PROPERTIES_IFACE) + active_connection_paths = netmgr_props.Get(NM_IFACE, 'ActiveConnections') + + for conn_path in active_connection_paths: + conn_obj = bus.get_object(NM_IFACE, conn_path) + conn_props = dbus.Interface(conn_obj, dbus.PROPERTIES_IFACE) + ap_path = conn_props.Get(NM_ACTIVE_CONN_IFACE, 'SpecificObject') + if ap_path == '/' or ap_path not in ap_paths: + continue + + dev_paths = conn_props.Get(NM_ACTIVE_CONN_IFACE, 'Devices') + for dev_path in dev_paths: + dev_obj = bus.get_object(NM_SERVICE, dev_path) + dev = dbus.Interface(dev_obj, NM_DEVICE_IFACE) + dev.Disconnect() diff --git a/src/jarabe/model/notifications.py b/src/jarabe/model/notifications.py index 10a0237..ec14056 100644 --- a/src/jarabe/model/notifications.py +++ b/src/jarabe/model/notifications.py @@ -23,9 +23,13 @@ from sugar import dispatch from jarabe import config -_DBUS_SERVICE = "org.freedesktop.Notifications" -_DBUS_IFACE = "org.freedesktop.Notifications" -_DBUS_PATH = "/org/freedesktop/Notifications" + +_DBUS_SERVICE = 'org.freedesktop.Notifications' +_DBUS_IFACE = 'org.freedesktop.Notifications' +_DBUS_PATH = '/org/freedesktop/Notifications' + +_instance = None + class NotificationService(dbus.service.Object): def __init__(self): @@ -43,7 +47,8 @@ class NotificationService(dbus.service.Object): hints, expire_timeout): logging.debug('Received notification: %r', [app_name, replaces_id, - app_icon, summary, body, actions, hints, expire_timeout]) + '<app_icon>', summary, body, actions, '<hints>', + expire_timeout]) if replaces_id > 0: notification_id = replaces_id @@ -73,16 +78,14 @@ class NotificationService(dbus.service.Object): def GetServerInformation(self, name, vendor, version): return 'Sugar Shell', 'Sugar', config.version - - @dbus.service.signal(_DBUS_IFACE, signature="uu") + @dbus.service.signal(_DBUS_IFACE, signature='uu') def NotificationClosed(self, notification_id, reason): pass - @dbus.service.signal(_DBUS_IFACE, signature="us") + @dbus.service.signal(_DBUS_IFACE, signature='us') def ActionInvoked(self, notification_id, action_key): pass -_instance = None def get_service(): global _instance @@ -90,6 +93,6 @@ def get_service(): _instance = NotificationService() return _instance + def init(): get_service() - diff --git a/src/jarabe/model/olpcmesh.py b/src/jarabe/model/olpcmesh.py index cbd7ddd..f070100 100644 --- a/src/jarabe/model/olpcmesh.py +++ b/src/jarabe/model/olpcmesh.py @@ -1,4 +1,4 @@ -# Copyright (C) 2009 One Laptop per Child +# Copyright (C) 2009, 2010 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 @@ -24,13 +24,14 @@ from jarabe.model.network import Settings from jarabe.model.network import OlpcMesh as OlpcMeshSettings from sugar.util import unique_id + _NM_SERVICE = 'org.freedesktop.NetworkManager' _NM_IFACE = 'org.freedesktop.NetworkManager' _NM_PATH = '/org/freedesktop/NetworkManager' _NM_DEVICE_IFACE = 'org.freedesktop.NetworkManager.Device' _NM_OLPC_MESH_IFACE = 'org.freedesktop.NetworkManager.Device.OlpcMesh' -_XS_ANYCAST = "\xc0\x27\xc0\x27\xc0\x00" +_XS_ANYCAST = '\xc0\x27\xc0\x27\xc0\x00' DEVICE_STATE_UNKNOWN = 0 DEVICE_STATE_UNMANAGED = 1 @@ -43,6 +44,7 @@ DEVICE_STATE_IP_CONFIG = 7 DEVICE_STATE_ACTIVATED = 8 DEVICE_STATE_FAILED = 9 + class OlpcMeshManager(object): def __init__(self, mesh_device): self._bus = dbus.SystemBus() @@ -56,14 +58,12 @@ class OlpcMeshManager(object): """ - props = dbus.Interface(self.mesh_device, - 'org.freedesktop.DBus.Properties') + props = dbus.Interface(self.mesh_device, dbus.PROPERTIES_IFACE) props.Get(_NM_DEVICE_IFACE, 'State', reply_handler=self.__get_mesh_state_reply_cb, error_handler=self.__get_state_error_cb) - props = dbus.Interface(self.eth_device, - 'org.freedesktop.DBus.Properties') + props = dbus.Interface(self.eth_device, dbus.PROPERTIES_IFACE) props.Get(_NM_DEVICE_IFACE, 'State', reply_handler=self.__get_eth_state_reply_cb, error_handler=self.__get_state_error_cb) @@ -83,13 +83,12 @@ class OlpcMeshManager(object): self._eth_device_state = DEVICE_STATE_UNKNOWN if self._have_configured_connections(): - self._start_automesh() - else: self._start_automesh_timer() + else: + self._start_automesh() def _get_companion_device(self): - props = dbus.Interface(self.mesh_device, - 'org.freedesktop.DBus.Properties') + props = dbus.Interface(self.mesh_device, dbus.PROPERTIES_IFACE) eth_device_o = props.Get(_NM_OLPC_MESH_IFACE, 'Companion') return self._bus.get_object(_NM_SERVICE, eth_device_o) @@ -119,7 +118,7 @@ class OlpcMeshManager(object): def __eth_device_state_changed_cb(self, new_state, old_state, reason): """If a connection is activated on the eth device, stop trying our automatic connections. - + """ self._eth_device_state = new_state self._maybe_schedule_idle_check() @@ -147,7 +146,7 @@ class OlpcMeshManager(object): def _idle_check(self): if self._mesh_device_state == DEVICE_STATE_DISCONNECTED \ and self._eth_device_state == DEVICE_STATE_DISCONNECTED: - logging.debug("starting automesh due to inactivity") + logging.debug('starting automesh due to inactivity') self._start_automesh() return False @@ -170,8 +169,8 @@ class OlpcMeshManager(object): logging.error('Failed to activate connection: %s', err) def _activate_connection(self, channel, anycast_address=None): - logging.debug("activate channel %d anycast %s", - (channel, repr(anycast_address))) + logging.debug('activate channel %d anycast %r', + channel, anycast_address) proxy = self._bus.get_object(_NM_SERVICE, _NM_PATH) network_manager = dbus.Interface(proxy, _NM_IFACE) connection = self._make_connection(channel, anycast_address) @@ -211,4 +210,3 @@ class OlpcMeshManager(object): self._connection_queue.append((6, _XS_ANYCAST)) self._connection_queue.append((1, _XS_ANYCAST)) self._try_next_connection_from_queue() - diff --git a/src/jarabe/model/owner.py b/src/jarabe/model/owner.py deleted file mode 100644 index 17996e6..0000000 --- a/src/jarabe/model/owner.py +++ /dev/null @@ -1,113 +0,0 @@ -# Copyright (C) 2006-2007 Red Hat, Inc. -# Copyright (C) 2008 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 gobject -import os -import gconf -import simplejson - -from telepathy.interfaces import CHANNEL_TYPE_TEXT - -from sugar import env -from sugar.presence import presenceservice -from sugar import util -from jarabe.model.invites import Invites - -class Owner(gobject.GObject): - """Class representing the owner of this machine/instance. This class - runs in the shell and serves up the buddy icon and other stuff. It's the - server portion of the Owner, paired with the client portion in Buddy.py. - """ - __gtype_name__ = "ShellOwner" - - __gsignals__ = { - 'nick-changed' : (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([gobject.TYPE_STRING])), - 'color-changed' : (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])), - 'icon-changed' : (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])) - } - - def __init__(self): - gobject.GObject.__init__(self) - - client = gconf.client_get_default() - self._nick = client.get_string("/desktop/sugar/user/nick") - - self._icon = None - self._icon_hash = "" - icon = os.path.join(env.get_profile_path(), "buddy-icon.jpg") - if not os.path.exists(icon): - raise RuntimeError("missing buddy icon") - - fd = open(icon, "r") - self._icon = fd.read() - fd.close() - if not self._icon: - raise RuntimeError("invalid buddy icon") - - # Get the icon's hash - import hashlib - digest = hashlib.md5(self._icon).digest() - self._icon_hash = util.printable_hash(digest) - - self._pservice = presenceservice.get_instance() - self._pservice.connect('activity-invitation', - self._activity_invitation_cb) - self._pservice.connect('private-invitation', - self._private_invitation_cb) - self._pservice.connect('activity-disappeared', - self._activity_disappeared_cb) - - self._invites = Invites() - - def get_invites(self): - return self._invites - - def get_nick(self): - return self._nick - - def _activity_invitation_cb(self, pservice, activity, buddy, message): - self._invites.add_invite(activity.props.type, - activity.props.id) - - def _private_invitation_cb(self, pservice, bus_name, connection, - channel, channel_type): - """Handle a private-invitation from Presence Service. - - This is a connection by a non-Sugar XMPP client, so - launch Chat or VideoChat with the Telepathy connection and - channel. - """ - if channel_type == CHANNEL_TYPE_TEXT: - bundle_id = 'org.laptop.Chat' - else: - bundle_id = 'org.laptop.VideoChat' - tp_channel = simplejson.dumps([bus_name, connection, channel]) - self._invites.add_private_invite(tp_channel, bundle_id) - - def _activity_disappeared_cb(self, pservice, activity): - self._invites.remove_activity(activity.props.id) - -_model = None - -def get_model(): - global _model - if _model is None: - _model = Owner() - return _model diff --git a/src/jarabe/model/screen.py b/src/jarabe/model/screen.py index 4403c1c..7d34d45 100644 --- a/src/jarabe/model/screen.py +++ b/src/jarabe/model/screen.py @@ -18,12 +18,14 @@ import logging import dbus + _HARDWARE_MANAGER_INTERFACE = 'org.freedesktop.ohm.Keystore' _HARDWARE_MANAGER_SERVICE = 'org.freedesktop.ohm' _HARDWARE_MANAGER_OBJECT_PATH = '/org/freedesktop/ohm/Keystore' _ohm_service = None + def _get_ohm(): global _ohm_service if _ohm_service is None: @@ -35,9 +37,9 @@ def _get_ohm(): return _ohm_service + def set_dcon_freeze(frozen): try: - _get_ohm().SetKey("display.dcon_freeze", frozen) + _get_ohm().SetKey('display.dcon_freeze', frozen) except dbus.DBusException: logging.error('Cannot unfreeze the DCON') - diff --git a/src/jarabe/model/session.py b/src/jarabe/model/session.py index 9e0f087..9b277ff 100644 --- a/src/jarabe/model/session.py +++ b/src/jarabe/model/session.py @@ -24,8 +24,10 @@ import logging from sugar import session from sugar import env + _session_manager = None + class SessionManager(session.SessionManager): MODE_LOGOUT = 0 MODE_SHUTDOWN = 1 @@ -53,15 +55,15 @@ class SessionManager(session.SessionManager): elif self._logout_mode != self.MODE_LOGOUT: try: bus = dbus.SystemBus() - proxy = bus.get_object('org.freedesktop.Hal', - '/org/freedesktop/Hal/devices/computer') + proxy = bus.get_object('org.freedesktop.ConsoleKit', + '/org/freedesktop/ConsoleKit/Manager') pm = dbus.Interface(proxy, - 'org.freedesktop.Hal.Device.SystemPowerManagement') + 'org.freedesktop.ConsoleKit.Manager') if self._logout_mode == self.MODE_SHUTDOWN: - pm.Shutdown() + pm.Stop() elif self._logout_mode == self.MODE_REBOOT: - pm.Reboot() + pm.Restart() except: logging.exception('Can not stop sugar') self.session.cancel_shutdown() @@ -73,14 +75,15 @@ class SessionManager(session.SessionManager): def _close_emulator(self): gtk.main_quit() - if os.environ.has_key('SUGAR_EMULATOR_PID'): + if 'SUGAR_EMULATOR_PID' in os.environ: pid = int(os.environ['SUGAR_EMULATOR_PID']) os.kill(pid, signal.SIGTERM) - # Need to call this ASAP so the atexit handlers get called before we get - # killed by the X (dis)connection + # Need to call this ASAP so the atexit handlers get called before we + # get killed by the X (dis)connection sys.exit() + def get_session_manager(): global _session_manager diff --git a/src/jarabe/model/shell.py b/src/jarabe/model/shell.py index e03e0f7..63f6173 100644 --- a/src/jarabe/model/shell.py +++ b/src/jarabe/model/shell.py @@ -27,13 +27,16 @@ import dbus from sugar import wm from sugar import dispatch from sugar.graphics.xocolor import XoColor -from sugar.presence import presenceservice from jarabe.model.bundleregistry import get_registry +from jarabe.model import neighborhood + +_SERVICE_NAME = 'org.laptop.Activity' +_SERVICE_PATH = '/org/laptop/Activity' +_SERVICE_INTERFACE = 'org.laptop.Activity' + +_model = None -_SERVICE_NAME = "org.laptop.Activity" -_SERVICE_PATH = "/org/laptop/Activity" -_SERVICE_INTERFACE = "org.laptop.Activity" class Activity(gobject.GObject): """Activity which appears in the "Home View" of the Sugar shell @@ -46,10 +49,9 @@ class Activity(gobject.GObject): __gtype_name__ = 'SugarHomeActivity' - __gproperties__ = { - 'launching' : (bool, None, None, False, - gobject.PARAM_READWRITE), - } + LAUNCHING = 0 + LAUNCH_FAILED = 1 + LAUNCHED = 2 def __init__(self, activity_info, activity_id, window=None): """Initialise the HomeActivity @@ -60,19 +62,20 @@ class Activity(gobject.GObject): the "type" of activity being created. activity_id -- unique identifier for this instance of the activity type - window -- Main WnckWindow of the activity + _windows -- WnckWindows registered for the activity. The lowest + one in the stack is the main window. """ gobject.GObject.__init__(self) - self._window = None + self._windows = [] self._service = None self._activity_id = activity_id self._activity_info = activity_info self._launch_time = time.time() - self._launching = True + self._launch_status = Activity.LAUNCHING if window is not None: - self.set_window(window) + self.add_window(window) self._retrieve_service() @@ -81,18 +84,32 @@ class Activity(gobject.GObject): bus = dbus.SessionBus() self._name_owner_changed_handler = bus.add_signal_receiver( self._name_owner_changed_cb, - signal_name="NameOwnerChanged", - dbus_interface="org.freedesktop.DBus") + signal_name='NameOwnerChanged', + dbus_interface='org.freedesktop.DBus') - def set_window(self, window): - """Set the window for the activity + self._launch_completed_hid = get_model().connect('launch-completed', + self.__launch_completed_cb) + self._launch_failed_hid = get_model().connect('launch-failed', + self.__launch_failed_cb) - We allow resetting the window for an activity so that we - can replace the launcher once we get its real window. - """ + def get_launch_status(self): + return self._launch_status + + launch_status = gobject.property(getter=get_launch_status) + + def add_window(self, window): + """Add a window to the windows stack.""" if not window: - raise ValueError("window must be valid") - self._window = window + raise ValueError('window must be valid') + self._windows.append(window) + + def remove_window_by_xid(self, xid): + """Remove a window from the windows stack.""" + for wnd in self._windows: + if wnd.get_xid() == xid: + self._windows.remove(wnd) + return True + return False def get_service(self): """Get the activity service @@ -106,8 +123,8 @@ class Activity(gobject.GObject): def get_title(self): """Retrieve the application's root window's suggested title""" - if self._window: - return self._window.get_name() + if self._windows: + return self._windows[0].get_name() else: return '' @@ -135,21 +152,19 @@ class Activity(gobject.GObject): have an entry (implying that this is not a Sugar-shared application) uses the local user's profile colour for the icon. """ - pservice = presenceservice.get_instance() - # HACK to suppress warning in logs when activity isn't found # (if it's locally launched and not shared yet) activity = None - for act in pservice.get_activities(): - if self._activity_id == act.props.id: + for act in neighborhood.get_model().get_activities(): + if self._activity_id == act.activity_id: activity = act break if activity != None: - return XoColor(activity.props.color) + return activity.props.color else: client = gconf.client_get_default() - return XoColor(client.get_string("/desktop/sugar/user/color")) + return XoColor(client.get_string('/desktop/sugar/user/color')) def get_activity_id(self): """Retrieve the "activity_id" passed in to our constructor @@ -161,31 +176,44 @@ class Activity(gobject.GObject): def get_xid(self): """Retrieve the X-windows ID of our root window""" - if self._window is not None: - return self._window.get_xid() + if self._windows: + return self._windows[0].get_xid() else: return None + def has_xid(self, xid): + """Check if an X-window with the given xid is in the windows stack""" + if self._windows: + for wnd in self._windows: + if wnd.get_xid() == xid: + return True + return False + def get_window(self): """Retrieve the X-windows root window of this application - This was stored by the set_window method, which was + This was stored by the add_window method, which was called by HomeModel._add_activity, which was called via a callback that looks for all 'window-opened' events. + We keep a stack of the windows. The lowest window in the + stack that is still valid we consider the main one. + HomeModel currently uses a dbus service query on the activity to determine to which HomeActivity the newly launched window belongs. """ - return self._window + if self._windows: + return self._windows[0] + return None def get_type(self): """Retrieve the activity bundle id for future reference""" - if self._window is None: + if not self._windows: return None else: - return wm.get_bundle_id(self._window) + return wm.get_bundle_id(self._windows[0]) def is_journal(self): """Returns boolean if the activity is of type JournalActivity""" @@ -201,7 +229,9 @@ class Activity(gobject.GObject): def get_pid(self): """Returns the activity's PID""" - return self._window.get_pid() + if not self._windows: + return None + return self._windows[0].get_pid() def get_bundle_path(self): """Returns the activity's bundle directory""" @@ -220,18 +250,10 @@ class Activity(gobject.GObject): def equals(self, activity): if self._activity_id and activity.get_activity_id(): return self._activity_id == activity.get_activity_id() - if self._window.get_xid() and activity.get_xid(): - return self._window.get_xid() == activity.get_xid() + if self._windows[0].get_xid() and activity.get_xid(): + return self._windows[0].get_xid() == activity.get_xid() return False - def do_set_property(self, pspec, value): - if pspec.name == 'launching': - self._launching = value - - def do_get_property(self, pspec): - if pspec.name == 'launching': - return self._launching - def _get_service_name(self): if self._activity_id: return _SERVICE_NAME + self._activity_id @@ -245,17 +267,24 @@ class Activity(gobject.GObject): try: bus = dbus.SessionBus() proxy = bus.get_object(self._get_service_name(), - _SERVICE_PATH + "/" + self._activity_id) + _SERVICE_PATH + '/' + self._activity_id) self._service = dbus.Interface(proxy, _SERVICE_INTERFACE) except dbus.DBusException: self._service = None def _name_owner_changed_cb(self, name, old, new): if name == self._get_service_name(): - self._retrieve_service() - self.set_active(True) - self._name_owner_changed_handler.remove() - self._name_owner_changed_handler = None + if old and not new: + logging.debug('Activity._name_owner_changed_cb: ' \ + 'activity %s went away', name) + self._name_owner_changed_handler.remove() + self._name_owner_changed_handler = None + self._service = None + elif not old and new: + logging.debug('Activity._name_owner_changed_cb: ' \ + 'activity %s started up', name) + self._retrieve_service() + self.set_active(True) def set_active(self, state): """Propagate the current state to the activity object""" @@ -268,7 +297,24 @@ 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) + + def _set_launch_status(self, value): + get_model().disconnect(self._launch_completed_hid) + get_model().disconnect(self._launch_failed_hid) + self._launch_completed_hid = None + self._launch_failed_hid = None + self._launch_status = value + self.notify('launch_status') + + def __launch_completed_cb(self, model, home_activity): + if home_activity is self: + self._set_launch_status(Activity.LAUNCHED) + + def __launch_failed_cb(self, model, home_activity): + if home_activity is self: + self._set_launch_status(Activity.LAUNCH_FAILED) + class ShellModel(gobject.GObject): """Model of the shell (activity management) @@ -286,27 +332,22 @@ class ShellModel(gobject.GObject): """ __gsignals__ = { - 'activity-added': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])), - 'activity-removed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])), + 'activity-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])), + 'activity-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])), 'active-activity-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])), + ([gobject.TYPE_PYOBJECT])), 'tabbing-activity-changed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])), - 'launch-started': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])), - 'launch-completed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])), - 'launch-failed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])) + gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])), + 'launch-started': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])), + 'launch-completed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])), + 'launch-failed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])), } ZOOM_MESH = 0 @@ -331,10 +372,20 @@ class ShellModel(gobject.GObject): self._activities = [] self._active_activity = None self._tabbing_activity = None - self._pservice = presenceservice.get_instance() + self._launchers = {} self._screen.toggle_showing_desktop(True) + def get_launcher(self, activity_id): + return self._launchers.get(str(activity_id)) + + def register_launcher(self, activity_id, launcher): + self._launchers[activity_id] = launcher + + def unregister_launcher(self, activity_id): + if activity_id in self._launchers: + del self._launchers[activity_id] + def _update_zoom_level(self, window): if window.get_window_type() == wnck.WINDOW_DIALOG: return @@ -426,7 +477,7 @@ class ShellModel(gobject.GObject): def set_tabbing_activity(self, activity): """Sets the activity that is currently highlighted during tabbing""" self._tabbing_activity = activity - self.emit("tabbing-activity-changed", self._tabbing_activity) + self.emit('tabbing-activity-changed', self._tabbing_activity) def _set_active_activity(self, home_activity): if self._active_activity == home_activity: @@ -454,6 +505,21 @@ class ShellModel(gobject.GObject): return self._activities.index(obj) def _window_opened_cb(self, screen, window): + """Handle the callback for the 'window opened' event. + + Most activities will register 2 windows during + their lifetime: the launcher window, and the 'main' + app window. + + When the main window appears, we send a signal to + the launcher window to close. + + Some activities (notably non-native apps) open several + windows during their lifetime, switching from one to + the next as the 'main' window. We use a stack to track + them. + + """ if window.get_window_type() == wnck.WINDOW_NORMAL: home_activity = None @@ -476,31 +542,36 @@ class ShellModel(gobject.GObject): window.maximize() if not home_activity: + logging.debug('first window registered for %s' % activity_id) home_activity = Activity(activity_info, activity_id, window) self._add_activity(home_activity) else: - home_activity.set_window(window) - - if wm.get_sugar_window_type(window) != 'launcher': - home_activity.props.launching = False - if not home_activity.is_journal(): - self.emit('launch-completed', home_activity) + logging.debug('window registered for %s' % activity_id) + home_activity.add_window(window) + if wm.get_sugar_window_type(window) != 'launcher' \ + and home_activity.get_launch_status() == Activity.LAUNCHING: + 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.' % + (activity_id, startup_time)) if self._active_activity is None: self._set_active_activity(home_activity) def _window_closed_cb(self, screen, window): if window.get_window_type() == wnck.WINDOW_NORMAL: - if self._get_activity_by_xid(window.get_xid()) is not None: - self._remove_activity_by_xid(window.get_xid()) + xid = window.get_xid() + activity = self._get_activity_by_xid(xid) + if activity is not None: + activity.remove_window_by_xid(xid) + if activity.get_window() is None: + logging.debug('last window gone - remove activity %s' % activity) + self._remove_activity(activity) def _get_activity_by_xid(self, xid): for home_activity in self._activities: - if home_activity.get_xid() == xid: + if home_activity.has_xid(xid): return home_activity return None @@ -545,13 +616,6 @@ class ShellModel(gobject.GObject): self.emit('activity-removed', home_activity) self._activities.remove(home_activity) - def _remove_activity_by_xid(self, xid): - home_activity = self._get_activity_by_xid(xid) - if home_activity: - self._remove_activity(home_activity) - else: - logging.error('Model for window %d does not exist.', xid) - def notify_launch(self, activity_id, service_name): registry = get_registry() activity_info = registry.get_bundle(service_name) @@ -560,7 +624,6 @@ class ShellModel(gobject.GObject): " was not found in the bundle registry." % service_name) home_activity = Activity(activity_info, activity_id) - home_activity.props.launching = True self._add_activity(home_activity) self._set_active_activity(home_activity) @@ -575,11 +638,12 @@ 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, + logging.debug('Activity %s (%s) launch failed', activity_id, home_activity.get_type()) - if home_activity.props.launching: + if self.get_launcher(activity_id) is not None: self.emit('launch-failed', home_activity) else: + # activity sent failure notification after closing launcher self._remove_activity(home_activity) else: logging.error('Model for activity id %s does not exist.', @@ -592,18 +656,15 @@ class ShellModel(gobject.GObject): logging.debug('Activity %s has been closed already.', activity_id) return False - if home_activity.props.launching: + if self.get_launcher(activity_id) is not None: logging.debug('Activity %s still launching, assuming it failed.', activity_id) self.notify_launch_failed(activity_id) return False -_model = None - def get_model(): global _model if _model is None: _model = ShellModel() return _model - diff --git a/src/jarabe/model/sound.py b/src/jarabe/model/sound.py index 65090a4..9e1e748 100644 --- a/src/jarabe/model/sound.py +++ b/src/jarabe/model/sound.py @@ -20,17 +20,23 @@ from sugar import env from sugar import _sugarext from sugar import dispatch + VOLUME_STEP = 10 muted_changed = dispatch.Signal() volume_changed = dispatch.Signal() +_volume = _sugarext.VolumeAlsa() + + def get_muted(): return _volume.get_mute() + def get_volume(): return _volume.get_volume() + def set_volume(new_volume): old_volume = _volume.get_volume() _volume.set_volume(new_volume) @@ -38,6 +44,7 @@ def set_volume(new_volume): volume_changed.send(None) save() + def set_muted(new_state): old_state = _volume.get_mute() _volume.set_mute(new_state) @@ -45,14 +52,14 @@ def set_muted(new_state): muted_changed.send(None) save() + def save(): if env.is_emulator() is False: client = gconf.client_get_default() client.set_int('/desktop/sugar/sound/volume', get_volume()) + def restore(): if env.is_emulator() is False: client = gconf.client_get_default() set_volume(client.get_int('/desktop/sugar/sound/volume')) - -_volume = _sugarext.VolumeAlsa() diff --git a/src/jarabe/model/telepathyclient.py b/src/jarabe/model/telepathyclient.py new file mode 100644 index 0000000..c6fbac1 --- /dev/null +++ b/src/jarabe/model/telepathyclient.py @@ -0,0 +1,103 @@ +# Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> +# +# 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 dbus +from dbus import PROPERTIES_IFACE +from telepathy.interfaces import CLIENT, \ + CLIENT_APPROVER, \ + CLIENT_HANDLER, \ + CLIENT_INTERFACE_REQUESTS +from telepathy.server import DBusProperties + +from sugar import dispatch + + +SUGAR_CLIENT_SERVICE = 'org.freedesktop.Telepathy.Client.Sugar' +SUGAR_CLIENT_PATH = '/org/freedesktop/Telepathy/Client/Sugar' + +_instance = None + + +class TelepathyClient(dbus.service.Object, DBusProperties): + def __init__(self): + self._interfaces = set([CLIENT, CLIENT_HANDLER, + CLIENT_INTERFACE_REQUESTS, PROPERTIES_IFACE, + CLIENT_APPROVER]) + + bus = dbus.Bus() + bus_name = dbus.service.BusName(SUGAR_CLIENT_SERVICE, bus=bus) + + dbus.service.Object.__init__(self, bus_name, SUGAR_CLIENT_PATH) + DBusProperties.__init__(self) + + self._implement_property_get(CLIENT, { + 'Interfaces': lambda: list(self._interfaces), + }) + self._implement_property_get(CLIENT_HANDLER, { + 'HandlerChannelFilter': self.__get_filters_cb, + }) + self._implement_property_get(CLIENT_APPROVER, { + 'ApproverChannelFilter': self.__get_filters_cb, + }) + + self.got_channel = dispatch.Signal() + self.got_dispatch_operation = dispatch.Signal() + + def __get_filters_cb(self): + logging.debug('__get_filters_cb') + filter_dict = dbus.Dictionary({}, signature='sv') + return dbus.Array([filter_dict], signature='a{sv}') + + @dbus.service.method(dbus_interface=CLIENT_HANDLER, + in_signature='ooa(oa{sv})aota{sv}', out_signature='') + def HandleChannels(self, account, connection, channels, requests_satisfied, + user_action_time, handler_info): + logging.debug('HandleChannels\n%r\n%r\n%r\n%r\n%r\n%r\n', account, + connection, channels, requests_satisfied, + user_action_time, handler_info) + for channel in channels: + self.got_channel.send(self, account=account, + connection=connection, channel=channel) + + @dbus.service.method(dbus_interface=CLIENT_INTERFACE_REQUESTS, + in_signature='oa{sv}', out_signature='') + def AddRequest(self, request, properties): + logging.debug('AddRequest\n%r\n%r', request, properties) + + @dbus.service.method(dbus_interface=CLIENT_APPROVER, + in_signature='a(oa{sv})oa{sv}', out_signature='', + async_callbacks=('success_cb', 'error_cb_')) + def AddDispatchOperation(self, channels, dispatch_operation_path, + properties, success_cb, error_cb_): + success_cb() + try: + logging.debug('AddDispatchOperation\n%r\n%r\n%r', channels, + dispatch_operation_path, properties) + + self.got_dispatch_operation.send(self, channels=channels, + dispatch_operation_path=dispatch_operation_path, + properties=properties) + except Exception, e: + logging.exception(e) + + +def get_instance(): + global _instance + if not _instance: + _instance = TelepathyClient() + return _instance diff --git a/src/jarabe/util/__init__.py b/src/jarabe/util/__init__.py index 1610dd0..9c80ecb 100644 --- a/src/jarabe/util/__init__.py +++ b/src/jarabe/util/__init__.py @@ -16,4 +16,3 @@ # 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/util/emulator.py b/src/jarabe/util/emulator.py index d9dca7f..fda1b59 100644 --- a/src/jarabe/util/emulator.py +++ b/src/jarabe/util/emulator.py @@ -20,6 +20,7 @@ import subprocess import sys import time from optparse import OptionParser +from gettext import gettext as _ import gtk import gobject @@ -29,36 +30,37 @@ from sugar import env ERROR_NO_DISPLAY = 30 ERROR_NO_SERVER = 31 +default_dimensions = (800, 600) -default_dimensions = (800, 600) def _run_xephyr(display, dpi, dimensions, fullscreen): - cmd = [ 'Xephyr' ] + cmd = ['Xephyr'] cmd.append(':%d' % display) - cmd.append('-ac') + cmd.append('-ac') + cmd += ['-title', _('Sugar in a window')] screen_size = (gtk.gdk.screen_width(), gtk.gdk.screen_height()) if (not dimensions) and (fullscreen is None) and \ - (screen_size < default_dimensions) : + (screen_size <= default_dimensions): # no forced settings, screen too small => fit screen fullscreen = True - elif (not dimensions) : + elif not dimensions: # screen is big enough or user has en/disabled fullscreen manually # => use default size (will get ignored for fullscreen) dimensions = '%dx%d' % default_dimensions - if not dpi : + if not dpi: dpi = gtk.settings_get_default().get_property('gtk-xft-dpi') / 1024 - if fullscreen : + if fullscreen: cmd.append('-fullscreen') - if dimensions : + if dimensions: cmd.append('-screen') cmd.append(dimensions) - if dpi : + if dpi: cmd.append('-dpi') cmd.append('%d' % dpi) @@ -71,15 +73,13 @@ def _run_xephyr(display, dpi, dimensions, fullscreen): sys.stderr.write('Error executing server: %s\n' % (exc, )) return None - os.environ['DISPLAY'] = ":%d" % (display) - os.environ['SUGAR_EMULATOR_PID'] = str(pipe.pid) return pipe def _check_server(display): result = subprocess.call(['xdpyinfo', '-display', ':%d' % display], - stdout=open(os.devnull, "w"), - stderr=open(os.devnull, "w")) + stdout=open(os.devnull, 'w'), + stderr=open(os.devnull, 'w')) return result == 0 @@ -98,17 +98,17 @@ def _start_xephyr(dpi, dimensions, fullscreen): if not _check_server(display): pipe = _run_xephyr(display, dpi, dimensions, fullscreen) if pipe is None: - return None + return None, None for i_ in range(10): if _check_server(display): - return pipe + return pipe, display time.sleep(0.1) _kill_pipe(pipe) - return None + return None, None def _start_window_manager(): @@ -118,21 +118,31 @@ def _start_window_manager(): gobject.spawn_async(cmd, flags=gobject.SPAWN_SEARCH_PATH) -def _setup_env(): + +def _setup_env(display, scaling, emulator_pid): os.environ['SUGAR_EMULATOR'] = 'yes' os.environ['GABBLE_LOGFILE'] = os.path.join( env.get_profile_path(), 'logs', 'telepathy-gabble.log') os.environ['SALUT_LOGFILE'] = os.path.join( env.get_profile_path(), 'logs', 'telepathy-salut.log') + os.environ['MC_LOGFILE'] = os.path.join( + env.get_profile_path(), 'logs', 'mission-control.log') os.environ['STREAM_ENGINE_LOGFILE'] = os.path.join( env.get_profile_path(), 'logs', 'telepathy-stream-engine.log') + os.environ['DISPLAY'] = ':%d' % (display) + os.environ['SUGAR_EMULATOR_PID'] = emulator_pid + os.environ['MC_ACCOUNT_DIR'] = os.path.join( + env.get_profile_path(), 'accounts') + + if scaling: + os.environ['SUGAR_SCALING'] = scaling def main(): """Script-level operations""" parser = OptionParser() - parser.add_option('-d', '--dpi', dest='dpi', type="int", + parser.add_option('-d', '--dpi', dest='dpi', type='int', help='Emulator dpi') parser.add_option('-s', '--scaling', dest='scaling', help='Sugar scaling in %') @@ -150,16 +160,14 @@ def main(): sys.stderr.write('DISPLAY not set, cannot connect to host X server.\n') return ERROR_NO_DISPLAY - _setup_env() - - server = _start_xephyr(options.dpi, options.dimensions, options.fullscreen) + server, display = _start_xephyr(options.dpi, options.dimensions, + options.fullscreen) if server is None: sys.stderr.write('Failed to start server. Please check output above' ' for any error message.\n') return ERROR_NO_SERVER - if options.scaling: - os.environ['SUGAR_SCALING'] = options.scaling + _setup_env(display, options.scaling, str(server.pid)) command = ['dbus-launch', '--exit-with-session'] diff --git a/src/jarabe/util/telepathy/__init__.py b/src/jarabe/util/telepathy/__init__.py index 387d09c..eee4abb 100644 --- a/src/jarabe/util/telepathy/__init__.py +++ b/src/jarabe/util/telepathy/__init__.py @@ -16,4 +16,3 @@ # 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/util/telepathy/connection_watcher.py b/src/jarabe/util/telepathy/connection_watcher.py index 4a4c6e0..96af1cf 100644 --- a/src/jarabe/util/telepathy/connection_watcher.py +++ b/src/jarabe/util/telepathy/connection_watcher.py @@ -28,12 +28,16 @@ from telepathy.interfaces import CONN_INTERFACE from telepathy.constants import CONNECTION_STATUS_CONNECTED, \ CONNECTION_STATUS_DISCONNECTED + +_instance = None + + class ConnectionWatcher(gobject.GObject): __gsignals__ = { 'connection-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT])), 'connection-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])) + ([gobject.TYPE_PYOBJECT])), } def __init__(self, bus=None): @@ -93,14 +97,22 @@ class ConnectionWatcher(gobject.GObject): def get_connections(self): return self._connections.values() + +def get_instance(): + global _instance + if _instance is None: + _instance = ConnectionWatcher() + return _instance + + if __name__ == '__main__': dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) def connection_added_cb(conn_watcher, conn): - print "new connection", conn.service_name + print 'new connection', conn.service_name def connection_removed_cb(conn_watcher, conn): - print "removed connection", conn.service_name + print 'removed connection', conn.service_name watcher = ConnectionWatcher() watcher.connect('connection-added', connection_added_cb) diff --git a/src/jarabe/view/__init__.py b/src/jarabe/view/__init__.py index a9dd95a..85f6a24 100644 --- a/src/jarabe/view/__init__.py +++ b/src/jarabe/view/__init__.py @@ -13,4 +13,3 @@ # 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/view/buddyicon.py b/src/jarabe/view/buddyicon.py index 13edb2c..a274605 100644 --- a/src/jarabe/view/buddyicon.py +++ b/src/jarabe/view/buddyicon.py @@ -19,42 +19,45 @@ from sugar.graphics import style from jarabe.view.buddymenu import BuddyMenu + class BuddyIcon(CanvasIcon): def __init__(self, buddy, size=style.STANDARD_ICON_SIZE): CanvasIcon.__init__(self, icon_name='computer-xo', size=size) self._greyed_out = False self._buddy = buddy - self._buddy.connect('appeared', self._buddy_presence_change_cb) - self._buddy.connect('disappeared', self._buddy_presence_change_cb) - self._buddy.connect('color-changed', self._buddy_presence_change_cb) + self._buddy.connect('notify::present', self.__buddy_notify_present_cb) + self._buddy.connect('notify::color', self.__buddy_notify_color_cb) - palette = BuddyMenu(buddy) - self.set_palette(palette) + self.palette_invoker.cache_palette = False self._update_color() - def _buddy_presence_change_cb(self, buddy, color=None): + def create_palette(self): + return BuddyMenu(self._buddy) + + def __buddy_notify_present_cb(self, buddy, pspec): # Update the icon's color when the buddy comes and goes self._update_color() - def _update_color(self): + def __buddy_notify_color_cb(self, buddy, pspec): + self._update_color() + def _update_color(self): # keep the icon in the palette in sync with the view palette = self.get_palette() - palette_icon = palette.props.icon - if self._greyed_out: self.props.stroke_color = '#D5D5D5' self.props.fill_color = style.COLOR_TRANSPARENT.get_svg() - palette_icon.props.stroke_color = '#D5D5D5' - palette_icon.props.fill_color = style.COLOR_TRANSPARENT.get_svg() + if palette is not None: + palette.props.icon.props.stroke_color = self.props.stroke_color + palette.props.icon.props.fill_color = self.props.fill_color else: self.props.xo_color = self._buddy.get_color() - palette_icon.props.xo_color = self._buddy.get_color() + if palette is not None: + palette.props.icon.props.xo_color = self._buddy.get_color() def set_filter(self, query): self._greyed_out = (self._buddy.get_nick().lower().find(query) == -1) \ and not self._buddy.is_owner() self._update_color() - diff --git a/src/jarabe/view/buddymenu.py b/src/jarabe/view/buddymenu.py index 4637751..f824e70 100644 --- a/src/jarabe/view/buddymenu.py +++ b/src/jarabe/view/buddymenu.py @@ -1,4 +1,5 @@ # Copyright (C) 2006-2007 Red Hat, Inc. +# Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> # # 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 @@ -19,6 +20,7 @@ from gettext import gettext as _ import gtk import gconf +import dbus from sugar.graphics.palette import Palette from sugar.graphics.menuitem import MenuItem @@ -28,6 +30,8 @@ from jarabe.model import shell from jarabe.model import friends from jarabe.model.session import get_session_manager from jarabe.controlpanel.gui import ControlPanel +import jarabe.desktop.homewindow + class BuddyMenu(Palette): def __init__(self, buddy): @@ -42,8 +46,7 @@ class BuddyMenu(Palette): self._active_activity_changed_hid = None self.connect('destroy', self.__destroy_cb) - self._buddy.connect('icon-changed', self._buddy_icon_changed_cb) - self._buddy.connect('nick-changed', self._buddy_nick_changed_cb) + self._buddy.connect('notify::nick', self.__buddy_notify_nick_cb) if buddy.is_owner(): self._add_my_items() @@ -54,8 +57,7 @@ class BuddyMenu(Palette): if self._active_activity_changed_hid is not None: home_model = shell.get_model() home_model.disconnect(self._active_activity_changed_hid) - self._buddy.disconnect_by_func(self._buddy_icon_changed_cb) - self._buddy.disconnect_by_func(self._buddy_nick_changed_cb) + self._buddy.disconnect_by_func(self.__buddy_notify_nick_cb) def _add_buddy_items(self): if friends.get_model().has_buddy(self._buddy): @@ -86,6 +88,12 @@ class BuddyMenu(Palette): client = gconf.client_get_default() + if client.get_bool('/desktop/sugar/show_restart'): + item = MenuItem(_('Restart'), 'system-restart') + item.connect('activate', self.__reboot_activate_cb) + self.menu.append(item) + item.show() + if client.get_bool('/desktop/sugar/show_logout'): item = MenuItem(_('Logout'), 'system-logout') item.connect('activate', self.__logout_activate_cb) @@ -97,17 +105,18 @@ class BuddyMenu(Palette): self.menu.append(item) item.show() + def _quit(self, action): + home_window = jarabe.desktop.homewindow.get_instance() + home_window.busy_during_delayed_action(action) + def __logout_activate_cb(self, menu_item): - session_manager = get_session_manager() - session_manager.logout() + self._quit(get_session_manager().logout) def __reboot_activate_cb(self, menu_item): - session_manager = get_session_manager() - session_manager.reboot() + self._quit(get_session_manager().reboot) def __shutdown_activate_cb(self, menu_item): - session_manager = get_session_manager() - session_manager.shutdown() + self._quit(get_session_manager().shutdown) def __controlpanel_activate_cb(self, menu_item): panel = ControlPanel() @@ -115,9 +124,9 @@ class BuddyMenu(Palette): panel.show() def _update_invite_menu(self, activity): - buddy_activity = self._buddy.get_current_activity() + buddy_activity = self._buddy.props.current_activity if buddy_activity is not None: - buddy_activity_id = buddy_activity.props.id + buddy_activity_id = buddy_activity.activity_id else: buddy_activity_id = None @@ -139,11 +148,8 @@ class BuddyMenu(Palette): def _cur_activity_changed_cb(self, home_model, activity_model): self._update_invite_menu(activity_model) - def _buddy_icon_changed_cb(self, buddy): - pass - - def _buddy_nick_changed_cb(self, buddy, nick): - self.set_primary_text(nick) + def __buddy_notify_nick_cb(self, buddy, pspec): + self.set_primary_text(buddy.props.nick) def _make_friend_cb(self, menuitem): friends.get_model().make_friend(self._buddy) @@ -155,7 +161,17 @@ class BuddyMenu(Palette): activity = shell.get_model().get_active_activity() service = activity.get_service() if service: - buddy = self._buddy.get_buddy() - service.Invite(buddy.props.key) + try: + service.InviteContact(self._buddy.props.account, + self._buddy.props.contact_id) + except dbus.DBusException, e: + expected_exceptions = [ + 'org.freedesktop.DBus.Error.UnknownMethod', + 'org.freedesktop.DBus.Python.NotImplementedError'] + if e.get_dbus_name() in expected_exceptions: + logging.warning('Trying deprecated Activity.Invite') + service.Invite(self._buddy.props.key) + else: + raise else: logging.error('Invite failed, activity service not ') diff --git a/src/jarabe/view/keyhandler.py b/src/jarabe/view/keyhandler.py index 8c07975..d79bfe6 100644 --- a/src/jarabe/view/keyhandler.py +++ b/src/jarabe/view/keyhandler.py @@ -17,7 +17,6 @@ import os import logging -import traceback import dbus import gtk @@ -32,39 +31,46 @@ from jarabe.model.shell import ShellModel from jarabe import config from jarabe.journal import journalactivity + _VOLUME_STEP = sound.VOLUME_STEP _VOLUME_MAX = 100 _TABBING_MODIFIER = gtk.gdk.MOD1_MASK + _actions_table = { - 'F1' : 'zoom_mesh', - 'F2' : 'zoom_group', - 'F3' : 'zoom_home', - 'F4' : 'zoom_activity', - 'XF86AudioMute' : 'volume_mute', - 'F11' : 'volume_down', - 'XF86AudioLowerVolume' : 'volume_down', - 'F12' : 'volume_up', - 'XF86AudioRaiseVolume' : 'volume_up', - '<alt>F11' : 'volume_min', - '<alt>F12' : 'volume_max', - '0x93' : 'frame', - '<alt>Tab' : 'next_window', - '<alt><shift>Tab' : 'previous_window', - '<alt>Escape' : 'close_window', - '0xDC' : 'open_search', + 'F1': 'zoom_mesh', + 'F2': 'zoom_group', + 'F3': 'zoom_home', + 'F4': 'zoom_activity', + 'F5': 'open_search', + 'F6': 'frame', + 'XF86AudioMute': 'volume_mute', + 'F11': 'volume_down', + 'XF86AudioLowerVolume': 'volume_down', + 'F12': 'volume_up', + 'XF86AudioRaiseVolume': 'volume_up', + '<alt>F11': 'volume_min', + '<alt>F12': 'volume_max', + '0x93': 'frame', + '<alt>Tab': 'next_window', + '<alt><shift>Tab': 'previous_window', + '<alt>Escape': 'close_window', + '0xDC': 'open_search', # the following are intended for emulator users - '<alt><shift>f' : 'frame', - '<alt><shift>q' : 'quit_emulator', - 'XF86Search' : 'open_search', - '<alt><shift>o' : 'open_search', - '<alt><shift>s' : 'say_text', + '<alt><shift>f': 'frame', + '<alt><shift>q': 'quit_emulator', + 'XF86Search': 'open_search', + '<alt><shift>o': 'open_search', + '<alt><shift>s': 'say_text', } SPEECH_DBUS_SERVICE = 'org.laptop.Speech' SPEECH_DBUS_PATH = '/org/laptop/Speech' SPEECH_DBUS_INTERFACE = 'org.laptop.Speech' +_instance = None + + class KeyHandler(object): def __init__(self, frame): self._frame = frame @@ -93,8 +99,7 @@ class KeyHandler(object): raise ValueError('Key %r is already bound' % key) _actions_table[key] = module except Exception: - logging.error('Exception while loading extension:\n' + \ - traceback.format_exc()) + logging.exception('Exception while loading extension:') self._key_grabber.grab_keys(_actions_table.keys()) @@ -124,11 +129,11 @@ class KeyHandler(object): def _primary_selection_cb(self, clipboard, text, user_data): logging.debug('KeyHandler._primary_selection_cb: %r', text) if text: - self._get_speech_proxy().SayText(text, reply_handler=lambda: None, \ + self._get_speech_proxy().SayText(text, reply_handler=lambda: None, error_handler=self._on_speech_err) def handle_say_text(self, event_time): - clipboard = gtk.clipboard_get(selection="PRIMARY") + clipboard = gtk.clipboard_get(selection='PRIMARY') clipboard.request_text(self._primary_selection_cb) def handle_previous_window(self, event_time): @@ -195,7 +200,7 @@ class KeyHandler(object): if self._tabbing_handler.is_tabbing(): # Only accept window tabbing events, everything else # cancels the tabbing operation. - if not action in ["next_window", "previous_window"]: + if not action in ['next_window', 'previous_window']: self._tabbing_handler.stop(event_time) return True @@ -218,7 +223,7 @@ class KeyHandler(object): return False def _key_released_cb(self, grabber, keycode, state, event_time): - logging.debug('_key_released_cb: %i %i' % (keycode, state)) + logging.debug('_key_released_cb: %i %i', keycode, state) if self._tabbing_handler.is_tabbing(): # We stop tabbing and switch to the new window as soon as the # modifier key is raised again. @@ -228,7 +233,6 @@ class KeyHandler(object): return True return False -_instance = None def setup(frame): global _instance @@ -237,4 +241,3 @@ def setup(frame): del _instance _instance = KeyHandler(frame) - diff --git a/src/jarabe/view/launcher.py b/src/jarabe/view/launcher.py index 422a49a..89251e5 100644 --- a/src/jarabe/view/launcher.py +++ b/src/jarabe/view/launcher.py @@ -156,9 +156,6 @@ class _Animation(animator.Animation): self._icon.props.size = int(self.start_size + d) -_launchers = {} - - def setup(): model = shell.get_model() model.connect('launch-started', __launch_started_cb) @@ -167,14 +164,15 @@ def setup(): def add_launcher(activity_id, icon_path, icon_color): + model = shell.get_model() - if activity_id in _launchers: + if model.get_launcher(activity_id) is not None: return launch_window = LaunchWindow(activity_id, icon_path, icon_color) launch_window.show() - _launchers[activity_id] = launch_window + model.register_launcher(activity_id, launch_window) def __launch_started_cb(home_model, home_activity): @@ -184,7 +182,7 @@ def __launch_started_cb(home_model, home_activity): def __launch_failed_cb(home_model, home_activity): activity_id = home_activity.get_activity_id() - launcher = _launchers.get(activity_id) + launcher = shell.get_model().get_launcher(activity_id) if launcher is None: logging.error('Launcher for %s is missing', activity_id) @@ -209,8 +207,11 @@ def __launch_completed_cb(home_model, home_activity): def _destroy_launcher(home_activity): activity_id = home_activity.get_activity_id() - if activity_id in _launchers: - _launchers[activity_id].destroy() - del _launchers[activity_id] - else: - logging.error('Launcher for %s is missing', activity_id) + launcher = shell.get_model().get_launcher(activity_id) + if launcher is None: + if not home_activity.is_journal(): + logging.error('Launcher was not registered for %s', activity_id) + return + + shell.get_model().unregister_launcher(activity_id) + launcher.destroy() diff --git a/src/jarabe/view/palettes.py b/src/jarabe/view/palettes.py index 2beceff..d9c1f6b 100644 --- a/src/jarabe/view/palettes.py +++ b/src/jarabe/view/palettes.py @@ -28,31 +28,42 @@ from sugar.graphics.menuitem import MenuItem from sugar.graphics.icon import Icon from sugar.graphics import style from sugar.graphics.xocolor import XoColor -from sugar.activity import activityfactory -from sugar.activity.activityhandle import ActivityHandle from jarabe.model import shell -from jarabe.view import launcher from jarabe.view.viewsource import setup_view_source +from jarabe.journal import misc + class BasePalette(Palette): def __init__(self, home_activity): Palette.__init__(self) - if home_activity.props.launching: - home_activity.connect('notify::launching', - self._launching_changed_cb) + self._notify_launch_hid = None + + if home_activity.props.launch_status == shell.Activity.LAUNCHING: + self._notify_launch_hid = home_activity.connect( \ + 'notify::launch-status', self.__notify_launch_status_cb) self.set_primary_text(_('Starting...')) + elif home_activity.props.launch_status == shell.Activity.LAUNCH_FAILED: + self._on_failed_launch() else: self.setup_palette() - def _launching_changed_cb(self, home_activity, pspec): - if not home_activity.props.launching: - self.setup_palette() - def setup_palette(self): raise NotImplementedError + def _on_failed_launch(self): + self.set_primary_text(_('Activity failed to start')) + + def __notify_launch_status_cb(self, home_activity, pspec): + home_activity.disconnect(self._notify_launch_hid) + self._notify_launch_hid = None + if home_activity.props.launch_status == shell.Activity.LAUNCH_FAILED: + self._on_failed_launch() + else: + self.setup_palette() + + class CurrentActivityPalette(BasePalette): def __init__(self, home_activity): self._home_activity = home_activity @@ -97,10 +108,6 @@ class CurrentActivityPalette(BasePalette): self._home_activity.get_window().activate( \ gtk.get_current_event_time()) - def __active_window_changed_cb(self, screen, previous_window=None): - setup_view_source() - self._screen.disconnect(self._active_window_changed_sid) - def __stop_activate_cb(self, menu_item): self._home_activity.get_window().close(1) @@ -112,7 +119,7 @@ class ActivityPalette(Palette): self._activity_info = activity_info client = gconf.client_get_default() - color = XoColor(client.get_string("/desktop/sugar/user/color")) + color = XoColor(client.get_string('/desktop/sugar/user/color')) activity_icon = Icon(file=activity_info.get_icon(), xo_color=color, icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR) @@ -133,17 +140,8 @@ class ActivityPalette(Palette): def __start_activate_cb(self, menu_item): self.popdown(immediate=True) + misc.launch(self._activity_info) - client = gconf.client_get_default() - xo_color = XoColor(client.get_string('/desktop/sugar/user/color')) - - activity_id = activityfactory.create_activity_id() - launcher.add_launcher(activity_id, - self._activity_info.get_icon(), - xo_color) - - handle = ActivityHandle(activity_id) - activityfactory.create(self._activity_info, handle) class JournalPalette(BasePalette): def __init__(self, home_activity): @@ -196,6 +194,7 @@ class JournalPalette(BasePalette): self._free_space_label.props.label = _('%(free_space)d MB Free') % \ {'free_space': free_space / (1024 * 1024)} + class VolumePalette(Palette): def __init__(self, mount): Palette.__init__(self, label=mount.get_name()) @@ -245,4 +244,3 @@ class VolumePalette(Palette): self._progress_bar.props.fraction = fraction self._free_space_label.props.label = _('%(free_space)d MB Free') % \ {'free_space': free_space / (1024 * 1024)} - diff --git a/src/jarabe/view/pulsingicon.py b/src/jarabe/view/pulsingicon.py index 43ec358..392a404 100644 --- a/src/jarabe/view/pulsingicon.py +++ b/src/jarabe/view/pulsingicon.py @@ -21,9 +21,11 @@ import gobject from sugar.graphics.icon import Icon, CanvasIcon + _INTERVAL = 100 _STEP = math.pi / 10 # must be a fraction of pi, for clean caching + class Pulser(object): def __init__(self, icon): self._pulse_hid = None @@ -83,6 +85,7 @@ class Pulser(object): return True + class PulsingIcon(Icon): __gtype_name__ = 'SugarPulsingIcon' @@ -161,6 +164,7 @@ class PulsingIcon(Icon): if self._palette is not None: self._palette.destroy() + class CanvasPulsingIcon(CanvasIcon): __gtype_name__ = 'SugarCanvasPulsingIcon' diff --git a/src/jarabe/view/service.py b/src/jarabe/view/service.py index fbcc961..29e46b2 100644 --- a/src/jarabe/view/service.py +++ b/src/jarabe/view/service.py @@ -1,4 +1,5 @@ # Copyright (C) 2006-2007 Red Hat, Inc. +# Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> # # 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 @@ -20,13 +21,13 @@ import dbus import gtk from jarabe.model import shell -from jarabe.model import owner from jarabe.model import bundleregistry -_DBUS_SERVICE = "org.laptop.Shell" -_DBUS_SHELL_IFACE = "org.laptop.Shell" -_DBUS_OWNER_IFACE = "org.laptop.Shell.Owner" -_DBUS_PATH = "/org/laptop/Shell" + +_DBUS_SERVICE = 'org.laptop.Shell' +_DBUS_SHELL_IFACE = 'org.laptop.Shell' +_DBUS_PATH = '/org/laptop/Shell' + class UIService(dbus.service.Object): """Provides d-bus service to script the shell's operations @@ -54,17 +55,8 @@ class UIService(dbus.service.Object): self._shell_model = shell.get_model() - def start(self): - owner_model = owner.get_model() - owner_model.connect('nick-changed', self._owner_nick_changed_cb) - owner_model.connect('icon-changed', self._owner_icon_changed_cb) - owner_model.connect('color-changed', self._owner_color_changed_cb) - - self._shell_model.connect('active-activity-changed', - self._cur_activity_changed_cb) - @dbus.service.method(_DBUS_SHELL_IFACE, - in_signature="s", out_signature="s") + in_signature='s', out_signature='s') def GetBundlePath(self, bundle_id): bundle = bundleregistry.get_registry().get_bundle(bundle_id) if bundle: @@ -73,59 +65,26 @@ class UIService(dbus.service.Object): return '' @dbus.service.method(_DBUS_SHELL_IFACE, - in_signature="s", out_signature="b") + in_signature='s', out_signature='b') def ActivateActivity(self, activity_id): - """Switch to the window related to this activity_id and return a boolean - indicating if there is a real (ie. not a launcher window) activity - already open. + """Switch to the window related to this activity_id and return a + boolean indicating if there is a real (ie. not a launcher window) + activity already open. """ activity = self._shell_model.get_activity_by_id(activity_id) if activity is not None and activity.get_window() is not None: activity.get_window().activate(gtk.get_current_event_time()) - return not activity.props.launching + return self._shell_model.get_launcher(activity_id) is None return False @dbus.service.method(_DBUS_SHELL_IFACE, - in_signature="ss", out_signature="") + in_signature='ss', out_signature='') def NotifyLaunch(self, bundle_id, activity_id): shell.get_model().notify_launch(activity_id, bundle_id) @dbus.service.method(_DBUS_SHELL_IFACE, - in_signature="s", out_signature="") + in_signature='s', out_signature='') def NotifyLaunchFailure(self, activity_id): shell.get_model().notify_launch_failed(activity_id) - - @dbus.service.signal(_DBUS_OWNER_IFACE, signature="s") - def ColorChanged(self, color): - pass - - def _owner_color_changed_cb(self, new_color): - self.ColorChanged(new_color.to_string()) - - @dbus.service.signal(_DBUS_OWNER_IFACE, signature="s") - def NickChanged(self, nick): - pass - - def _owner_nick_changed_cb(self, new_nick): - self.NickChanged(new_nick) - - @dbus.service.signal(_DBUS_OWNER_IFACE, signature="ay") - def IconChanged(self, icon_data): - pass - - def _owner_icon_changed_cb(self, new_icon): - self.IconChanged(dbus.ByteArray(new_icon)) - - @dbus.service.signal(_DBUS_OWNER_IFACE, signature="s") - def CurrentActivityChanged(self, activity_id): - pass - - def _cur_activity_changed_cb(self, shell_model, new_activity): - new_id = "" - if new_activity: - new_id = new_activity.get_activity_id() - if new_id: - self.CurrentActivityChanged(new_id) - diff --git a/src/jarabe/view/tabbinghandler.py b/src/jarabe/view/tabbinghandler.py index f52bda3..0889792 100644 --- a/src/jarabe/view/tabbinghandler.py +++ b/src/jarabe/view/tabbinghandler.py @@ -21,8 +21,10 @@ import gtk from jarabe.model import shell + _RAISE_DELAY = 250 + class TabbingHandler(object): def __init__(self, frame, modifier): self._frame = frame @@ -145,4 +147,3 @@ class TabbingHandler(object): def is_tabbing(self): return self._tabbing - diff --git a/src/jarabe/view/viewsource.py b/src/jarabe/view/viewsource.py index 9905713..a1c0be3 100644 --- a/src/jarabe/view/viewsource.py +++ b/src/jarabe/view/viewsource.py @@ -17,7 +17,6 @@ import os import logging -import traceback from gettext import gettext as _ import gobject @@ -37,11 +36,13 @@ from sugar.bundle.activitybundle import ActivityBundle from sugar.datastore import datastore from sugar import mime + _SOURCE_FONT = pango.FontDescription('Monospace %d' % style.FONT_SIZE) _logger = logging.getLogger('ViewSource') map_activity_to_window = {} + def setup_view_source(activity): service = activity.get_service() if service is not None: @@ -52,9 +53,9 @@ def setup_view_source(activity): expected_exceptions = ['org.freedesktop.DBus.Error.UnknownMethod', 'org.freedesktop.DBus.Python.NotImplementedError'] if e.get_dbus_name() not in expected_exceptions: - logging.error(traceback.format_exc()) + logging.exception('Exception occured in HandleViewSource():') except Exception: - logging.error(traceback.format_exc()) + logging.exception('Exception occured in HandleViewSource():') window_xid = activity.get_xid() if window_xid is None: @@ -76,9 +77,9 @@ def setup_view_source(activity): expected_exceptions = ['org.freedesktop.DBus.Error.UnknownMethod', 'org.freedesktop.DBus.Python.NotImplementedError'] if e.get_dbus_name() not in expected_exceptions: - logging.error(traceback.format_exc()) + logging.exception('Exception occured in GetDocumentPath():') except Exception: - logging.error(traceback.format_exc()) + logging.exception('Exception occured in GetDocumentPath():') if bundle_path is None and document_path is None: _logger.debug('Activity without bundle_path nor document_path') @@ -89,6 +90,7 @@ def setup_view_source(activity): map_activity_to_window[window_xid] = view_source view_source.show() + class ViewSource(gtk.Window): __gtype_name__ = 'SugarViewSource' @@ -129,10 +131,10 @@ class ViewSource(gtk.Window): file_name = '' activity_bundle = ActivityBundle(bundle_path) - command = activity_bundle.get_command() + command = activity_bundle.get_command() if len(command.split(' ')) > 1: name = command.split(' ')[1].split('.')[0] - file_name = name + '.py' + file_name = name + '.py' path = os.path.join(activity_bundle.get_path(), file_name) self._selected_file = path @@ -195,6 +197,7 @@ class ViewSource(gtk.Window): else: self._source_display.file_path = None + class DocumentButton(RadioToolButton): __gtype_name__ = 'SugarDocumentButton' @@ -244,22 +247,20 @@ class DocumentButton(RadioToolButton): error_handler=self.__internal_save_error_cb) def __internal_save_cb(self): - logging.debug("Saved Source object to datastore.") + logging.debug('Saved Source object to datastore.') self._jobject.destroy() def __internal_save_error_cb(self, err): logging.debug('Error saving Source object to datastore: %s', err) self._jobject.destroy() + class Toolbar(gtk.Toolbar): __gtype_name__ = 'SugarViewSourceToolbar' __gsignals__ = { - 'stop-clicked': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([])), - 'source-selected': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, + 'stop-clicked': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), + 'source-selected': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([str])), } @@ -309,7 +310,6 @@ class Toolbar(gtk.Toolbar): stop = ToolButton(icon_name='dialog-cancel') stop.set_tooltip(_('Close')) stop.connect('clicked', self.__stop_clicked_cb) - stop.show() self.insert(stop, -1) stop.show() @@ -340,6 +340,7 @@ class Toolbar(gtk.Toolbar): if button.props.active: self.emit('source-selected', path) + class FileViewer(gtk.ScrolledWindow): __gtype_name__ = 'SugarFileViewer' @@ -406,6 +407,7 @@ class FileViewer(gtk.ScrolledWindow): file_path = model.get_value(tree_iter, 1) self.emit('file-selected', file_path) + class SourceDisplay(gtk.ScrolledWindow): __gtype_name__ = 'SugarSourceDisplay' @@ -462,4 +464,3 @@ class SourceDisplay(gtk.ScrolledWindow): return self._file_path file_path = property(_get_file_path, _set_file_path) - |