From 37e027e391ab0c561f5d87fbb60871708c9f58af Mon Sep 17 00:00:00 2001 From: Simon Schampijer Date: Thu, 10 Feb 2011 20:05:42 +0000 Subject: Clean up the the mess introdueced by pootle that had an outdated repo --- diff --git a/bin/sugar-session b/bin/sugar-session index f5d4c6c..57a361e 100644 --- a/bin/sugar-session +++ b/bin/sugar-session @@ -138,7 +138,16 @@ def set_fonts(): settings.set_property("gtk-font-name", "%s %f" % (face, size)) def main(): - cleanup_logs() + try: + # 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() + except OSError, e: + # logs cleanup is not critical; it should not prevent sugar from + # starting if (for example) the disk is full or read-only. + print 'logs cleanup failed: %s' % e logger.start('shell') set_fonts() diff --git a/configure.ac b/configure.ac index 90fa633..e188fa4 100644 --- a/configure.ac +++ b/configure.ac @@ -1,11 +1,11 @@ -AC_INIT([Sugar],[0.84.11],[],[sugar]) +AC_INIT([Sugar],[0.84.31],[],[sugar]) AC_PREREQ([2.59]) AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_SRCDIR([configure.ac]) -SUCROSE_VERSION="0.84.11" +SUCROSE_VERSION="0.84.31" AC_SUBST(SUCROSE_VERSION) AM_INIT_AUTOMAKE([1.9 foreign dist-bzip2 no-dist-gzip]) @@ -57,6 +57,7 @@ extensions/cpsection/aboutcomputer/Makefile extensions/cpsection/datetime/Makefile extensions/cpsection/frame/Makefile extensions/cpsection/language/Makefile +extensions/cpsection/modemconfiguration/Makefile extensions/cpsection/network/Makefile extensions/cpsection/power/Makefile extensions/deviceicon/Makefile diff --git a/data/activities.defaults b/data/activities.defaults index b726355..c294b99 100644 --- a/data/activities.defaults +++ b/data/activities.defaults @@ -10,7 +10,6 @@ org.laptop.Analyze org.laptop.Calculate org.laptop.Chat org.laptop.HelpActivity -org.laptop.Log org.laptop.MeasureActivity org.laptop.Memorize org.laptop.Oficina @@ -20,7 +19,6 @@ org.laptop.TamTamEdit org.laptop.TamTamJam org.laptop.TamTamMini org.laptop.TamTamSynthLab -org.laptop.Terminal org.laptop.TurtleArtActivity org.laptop.WebActivity org.laptop.WikipediaActivityEN diff --git a/data/icons/Makefile.am b/data/icons/Makefile.am index e1f8fa7..f719292 100644 --- a/data/icons/Makefile.am +++ b/data/icons/Makefile.am @@ -6,6 +6,7 @@ sugar_DATA = \ module-date_and_time.svg \ module-frame.svg \ module-language.svg \ + module-modemconfiguration.svg \ module-network.svg \ module-power.svg diff --git a/data/icons/module-modemconfiguration.svg b/data/icons/module-modemconfiguration.svg new file mode 100644 index 0000000..02ccc81 --- /dev/null +++ b/data/icons/module-modemconfiguration.svg @@ -0,0 +1,11 @@ + + +]> + + + + + + + diff --git a/data/sugar.schemas.in b/data/sugar.schemas.in index b58f2a8..cf234a1 100644 --- a/data/sugar.schemas.in +++ b/data/sugar.schemas.in @@ -204,5 +204,100 @@ + + /schemas/desktop/sugar/network/gsm/username + /desktop/sugar/network/gsm/username + sugar + string + + + GSM network username + GSM network username configuration + + + + /schemas/desktop/sugar/network/gsm/password + /desktop/sugar/network/gsm/password + sugar + string + + + GSM network password + GSM network password configuration + + + + /schemas/desktop/sugar/network/gsm/number + /desktop/sugar/network/gsm/number + sugar + string + *99# + + GSM network number + GSM network telephone number configuration + + + + /schemas/desktop/sugar/network/gsm/apn + /desktop/sugar/network/gsm/apn + sugar + string + + + GSM network APN + GSM network access point name configuration + + + + /schemas/desktop/sugar/network/gsm/pin + /desktop/sugar/network/gsm/pin + sugar + string + + + GSM network PIN + GSM network personal identification number configuration + + + + /schemas/desktop/sugar/network/gsm/puk + /desktop/sugar/network/gsm/puk + sugar + string + + + GSM network PUK + GSM network personal unlock key configuration + + + + + /schemas/desktop/sugar/network/adhoc + /desktop/sugar/network/adhoc + sugar + bool + true + + Show Sugar Ad-hoc networks + 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. + + + + + /schemas/desktop/sugar/protected_activities + /desktop/sugar/protected_activities + sugar + list + string + [] + + Bundle IDs of protected activities + Users will not be allowed to erase these + activities through the list view. + + + diff --git a/extensions/cpsection/Makefile.am b/extensions/cpsection/Makefile.am index 73e5164..90d36ea 100644 --- a/extensions/cpsection/Makefile.am +++ b/extensions/cpsection/Makefile.am @@ -1,4 +1,5 @@ -SUBDIRS = aboutme aboutcomputer datetime frame language network power +SUBDIRS = aboutme aboutcomputer datetime frame language \ + modemconfiguration network power sugardir = $(pkgdatadir)/extensions/cpsection sugar_PYTHON = __init__.py diff --git a/extensions/cpsection/aboutcomputer/view.py b/extensions/cpsection/aboutcomputer/view.py index dd4f8f3..4b638ff 100644 --- a/extensions/cpsection/aboutcomputer/view.py +++ b/extensions/cpsection/aboutcomputer/view.py @@ -174,7 +174,7 @@ 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 " + label_copyright = gtk.Label("© 2006-2010 One Laptop per Child " "Association Inc; Red Hat Inc; Collabora Ltd; " "and Contributors.") label_copyright.set_alignment(0, 0) diff --git a/extensions/cpsection/modemconfiguration/Makefile.am b/extensions/cpsection/modemconfiguration/Makefile.am new file mode 100644 index 0000000..3e2613e --- /dev/null +++ b/extensions/cpsection/modemconfiguration/Makefile.am @@ -0,0 +1,6 @@ +sugardir = $(pkgdatadir)/extensions/cpsection/modemconfiguration + +sugar_PYTHON = \ + __init__.py \ + model.py \ + view.py diff --git a/extensions/cpsection/modemconfiguration/__init__.py b/extensions/cpsection/modemconfiguration/__init__.py new file mode 100644 index 0000000..8a219dc --- /dev/null +++ b/extensions/cpsection/modemconfiguration/__init__.py @@ -0,0 +1,22 @@ +# Copyright (C) 2009 Paraguay Educa, Martin Abente +# +# 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 US + +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 new file mode 100644 index 0000000..2545ce1 --- /dev/null +++ b/extensions/cpsection/modemconfiguration/model.py @@ -0,0 +1,70 @@ +# Copyright (C) 2009 Paraguay Educa, Martin Abente +# +# 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 US + +import gconf + +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 new file mode 100644 index 0000000..b236f3f --- /dev/null +++ b/extensions/cpsection/modemconfiguration/view.py @@ -0,0 +1,250 @@ +# Copyright (C) 2009 Paraguay Educa, Martin Abente +# +# 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 US + +import os +import logging +from gettext import gettext as _ + +import gtk +import gobject + +from sugar.graphics import style + +from jarabe.controlpanel.sectionview import SectionView + +APPLY_TIMEOUT = 1000 + +class EntryWithLabel(gtk.HBox): + __gtype_name__ = "SugarEntryWithLabel" + + def __init__(self, label_text): + gtk.HBox.__init__(self, spacing=style.DEFAULT_SPACING) + + self._timeout_sid = 0 + self._changed_handler = None + self._is_valid = True + + self.label = gtk.Label(label_text) + self.label.modify_fg(gtk.STATE_NORMAL, + style.COLOR_SELECTION_GREY.get_gdk_color()) + self.label.set_alignment(1, 0.5) + self.pack_start(self.label, expand=False) + self.label.show() + + self._entry = gtk.Entry(25) + self._entry.connect('changed', self.__entry_changed_cb) + self._entry.set_width_chars(25) + self.pack_start(self._entry, expand=False) + self._entry.show() + + 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_cb) + + def __timeout_cb(self): + self._timeout_sid = 0 + + if self._entry.get_text() == self.get_value(): + return False + + try: + self.set_value(self._entry.get_text()) + except ValueError: + self._is_valid = False + else: + self._is_valid = True + + self.notify('is-valid') + + return False + + def set_text_from_model(self): + self._entry.set_text(self.get_value()) + + def get_value(self): + raise NotImplementedError + + def set_value(self): + 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:')) + self._model = model + + def get_value(self): + return self._model.get_username() + + def set_value(self, username): + self._model.set_username(username) + +class PasswordEntry(EntryWithLabel): + def __init__(self, model): + EntryWithLabel.__init__(self, _('Password:')) + self._model = model + + def get_value(self): + return self._model.get_password() + + def set_value(self, password): + self._model.set_password(password) + +class NumberEntry(EntryWithLabel): + def __init__(self, model): + EntryWithLabel.__init__(self, _('Number:')) + self._model = model + + def get_value(self): + return self._model.get_number() + + def set_value(self, number): + self._model.set_number(number) + +class ApnEntry(EntryWithLabel): + def __init__(self, model): + EntryWithLabel.__init__(self, _('Access Point Name (APN):')) + self._model = model + + def get_value(self): + return self._model.get_apn() + + def set_value(self, apn): + self._model.set_apn(apn) + +class PinEntry(EntryWithLabel): + def __init__(self, model): + EntryWithLabel.__init__(self, _('Personal Identity Number (PIN):')) + self._model = model + + def get_value(self): + return self._model.get_pin() + + def set_value(self, pin): + self._model.set_pin(pin) + +class PukEntry(EntryWithLabel): + def __init__(self, model): + EntryWithLabel.__init__(self, _('Personal Unblocking Key (PUK):')) + self._model = model + + def get_value(self): + return self._model.get_puk() + + def set_value(self, puk): + self._model.set_puk(puk) + + +class ModemConfiguration(SectionView): + def __init__(self, model, alerts=None): + SectionView.__init__(self) + + self._model = model + self.restart_alerts = alerts + + self.set_border_width(style.DEFAULT_SPACING) + 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.") + self._text = gtk.Label(explanation) + self._text.set_width_chars(100) + self._text.set_line_wrap(True) + self._text.set_alignment(0, 0) + self.pack_start(self._text, False) + self._text.show() + + self._username_entry = UsernameEntry(model) + self._username_entry.connect('notify::is-valid', + self.__notify_is_valid_cb) + self._group.add_widget(self._username_entry.label) + self.pack_start(self._username_entry, expand=False) + self._username_entry.show() + + self._password_entry = PasswordEntry(model) + self._password_entry.connect('notify::is-valid', + self.__notify_is_valid_cb) + self._group.add_widget(self._password_entry.label) + self.pack_start(self._password_entry, expand=False) + self._password_entry.show() + + self._number_entry = NumberEntry(model) + self._number_entry.connect('notify::is-valid', + self.__notify_is_valid_cb) + self._group.add_widget(self._number_entry.label) + self.pack_start(self._number_entry, expand=False) + self._number_entry.show() + + self._apn_entry = ApnEntry(model) + self._apn_entry.connect('notify::is-valid', + self.__notify_is_valid_cb) + self._group.add_widget(self._apn_entry.label) + self.pack_start(self._apn_entry, expand=False) + self._apn_entry.show() + + self._pin_entry = PinEntry(model) + self._pin_entry.connect('notify::is-valid', + self.__notify_is_valid_cb) + 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._puk_entry.show() + + self.setup() + + def setup(self): + self._username_entry.set_text_from_model() + self._password_entry.set_text_from_model() + self._number_entry.set_text_from_model() + self._apn_entry.set_text_from_model() + self._pin_entry.set_text_from_model() + self._puk_entry.set_text_from_model() + + self.needs_restart = False + + def undo(self): + self._model.undo() + + def _validate(self): + if self._username_entry.is_valid and \ + self._password_entry.is_valid and \ + self._number_entry.is_valid and \ + self._apn_entry.is_valid and \ + self._pin_entry.is_valid and \ + self._puk_entry.is_valid: + self.props.is_valid = True + else: + self.props.is_valid = False + + def __notify_is_valid_cb(self, entry, pspec): + if entry.is_valid: + self.needs_restart = True + self._validate() + diff --git a/extensions/cpsection/network/model.py b/extensions/cpsection/network/model.py index 87db6d9..945254a 100644 --- a/extensions/cpsection/network/model.py +++ b/extensions/cpsection/network/model.py @@ -15,9 +15,14 @@ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # +import logging import dbus from gettext import gettext as _ +from jarabe.model import network import gconf +import os + +_logger = logging.getLogger('ControlPanel - Network') _NM_SERVICE = 'org.freedesktop.NetworkManager' _NM_PATH = '/org/freedesktop/NetworkManager' @@ -62,50 +67,107 @@ def _restart_jabber(): _PS_INTERFACE) except dbus.DBusException: raise ReadError('%s service not available' % _PS_SERVICE) - ps.RestartServerConnection() + ps.RetryConnections() -def get_radio(): +def print_radio(): + print ('off', 'on')[get_radio()] + +def get_radio_nm(): + """ Get the state of NetworkManager + The user can enable/disable wireless and/or networking + return true only if wireless and network are enabled + """ bus = dbus.SystemBus() try: 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) - state = nm_props.Get(_NM_IFACE, 'WirelessEnabled') - if state in (0, 1): - return state + state = nm_props.Get(_NM_IFACE, 'NetworkingEnabled') + wireless_state = nm_props.Get(_NM_IFACE, 'WirelessEnabled') + _logger.debug('nm state: %s' % state) + _logger.debug('nm wireless_state: %s' % wireless_state) + if state in (0, 1) and wireless_state in (0, 1): + return (state == 1) and (wireless_state == 1) else: raise ReadError(_('State is unknown.')) -def print_radio(): - print ('off', 'on')[get_radio()] - -def set_radio(state): +def set_radio_nm(state): + """Enable/disable NetworkManager + state : 'on/off' + """ + if not state in ('on', 1, 'off', 0): + raise ValueError(_("Error in specified radio argument use on/off.")) + + bus = dbus.SystemBus() + try: + obj = bus.get_object(_NM_SERVICE, _NM_PATH) + nm_props = dbus.Interface(obj, dbus.PROPERTIES_IFACE) + nm = dbus.Interface(obj, _NM_IFACE) + except dbus.DBusException: + raise ReadError('%s service not available' % _NM_SERVICE) + + if state == 'on' or state == 1: + new_state = True + else: + new_state = False + + prev_state = nm_props.Get(_NM_IFACE, 'NetworkingEnabled') + if prev_state != new_state: + nm.Enable(new_state) + nm_props.Set(_NM_IFACE, 'WirelessEnabled', new_state) + + return 0 + +def get_radio_rfkill(): + pipe_stdout = os.popen('/sbin/rfkill list wifi', 'r') + try: + output = pipe_stdout.read() + _logger.debug('rfkill said: %s' % output) + blocked = " blocked: yes" in output + # if not soft- or hard-blocked, radio is on + return not blocked + + finally: + pipe_stdout.close() + +RFKILL_STATE_FILE = '/home/olpc/.rfkill_block_wifi' + +def set_radio_rfkill(state): """Turn Radio 'on' or 'off' state : 'on/off' - """ + """ if state == 'on' or state == 1: - bus = dbus.SystemBus() + os.spawnl(os.P_WAIT, "/sbin/rfkill", "rfkill", "unblock", "wifi") + # remove the flag file (checked at boot) try: - obj = bus.get_object(_NM_SERVICE, _NM_PATH) - nm_props = dbus.Interface(obj, 'org.freedesktop.DBus.Properties') - except dbus.DBusException: - raise ReadError('%s service not available' % _NM_SERVICE) - nm_props.Set(_NM_IFACE, 'WirelessEnabled', True) + os.unlink(RFKILL_STATE_FILE) + except: + _logger.debug('File %s was not unlinked' % RFKILL_STATE_FILE) elif state == 'off' or state == 0: - bus = dbus.SystemBus() + os.spawnl(os.P_WAIT, "/sbin/rfkill", "rfkill", "block", "wifi") + # touch the flag file try: - obj = bus.get_object(_NM_SERVICE, _NM_PATH) - nm_props = dbus.Interface(obj, 'org.freedesktop.DBus.Properties') - except dbus.DBusException: - raise ReadError('%s service not available' % _NM_SERVICE) - nm_props.Set(_NM_IFACE, 'WirelessEnabled', False) + fd = open(RFKILL_STATE_FILE, 'w') + except IOError: + _logger.debug('File %s is not writeable' % RFKILL_STATE_FILE) + else: + fd.close() else: raise ValueError(_("Error in specified radio argument use on/off.")) return 0 +def get_radio(): + """Get status from rfkill and nm""" + return get_radio_rfkill() and get_radio_nm() + +def set_radio(state): + """ Set status to dot-file and rfkill, and nm""" + set_radio_rfkill(state) + set_radio_nm(state) + def clear_registration(): """Clear the registration with the schoolserver """ @@ -116,13 +178,16 @@ def clear_registration(): def clear_networks(): """Clear saved passwords and network configurations. """ - pass + network.clear_connections() + +def count_networks(): + return network.count_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() diff --git a/extensions/cpsection/network/view.py b/extensions/cpsection/network/view.py index ef28f00..c387667 100644 --- a/extensions/cpsection/network/view.py +++ b/extensions/cpsection/network/view.py @@ -101,6 +101,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 self._model.count_networks() == 0: + 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() @@ -208,7 +210,9 @@ 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.count_networks() != 0: + self._clear_history_button.set_sensitive(True) self._validate() return False @@ -239,3 +243,5 @@ class Network(SectionView): def __network_configuration_reset_cb(self, widget): self._model.clear_networks() + if self._model.count_networks() == 0: + self._clear_history_button.set_sensitive(False) diff --git a/extensions/cpsection/power/model.py b/extensions/cpsection/power/model.py index 33ec905..c580439 100644 --- a/extensions/cpsection/power/model.py +++ b/extensions/cpsection/power/model.py @@ -66,7 +66,10 @@ def set_automatic_pm(enabled): else: fd.close() else: - os.unlink(POWERD_INHIBIT_FLAG) + try: + os.unlink(POWERD_INHIBIT_FLAG) + except: + _logger.debug('File %s was not unlinked' % POWERD_INHIBIT_FLAG) return 0 # ohmd @@ -87,29 +90,3 @@ def set_automatic_pm(enabled): client.set_bool('/desktop/sugar/power/automatic', enabled) return 0 -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) - enabled = True - elif enabled == 'off' or enabled == 0: - keystore.SetKey("suspend.extreme_pm", 0) - enabled = False - else: - raise ValueError(_("Error in extreme pm argument, use on/off.")) - - client = gconf.client_get_default() - client.set_bool('/desktop/sugar/power/extreme', enabled) - return 0 diff --git a/extensions/cpsection/power/view.py b/extensions/cpsection/power/view.py index 8f1ed56..d6e2b8c 100644 --- a/extensions/cpsection/power/view.py +++ b/extensions/cpsection/power/view.py @@ -29,8 +29,6 @@ class Power(SectionView): self._model = model self.restart_alerts = alerts self._automatic_pm_valid = True - self._extreme_pm_valid = True - self._extreme_pm_change_handler = None self._automatic_pm_change_handler = None self.set_border_width(style.DEFAULT_SPACING * 2) @@ -38,7 +36,6 @@ class Power(SectionView): group = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL) self._automatic_pm_alert_box = gtk.HBox(spacing=style.DEFAULT_SPACING) - self._extreme_pm_alert_box = gtk.HBox(spacing=style.DEFAULT_SPACING) separator_pm = gtk.HSeparator() self.pack_start(separator_pm, expand=False) @@ -80,35 +77,6 @@ class Power(SectionView): self._automatic_pm_alert.props.msg = self.restart_msg self._automatic_pm_alert.show() - box_extreme_pm = gtk.HBox(spacing=style.DEFAULT_SPACING) - label_extreme_pm = gtk.Label( - _('Extreme power management (disables' \ - 'wireless radio, increases battery life)')) - label_extreme_pm.set_alignment(0, 0.5) - self._extreme_button = gtk.CheckButton() - self._extreme_button.set_alignment(0, 0) - box_extreme_pm.pack_start(self._extreme_button, expand=False) - self._extreme_button.show() - box_extreme_pm.pack_start(label_extreme_pm, expand=False) - group.add_widget(label_extreme_pm) - label_extreme_pm.show() - box_pm.pack_start(box_extreme_pm, expand=False) - box_extreme_pm.show() - - self._extreme_pm_alert = InlineAlert() - label_extreme_pm_error = gtk.Label() - group.add_widget(label_extreme_pm_error) - self._extreme_pm_alert_box.pack_start(label_extreme_pm_error, - expand=False) - label_extreme_pm_error.show() - self._extreme_pm_alert_box.pack_start(self._extreme_pm_alert, - expand=False) - box_pm.pack_end(self._extreme_pm_alert_box, expand=False) - self._extreme_pm_alert_box.show() - if 'extreme_pm' in self.restart_alerts: - self._extreme_pm_alert.props.msg = self.restart_msg - self._extreme_pm_alert.show() - self.pack_start(box_pm, expand=False) box_pm.show() @@ -117,38 +85,26 @@ class Power(SectionView): def setup(self): try: automatic_state = self._model.get_automatic_pm() - extreme_state = self._model.get_extreme_pm() except Exception, detail: self._automatic_pm_alert.props.msg = detail self._automatic_pm_alert.show() - self._extreme_pm_alert.props.msg = detail - self._extreme_pm_alert.show() else: self._automatic_button.set_active(automatic_state) - self._extreme_button.set_active(extreme_state) - self._extreme_pm_valid = True self._automatic_pm_valid = True self.needs_restart = False self._automatic_pm_change_handler = self._automatic_button.connect( \ 'toggled', self.__automatic_pm_toggled_cb) - self._extreme_pm_change_handler = self._extreme_button.connect( \ - 'toggled', self.__extreme_pm_toggled_cb) def undo(self): self._automatic_button.disconnect(self._automatic_pm_change_handler) - self._extreme_button.disconnect(self._extreme_pm_change_handler) self._model.undo() - self._extreme_pm_alert.hide() self._automatic_pm_alert.hide() def _validate(self): - if self._extreme_pm_valid and self._automatic_pm_valid: - self.props.is_valid = True - else: - self.props.is_valid = False + self.props.is_valid = self._automatic_pm_valid def __automatic_pm_toggled_cb(self, widget, data=None): state = widget.get_active() @@ -163,15 +119,3 @@ class Power(SectionView): self._validate() return False - def __extreme_pm_toggled_cb(self, widget, data=None): - state = widget.get_active() - try: - self._model.set_extreme_pm(state) - except Exception, detail: - print detail - self._extreme_pm_alert.props.msg = detail - else: - self._extreme_pm_valid = True - - self._validate() - return False diff --git a/extensions/deviceicon/network.py b/extensions/deviceicon/network.py index dc14f9c..12ea9f6 100644 --- a/extensions/deviceicon/network.py +++ b/extensions/deviceicon/network.py @@ -1,6 +1,7 @@ # -# Copyright (C) 2008 One Laptop Per Child +# Copyright (C) 2008-2010 One Laptop Per Child # Copyright (C) 2009 Tomeu Vizoso, Simon Schampijer +# Copyright (C) 2009 Paraguay Educa, Martin Abente # # 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 @@ -22,7 +23,8 @@ import sha import socket import struct import re - +import datetime +import time import gtk import gobject import gconf @@ -35,6 +37,7 @@ from sugar.graphics.toolbutton import ToolButton from sugar.graphics.tray import TrayIcon 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 @@ -50,35 +53,23 @@ _NM_PATH = '/org/freedesktop/NetworkManager' _NM_DEVICE_IFACE = 'org.freedesktop.NetworkManager.Device' _NM_WIRED_IFACE = 'org.freedesktop.NetworkManager.Device.Wired' _NM_WIRELESS_IFACE = 'org.freedesktop.NetworkManager.Device.Wireless' +_NM_OLPC_MESH_IFACE = 'org.freedesktop.NetworkManager.Device.OlpcMesh' +_NM_SERIAL_IFACE = 'org.freedesktop.NetworkManager.Device.Serial' _NM_ACCESSPOINT_IFACE = 'org.freedesktop.NetworkManager.AccessPoint' _NM_ACTIVE_CONN_IFACE = 'org.freedesktop.NetworkManager.Connection.Active' -_NM_DEVICE_STATE_UNKNOWN = 0 -_NM_DEVICE_STATE_UNMANAGED = 1 -_NM_DEVICE_STATE_UNAVAILABLE = 2 -_NM_DEVICE_STATE_DISCONNECTED = 3 -_NM_DEVICE_STATE_PREPARE = 4 -_NM_DEVICE_STATE_CONFIG = 5 -_NM_DEVICE_STATE_NEED_AUTH = 6 -_NM_DEVICE_STATE_IP_CONFIG = 7 -_NM_DEVICE_STATE_ACTIVATED = 8 -_NM_DEVICE_STATE_FAILED = 9 - -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] +_GSM_STATE_NOT_READY = 0 +_GSM_STATE_DISCONNECTED = 1 +_GSM_STATE_CONNECTING = 2 +_GSM_STATE_CONNECTED = 3 +_GSM_STATE_NEED_AUTH = 4 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, ([])), + gobject.TYPE_NONE, ([])) } def __init__(self, primary_text): @@ -112,21 +103,23 @@ class WirelessPalette(Palette): self._disconnect_item.connect('activate', self.__disconnect_activate_cb) self.menu.append(self._disconnect_item) - 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...') - def set_connected(self, frequency, iaddress): + def _set_connected(self, iaddress): self.set_content(self._info) self.props.secondary_text = _('Connected') - self._set_channel(frequency) self._set_ip_address(iaddress) self._disconnect_item.show() + def set_connected_with_frequency(self, frequency, iaddress): + self._set_connected(iaddress) + self._set_frequency(frequency) + + def set_connected_with_channel(self, channel, iaddress): + self._set_connected(iaddress) + self._set_channel(channel) + def set_disconnected(self): self.props.primary_text = '' self.props.secondary_text = '' @@ -136,14 +129,14 @@ 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_channel(self, frequency): + def _set_frequency(self, frequency): try: - channel = frequency_to_channel(frequency) + channel = network.frequency_to_channel(frequency) except KeyError: channel = 0 + self._set_channel(channel) + + def _set_channel(self, channel): self._channel_label.set_text("%s: %d" % (_("Channel"), channel)) def _set_ip_address(self, ip_address): @@ -202,10 +195,105 @@ 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, ([])), + } + + def __init__(self): + Palette.__init__(self, label=_('Wireless modem')) + + self._current_state = None + + self._toggle_state_item = gtk.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_gsm_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() + 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() + label_alignment.show() + + self.info_box.show() + self.set_content(self.info_box) + + def _add_widget_with_padding(self, child, xalign=0, yalign=0.5): + alignment = gtk.Alignment(xalign=xalign, yalign=yalign, + xscale=1, yscale=0.33) + alignment.set_padding(style.DEFAULT_SPACING, + style.DEFAULT_SPACING, + style.DEFAULT_SPACING, + style.DEFAULT_SPACING) + alignment.add(child) + return alignment + + def set_gsm_state(self, state): + self._current_state = state + self._update_label_and_text() + + def _update_label_and_text(self): + 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')) + self.props.secondary_text = _('Disconnected') + + elif self._current_state == _GSM_STATE_CONNECTING: + self._toggle_state_item.get_child().set_label(_('Cancel')) + self.props.secondary_text = _('Connecting...') + + elif self._current_state == _GSM_STATE_CONNECTED: + 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') + + else: + raise ValueError('Invalid GSM state while updating label and ' \ + 'text, %s' % str(self._current_state)) + + def __toggle_state_cb(self, menuitem): + if self._current_state == _GSM_STATE_NOT_READY: + pass + elif self._current_state == _GSM_STATE_DISCONNECTED: + 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)) + class WirelessDeviceView(ToolButton): - _ICON_NAME = 'network-wireless' FRAME_POSITION_RELATIVE = 302 def __init__(self, device): @@ -224,7 +312,7 @@ class WirelessDeviceView(ToolButton): self._active_ap_op = None self._icon = PulsingIcon() - self._icon.props.icon_name = get_icon_state(self._ICON_NAME, 0) + 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())) @@ -238,13 +326,10 @@ 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 = 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) @@ -285,7 +370,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, @@ -309,11 +394,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'] @@ -329,11 +409,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 \ + self._name.startswith('Ad-hoc Network'): + self._color = profile.get_color() else: sh = sha.new() data = self._name + hex(self._flags) @@ -369,14 +447,27 @@ 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 \ + self._name.startswith('Ad-hoc Network') == 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: + try: + channel = network.frequency_to_channel(self._frequency) + except KeyError: + channel = 1 + 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 \ @@ -386,7 +477,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(self._frequency, address) + self._palette.set_connected_with_frequency(self._frequency, + address) self._icon.props.pulsing = False else: self._icon.props.badge_name = None @@ -399,75 +491,146 @@ class WirelessDeviceView(ToolButton): self._icon.props.base_color = self._color def __deactivate_connection_cb(self, palette, data=None): + connection = network.find_connection_by_ssid(self._name) + if connection: + if self._mode == network.NM_802_11_MODE_INFRA: + connection.set_disconnected() + 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') + 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) 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] - nick = re.sub("([\xf6-\xf7][\x80-\xbf]{0,2}|[\xe0-\xef][\x80-\xbf]{0,1}|[\xc0-\xdf])$", '', nick) - - connection_name = format % nick - connection_name += color_suffix - - connection = network.find_connection(connection_name) - if connection is None: - settings = Settings() - settings.connection.id = 'Auto ' + connection_name - 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(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 __activate_reply_cb(self, connection): logging.debug('Network created: %s', connection) def __activate_error_cb(self, err): logging.debug('Failed to create network: %s', err) +class OlpcMeshDeviceView(ToolButton): + _ICON_NAME = 'network-mesh' + FRAME_POSITION_RELATIVE = 302 + + def __init__(self, device, state): + ToolButton.__init__(self) + + self._bus = dbus.SystemBus() + self._device = device + self._device_props = None + self._device_state = None + self._channel = 0 + + self._icon = PulsingIcon(icon_name=self._ICON_NAME) + self._inactive_color = xocolor.XoColor( \ + "%s,%s" % (style.COLOR_BUTTON_GREY.get_svg(), + style.COLOR_TRANSPARENT.get_svg())) + 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.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.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.__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.__wireless_properties_changed_cb, + signal_name='PropertiesChanged', + path=self._device.object_path, + dbus_interface=_NM_OLPC_MESH_IFACE) + + def __get_active_channel_reply_cb(self, channel): + self._channel = channel + self._update_text() + + def __get_active_channel_error_cb(self, err): + logging.error('Error getting the active channel: %s', err) + + def __state_changed_cb(self, new_state, old_state, reason): + self._device_state = new_state + self._update() + + def __wireless_properties_changed_cb(self, properties): + if 'ActiveChannel' in properties: + self._channel = properties['ActiveChannel'] + self._update_text() + + def _update_text(self): + text = _("Mesh Network") + " " + str(self._channel) + self._palette.props.primary_text = text + + def _update(self): + state = self._device_state + + if state in [network.DEVICE_STATE_PREPARE, + 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') + 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') + 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: + netmgr.DeactivateConnection(conn_o) + break + except dbus.exceptions.DBusException: + pass + class WiredDeviceView(TrayIcon): _ICON_NAME = 'network-wired' @@ -486,12 +649,172 @@ class WiredDeviceView(TrayIcon): self._palette.set_connected(speed, address) +class GsmDeviceView(TrayIcon): + + _ICON_NAME = 'network-gsm' + FRAME_POSITION_RELATIVE = 303 + + def __init__(self, device): + self._connection_time_handler = None + self._connection_timestamp = 0 + + client = gconf.client_get_default() + color = xocolor.XoColor(client.get_string('/desktop/sugar/user/color')) + + TrayIcon.__init__(self, icon_name=self._ICON_NAME, xo_color=color) + + self._bus = dbus.SystemBus() + self._device = device + + self.set_palette_invoker(FrameWidgetInvoker(self)) + self._palette = self._create_gsm_palette() + self.set_palette(self._palette) + + 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.__ppp_stats_changed_cb, + signal_name='PppStats', + path=self._device.object_path, + dbus_interface=_NM_SERIAL_IFACE) + + def _create_gsm_palette(self): + palette = GsmPalette() + + palette.set_group_id('frame') + palette.connect('gsm-connect', self.__gsm_connect_cb) + palette.connect('gsm-disconnect', self.__gsm_disconnect_cb) + + props = dbus.Interface(self._device, 'org.freedesktop.DBus.Properties') + props.GetAll(_NM_DEVICE_IFACE, byte_arrays=True, + reply_handler=self.__current_state_check_cb, + error_handler=self.__current_state_check_error_cb) + + return palette + + def __gsm_connect_cb(self, palette, data=None): + connection = network.find_gsm_connection() + if connection is not None: + 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.__connect_cb, + error_handler=self.__connect_error_cb) + + def __connect_cb(self, active_connection): + logging.debug('Connected successfully to gsm device, %s', + active_connection) + + def __connect_error_cb(self, error): + raise RuntimeError('Error when connecting to gsm device, %s' % error) + + 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') + 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') + devices = props.Get(_NM_ACTIVE_CONN_IFACE, 'Devices') + if self._device.object_path in devices: + netmgr.DeactivateConnection( + conn_o, + reply_handler=self.__disconnect_cb, + error_handler=self.__disconnect_error_cb) + break + + def __disconnect_cb(self): + logging.debug('Disconnected successfully gsm device') + + def __disconnect_error_cb(self, error): + raise RuntimeError('Error when disconnecting gsm device, %s' % error) + + def __state_changed_cb(self, new_state, old_state, reason): + logging.debug('GSM State: %s to %s, reason %s', old_state, new_state, reason) + self._update_state(int(new_state)) + + def __current_state_check_cb(self, properties): + self._update_state(int(properties['State'])) + + def __current_state_check_error_cb(self, error): + raise RuntimeError('Error when checking gsm device state, %s' % error) + + def _update_state(self, state): + gsm_state = None + + if state == network.DEVICE_STATE_ACTIVATED: + gsm_state = _GSM_STATE_CONNECTED + connection = network.find_gsm_connection() + if connection is not None: + connection.set_connected() + self._connection_timestamp = time.time() - \ + connection.get_settings().connection.timestamp + self._connection_time_handler = gobject.timeout_add( \ + 1000, self.__connection_timecount_cb) + self._update_stats(0, 0) + self._update_connection_time() + self._palette.info_box.show() + + if state == 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() + + elif state in [network.DEVICE_STATE_UNMANAGED, + network.DEVICE_STATE_UNAVAILABLE, + network.DEVICE_STATE_UNKNOWN]: + gsm_state = _GSM_STATE_NOT_READY + + elif state in [network.DEVICE_STATE_PREPARE, + network.DEVICE_STATE_CONFIG, + network.DEVICE_STATE_IP_CONFIG]: + gsm_state = _GSM_STATE_CONNECTING + + elif state in [network.DEVICE_STATE_NEED_AUTH]: + gsm_state = _GSM_STATE_NEED_AUTH + + if self._palette is not None: + self._palette.set_gsm_state(gsm_state) + + 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) + + 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) + + def __connection_timecount_cb(self): + self._connection_timestamp = self._connection_timestamp + 1 + self._update_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): self._device = device self._device_view = None self._tray = tray - self._device_view = WirelessDeviceView(self._device) self._tray.add_device(self._device_view) @@ -502,6 +825,65 @@ 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() @@ -510,7 +892,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) @@ -538,8 +920,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) @@ -550,6 +931,19 @@ class WiredDeviceObserver(object): del self._device_view self._device_view = None +class GsmDeviceObserver(object): + def __init__(self, device, tray): + self._device = device + self._device_view = None + self._tray = tray + + self._device_view = GsmDeviceView(device) + self._tray.add_device(self._device_view) + + def disconnect(self): + self._device_view.disconnect() + self._tray.remove_device(self._device_view) + self._device_view = None class NetworkManagerObserver(object): def __init__(self, tray): @@ -584,7 +978,7 @@ 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: @@ -593,6 +987,12 @@ class NetworkManagerObserver(object): 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) + self._devices[device_op] = device def __device_added_cb(self, device_op): self._check_device(device_op) diff --git a/po/POTFILES.in b/po/POTFILES.in index 5f19663..b87175f 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -13,6 +13,8 @@ extensions/cpsection/frame/view.py extensions/cpsection/language/__init__.py extensions/cpsection/language/model.py extensions/cpsection/language/view.py +extensions/cpsection/modemconfiguration/__init__.py +extensions/cpsection/modemconfiguration/view.py extensions/cpsection/network/__init__.py extensions/cpsection/network/model.py extensions/cpsection/network/view.py @@ -29,6 +31,7 @@ src/jarabe/controlpanel/cmd.py src/jarabe/controlpanel/gui.py src/jarabe/controlpanel/sectionview.py src/jarabe/controlpanel/toolbar.py +src/jarabe/desktop/activitieslist.py src/jarabe/desktop/favoriteslayout.py src/jarabe/desktop/favoritesview.py src/jarabe/desktop/homebox.py diff --git a/po/es.po b/po/es.po index a1e5fe3..b4572de 100644 --- a/po/es.po +++ b/po/es.po @@ -6,14 +6,14 @@ msgid "" msgstr "" "Project-Id-Version: olpc-sugar\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2009-04-29 17:36-0400\n" -"PO-Revision-Date: 2010-01-18 06:17+0200\n" -"Last-Translator: Chris \n" +"POT-Creation-Date: 2011-01-05 01:10-0200\n" +"PO-Revision-Date: 2010-03-12 18:44+0200\n" +"Last-Translator: Roger Orellana \n" "Language-Team: Fedora Spanish \n" -"Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"Language: es\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Pootle 2.0.1\n" "X-Poedit-Language: Spanish\n" @@ -62,7 +62,7 @@ msgstr "Nombre:" #: ../extensions/cpsection/aboutme/view.py:128 msgid "Click to change your color:" -msgstr "Clic para cambiar su color:" +msgstr "Haga Clic para cambiar su color:" #: ../extensions/cpsection/aboutcomputer/__init__.py:21 msgid "About my Computer" @@ -99,11 +99,11 @@ msgstr "Firmware:" #: ../extensions/cpsection/aboutcomputer/view.py:146 msgid "Wireless Firmware:" -msgstr "Firmware Wireless:" +msgstr "Firmware de la red inalámbrica:" #: ../extensions/cpsection/aboutcomputer/view.py:169 msgid "Copyright and License" -msgstr "Licencia y Copyright" +msgstr "Licencia y derechos de autor" #: ../extensions/cpsection/aboutcomputer/view.py:184 msgid "" @@ -129,7 +129,7 @@ msgstr "Fecha y hora" msgid "Error timezone does not exist." msgstr "Error, zona horaria no existe." -#: ../extensions/cpsection/datetime/view.py:68 ../data/sugar.schemas.in.h:19 +#: ../extensions/cpsection/datetime/view.py:68 ../data/sugar.schemas.in.h:38 msgid "Timezone" msgstr "Zona horaria" @@ -174,8 +174,8 @@ msgstr "Idioma" #: ../extensions/cpsection/language/model.py:28 msgid "Could not access ~/.i18n. Create standard settings." -msgstr "" -"No se puede acceder a ~/.i18n. Crear configuración internacional estándar." +msgstr "No se puede acceder a ~/.i18n. Crear una configuración internacional " +"estándar." #: ../extensions/cpsection/language/model.py:124 #, python-format @@ -187,20 +187,57 @@ msgstr "El lenguaje del código=%s no pudo ser determinado." msgid "Sorry I do not speak '%s'." msgstr "Lo siento, yo no hablo '%s'." +#: ../extensions/cpsection/modemconfiguration/__init__.py:21 +msgid "Modem Configuration" +msgstr "Configuración del módem" + +#: ../extensions/cpsection/modemconfiguration/view.py:91 +msgid "Username:" +msgstr "Nombre de usuario:" + +#: ../extensions/cpsection/modemconfiguration/view.py:102 +msgid "Password:" +msgstr "Contraseña:" + +#: ../extensions/cpsection/modemconfiguration/view.py:113 +msgid "Number:" +msgstr "Número:" + +#: ../extensions/cpsection/modemconfiguration/view.py:124 +msgid "Access Point Name (APN):" +msgstr "Nombre del Punto de Acceso (APN):" + +#: ../extensions/cpsection/modemconfiguration/view.py:135 +msgid "Personal Identity Number (PIN):" +msgstr "Numero de Identificacion Personal" + +#: ../extensions/cpsection/modemconfiguration/view.py:146 +msgid "Personal Unblocking Key (PUK):" +msgstr "Clave personal de desbloqueo" + +#: ../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 "" +"Necesitará dar la siguiente informacion para configurar una conexion mobil " +"de banda ancha hacia una red celular (3G)." + #: ../extensions/cpsection/network/__init__.py:21 #: ../extensions/cpsection/network/view.py:28 msgid "Network" msgstr "Red" -#: ../extensions/cpsection/network/model.py:79 +#: ../extensions/cpsection/network/model.py:94 msgid "State is unknown." msgstr "Estado desconocido." -#: ../extensions/cpsection/network/model.py:105 +#: ../extensions/cpsection/network/model.py:101 +#: ../extensions/cpsection/network/model.py:158 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:202 msgid "Error in specified argument use 0/1." msgstr "Error en argumento especificado use 0/1." @@ -224,11 +261,11 @@ msgstr "Descarte el historial de la red si tiene problemas de conexión" msgid "Discard network history" msgstr "Descarte historial de la red" -#: ../extensions/cpsection/network/view.py:115 +#: ../extensions/cpsection/network/view.py:117 msgid "Collaboration" msgstr "Colaboración" -#: ../extensions/cpsection/network/view.py:123 +#: ../extensions/cpsection/network/view.py:125 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 " @@ -237,7 +274,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:133 +#: ../extensions/cpsection/network/view.py:135 msgid "Server:" msgstr "Servidor:" @@ -245,30 +282,18 @@ msgstr "Servidor:" msgid "Power" msgstr "Energía" -#: ../extensions/cpsection/power/model.py:54 +#: ../extensions/cpsection/power/model.py:87 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 -msgid "Error in extreme pm argument, use on/off." -msgstr "Error en argumento extremo de pm, use on/off." - -#: ../extensions/cpsection/power/view.py:47 +#: ../extensions/cpsection/power/view.py:44 msgid "Power management" msgstr "Manejo de energía" -#: ../extensions/cpsection/power/view.py:57 +#: ../extensions/cpsection/power/view.py:54 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 -msgid "" -"Extreme power management (disableswireless radio, increases battery life)" -msgstr "" -"Manejo extremo de energía (deshabilita el radio wireless, incrementa la vida " -"de la batería)" - #: ../extensions/deviceicon/battery.py:58 msgid "My Battery" msgstr "Mi batería" @@ -294,7 +319,7 @@ msgstr "Quedan %(hour)d:%(min).2d" msgid "Charged" msgstr "Cargada" -#: ../extensions/deviceicon/network.py:40 +#: ../extensions/deviceicon/network.py:48 #, python-format msgid "IP address: %s" msgstr "Direccion IP: %s" @@ -303,34 +328,92 @@ 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:104 +#: ../extensions/deviceicon/network.py:102 msgid "Disconnect..." msgstr "Desconectando..." -#: ../extensions/deviceicon/network.py:109 -#: ../src/jarabe/desktop/meshbox.py:250 +#: ../extensions/deviceicon/network.py:107 +#: ../extensions/deviceicon/network.py:265 +#: ../src/jarabe/desktop/meshbox.py:241 ../src/jarabe/desktop/meshbox.py:572 +#: ../src/jarabe/desktop/meshbox.py:702 msgid "Connecting..." msgstr "Conectando..." # TODO: show the channel number -#: ../extensions/deviceicon/network.py:113 -#: ../extensions/deviceicon/network.py:166 -#: ../src/jarabe/desktop/meshbox.py:256 +#: ../extensions/deviceicon/network.py:111 +#: ../extensions/deviceicon/network.py:180 +#: ../extensions/deviceicon/network.py:269 +#: ../src/jarabe/desktop/meshbox.py:251 ../src/jarabe/desktop/meshbox.py:578 +#: ../src/jarabe/desktop/meshbox.py:708 msgid "Connected" msgstr "Conectado" -#: ../extensions/deviceicon/network.py:126 +#: ../extensions/deviceicon/network.py:140 msgid "Channel" msgstr "Canal" -#: ../extensions/deviceicon/network.py:141 +#: ../extensions/deviceicon/network.py:155 msgid "Wired Network" msgstr "Red Cableada" -#: ../extensions/deviceicon/network.py:169 +#: ../extensions/deviceicon/network.py:183 msgid "Speed" msgstr "Velocidad" +#: ../extensions/deviceicon/network.py:209 +msgid "Wireless modem" +msgstr "Módem inalámbrico" + +#: ../extensions/deviceicon/network.py:257 +msgid "Please wait..." +msgstr "Espere por favor..." + +#: ../extensions/deviceicon/network.py:260 +#: ../src/jarabe/desktop/meshbox.py:149 ../src/jarabe/desktop/meshbox.py:529 +#: ../src/jarabe/desktop/meshbox.py:659 +msgid "Connect" +msgstr "Conectar" + +#: ../extensions/deviceicon/network.py:261 +msgid "Disconnected" +msgstr "Desconectado" + +#: ../extensions/deviceicon/network.py:264 +#: ../src/jarabe/controlpanel/toolbar.py:115 +#: ../src/jarabe/desktop/homebox.py:68 +#: ../src/jarabe/frame/activitiestray.py:726 +#: ../src/jarabe/frame/activitiestray.py:821 +#: ../src/jarabe/frame/activitiestray.py:849 +msgid "Cancel" +msgstr "Cancelar" + +#: ../extensions/deviceicon/network.py:268 +#: ../src/jarabe/desktop/meshbox.py:153 ../src/jarabe/desktop/meshbox.py:533 +msgid "Disconnect" +msgstr "Desconectar" + +#: ../extensions/deviceicon/network.py:272 +msgid "Sim requires Pin/Puk" +msgstr "Sim requiere Pin/Puk" + +#: ../extensions/deviceicon/network.py:273 +msgid "Authentication Error" +msgstr "Error de autenticación" + +#: ../extensions/deviceicon/network.py:544 +#: ../extensions/deviceicon/network.py:586 +msgid "Mesh Network" +msgstr "Red Malla" + +#: ../extensions/deviceicon/network.py:799 +#, python-format +msgid "Data sent %d kb / received %d kb" +msgstr "Datos enviados %d kb / recibidos %d kb" + +#: ../extensions/deviceicon/network.py:810 +msgid "Connection time " +msgstr "Tiempo de conexión " + #: ../extensions/deviceicon/speaker.py:59 msgid "My Speakers" msgstr "Mis parlantes" @@ -353,6 +436,10 @@ msgid "Backup URL" msgstr "URL de Respaldo" #: ../data/sugar.schemas.in.h:2 +msgid "Bundle IDs of protected activities" +msgstr "Bundle IDs de actividades protegidas" + +#: ../data/sugar.schemas.in.h:3 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. " @@ -360,35 +447,99 @@ msgid "" 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:4 msgid "Corner Delay" msgstr "Retraso de las Esquinas" -#: ../data/sugar.schemas.in.h:4 +#: ../data/sugar.schemas.in.h:5 +msgid "Default font face" +msgstr "Tipo de letra predeterminado" + +#: ../data/sugar.schemas.in.h:6 +msgid "Default font size" +msgstr "Tamaño de letra predeterminado" + +#: ../data/sugar.schemas.in.h:7 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:8 msgid "Delay for the activation of the frame using the edges." msgstr "Retraso para la activación del cuadro utilizando los bordes." # es la mejor traduccion ? -#: ../data/sugar.schemas.in.h:6 +#: ../data/sugar.schemas.in.h:9 msgid "Edge Delay" msgstr "Retraso del Borde" -#: ../data/sugar.schemas.in.h:7 +#: ../data/sugar.schemas.in.h:10 msgid "Favorites Layout" msgstr "Diseño de favoritos" -#: ../data/sugar.schemas.in.h:8 +#: ../data/sugar.schemas.in.h:11 msgid "Favorites resume mode" msgstr "Modo de reanudar favoritos" -#: ../data/sugar.schemas.in.h:9 +#: ../data/sugar.schemas.in.h:12 +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:13 +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:14 +msgid "GSM network APN" +msgstr "APN de red GSM" + +#: ../data/sugar.schemas.in.h:15 +msgid "GSM network PIN" +msgstr "PIN de red GSM" + +#: ../data/sugar.schemas.in.h:16 +msgid "GSM network PUK" +msgstr "PUK de red GSM" + +#: ../data/sugar.schemas.in.h:17 +msgid "GSM network access point name configuration" +msgstr "Configuracion del nombre del punto de acceso a una red GSM" + +#: ../data/sugar.schemas.in.h:18 +msgid "GSM network number" +msgstr "Numero de red GSM" + +#: ../data/sugar.schemas.in.h:19 +msgid "GSM network password" +msgstr "Contraseña de la red GSM" + +#: ../data/sugar.schemas.in.h:20 +msgid "GSM network password configuration" +msgstr "Configuración de password de red GSM" + +#: ../data/sugar.schemas.in.h:21 +msgid "GSM network personal identification number configuration" +msgstr "Configuracion del numero de identificacion personal de una red GSM" + +#: ../data/sugar.schemas.in.h:22 +msgid "GSM network personal unlock key configuration" +msgstr "Configuracion de la clave de desbloqueo personal de una red GSM" + +#: ../data/sugar.schemas.in.h:23 +msgid "GSM network telephone number configuration" +msgstr "Configuracion del numero telefonico de una red GSM" + +#: ../data/sugar.schemas.in.h:24 +msgid "GSM network username" +msgstr "Nombre de usario de una red GSM" + +#: ../data/sugar.schemas.in.h:25 +msgid "GSM network username configuration" +msgstr "Configuración de nombre de usuario de red GSM" + +#: ../data/sugar.schemas.in.h:26 msgid "" "If TRUE, Sugar will make us searchable for the other users of the Jabber " "server." @@ -396,75 +547,96 @@ 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:27 +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 es TRUE, Azúcar mostrará redes defecto Ad-hoc para los canales 1, 6 y 11. Si " +"Azúcar no encuentra ninguna red conocida cuando comienza, se autoconectará a " +"una red Ad-hoc" + +#: ../data/sugar.schemas.in.h:28 msgid "Jabber Server" msgstr "Servidor Jabber" -#: ../data/sugar.schemas.in.h:11 +#: ../data/sugar.schemas.in.h:29 msgid "Layout of the favorites view." msgstr "Distribución de las actividades favoritas." -#: ../data/sugar.schemas.in.h:12 +#: ../data/sugar.schemas.in.h:30 msgid "Power Automatic" msgstr "Manejo automática de energía" -#: ../data/sugar.schemas.in.h:13 +#: ../data/sugar.schemas.in.h:31 msgid "Power Automatic." msgstr "Manejo automática de energía." -#: ../data/sugar.schemas.in.h:14 +#: ../data/sugar.schemas.in.h:32 msgid "Power Extreme" msgstr "Manejo extremo de energía" -#: ../data/sugar.schemas.in.h:15 +#: ../data/sugar.schemas.in.h:33 msgid "Power Extreme." msgstr "Manejo extremo de energía." -#: ../data/sugar.schemas.in.h:16 +#: ../data/sugar.schemas.in.h:34 msgid "Publish to Gadget" msgstr "Publicar en Gadget" -#: ../data/sugar.schemas.in.h:17 +#: ../data/sugar.schemas.in.h:35 msgid "Setting for muting the sound device." msgstr "Configuración para silenciar el dispositivo de sonido." -#: ../data/sugar.schemas.in.h:18 +#: ../data/sugar.schemas.in.h:36 +msgid "Show Sugar Ad-hoc networks" +msgstr "Mostrar redes Ad-hoc" + +#: ../data/sugar.schemas.in.h:37 msgid "Sound Muted" msgstr "Sonido silenciado" -#: ../data/sugar.schemas.in.h:20 +#: ../data/sugar.schemas.in.h:39 msgid "Timezone setting for the system." msgstr "Configuración de zona horaria para el sistema." -#: ../data/sugar.schemas.in.h:21 +#: ../data/sugar.schemas.in.h:40 msgid "Url of the jabber server to use." msgstr "URL del servidor de Jabber para usar." -#: ../data/sugar.schemas.in.h:22 +#: ../data/sugar.schemas.in.h:41 msgid "Url where the backup is saved to." msgstr "URL donde se guarda el backup." -#: ../data/sugar.schemas.in.h:23 +#: ../data/sugar.schemas.in.h:42 msgid "User Color" msgstr "Color del usuario" -#: ../data/sugar.schemas.in.h:24 +#: ../data/sugar.schemas.in.h:43 msgid "User Name" msgstr "Nombre de usuario" -#: ../data/sugar.schemas.in.h:25 +#: ../data/sugar.schemas.in.h:44 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:26 +#: ../data/sugar.schemas.in.h:45 +msgid "" +"Users will not be allowed to erase these activities through the list view." +msgstr "" +"Los usuarios no estarán habilitados a borrar estas actividades desde la " +"vista lista." + +#: ../data/sugar.schemas.in.h:46 msgid "Volume Level" msgstr "Nivel de volumen" -#: ../data/sugar.schemas.in.h:27 +#: ../data/sugar.schemas.in.h:47 msgid "Volume level for the sound device." msgstr "Nivel de volumen para el dispositivo de sonido." -#: ../data/sugar.schemas.in.h:28 +#: ../data/sugar.schemas.in.h:48 msgid "" "When in resume mode, clicking on a favorite icon will cause the last entry " "for that activity to be resumed." @@ -479,7 +651,7 @@ msgid "" "%s module: %r" msgstr "" "sugar-control-panel: ADVERTENCIA, hay más de una opción con el mismo nombre: " -"%s módulo: %r" +"módulo %s: %r" #: ../src/jarabe/controlpanel/cmd.py:30 #, python-format @@ -491,7 +663,7 @@ msgstr "sugar-control-panel: clave=%s no es una opción disponible" msgid "sugar-control-panel: %s" msgstr "sugar-control-panel: %s" -# TRANS: Translators, there's a empty line at the end of this string, +# TRANS: Translators, there's a empty line at the end of this string,

# 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. @@ -508,22 +680,24 @@ msgid "" " -c key clear the current value for the key \n" " " msgstr "" -"Uso: sugar-control-panel [opción] clave [args ...] \n" +"Uso: sugar-control-panel [ opción ] clave [ args ... ] \n" " Control para el ambiente de sugar. \n" " Opciones: \n" -" -h muestra este mensaje de ayuda y sale \n" -" -l enumera todas las opciones disponibles \n" +" -h muestra este mensaje de ayuda y sale \n" +" -l enumera todas las opciones disponibles \n" " -h clave muestra la información sobre esta clave \n" " -g clave obtiene el valor actual de la clave \n" -" -s clave establece el valor actual para la clave \n" -" -c clave borrar el valor actual para la clave \n" +" -s clave establece el valor actual de la clave \n" +" -c clave vaciar el valor actual de la clave \n" " " #: ../src/jarabe/controlpanel/cmd.py:50 msgid "To apply your changes you have to restart sugar.\n" -msgstr "Para aplicar sus cambios tiene que reiniciar sugar.\n" +msgstr "Para aplicar sus cambios tiene que reiniciar Azúcar.\n" #: ../src/jarabe/controlpanel/gui.py:275 +#: ../src/jarabe/journal/journaltoolbox.py:408 +#: ../src/jarabe/journal/volumestoolbar.py:280 msgid "Warning" msgstr "Advertencia" @@ -536,7 +710,7 @@ msgstr "Los cambios requieren reiniciar" msgid "Cancel changes" msgstr "Cancelar cambios" -#: ../src/jarabe/controlpanel/gui.py:284 ../src/jarabe/desktop/homebox.py:113 +#: ../src/jarabe/controlpanel/gui.py:284 ../src/jarabe/desktop/homebox.py:70 msgid "Later" msgstr "Después" @@ -548,18 +722,45 @@ msgstr "Reiniciar ahora" msgid "Done" msgstr "Hecho" -#: ../src/jarabe/controlpanel/toolbar.py:115 -#: ../src/jarabe/desktop/homebox.py:111 -#: ../src/jarabe/frame/activitiestray.py:726 -#: ../src/jarabe/frame/activitiestray.py:821 -#: ../src/jarabe/frame/activitiestray.py:849 -msgid "Cancel" -msgstr "Cancelar" - #: ../src/jarabe/controlpanel/toolbar.py:121 -#: ../src/jarabe/desktop/favoritesview.py:339 +#: ../src/jarabe/desktop/favoritesview.py:332 +#: ../src/jarabe/journal/journalactivity.py:147 msgid "Ok" -msgstr "Ok" +msgstr "Aceptar" + +#: ../src/jarabe/desktop/activitieslist.py:100 +msgid "Confirm erase" +msgstr "Confirmar borrado" + +#: ../src/jarabe/desktop/activitieslist.py:102 +#, python-format +msgid "Confirm erase: Do you want to permanently erase %s?" +msgstr "Confirmar el borrado: ¿Quiere borrar %s de forma permanente?" + +# self._stop_item = MenuItem(_('Stop download'), 'stock-close') +# TODO: Implement stopping downloads +# self._stop_item.connect('activate', self._stop_item_activate_cb) +# self.append_menu_item(self._stop_item) +#: ../src/jarabe/desktop/activitieslist.py:106 +#: ../src/jarabe/frame/clipboardmenu.py:62 +#: ../src/jarabe/view/viewsource.py:218 +msgid "Keep" +msgstr "Guardar" + +#: ../src/jarabe/desktop/activitieslist.py:109 +#: ../src/jarabe/desktop/activitieslist.py:388 +#: ../src/jarabe/journal/journaltoolbox.py:362 +#: ../src/jarabe/journal/palettes.py:119 +msgid "Erase" +msgstr "Borrar" + +#: ../src/jarabe/desktop/activitieslist.py:404 +msgid "Remove favorite" +msgstr "Remover favorito" + +#: ../src/jarabe/desktop/activitieslist.py:408 +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 @@ -591,128 +792,104 @@ msgstr "Caja" msgid "Triangle" msgstr "Triángulo" -#: ../src/jarabe/desktop/favoritesview.py:330 +#: ../src/jarabe/desktop/favoritesview.py:323 msgid "Registration Failed" -msgstr "Registro fallido" +msgstr "Error al registrar" -#: ../src/jarabe/desktop/favoritesview.py:331 +#: ../src/jarabe/desktop/favoritesview.py:324 #, python-format msgid "%s" msgstr "%s" -#: ../src/jarabe/desktop/favoritesview.py:333 +#: ../src/jarabe/desktop/favoritesview.py:326 msgid "Registration Successful" msgstr "Registro exitoso" -#: ../src/jarabe/desktop/favoritesview.py:334 +#: ../src/jarabe/desktop/favoritesview.py:327 msgid "You are now registered with your school server." msgstr "Ahora estás registrado en el servidor de colegio." -#: ../src/jarabe/desktop/favoritesview.py:674 +#: ../src/jarabe/desktop/favoritesview.py:661 msgid "Register" msgstr "Registro" -#: ../src/jarabe/desktop/homebox.py:67 -msgid "Confirm erase" -msgstr "Confirmar borrado" - -#: ../src/jarabe/desktop/homebox.py:69 -#, python-format -msgid "Confirm erase: Do you want to permanently erase %s?" -msgstr "Confirmar el borrado: ¿Quiere borrar %s de forma permanente?" - -# self._stop_item = MenuItem(_('Stop download'), 'stock-close') -# TODO: Implement stopping downloads -# self._stop_item.connect('activate', self._stop_item_activate_cb) -# self.append_menu_item(self._stop_item) -#: ../src/jarabe/desktop/homebox.py:73 ../src/jarabe/frame/clipboardmenu.py:62 -#: ../src/jarabe/view/viewsource.py:218 -msgid "Keep" -msgstr "Guardar" - -#: ../src/jarabe/desktop/homebox.py:76 -#: ../src/jarabe/journal/journaltoolbox.py:357 -#: ../src/jarabe/journal/palettes.py:112 ../src/jarabe/view/palettes.py:153 -msgid "Erase" -msgstr "Borrar" - -#: ../src/jarabe/desktop/homebox.py:106 +#: ../src/jarabe/desktop/homebox.py:63 msgid "Software Update" msgstr "Actualización de Software" -#: ../src/jarabe/desktop/homebox.py:107 +#: ../src/jarabe/desktop/homebox.py:64 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:116 +#: ../src/jarabe/desktop/homebox.py:73 msgid "Check now" msgstr "Pruebe ahora" -#: ../src/jarabe/desktop/homebox.py:233 +#: ../src/jarabe/desktop/homebox.py:190 msgid "List view" msgstr "Vista en lista" -#: ../src/jarabe/desktop/homebox.py:234 +#: ../src/jarabe/desktop/homebox.py:191 msgid "2" msgstr "2" -#: ../src/jarabe/desktop/homebox.py:296 +#: ../src/jarabe/desktop/homebox.py:253 msgid "Favorites view" msgstr "Vista de favoritos" -#: ../src/jarabe/desktop/homebox.py:297 +#: ../src/jarabe/desktop/homebox.py:254 msgid "1" msgstr "1" # This is an encryption key type, not a keyboard key -#: ../src/jarabe/desktop/keydialog.py:131 +#: ../src/jarabe/desktop/keydialog.py:135 msgid "Key Type:" msgstr "Tipo de clave:" -#: ../src/jarabe/desktop/keydialog.py:151 +#: ../src/jarabe/desktop/keydialog.py:155 msgid "Authentication Type:" msgstr "Tipo de autenticación:" -#: ../src/jarabe/desktop/keydialog.py:215 +#: ../src/jarabe/desktop/keydialog.py:220 msgid "WPA & WPA2 Personal" msgstr "WPA y WPA2 Personal" -#: ../src/jarabe/desktop/keydialog.py:224 +#: ../src/jarabe/desktop/keydialog.py:229 msgid "Wireless Security:" msgstr "Seguridad inalámbrica:" -#: ../src/jarabe/desktop/meshbox.py:134 -msgid "Connect" -msgstr "Conectar" +#: ../src/jarabe/desktop/meshbox.py:526 +msgid "Ad-hoc Network %d" +msgstr "Red Ad-hoc %d" -#: ../src/jarabe/desktop/meshbox.py:138 -msgid "Disconnect" -msgstr "Desconectar" +#: ../src/jarabe/desktop/meshbox.py:657 +msgid "Mesh Network %d" +msgstr "Red Malla %d" # TRANS: Action label for resuming an activity. #. TRANS: Action label for resuming an activity. -#: ../src/jarabe/desktop/meshbox.py:444 +#: ../src/jarabe/desktop/meshbox.py:796 #: ../src/jarabe/frame/activitiestray.py:761 -#: ../src/jarabe/journal/journaltoolbox.py:425 -#: ../src/jarabe/journal/palettes.py:72 ../src/jarabe/view/palettes.py:66 +#: ../src/jarabe/journal/journaltoolbox.py:445 +#: ../src/jarabe/journal/palettes.py:73 ../src/jarabe/view/palettes.py:64 msgid "Resume" msgstr "Retomar" -#: ../src/jarabe/desktop/meshbox.py:449 +#: ../src/jarabe/desktop/meshbox.py:801 #: ../src/jarabe/frame/activitiestray.py:235 msgid "Join" msgstr "Unirse" -#: ../src/jarabe/desktop/schoolserver.py:34 +#: ../src/jarabe/desktop/schoolserver.py:35 msgid "Cannot obtain data needed for registration." -msgstr "No se puede obtener datos necesarios para el registro." +msgstr "No se puede obtener datos necesarios para el registro" -#: ../src/jarabe/desktop/schoolserver.py:51 +#: ../src/jarabe/desktop/schoolserver.py:52 msgid "Cannot connect to the server." msgstr "No se puede conectar al servidor." -#: ../src/jarabe/desktop/schoolserver.py:56 +#: ../src/jarabe/desktop/schoolserver.py:57 msgid "The server could not complete the request." msgstr "El servidor no pudo completar el pedido." @@ -756,6 +933,11 @@ msgstr "Aceptar" msgid "%s (%s)" msgstr "%s (%s)" +#: ../src/jarabe/frame/activitiestray.py:750 +#: ../src/jarabe/frame/activitiestray.py:873 +msgid "Dismiss" +msgstr "Descartar" + #: ../src/jarabe/frame/activitiestray.py:810 #, python-format msgid "Transfer to %r" @@ -807,30 +989,31 @@ msgstr "Atrás" msgid "Next" msgstr "Siguiente" -#: ../src/jarabe/journal/collapsedentry.py:258 -#: ../src/jarabe/journal/expandedentry.py:159 +#: ../src/jarabe/journal/collapsedentry.py:260 +#: ../src/jarabe/journal/expandedentry.py:158 #: ../src/jarabe/journal/palettes.py:66 +#: ../src/jarabe/journal/volumestoolbar.py:114 msgid "Untitled" msgstr "Sin título" -#: ../src/jarabe/journal/expandedentry.py:205 +#: ../src/jarabe/journal/expandedentry.py:211 msgid "No preview" msgstr "Sin vista previa" -#: ../src/jarabe/journal/expandedentry.py:224 +#: ../src/jarabe/journal/expandedentry.py:230 msgid "Participants:" msgstr "Participantes:" -#: ../src/jarabe/journal/expandedentry.py:247 +#: ../src/jarabe/journal/expandedentry.py:253 msgid "Description:" msgstr "Descripción:" -#: ../src/jarabe/journal/expandedentry.py:273 +#: ../src/jarabe/journal/expandedentry.py:282 msgid "Tags:" msgstr "Etiquetas:" -#: ../src/jarabe/journal/journalactivity.py:108 -#: ../src/jarabe/journal/volumestoolbar.py:47 +#: ../src/jarabe/journal/journalactivity.py:111 +#: ../src/jarabe/journal/volumestoolbar.py:166 msgid "Journal" msgstr "Diario" @@ -887,27 +1070,42 @@ msgid "Anything" msgstr "Cualquiera" # TODO: Add "Start with" menu item -#: ../src/jarabe/journal/journaltoolbox.py:347 -#: ../src/jarabe/journal/palettes.py:90 +#: ../src/jarabe/journal/journaltoolbox.py:352 +#: ../src/jarabe/journal/palettes.py:97 msgid "Copy" msgstr "Copiar" +#: ../src/jarabe/journal/journaltoolbox.py:407 +#: ../src/jarabe/journal/volumestoolbar.py:279 +msgid "Entries without a file cannot be copied." +msgstr "Entradas sin un archivo no pueden ser copiadas" + +#: ../src/jarabe/journal/journaltoolbox.py:416 +#: ../src/jarabe/journal/volumestoolbar.py:288 +msgid "Error while copying the entry. %s" +msgstr "Error copiando la entrada. %s" + +#: ../src/jarabe/journal/journaltoolbox.py:417 +#: ../src/jarabe/journal/volumestoolbar.py:289 +msgid "Error" +msgstr "Error" + # TRANS: Action label for starting an entry. #. TRANS: Action label for starting an entry. -#: ../src/jarabe/journal/journaltoolbox.py:428 -#: ../src/jarabe/journal/palettes.py:75 ../src/jarabe/view/palettes.py:135 +#: ../src/jarabe/journal/journaltoolbox.py:448 +#: ../src/jarabe/journal/palettes.py:76 ../src/jarabe/view/palettes.py:122 msgid "Start" msgstr "Iniciar" -#: ../src/jarabe/journal/listview.py:40 +#: ../src/jarabe/journal/listview.py:35 msgid "Your Journal is empty" msgstr "Su diario está vacío" -#: ../src/jarabe/journal/listview.py:41 +#: ../src/jarabe/journal/listview.py:36 msgid "No matching entries " msgstr "No hay entradas coincidentes " -#: ../src/jarabe/journal/listview.py:370 +#: ../src/jarabe/journal/listview.py:366 msgid "Clear search" msgstr "Limpiar búsqueda" @@ -938,40 +1136,40 @@ msgstr "Escoja un objeto" msgid "Close" msgstr "Cerrar" -#: ../src/jarabe/journal/palettes.py:73 +#: ../src/jarabe/journal/palettes.py:74 msgid "Resume with" msgstr "Reiniciar con" -#: ../src/jarabe/journal/palettes.py:76 +#: ../src/jarabe/journal/palettes.py:77 msgid "Start with" msgstr "Empezar con" -#: ../src/jarabe/journal/palettes.py:98 +#: ../src/jarabe/journal/palettes.py:90 ../src/jarabe/journal/palettes.py:222 +msgid "No activity to start entry" +msgstr "No se encontró una actividad para iniciar la entrada" + +#: ../src/jarabe/journal/palettes.py:105 msgid "Send to" msgstr "Enviar a" -#: ../src/jarabe/journal/palettes.py:107 +#: ../src/jarabe/journal/palettes.py:114 msgid "View Details" msgstr "Ver detalles" -#: ../src/jarabe/journal/palettes.py:185 +#: ../src/jarabe/journal/palettes.py:187 msgid "No friends present" msgstr "No hay amigos presentes" # tildes -#: ../src/jarabe/journal/palettes.py:190 +#: ../src/jarabe/journal/palettes.py:192 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:220 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" - # "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 @@ -983,57 +1181,45 @@ msgid "Make friend" msgstr "Agregar amigo" #: ../src/jarabe/view/buddymenu.py:82 -msgid "My Settings" -msgstr "Mis ajustes" +msgid "Shutdown" +msgstr "Apagar" #: ../src/jarabe/view/buddymenu.py:90 msgid "Logout" msgstr "Salir" #: ../src/jarabe/view/buddymenu.py:95 -msgid "Restart" -msgstr "Reiniciar" - -#: ../src/jarabe/view/buddymenu.py:100 -msgid "Shutdown" -msgstr "Apagar" +msgid "My Settings" +msgstr "Mis ajustes" -#: ../src/jarabe/view/buddymenu.py:135 +#: ../src/jarabe/view/buddymenu.py:130 #, python-format msgid "Invite to %s" msgstr "Invitar a %s" -#: ../src/jarabe/view/palettes.py:47 +#: ../src/jarabe/view/palettes.py:45 msgid "Starting..." msgstr "Iniciando..." #. TODO: share-with, keep -#: ../src/jarabe/view/palettes.py:73 +#: ../src/jarabe/view/palettes.py:71 msgid "View Source" msgstr "Ver fuente" -#: ../src/jarabe/view/palettes.py:84 +#: ../src/jarabe/view/palettes.py:82 msgid "Stop" msgstr "Parar" -#: ../src/jarabe/view/palettes.py:174 -msgid "Remove favorite" -msgstr "Remover favorito" - -#: ../src/jarabe/view/palettes.py:178 -msgid "Make favorite" -msgstr "Hacer favorito" - -#: ../src/jarabe/view/palettes.py:241 +#: ../src/jarabe/view/palettes.py:171 msgid "Show contents" msgstr "Mostrar contenidos" -#: ../src/jarabe/view/palettes.py:263 ../src/jarabe/view/palettes.py:313 +#: ../src/jarabe/view/palettes.py:193 ../src/jarabe/view/palettes.py:243 #, python-format msgid "%(free_space)d MB Free" msgstr "%(free_space)d MB libres" -#: ../src/jarabe/view/palettes.py:288 +#: ../src/jarabe/view/palettes.py:218 msgid "Unmount" msgstr "Desmontar" @@ -1054,10 +1240,195 @@ msgstr "Fuente del paquete de la actividad" msgid "View source: %r" msgstr "Ver código fuente: %r" +#~ msgid "Create new wireless network" +#~ msgstr "Crear nueva red inalámbrica" + +#~ msgid "%s's network" +#~ msgstr "Red de %s" + +#~ msgid "Restart" +#~ msgstr "Reiniciar" + +#~ msgid "Keyboard" +#~ msgstr "Teclado" + +#~ msgid "Keyboard Model" +#~ msgstr "Modelo de teclado" + +#~ msgid "Key(s) to change layout" +#~ msgstr "Tecla(s) para cambiar el diseño" + +#~ msgid "Keyboard Layout(s)" +#~ msgstr "Diseño(s) de teclado" + #~ msgid "" -#~ "© 2008 One Laptop per Child Association Inc; Red Hat Inc; and Contributors." +#~ "Add languages in the order you prefer. If a translation is not available, " +#~ "the next in the list will be used." #~ msgstr "" -#~ "© 2008 One Laptop per Child Association Inc; Red Hat Inc; y Contribuyentes." +#~ "Añade idiomas en el orden que prefieres. Si una traducción no se " +#~ "encuentra disponible, se usará la siguiente en la lista." + +# Access point name for GPRS network +#~ msgid "APN:" +#~ msgstr "Nombre del Punto de Acceso:" + +#~ msgid "Error in extreme pm argument, use on/off." +#~ msgstr "Error en argumento extremo de manejo de energía, use on/off." + +# best translationfor now +#~ msgid "" +#~ "Extreme power management (disableswireless radio, increases battery life)" +#~ msgstr "" +#~ "Manejo extremo de energía (deshabilita el radio wireless, incrementa la " +#~ "vida de la batería)" + +#~ msgid "Software update" +#~ msgstr "Actualización de software" + +#~ msgid "" +#~ "Software updates correct errors, eliminate security vulnerabilities, and " +#~ "provide new features." +#~ msgstr "" +#~ "Las actualizaciones de software corrigen errores, eliminan " +#~ "vulnerabilidades de seguridad y proveen nuevas características." + +#~ msgid "Checking %s..." +#~ msgstr "Probando %s..." + +#~ msgid "Downloading %s..." +#~ msgstr "Descargando %s..." + +#~ msgid "Updating %s..." +#~ msgstr "Actualizando %s..." + +#~ msgid "Your software is up-to-date" +#~ msgstr "Tu software esta actualizado" + +#~ 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" + +#~ msgid "Checking for updates..." +#~ msgstr "Buscando actualizaciones..." + +#~ msgid "Installing updates..." +#~ msgstr "Instalando actualizaciones..." + +#~ msgid "%s update was installed" +#~ msgid_plural "%s updates were installed" +#~ msgstr[0] "%s actualización fue instalada" +#~ msgstr[1] "%s actualizaciones fueron instaladas" + +#~ msgid "Install selected" +#~ msgstr "Instalación seleccionada" + +#~ msgid "Download size: %s" +#~ msgstr "Tamaño de descarga: %s" + +#~ msgid "From version %(current)d to %(new)s (Size: %(size)s)" +#~ msgstr "Desde la version %(current)d hacia %(new)s (Size: %(size)s)" + +#~ msgid "None" +#~ msgstr "Ninguno" + +#~ msgid "1 KB" +#~ msgstr "1 KB" + +#~ msgid "%.0f KB" +#~ msgstr "%.0f KB" + +#~ msgid "%.1f MB" +#~ msgstr "%.1f MB" + +#~ msgid "Mesh" +#~ msgstr "Malla" + +#~ msgid "Screenshot of \"%s\"" +#~ msgstr "Captura pantalla de \"%s\"" + +#~ 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." + +#~ msgid "Default nick" +#~ msgstr "Apodo predeterminado" + +#~ msgid "If TRUE, Sugar will show a \"Log out\" option." +#~ msgstr "Si es TRUE, Azúcar mostrará una opción \"Terminar Sesión\"." + +#~ msgid "Keyboard layouts" +#~ msgstr "Distribuciones del teclado" + +#~ msgid "Keyboard model" +#~ msgstr "Modelo del teclado" + +#~ msgid "Keyboard options" +#~ msgstr "Opciones del teclado" + +#~ 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)" + +#~ msgid "List of keyboard options." +#~ msgstr "Lista de las opciones del teclado." + +#~ msgid "Show Log out" +#~ msgstr "Mostrar Terminar Sesión" + +#~ msgid "The keyboard model to be used" +#~ msgstr "El modelo del teclado que se utilizará" + +#~ msgid "Version %s" +#~ msgstr "Versión %s" + +#~ msgid "F1" +#~ msgstr "F1" + +#~ msgid "F2" +#~ msgstr "F2" + +#~ msgid "F3" +#~ msgstr "F3" + +#~ msgid "F4" +#~ msgstr "F4" + +#~ msgid "Kind: %s" +#~ msgstr "Tipo: %s" + +#~ msgid "Unknown" +#~ msgstr "Desconocido" + +#~ msgid "Date: %s" +#~ msgstr "Fecha: %s" + +#~ msgid "Size: %s" +#~ msgstr "Tamaño: %s" + +#~ msgid "Start new" +#~ msgstr "Empezar nuevo" + +#~ msgid "Title" +#~ msgstr "Título" + +#~ msgid "Version" +#~ msgstr "Versión" + +#~ msgid "Date" +#~ msgstr "Fecha" + +#~ msgid "" +#~ "© 2008 One Laptop per Child Association Inc; Red Hat Inc; and " +#~ "Contributors." +#~ msgstr "" +#~ "© 2008 One Laptop per Child Association Inc; Red Hat Inc; y " +#~ "Contribuyentes." #~ msgid "Document" #~ msgstr "Documento" @@ -1076,18 +1447,9 @@ 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" -#~ msgid "Mesh" -#~ msgstr "Malla" - #~ msgid "Connected to a School Mesh Portal" #~ msgstr "Conectado a un enlace escolar de red malla" @@ -1113,7 +1475,6 @@ msgstr "Ver código fuente: %r" #~ msgid "Settings" #~ msgstr "Configuración " -#, python-format #~ msgid "Clipboard object: %s." #~ msgstr "Objeto de portapapel: %s." @@ -1128,7 +1489,7 @@ msgstr "Ver código fuente: %r" #, fuzzy #~ msgid "Ring view" -#~ msgstr "Vista de llamada" +#~ msgstr "Vista de anillo" #~ msgid "Remove from ring" #~ msgstr "Eliminar del anillo" @@ -1158,16 +1519,12 @@ msgstr "Ver código fuente: %r" #~ msgstr "encendido" #~ msgid "Permission denied. You need to be root to run this method." -#~ msgstr "permiso denegado. Usted necesita ser root para ejecutar este método." +#~ msgstr "" +#~ "permiso denegado. Usted necesita ser root para ejecutar este método." #~ msgid "Error in reading timezone" #~ msgstr "Error en la lectura de la zona horaria" -#, python-format -#~ msgid "Error copying timezone (from %s): %s" -#~ msgstr "Error copiando zona horaria (desde %s): %s" - -#, python-format #~ msgid "Changing permission of timezone: %s" #~ msgstr "Cambiando permisos de zona horaria: %s" @@ -1231,43 +1588,18 @@ msgstr "Ver código fuente: %r" #~ msgid "Paste" #~ msgstr "Pegar" -#, python-format #~ msgid "%s Activity" #~ msgstr "Actividad %s" -#, python-format -#~ msgid "Text snippet" -#~ "Web Page" -#~ "PDF file" -#~ "MS Word file" -#~ "RTF file" -#~ "Abiword file" -#~ "Squeak project" -#~ "OpenOffice text file" -#~ "Object" -#~ "Pick a buddy picture" -#~ "My Picture:" -#~ "My Color:" -#~ "Stop download" -#~ "Close" -#~ "No options" -#~ "Send" -#~ msgstr "Recorte de texto" -#~ "Página web" -#~ "Archivo PDF" -#~ "Archivo MS-Word" -#~ "Archivo RTF" -#~ "Archivo Abiword" -#~ "Proyecto de Squeak" -#~ "Archivo de texto de OpenOffice" -#~ "Objeto" -#~ "Elegir la imagen de amigo" -#~ "Mi imagen:" -#~ "Mi color:" -#~ "Interrumpir la bajada" -#~ "Cerrar" -#~ "Ninguna opción" -#~ "Enviar" +#~ msgid "" +#~ "Text snippetWeb PagePDF fileMS Word fileRTF fileAbiword fileSqueak " +#~ "projectOpenOffice text fileObjectPick a buddy pictureMy Picture:My Color:" +#~ "Stop downloadCloseNo optionsSend" +#~ msgstr "" +#~ "Recorte de textoPágina webArchivo PDFArchivo MS-WordArchivo RTFArchivo " +#~ "AbiwordProyecto de SqueakArchivo de texto de OpenOfficeObjetoElegir la " +#~ "imagen de amigoMi imagen:Mi color:Interrumpir la bajadaCerrarNinguna " +#~ "opciónEnviar" #~ msgid "Keep error" #~ msgstr "Error de guardado" @@ -1287,55 +1619,42 @@ msgstr "Ver código fuente: %r" #~ msgid "OK" #~ msgstr "OK" -#, python-format #~ msgid "%d year" #~ msgstr "%d año" -#, python-format #~ msgid "%d years" #~ msgstr "%d años" -#, python-format #~ msgid "%d month" #~ msgstr "%d mes" -#, python-format #~ msgid "%d months" #~ msgstr "%d meses" -#, python-format #~ msgid "%d week" #~ msgstr "%d semana" -#, python-format #~ msgid "%d weeks" #~ msgstr "%d semanas" -#, python-format #~ msgid "%d day" #~ msgstr "%d día" -#, python-format #~ msgid "%d days" #~ msgstr "%d días" -#, python-format #~ msgid "%d hour" #~ msgstr "%d hora" -#, python-format #~ msgid "%d hours" #~ msgstr "%d horas" -#, python-format #~ msgid "%d minute" #~ msgstr "%d minuto" -#, python-format #~ msgid "%d minutes" #~ msgstr "%d minutos" -#, python-format #~ msgid "%d second" #~ msgstr "%d segundo" @@ -1344,17 +1663,3 @@ msgstr "Ver código fuente: %r" #~ msgid ", " #~ msgstr ", " - -#: ../extensions/deviceicon/network.py:114 -msgid "Create new wireless network" -msgstr "Crear nueva red inalámbrica" - -#: ../src/jarabe/frame/activitiestray.py:750 -#: ../src/jarabe/frame/activitiestray.py:875 -msgid "Dismiss" -msgstr "Descartar" - -#: ../extensions/deviceicon/network.py:415 -#, python-format -msgid "%s's network %s" -msgstr "%s's red %s" diff --git a/po/fr.po b/po/fr.po index 360f771..49c14cd 100644 --- a/po/fr.po +++ b/po/fr.po @@ -6,16 +6,16 @@ msgid "" msgstr "" "Project-Id-Version: sugar\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2009-04-29 17:36-0400\n" -"PO-Revision-Date: 2009-05-01 11:55-0400\n" +"POT-Creation-Date: 2010-05-11 01:07+0530\n" +"PO-Revision-Date: 2010-02-27 11:34+0200\n" "Last-Translator: samy boutayeb \n" "Language-Team: French \n" -"Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"Language: fr\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -"X-Generator: Pootle 1.2.1\n" +"X-Generator: Pootle 2.0.1\n" #: ../extensions/cpsection/aboutme/__init__.py:24 msgid "About Me" @@ -125,7 +125,7 @@ msgstr "Date & heure" msgid "Error timezone does not exist." msgstr "Erreur : le fuseau horaire n'existe pas." -#: ../extensions/cpsection/datetime/view.py:68 ../data/sugar.schemas.in.h:19 +#: ../extensions/cpsection/datetime/view.py:68 ../data/sugar.schemas.in.h:35 msgid "Timezone" msgstr "Fuseau horaire" @@ -182,20 +182,55 @@ msgstr "La langue associée au code = %s n'a pas pu être déterminée." msgid "Sorry I do not speak '%s'." msgstr "Désolé je ne parle pas '%s'." +#: ../extensions/cpsection/modemconfiguration/__init__.py:21 +msgid "Modem Configuration" +msgstr "Configuration du modem" + +#: ../extensions/cpsection/modemconfiguration/view.py:91 +msgid "Username:" +msgstr "Identifiant :" + +#: ../extensions/cpsection/modemconfiguration/view.py:102 +msgid "Password:" +msgstr "Mot de passe :" + +#: ../extensions/cpsection/modemconfiguration/view.py:113 +msgid "Number:" +msgstr "Nombre :" + +#: ../extensions/cpsection/modemconfiguration/view.py:124 +msgid "Access Point Name (APN):" +msgstr "APN:" + +#: ../extensions/cpsection/modemconfiguration/view.py:135 +msgid "Personal Identity Number (PIN):" +msgstr "PIN:" + +#: ../extensions/cpsection/modemconfiguration/view.py:146 +msgid "Personal Unblocking Key (PUK):" +msgstr "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 "" + #: ../extensions/cpsection/network/__init__.py:21 #: ../extensions/cpsection/network/view.py:28 msgid "Network" msgstr "Réseau" -#: ../extensions/cpsection/network/model.py:79 +#: ../extensions/cpsection/network/model.py:87 msgid "State is unknown." msgstr "État inconnu." -#: ../extensions/cpsection/network/model.py:105 +#: ../extensions/cpsection/network/model.py:115 +#: ../extensions/cpsection/network/model.py:154 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:197 msgid "Error in specified argument use 0/1." msgstr "Argument spécifié incorrect. Utiliser 0/1." @@ -214,18 +249,17 @@ msgstr "Radio" #: ../extensions/cpsection/network/view.py:93 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" +"Ignorer l'historique du réseau si vous avez du mal à vous connecter au réseau" #: ../extensions/cpsection/network/view.py:102 msgid "Discard network history" msgstr "Ignorer l'historique du réseau" -#: ../extensions/cpsection/network/view.py:115 +#: ../extensions/cpsection/network/view.py:117 msgid "Collaboration" msgstr "Collaboration" -#: ../extensions/cpsection/network/view.py:123 +#: ../extensions/cpsection/network/view.py:125 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 " @@ -235,7 +269,7 @@ msgstr "" "personnes présentes sur le même serveur pourront se voir même si elles ne se " "trouvent pas sur le même réseau." -#: ../extensions/cpsection/network/view.py:133 +#: ../extensions/cpsection/network/view.py:135 msgid "Server:" msgstr "Serveur :" @@ -243,36 +277,25 @@ msgstr "Serveur :" msgid "Power" msgstr "Alimentation" -#: ../extensions/cpsection/power/model.py:54 +#: ../extensions/cpsection/power/model.py:87 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 -msgid "Error in extreme pm argument, use on/off." -msgstr "Erreur dans l'argument gestion de l'alimentation extrême" - -#: ../extensions/cpsection/power/view.py:47 +#: ../extensions/cpsection/power/view.py:44 msgid "Power management" msgstr "Gestion de l'alimentation" -#: ../extensions/cpsection/power/view.py:57 +#: ../extensions/cpsection/power/view.py:54 msgid "Automatic power management (increases battery life)" msgstr "Gestion automatique de l'alimentation (prolonge la batterie)" -#: ../extensions/cpsection/power/view.py:85 -msgid "" -"Extreme power management (disableswireless radio, increases battery life)" -msgstr "" -"Gestion extrême de l'alimentation (désactive la radio sans fil, prolonge la " -"durée de vie de la batterie)" - #: ../extensions/deviceicon/battery.py:58 msgid "My Battery" msgstr "Ma batterie" #: ../extensions/deviceicon/battery.py:137 msgid "Removed" -msgstr "Retirer" +msgstr "Retiré" #: ../extensions/deviceicon/battery.py:140 msgid "Charging" @@ -291,7 +314,7 @@ msgstr "%(hour)d:%(min).2d restantes" msgid "Charged" msgstr "Charge complète" -#: ../extensions/deviceicon/network.py:40 +#: ../extensions/deviceicon/network.py:47 #, python-format msgid "IP address: %s" msgstr "Adresse IP : %s" @@ -300,34 +323,94 @@ msgstr "Adresse IP : %s" # priority over the normal wireless device. NM doesn't have a "disconnect" # method for a device either (for various reasons) so this doesn't # have a good mapping -#: ../extensions/deviceicon/network.py:104 +#: ../extensions/deviceicon/network.py:109 msgid "Disconnect..." msgstr "Déconnexion..." -#: ../extensions/deviceicon/network.py:109 -#: ../src/jarabe/desktop/meshbox.py:250 +#: ../extensions/deviceicon/network.py:113 +msgid "Create new wireless network" +msgstr "Créer un nouveau réseau sans fil" + +#: ../extensions/deviceicon/network.py:119 +#: ../extensions/deviceicon/network.py:270 +#: ../src/jarabe/desktop/meshbox.py:236 msgid "Connecting..." msgstr "Connexion..." # TODO: show the channel number -#: ../extensions/deviceicon/network.py:113 -#: ../extensions/deviceicon/network.py:166 -#: ../src/jarabe/desktop/meshbox.py:256 +#: ../extensions/deviceicon/network.py:123 +#: ../extensions/deviceicon/network.py:185 +#: ../extensions/deviceicon/network.py:274 +#: ../src/jarabe/desktop/meshbox.py:242 msgid "Connected" msgstr "Connecté" -#: ../extensions/deviceicon/network.py:126 +#: ../extensions/deviceicon/network.py:145 msgid "Channel" msgstr "Canal" -#: ../extensions/deviceicon/network.py:141 +#: ../extensions/deviceicon/network.py:160 msgid "Wired Network" msgstr "Réseau filaire" -#: ../extensions/deviceicon/network.py:169 +#: ../extensions/deviceicon/network.py:188 msgid "Speed" msgstr "Vitesse" +#: ../extensions/deviceicon/network.py:214 +msgid "Wireless modem" +msgstr "Modem sans fil" + +#: ../extensions/deviceicon/network.py:262 +msgid "Please wait..." +msgstr "Patienter..." + +#: ../extensions/deviceicon/network.py:265 +#: ../src/jarabe/desktop/meshbox.py:150 +msgid "Connect" +msgstr "Connecter" + +#: ../extensions/deviceicon/network.py:266 +msgid "Disconnected" +msgstr "Déconnecté" + +#: ../extensions/deviceicon/network.py:269 +#: ../src/jarabe/controlpanel/toolbar.py:115 +#: ../src/jarabe/desktop/homebox.py:111 +#: ../src/jarabe/frame/activitiestray.py:726 +#: ../src/jarabe/frame/activitiestray.py:821 +#: ../src/jarabe/frame/activitiestray.py:849 +msgid "Cancel" +msgstr "Annuler" + +#: ../extensions/deviceicon/network.py:273 +#: ../src/jarabe/desktop/meshbox.py:154 +msgid "Disconnect" +msgstr "Déconnecter" + +#: ../extensions/deviceicon/network.py:277 +msgid "Sim requires Pin/Puk" +msgstr "" + +#: ../extensions/deviceicon/network.py:278 +#, fuzzy +msgid "Authentication Error" +msgstr "Type d'authentification :" + +#: ../extensions/deviceicon/network.py:526 +#, python-format +msgid "%s's network" +msgstr "Réseau %s" + +#: ../extensions/deviceicon/network.py:734 +#, python-format +msgid "Data sent %d kb / received %d kb" +msgstr "Données envoyées %d ko / reçues %d ko" + +#: ../extensions/deviceicon/network.py:745 +msgid "Connection time " +msgstr "Durée de connexion" + #: ../extensions/deviceicon/speaker.py:59 msgid "My Speakers" msgstr "Haut-parleurs" @@ -355,110 +438,179 @@ msgid "" "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" +"et du remplissage. Le format correspond aux couleurs RVB. Exemple : #AC32FF," +"#9A5200" #: ../data/sugar.schemas.in.h:3 msgid "Corner Delay" msgstr "Délai des coins" #: ../data/sugar.schemas.in.h:4 +msgid "Default font face" +msgstr "Police par défaut" + +#: ../data/sugar.schemas.in.h:5 +msgid "Default font size" +msgstr "Corps de la police par défaut" + +#: ../data/sugar.schemas.in.h:6 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:7 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:8 msgid "Edge Delay" msgstr "Délai des bords" -#: ../data/sugar.schemas.in.h:7 +#: ../data/sugar.schemas.in.h:9 msgid "Favorites Layout" msgstr "Disposition favorite" -#: ../data/sugar.schemas.in.h:8 +#: ../data/sugar.schemas.in.h:10 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:11 +msgid "Font face that is used throughout the desktop." +msgstr "Police utilisée sur le bureau." + +#: ../data/sugar.schemas.in.h:12 +msgid "Font size that is used throughout the desktop." +msgstr "Corps de la police utilisée sur le bureau." + +#: ../data/sugar.schemas.in.h:13 +#, fuzzy +msgid "GSM network APN" +msgstr "Réseau maillé %d" + +#: ../data/sugar.schemas.in.h:14 +#, fuzzy +msgid "GSM network PIN" +msgstr "Réseau maillé %d" + +#: ../data/sugar.schemas.in.h:15 +#, fuzzy +msgid "GSM network PUK" +msgstr "Réseau maillé %d" + +#: ../data/sugar.schemas.in.h:16 +msgid "GSM network access point name configuration" +msgstr "" + +#: ../data/sugar.schemas.in.h:17 +msgid "GSM network number" +msgstr "" + +#: ../data/sugar.schemas.in.h:18 +msgid "GSM network password" +msgstr "" + +#: ../data/sugar.schemas.in.h:19 +#, fuzzy +msgid "GSM network password configuration" +msgstr "Configuration du modem" + +#: ../data/sugar.schemas.in.h:20 +msgid "GSM network personal identification number configuration" +msgstr "" + +#: ../data/sugar.schemas.in.h:21 +msgid "GSM network personal unlock key configuration" +msgstr "" + +#: ../data/sugar.schemas.in.h:22 +msgid "GSM network telephone number configuration" +msgstr "" + +#: ../data/sugar.schemas.in.h:23 +msgid "GSM network username" +msgstr "" + +#: ../data/sugar.schemas.in.h:24 +#, fuzzy +msgid "GSM network username configuration" +msgstr "Configuration du modem" + +#: ../data/sugar.schemas.in.h:25 msgid "" "If TRUE, Sugar will make us searchable for the other users of the Jabber " "server." msgstr "" -"Si VRAI, Sugar les autres utilisateurs du serveur Jabber pourront nous " +"Si VRAI, Sugar permettra aux autres utilisateurs du serveur Jabber de nous " "retrouver." -#: ../data/sugar.schemas.in.h:10 +#: ../data/sugar.schemas.in.h:26 msgid "Jabber Server" msgstr "Serveur Jabber" -#: ../data/sugar.schemas.in.h:11 +#: ../data/sugar.schemas.in.h:27 msgid "Layout of the favorites view." msgstr "Disposition de la vue favorite." -#: ../data/sugar.schemas.in.h:12 +#: ../data/sugar.schemas.in.h:28 msgid "Power Automatic" msgstr "Alimentation automatique" -#: ../data/sugar.schemas.in.h:13 +#: ../data/sugar.schemas.in.h:29 msgid "Power Automatic." msgstr "Alimentation automatique." -#: ../data/sugar.schemas.in.h:14 +#: ../data/sugar.schemas.in.h:30 msgid "Power Extreme" msgstr "Alimentation extrême" -#: ../data/sugar.schemas.in.h:15 +#: ../data/sugar.schemas.in.h:31 msgid "Power Extreme." msgstr "Alimentation extrême." -#: ../data/sugar.schemas.in.h:16 +#: ../data/sugar.schemas.in.h:32 msgid "Publish to Gadget" msgstr "Publication vers Gadget" -#: ../data/sugar.schemas.in.h:17 +#: ../data/sugar.schemas.in.h:33 msgid "Setting for muting the sound device." msgstr "Configuration de la mise en sourdine du périphérique audio." -#: ../data/sugar.schemas.in.h:18 +#: ../data/sugar.schemas.in.h:34 msgid "Sound Muted" msgstr "Audio désactivé" -#: ../data/sugar.schemas.in.h:20 +#: ../data/sugar.schemas.in.h:36 msgid "Timezone setting for the system." msgstr "Configuration du fuseau horaire du système." -#: ../data/sugar.schemas.in.h:21 +#: ../data/sugar.schemas.in.h:37 msgid "Url of the jabber server to use." msgstr "URL du serveur Jabber à utiliser." -#: ../data/sugar.schemas.in.h:22 +#: ../data/sugar.schemas.in.h:38 msgid "Url where the backup is saved to." msgstr "URL d'enregistrement de la sauvegarde." -#: ../data/sugar.schemas.in.h:23 +#: ../data/sugar.schemas.in.h:39 msgid "User Color" msgstr "Couleurs de l'utilisateur" -#: ../data/sugar.schemas.in.h:24 +#: ../data/sugar.schemas.in.h:40 msgid "User Name" msgstr "Nom de l'utilisateur" -#: ../data/sugar.schemas.in.h:25 +#: ../data/sugar.schemas.in.h:41 msgid "User name that is used throughout the desktop." msgstr "Nom identifiant l'utilisateur sur le bureau." -#: ../data/sugar.schemas.in.h:26 +#: ../data/sugar.schemas.in.h:42 msgid "Volume Level" msgstr "Niveau de volume" -#: ../data/sugar.schemas.in.h:27 +#: ../data/sugar.schemas.in.h:43 msgid "Volume level for the sound device." msgstr "Niveau de volume du périphérique audio." -#: ../data/sugar.schemas.in.h:28 +#: ../data/sugar.schemas.in.h:44 msgid "" "When in resume mode, clicking on a favorite icon will cause the last entry " "for that activity to be resumed." @@ -541,14 +693,6 @@ msgstr "Maintenant" msgid "Done" msgstr "Accepter" -#: ../src/jarabe/controlpanel/toolbar.py:115 -#: ../src/jarabe/desktop/homebox.py:111 -#: ../src/jarabe/frame/activitiestray.py:726 -#: ../src/jarabe/frame/activitiestray.py:821 -#: ../src/jarabe/frame/activitiestray.py:849 -msgid "Cancel" -msgstr "Annuler" - #: ../src/jarabe/controlpanel/toolbar.py:121 #: ../src/jarabe/desktop/favoritesview.py:339 msgid "Ok" @@ -599,9 +743,9 @@ msgstr "Enregistrement réussi" #: ../src/jarabe/desktop/favoritesview.py:334 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:674 +#: ../src/jarabe/desktop/favoritesview.py:677 msgid "Register" msgstr "S'enregistrer" @@ -658,40 +802,32 @@ msgstr "Écran favoris" msgid "1" msgstr "1" -#: ../src/jarabe/desktop/keydialog.py:131 +#: ../src/jarabe/desktop/keydialog.py:135 msgid "Key Type:" msgstr "Type de clé :" -#: ../src/jarabe/desktop/keydialog.py:151 +#: ../src/jarabe/desktop/keydialog.py:155 msgid "Authentication Type:" msgstr "Type d'authentification :" -#: ../src/jarabe/desktop/keydialog.py:215 +#: ../src/jarabe/desktop/keydialog.py:220 msgid "WPA & WPA2 Personal" msgstr "WPA & WPA2 Personal" -#: ../src/jarabe/desktop/keydialog.py:224 +#: ../src/jarabe/desktop/keydialog.py:229 msgid "Wireless Security:" msgstr "Sécurité sans fil :" -#: ../src/jarabe/desktop/meshbox.py:134 -msgid "Connect" -msgstr "Connecter" - -#: ../src/jarabe/desktop/meshbox.py:138 -msgid "Disconnect" -msgstr "Déconnecter" - # TRANS: Action label for resuming an activity. #. TRANS: Action label for resuming an activity. -#: ../src/jarabe/desktop/meshbox.py:444 +#: ../src/jarabe/desktop/meshbox.py:498 #: ../src/jarabe/frame/activitiestray.py:761 #: ../src/jarabe/journal/journaltoolbox.py:425 #: ../src/jarabe/journal/palettes.py:72 ../src/jarabe/view/palettes.py:66 msgid "Resume" msgstr "Reprendre" -#: ../src/jarabe/desktop/meshbox.py:449 +#: ../src/jarabe/desktop/meshbox.py:503 #: ../src/jarabe/frame/activitiestray.py:235 msgid "Join" msgstr "Rejoindre" @@ -804,25 +940,25 @@ msgstr "Précédent" msgid "Next" msgstr "Suivant" -#: ../src/jarabe/journal/collapsedentry.py:258 -#: ../src/jarabe/journal/expandedentry.py:159 +#: ../src/jarabe/journal/collapsedentry.py:260 +#: ../src/jarabe/journal/expandedentry.py:158 #: ../src/jarabe/journal/palettes.py:66 msgid "Untitled" msgstr "Sans titre" -#: ../src/jarabe/journal/expandedentry.py:205 +#: ../src/jarabe/journal/expandedentry.py:211 msgid "No preview" msgstr "Pas de prévisualisation" -#: ../src/jarabe/journal/expandedentry.py:224 +#: ../src/jarabe/journal/expandedentry.py:230 msgid "Participants:" msgstr "Participants :" -#: ../src/jarabe/journal/expandedentry.py:247 +#: ../src/jarabe/journal/expandedentry.py:253 msgid "Description:" msgstr "Description :" -#: ../src/jarabe/journal/expandedentry.py:273 +#: ../src/jarabe/journal/expandedentry.py:282 msgid "Tags:" msgstr "Étiquettes :" @@ -901,10 +1037,11 @@ msgid "Your Journal is empty" msgstr "Le journal est vide" #: ../src/jarabe/journal/listview.py:41 +#, fuzzy msgid "No matching entries " msgstr "Aucune entrée correspondante" -#: ../src/jarabe/journal/listview.py:370 +#: ../src/jarabe/journal/listview.py:372 msgid "Clear search" msgstr "Effacer la recherche" @@ -1047,8 +1184,184 @@ msgstr "Source du paquet activité" msgid "View source: %r" msgstr "Afficher le code source : %r" +#~ msgid "Keyboard" +#~ msgstr "Clavier" + +#~ msgid "Keyboard Model" +#~ msgstr "Modèle de clavier" + +#~ msgid "Key(s) to change layout" +#~ msgstr "Touche(s) de modification de la disposition" + +#~ msgid "Keyboard Layout(s)" +#~ msgstr "Disposition(s) du clavier" + #~ msgid "" -#~ "© 2008 One Laptop per Child Association Inc; Red Hat Inc; and Contributors." +#~ "Add languages in the order you prefer. If a translation is not available, " +#~ "the next in the list will be used." +#~ msgstr "" +#~ "Ajoutez des langues dans l'ordre souhaité. Si la traduction n'est pas " +#~ "disponible, la suivante dans la liste sera utilisée." + +#~ msgid "APN:" +#~ msgstr "APN :" + +#~ msgid "Error in extreme pm argument, use on/off." +#~ msgstr "Erreur dans l'argument gestion de l'alimentation extrême." + +#~ msgid "" +#~ "Extreme power management (disableswireless radio, increases battery life)" +#~ msgstr "" +#~ "Gestion extrême de l'alimentation (désactive la radio sans fil, prolonge " +#~ "la durée de vie de la batterie)" + +#~ msgid "Software update" +#~ msgstr "Mise à jour logicielle" + +#~ msgid "" +#~ "Software updates correct errors, eliminate security vulnerabilities, and " +#~ "provide new features." +#~ msgstr "" +#~ "Les mises à jour logicielles corrigent les erreurs, éliminent les failles " +#~ "de sécurité et apportent de nouvelles fonctionnalités." + +#~ msgid "Checking %s..." +#~ msgstr "Vérification de %s..." + +#~ msgid "Downloading %s..." +#~ msgstr "Téléchargement de %s..." + +#~ msgid "Updating %s..." +#~ msgstr "Mise à jour de %s..." + +#~ msgid "Your software is up-to-date" +#~ msgstr "Vos logiciels sont à jour" + +#~ 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" + +#~ msgid "Checking for updates..." +#~ msgstr "Vérification des mises à jour..." + +#~ msgid "Installing updates..." +#~ msgstr "Installation des mises à jour..." + +#~ 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" + +#~ msgid "Install selected" +#~ msgstr "Installer les activités sélectionnées" + +#~ msgid "Download size: %s" +#~ msgstr "Taille du téléchargement : %s" + +#~ msgid "From version %(current)d to %(new)s (Size: %(size)s)" +#~ msgstr "De la version %(current)d à %(new)s (taille : %(size)s)" + +#~ msgid "None" +#~ msgstr "Zéro" + +#~ msgid "1 KB" +#~ msgstr "1 Ko" + +#~ msgid "%.0f KB" +#~ msgstr "%.0f Ko" + +#~ msgid "%.1f MB" +#~ msgstr "%.1f Mo" + +#~ msgid "Mesh Network" +#~ msgstr "Réseau maillé" + +#~ msgid "Mesh" +#~ msgstr "Réseau maillé" + +#~ msgid "Screenshot of \"%s\"" +#~ msgstr "Capture d'écran de \"%s\"" + +#~ 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." + +#~ msgid "Default nick" +#~ msgstr "Pseudo par défaut" + +#~ msgid "If TRUE, Sugar will show a \"Log out\" option." +#~ msgstr "Si VRAI, Sugar affichera une option \"Déconnexion\"." + +#~ msgid "Keyboard layouts" +#~ msgstr "Dispositions du clavier" + +#~ msgid "Keyboard model" +#~ msgstr "Modèle de clavier" + +#~ msgid "Keyboard options" +#~ msgstr "Options du clavier" + +#~ 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)" + +#~ msgid "List of keyboard options." +#~ msgstr "Liste des options de clavier." + +#~ msgid "Show Log out" +#~ msgstr "Afficher Déconnexion" + +#~ msgid "The keyboard model to be used" +#~ msgstr "Modèle de clavier à utiliser" + +#~ msgid "Version %s" +#~ msgstr "Version %s" + +#~ msgid "F1" +#~ msgstr "F1" + +#~ msgid "F2" +#~ msgstr "F2" + +#~ msgid "F3" +#~ msgstr "F3" + +#~ msgid "F4" +#~ msgstr "F4" + +#~ msgid "Kind: %s" +#~ msgstr "Variante : %s" + +#~ msgid "Unknown" +#~ msgstr "Inconnu" + +#~ msgid "Date: %s" +#~ msgstr "Date : %s" + +#~ msgid "Size: %s" +#~ msgstr "Taille : %s" + +#~ msgid "Start new" +#~ msgstr "Commencer un nouveau" + +#~ msgid "Title" +#~ msgstr "Titre" + +#~ msgid "Version" +#~ msgstr "Version" + +#~ msgid "Date" +#~ msgstr "Date" + +#~ msgid "" +#~ "© 2008 One Laptop per Child Association Inc; Red Hat Inc; and " +#~ "Contributors." #~ msgstr "" #~ "© 2008 One Laptop per Child Association Inc ; Red Hat Inc ; et " #~ "contributeurs." @@ -1069,18 +1382,9 @@ 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" -#~ msgid "Mesh" -#~ msgstr "Réseau maillé" - #~ msgid "Connected to a School Mesh Portal" #~ msgstr "Connecté au portail du réseau maillé d'école" @@ -1105,7 +1409,6 @@ msgstr "Afficher le code source : %r" #~ msgid "Settings" #~ msgstr "Configuration" -#, python-format #~ msgid "Clipboard object: %s." #~ msgstr "Objet dans le presse-papier : %s." @@ -1166,11 +1469,9 @@ msgstr "Afficher le code source : %r" #~ msgid "Error in reading timezone" #~ msgstr "Erreur de lecture de la zone temporelle" -#, python-format #~ msgid "Error copying timezone (from %s): %s" #~ msgstr "Erreur en copiant la zone temporelle (de %s): %s" -#, python-format #~ msgid "Changing permission of timezone: %s" #~ msgstr "Changement de la permission de zone temporelle : %s" @@ -1225,7 +1526,6 @@ msgstr "Afficher le code source : %r" #~ msgid "Share" #~ msgstr "Partager" -#, python-format #~ msgid "%s Activity" #~ msgstr "Activité %s" @@ -1265,55 +1565,42 @@ msgstr "Afficher le code source : %r" #~ msgid "OK" #~ msgstr "OK" -#, python-format #~ msgid "%d year" #~ msgstr "%d an" -#, python-format #~ msgid "%d years" #~ msgstr "%d ans" -#, python-format #~ msgid "%d month" #~ msgstr "%d mois" -#, python-format #~ msgid "%d months" #~ msgstr "%d mois" -#, python-format #~ msgid "%d week" #~ msgstr "%d semaine" -#, python-format #~ msgid "%d weeks" #~ msgstr "%d semaines" -#, python-format #~ msgid "%d day" #~ msgstr "%d jour" -#, python-format #~ msgid "%d days" #~ msgstr "%d jours" -#, python-format #~ msgid "%d hour" #~ msgstr "%d heure" -#, python-format #~ msgid "%d hours" #~ msgstr "%d heures" -#, python-format #~ msgid "%d minute" #~ msgstr "%d minute" -#, python-format #~ msgid "%d minutes" #~ msgstr "%d minutes" -#, python-format #~ msgid "%d second" #~ msgstr "%d seconde" @@ -1322,13 +1609,3 @@ msgstr "Afficher le code source : %r" #~ msgid ", " #~ msgstr ", " - -#: ../extensions/deviceicon/network.py:114 -msgid "Create new wireless network" -msgstr "Créer un nouveau réseau sans fil" - - -#: ../extensions/deviceicon/network.py:415 -#, python-format -msgid "%s's network %s" -msgstr "Réseau %s %s" diff --git a/po/pt_BR.po b/po/pt_BR.po index 9d3d467..e71729b 100644 --- a/po/pt_BR.po +++ b/po/pt_BR.po @@ -6,39 +6,39 @@ msgid "" msgstr "" "Project-Id-Version: olpc-sugar-pt_BR\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2009-04-29 17:36-0400\n" -"PO-Revision-Date: 2010-01-17 02:28+0200\n" +"POT-Creation-Date: 2010-05-11 01:07+0530\n" +"PO-Revision-Date: 2010-05-11 23:59-0300\n" "Last-Translator: Robson Mendonça \n" "Language-Team: Brazilian Portuguese \n" -"Language: pt_BR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"Language: pt_BR\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" "X-Generator: Pootle 2.0.1\n" #: ../extensions/cpsection/aboutme/__init__.py:24 msgid "About Me" -msgstr "Sobre Mim" +msgstr "Sobre mim" #: ../extensions/cpsection/aboutme/model.py:43 msgid "You must enter a name." -msgstr "Você deve digitar um nome." +msgstr "Você precisa digitar um nome." #: ../extensions/cpsection/aboutme/model.py:68 #, python-format msgid "stroke: color=%s hue=%s" -msgstr "contorno: cor=%s matiz=%s" +msgstr "traço: cor=%s tonalidade=%s" #: ../extensions/cpsection/aboutme/model.py:71 #, python-format msgid "stroke: %s" -msgstr "contorno: %s" +msgstr "traço: %s" #: ../extensions/cpsection/aboutme/model.py:73 #, python-format msgid "fill: color=%s hue=%s" -msgstr "preenchimento: cor=%s matiz=%s" +msgstr "preenchimento: cor=%s tonalidade=%s" #: ../extensions/cpsection/aboutme/model.py:75 #, python-format @@ -53,17 +53,18 @@ msgstr "Erro nos alteradores de cor selecionados." msgid "Error in specified colors." msgstr "Erro nas cores especificadas." -#: ../extensions/cpsection/aboutme/view.py:94 ../src/jarabe/intro/window.py:92 +#: ../extensions/cpsection/aboutme/view.py:94 +#: ../src/jarabe/intro/window.py:92 msgid "Name:" msgstr "Nome:" #: ../extensions/cpsection/aboutme/view.py:128 msgid "Click to change your color:" -msgstr "Clique para mudar a sua cor:" +msgstr "Clique para mudar a cor:" #: ../extensions/cpsection/aboutcomputer/__init__.py:21 msgid "About my Computer" -msgstr "Sobre o meu Computador" +msgstr "Sobre meu Computador" #: ../extensions/cpsection/aboutcomputer/model.py:28 msgid "Not available" @@ -75,19 +76,19 @@ msgstr "Identidade" #: ../extensions/cpsection/aboutcomputer/view.py:69 msgid "Serial Number:" -msgstr "Número Serial:" +msgstr "Número de Série:" #: ../extensions/cpsection/aboutcomputer/view.py:91 msgid "Software" -msgstr "Programa" +msgstr "Software" #: ../extensions/cpsection/aboutcomputer/view.py:100 msgid "Build:" -msgstr "Compilação:" +msgstr "Construção:" #: ../extensions/cpsection/aboutcomputer/view.py:115 msgid "Sugar:" -msgstr "Glucose:" +msgstr "Sugar:" #: ../extensions/cpsection/aboutcomputer/view.py:131 msgid "Firmware:" @@ -95,27 +96,19 @@ msgstr "Firmware:" #: ../extensions/cpsection/aboutcomputer/view.py:146 msgid "Wireless Firmware:" -msgstr "Firmware da Rede sem Fio:" +msgstr "Firmware sem Fio:" #: ../extensions/cpsection/aboutcomputer/view.py:169 msgid "Copyright and License" -msgstr "Copyright e Licensa" +msgstr "Direito Autoral e Licença" #: ../extensions/cpsection/aboutcomputer/view.py:184 -msgid "" -"Sugar is the graphical user interface that you are looking at. Sugar is free " -"software, covered by the GNU General Public License, and you are welcome to " -"change it and/or distribute copies of it under certain conditions described " -"therein." -msgstr "" -"Sugar é a interface gráfica para qual você está olhando. Sugar é um Software " -"Livre, coberto pela Licensa GNU General Public License, e você é bem-vindo " -"para mudá-lo e/ou distribuir cópias dele sobre certas condicões descritas " -"aqui." +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 "Sugar é um interface gráfico para usuário que você estava procurando. Sugar é software livre, coberto pela Licença Geral Pública GNU, e você está convidado(a) para modificá-lo e/ou distribuir copias dele, sob certas condições descritas na Licença." #: ../extensions/cpsection/aboutcomputer/view.py:196 msgid "Full license:" -msgstr "Licensa completa:" +msgstr "Licença Completa:" #: ../extensions/cpsection/datetime/__init__.py:21 msgid "Date & Time" @@ -125,7 +118,8 @@ msgstr "Data e Hora" msgid "Error timezone does not exist." msgstr "Erro: fuso horário não existe." -#: ../extensions/cpsection/datetime/view.py:68 ../data/sugar.schemas.in.h:19 +#: ../extensions/cpsection/datetime/view.py:68 +#: ../data/sugar.schemas.in.h:35 msgid "Timezone" msgstr "Fuso horário" @@ -136,7 +130,7 @@ msgstr "Moldura" #: ../extensions/cpsection/frame/model.py:38 #: ../extensions/cpsection/frame/model.py:60 msgid "Value must be an integer." -msgstr "Valor precisa ser um número inteiro." +msgstr "Valor precisa ser um inteiro." #: ../extensions/cpsection/frame/view.py:26 msgid "never" @@ -153,7 +147,7 @@ msgstr "%s segundos" #: ../extensions/cpsection/frame/view.py:52 msgid "Activation Delay" -msgstr "Espera na Ativação" +msgstr "Atraso de Ativação" #: ../extensions/cpsection/frame/view.py:76 msgid "Corner" @@ -166,7 +160,7 @@ msgstr "Borda" #: ../extensions/cpsection/language/__init__.py:21 #: ../extensions/cpsection/language/view.py:32 msgid "Language" -msgstr "Língua" +msgstr "Idioma" #: ../extensions/cpsection/language/model.py:28 msgid "Could not access ~/.i18n. Create standard settings." @@ -182,90 +176,107 @@ msgstr "Não foi possível determinar a língua para o código=%s." msgid "Sorry I do not speak '%s'." msgstr "Desculpe, eu não falo '%s'." +#: ../extensions/cpsection/modemconfiguration/__init__.py:21 +msgid "Modem Configuration" +msgstr "Configuração do Modem" + +#: ../extensions/cpsection/modemconfiguration/view.py:91 +msgid "Username:" +msgstr "Nome de usuário:" + +#: ../extensions/cpsection/modemconfiguration/view.py:102 +msgid "Password:" +msgstr "Senha:" + +#: ../extensions/cpsection/modemconfiguration/view.py:113 +msgid "Number:" +msgstr "Número:" + +#: ../extensions/cpsection/modemconfiguration/view.py:124 +msgid "Access Point Name (APN):" +msgstr "Nome do Ponto de Acesso (APN):" + +#: ../extensions/cpsection/modemconfiguration/view.py:135 +msgid "Personal Identity Number (PIN):" +msgstr "Número Indentificador Pessoal (PIN):" + +#: ../extensions/cpsection/modemconfiguration/view.py:146 +msgid "Personal Unblocking Key (PUK):" +msgstr "Chave de Desbloqueio Pessoal (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 "Você precisa fornecer as seguintes informações para configurar sua conexão de banda larga móvel (3G) por celular." + #: ../extensions/cpsection/network/__init__.py:21 #: ../extensions/cpsection/network/view.py:28 msgid "Network" -msgstr "Rede" +msgstr "rede" -#: ../extensions/cpsection/network/model.py:79 +#: ../extensions/cpsection/network/model.py:87 msgid "State is unknown." msgstr "Estado desconhecido." -#: ../extensions/cpsection/network/model.py:105 +#: ../extensions/cpsection/network/model.py:115 +#: ../extensions/cpsection/network/model.py:154 msgid "Error in specified radio argument use on/off." msgstr "Erro no argumento de rádio especificado, use on/off." -#: ../extensions/cpsection/network/model.py:137 +#: ../extensions/cpsection/network/model.py:197 msgid "Error in specified argument use 0/1." -msgstr "Erro no argumento especificado, use 0 ou 1." +msgstr "Erro no argumento de rádio especificado, use ligado / desligado." #: ../extensions/cpsection/network/view.py:56 msgid "Wireless" -msgstr "Rede sem fio" +msgstr "rede sem fio" #: ../extensions/cpsection/network/view.py:64 msgid "Turn off the wireless radio to save battery life" -msgstr "Desligue a transmissão sem fio para economizar bateria" +msgstr "Desligue a placa sem fio para economizar energia e aumentar a durabilidade da bateria" #: ../extensions/cpsection/network/view.py:77 msgid "Radio" -msgstr "Rádio" +msgstr "Radio" #: ../extensions/cpsection/network/view.py:93 msgid "Discard network history if you have trouble connecting to the network" -msgstr "Desprezar o histórico se você tem problemas para conectar na rede" +msgstr "Desprezar o histórico da rede se você tem problemas para conectar a rede" #: ../extensions/cpsection/network/view.py:102 msgid "Discard network history" -msgstr "Desprezar o histórico de rede" +msgstr "Desprezar o histórico da rede" -#: ../extensions/cpsection/network/view.py:115 +#: ../extensions/cpsection/network/view.py:117 msgid "Collaboration" msgstr "Colaboração" -#: ../extensions/cpsection/network/view.py:123 -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 " -"network." -msgstr "" -"O servidor é equivalente a sala que você esta; pessoas no mesmo servidor " -"estão aptas a ver umas às outras, mesmo que elas não estejam na mesma rede." +#: ../extensions/cpsection/network/view.py:125 +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 network." +msgstr "O servidor é o equivalente a uma sala na qual você está. Pessoas no mesmo servidor são capazes de ver umas às outras, mesmo quando elas não estão na mesma rede." -#: ../extensions/cpsection/network/view.py:133 +#: ../extensions/cpsection/network/view.py:135 msgid "Server:" msgstr "Servidor:" #: ../extensions/cpsection/power/__init__.py:21 msgid "Power" -msgstr "Carga" +msgstr "Energia" -#: ../extensions/cpsection/power/model.py:54 +#: ../extensions/cpsection/power/model.py:87 msgid "Error in automatic pm argument, use on/off." -msgstr "Erro no argumento automático pm, utilize ligado/desligado." +msgstr "Erro no argumento automático pm, use ligado / desligado." -#: ../extensions/cpsection/power/model.py:81 -msgid "Error in extreme pm argument, use on/off." -msgstr "Erro no argumento extremo pm, use ligado/desligado." - -#: ../extensions/cpsection/power/view.py:47 +#: ../extensions/cpsection/power/view.py:44 msgid "Power management" msgstr "Gerenciamento de energia" -#: ../extensions/cpsection/power/view.py:57 +#: ../extensions/cpsection/power/view.py:54 msgid "Automatic power management (increases battery life)" -msgstr "Gerenciamento automático de energia (estende a duração da bateria)" - -#: ../extensions/cpsection/power/view.py:85 -msgid "" -"Extreme power management (disableswireless radio, increases battery life)" -msgstr "" -"Gerenciamento extremo de energia (desabilita rede sem fio, incrementa a vida " -"da bateria)" +msgstr "Gerenciamento de energia automático (aumenta a duração da bateria)" #: ../extensions/deviceicon/battery.py:58 msgid "My Battery" -msgstr "Minha Bateria" +msgstr "Minha bateria" #: ../extensions/deviceicon/battery.py:137 msgid "Removed" @@ -277,52 +288,123 @@ msgstr "Carregando" #: ../extensions/deviceicon/battery.py:143 msgid "Very little power remaining" -msgstr "Pouca energia restando" +msgstr "Resta um pouquinho de energia" #: ../extensions/deviceicon/battery.py:149 #, python-format msgid "%(hour)d:%(min).2d remaining" -msgstr "%(hour)d:%(min).2d restando" +msgstr "resta %(hour)d:%(min).2d" #: ../extensions/deviceicon/battery.py:152 msgid "Charged" -msgstr "Carregado" +msgstr "Carregada" -#: ../extensions/deviceicon/network.py:40 +#: ../extensions/deviceicon/network.py:47 #, python-format msgid "IP address: %s" msgstr "Endereço IP: %s" -#: ../extensions/deviceicon/network.py:104 +# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. +# +# NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping +#: ../extensions/deviceicon/network.py:109 msgid "Disconnect..." msgstr "Desconectar..." -#: ../extensions/deviceicon/network.py:109 -#: ../src/jarabe/desktop/meshbox.py:250 +#: ../extensions/deviceicon/network.py:113 +msgid "Create new wireless network" +msgstr "Criar nova rede wireless" + +# 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:119 +#: ../extensions/deviceicon/network.py:270 +#: ../src/jarabe/desktop/meshbox.py:236 msgid "Connecting..." msgstr "Conectando..." -#: ../extensions/deviceicon/network.py:113 -#: ../extensions/deviceicon/network.py:166 -#: ../src/jarabe/desktop/meshbox.py:256 +#: ../extensions/deviceicon/network.py:123 +#: ../extensions/deviceicon/network.py:185 +#: ../extensions/deviceicon/network.py:274 +#: ../src/jarabe/desktop/meshbox.py:242 msgid "Connected" msgstr "Conectado" -#: ../extensions/deviceicon/network.py:126 +#: ../extensions/deviceicon/network.py:145 msgid "Channel" msgstr "Canal" -#: ../extensions/deviceicon/network.py:141 +#: ../extensions/deviceicon/network.py:160 msgid "Wired Network" -msgstr "Rede sem fio" +msgstr "Rede por fio" -#: ../extensions/deviceicon/network.py:169 +#: ../extensions/deviceicon/network.py:188 msgid "Speed" msgstr "Velocidade" +#: ../extensions/deviceicon/network.py:214 +msgid "Wireless modem" +msgstr "Modem wireless" + +#: ../extensions/deviceicon/network.py:262 +msgid "Please wait..." +msgstr "Por favor espere..." + +#: ../extensions/deviceicon/network.py:265 +#: ../src/jarabe/desktop/meshbox.py:150 +msgid "Connect" +msgstr "Conectar" + +#: ../extensions/deviceicon/network.py:266 +msgid "Disconnected" +msgstr "Desconectado" + +#: ../extensions/deviceicon/network.py:269 +#: ../src/jarabe/controlpanel/toolbar.py:115 +#: ../src/jarabe/desktop/homebox.py:111 +#: ../src/jarabe/frame/activitiestray.py:726 +#: ../src/jarabe/frame/activitiestray.py:821 +#: ../src/jarabe/frame/activitiestray.py:849 +msgid "Cancel" +msgstr "Cancelar" + +#: ../extensions/deviceicon/network.py:273 +#: ../src/jarabe/desktop/meshbox.py:154 +msgid "Disconnect" +msgstr "Desconectar" + +#: ../extensions/deviceicon/network.py:277 +msgid "Sim requires Pin/Puk" +msgstr "Sim requer Pin/Puk" + +#: ../extensions/deviceicon/network.py:278 +msgid "Authentication Error" +msgstr "Erro de Autenticação" + +#: ../extensions/deviceicon/network.py:526 +#, python-format +msgid "%s's network" +msgstr "rede da %s" + +#: ../extensions/deviceicon/network.py:734 +#, python-format +msgid "Data sent %d kb / received %d kb" +msgstr "Dados enviados %d kb / recebidos %d kb" + +#: ../extensions/deviceicon/network.py:745 +msgid "Connection time " +msgstr "Tempo de conexão" + #: ../extensions/deviceicon/speaker.py:59 msgid "My Speakers" -msgstr "Meus Alto Falantes" +msgstr "Meus Alto-falantes" #: ../extensions/deviceicon/speaker.py:133 msgid "Unmute" @@ -338,145 +420,190 @@ msgstr "Foto da tela" #: ../data/sugar.schemas.in.h:1 msgid "Backup URL" -msgstr "URL reserva" +msgstr "URL de backup" #: ../data/sugar.schemas.in.h:2 -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. " -"Example: #AC32FF,#9A5200" -msgstr "" -"Cor para o ícone XO que será usado na área de trabalho. A sequência é " -"composta de tom e preenchimento da cor, formato das cores rgb (vermelho, " -"verde e azul). Exemplo: #AC32FF,#9A5200" +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. Example: #AC32FF,#9A5200" +msgstr "Cor para o ícone XO que é usado na área de trabalho. A string é composta pela cor do contorno e preenchimento, no formato de cores rgb. Exemplo: #AC32FF,#9A5200" #: ../data/sugar.schemas.in.h:3 msgid "Corner Delay" -msgstr "Atraso de Canto" +msgstr "Atraso do Canto" #: ../data/sugar.schemas.in.h:4 -msgid "Delay for the activation of the frame using the corners." -msgstr "Atraso para ativação da moldura utilizando os cantos." +msgid "Default font face" +msgstr "Fonte padrão" #: ../data/sugar.schemas.in.h:5 -msgid "Delay for the activation of the frame using the edges." -msgstr "Atraso na ativação das molduras utilizando as bordas." +msgid "Default font size" +msgstr "Tamanho padrão da fonte" #: ../data/sugar.schemas.in.h:6 -msgid "Edge Delay" -msgstr "Atraso de Borda" +msgid "Delay for the activation of the frame using the corners." +msgstr "Atraso para ativação da moldura usando os cantos." #: ../data/sugar.schemas.in.h:7 -msgid "Favorites Layout" -msgstr "Organização ou Arranjo Preferido" +msgid "Delay for the activation of the frame using the edges." +msgstr "Atraso para a ativação da moldura usando bordas." #: ../data/sugar.schemas.in.h:8 -msgid "Favorites resume mode" -msgstr "Modo preferido de retomada" +msgid "Edge Delay" +msgstr "Atraso da Borda" #: ../data/sugar.schemas.in.h:9 -msgid "" -"If TRUE, Sugar will make us searchable for the other users of the Jabber " -"server." -msgstr "" -"Se VERDADEIRO, Sugar deve tornar-nos pesquisável para os outros usuários do " -"servidor Jabber." +msgid "Favorites Layout" +msgstr "Disposição dos Favoritos" #: ../data/sugar.schemas.in.h:10 +msgid "Favorites resume mode" +msgstr "Modo de resumo dos favoritos" + +#: ../data/sugar.schemas.in.h:11 +msgid "Font face that is used throughout the desktop." +msgstr "Fonte que é utilizada por toda área de trabalho." + +#: ../data/sugar.schemas.in.h:12 +msgid "Font size that is used throughout the desktop." +msgstr "Tamanho da fonte que é utilizada por toda área de trabalho." + +#: ../data/sugar.schemas.in.h:13 +msgid "GSM network APN" +msgstr "Rede GSM APN" + +#: ../data/sugar.schemas.in.h:14 +msgid "GSM network PIN" +msgstr "Rede GSM PIN" + +#: ../data/sugar.schemas.in.h:15 +msgid "GSM network PUK" +msgstr "Rede GSM PUK" + +#: ../data/sugar.schemas.in.h:16 +msgid "GSM network access point name configuration" +msgstr "Configuração do nome do ponto de acesso da rede GSM" + +#: ../data/sugar.schemas.in.h:17 +msgid "GSM network number" +msgstr "Número da rede GSM" + +#: ../data/sugar.schemas.in.h:18 +msgid "GSM network password" +msgstr "Senha da rede GSM" + +#: ../data/sugar.schemas.in.h:19 +msgid "GSM network password configuration" +msgstr "Configuração de rede GSM senha" + +#: ../data/sugar.schemas.in.h:20 +msgid "GSM network personal identification number configuration" +msgstr "Configuração de rede GSM número de identificação pessoal" + +#: ../data/sugar.schemas.in.h:21 +msgid "GSM network personal unlock key configuration" +msgstr "Configuração de rede GSM chave de desbloqueio" + +#: ../data/sugar.schemas.in.h:22 +msgid "GSM network telephone number configuration" +msgstr "Configuração de rede GSM número de telefone" + +#: ../data/sugar.schemas.in.h:23 +msgid "GSM network username" +msgstr "Nome de usuário da rede GSM" + +#: ../data/sugar.schemas.in.h:24 +msgid "GSM network username configuration" +msgstr "Configuração de rede GSM nome de usuário" + +#: ../data/sugar.schemas.in.h:25 +msgid "If TRUE, Sugar will make us searchable for the other users of the Jabber server." +msgstr "Se VERDADEIRO, o Sugar nos tornará buscáveis por outros usuários do servidor Jabber." + +#: ../data/sugar.schemas.in.h:26 msgid "Jabber Server" msgstr "Servidor Jabber" -#: ../data/sugar.schemas.in.h:11 +#: ../data/sugar.schemas.in.h:27 msgid "Layout of the favorites view." -msgstr "Arranjo da visão dos favoritos." +msgstr "Disposição da visão de favoritos." -#: ../data/sugar.schemas.in.h:12 +#: ../data/sugar.schemas.in.h:28 msgid "Power Automatic" -msgstr "Energia em Automático" +msgstr "Gerenciamento de Energia Automático" -#: ../data/sugar.schemas.in.h:13 +#: ../data/sugar.schemas.in.h:29 msgid "Power Automatic." -msgstr "Energia em Automático." +msgstr "Gerenciamento de Energia Automático." -# Energia modo Extremo -#: ../data/sugar.schemas.in.h:14 +#: ../data/sugar.schemas.in.h:30 msgid "Power Extreme" -msgstr "Energia modo Extremo" +msgstr "Gerenciamento de Energia Extremo" -#: ../data/sugar.schemas.in.h:15 +#: ../data/sugar.schemas.in.h:31 msgid "Power Extreme." -msgstr "Energia em modo Extremo." +msgstr "Gerenciamento de Energia Extremo." -#: ../data/sugar.schemas.in.h:16 +#: ../data/sugar.schemas.in.h:32 msgid "Publish to Gadget" msgstr "Publicar para Dispositivos" -#: ../data/sugar.schemas.in.h:17 +#: ../data/sugar.schemas.in.h:33 msgid "Setting for muting the sound device." -msgstr "Colocar o som do equipamento no modo mudo." +msgstr "Configurar ou silenciar o dispositivo de som." -#: ../data/sugar.schemas.in.h:18 +#: ../data/sugar.schemas.in.h:34 msgid "Sound Muted" -msgstr "Som em Mudo" +msgstr "Som mudo" -#: ../data/sugar.schemas.in.h:20 +#: ../data/sugar.schemas.in.h:36 msgid "Timezone setting for the system." -msgstr "Ajuste de fuso horário para o sistema." +msgstr "Configuração de fuso-horário do sistema." -#: ../data/sugar.schemas.in.h:21 +#: ../data/sugar.schemas.in.h:37 msgid "Url of the jabber server to use." -msgstr "Endereço do servidor jabber a ser usado." +msgstr "Url do servidor Jabber para usar." -#: ../data/sugar.schemas.in.h:22 +#: ../data/sugar.schemas.in.h:38 msgid "Url where the backup is saved to." -msgstr "Endereço onde o backup será salvo." +msgstr "Url onde o backup é salvo." -#: ../data/sugar.schemas.in.h:23 +#: ../data/sugar.schemas.in.h:39 msgid "User Color" msgstr "Cor do Usuário" -#: ../data/sugar.schemas.in.h:24 +#: ../data/sugar.schemas.in.h:40 msgid "User Name" msgstr "Nome do Usuário" -#: ../data/sugar.schemas.in.h:25 +#: ../data/sugar.schemas.in.h:41 msgid "User name that is used throughout the desktop." -msgstr "Nome do usuário que será utilizado pelo computador." +msgstr "Nome do usuário que é usado por toda área de trabalho." -#: ../data/sugar.schemas.in.h:26 +#: ../data/sugar.schemas.in.h:42 msgid "Volume Level" -msgstr "Nivel do Volume" +msgstr "Nível do volume" -#: ../data/sugar.schemas.in.h:27 +#: ../data/sugar.schemas.in.h:43 msgid "Volume level for the sound device." -msgstr "Nivel do volume para o equipamento de som." +msgstr "Nível do volume para o dispositivo de som." -#: ../data/sugar.schemas.in.h:28 -msgid "" -"When in resume mode, clicking on a favorite icon will cause the last entry " -"for that activity to be resumed." -msgstr "" -"Quando em modo retomada, clicando num item favorito ocasionará a última " -"atividade solicitada ser retomada." +#: ../data/sugar.schemas.in.h:44 +msgid "When in resume mode, clicking on a favorite icon will cause the last entry for that activity to be resumed." +msgstr "Quando estiver no modo continuar, clicando num ícone favorito fará com que a última entrada para essa atividade seja retomada." #: ../src/jarabe/controlpanel/cmd.py:28 #, python-format -msgid "" -"sugar-control-panel: WARNING, found more than one option with the same name: " -"%s module: %r" -msgstr "" -"painel-de-controle-sugar: AVISO, foi encontrado mais de uma opção com o " -"mesmo nome: %s módulo: %r" +msgid "sugar-control-panel: WARNING, found more than one option with the same name: %s module: %r" +msgstr "sugar-control-panel: AVISO, encontrado mais de uma opção com o mesmo nome: %s módulo: %r" #: ../src/jarabe/controlpanel/cmd.py:30 #, python-format msgid "sugar-control-panel: key=%s not an available option" -msgstr "painel-de-controle-sugar: chave=%s não é uma opção disponível" +msgstr "sugar-control-panel: chave=%s não é uma opção válida" #: ../src/jarabe/controlpanel/cmd.py:31 #, python-format msgid "sugar-control-panel: %s" -msgstr "painel-de-controle-sugar: %s" +msgstr "sugar-control-panel: %s" #. TRANS: Translators, there's a empty line at the end of this string, #. which must appear in the translated string (msgstr) as well. @@ -493,15 +620,15 @@ msgid "" " -c key clear the current value for the key \n" " " msgstr "" -"Uso: sugar-control-panel [ opção ] chave [ args ... ] \n" -" Ambiente de controle para sugar. \n" +"Uso: sugar-control-panel[opção] chave [argumentos ...] \n" +" Controle para o ambiente do sugar. \n" " Opções: \n" -" -h (exibe mensagem de ajuda e sai) \n" -" -l (lista todas opções disponíveis) \n" -" -h chave (exibe informações sobre a chave solicitada) \n" -" -g chave (obtem o valor atual da chave solicitada) \n" -" -s chave (estabelece o valor para a chave solicitada) \n" -" -c chave (limpa o valor para a chave solicitada \n" +" -h mostrar esta mensagem de ajuda e sair \n" +" -l lista todas as opções disponíveis \n" +" -h chave mostrar informações sobre essa chave \n" +" -g chave pegar o valor atual da chave \n" +" -s chave definir o valor atual da chave \n" +" -c chave limpar o valor atual da chave \n" " " #: ../src/jarabe/controlpanel/cmd.py:50 @@ -510,37 +637,31 @@ msgstr "Para terminar suas mudanças você deve reiniciar o sugar.\n" #: ../src/jarabe/controlpanel/gui.py:275 msgid "Warning" -msgstr "Cuidado" +msgstr "Aviso" #: ../src/jarabe/controlpanel/gui.py:276 #: ../src/jarabe/controlpanel/sectionview.py:42 msgid "Changes require restart" -msgstr "Mudanças requerem reiniciar" +msgstr "As mudanças exigem um reinício" #: ../src/jarabe/controlpanel/gui.py:279 msgid "Cancel changes" -msgstr "Cancelar modificações" +msgstr "Cancele as mudanças" -#: ../src/jarabe/controlpanel/gui.py:284 ../src/jarabe/desktop/homebox.py:113 +#: ../src/jarabe/controlpanel/gui.py:284 +#: ../src/jarabe/desktop/homebox.py:113 msgid "Later" -msgstr "Mais tarde" +msgstr "Posterior" #: ../src/jarabe/controlpanel/gui.py:288 msgid "Restart now" -msgstr "Reiniciar ngora" +msgstr "Reiniciar agora" -#: ../src/jarabe/controlpanel/toolbar.py:61 ../src/jarabe/intro/window.py:188 +#: ../src/jarabe/controlpanel/toolbar.py:61 +#: ../src/jarabe/intro/window.py:188 msgid "Done" msgstr "Pronto" -#: ../src/jarabe/controlpanel/toolbar.py:115 -#: ../src/jarabe/desktop/homebox.py:111 -#: ../src/jarabe/frame/activitiestray.py:726 -#: ../src/jarabe/frame/activitiestray.py:821 -#: ../src/jarabe/frame/activitiestray.py:849 -msgid "Cancel" -msgstr "Cancelar" - #: ../src/jarabe/controlpanel/toolbar.py:121 #: ../src/jarabe/desktop/favoritesview.py:339 msgid "Ok" @@ -549,12 +670,12 @@ msgstr "Ok" #. TRANS: label for the freeform layout in the favorites view #: ../src/jarabe/desktop/favoriteslayout.py:116 msgid "Freeform" -msgstr "Liberdade" +msgstr "Forma livre" #. TRANS: label for the ring layout in the favorites view #: ../src/jarabe/desktop/favoriteslayout.py:198 msgid "Ring" -msgstr "Toque" +msgstr "Anel" #. TRANS: label for the spiral layout in the favorites view #: ../src/jarabe/desktop/favoriteslayout.py:334 @@ -573,7 +694,7 @@ msgstr "Triângulo" #: ../src/jarabe/desktop/favoritesview.py:330 msgid "Registration Failed" -msgstr "O cadastro falhou" +msgstr "Cadastro falhou" #: ../src/jarabe/desktop/favoritesview.py:331 #, python-format @@ -586,49 +707,53 @@ msgstr "Cadastrado com sucesso" #: ../src/jarabe/desktop/favoritesview.py:334 msgid "You are now registered with your school server." -msgstr "Agora você está registrado no servidor de sua escola." +msgstr "Você está cadastrado no servidor de sua escola agora." -#: ../src/jarabe/desktop/favoritesview.py:674 +#: ../src/jarabe/desktop/favoritesview.py:677 msgid "Register" msgstr "Cadastrar" #: ../src/jarabe/desktop/homebox.py:67 msgid "Confirm erase" -msgstr "Confirme a remoção" +msgstr "Confirmar a remoção" #: ../src/jarabe/desktop/homebox.py:69 #, python-format msgid "Confirm erase: Do you want to permanently erase %s?" -msgstr "Confirme a remoção: Você deseja remover %s permanentemente?" - -#: ../src/jarabe/desktop/homebox.py:73 ../src/jarabe/frame/clipboardmenu.py:62 +msgstr "Confirmar remoção: Você deseja remover %s permanentemente?" + +# self._stop_item = MenuItem(_('Stop download'), 'stock-close') +# TODO: Implement stopping downloads +# self._stop_item.connect('activate', self._stop_item_activate_cb) +# self.append_menu_item(self._stop_item) +#: ../src/jarabe/desktop/homebox.py:73 +#: ../src/jarabe/frame/clipboardmenu.py:62 #: ../src/jarabe/view/viewsource.py:218 msgid "Keep" msgstr "Manter" #: ../src/jarabe/desktop/homebox.py:76 #: ../src/jarabe/journal/journaltoolbox.py:357 -#: ../src/jarabe/journal/palettes.py:112 ../src/jarabe/view/palettes.py:153 +#: ../src/jarabe/journal/palettes.py:112 +#: ../src/jarabe/view/palettes.py:153 msgid "Erase" -msgstr "Excluir" +msgstr "Apagar" #: ../src/jarabe/desktop/homebox.py:106 msgid "Software Update" -msgstr "Atualização de Software" +msgstr "Atualização de software" #: ../src/jarabe/desktop/homebox.py:107 msgid "Update your activities to ensure compatibility with your new software" -msgstr "" -"Atualize suas atividades para assegurar a compatibilidade com seu novo " -"software" +msgstr "Atualize suas atividades para assegurar compatibilidade com seu novo software" #: ../src/jarabe/desktop/homebox.py:116 msgid "Check now" -msgstr "Verifique agora" +msgstr "Verificar agora" #: ../src/jarabe/desktop/homebox.py:233 msgid "List view" -msgstr "Exibição de lista" +msgstr "Visão de lista" #: ../src/jarabe/desktop/homebox.py:234 msgid "2" @@ -636,56 +761,50 @@ msgstr "2" #: ../src/jarabe/desktop/homebox.py:296 msgid "Favorites view" -msgstr "Exibição de favoritos" +msgstr "Visão de favoritos" #: ../src/jarabe/desktop/homebox.py:297 msgid "1" msgstr "1" -#: ../src/jarabe/desktop/keydialog.py:131 +#: ../src/jarabe/desktop/keydialog.py:135 msgid "Key Type:" msgstr "Tipo de chave:" -#: ../src/jarabe/desktop/keydialog.py:151 +#: ../src/jarabe/desktop/keydialog.py:155 msgid "Authentication Type:" msgstr "Tipo de Autenticação:" -#: ../src/jarabe/desktop/keydialog.py:215 +#: ../src/jarabe/desktop/keydialog.py:220 msgid "WPA & WPA2 Personal" msgstr "WPA & WPA2 Pessoal" -#: ../src/jarabe/desktop/keydialog.py:224 +#: ../src/jarabe/desktop/keydialog.py:229 msgid "Wireless Security:" msgstr "Segurança do Wireless:" -#: ../src/jarabe/desktop/meshbox.py:134 -msgid "Connect" -msgstr "Conectar" - -#: ../src/jarabe/desktop/meshbox.py:138 -msgid "Disconnect" -msgstr "Desconectado" - +# TRANS: Action label for resuming an activity. #. TRANS: Action label for resuming an activity. -#: ../src/jarabe/desktop/meshbox.py:444 +#: ../src/jarabe/desktop/meshbox.py:498 #: ../src/jarabe/frame/activitiestray.py:761 #: ../src/jarabe/journal/journaltoolbox.py:425 -#: ../src/jarabe/journal/palettes.py:72 ../src/jarabe/view/palettes.py:66 +#: ../src/jarabe/journal/palettes.py:72 +#: ../src/jarabe/view/palettes.py:66 msgid "Resume" msgstr "Continuar" -#: ../src/jarabe/desktop/meshbox.py:449 +#: ../src/jarabe/desktop/meshbox.py:503 #: ../src/jarabe/frame/activitiestray.py:235 msgid "Join" msgstr "Juntar-se" #: ../src/jarabe/desktop/schoolserver.py:34 msgid "Cannot obtain data needed for registration." -msgstr "Não pude obter dados necessários para o cadastro." +msgstr "Não foi possível obter os dados necessários para o resgistro." #: ../src/jarabe/desktop/schoolserver.py:51 msgid "Cannot connect to the server." -msgstr "Não pude conectar com o servidor." +msgstr "Não consegui conectar com o servidor." #: ../src/jarabe/desktop/schoolserver.py:56 msgid "The server could not complete the request." @@ -694,7 +813,7 @@ msgstr "O servidor não completou a requisição." #: ../src/jarabe/frame/activitiestray.py:240 #: ../src/jarabe/frame/activitiestray.py:698 msgid "Decline" -msgstr "Rejeitar" +msgstr "Recusar" #: ../src/jarabe/frame/activitiestray.py:650 #, python-format @@ -734,12 +853,12 @@ msgstr "%s (%s)" #: ../src/jarabe/frame/activitiestray.py:750 #: ../src/jarabe/frame/activitiestray.py:873 msgid "Dismiss" -msgstr "Recusar" +msgstr "Dispensar" #: ../src/jarabe/frame/activitiestray.py:810 #, python-format msgid "Transfer to %r" -msgstr "Transferência para %r" +msgstr "Transferir para %r" #: ../src/jarabe/frame/clipboardmenu.py:52 msgid "Remove" @@ -757,7 +876,7 @@ msgstr "Abrir com" #: ../src/jarabe/frame/clipboardobject.py:49 #, python-format msgid "%s clipping" -msgstr "recorte de %s" +msgstr "Recorte de %s" #: ../src/jarabe/frame/zoomtoolbar.py:36 msgid "Neighborhood" @@ -779,7 +898,8 @@ msgstr "Atividade" msgid "Click to change color:" msgstr "Clique para mudar a cor:" -#: ../src/jarabe/intro/window.py:174 ../src/jarabe/journal/detailview.py:103 +#: ../src/jarabe/intro/window.py:174 +#: ../src/jarabe/journal/detailview.py:103 msgid "Back" msgstr "Voltar" @@ -787,27 +907,27 @@ msgstr "Voltar" msgid "Next" msgstr "Próximo" -#: ../src/jarabe/journal/collapsedentry.py:258 -#: ../src/jarabe/journal/expandedentry.py:159 +#: ../src/jarabe/journal/collapsedentry.py:260 +#: ../src/jarabe/journal/expandedentry.py:158 #: ../src/jarabe/journal/palettes.py:66 msgid "Untitled" msgstr "Sem título" -#: ../src/jarabe/journal/expandedentry.py:205 +#: ../src/jarabe/journal/expandedentry.py:211 msgid "No preview" msgstr "Sem previsão" -#: ../src/jarabe/journal/expandedentry.py:224 +#: ../src/jarabe/journal/expandedentry.py:230 msgid "Participants:" msgstr "Participantes:" -#: ../src/jarabe/journal/expandedentry.py:247 +#: ../src/jarabe/journal/expandedentry.py:253 msgid "Description:" msgstr "Descrição:" -#: ../src/jarabe/journal/expandedentry.py:273 +#: ../src/jarabe/journal/expandedentry.py:282 msgid "Tags:" -msgstr "Tags:" +msgstr "Etiquetas:" #: ../src/jarabe/journal/journalactivity.py:108 #: ../src/jarabe/journal/volumestoolbar.py:47 @@ -816,11 +936,11 @@ msgstr "Diário" #: ../src/jarabe/journal/journaltoolbox.py:65 msgid "Search" -msgstr "Busca" +msgstr "Buscar" #: ../src/jarabe/journal/journaltoolbox.py:124 msgid "Anytime" -msgstr "Qualquer momento" +msgstr "Em qualquer momento" #: ../src/jarabe/journal/journaltoolbox.py:126 msgid "Today" @@ -830,16 +950,19 @@ msgstr "Hoje" msgid "Since yesterday" msgstr "Desde ontem" +# TRANS: Filter entries modified during the last 7 days. #. TRANS: Filter entries modified during the last 7 days. #: ../src/jarabe/journal/journaltoolbox.py:130 msgid "Past week" msgstr "Semana passada" +# TRANS: Filter entries modified during the last 30 days. #. TRANS: Filter entries modified during the last 30 days. #: ../src/jarabe/journal/journaltoolbox.py:132 msgid "Past month" msgstr "Mês passado" +# TRANS: Filter entries modified during the last 356 days. #. TRANS: Filter entries modified during the last 356 days. #: ../src/jarabe/journal/journaltoolbox.py:134 msgid "Past year" @@ -855,21 +978,25 @@ msgstr "Meus amigos" #: ../src/jarabe/journal/journaltoolbox.py:144 msgid "My class" -msgstr "Minha classe" +msgstr "Minha turma" +# TRANS: Item in a combo box that filters by entry type. #. TRANS: Item in a combo box that filters by entry type. #: ../src/jarabe/journal/journaltoolbox.py:271 msgid "Anything" msgstr "Qualquer coisa" +# TODO: Add "Start with" menu item #: ../src/jarabe/journal/journaltoolbox.py:347 #: ../src/jarabe/journal/palettes.py:90 msgid "Copy" msgstr "Copiar" +# TRANS: Action label for starting an entry. #. TRANS: Action label for starting an entry. #: ../src/jarabe/journal/journaltoolbox.py:428 -#: ../src/jarabe/journal/palettes.py:75 ../src/jarabe/view/palettes.py:135 +#: ../src/jarabe/journal/palettes.py:75 +#: ../src/jarabe/view/palettes.py:135 msgid "Start" msgstr "Iniciar" @@ -879,9 +1006,9 @@ msgstr "Seu Diário está vazio" #: ../src/jarabe/journal/listview.py:41 msgid "No matching entries " -msgstr "Nenhum registro encontrado " +msgstr "Nenhuma entrada correspondente" -#: ../src/jarabe/journal/listview.py:370 +#: ../src/jarabe/journal/listview.py:372 msgid "Clear search" msgstr "Limpar busca" @@ -895,15 +1022,15 @@ msgstr "Seu Diário está cheio" #: ../src/jarabe/journal/modalalert.py:67 msgid "Please delete some old Journal entries to make space for new ones." -msgstr "Por favor, apague registros antigos do Diário para liberar mais espaço." +msgstr "Por favor, remova algumas entradas antigas do Diário para liberar mais espaço para as novas." #: ../src/jarabe/journal/modalalert.py:79 msgid "Show Journal" -msgstr "Mostrar Diário" +msgstr "Mostar Diário" #: ../src/jarabe/journal/objectchooser.py:147 msgid "Choose an object" -msgstr "Escolha um objeto" +msgstr "Escolher um objeto" #: ../src/jarabe/journal/objectchooser.py:152 #: ../src/jarabe/view/viewsource.py:308 @@ -994,21 +1121,21 @@ msgstr "Tornar favorito" #: ../src/jarabe/view/palettes.py:241 msgid "Show contents" -msgstr "Mostrar conteúdos" +msgstr "Mostar conteúdos" -#: ../src/jarabe/view/palettes.py:263 ../src/jarabe/view/palettes.py:313 +#: ../src/jarabe/view/palettes.py:263 +#: ../src/jarabe/view/palettes.py:313 #, python-format msgid "%(free_space)d MB Free" msgstr "%(free_space)d MB Livre" -# Pode ser Ejetar #: ../src/jarabe/view/palettes.py:288 msgid "Unmount" -msgstr "Desmontar" +msgstr "Desconectar" #: ../src/jarabe/view/viewsource.py:208 msgid "Instance Source" -msgstr "Fonte de instância" +msgstr "Instância de Fonte" #: ../src/jarabe/view/viewsource.py:233 msgid "Source" @@ -1023,144 +1150,165 @@ msgstr "Pacote Fonte da Atividade" msgid "View source: %r" msgstr "Ver fonte: %r" -#~ msgid "Invite" -#~ msgstr "Convidar" - -#~ msgid "Add to journal" -#~ msgstr "Adicionar ao diário" - -#, python-format -#~ msgid "Clipboard object: %s." -#~ msgstr "Objeto da prancheta: %s" - -#~ msgid "Text" -#~ msgstr "Texto" - -#~ msgid "Image" -#~ msgstr "Imagem" - +#~ msgid "Keyboard" +#~ msgstr "Teclado" + +# in Brasil we have keyboard: US-International, abnt, abnt-2 (abnt-associação brasileira de normas técnicas) +#~ msgid "Keyboard Model" +#~ msgstr "Modelo do Teclado" +#~ msgid "Key(s) to change layout" +#~ msgstr "Teclas(s) para trocar a configuração" +#~ msgid "Keyboard Layout(s)" +#~ msgstr "Disposições de Teclado" +#~ msgid "" +#~ "Add languages in the order you prefer. If a translation is not available, " +#~ "the next in the list will be used." +#~ msgstr "" +#~ "Adicione idiomas na ordem que preferir. Se uma tradução não estiver " +#~ "disponível, a próxima da lista será usada." +#~ msgid "APN:" +#~ msgstr "APN:" +#~ msgid "Error in extreme pm argument, use on/off." +#~ msgstr "Erro no argumento pm extremo, usar ligar/desligar." +#~ msgid "" +#~ "Extreme power management (disableswireless radio, increases battery life)" +#~ msgstr "" +#~ "Gerenciamento de energia extremo (desliga o wireless, aumentando a " +#~ "duração da bateria)" +#~ msgid "Software update" +#~ msgstr "Atualização de software" +#~ msgid "" +#~ "Software updates correct errors, eliminate security vulnerabilities, and " +#~ "provide new features." +#~ msgstr "" +#~ "Atualização de software corrige erros, elimina vulnerabilidades de " +#~ "segurança, e fornece novas funcionalidades." +#~ msgid "Checking %s..." +#~ msgstr "Verificando %s..." +#~ msgid "Downloading %s..." +#~ msgstr "Transferindo %s..." +#~ msgid "Updating %s..." +#~ msgstr "Atualizando %s..." +#~ msgid "Your software is up-to-date" +#~ msgstr "Seu software está atualizado" +#~ msgid "You can install %s update" +#~ msgid_plural "You can install %s updates" +#~ msgstr[0] "Você pode instalar %s atualização" +#~ msgstr[1] "Você pode instalar %s atualizações" +#~ msgid "Checking for updates..." +#~ msgstr "Verificando atualizações..." +#~ msgid "Installing updates..." +#~ msgstr "Instalando atualizações..." +#~ msgid "%s update was installed" +#~ msgid_plural "%s updates were installed" +#~ msgstr[0] "A atualização %s foi instalada" +#~ msgstr[1] "As atualizações %s foram instaladas" +#~ msgid "Install selected" +#~ msgstr "Instalar selecionados" +#~ msgid "Download size: %s" +#~ msgstr "Tamanho da transferência: %s" +#~ msgid "From version %(current)d to %(new)s (Size: %(size)s)" +#~ msgstr "Da versão %(current)d para %(new)s (Tamanho: %(size)s)" +#~ msgid "None" +#~ msgstr "Nulo" +#~ msgid "1 KB" +#~ msgstr "1 KB" +#~ msgid "%.0f KB" +#~ msgstr "%.0f KB" +#~ msgid "%.1f MB" +#~ msgstr "%.1f MB" #~ msgid "Mesh Network" #~ msgstr "Rede Mesh" - -#~ msgid "My Battery life" -#~ msgstr "Carga de minha Bateria" - -#~ msgid "Battery charging" -#~ msgstr "Carregando a bateria" - -#~ msgid "Battery discharging" -#~ msgstr "Descarregando a bateria" - -#~ msgid "Battery fully charged" -#~ msgstr "Bateria completamente carregada" - -#~ msgid "Share" -#~ msgstr "Compartilhar" - -#, python-format -#~ msgid "%s Activity" -#~ msgstr "Atividade %s" - +#~ msgid "Mesh" +#~ msgstr "Mesh" +#~ msgid "Screenshot of \"%s\"" +#~ msgstr "Tela capturada de \"%s\"" +#~ msgid "" +#~ "\"disabled\" to ask nick on initialization; \"system\" to reuse UNIX " +#~ "account long name." +#~ msgstr "" +#~ "\"desativado\" para pedir apelido na inicialização; \"sistema\" para " +#~ "reusar nome completo da conta UNIX." +#~ msgid "Default nick" +#~ msgstr "Apelido padrão" +#~ msgid "If TRUE, Sugar will show a \"Log out\" option." +#~ msgstr "Se TRUE (verdadeiro), o Sugar mostrará uma opção \"Deslogar\"." +#~ msgid "Keyboard layouts" +#~ msgstr "Disposições de teclado" +#~ msgid "Keyboard model" +#~ msgstr "Modelo do teclado" +#~ msgid "Keyboard options" +#~ msgstr "Opções do teclado" +#~ msgid "" +#~ "List of keyboard layouts. Each entry should be in the form layout(variant)" +#~ msgstr "" +#~ "Lista de disposições de teclado. Cada entrada deve estar no esquema " +#~ "(forma variante)" +#~ msgid "List of keyboard options." +#~ msgstr "Lista de opções do teclado." +#~ msgid "Show Log out" +#~ msgstr "Motrar Deslogar" +#~ msgid "The keyboard model to be used" +#~ msgstr "Modelo de teclado a ser usado" +#~ msgid "Version %s" +#~ msgstr "Versão %s" +#~ msgid "F1" +#~ msgstr "F1" +#~ msgid "F2" +#~ msgstr "F2" +#~ msgid "F3" +#~ msgstr "F3" +#~ msgid "F4" +#~ msgstr "F4" +#~ msgid "Kind: %s" +#~ msgstr "Tipo: %s" +#~ msgid "Unknown" +#~ msgstr "Desconhecido" +#~ msgid "Date: %s" +#~ msgstr "Data: %s" +#~ msgid "Size: %s" +#~ msgstr "Tamanho: %s" +#~ msgid "Start new" +#~ msgstr "Iniciar novo" +#~ msgid "Title" +#~ msgstr "Título" +#~ msgid "Version" +#~ msgstr "Versão" +#~ msgid "Date" +#~ msgstr "Data" #~ msgid "Encryption Type:" #~ msgstr "Tipo de Encriptação:" +# 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 +#, fuzzy +#~ msgid "Disconnecting..." +#~ msgstr "Desconectar" -#~ msgid "Reboot" -#~ msgstr "Reiniciar" - -#~ msgid "Share with:" -#~ msgstr "Partilhar com:" - -#~ msgid "Private" -#~ msgstr "Privado" - -#~ msgid "My Neighborhood" -#~ msgstr "Minha Vizinhança" - -#~ msgid "Undo" -#~ msgstr "Desfazer" - -# optei pela forma de "fazer de novo" ao invés de refazer por acreditar que ela seja mais fácil de compreender para crianças. -#~ msgid "Redo" -#~ msgstr "Fazer de novo" - -#~ msgid "Paste" -#~ msgstr "Colar" - -#~ msgid "Keep error" -#~ msgstr "Erro ao manter" - -#~ msgid "Keep error: all changes will be lost" -#~ msgstr "Erro ao manter: todas as alterações serão desfeitas" - -#~ msgid "Don't stop" -#~ msgstr "Não pare" - -#~ msgid "Stop anyway" -#~ msgstr "Pare mesmo assim" - -#~ msgid "Continue" -#~ msgstr "Continuar" - -#~ msgid "OK" -#~ msgstr "OK" - -#, python-format -#~ msgid "%d year" -#~ msgstr "%d ano" - -#, python-format -#~ msgid "%d years" -#~ msgstr "%d anos" - -#, python-format -#~ msgid "%d month" -#~ msgstr "%d mês" - -#, python-format -#~ msgid "%d months" -#~ msgstr "%d meses" - -#, python-format -#~ msgid "%d week" -#~ msgstr "%d semana" - -#, python-format -#~ msgid "%d weeks" -#~ msgstr "%d semanas" - -#, python-format -#~ msgid "%d day" -#~ msgstr "%d dia" - -#, python-format -#~ msgid "%d days" -#~ msgstr "%d dias" +#~ msgid "Connected to a School Mesh Portal" +#~ msgstr "Conectado ao Portal Mesh da Escola" -#, python-format -#~ msgid "%d hour" -#~ msgstr "%d hora" +#~ msgid "Looking for a School Mesh Portal..." +#~ msgstr "Procurando por um Portal Mesh da Escola..." -#, python-format -#~ msgid "%d hours" -#~ msgstr "%d horas" +#~ msgid "Connected to an XO Mesh Portal" +#~ msgstr "Conectado ao Portal Mesh de um XO" -#, python-format -#~ msgid "%d minute" -#~ msgstr "%d minuto" +#~ msgid "Looking for an XO Mesh Portal..." +#~ msgstr "Procurando pelo Portal Mesh de algum XO..." -#, python-format -#~ msgid "%d minutes" -#~ msgstr "%d minutos" +#~ msgid "Connected to a Simple Mesh" +#~ msgstr "Conectado a uma Mesh Simples" -#, python-format -#~ msgid "%d second" -#~ msgstr "%d segundo" +#~ msgid "Starting a Simple Mesh" +#~ msgstr "Iniciando uma Mesh Simples" -#~ msgid " and " -#~ msgstr " e " +#~ msgid "Unknown Mesh" +#~ msgstr "Mesh desconhecida" -#~ msgid ", " -#~ msgstr ", " +#~ msgid "Clipboard object: %s." +#~ msgstr "Objeto da prancheta: %s" #~ msgid "off" #~ msgstr "desligado" @@ -1170,48 +1318,17 @@ msgstr "Ver fonte: %r" #~ msgid "Permission denied. You need to be root to run this method." #~ msgstr "" -#~ "Permissão negada. Você precisa ser o usuário root para executar esse método." +#~ "Permissão negada. Você precisa ser o usuário root para executar esse " +#~ "método." #~ msgid "Error in reading timezone" #~ msgstr "Erro ao ler o fuso horário" -#, python-format #~ msgid "Error copying timezone (from %s): %s" #~ msgstr "Erro ao copiar o fuso horário (de %s): %s" -#, python-format #~ msgid "Changing permission of timezone: %s" #~ msgstr "Mudando a permissão do fuso horário: %s" -#~ msgid "Connected to a School Mesh Portal" -#~ msgstr "Conectado ao Portal Mesh da Escola" - -#~ msgid "Looking for a School Mesh Portal..." -#~ msgstr "Procurando por um Portal Mesh da Escola..." - -#~ msgid "Connected to an XO Mesh Portal" -#~ msgstr "Conectado ao Portal Mesh de um XO" - -#~ msgid "Looking for an XO Mesh Portal..." -#~ msgstr "Procurando pelo Portal Mesh de algum XO..." - -#~ msgid "Connected to a Simple Mesh" -#~ msgstr "Conectado a uma Mesh Simples" - -#~ msgid "Starting a Simple Mesh" -#~ msgstr "Iniciando uma Mesh Simples" - -#~ msgid "Unknown Mesh" -#~ msgstr "Mesh desconhecida" - #~ msgid "About this XO" #~ msgstr "Sobre este XO" - -#: ../extensions/deviceicon/network.py:114 -msgid "Create new wireless network" -msgstr "Criar nova rede wireless" - -#: ../extensions/deviceicon/network.py:415 -#, python-format -msgid "%s's network %s" -msgstr "rede da %s %s" diff --git a/src/jarabe/desktop/activitieslist.py b/src/jarabe/desktop/activitieslist.py index 5d6f900..90b0752 100644 --- a/src/jarabe/desktop/activitieslist.py +++ b/src/jarabe/desktop/activitieslist.py @@ -14,7 +14,9 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +import os import logging +from gettext import gettext as _ import gobject import gtk @@ -23,8 +25,10 @@ import gconf from sugar import util from sugar.graphics import style -from sugar.graphics.icon import CanvasIcon +from sugar.graphics.icon import CanvasIcon, Icon 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 @@ -35,11 +39,6 @@ from jarabe.view import launcher class ActivitiesList(gtk.VBox): __gtype_name__ = 'SugarActivitiesList' - __gsignals__ = { - 'erase-activated' : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([str])) - } - def __init__(self): logging.debug('STARTUP: Loading the activities list') @@ -94,7 +93,33 @@ class ActivitiesList(gtk.VBox): entry.set_visible(entry.matches(self._query)) def __erase_activated_cb(self, activity_icon, bundle_id): - self.emit('erase-activated', bundle_id) + registry = bundleregistry.get_registry() + activity_info = registry.get_bundle(bundle_id) + + alert = Alert() + alert.props.title = _('Confirm erase') + alert.props.msg = \ + _('Confirm erase: Do you want to permanently erase %s?') \ + % activity_info.get_name() + + cancel_icon = Icon(icon_name='dialog-cancel') + alert.add_button(gtk.RESPONSE_CANCEL, _('Keep'), cancel_icon) + + erase_icon = Icon(icon_name='dialog-ok') + alert.add_button(gtk.RESPONSE_OK, _('Erase'), erase_icon) + + alert.connect('response', self.__erase_confirmation_dialog_response_cb, + bundle_id) + + self.add_alert(alert) + + def __erase_confirmation_dialog_response_cb(self, alert, response_id, + bundle_id): + self.remove_alert() + if response_id == gtk.RESPONSE_OK: + registry = bundleregistry.get_registry() + bundle = registry.get_bundle(bundle_id) + registry.uninstall(bundle) def set_filter(self, query): self._query = query @@ -150,11 +175,11 @@ class ActivityIcon(CanvasIcon): self._xocolor = XoColor(client.get_string("/desktop/sugar/user/color")) def create_palette(self): - palette = ActivityPalette(self._activity_info) + palette = ActivityListPalette(self._activity_info) palette.connect('erase-activated', self.__erase_activated_cb) return palette - def __erase_activated_cb(self, palette): + def __erase_activated_cb(self, palette, bundle_id): self.emit('erase-activated', self._activity_info.get_bundle_id()) def _color(self): @@ -178,7 +203,7 @@ class ActivityEntry(hippo.CanvasBox, hippo.CanvasItem): __gtype_name__ = 'SugarActivityEntry' _TITLE_COL_WIDTH = style.GRID_CELL_SIZE * 3 - _VERSION_COL_WIDTH = style.GRID_CELL_SIZE * 1 + _VERSION_COL_WIDTH = style.GRID_CELL_SIZE * 3 _DATE_COL_WIDTH = style.GRID_CELL_SIZE * 5 def __init__(self, activity_info): @@ -322,3 +347,84 @@ class FavoriteIcon(CanvasIcon): icon.props.fill_color = style.COLOR_BUTTON_GREY.get_svg() elif event.detail == hippo.MOTION_DETAIL_LEAVE: icon.props.fill_color = style.COLOR_TRANSPARENT.get_svg() + +class ActivityListPalette(ActivityPalette): + __gtype_name__ = 'SugarActivityListPalette' + + __gsignals__ = { + 'erase-activated' : (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, ([str])) + } + + def __init__(self, activity_info): + ActivityPalette.__init__(self, activity_info) + + self._bundle_id = activity_info.get_bundle_id() + self._version = activity_info.get_activity_version() + + registry = bundleregistry.get_registry() + self._favorite = registry.is_bundle_favorite(self._bundle_id, + self._version) + + self._favorite_item = MenuItem('') + self._favorite_icon = Icon(icon_name='emblem-favorite', + icon_size=gtk.ICON_SIZE_MENU) + self._favorite_item.set_image(self._favorite_icon) + self._favorite_item.connect('activate', + self.__change_favorite_activate_cb) + self.menu.append(self._favorite_item) + self._favorite_item.show() + + self._add_erase_option(registry, activity_info) + + registry = bundleregistry.get_registry() + self._activity_changed_sid = registry.connect('bundle_changed', + self.__activity_changed_cb) + self._update_favorite_item() + + self.connect('destroy', self.__destroy_cb) + + def _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) + + def _update_favorite_item(self): + label = self._favorite_item.child + if self._favorite: + label.set_text(_('Remove favorite')) + xo_color = XoColor('%s,%s' % (style.COLOR_WHITE.get_svg(), + style.COLOR_TRANSPARENT.get_svg())) + else: + label.set_text(_('Make favorite')) + client = gconf.client_get_default() + xo_color = XoColor(client.get_string("/desktop/sugar/user/color")) + + self._favorite_icon.props.xo_color = xo_color + + def __change_favorite_activate_cb(self, menu_item): + registry = bundleregistry.get_registry() + registry.set_bundle_favorite(self._bundle_id, + self._version, + not self._favorite) + + def __activity_changed_cb(self, activity_registry, activity_info): + if activity_info.get_bundle_id() == self._bundle_id and \ + activity_info.get_activity_version() == self._version: + registry = bundleregistry.get_registry() + self._favorite = registry.is_bundle_favorite(self._bundle_id, + self._version) + self._update_favorite_item() + + def __erase_activate_cb(self, menu_item): + self.emit('erase-activated', self._bundle_id) + diff --git a/src/jarabe/desktop/favoritesview.py b/src/jarabe/desktop/favoritesview.py index 5ea76b8..848ee9e 100644 --- a/src/jarabe/desktop/favoritesview.py +++ b/src/jarabe/desktop/favoritesview.py @@ -65,11 +65,6 @@ about the layout can be accessed with fields of the class.""" class FavoritesView(hippo.Canvas): __gtype_name__ = 'SugarFavoritesView' - __gsignals__ = { - 'erase-activated' : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([str])) - } - def __init__(self, **kwargs): logging.debug('STARTUP: Loading the favorites view') @@ -134,14 +129,10 @@ class FavoritesView(hippo.Canvas): if activity_info.get_bundle_id() == 'org.laptop.JournalActivity': return icon = ActivityIcon(activity_info, self._datastore_listener) - icon.connect('erase-activated', self.__erase_activated_cb) icon.props.size = style.STANDARD_ICON_SIZE self._box.insert_sorted(icon, 0, self._layout.compare_activities) self._layout.append(icon) - def __erase_activated_cb(self, activity_icon, bundle_id): - self.emit('erase-activated', bundle_id) - def __activity_added_cb(self, activity_registry, activity_info): registry = bundleregistry.get_registry() if registry.is_bundle_favorite(activity_info.get_bundle_id(), @@ -282,7 +273,9 @@ class FavoritesView(hippo.Canvas): def _set_layout(self, layout): if layout not in LAYOUT_MAP: - raise ValueError('Unknown favorites layout: %r' % layout) + logging.warn('Unknown favorites layout: %r' % layout) + layout = favoriteslayout.RingLayout.key + assert layout in LAYOUT_MAP if type(self._layout) == LAYOUT_MAP[layout]: return @@ -402,11 +395,6 @@ class ActivityIcon(CanvasIcon): _BORDER_WIDTH = style.zoom(3) - __gsignals__ = { - 'erase-activated' : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([str])) - } - def __init__(self, activity_info, datastore_listener): CanvasIcon.__init__(self, cache=True, file_name=activity_info.get_icon()) @@ -469,12 +457,8 @@ class ActivityIcon(CanvasIcon): def create_palette(self): palette = FavoritePalette(self._activity_info, self._journal_entries) palette.connect('activate', self.__palette_activate_cb) - palette.connect('erase-activated', self.__erase_activated_cb) return palette - def __erase_activated_cb(self, palette): - self.emit('erase-activated', self._activity_info.get_bundle_id()) - def __palette_activate_cb(self, palette): self._activate() @@ -609,7 +593,7 @@ class FavoritePalette(ActivityPalette): def __resume_entry_cb(self, menu_item, entry): if entry is not None: - journal.misc.resume(entry, self._bundle_id) + journal.misc.resume(entry, entry['activity']) class CurrentActivityIcon(CanvasIcon, hippo.CanvasItem): def __init__(self): @@ -688,7 +672,7 @@ class _MyIcon(MyIcon): self.emit('register-activate') def remove_register_menu(self): - self.palette.remove(self._register_menu) + self.palette.menu.remove(self._register_menu) class FavoritesSetting(object): diff --git a/src/jarabe/desktop/homebox.py b/src/jarabe/desktop/homebox.py index 6fdc8f1..fdfb7a4 100644 --- a/src/jarabe/desktop/homebox.py +++ b/src/jarabe/desktop/homebox.py @@ -27,7 +27,6 @@ from sugar.graphics.radiotoolbutton import RadioToolButton from sugar.graphics.alert import Alert from sugar.graphics.icon import Icon -from jarabe.model import bundleregistry from jarabe.desktop import favoritesview from jarabe.desktop.activitieslist import ActivitiesList @@ -47,10 +46,6 @@ class HomeBox(gtk.VBox): self._favorites_view = favoritesview.FavoritesView() self._list_view = ActivitiesList() - self._favorites_view.connect('erase-activated', - self.__erase_activated_cb) - self._list_view.connect('erase-activated', self.__erase_activated_cb) - self._toolbar = HomeToolbar() self._toolbar.connect('query-changed', self.__toolbar_query_changed_cb) self._toolbar.connect('view-changed', self.__toolbar_view_changed_cb) @@ -59,44 +54,6 @@ class HomeBox(gtk.VBox): self._set_view(_FAVORITES_VIEW) - def __erase_activated_cb(self, view, bundle_id): - registry = bundleregistry.get_registry() - activity_info = registry.get_bundle(bundle_id) - - alert = Alert() - alert.props.title = _('Confirm erase') - alert.props.msg = \ - _('Confirm erase: Do you want to permanently erase %s?') \ - % activity_info.get_name() - - cancel_icon = Icon(icon_name='dialog-cancel') - alert.add_button(gtk.RESPONSE_CANCEL, _('Keep'), cancel_icon) - - erase_icon = Icon(icon_name='dialog-ok') - alert.add_button(gtk.RESPONSE_OK, _('Erase'), erase_icon) - - if self._list_view in self.get_children(): - self._list_view.add_alert(alert) - else: - self._favorites_view.add_alert(alert) - # TODO: If the favorite layouts didn't hardcoded the box size, we could - # just pack an alert between the toolbar and the canvas. - #self.pack_start(alert, False) - #self.reorder_child(alert, 1) - alert.connect('response', self.__erase_confirmation_dialog_response_cb, - bundle_id) - - def __erase_confirmation_dialog_response_cb(self, alert, response_id, - bundle_id): - if self._list_view in self.get_children(): - self._list_view.remove_alert() - else: - self._favorites_view.remove_alert() - if response_id == gtk.RESPONSE_OK: - registry = bundleregistry.get_registry() - bundle = registry.get_bundle(bundle_id) - registry.uninstall(bundle) - def show_software_updates_alert(self): alert = Alert() updater_icon = Icon(icon_name='module-updater', diff --git a/src/jarabe/desktop/homewindow.py b/src/jarabe/desktop/homewindow.py index bbb0db1..19cc5a2 100644 --- a/src/jarabe/desktop/homewindow.py +++ b/src/jarabe/desktop/homewindow.py @@ -82,9 +82,9 @@ class HomeWindow(gtk.Window): def _visibility_notify_event_cb(self, window, event): if event.state == gtk.gdk.VISIBILITY_FULLY_OBSCURED: - self._deactivate_view() + self._deactivate_view(shell.get_model().zoom_level) else: - self._activate_view() + self._activate_view(shell.get_model().zoom_level) def __zoom_level_changed_cb(self, **kwargs): old_level = kwargs['old_level'] diff --git a/src/jarabe/desktop/meshbox.py b/src/jarabe/desktop/meshbox.py index e7bba7b..41ffcc6 100644 --- a/src/jarabe/desktop/meshbox.py +++ b/src/jarabe/desktop/meshbox.py @@ -1,6 +1,6 @@ # 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 # # 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,6 +24,7 @@ import dbus import hippo import gobject import gtk +import gconf from sugar.graphics.icon import CanvasIcon, Icon from sugar.graphics.xocolor import XoColor @@ -36,6 +37,7 @@ 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.view.buddyicon import BuddyIcon @@ -51,17 +53,21 @@ 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.network import OlpcMesh as OlpcMeshSettings +from jarabe.model.olpcmesh import OlpcMeshManager +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' -_ICON_NAME = 'network-wireless' - +_AP_ICON_NAME = 'network-wireless' +_OLPC_MESH_ICON_NAME = 'network-mesh' class WirelessNetworkView(CanvasPulsingIcon): def __init__(self, initial_ap): @@ -83,14 +89,11 @@ class WirelessNetworkView(CanvasPulsingIcon): 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]) + if self._mode == network.NM_802_11_MODE_ADHOC and \ + network.is_sugar_adhoc_network(self._name): + self._color = profile.get_color() else: sh = sha.new() data = self._name + hex(self._flags) @@ -112,18 +115,9 @@ class WirelessNetworkView(CanvasPulsingIcon): self.set_palette(self._palette) self._palette_icon.props.xo_color = self._color - if network.find_connection(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 + self._update_badge() - interface_props = dbus.Interface(self._device, - 'org.freedesktop.DBus.Properties') + 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) @@ -143,17 +137,12 @@ class WirelessNetworkView(CanvasPulsingIcon): 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(_ICON_NAME, self._strength) + 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) @@ -171,6 +160,8 @@ class WirelessNetworkView(CanvasPulsingIcon): 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: @@ -178,12 +169,10 @@ class WirelessNetworkView(CanvasPulsingIcon): # 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: @@ -203,14 +192,38 @@ class WirelessNetworkView(CanvasPulsingIcon): def __get_device_state_reply_cb(self, state): self._device_state = state - self._update() + 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(self): - self._update_state() - self._update_color() + 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_state(self): if self._active_ap is not None: @@ -218,22 +231,6 @@ class WirelessNetworkView(CanvasPulsingIcon): else: state = network.DEVICE_STATE_UNKNOWN - if state == network.DEVICE_STATE_ACTIVATED: - connection = network.find_connection(self._name) - if connection: - if self._mode == network.NM_802_11_MODE_INFRA: - connection.set_connected() - - icon_name = '%s-connected' % _ICON_NAME - else: - icon_name = _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 \ @@ -244,6 +241,10 @@ class WirelessNetworkView(CanvasPulsingIcon): 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() @@ -256,15 +257,51 @@ class WirelessNetworkView(CanvasPulsingIcon): self._palette.props.secondary_text = None self.props.pulsing = False - def _update_color(self): + 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 _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 _disconnect_activate_cb(self, item): - pass + connection = network.find_connection_by_ssid(self._name) + if connection: + if self._mode == network.NM_802_11_MODE_INFRA: + connection.set_disconnected() + + obj = self._bus.get_object(_NM_SERVICE, _NM_PATH) + netmgr = dbus.Interface(obj, _NM_IFACE) + + 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, dbus.PROPERTIES_IFACE) + state = props.Get(_NM_ACTIVE_CONN_IFACE, 'State') + if state == network.NM_ACTIVE_CONNECTION_STATE_ACTIVATED: + ap_o = props.Get(_NM_ACTIVE_CONN_IFACE, 'SpecificObject') + if ap_o != '/' and self.find_ap(ap_o) is not None: + netmgr.DeactivateConnection(conn_o) + else: + logging.error('Could not determine AP for' + ' specific object %s' % conn_o) def _add_ciphers_from_flags(self, flags, pairwise): ciphers = [] @@ -336,11 +373,11 @@ class WirelessNetworkView(CanvasPulsingIcon): self._connect() def _connect(self): - connection = network.find_connection(self._name) + connection = network.find_connection_by_ssid(self._name) if connection is None: settings = Settings() settings.connection.id = 'Auto ' + self._name - settings.connection.uuid = unique_id() + uuid = settings.connection.uuid = unique_id() settings.connection.type = '802-11-wireless' settings.wireless.ssid = self._name @@ -358,7 +395,7 @@ class WirelessNetworkView(CanvasPulsingIcon): if wireless_security is not None: settings.wireless.security = '802-11-wireless-security' - connection = network.add_connection(self._name, settings) + connection = network.add_connection(uuid, settings) obj = self._bus.get_object(_NM_SERVICE, _NM_PATH) netmgr = dbus.Interface(obj, _NM_IFACE) @@ -377,7 +414,7 @@ class WirelessNetworkView(CanvasPulsingIcon): def set_filter(self, query): self._greyed_out = self._name.lower().find(query) == -1 - self._update_state() + self._update_icon() self._update_color() def create_keydialog(self, settings, response): @@ -396,7 +433,7 @@ class WirelessNetworkView(CanvasPulsingIcon): if new_strength != self._strength: self._strength = new_strength - self._update_state() + self._update_icon() def add_ap(self, ap): self._access_points[ap.model.object_path] = ap @@ -419,6 +456,17 @@ class WirelessNetworkView(CanvasPulsingIcon): 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', @@ -430,6 +478,280 @@ class WirelessNetworkView(CanvasPulsingIcon): 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._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): + 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) + + class ActivityView(hippo.CanvasBox): def __init__(self, model): hippo.CanvasBox.__init__(self) @@ -616,13 +938,19 @@ 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 = dbus.Interface(device, _NM_WIRELESS_IFACE) wireless.GetAccessPoints(reply_handler=self._get_access_points_reply_cb, error_handler=self._get_access_points_error_cb) @@ -638,35 +966,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 = dbus.SystemBus() 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: @@ -685,6 +1020,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: @@ -694,13 +1032,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') @@ -724,11 +1061,20 @@ 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): logging.error('Failed to get device type: %s', err) @@ -741,6 +1087,28 @@ 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 + + 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): @@ -752,11 +1120,13 @@ class MeshBox(gtk.VBox): gobject.GObject.__init__(self) self.wireless_networks = {} + self._adhoc_manager = None + self._adhoc_networks = [] self._model = neighborhood.get_model() self._buddies = {} self._activities = {} - self._mesh = {} + self._mesh = [] self._buddy_to_activity = {} self._suspended = True self._query = '' @@ -901,6 +1271,23 @@ class MeshBox(gtk.VBox): del self.wireless_networks[hash] def _ap_props_changed_cb(self, ap, old_hash): + # 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") + ap.disconnect() + return + + 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 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 is None: # new AP finished initializing self._add_ap_to_network(ap) return @@ -923,6 +1310,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(): @@ -935,18 +1327,68 @@ class MeshBox(gtk.VBox): self._remove_net_if_empty(net, ap.network_hash()) return - logging.error('Can not remove access point %s', ap_o) + # 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) + + 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) + self._layout.add(icon) + self._mesh.append(icon) + + def enable_olpc_mesh(self, mesh_device): + mesh_mgr = OlpcMeshManager(mesh_device) + self._add_olpc_mesh_icon(mesh_mgr, 1) + self._add_olpc_mesh_icon(mesh_mgr, 6) + self._add_olpc_mesh_icon(mesh_mgr, 11) + + # 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(): + if not net.is_olpc_mesh(): + continue + + logging.debug("removing OLPC mesh IBSS") + net.remove_all_aps() + net.disconnect() + self._layout.remove(net) + del self.wireless_networks[hash] + + def disable_olpc_mesh(self, mesh_device): + for icon in self._mesh: + icon.disconnect() + self._layout.remove(icon) + self._mesh = [] def suspend(self): if not self._suspended: self._suspended = True - for net in self.wireless_networks.values(): + for net in self.wireless_networks.values() + self._mesh: net.props.paused = True def resume(self): if self._suspended: self._suspended = False - for net in self.wireless_networks.values(): + for net in self.wireless_networks.values() + self._mesh: net.props.paused = False def _toolbar_query_changed_cb(self, toolbar, query): diff --git a/src/jarabe/desktop/schoolserver.py b/src/jarabe/desktop/schoolserver.py index 1dd9edc..a7d0e63 100644 --- a/src/jarabe/desktop/schoolserver.py +++ b/src/jarabe/desktop/schoolserver.py @@ -20,6 +20,7 @@ from xmlrpclib import ServerProxy, Error import socket import os import gconf +import dbus from sugar.profile import get_profile @@ -57,6 +58,8 @@ def register_laptop(url=REGISTER_URL): client.set_string('/desktop/sugar/collaboration/jabber_server', data['jabberserver']) + _restart_jabber() + client.set_string('/desktop/sugar/backup_url', data['backupurl']) return True @@ -72,3 +75,19 @@ def read_ofw(path): data = fh.read().rstrip('\0\n') fh.close() return data + +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 RegisterError('%s service not available' % _PS_SERVICE) + ps.RetryConnections() diff --git a/src/jarabe/journal/journalactivity.py b/src/jarabe/journal/journalactivity.py index 18cc64a..657f03c 100644 --- a/src/jarabe/journal/journalactivity.py +++ b/src/jarabe/journal/journalactivity.py @@ -27,6 +27,9 @@ import statvfs import os from sugar.graphics.window import Window +from sugar.graphics.alert import Alert +from sugar.graphics.icon import Icon + from sugar.bundle.bundle import ZipExtractException, RegistrationException from sugar import env from sugar.activity import activityfactory @@ -138,6 +141,18 @@ class JournalActivity(Window): self._critical_space_alert = None self._check_available_space() + def __volume_error_cb(self, gobject, message, severity): + alert = Alert(title=severity, msg=message) + icon = Icon(icon_name='dialog-ok') + alert.add_button(gtk.RESPONSE_OK, _('Ok'), icon) + icon.show() + 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 +176,8 @@ 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,8 +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) self._secondary_view.pack_end(self._detail_view) @@ -180,8 +197,6 @@ class JournalActivity(Window): def _key_press_event_cb(self, widget, event): keyname = gtk.gdk.keyval_name(event.keyval) - logging.info(keyname) - logging.info(event.state) if keyname == 'Escape': self.show_main_view() diff --git a/src/jarabe/journal/journalentrybundle.py b/src/jarabe/journal/journalentrybundle.py index 9e68c06..41777c7 100644 --- a/src/jarabe/journal/journalentrybundle.py +++ b/src/jarabe/journal/journalentrybundle.py @@ -40,7 +40,7 @@ class JournalEntryBundle(Bundle): def __init__(self, path): Bundle.__init__(self, path) - def install(self, install_path, uid=''): + def install(self, uid=''): if os.environ.has_key('SUGAR_ACTIVITY_ROOT'): install_dir = os.path.join(os.environ['SUGAR_ACTIVITY_ROOT'], 'data') diff --git a/src/jarabe/journal/journaltoolbox.py b/src/jarabe/journal/journaltoolbox.py index 17a65e6..f71049e 100644 --- a/src/jarabe/journal/journaltoolbox.py +++ b/src/jarabe/journal/journaltoolbox.py @@ -325,6 +325,11 @@ class DetailToolbox(Toolbox): 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) @@ -394,7 +399,22 @@ class EntryToolbar(gtk.Toolbar): misc.resume(self._metadata, service_name) def _copy_menu_item_activate_cb(self, menu_item, mount): - model.copy(self._metadata, mount.get_root().get_path()) + 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.get_root().get_path()) + except (IOError, OSError), 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() diff --git a/src/jarabe/journal/listview.py b/src/jarabe/journal/listview.py index 6556b08..e1ca620 100644 --- a/src/jarabe/journal/listview.py +++ b/src/jarabe/journal/listview.py @@ -1,4 +1,4 @@ -# Copyright (C) 2007, One Laptop Per Child +# Copyright (C) 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 @@ -23,7 +23,6 @@ import time import hippo import gobject import gtk -import dbus from sugar.graphics import style from sugar.graphics.icon import CanvasIcon, Icon @@ -31,10 +30,6 @@ from sugar.graphics.icon import CanvasIcon, Icon from jarabe.journal.collapsedentry import CollapsedEntry from jarabe.journal import model -DS_DBUS_SERVICE = 'org.laptop.sugar.DataStore' -DS_DBUS_INTERFACE = 'org.laptop.sugar.DataStore' -DS_DBUS_PATH = '/org/laptop/sugar/DataStore' - UPDATE_INTERVAL = 300 EMPTY_JOURNAL = _("Your Journal is empty") @@ -109,19 +104,18 @@ class BaseListView(gtk.HBox): self._refresh_idle_handler = None self._update_dates_timer = None - bus = dbus.SessionBus() - datastore = dbus.Interface( - bus.get_object(DS_DBUS_SERVICE, DS_DBUS_PATH), DS_DBUS_INTERFACE) - self._datastore_created_handler = \ - datastore.connect_to_signal('Created', - self.__datastore_created_cb) - self._datastore_updated_handler = \ - datastore.connect_to_signal('Updated', - self.__datastore_updated_cb) + model.created.connect(self.__model_created_cb) + model.updated.connect(self.__model_updated_cb) + model.deleted.connect(self.__model_deleted_cb) + + def __model_created_cb(self, sender, **kwargs): + self._set_dirty() + + def __model_updated_cb(self, sender, **kwargs): + self._set_dirty() - self._datastore_deleted_handler = \ - datastore.connect_to_signal('Deleted', - self.__datastore_deleted_cb) + def __model_deleted_cb(self, sender, **kwargs): + self._set_dirty() def __destroy_cb(self, widget): self._datastore_created_handler.remove() @@ -463,15 +457,6 @@ class BaseListView(gtk.HBox): if entry.get_visible(): entry.update_date() - def __datastore_created_cb(self, uid): - self._set_dirty() - - def __datastore_updated_cb(self, uid): - self._set_dirty() - - def __datastore_deleted_cb(self, uid): - self._set_dirty() - def _set_dirty(self): if self._fully_obscured: self._dirty = True diff --git a/src/jarabe/journal/misc.py b/src/jarabe/journal/misc.py index b29b744..890fe60 100644 --- a/src/jarabe/journal/misc.py +++ b/src/jarabe/journal/misc.py @@ -95,21 +95,21 @@ def get_date(metadata): 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 diff --git a/src/jarabe/journal/model.py b/src/jarabe/journal/model.py index 1b4e236..a93321e 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, 2008, 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,16 +16,18 @@ import logging import os +import errno from datetime import datetime import time import shutil -from stat import S_IFMT, S_IFDIR, S_IFREG -import traceback +import tempfile +from stat import S_IFLNK, S_IFMT, S_IFDIR, S_IFREG import re +import json +from gettext import gettext as _ import gobject import dbus -import gconf import gio from sugar import dispatch @@ -43,6 +45,8 @@ PROPERTIES = ['uid', 'title', 'mtime', 'timestamp', 'keep', 'buddies', PAGES_TO_CACHE = 5 +JOURNAL_METADATA_DIR = '.Sugar-Metadata' + class _Cache(object): __gtype_name__ = 'model_Cache' @@ -258,7 +262,9 @@ class InplaceResultSet(BaseResultSet): BaseResultSet.__init__(self, query, cache_limit) 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', '') @@ -283,7 +289,10 @@ class InplaceResultSet(BaseResultSet): 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 @@ -308,8 +317,9 @@ class InplaceResultSet(BaseResultSet): files = self._file_list[offset:offset + limit] entries = [] - for file_path, stat, mtime_ in files: - metadata = _get_file_metadata(file_path, stat) + for file_path, stat, mtime_, metadata in files: + if metadata is None: + metadata = _get_file_metadata(file_path, stat) metadata['mountpoint'] = self._mount_point entries.append(metadata) @@ -317,63 +327,166 @@ class InplaceResultSet(BaseResultSet): return entries, total_count - def _recurse_dir(self, dir_path): + def _scan(self): if self._stopped: + return False + + self.progress.send(self) + + 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) + metadata = None + + try: + stat = os.lstat(full_path) + except OSError, e: + if e.errno != errno.ENOENT: + logging.exception( + 'Error reading metadata of file %r', full_path) return - for entry in os.listdir(dir_path): - if entry.startswith('.'): - continue - full_path = dir_path + '/' + entry + 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 + try: stat = os.stat(full_path) - if S_IFMT(stat.st_mode) == S_IFDIR: - self._pending_directories += 1 - gobject.idle_add(lambda s=full_path: self._recurse_dir(s)) - 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): + filename = os.path.basename(full_path) + dir_path = os.path.dirname(full_path) + metadata = _get_file_metadata_from_json( \ + dir_path, filename, preview=False) + add_to_list = False + if metadata is not None: + for f in ['fulltext', 'title', + 'description', 'tags']: + if f in metadata and \ + self._regex.match(metadata[f]): + add_to_list = True + break + if not add_to_list: + return + + if self._date_start is not None and stat.st_mtime < self._date_start: + 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_end is not None and stat.st_mtime > self._date_end: + 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._mime_types: + mime_type = gio.content_type_guess(filename=full_path) + if mime_type not in self._mime_types: + return - if add_to_list: - file_info = (full_path, stat, int(stat.st_mtime)) - self._file_list.append(file_info) + file_info = (full_path, stat, int(stat.st_mtime), metadata) + self._file_list.append(file_info) - self.progress.send(self) + return - except Exception: - logging.error('Error reading file %r: %s' % \ - (full_path, traceback.format_exc())) + def _scan_a_directory(self): + dir_path = self._pending_directories.pop(0) - if self._pending_directories == 0: - self.setup_ready() - else: - self._pending_directories -= 1 + 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 def _get_file_metadata(path, stat): - client = gconf.client_get_default() + """Returns the metadata from the corresponding file + on the external device or does create the metadata + based on the file properties. + + """ + filename = os.path.basename(path) + dir_path = os.path.dirname(path) + metadata = _get_file_metadata_from_json(dir_path, filename, preview=True) + if metadata: + return metadata + return {'uid': path, 'title': os.path.basename(path), 'timestamp': stat.st_mtime, 'mime_type': gio.content_type_guess(filename=path), 'activity': '', 'activity_id': '', - 'icon-color': client.get_string('/desktop/sugar/user/color'), + 'icon-color': '', 'description': path} +def _get_file_metadata_from_json(dir_path, filename, preview=False): + """Returns the metadata from the json file and the preview + stored on the external device. + + """ + metadata = None + metadata_path = os.path.join(dir_path, JOURNAL_METADATA_DIR, + filename + '.metadata') + if os.path.exists(metadata_path): + try: + metadata = json.load(open(metadata_path)) + except ValueError: + logging.debug("Could not read metadata for file %r on" \ + "external device.", filename) + else: + metadata['uid'] = os.path.join(dir_path, filename) + if preview: + preview_path = os.path.join(dir_path, JOURNAL_METADATA_DIR, + filename + '.preview') + if os.path.exists(preview_path): + try: + metadata['preview'] = dbus.ByteArray(open(preview_path).read()) + except: + logging.debug("Could not read preview for file %r on" \ + "external device.", filename) + else: + if metadata and 'preview' in metadata: + del(metadata['preview']) + return metadata + _datastore = None def _get_datastore(): global _datastore @@ -460,6 +573,19 @@ def delete(object_id): """ if os.path.exists(object_id): os.unlink(object_id) + dir_path = os.path.dirname(object_id) + filename = os.path.basename(object_id) + old_files = [os.path.join(dir_path, JOURNAL_METADATA_DIR, + filename + '.metadata'), + os.path.join(dir_path, JOURNAL_METADATA_DIR, + filename + '.preview')] + for old_file in old_files: + if os.path.exists(old_file): + try: + os.unlink(old_file) + except: + pass + deleted.send(None, object_id=object_id) else: _get_datastore().delete(object_id) @@ -472,9 +598,9 @@ def copy(metadata, mount_point): metadata['mountpoint'] = mount_point del metadata['uid'] - return write(metadata, file_path) + return write(metadata, file_path, transfer_ownership=False) -def write(metadata, file_path='', update_mtime=True): +def write(metadata, file_path='', update_mtime=True, transfer_ownership=True): """Creates or updates an entry for that id """ logging.debug('model.write %r %r %r' % (metadata.get('uid', ''), file_path, @@ -488,31 +614,110 @@ def write(metadata, file_path='', update_mtime=True): object_id = _get_datastore().update(metadata['uid'], dbus.Dictionary(metadata), file_path, - True) + transfer_ownership) else: object_id = _get_datastore().create(dbus.Dictionary(metadata), file_path, - True) + transfer_ownership) else: - if not os.path.exists(file_path): - raise ValueError('Entries without a file cannot be copied to ' - 'removable devices') + object_id = _write_entry_on_external_device(metadata, file_path) - file_name = _get_file_name(metadata['title'], metadata['mime_type']) - file_name = _get_unique_file_name(metadata['mountpoint'], file_name) + return object_id + +def _write_entry_on_external_device(metadata, file_path): + """This creates and updates an entry copied from the + DS to external storage device. Besides copying the + associated file a hidden file for the preview and one + for the metadata are stored. We make sure that the + metadata and preview file are in the same directory + as the data file. + + This function handles renames of an entry on the + external device and avoids name collisions. Renames are + handled failsafe. + + """ + if 'uid' in metadata and os.path.exists(metadata['uid']): + file_path = metadata['uid'] + + if not file_path or not os.path.exists(file_path): + raise ValueError('Entries without a file cannot be copied to ' + 'removable devices') + if metadata['title'] == '': + metadata['title'] = _('Untitled') + file_name = get_file_name(metadata['title'], metadata['mime_type']) + + destination_path = os.path.join(metadata['mountpoint'], file_name) + if destination_path != file_path: + file_name = get_unique_file_name(metadata['mountpoint'], file_name) destination_path = os.path.join(metadata['mountpoint'], file_name) + clean_name, extension_ = os.path.splitext(file_name) + metadata['title'] = clean_name + + metadata_copy = metadata.copy() + del metadata_copy['mountpoint'] + if 'uid' in metadata_copy: + del metadata_copy['uid'] + + metadata_dir_path = os.path.join(metadata['mountpoint'], + JOURNAL_METADATA_DIR) + if not os.path.exists(metadata_dir_path): + os.mkdir(metadata_dir_path) + + if 'preview' in metadata_copy: + preview = metadata_copy['preview'] + preview_fname = file_name + '.preview' + preview_path = os.path.join(metadata['mountpoint'], + JOURNAL_METADATA_DIR, preview_fname) + metadata_copy['preview'] = preview_fname + + (fh, fn) = tempfile.mkstemp(dir=metadata['mountpoint']) + os.write(fh, preview) + os.close(fh) + os.rename(fn, preview_path) + + metadata_path = os.path.join(metadata['mountpoint'], + JOURNAL_METADATA_DIR, + file_name + '.metadata') + (fh, fn) = tempfile.mkstemp(dir=metadata['mountpoint']) + os.write(fh, json.dumps(metadata_copy)) + os.close(fh) + os.rename(fn, metadata_path) + + if os.path.dirname(destination_path) == os.path.dirname(file_path): + old_file_path = file_path + if old_file_path != destination_path: + os.rename(file_path, destination_path) + old_fname = os.path.basename(file_path) + old_files = [os.path.join(metadata['mountpoint'], + JOURNAL_METADATA_DIR, + old_fname + '.metadata'), + os.path.join(metadata['mountpoint'], + JOURNAL_METADATA_DIR, + old_fname + '.preview')] + for ofile in old_files: + if os.path.exists(ofile): + try: + os.unlink(ofile) + except: + pass + else: shutil.copy(file_path, destination_path) - object_id = destination_path + + object_id = destination_path + created.send(None, object_id=object_id) return object_id -def _get_file_name(title, mime_type): +def get_file_name(title, mime_type): file_name = title - extension = '.' + mime.get_primary_extension(mime_type) - if not file_name.endswith(extension): - file_name += extension + mime_extension = mime.get_primary_extension(mime_type) + if mime_extension: + extension = '.' + mime_extension + if not file_name.endswith(extension): + file_name += extension # Invalid characters in VFAT filenames. From # http://en.wikipedia.org/wiki/File_Allocation_Table @@ -529,11 +734,11 @@ def _get_file_name(title, mime_type): return file_name -def _get_unique_file_name(mount_point, 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 diff --git a/src/jarabe/journal/palettes.py b/src/jarabe/journal/palettes.py index 2c15591..c16f374 100644 --- a/src/jarabe/journal/palettes.py +++ b/src/jarabe/journal/palettes.py @@ -68,22 +68,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')) @@ -134,11 +141,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): diff --git a/src/jarabe/journal/volumestoolbar.py b/src/jarabe/journal/volumestoolbar.py index b21832e..9a49cdf 100644 --- a/src/jarabe/journal/volumestoolbar.py +++ b/src/jarabe/journal/volumestoolbar.py @@ -15,7 +15,13 @@ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA import logging +import os from gettext import gettext as _ +import cPickle +import xapian +import json +import tempfile +import shutil import gobject import gio @@ -29,13 +35,126 @@ from sugar.graphics.xocolor import XoColor from jarabe.journal import model from jarabe.view.palettes import VolumePalette +_JOURNAL_0_METADATA_DIR = '.olpc.store' + +def _get_id(document): + """Get the ID for the document in the xapian database.""" + tl = document.termlist() + try: + term = tl.skip_to('Q').term + if len(term) == 0 or term[0] != 'Q': + return None + return term[1:] + except StopIteration: + return None + +def _convert_entries(root): + """Converts the entries written by the datastore version 0. + The metadata and the preview will be written using the new + scheme for writing Journal entries to removable storage + devices. + + - entries that do not have an associated file are not + converted. + - if an entry has no title we set it to Untitled and rename + the file accordingly, taking care of creating a unique + filename + + """ + try: + database = xapian.Database(os.path.join(root, _JOURNAL_0_METADATA_DIR, + 'index')) + except xapian.DatabaseError, e: + logging.error('Convert DS-0 Journal entry. Error reading db: %s', + os.path.join(root, _JOURNAL_0_METADATA_DIR, 'index')) + return + + metadata_dir_path = os.path.join(root, model.JOURNAL_METADATA_DIR) + if not os.path.exists(metadata_dir_path): + os.mkdir(metadata_dir_path) + + for i in range(1, database.get_lastdocid() + 1): + try: + document = database.get_document(i) + except xapian.DocNotFoundError, e: + logging.debug('Convert DS-0 Journal entry. ' \ + 'Error getting document %s: %s', i, e) + continue + + try: + metadata_loaded = cPickle.loads(document.get_data()) + except cPickle.PickleError, e: + logging.debug('Convert DS-0 Journal entry. ' \ + 'Error converting metadata: %s', e) + continue + + if 'activity_id' in metadata_loaded and \ + 'mime_type' in metadata_loaded and \ + 'title' in metadata_loaded: + metadata = {} + + uid = _get_id(document) + if uid is None: + continue + + for key, value in metadata_loaded.items(): + metadata[str(key)] = str(value[0]) + + if 'uid' not in metadata: + metadata['uid'] = uid + + if 'filename' in metadata: + filename = metadata['filename'] + else: + continue + if not os.path.exists(os.path.join(root, filename)): + continue + + if metadata['title'] == '': + metadata['title'] = _('Untitled') + fn = model.get_file_name(metadata['title'], + metadata['mime_type']) + new_filename = model.get_unique_file_name(root, fn) + metadata['filename'] = new_filename + os.rename(os.path.join(root, filename), + os.path.join(root, new_filename)) + filename = new_filename + + preview_path = os.path.join(root, _JOURNAL_0_METADATA_DIR, + 'preview', uid) + if os.path.exists(preview_path): + preview_fname = filename + '.preview' + new_preview_path = os.path.join(root, + model.JOURNAL_METADATA_DIR, + preview_fname) + if not os.path.exists(new_preview_path): + metadata['preview'] = preview_fname + shutil.copy(preview_path, new_preview_path) + + metadata_fname = filename + '.metadata' + metadata_path = os.path.join(root, model.JOURNAL_METADATA_DIR, + metadata_fname) + if not os.path.exists(metadata_path): + (fh, fn) = tempfile.mkstemp(dir=root) + os.write(fh, json.dumps(metadata)) + os.close(fh) + os.rename(fn, metadata_path) + + logging.debug('Convert DS-0 Journal entry. Entry converted: ' \ + 'File=%s Metadata=%s', + os.path.join(root, filename), metadata) + + class VolumesToolbar(gtk.Toolbar): __gtype_name__ = 'VolumesToolbar' __gsignals__ = { 'volume-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([str])) + ([str])), + 'volume-error': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([str, str])) } def __init__(self): @@ -78,9 +197,15 @@ class VolumesToolbar(gtk.Toolbar): def _add_button(self, mount): logging.debug('VolumeToolbar._add_button: %r' % mount.get_name()) + if os.path.exists(os.path.join(mount.get_root().get_path(), + _JOURNAL_0_METADATA_DIR)): + logging.debug('Convert DS-0 Journal entries.') + gobject.idle_add(_convert_entries, mount.get_root().get_path()) + 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 +215,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) @@ -123,6 +251,12 @@ class VolumesToolbar(gtk.Toolbar): 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) @@ -137,7 +271,22 @@ class BaseButton(RadioToolButton): 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, OSError), 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): diff --git a/src/jarabe/model/Makefile.am b/src/jarabe/model/Makefile.am index 399db65..1df2cde 100644 --- a/src/jarabe/model/Makefile.am +++ b/src/jarabe/model/Makefile.am @@ -1,11 +1,13 @@ sugardir = $(pythondir)/jarabe/model sugar_PYTHON = \ + adhoc.py \ __init__.py \ buddy.py \ bundleregistry.py \ filetransfer.py \ friends.py \ invites.py \ + olpcmesh.py \ owner.py \ neighborhood.py \ network.py \ diff --git a/src/jarabe/model/adhoc.py b/src/jarabe/model/adhoc.py new file mode 100644 index 0000000..5c9d6f5 --- /dev/null +++ b/src/jarabe/model/adhoc.py @@ -0,0 +1,280 @@ +# 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, 'org.freedesktop.DBus.Properties') + 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 autoconnect(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/bundleregistry.py b/src/jarabe/model/bundleregistry.py index ac785fd..924c18f 100644 --- a/src/jarabe/model/bundleregistry.py +++ b/src/jarabe/model/bundleregistry.py @@ -20,6 +20,7 @@ import logging import traceback import sys +import gconf import gobject import gio import simplejson @@ -27,6 +28,7 @@ import simplejson from sugar.bundle.activitybundle import ActivityBundle from sugar.bundle.contentbundle import ContentBundle from jarabe.journal.journalentrybundle import JournalEntryBundle +from sugar.bundle.bundleversion import NormalizedVersion from sugar.bundle.bundle import MalformedBundleException, \ AlreadyInstalledException, RegistrationException from sugar import env @@ -62,6 +64,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: @@ -141,14 +151,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) @@ -272,6 +284,9 @@ 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: @@ -324,8 +339,8 @@ 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 @@ -338,15 +353,15 @@ 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()): raise AlreadyInstalledException elif bundle.get_bundle_id() == installed_bundle.get_bundle_id(): self.uninstall(installed_bundle, force=True) install_dir = env.get_user_activities_path() if isinstance(bundle, JournalEntryBundle): - install_path = bundle.install(install_dir, uid) + install_path = bundle.install(uid) else: install_path = bundle.install(install_dir) @@ -371,7 +386,8 @@ class BundleRegistry(gobject.GObject): act = self.get_bundle(bundle.get_bundle_id()) if not force and \ - act.get_activity_version() != bundle.get_activity_version(): + NormalizedVersion(act.get_activity_version()) != \ + NormalizedVersion(bundle.get_activity_version()): logging.warning('Not uninstalling, different bundle present') return elif not act.get_path().startswith(env.get_user_activities_path()): diff --git a/src/jarabe/model/network.py b/src/jarabe/model/network.py index c1f7969..f0297c9 100644 --- a/src/jarabe/model/network.py +++ b/src/jarabe/model/network.py @@ -1,6 +1,7 @@ # 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 # # 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 @@ -21,14 +22,20 @@ 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 +DEVICE_TYPE_802_11_OLPC_MESH = 6 DEVICE_STATE_UNKNOWN = 0 DEVICE_STATE_UNMANAGED = 1 @@ -41,6 +48,9 @@ DEVICE_STATE_IP_CONFIG = 7 DEVICE_STATE_ACTIVATED = 8 DEVICE_STATE_FAILED = 9 +NM_CONNECTION_TYPE_802_11_WIRELESS = '802-11-wireless' +NM_CONNECTION_TYPE_GSM = 'gsm' + NM_ACTIVE_CONNECTION_STATE_UNKNOWN = 0 NM_ACTIVE_CONNECTION_STATE_ACTIVATING = 1 NM_ACTIVE_CONNECTION_STATE_ACTIVATED = 2 @@ -80,9 +90,48 @@ NM_CONNECTION_IFACE = 'org.freedesktop.NetworkManagerSettings.Connection' NM_SECRETS_IFACE = 'org.freedesktop.NetworkManagerSettings.Connection.Secrets' NM_ACCESSPOINT_IFACE = 'org.freedesktop.NetworkManager.AccessPoint' +GSM_USERNAME_PATH = '/desktop/sugar/network/gsm/username' +GSM_PASSWORD_PATH = '/desktop/sugar/network/gsm/password' +GSM_NUMBER_PATH = '/desktop/sugar/network/gsm/number' +GSM_APN_PATH = '/desktop/sugar/network/gsm/apn' +GSM_PIN_PATH = '/desktop/sugar/network/gsm/pin' +GSM_PUK_PATH = '/desktop/sugar/network/gsm/puk' + _nm_settings = None _conn_counter = 0 +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 @@ -103,11 +152,14 @@ class WirelessSecurity(object): return wireless_security class Wireless(object): + 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} @@ -117,8 +169,27 @@ 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" + + def __init__(self, channel, anycast_addr): + self.channel = channel + self.anycast_addr = anycast_addr + + def get_dict(self): + ret = { + "ssid": dbus.ByteArray("olpc-mesh"), + "channel": self.channel, + } + + if self.anycast_addr: + ret["dhcp-anycast-address"] = dbus.ByteArray(self.anycast_addr) + return ret + class Connection(object): def __init__(self): self.id = None @@ -146,17 +217,60 @@ class IP4Config(object): ip4_config['method'] = self.method return ip4_config -class Settings(object): +class Serial(object): + def __init__(self): + self.baud = None + + def get_dict(self): + serial = {} + + if self.baud is not None: + serial['baud'] = self.baud + + return serial + +class Ppp(object): def __init__(self): + pass + + def get_dict(self): + ppp = {} + return ppp + +class Gsm(object): + def __init__(self): + self.apn = None + self.number = None + self.username = None + + def get_dict(self): + gsm = {} + + if self.apn is not None: + gsm['apn'] = self.apn + if self.number is not None: + gsm['number'] = self.number + if self.username is not None: + gsm['username'] = self.username + + return gsm + +class Settings(object): + def __init__(self, wireless_cfg=None): self.connection = Connection() self.wireless = Wireless() self.ip4_config = None self.wireless_security = None + if wireless_cfg is not None: + self.wireless = wireless_cfg + else: + self.wireless = Wireless() + def get_dict(self): settings = {} settings['connection'] = self.connection.get_dict() - settings['802-11-wireless'] = self.wireless.get_dict() + settings[self.wireless.nm_name] = self.wireless.get_dict() if self.wireless_security is not None: settings['802-11-wireless-security'] = \ self.wireless_security.get_dict() @@ -189,6 +303,41 @@ class Secrets(object): return settings +class SettingsGsm(object): + def __init__(self): + self.connection = Connection() + self.ip4_config = IP4Config() + self.serial = Serial() + self.ppp = Ppp() + self.gsm = Gsm() + + def get_dict(self): + settings = {} + + settings['connection'] = self.connection.get_dict() + settings['serial'] = self.serial.get_dict() + settings['ppp'] = self.ppp.get_dict() + settings['gsm'] = self.gsm.get_dict() + settings['ipv4'] = self.ip4_config.get_dict() + + 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: + secrets['puk'] = self.puk + return {'gsm': secrets} + class NMSettings(dbus.service.Object): def __init__(self): bus = dbus.SystemBus() @@ -207,8 +356,8 @@ class NMSettings(dbus.service.Object): def NewConnection(self, connection_path): pass - def add_connection(self, ssid, conn): - self.connections[ssid] = conn + def add_connection(self, uuid, conn): + self.connections[uuid] = conn conn.secrets_request.connect(self.__secrets_request_cb) self.NewConnection(conn.path) @@ -216,6 +365,11 @@ class NMSettings(dbus.service.Object): self.secrets_request.send(self, connection=sender, response=kwargs['response']) + def clear_connections(self): + for connection in self.connections.values(): + connection.Removed() + self.connections = {} + class SecretsResponse(object): ''' Intermediate object to report the secrets from the dialog back to the connection object and which will inform NM @@ -244,10 +398,40 @@ 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 not self._settings.connection.autoconnect: - self._settings.connection.autoconnect = True + if self._settings.connection.type == NM_CONNECTION_TYPE_GSM: + self._settings.connection.timestamp = int(time.time()) + elif not self._settings.connection.autoconnect: self._settings.connection.timestamp = int(time.time()) + self._settings.connection.autoconnect = True + 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: + logging.exception('Error calling libc.__res_init') + + def set_disconnected(self): + if 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): @@ -258,6 +442,10 @@ class NMSettingsConnection(dbus.service.Object): return self._settings def save(self): + # We only save wifi settins + if self._settings.connection.type != NM_CONNECTION_TYPE_802_11_WIRELESS: + return + profile_path = env.get_profile_path() config_path = os.path.join(profile_path, 'nm', 'connections.cfg') @@ -336,7 +524,6 @@ class NMSettingsConnection(dbus.service.Object): else: reply(self._secrets.get_dict()) - class AccessPoint(gobject.GObject): __gsignals__ = { 'props-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, @@ -357,10 +544,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) @@ -426,6 +613,8 @@ 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) @@ -452,36 +641,38 @@ def get_settings(): load_connections() return _nm_settings -def find_connection(ssid): +def find_connection_by_ssid(ssid): connections = get_settings().connections - if ssid in connections: - return connections[ssid] - else: - return None -def add_connection(ssid, settings, secrets=None): + 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 + + return None + +def add_connection(uuid, settings, secrets=None): global _conn_counter path = NM_SETTINGS_PATH + '/' + str(_conn_counter) _conn_counter += 1 conn = NMSettingsConnection(path, settings, secrets) - _nm_settings.add_connection(ssid, conn) + _nm_settings.add_connection(uuid, conn) return conn -def load_connections(): +def load_wifi_connections(): profile_path = env.get_profile_path() config_path = os.path.join(profile_path, 'nm', 'connections.cfg') - config = ConfigParser.ConfigParser() - 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() + config = ConfigParser.ConfigParser() try: if not config.read(config_path): logging.error('Error reading the nm config file') @@ -534,4 +725,56 @@ def load_connections(): except ConfigParser.Error, e: logging.error('Error reading section: %s' % e) else: - add_connection(ssid, settings, secrets) + add_connection(uuid, settings, secrets) + +def count_connections(): + return len(get_settings().connections) + +def clear_connections(): + _nm_settings.clear_connections() + + profile_path = env.get_profile_path() + config_path = os.path.join(profile_path, 'nm', 'connections.cfg') + + 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_gsm_connection(): + client = gconf.client_get_default() + + settings = SettingsGsm() + settings.gsm.username = client.get_string(GSM_USERNAME_PATH) or '' + settings.gsm.number = client.get_string(GSM_NUMBER_PATH) or '' + settings.gsm.apn = client.get_string(GSM_APN_PATH) or '' + + secrets = SecretsGsm() + secrets.pin = client.get_string(GSM_PIN_PATH) or '' + secrets.puk = client.get_string(GSM_PUK_PATH) or '' + secrets.password = client.get_string(GSM_PASSWORD_PATH) or '' + + settings.connection.id = 'gsm' + settings.connection.type = NM_CONNECTION_TYPE_GSM + uuid = settings.connection.uuid = unique_id() + settings.connection.autoconnect = False + settings.ip4_config.method = 'auto' + settings.serial.baud = 115200 + + try: + add_connection(uuid, settings, secrets) + except Exception: + logging.exception('While adding gsm connection') + +def load_connections(): + load_wifi_connections() + load_gsm_connection() + +def find_gsm_connection(): + connections = get_settings().connections + + for connection in connections.values(): + if connection.get_settings().connection.type == NM_CONNECTION_TYPE_GSM: + return connection + + return None diff --git a/src/jarabe/model/olpcmesh.py b/src/jarabe/model/olpcmesh.py new file mode 100644 index 0000000..7873692 --- /dev/null +++ b/src/jarabe/model/olpcmesh.py @@ -0,0 +1,214 @@ +# 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 + +import logging + +import dbus +import gobject + +from jarabe.model import network +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" + +DEVICE_STATE_UNKNOWN = 0 +DEVICE_STATE_UNMANAGED = 1 +DEVICE_STATE_UNAVAILABLE = 2 +DEVICE_STATE_DISCONNECTED = 3 +DEVICE_STATE_PREPARE = 4 +DEVICE_STATE_CONFIG = 5 +DEVICE_STATE_NEED_AUTH = 6 +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() + + self.mesh_device = mesh_device + self.eth_device = self._get_companion_device() + + self._connection_queue = [] + """Stack of connections that we'll iterate through until we find one + that works. + + """ + + props = dbus.Interface(self.mesh_device, + 'org.freedesktop.DBus.Properties') + 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.Get(_NM_DEVICE_IFACE, 'State', + reply_handler=self.__get_eth_state_reply_cb, + error_handler=self.__get_state_error_cb) + + self._bus.add_signal_receiver(self.__eth_device_state_changed_cb, + signal_name='StateChanged', + path=self.eth_device.object_path, + dbus_interface=_NM_DEVICE_IFACE) + + self._bus.add_signal_receiver(self.__mshdev_state_changed_cb, + signal_name='StateChanged', + path=self.mesh_device.object_path, + dbus_interface=_NM_DEVICE_IFACE) + + self._idle_source = 0 + self._mesh_device_state = DEVICE_STATE_UNKNOWN + self._eth_device_state = DEVICE_STATE_UNKNOWN + + if self._have_configured_connections(): + self._start_automesh_timer() + else: + self._start_automesh() + + def _get_companion_device(self): + props = dbus.Interface(self.mesh_device, + 'org.freedesktop.DBus.Properties') + eth_device_o = props.Get(_NM_OLPC_MESH_IFACE, 'Companion') + return self._bus.get_object(_NM_SERVICE, eth_device_o) + + def _have_configured_connections(self): + return len(network.get_settings().connections) > 0 + + def _start_automesh_timer(self): + """Start our timer system which basically looks for 10 seconds of + inactivity on both devices, then starts automesh. + + """ + if self._idle_source != 0: + gobject.source_remove(self._idle_source) + self._idle_source = gobject.timeout_add_seconds(10, self._idle_check) + + def __get_state_error_cb(self, err): + logging.debug('Error getting the device state: %s', err) + + def __get_mesh_state_reply_cb(self, state): + self._mesh_device_state = state + self._maybe_schedule_idle_check() + + def __get_eth_state_reply_cb(self, state): + self._eth_device_state = state + self._maybe_schedule_idle_check() + + 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() + + if new_state >= DEVICE_STATE_PREPARE \ + and new_state <= DEVICE_STATE_ACTIVATED \ + and len(self._connection_queue) > 0: + self._connection_queue = [] + + def __mshdev_state_changed_cb(self, new_state, old_state, reason): + self._mesh_device_state = new_state + self._maybe_schedule_idle_check() + + if new_state == DEVICE_STATE_FAILED: + self._try_next_connection_from_queue() + elif new_state == DEVICE_STATE_ACTIVATED \ + and len(self._connection_queue) > 0: + self._empty_connection_queue() + + def _maybe_schedule_idle_check(self): + if self._mesh_device_state == DEVICE_STATE_DISCONNECTED \ + and self._eth_device_state == DEVICE_STATE_DISCONNECTED: + self._start_automesh_timer() + + 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") + self._start_automesh() + return False + + def _make_connection(self, channel, anycast_address=None): + wireless_config = OlpcMeshSettings(channel, anycast_address) + settings = Settings(wireless_cfg=wireless_config) + if not anycast_address: + settings.ip4_config = network.IP4Config() + settings.ip4_config.method = 'link-local' + settings.connection.id = 'olpc-mesh-' + str(channel) + settings.connection.uuid = unique_id() + settings.connection.type = '802-11-olpc-mesh' + connection = network.add_connection(settings.connection.id, settings) + return connection + + 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 _activate_connection(self, channel, anycast_address=None): + 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) + + network_manager.ActivateConnection(network.SETTINGS_SERVICE, + connection.path, + self.mesh_device.object_path, + self.mesh_device.object_path, + reply_handler=self.__activate_reply_cb, + error_handler=self.__activate_error_cb) + + def _try_next_connection_from_queue(self): + if len(self._connection_queue) == 0: + return + + channel, anycast = self._connection_queue.pop() + self._activate_connection(channel, anycast) + + def _empty_connection_queue(self): + self._connection_queue = [] + + def user_activate_channel(self, channel): + """Activate a mesh connection on a user-specified channel. + Looks for XS first, then resorts to simple mesh.""" + self._empty_connection_queue() + self._connection_queue.append((channel, None)) + self._connection_queue.append((channel, _XS_ANYCAST)) + self._try_next_connection_from_queue() + + def _start_automesh(self): + """Start meshing automatically, intended when there are no better + networks to connect to. First looks for XS on all channels, then falls + back to simple mesh on channel 1.""" + self._empty_connection_queue() + self._connection_queue.append((1, None)) + self._connection_queue.append((11, _XS_ANYCAST)) + 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/view/buddymenu.py b/src/jarabe/view/buddymenu.py index 35a8301..e9e9f8e 100644 --- a/src/jarabe/view/buddymenu.py +++ b/src/jarabe/view/buddymenu.py @@ -79,8 +79,8 @@ class BuddyMenu(Palette): self._update_invite_menu(activity) def _add_my_items(self): - item = MenuItem(_('My Settings'), 'preferences-system') - item.connect('activate', self.__controlpanel_activate_cb) + item = MenuItem(_('Shutdown'), 'system-shutdown') + item.connect('activate', self.__shutdown_activate_cb) self.menu.append(item) item.show() @@ -92,13 +92,8 @@ class BuddyMenu(Palette): self.menu.append(item) item.show() - item = MenuItem(_('Restart'), 'system-restart') - item.connect('activate', self.__reboot_activate_cb) - self.menu.append(item) - item.show() - - item = MenuItem(_('Shutdown'), 'system-shutdown') - item.connect('activate', self.__shutdown_activate_cb) + item = MenuItem(_('My Settings'), 'preferences-system') + item.connect('activate', self.__controlpanel_activate_cb) self.menu.append(item) item.show() diff --git a/src/jarabe/view/keyhandler.py b/src/jarabe/view/keyhandler.py index 1da1f6a..5358da8 100644 --- a/src/jarabe/view/keyhandler.py +++ b/src/jarabe/view/keyhandler.py @@ -47,6 +47,8 @@ _actions_table = { 'F2' : 'zoom_group', 'F3' : 'zoom_home', 'F4' : 'zoom_activity', + 'F5' : 'open_search', + 'F6' : 'frame', 'F9' : 'brightness_down', 'F10' : 'brightness_up', 'F9' : 'brightness_min', @@ -249,9 +251,9 @@ class KeyHandler(object): # If either the xmodmap or xrandr command fails, check_call will fail # with CalledProcessError, which we raise. try: - subprocess.check_call(argv) subprocess.check_call(['xrandr', '-o', states[self._screen_rotation]]) + subprocess.check_call(argv) except OSError, e: if e.errno != errno.EINTR: raise diff --git a/src/jarabe/view/launcher.py b/src/jarabe/view/launcher.py index 6ddb04a..3071790 100644 --- a/src/jarabe/view/launcher.py +++ b/src/jarabe/view/launcher.py @@ -28,14 +28,20 @@ from sugar.graphics.xocolor import XoColor from jarabe.model import shell from jarabe.view.pulsingicon import CanvasPulsingIcon -class LaunchWindow(hippo.CanvasWindow): +class LaunchWindow(gtk.Window): def __init__(self, activity_id, icon_path, icon_color): - gobject.GObject.__init__( - self, type_hint=gtk.gdk.WINDOW_TYPE_HINT_NORMAL) + gobject.GObject.__init__(self) + + self.props.type_hint = gtk.gdk.WINDOW_TYPE_HINT_NORMAL + + canvas = hippo.Canvas() + canvas.modify_bg(gtk.STATE_NORMAL, style.COLOR_WHITE.get_gdk_color()) + self.add(canvas) + canvas.show() self._activity_id = activity_id self._box = LaunchBox(activity_id, icon_path, icon_color) - self.set_root(self._box) + canvas.set_root(self._box) self.connect('realize', self.__realize_cb) @@ -61,8 +67,7 @@ class LaunchWindow(hippo.CanvasWindow): class LaunchBox(hippo.CanvasBox): def __init__(self, activity_id, icon_path, icon_color): - gobject.GObject.__init__(self, orientation=hippo.ORIENTATION_VERTICAL, - background_color=style.COLOR_WHITE.get_int()) + gobject.GObject.__init__(self, orientation=hippo.ORIENTATION_VERTICAL) self._activity_id = activity_id self._activity_icon = CanvasPulsingIcon( diff --git a/src/jarabe/view/palettes.py b/src/jarabe/view/palettes.py index b222fc7..170f42f 100644 --- a/src/jarabe/view/palettes.py +++ b/src/jarabe/view/palettes.py @@ -17,10 +17,9 @@ import os import statvfs from gettext import gettext as _ -import gconf import logging -import gobject +import gconf import gtk from sugar import env @@ -32,7 +31,6 @@ from sugar.graphics.xocolor import XoColor from sugar.activity import activityfactory from sugar.activity.activityhandle import ActivityHandle -from jarabe.model import bundleregistry from jarabe.model import shell from jarabe.view import launcher from jarabe.view.viewsource import setup_view_source @@ -107,12 +105,9 @@ class CurrentActivityPalette(BasePalette): class ActivityPalette(Palette): __gtype_name__ = 'SugarActivityPalette' - __gsignals__ = { - 'erase-activated' : (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([])) - } - def __init__(self, activity_info): + self._activity_info = activity_info + client = gconf.client_get_default() color = XoColor(client.get_string("/desktop/sugar/user/color")) activity_icon = Icon(file=activity_info.get_icon(), @@ -122,14 +117,6 @@ class ActivityPalette(Palette): Palette.__init__(self, primary_text=activity_info.get_name(), icon=activity_icon) - registry = bundleregistry.get_registry() - - self._bundle = activity_info - self._bundle_id = activity_info.get_bundle_id() - self._version = activity_info.get_activity_version() - self._favorite = registry.is_bundle_favorite(self._bundle_id, - self._version) - xo_color = XoColor('%s,%s' % (style.COLOR_WHITE.get_svg(), style.COLOR_TRANSPARENT.get_svg())) menu_item = MenuItem(text_label=_('Start'), @@ -141,46 +128,6 @@ class ActivityPalette(Palette): # TODO: start-with - self._favorite_item = MenuItem('') - self._favorite_icon = Icon(icon_name='emblem-favorite', - icon_size=gtk.ICON_SIZE_MENU) - self._favorite_item.set_image(self._favorite_icon) - self._favorite_item.connect('activate', - self.__change_favorite_activate_cb) - self.menu.append(self._favorite_item) - self._favorite_item.show() - - menu_item = MenuItem(_('Erase'), 'list-remove') - menu_item.connect('activate', self.__erase_activate_cb) - self.menu.append(menu_item) - menu_item.show() - - if not os.access(self._bundle.get_path(), os.W_OK): - menu_item.props.sensitive = False - - registry = bundleregistry.get_registry() - self._activity_changed_sid = registry.connect('bundle_changed', - self.__activity_changed_cb) - self._update_favorite_item() - - self.connect('destroy', self.__destroy_cb) - - def __destroy_cb(self, palette): - self.disconnect(self._activity_changed_sid) - - def _update_favorite_item(self): - label = self._favorite_item.child - if self._favorite: - label.set_text(_('Remove favorite')) - xo_color = XoColor('%s,%s' % (style.COLOR_WHITE.get_svg(), - style.COLOR_TRANSPARENT.get_svg())) - else: - label.set_text(_('Make favorite')) - client = gconf.client_get_default() - xo_color = XoColor(client.get_string("/desktop/sugar/user/color")) - - self._favorite_icon.props.xo_color = xo_color - def __start_activate_cb(self, menu_item): self.popdown(immediate=True) @@ -189,28 +136,11 @@ class ActivityPalette(Palette): activity_id = activityfactory.create_activity_id() launcher.add_launcher(activity_id, - self._bundle.get_icon(), + self._activity_info.get_icon(), xo_color) handle = ActivityHandle(activity_id) - activityfactory.create(self._bundle, handle) - - def __change_favorite_activate_cb(self, menu_item): - registry = bundleregistry.get_registry() - registry.set_bundle_favorite(self._bundle_id, - self._version, - not self._favorite) - - def __activity_changed_cb(self, activity_registry, activity_info): - if activity_info.get_bundle_id() == self._bundle_id and \ - activity_info.get_activity_version() == self._version: - registry = bundleregistry.get_registry() - self._favorite = registry.is_bundle_favorite(self._bundle_id, - self._version) - self._update_favorite_item() - - def __erase_activate_cb(self, menu_item): - self.emit('erase-activated') + activityfactory.create(self._activity_info, handle) class JournalPalette(BasePalette): def __init__(self, home_activity): -- cgit v0.9.1