Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/shell/extensions
diff options
context:
space:
mode:
Diffstat (limited to 'shell/extensions')
-rw-r--r--shell/extensions/Makefile.am1
-rw-r--r--shell/extensions/cpsection/Makefile.am5
-rw-r--r--shell/extensions/cpsection/__init__.py0
-rw-r--r--shell/extensions/cpsection/aboutcomputer/Makefile.am6
-rw-r--r--shell/extensions/cpsection/aboutcomputer/__init__.py22
-rw-r--r--shell/extensions/cpsection/aboutcomputer/model.py138
-rw-r--r--shell/extensions/cpsection/aboutcomputer/view.py217
-rw-r--r--shell/extensions/cpsection/aboutme/Makefile.am6
-rw-r--r--shell/extensions/cpsection/aboutme/__init__.py28
-rw-r--r--shell/extensions/cpsection/aboutme/model.py115
-rw-r--r--shell/extensions/cpsection/aboutme/view.py340
-rw-r--r--shell/extensions/cpsection/datetime/Makefile.am6
-rw-r--r--shell/extensions/cpsection/datetime/__init__.py21
-rw-r--r--shell/extensions/cpsection/datetime/model.py92
-rw-r--r--shell/extensions/cpsection/datetime/view.py138
-rw-r--r--shell/extensions/cpsection/frame/Makefile.am6
-rw-r--r--shell/extensions/cpsection/frame/__init__.py21
-rw-r--r--shell/extensions/cpsection/frame/model.py63
-rw-r--r--shell/extensions/cpsection/frame/view.py232
-rw-r--r--shell/extensions/cpsection/keyboard/Makefile.am6
-rw-r--r--shell/extensions/cpsection/keyboard/__init__.py22
-rw-r--r--shell/extensions/cpsection/keyboard/model.py169
-rw-r--r--shell/extensions/cpsection/keyboard/view.py426
-rw-r--r--shell/extensions/cpsection/language/Makefile.am6
-rw-r--r--shell/extensions/cpsection/language/__init__.py22
-rw-r--r--shell/extensions/cpsection/language/model.py151
-rw-r--r--shell/extensions/cpsection/language/view.py282
-rw-r--r--shell/extensions/cpsection/modemconfiguration/Makefile.am6
-rw-r--r--shell/extensions/cpsection/modemconfiguration/__init__.py22
-rwxr-xr-xshell/extensions/cpsection/modemconfiguration/model.py70
-rw-r--r--shell/extensions/cpsection/modemconfiguration/view.py250
-rw-r--r--shell/extensions/cpsection/network/Makefile.am6
-rw-r--r--shell/extensions/cpsection/network/__init__.py25
-rw-r--r--shell/extensions/cpsection/network/model.py141
-rw-r--r--shell/extensions/cpsection/network/view.py250
-rw-r--r--shell/extensions/cpsection/power/Makefile.am6
-rw-r--r--shell/extensions/cpsection/power/__init__.py23
-rw-r--r--shell/extensions/cpsection/power/model.py116
-rw-r--r--shell/extensions/cpsection/power/view.py177
-rw-r--r--shell/extensions/cpsection/updater/Makefile.am8
-rw-r--r--shell/extensions/cpsection/updater/__init__.py22
-rw-r--r--shell/extensions/cpsection/updater/backends/Makefile.am5
-rw-r--r--shell/extensions/cpsection/updater/backends/__init__.py16
-rw-r--r--shell/extensions/cpsection/updater/backends/aslo.py161
-rwxr-xr-xshell/extensions/cpsection/updater/model.py346
-rw-r--r--shell/extensions/cpsection/updater/view.py391
-rw-r--r--shell/extensions/deviceicon/Makefile.am9
-rw-r--r--shell/extensions/deviceicon/__init__.py0
-rw-r--r--shell/extensions/deviceicon/battery.py251
-rw-r--r--shell/extensions/deviceicon/network.py1006
-rw-r--r--shell/extensions/deviceicon/speaker.py216
-rw-r--r--shell/extensions/deviceicon/touchpad.py132
-rw-r--r--shell/extensions/deviceicon/volume.py121
-rw-r--r--shell/extensions/globalkey/Makefile.am6
-rw-r--r--shell/extensions/globalkey/__init__.py0
-rw-r--r--shell/extensions/globalkey/screenshot.py100
-rw-r--r--shell/extensions/globalkey/viewsource.py27
57 files changed, 6450 insertions, 0 deletions
diff --git a/shell/extensions/Makefile.am b/shell/extensions/Makefile.am
new file mode 100644
index 0000000..d4ab534
--- /dev/null
+++ b/shell/extensions/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = cpsection deviceicon globalkey
diff --git a/shell/extensions/cpsection/Makefile.am b/shell/extensions/cpsection/Makefile.am
new file mode 100644
index 0000000..a92b5dd
--- /dev/null
+++ b/shell/extensions/cpsection/Makefile.am
@@ -0,0 +1,5 @@
+SUBDIRS = aboutme aboutcomputer datetime frame keyboard language \
+ modemconfiguration network power updater
+
+sugardir = $(pkgdatadir)/extensions/cpsection
+sugar_PYTHON = __init__.py
diff --git a/shell/extensions/cpsection/__init__.py b/shell/extensions/cpsection/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/shell/extensions/cpsection/__init__.py
diff --git a/shell/extensions/cpsection/aboutcomputer/Makefile.am b/shell/extensions/cpsection/aboutcomputer/Makefile.am
new file mode 100644
index 0000000..a3bdec8
--- /dev/null
+++ b/shell/extensions/cpsection/aboutcomputer/Makefile.am
@@ -0,0 +1,6 @@
+sugardir = $(pkgdatadir)/extensions/cpsection/aboutcomputer
+
+sugar_PYTHON = \
+ __init__.py \
+ model.py \
+ view.py
diff --git a/shell/extensions/cpsection/aboutcomputer/__init__.py b/shell/extensions/cpsection/aboutcomputer/__init__.py
new file mode 100644
index 0000000..ceb515a
--- /dev/null
+++ b/shell/extensions/cpsection/aboutcomputer/__init__.py
@@ -0,0 +1,22 @@
+# Copyright (C) 2008, OLPC
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from gettext import gettext as _
+
+CLASS = 'AboutComputer'
+ICON = 'module-about_my_computer'
+TITLE = _('About my Computer')
+
diff --git a/shell/extensions/cpsection/aboutcomputer/model.py b/shell/extensions/cpsection/aboutcomputer/model.py
new file mode 100644
index 0000000..898d79c
--- /dev/null
+++ b/shell/extensions/cpsection/aboutcomputer/model.py
@@ -0,0 +1,138 @@
+# Copyright (C) 2008 One Laptop Per Child
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+import os
+import logging
+import re
+import subprocess
+from gettext import gettext as _
+import errno
+
+from jarabe import config
+
+_logger = logging.getLogger('ControlPanel - AboutComputer')
+_not_available = _('Not available')
+
+def get_aboutcomputer():
+ msg = 'Serial Number: %s \nBuild Number: %s \nFirmware Number: %s \n' \
+ % (get_serial_number(), get_build_number(), get_firmware_number())
+ return msg
+
+def print_aboutcomputer():
+ print get_aboutcomputer()
+
+def get_serial_number():
+ serial_no = _read_file('/ofw/serial-number')
+ if serial_no is None:
+ serial_no = _not_available
+ return serial_no
+
+def print_serial_number():
+ serial_no = get_serial_number()
+ if serial_no is None:
+ serial_no = _not_available
+ print serial_no
+
+def get_build_number():
+ build_no = _read_file('/boot/olpc_build')
+
+ if build_no is None:
+ build_no = _read_file('/etc/redhat-release')
+
+ if build_no is None:
+ try:
+ popen = subprocess.Popen(['lsb_release', '-ds'],
+ stdout=subprocess.PIPE)
+ except OSError, e:
+ if e.errno != errno.ENOENT:
+ raise
+ else:
+ build_no, stderr_ = popen.communicate()
+
+ if build_no is None or not build_no:
+ build_no = _not_available
+
+ return build_no
+
+def print_build_number():
+ print get_build_number()
+
+def get_firmware_number():
+ firmware_no = _read_file('/ofw/openprom/model')
+ if firmware_no is None:
+ firmware_no = _not_available
+ else:
+ firmware_no = re.split(" +", firmware_no)
+ if len(firmware_no) == 3:
+ firmware_no = firmware_no[1]
+ return firmware_no
+
+def print_firmware_number():
+ print get_firmware_number()
+
+def get_wireless_firmware():
+ try:
+ info = subprocess.Popen(["/usr/sbin/ethtool", "-i", "eth0"],
+ stdout=subprocess.PIPE).stdout.readlines()
+ except OSError:
+ return _not_available
+ try:
+ wireless_firmware = [line for line in info
+ if line.startswith('firmware')][0].split()[1]
+ except IndexError:
+ wireless_firmware = _not_available
+ return wireless_firmware
+
+def print_wireless_firmware():
+ print get_wireless_firmware()
+
+def _read_file(path):
+ if os.access(path, os.R_OK) == 0:
+ return None
+
+ fd = open(path, 'r')
+ value = fd.read()
+ fd.close()
+ if value:
+ value = value.strip('\n')
+ return value
+ else:
+ _logger.debug('No information in file or directory: %s', path)
+ return None
+
+def get_license():
+ license_file = os.path.join(config.data_path, 'GPLv2')
+ lang = os.environ['LANG']
+ if lang.endswith("UTF-8"):
+ lang = lang[:-6]
+
+ try_file = license_file + "." + lang
+ if os.path.isfile(try_file):
+ license_file = try_file
+ else:
+ try_file = license_file + "." + lang.split("_")[0]
+ if os.path.isfile(try_file):
+ license_file = try_file
+
+ try:
+ fd = open(license_file)
+ # remove 0x0c page breaks which can't be rendered in text views
+ license_text = fd.read().replace('\x0c', '')
+ fd.close()
+ except IOError:
+ license_text = _not_available
+ return license_text
diff --git a/shell/extensions/cpsection/aboutcomputer/view.py b/shell/extensions/cpsection/aboutcomputer/view.py
new file mode 100644
index 0000000..b6ff43f
--- /dev/null
+++ b/shell/extensions/cpsection/aboutcomputer/view.py
@@ -0,0 +1,217 @@
+# coding=utf-8
+# Copyright (C) 2008, OLPC
+# Copyright (C) 2009 Simon Schampijer
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+from gettext import gettext as _
+
+import gtk
+
+from sugar.graphics import style
+
+from jarabe import config
+from jarabe.controlpanel.sectionview import SectionView
+
+class AboutComputer(SectionView):
+ def __init__(self, model, alerts=None):
+ SectionView.__init__(self)
+
+ self._model = model
+
+ self.set_border_width(style.DEFAULT_SPACING * 2)
+ self.set_spacing(style.DEFAULT_SPACING)
+
+ self._group = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
+
+ scrollwindow = gtk.ScrolledWindow()
+ scrollwindow.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+ self.pack_start(scrollwindow, expand=True)
+ scrollwindow.show()
+
+ self._vbox = gtk.VBox()
+ scrollwindow.add_with_viewport(self._vbox)
+ self._vbox.show()
+
+ if os.path.exists('/ofw'):
+ self._setup_identity()
+
+ self._setup_software()
+ self._setup_copyright()
+
+ def _setup_identity(self):
+ separator_identity = gtk.HSeparator()
+ self._vbox.pack_start(separator_identity, expand=False)
+ separator_identity.show()
+
+ label_identity = gtk.Label(_('Identity'))
+ label_identity.set_alignment(0, 0)
+ self._vbox.pack_start(label_identity, expand=False)
+ label_identity.show()
+ vbox_identity = gtk.VBox()
+ vbox_identity.set_border_width(style.DEFAULT_SPACING * 2)
+ vbox_identity.set_spacing(style.DEFAULT_SPACING)
+
+ box_identity = gtk.HBox(spacing=style.DEFAULT_SPACING)
+ label_serial = gtk.Label(_('Serial Number:'))
+ label_serial.set_alignment(1, 0)
+ label_serial.modify_fg(gtk.STATE_NORMAL,
+ style.COLOR_SELECTION_GREY.get_gdk_color())
+ box_identity.pack_start(label_serial, expand=False)
+ self._group.add_widget(label_serial)
+ label_serial.show()
+ label_serial_no = gtk.Label(self._model.get_serial_number())
+ label_serial_no.set_alignment(0, 0)
+ box_identity.pack_start(label_serial_no, expand=False)
+ label_serial_no.show()
+ vbox_identity.pack_start(box_identity, expand=False)
+ box_identity.show()
+
+ self._vbox.pack_start(vbox_identity, expand=False)
+ vbox_identity.show()
+
+ def _setup_software(self):
+ separator_software = gtk.HSeparator()
+ self._vbox.pack_start(separator_software, expand=False)
+ separator_software.show()
+
+ label_software = gtk.Label(_('Software'))
+ label_software.set_alignment(0, 0)
+ self._vbox.pack_start(label_software, expand=False)
+ label_software.show()
+ box_software = gtk.VBox()
+ box_software.set_border_width(style.DEFAULT_SPACING * 2)
+ box_software.set_spacing(style.DEFAULT_SPACING)
+
+ box_build = gtk.HBox(spacing=style.DEFAULT_SPACING)
+ label_build = gtk.Label(_('Build:'))
+ label_build.set_alignment(1, 0)
+ label_build.modify_fg(gtk.STATE_NORMAL,
+ style.COLOR_SELECTION_GREY.get_gdk_color())
+ box_build.pack_start(label_build, expand=False)
+ self._group.add_widget(label_build)
+ label_build.show()
+ label_build_no = gtk.Label(self._model.get_build_number())
+ label_build_no.set_alignment(0, 0)
+ box_build.pack_start(label_build_no, expand=False)
+ label_build_no.show()
+ box_software.pack_start(box_build, expand=False)
+ box_build.show()
+
+ box_sugar = gtk.HBox(spacing=style.DEFAULT_SPACING)
+ label_sugar = gtk.Label(_('Sugar:'))
+ label_sugar.set_alignment(1, 0)
+ label_sugar.modify_fg(gtk.STATE_NORMAL,
+ style.COLOR_SELECTION_GREY.get_gdk_color())
+ box_sugar.pack_start(label_sugar, expand=False)
+ self._group.add_widget(label_sugar)
+ label_sugar.show()
+ label_sugar_ver = gtk.Label(config.version)
+ label_sugar_ver.set_alignment(0, 0)
+ box_sugar.pack_start(label_sugar_ver, expand=False)
+ label_sugar_ver.show()
+ box_software.pack_start(box_sugar, expand=False)
+ box_sugar.show()
+
+ if os.path.exists('/ofw'):
+ box_firmware = gtk.HBox(spacing=style.DEFAULT_SPACING)
+ label_firmware = gtk.Label(_('Firmware:'))
+ label_firmware.set_alignment(1, 0)
+ label_firmware.modify_fg(gtk.STATE_NORMAL,
+ style.COLOR_SELECTION_GREY.get_gdk_color())
+ box_firmware.pack_start(label_firmware, expand=False)
+ self._group.add_widget(label_firmware)
+ label_firmware.show()
+ label_firmware_no = gtk.Label(self._model.get_firmware_number())
+ label_firmware_no.set_alignment(0, 0)
+ box_firmware.pack_start(label_firmware_no, expand=False)
+ label_firmware_no.show()
+ box_software.pack_start(box_firmware, expand=False)
+ box_firmware.show()
+
+ box_wireless_fw = gtk.HBox(spacing=style.DEFAULT_SPACING)
+ label_wireless_fw = gtk.Label(_('Wireless Firmware:'))
+ label_wireless_fw.set_alignment(1, 0)
+ label_wireless_fw.modify_fg(gtk.STATE_NORMAL,
+ style.COLOR_SELECTION_GREY.get_gdk_color())
+ box_wireless_fw.pack_start(label_wireless_fw, expand=False)
+ self._group.add_widget(label_wireless_fw)
+ label_wireless_fw.show()
+ wireless_fw_no = self._model.get_wireless_firmware()
+ label_wireless_fw_no = gtk.Label(wireless_fw_no)
+ label_wireless_fw_no.set_alignment(0, 0)
+ box_wireless_fw.pack_start(label_wireless_fw_no, expand=False)
+ label_wireless_fw_no.show()
+ box_software.pack_start(box_wireless_fw, expand=False)
+ box_wireless_fw.show()
+
+ self._vbox.pack_start(box_software, expand=False)
+ box_software.show()
+
+ def _setup_copyright(self):
+ separator_copyright = gtk.HSeparator()
+ self._vbox.pack_start(separator_copyright, expand=False)
+ separator_copyright.show()
+
+ label_copyright = gtk.Label(_('Copyright and License'))
+ label_copyright.set_alignment(0, 0)
+ self._vbox.pack_start(label_copyright, expand=False)
+ label_copyright.show()
+ vbox_copyright = gtk.VBox()
+ vbox_copyright.set_border_width(style.DEFAULT_SPACING * 2)
+ vbox_copyright.set_spacing(style.DEFAULT_SPACING)
+
+ label_copyright = gtk.Label("© 2006-2010 One Laptop per Child "
+ "Association Inc, Sugar Labs Inc, "
+ "Red Hat Inc, Collabora Ltd "
+ "and Contributors.")
+ label_copyright.set_alignment(0, 0)
+ label_copyright.set_size_request(gtk.gdk.screen_width() / 2, -1)
+ label_copyright.set_line_wrap(True)
+ label_copyright.show()
+ vbox_copyright.pack_start(label_copyright, expand=False)
+
+ label_info = gtk.Label(_("Sugar is the graphical user interface that "
+ "you are looking at. Sugar is free software, "
+ "covered by the GNU General Public License, "
+ "and you are welcome to change it and/or "
+ "distribute copies of it under certain "
+ "conditions described therein."))
+ label_info.set_alignment(0, 0)
+ label_info.set_line_wrap(True)
+ label_info.set_size_request(gtk.gdk.screen_width() / 2, -1)
+ label_info.show()
+ vbox_copyright.pack_start(label_info, expand=False)
+
+ expander = gtk.Expander(_("Full license:"))
+ expander.connect("notify::expanded", self.license_expander_cb)
+ expander.show()
+ vbox_copyright.pack_start(expander, expand=True)
+
+ self._vbox.pack_start(vbox_copyright, expand=True)
+ vbox_copyright.show()
+
+ def license_expander_cb(self, expander, param_spec):
+ # load/destroy the license viewer on-demand, to avoid storing the
+ # GPL in memory at all times
+ if expander.get_expanded():
+ view_license = gtk.TextView()
+ view_license.set_editable(False)
+ view_license.get_buffer().set_text(self._model.get_license())
+ view_license.show()
+ expander.add(view_license)
+ else:
+ expander.get_child().destroy()
diff --git a/shell/extensions/cpsection/aboutme/Makefile.am b/shell/extensions/cpsection/aboutme/Makefile.am
new file mode 100644
index 0000000..9ca91d2
--- /dev/null
+++ b/shell/extensions/cpsection/aboutme/Makefile.am
@@ -0,0 +1,6 @@
+sugardir = $(pkgdatadir)/extensions/cpsection/aboutme
+
+sugar_PYTHON = \
+ __init__.py \
+ model.py \
+ view.py
diff --git a/shell/extensions/cpsection/aboutme/__init__.py b/shell/extensions/cpsection/aboutme/__init__.py
new file mode 100644
index 0000000..98843e1
--- /dev/null
+++ b/shell/extensions/cpsection/aboutme/__init__.py
@@ -0,0 +1,28 @@
+# Copyright (C) 2008, OLPC
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from gettext import gettext as _
+import gconf
+
+from sugar.graphics.xocolor import XoColor
+
+CLASS = 'AboutMe'
+ICON = 'module-about_me'
+TITLE = _('About Me')
+client = gconf.client_get_default()
+COLOR = XoColor(client.get_string('/desktop/sugar/user/color'))
+
+
diff --git a/shell/extensions/cpsection/aboutme/model.py b/shell/extensions/cpsection/aboutme/model.py
new file mode 100644
index 0000000..8500799
--- /dev/null
+++ b/shell/extensions/cpsection/aboutme/model.py
@@ -0,0 +1,115 @@
+# Copyright (C) 2008 One Laptop Per Child
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+from gettext import gettext as _
+import gconf
+
+_COLORS = {'red': {'dark':'#b20008', 'medium':'#e6000a', 'light':'#ffadce'},
+ 'orange': {'dark':'#9a5200', 'medium':'#c97e00', 'light':'#ffc169'},
+ 'yellow': {'dark':'#807500', 'medium':'#be9e00', 'light':'#fffa00'},
+ 'green': {'dark':'#008009', 'medium':'#00b20d', 'light':'#8bff7a'},
+ 'blue': {'dark':'#00588c', 'medium':'#005fe4', 'light':'#bccdff'},
+ 'purple': {'dark':'#5e008c', 'medium':'#7f00bf', 'light':'#d1a3ff'}
+ }
+
+_MODIFIERS = ('dark', 'medium', 'light')
+
+def get_nick():
+ client = gconf.client_get_default()
+ return client.get_string("/desktop/sugar/user/nick")
+
+def print_nick():
+ print get_nick()
+
+def set_nick(nick):
+ """Set the nickname.
+ nick : e.g. 'walter'
+ """
+ if not nick:
+ raise ValueError(_("You must enter a name."))
+ if not isinstance(nick, unicode):
+ nick = unicode(nick, 'utf-8')
+ client = gconf.client_get_default()
+ client.set_string("/desktop/sugar/user/nick", nick)
+ return 1
+
+def get_color():
+ client = gconf.client_get_default()
+ return client.get_string("/desktop/sugar/user/color")
+
+def print_color():
+ color_string = get_color()
+ tmp = color_string.split(',')
+
+ stroke_tuple = None
+ fill_tuple = None
+ for color in _COLORS:
+ for hue in _COLORS[color]:
+ if _COLORS[color][hue] == tmp[0]:
+ stroke_tuple = (color, hue)
+ if _COLORS[color][hue] == tmp[1]:
+ fill_tuple = (color, hue)
+
+ if stroke_tuple is not None:
+ print _('stroke: color=%s hue=%s') % (stroke_tuple[0],
+ stroke_tuple[1])
+ else:
+ print _('stroke: %s') % (tmp[0])
+ if fill_tuple is not None:
+ print _('fill: color=%s hue=%s') % (fill_tuple[0], fill_tuple[1])
+ else:
+ print _('fill: %s') % (tmp[1])
+
+def set_color(stroke, fill, stroke_modifier='medium', fill_modifier='medium'):
+ """Set the system color by setting a fill and stroke color.
+ fill : [red, orange, yellow, blue, green, purple]
+ stroke : [red, orange, yellow, blue, green, purple]
+ hue stroke : [dark, medium, light] (optional)
+ hue fill : [dark, medium, light] (optional)
+ """
+
+ if stroke_modifier not in _MODIFIERS or fill_modifier not in _MODIFIERS:
+ print (_("Error in specified color modifiers."))
+ return
+ if stroke not in _COLORS or fill not in _COLORS:
+ print (_("Error in specified colors."))
+ return
+
+ if stroke_modifier == fill_modifier:
+ if fill_modifier == 'medium':
+ fill_modifier = 'light'
+ else:
+ fill_modifier = 'medium'
+
+ color = _COLORS[stroke][stroke_modifier] + ',' \
+ + _COLORS[fill][fill_modifier]
+
+ client = gconf.client_get_default()
+ client.set_string("/desktop/sugar/user/color", color)
+ return 1
+
+def get_color_xo():
+ client = gconf.client_get_default()
+ return client.get_string("/desktop/sugar/user/color")
+
+def set_color_xo(color):
+ """Set a color with an XoColor
+ This method is used by the graphical user interface
+ """
+ client = gconf.client_get_default()
+ client.set_string("/desktop/sugar/user/color", color)
+ return 1
diff --git a/shell/extensions/cpsection/aboutme/view.py b/shell/extensions/cpsection/aboutme/view.py
new file mode 100644
index 0000000..95314a1
--- /dev/null
+++ b/shell/extensions/cpsection/aboutme/view.py
@@ -0,0 +1,340 @@
+# Copyright (C) 2008, OLPC
+# Copyright (C) 2010, Sugar Labs
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# 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 gtk
+import gobject
+from gettext import gettext as _
+
+from sugar.graphics.icon import Icon
+from sugar.graphics import style
+from sugar.graphics.xocolor import XoColor, colors
+
+from jarabe.controlpanel.sectionview import SectionView
+from jarabe.controlpanel.inlinealert import InlineAlert
+
+_STROKE_COLOR = 0
+_FILL_COLOR = 1
+
+
+def _get_next_stroke_color(color):
+ """ Return the next color pair in the list that shares the same fill
+ as color. """
+ current_index = _get_current_index(color)
+ if current_index == -1:
+ return "%s,%s" % (color.stroke, color.fill)
+ next_index = _next_index(current_index)
+ while(colors[next_index][_FILL_COLOR] != \
+ colors[current_index][_FILL_COLOR]):
+ next_index = _next_index(next_index)
+ return "%s,%s" % (colors[next_index][_STROKE_COLOR],
+ colors[next_index][_FILL_COLOR])
+
+
+def _get_previous_stroke_color(color):
+ """ Return the previous color pair in the list that shares the same fill
+ as color. """
+ current_index = _get_current_index(color)
+ if current_index == -1:
+ return "%s,%s" % (color.stroke, color.fill)
+ previous_index = _previous_index(current_index)
+ while (colors[previous_index][_FILL_COLOR] != \
+ colors[current_index][_FILL_COLOR]):
+ previous_index = _previous_index(previous_index)
+ return "%s,%s" % (colors[previous_index][_STROKE_COLOR],
+ colors[previous_index][_FILL_COLOR])
+
+
+def _get_next_fill_color(color):
+ """ Return the next color pair in the list that shares the same stroke
+ as color. """
+ current_index = _get_current_index(color)
+ if current_index == -1:
+ return "%s,%s" % (color.stroke, color.fill)
+ next_index = _next_index(current_index)
+ while (colors[next_index][_STROKE_COLOR] != \
+ colors[current_index][_STROKE_COLOR]):
+ next_index = _next_index(next_index)
+ return "%s,%s" % (colors[next_index][_STROKE_COLOR],
+ colors[next_index][_FILL_COLOR])
+
+
+def _get_previous_fill_color(color):
+ """ Return the previous color pair in the list that shares the same stroke
+ as color. """
+ current_index = _get_current_index(color)
+ if current_index == -1:
+ return "%s,%s" % (color.stroke, color.fill)
+ previous_index = _previous_index(current_index)
+ while (colors[previous_index][_STROKE_COLOR] != \
+ colors[current_index][_STROKE_COLOR]):
+ previous_index = _previous_index(previous_index)
+ return "%s,%s" % (colors[previous_index][_STROKE_COLOR],
+ colors[previous_index][_FILL_COLOR])
+
+
+def _next_index(current_index):
+ next_index = current_index + 1
+ if next_index == len(colors):
+ next_index = 0
+ return next_index
+
+
+def _previous_index(current_index):
+ previous_index = current_index - 1
+ if previous_index < 0:
+ previous_index = len(colors)-1
+ return previous_index
+
+
+def _get_current_index(color):
+ return colors.index([color.stroke, color.fill])
+
+
+_PREVIOUS_FILL_COLOR = 0
+_NEXT_FILL_COLOR = 1
+_CURRENT_COLOR = 2
+_NEXT_STROKE_COLOR = 3
+_PREVIOUS_STROKE_COLOR = 4
+
+
+class EventIcon(gtk.EventBox):
+ __gtype_name__ = "SugarEventIcon"
+
+ def __init__(self, **kwargs):
+ gtk.EventBox.__init__(self)
+
+ self.icon = Icon(pixel_size=style.XLARGE_ICON_SIZE, **kwargs)
+
+ self.set_visible_window(False)
+ self.set_app_paintable(True)
+ self.set_events(gtk.gdk.BUTTON_PRESS_MASK)
+
+ self.add(self.icon)
+ self.icon.show()
+
+
+class ColorPicker(EventIcon):
+ __gsignals__ = {
+ 'color-changed': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([object]))
+ }
+
+ def __init__(self, picker):
+ EventIcon.__init__(self)
+
+ self.icon.props.icon_name = 'computer-xo'
+ self._picker = picker
+ self._color = None
+
+ self.icon.props.pixel_size = style.XLARGE_ICON_SIZE
+
+ self.connect('button_press_event', self.__pressed_cb, picker)
+
+ def update(self, color):
+ if self._picker == _PREVIOUS_FILL_COLOR:
+ self._color = XoColor(_get_previous_fill_color(color))
+ elif self._picker == _PREVIOUS_STROKE_COLOR:
+ self._color = XoColor(_get_previous_stroke_color(color))
+ elif self._picker == _NEXT_FILL_COLOR:
+ self._color = XoColor(_get_next_fill_color(color))
+ elif self._picker == _NEXT_STROKE_COLOR:
+ self._color = XoColor(_get_next_stroke_color(color))
+ else:
+ self._color = color
+ self.icon.props.xo_color = self._color
+
+ def __pressed_cb(self, button, event, picker):
+ if picker != _CURRENT_COLOR:
+ self.emit('color-changed', self._color)
+
+
+class AboutMe(SectionView):
+
+ def __init__(self, model, alerts):
+ SectionView.__init__(self)
+
+ self._model = model
+ self.restart_alerts = alerts
+ self._nick_sid = 0
+ self._color_valid = True
+ self._nick_valid = True
+
+ self.set_border_width(style.DEFAULT_SPACING * 2)
+ self.set_spacing(style.DEFAULT_SPACING)
+ self._group = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
+
+ self._color_label = gtk.HBox(spacing=style.DEFAULT_SPACING)
+ self._color_box = gtk.HBox(spacing=style.DEFAULT_SPACING)
+ self._color_alert_box = gtk.HBox(spacing=style.DEFAULT_SPACING)
+ self._color_alert = None
+
+ self._pickers = {
+ _PREVIOUS_FILL_COLOR: ColorPicker(_PREVIOUS_FILL_COLOR),
+ _NEXT_FILL_COLOR: ColorPicker(_NEXT_FILL_COLOR),
+ _CURRENT_COLOR: ColorPicker(_CURRENT_COLOR),
+ _NEXT_STROKE_COLOR: ColorPicker(_NEXT_STROKE_COLOR),
+ _PREVIOUS_STROKE_COLOR: ColorPicker(_PREVIOUS_STROKE_COLOR)
+ }
+
+ self._setup_color()
+ initial_color = XoColor(self._model.get_color_xo())
+ self._update_pickers(initial_color)
+
+ self._nick_box = gtk.HBox(spacing=style.DEFAULT_SPACING)
+ self._nick_alert_box = gtk.HBox(spacing=style.DEFAULT_SPACING)
+ self._nick_entry = None
+ self._nick_alert = None
+ self._setup_nick()
+ self.setup()
+
+ def _setup_nick(self):
+ self._nick_entry = gtk.Entry()
+ self._nick_entry.modify_bg(gtk.STATE_INSENSITIVE,
+ style.COLOR_WHITE.get_gdk_color())
+ self._nick_entry.modify_base(gtk.STATE_INSENSITIVE,
+ style.COLOR_WHITE.get_gdk_color())
+ self._nick_entry.set_width_chars(25)
+ self._nick_box.pack_start(self._nick_entry, expand=False)
+ self._nick_entry.show()
+
+ label_entry_error = gtk.Label()
+ self._group.add_widget(label_entry_error)
+ self._nick_alert_box.pack_start(label_entry_error, expand=False)
+ label_entry_error.show()
+
+ self._nick_alert = InlineAlert()
+ self._nick_alert_box.pack_start(self._nick_alert)
+ if 'nick' in self.restart_alerts:
+ self._nick_alert.props.msg = self.restart_msg
+ self._nick_alert.show()
+
+ self._center_in_panel = gtk.Alignment(0.5)
+ self._center_in_panel.add(self._nick_box)
+ self.pack_start(self._center_in_panel, False)
+ self.pack_start(self._nick_alert_box, False)
+ self._nick_box.show()
+ self._nick_alert_box.show()
+ self._center_in_panel.show()
+
+ def _setup_color(self):
+ label_color = gtk.Label(_('Click to change your color:'))
+ label_color.modify_fg(gtk.STATE_NORMAL,
+ style.COLOR_SELECTION_GREY.get_gdk_color())
+ self._group.add_widget(label_color)
+ self._color_label.pack_start(label_color, expand=False)
+ label_color.show()
+
+ for picker_index in sorted(self._pickers.keys()):
+ if picker_index == _CURRENT_COLOR:
+ left_separator = gtk.SeparatorToolItem()
+ left_separator.show()
+ self._color_box.pack_start(left_separator, expand=False)
+
+ picker = self._pickers[picker_index]
+ picker.show()
+ self._color_box.pack_start(picker, expand=False)
+
+ if picker_index == _CURRENT_COLOR:
+ right_separator = gtk.SeparatorToolItem()
+ right_separator.show()
+ self._color_box.pack_start(right_separator, expand=False)
+
+ label_color_error = gtk.Label()
+ self._group.add_widget(label_color_error)
+ self._color_alert_box.pack_start(label_color_error, expand=False)
+ label_color_error.show()
+
+ self._color_alert = InlineAlert()
+ self._color_alert_box.pack_start(self._color_alert)
+ if 'color' in self.restart_alerts:
+ self._color_alert.props.msg = self.restart_msg
+ self._color_alert.show()
+
+ self._center_in_panel = gtk.Alignment(0.5)
+ self._center_in_panel.add(self._color_box)
+ self.pack_start(self._color_label, False)
+ self.pack_start(self._center_in_panel, False)
+ self.pack_start(self._color_alert_box, False)
+ self._color_label.show()
+ self._color_box.show()
+ self._color_alert_box.show()
+ self._center_in_panel.show()
+
+ def setup(self):
+ self._nick_entry.set_text(self._model.get_nick())
+ self._color_valid = True
+ self._nick_valid = True
+ self.needs_restart = False
+
+ self._nick_entry.connect('changed', self.__nick_changed_cb)
+ for picker in self._pickers.values():
+ picker.connect('color-changed', self.__color_changed_cb)
+
+ def undo(self):
+ self._model.undo()
+ self._nick_alert.hide()
+ self._color_alert.hide()
+
+ def _update_pickers(self, color):
+ for picker in self._pickers.values():
+ picker.update(color)
+
+ def _validate(self):
+ if self._nick_valid and self._color_valid:
+ self.props.is_valid = True
+ else:
+ self.props.is_valid = False
+
+ def __nick_changed_cb(self, widget, data=None):
+ if self._nick_sid:
+ gobject.source_remove(self._nick_sid)
+ self._nick_sid = gobject.timeout_add(self._APPLY_TIMEOUT,
+ self.__nick_timeout_cb, widget)
+
+ def __nick_timeout_cb(self, widget):
+ self._nick_sid = 0
+
+ if widget.get_text() == self._model.get_nick():
+ return False
+ try:
+ self._model.set_nick(widget.get_text())
+ except ValueError, detail:
+ self._nick_alert.props.msg = detail
+ self._nick_valid = False
+ else:
+ self._nick_alert.props.msg = self.restart_msg
+ self._nick_valid = True
+ self.needs_restart = True
+ self.restart_alerts.append('nick')
+ self._validate()
+ self._nick_alert.show()
+ return False
+
+ def __color_changed_cb(self, colorpicker, color):
+ self._model.set_color_xo(color.to_string())
+ self.needs_restart = True
+ self._color_alert.props.msg = self.restart_msg
+ self._color_valid = True
+ self.restart_alerts.append('color')
+
+ self._validate()
+ self._color_alert.show()
+
+ self._update_pickers(color)
+
+ return False
diff --git a/shell/extensions/cpsection/datetime/Makefile.am b/shell/extensions/cpsection/datetime/Makefile.am
new file mode 100644
index 0000000..b5b518e
--- /dev/null
+++ b/shell/extensions/cpsection/datetime/Makefile.am
@@ -0,0 +1,6 @@
+sugardir = $(pkgdatadir)/extensions/cpsection/datetime
+
+sugar_PYTHON = \
+ __init__.py \
+ model.py \
+ view.py
diff --git a/shell/extensions/cpsection/datetime/__init__.py b/shell/extensions/cpsection/datetime/__init__.py
new file mode 100644
index 0000000..fc9be45
--- /dev/null
+++ b/shell/extensions/cpsection/datetime/__init__.py
@@ -0,0 +1,21 @@
+# Copyright (C) 2008, OLPC
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from gettext import gettext as _
+
+CLASS = 'TimeZone'
+ICON = 'module-date_and_time'
+TITLE = _('Date & Time')
diff --git a/shell/extensions/cpsection/datetime/model.py b/shell/extensions/cpsection/datetime/model.py
new file mode 100644
index 0000000..76064e4
--- /dev/null
+++ b/shell/extensions/cpsection/datetime/model.py
@@ -0,0 +1,92 @@
+# Copyright (C) 2007, 2008 One Laptop Per Child
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+#
+# The timezone config is based on the system-config-date
+# (http://fedoraproject.org/wiki/SystemConfig/date) tool.
+# Parts of the code were reused.
+#
+
+import os
+from gettext import gettext as _
+import gconf
+
+_zone_tab = '/usr/share/zoneinfo/zone.tab'
+
+def _initialize():
+ '''Initialize the docstring of the set function'''
+ if set_timezone.__doc__ is None:
+ # when running under 'python -OO', all __doc__ fields are None,
+ # so += would fail -- and this function would be unnecessary anyway.
+ return
+ timezones = read_all_timezones()
+ for timezone in timezones:
+ set_timezone.__doc__ += timezone + '\n'
+
+def read_all_timezones(fn=_zone_tab):
+ fd = open (fn, 'r')
+ lines = fd.readlines()
+ fd.close()
+ timezones = []
+ for line in lines:
+ if line.startswith('#'):
+ continue
+ line = line.split()
+ if len(line) > 1:
+ timezones.append(line[2])
+ timezones.sort()
+
+ for offset in xrange(-12, 13):
+ if offset < 0:
+ tz = 'GMT%d' % offset
+ elif offset > 0:
+ tz = 'GMT+%d' % offset
+ else:
+ tz = 'GMT'
+ timezones.append(tz)
+ for offset in xrange(-12, 13):
+ if offset < 0:
+ tz = 'UTC%d' % offset
+ elif offset > 0:
+ tz = 'UTC+%d' % offset
+ else:
+ tz = 'UTC'
+ timezones.append(tz)
+ return timezones
+
+def get_timezone():
+ client = gconf.client_get_default()
+ return client.get_string('/desktop/sugar/date/timezone')
+
+def print_timezone():
+ print get_timezone()
+
+def set_timezone(timezone):
+ """Set the system timezone
+ timezone : e.g. 'America/Los_Angeles'
+ """
+ timezones = read_all_timezones()
+ if timezone in timezones:
+ os.environ['TZ'] = timezone
+ client = gconf.client_get_default()
+ client.set_string('/desktop/sugar/date/timezone', timezone)
+ else:
+ raise ValueError(_("Error timezone does not exist."))
+ return 1
+
+# inilialize the docstrings for the timezone
+_initialize()
+
diff --git a/shell/extensions/cpsection/datetime/view.py b/shell/extensions/cpsection/datetime/view.py
new file mode 100644
index 0000000..58719b4
--- /dev/null
+++ b/shell/extensions/cpsection/datetime/view.py
@@ -0,0 +1,138 @@
+# Copyright (C) 2008, OLPC
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import gtk
+import gobject
+from gettext import gettext as _
+
+from sugar.graphics import style
+from sugar.graphics import iconentry
+
+from jarabe.controlpanel.sectionview import SectionView
+from jarabe.controlpanel.inlinealert import InlineAlert
+
+class TimeZone(SectionView):
+ def __init__(self, model, alerts):
+ SectionView.__init__(self)
+
+ self._model = model
+ self.restart_alerts = alerts
+ self._zone_sid = 0
+ self._cursor_change_handler = None
+
+ self.set_border_width(style.DEFAULT_SPACING * 2)
+ self.set_spacing(style.DEFAULT_SPACING)
+
+ self.connect("realize", self.__realize_cb)
+
+ self._entry = iconentry.IconEntry()
+ self._entry.set_icon_from_name(iconentry.ICON_ENTRY_PRIMARY,
+ 'system-search')
+ self._entry.add_clear_button()
+ self._entry.modify_bg(gtk.STATE_INSENSITIVE,
+ style.COLOR_WHITE.get_gdk_color())
+ self._entry.modify_base(gtk.STATE_INSENSITIVE,
+ style.COLOR_WHITE.get_gdk_color())
+ self.pack_start(self._entry, False)
+ self._entry.show()
+
+ self._scrolled_window = gtk.ScrolledWindow()
+ self._scrolled_window.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+ self._scrolled_window.set_shadow_type(gtk.SHADOW_IN)
+
+ self._store = gtk.ListStore(gobject.TYPE_STRING)
+ zones = model.read_all_timezones()
+ for zone in zones:
+ self._store.append([zone])
+
+ self._treeview = gtk.TreeView(self._store)
+ self._treeview.set_search_entry(self._entry)
+ self._treeview.set_search_equal_func(self._search)
+ self._treeview.set_search_column(0)
+ self._scrolled_window.add(self._treeview)
+ self._treeview.show()
+
+ self._timezone_column = gtk.TreeViewColumn(_('Timezone'))
+ self._cell = gtk.CellRendererText()
+ self._timezone_column.pack_start(self._cell, True)
+ self._timezone_column.add_attribute(self._cell, 'text', 0)
+ self._timezone_column.set_sort_column_id(0)
+ self._treeview.append_column(self._timezone_column)
+
+ self.pack_start(self._scrolled_window)
+ self._scrolled_window.show()
+
+ self._zone_alert_box = gtk.HBox(spacing=style.DEFAULT_SPACING)
+ self.pack_start(self._zone_alert_box, False)
+
+ self._zone_alert = InlineAlert()
+ self._zone_alert_box.pack_start(self._zone_alert)
+ if 'zone' in self.restart_alerts:
+ self._zone_alert.props.msg = self.restart_msg
+ self._zone_alert.show()
+ self._zone_alert_box.show()
+
+ self.setup()
+
+ def setup(self):
+ zone = self._model.get_timezone()
+ for row in self._store:
+ if zone == row[0]:
+ self._treeview.set_cursor(row.path, self._timezone_column,
+ False)
+ self._treeview.scroll_to_cell(row.path, self._timezone_column,
+ True, 0.5, 0.5)
+ break
+
+ self.needs_restart = False
+ self._cursor_change_handler = self._treeview.connect( \
+ "cursor-changed", self.__zone_changed_cd)
+
+ def undo(self):
+ self._treeview.disconnect(self._cursor_change_handler)
+ self._model.undo()
+ self._zone_alert.hide()
+
+ def __realize_cb(self, widget):
+ self._entry.grab_focus()
+
+ def _search(self, model, column, key, iterator, data=None):
+ value = model.get_value(iterator, column)
+ if key.lower() in value.lower():
+ return False
+ return True
+
+ def __zone_changed_cd(self, treeview, data=None):
+ list_, row = treeview.get_selection().get_selected()
+ if not row:
+ return False
+ if self._model.get_timezone() == self._store.get_value(row, 0):
+ return False
+
+ if self._zone_sid:
+ gobject.source_remove(self._zone_sid)
+ self._zone_sid = gobject.timeout_add(self._APPLY_TIMEOUT,
+ self.__zone_timeout_cb, row)
+ return True
+
+ def __zone_timeout_cb(self, row):
+ self._zone_sid = 0
+ self._model.set_timezone(self._store.get_value(row, 0))
+ self.restart_alerts.append('zone')
+ self.needs_restart = True
+ self._zone_alert.props.msg = self.restart_msg
+ self._zone_alert.show()
+ return False
diff --git a/shell/extensions/cpsection/frame/Makefile.am b/shell/extensions/cpsection/frame/Makefile.am
new file mode 100644
index 0000000..1e09c04
--- /dev/null
+++ b/shell/extensions/cpsection/frame/Makefile.am
@@ -0,0 +1,6 @@
+sugardir = $(pkgdatadir)/extensions/cpsection/frame
+
+sugar_PYTHON = \
+ __init__.py \
+ model.py \
+ view.py
diff --git a/shell/extensions/cpsection/frame/__init__.py b/shell/extensions/cpsection/frame/__init__.py
new file mode 100644
index 0000000..a93f9c7
--- /dev/null
+++ b/shell/extensions/cpsection/frame/__init__.py
@@ -0,0 +1,21 @@
+# Copyright (C) 2008, OLPC
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from gettext import gettext as _
+
+CLASS = 'Frame'
+ICON = 'module-frame'
+TITLE = _('Frame')
diff --git a/shell/extensions/cpsection/frame/model.py b/shell/extensions/cpsection/frame/model.py
new file mode 100644
index 0000000..9eea9ad
--- /dev/null
+++ b/shell/extensions/cpsection/frame/model.py
@@ -0,0 +1,63 @@
+# Copyright (C) 2008 One Laptop Per Child
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+from gettext import gettext as _
+import gconf
+
+def get_corner_delay():
+ client = gconf.client_get_default()
+ corner_delay = client.get_int('/desktop/sugar/frame/corner_delay')
+ return corner_delay
+
+def print_corner_delay():
+ print get_corner_delay()
+
+def set_corner_delay(delay):
+ """Set a delay for the activation of the frame using hot corners.
+ instantaneous: 0 (0 milliseconds)
+ delay: 100 (100 milliseconds)
+ never: 1000 (disable activation)
+ """
+ try:
+ int(delay)
+ except ValueError:
+ raise ValueError(_("Value must be an integer."))
+ client = gconf.client_get_default()
+ client.set_int('/desktop/sugar/frame/corner_delay', int(delay))
+ return 0
+
+def get_edge_delay():
+ client = gconf.client_get_default()
+ edge_delay = client.get_int('/desktop/sugar/frame/edge_delay')
+ return edge_delay
+
+def print_edge_delay():
+ print get_edge_delay()
+
+def set_edge_delay(delay):
+ """Set a delay for the activation of the frame using warm edges.
+ instantaneous: 0 (0 milliseconds)
+ delay: 100 (100 milliseconds)
+ never: 1000 (disable activation)
+ """
+ try:
+ int(delay)
+ except ValueError:
+ raise ValueError(_("Value must be an integer."))
+ client = gconf.client_get_default()
+ client.set_int('/desktop/sugar/frame/edge_delay', int(delay))
+ return 0
diff --git a/shell/extensions/cpsection/frame/view.py b/shell/extensions/cpsection/frame/view.py
new file mode 100644
index 0000000..cbe43bb
--- /dev/null
+++ b/shell/extensions/cpsection/frame/view.py
@@ -0,0 +1,232 @@
+# Copyright (C) 2008, OLPC
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import gtk
+import gobject
+from gettext import gettext as _
+
+from sugar.graphics import style
+
+from jarabe.controlpanel.sectionview import SectionView
+from jarabe.controlpanel.inlinealert import InlineAlert
+
+_never = _('never')
+_instantaneous = _('instantaneous')
+_seconds_label = _('%s seconds')
+_MAX_DELAY = 1000
+
+class Frame(SectionView):
+ def __init__(self, model, alerts):
+ SectionView.__init__(self)
+
+ self._model = model
+ self._corner_delay_sid = 0
+ self._corner_delay_is_valid = True
+ self._corner_delay_change_handler = None
+ self._edge_delay_sid = 0
+ self._edge_delay_is_valid = True
+ self._edge_delay_change_handler = None
+ self.restart_alerts = alerts
+
+ self.set_border_width(style.DEFAULT_SPACING * 2)
+ self.set_spacing(style.DEFAULT_SPACING)
+ self._group = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
+
+ separator = gtk.HSeparator()
+ self.pack_start(separator, expand=False)
+ separator.show()
+
+ label_activation = gtk.Label(_('Activation Delay'))
+ label_activation.set_alignment(0, 0)
+ self.pack_start(label_activation, expand=False)
+ label_activation.show()
+
+ self._box_sliders = gtk.VBox()
+ self._box_sliders.set_border_width(style.DEFAULT_SPACING * 2)
+ self._box_sliders.set_spacing(style.DEFAULT_SPACING)
+
+ self._corner_delay_slider = None
+ self._corner_delay_alert = None
+ self._setup_corner()
+
+ self._edge_delay_slider = None
+ self._edge_delay_alert = None
+ self._setup_edge()
+
+ self.pack_start(self._box_sliders, expand=False)
+ self._box_sliders.show()
+
+ self.setup()
+
+ def _setup_corner(self):
+ box_delay = gtk.HBox(spacing=style.DEFAULT_SPACING)
+ label_delay = gtk.Label(_('Corner'))
+ label_delay.set_alignment(1, 0.75)
+ label_delay.modify_fg(gtk.STATE_NORMAL,
+ style.COLOR_SELECTION_GREY.get_gdk_color())
+ box_delay.pack_start(label_delay, expand=False)
+ self._group.add_widget(label_delay)
+ label_delay.show()
+
+ adj = gtk.Adjustment(value=100, lower=0, upper=_MAX_DELAY,
+ step_incr=100, page_incr=100, page_size=0)
+ self._corner_delay_slider = gtk.HScale(adj)
+ self._corner_delay_slider.set_digits(0)
+ self._corner_delay_slider.connect('format-value',
+ self.__corner_delay_format_cb)
+ box_delay.pack_start(self._corner_delay_slider)
+ self._corner_delay_slider.show()
+ self._box_sliders.pack_start(box_delay, expand=False)
+ box_delay.show()
+
+ self._corner_delay_alert = InlineAlert()
+ label_delay_error = gtk.Label()
+ self._group.add_widget(label_delay_error)
+
+ delay_alert_box = gtk.HBox(spacing=style.DEFAULT_SPACING)
+ delay_alert_box.pack_start(label_delay_error, expand=False)
+ label_delay_error.show()
+ delay_alert_box.pack_start(self._corner_delay_alert, expand=False)
+ self._box_sliders.pack_start(delay_alert_box, expand=False)
+ delay_alert_box.show()
+ if 'corner_delay' in self.restart_alerts:
+ self._corner_delay_alert.props.msg = self.restart_msg
+ self._corner_delay_alert.show()
+
+ def _setup_edge(self):
+ box_delay = gtk.HBox(spacing=style.DEFAULT_SPACING)
+ label_delay = gtk.Label(_('Edge'))
+ label_delay.set_alignment(1, 0.75)
+ label_delay.modify_fg(gtk.STATE_NORMAL,
+ style.COLOR_SELECTION_GREY.get_gdk_color())
+ box_delay.pack_start(label_delay, expand=False)
+ self._group.add_widget(label_delay)
+ label_delay.show()
+
+ adj = gtk.Adjustment(value=100, lower=0, upper=_MAX_DELAY,
+ step_incr=100, page_incr=100, page_size=0)
+ self._edge_delay_slider = gtk.HScale(adj)
+ self._edge_delay_slider.set_digits(0)
+ self._edge_delay_slider.connect('format-value',
+ self.__edge_delay_format_cb)
+ box_delay.pack_start(self._edge_delay_slider)
+ self._edge_delay_slider.show()
+ self._box_sliders.pack_start(box_delay, expand=False)
+ box_delay.show()
+
+ self._edge_delay_alert = InlineAlert()
+ label_delay_error = gtk.Label()
+ self._group.add_widget(label_delay_error)
+
+ delay_alert_box = gtk.HBox(spacing=style.DEFAULT_SPACING)
+ delay_alert_box.pack_start(label_delay_error, expand=False)
+ label_delay_error.show()
+ delay_alert_box.pack_start(self._edge_delay_alert, expand=False)
+ self._box_sliders.pack_start(delay_alert_box, expand=False)
+ delay_alert_box.show()
+ if 'edge_delay' in self.restart_alerts:
+ self._edge_delay_alert.props.msg = self.restart_msg
+ self._edge_delay_alert.show()
+
+ def setup(self):
+ self._corner_delay_slider.set_value(self._model.get_corner_delay())
+ self._edge_delay_slider.set_value(self._model.get_edge_delay())
+ self._corner_delay_is_valid = True
+ self._edge_delay_is_valid = True
+ self.needs_restart = False
+ self._corner_delay_change_handler = self._corner_delay_slider.connect( \
+ 'value-changed', self.__corner_delay_changed_cb)
+ self._edge_delay_change_handler = self._edge_delay_slider.connect( \
+ 'value-changed', self.__edge_delay_changed_cb)
+
+ def undo(self):
+ self._corner_delay_slider.disconnect(self._corner_delay_change_handler)
+ self._edge_delay_slider.disconnect(self._edge_delay_change_handler)
+ self._model.undo()
+ self._corner_delay_alert.hide()
+ self._edge_delay_alert.hide()
+
+ def _validate(self):
+ if self._edge_delay_is_valid and self._corner_delay_is_valid:
+ self.props.is_valid = True
+ else:
+ self.props.is_valid = False
+
+ def __corner_delay_changed_cb(self, scale, data=None):
+ if self._corner_delay_sid:
+ gobject.source_remove(self._corner_delay_sid)
+ self._corner_delay_sid = gobject.timeout_add( \
+ self._APPLY_TIMEOUT, self.__corner_delay_timeout_cb, scale)
+
+ def __corner_delay_timeout_cb(self, scale):
+ self._corner_delay_sid = 0
+ if scale.get_value() == self._model.get_corner_delay():
+ return
+ try:
+ self._model.set_corner_delay(scale.get_value())
+ except ValueError, detail:
+ self._corner_delay_alert.props.msg = detail
+ self._corner_delay_is_valid = False
+ else:
+ self._corner_delay_alert.props.msg = self.restart_msg
+ self._corner_delay_is_valid = True
+ self.needs_restart = True
+ self.restart_alerts.append('corner_delay')
+
+ self._validate()
+ self._corner_delay_alert.show()
+ return False
+
+ def __corner_delay_format_cb(self, scale, value):
+ if value == _MAX_DELAY:
+ return _never
+ elif value == 0:
+ return _instantaneous
+ else:
+ return _seconds_label % (value / _MAX_DELAY)
+
+ def __edge_delay_changed_cb(self, scale, data=None):
+ if self._edge_delay_sid:
+ gobject.source_remove(self._edge_delay_sid)
+ self._edge_delay_sid = gobject.timeout_add( \
+ self._APPLY_TIMEOUT, self.__edge_delay_timeout_cb, scale)
+
+ def __edge_delay_timeout_cb(self, scale):
+ self._edge_delay_sid = 0
+ if scale.get_value() == self._model.get_edge_delay():
+ return
+ try:
+ self._model.set_edge_delay(scale.get_value())
+ except ValueError, detail:
+ self._edge_delay_alert.props.msg = detail
+ self._edge_delay_is_valid = False
+ else:
+ self._edge_delay_alert.props.msg = self.restart_msg
+ self._edge_delay_is_valid = True
+ self.needs_restart = True
+ self.restart_alerts.append('edge_delay')
+
+ self._validate()
+ self._edge_delay_alert.show()
+ return False
+
+ def __edge_delay_format_cb(self, scale, value):
+ if value == _MAX_DELAY:
+ return _never
+ elif value == 0:
+ return _instantaneous
+ else:
+ return _seconds_label % (value / _MAX_DELAY)
diff --git a/shell/extensions/cpsection/keyboard/Makefile.am b/shell/extensions/cpsection/keyboard/Makefile.am
new file mode 100644
index 0000000..7a95da5
--- /dev/null
+++ b/shell/extensions/cpsection/keyboard/Makefile.am
@@ -0,0 +1,6 @@
+sugardir = $(pkgdatadir)/extensions/cpsection/keyboard
+
+sugar_PYTHON = \
+ __init__.py \
+ model.py \
+ view.py
diff --git a/shell/extensions/cpsection/keyboard/__init__.py b/shell/extensions/cpsection/keyboard/__init__.py
new file mode 100644
index 0000000..568e7a5
--- /dev/null
+++ b/shell/extensions/cpsection/keyboard/__init__.py
@@ -0,0 +1,22 @@
+# Copyright (C) 2009, OLPC
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from gettext import gettext as _
+
+CLASS = 'Keyboard'
+ICON = 'module-keyboard'
+TITLE = _('Keyboard')
+
diff --git a/shell/extensions/cpsection/keyboard/model.py b/shell/extensions/cpsection/keyboard/model.py
new file mode 100644
index 0000000..089c2ea
--- /dev/null
+++ b/shell/extensions/cpsection/keyboard/model.py
@@ -0,0 +1,169 @@
+# Copyright (C) 2009 OLPC
+# Author: Sayamindu Dasgupta <sayamindu@laptop.org>
+#
+# 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 xklavier
+import gconf
+
+
+_GROUP_NAME = 'grp' # The XKB name for group switch options
+
+_LAYOUTS_KEY = '/desktop/sugar/peripherals/keyboard/layouts'
+_OPTIONS_KEY = '/desktop/sugar/peripherals/keyboard/options'
+_MODEL_KEY = '/desktop/sugar/peripherals/keyboard/model'
+
+class KeyboardManager(object):
+ def __init__(self, display):
+ self._engine = xklavier.Engine(display)
+ self._configregistry = xklavier.ConfigRegistry(self._engine)
+ self._configregistry.load(False)
+ self._configrec = xklavier.ConfigRec()
+ self._configrec.get_from_server(self._engine)
+
+ self._gconf_client = gconf.client_get_default()
+
+ def _populate_one(self, config_registry, item, store):
+ store.append([item.get_description(), item.get_name()])
+
+ def _populate_two(self, config_registry, item, subitem, store):
+ layout = item.get_name()
+ if subitem:
+ description = '%s, %s' % (subitem.get_description(), \
+ item.get_description())
+ variant = subitem.get_name()
+ else:
+ description = 'Default layout, %s' % item.get_description()
+ variant = ''
+
+ store.append([description, ('%s(%s)' % (layout, variant))])
+
+ def get_models(self):
+ """Return list of supported keyboard models"""
+ models = []
+ self._configregistry.foreach_model(self._populate_one, models)
+ models.sort()
+ return models
+
+ def get_languages(self):
+ """Return list of supported keyboard languages"""
+ languages = []
+ self._configregistry.foreach_language(self._populate_one, languages)
+ languages.sort()
+ return languages
+
+ def get_layouts_for_language(self, language):
+ """Return list of supported keyboard layouts for a given language"""
+ layouts = []
+ self._configregistry.foreach_language_variant(language, \
+ self._populate_two, layouts)
+ layouts.sort()
+ return layouts
+
+ def get_options_group(self):
+ """Return list of supported options for switching keyboard group"""
+ options = []
+ self._configregistry.foreach_option(_GROUP_NAME, self._populate_one, \
+ options)
+ options.sort()
+ return options
+
+ def get_current_model(self):
+ """Return the enabled keyboard model"""
+ model = self._gconf_client.get_string(_MODEL_KEY)
+ if model:
+ return model
+ else:
+ model = self._configrec.get_model()
+ self.set_model(model)
+ return model
+
+ def get_current_layouts(self):
+ """Return the enabled keyboard layouts with variants"""
+ layouts = self._gconf_client.get_list(_LAYOUTS_KEY, gconf.VALUE_STRING)
+ if layouts:
+ return layouts
+
+ layouts = self._configrec.get_layouts()
+ variants = self._configrec.get_variants()
+
+ layout_list = []
+ i = 0
+ for layout in layouts:
+ if len(variants) <= i or variants[i] == '':
+ layout_list.append('%s(%s)' % (layout, ''))
+ else:
+ layout_list.append('%s(%s)' % (layout, variants[i]))
+ i += 1
+
+ self.set_layouts(layout_list)
+
+ return layout_list
+
+ def get_current_option_group(self):
+ """Return the enabled option for switching keyboard group"""
+ options = self._gconf_client.get_list(_OPTIONS_KEY, gconf.VALUE_STRING)
+
+ if not options:
+ options = self._configrec.get_options()
+ self.set_option_group(options)
+
+ for option in options:
+ if option.startswith(_GROUP_NAME):
+ return option
+
+ return None
+
+ def get_max_layouts(self):
+ """Return the maximum number of layouts supported simultaneously"""
+ return self._engine.get_max_num_groups()
+
+ def set_model(self, model):
+ """Sets the supplied keyboard model"""
+ if model is None or not model:
+ return
+ self._gconf_client.set_string(_MODEL_KEY, model)
+ self._configrec.set_model(model)
+ self._configrec.activate(self._engine)
+
+ def set_option_group(self, option_group):
+ """Sets the supplied option for switching keyboard group"""
+ #XXX: Merge, not overwrite previous options
+ if not option_group:
+ options = ['']
+ elif isinstance(option_group, list):
+ options = option_group
+ else:
+ options = [option_group]
+ self._gconf_client.set_list(_OPTIONS_KEY, gconf.VALUE_STRING, options)
+ self._configrec.set_options(options)
+ self._configrec.activate(self._engine)
+
+ def set_layouts(self, layouts):
+ """Sets the supplied keyboard layouts (with variants)"""
+ if layouts is None or not layouts:
+ return
+ self._gconf_client.set_list(_LAYOUTS_KEY, gconf.VALUE_STRING, layouts)
+ layouts_list = []
+ variants_list = []
+ for layout in layouts:
+ layouts_list.append(layout.split('(')[0])
+ variants_list.append(layout.split('(')[1][:-1])
+
+ self._configrec.set_layouts(layouts_list)
+ self._configrec.set_variants(variants_list)
+ self._configrec.activate(self._engine)
+
diff --git a/shell/extensions/cpsection/keyboard/view.py b/shell/extensions/cpsection/keyboard/view.py
new file mode 100644
index 0000000..dd62a85
--- /dev/null
+++ b/shell/extensions/cpsection/keyboard/view.py
@@ -0,0 +1,426 @@
+# Copyright (C) 2009, OLPC
+# Author: Sayamindu Dasgupta <sayamindu@laptop.org>
+#
+# 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 gtk
+import gobject
+import pango
+import logging
+from gettext import gettext as _
+
+from sugar.graphics import style
+from sugar.graphics.icon import Icon
+
+from jarabe.controlpanel.sectionview import SectionView
+
+CLASS = 'Language'
+ICON = 'module-keyboard'
+TITLE = _('Keyboard')
+
+_APPLY_TIMEOUT = 500
+
+#TODO: This cpsection adds checks for xklavier in bin/sugar-session and
+# src/jarabe/controlpanel/gui.py. We should get rid of these checks
+# once python-xklavier has been packaged for all major distributions
+# For more information, see: http://dev.sugarlabs.org/ticket/407
+
+class LayoutCombo(gtk.HBox):
+
+ """
+ Custom GTK widget with two comboboxes side by side, one for layout, and
+ the other for variants for the selected layout.
+ """
+
+ __gsignals__ = {
+ 'selection-changed' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
+ (gobject.TYPE_STRING, gobject.TYPE_INT))
+ }
+
+ def __init__(self, keyboard_manager, n):
+ gtk.HBox.__init__(self)
+ self._keyboard_manager = keyboard_manager
+ self._index = n
+
+ self.set_border_width(style.DEFAULT_SPACING)
+ self.set_spacing(style.DEFAULT_SPACING)
+
+ label = gtk.Label(' <b>%s</b> ' % str(n+1))
+ label.set_use_markup(True)
+ label.modify_fg(gtk.STATE_NORMAL,
+ style.COLOR_SELECTION_GREY.get_gdk_color())
+ label.set_alignment(0.5, 0.5)
+ self.pack_start(label, expand=False)
+
+ self._klang_store = gtk.ListStore(gobject.TYPE_STRING, \
+ gobject.TYPE_STRING)
+ for description, name in self._keyboard_manager.get_languages():
+ self._klang_store.append([name, description])
+
+ self._klang_combo = gtk.ComboBox(model = self._klang_store)
+ self._klang_combo_changed_id = \
+ self._klang_combo.connect('changed', self._klang_combo_changed_cb)
+ cell = gtk.CellRendererText()
+ cell.props.ellipsize = pango.ELLIPSIZE_MIDDLE
+ cell.props.ellipsize_set = True
+ self._klang_combo.pack_start(cell)
+ self._klang_combo.add_attribute(cell, 'text', 1)
+ self.pack_start(self._klang_combo, expand=True, fill = True)
+
+ self._kvariant_store = None
+ self._kvariant_combo = gtk.ComboBox(model = None)
+ self._kvariant_combo_changed_id = \
+ self._kvariant_combo.connect('changed', \
+ self._kvariant_combo_changed_cb)
+ cell = gtk.CellRendererText()
+ cell.props.ellipsize = pango.ELLIPSIZE_MIDDLE
+ cell.props.ellipsize_set = True
+ self._kvariant_combo.pack_start(cell)
+ self._kvariant_combo.add_attribute(cell, 'text', 1)
+ self.pack_start(self._kvariant_combo, expand=True, fill = True)
+
+ self._klang_combo.set_active(self._index)
+
+ def select_layout(self, layout):
+ """Select a given keyboard layout and show appropriate variants"""
+ self._kvariant_combo.handler_block(self._kvariant_combo_changed_id)
+ for i in range(0, len(self._klang_store)):
+ self._klang_combo.set_active(i)
+ for j in range(0, len(self._kvariant_store)):
+ if self._kvariant_store[j][0] == layout:
+ self._kvariant_combo.set_active(j)
+ self._kvariant_combo.handler_unblock(\
+ self._kvariant_combo_changed_id)
+ return True
+
+ self._kvariant_combo.handler_unblock(self._kvariant_combo_changed_id)
+ self._klang_combo.set_active(0)
+ return False
+
+ def get_layout(self):
+ """Gets the selected layout (with variant)"""
+ it = self._kvariant_combo.get_active_iter()
+ model = self._kvariant_combo.get_model()
+ return model.get(it, 0)[0]
+
+ def _set_kvariant_store(self, lang):
+ self._kvariant_store = gtk.ListStore(gobject.TYPE_STRING, \
+ gobject.TYPE_STRING)
+ layouts = self._keyboard_manager.get_layouts_for_language(lang)
+ for description, name in layouts:
+ self._kvariant_store.append([name, description])
+ self._kvariant_combo.set_model(self._kvariant_store)
+ self._kvariant_combo.set_active(0)
+
+ def _klang_combo_changed_cb(self, combobox):
+ it = combobox.get_active_iter()
+ model = combobox.get_model()
+ lang = model.get(it, 0)[0]
+ self._set_kvariant_store(lang)
+
+ def _kvariant_combo_changed_cb(self, combobox):
+ it = combobox.get_active_iter()
+ model = combobox.get_model()
+ layout = model.get(it, 0)[0]
+ self.emit('selection-changed', layout, self._index)
+
+
+class Keyboard(SectionView):
+ def __init__(self, model, alerts):
+ SectionView.__init__(self)
+
+ self._model = model
+
+ self._kmodel = None
+ self._selected_kmodel = None
+
+ self._klayouts = []
+ self._selected_klayouts = []
+
+ self._group_switch_option = None
+ self._selected_group_switch_option = None
+
+ self.set_border_width(style.DEFAULT_SPACING * 2)
+ self.set_spacing(style.DEFAULT_SPACING)
+
+ self._layout_table = gtk.Table(rows = 4, columns = 2, \
+ homogeneous = False)
+
+ self._keyboard_manager = model.KeyboardManager(self.get_display())
+ self._layout_combo_list = []
+ self._layout_addremovebox_list = []
+
+ scrollwindow = gtk.ScrolledWindow()
+ scrollwindow.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+ self.pack_start(scrollwindow, expand=True)
+ scrollwindow.show()
+
+ self._vbox = gtk.VBox()
+ scrollwindow.add_with_viewport(self._vbox)
+
+ self.__kmodel_sid = None
+ self.__layout_sid = None
+ self.__group_switch_sid = None
+
+ self._setup_kmodel()
+ self._setup_layouts()
+ self._setup_group_switch_option()
+
+ self._vbox.show()
+
+ def _setup_kmodel(self):
+ """Adds the controls for changing the keyboard model"""
+ separator_kmodel = gtk.HSeparator()
+ self._vbox.pack_start(separator_kmodel, expand=False)
+ separator_kmodel.show_all()
+
+ label_kmodel = gtk.Label(_('Keyboard Model'))
+ label_kmodel.set_alignment(0, 0)
+ self._vbox.pack_start(label_kmodel, expand=False)
+ label_kmodel.show_all()
+
+ box_kmodel = gtk.VBox()
+ box_kmodel.set_border_width(style.DEFAULT_SPACING * 2)
+ box_kmodel.set_spacing(style.DEFAULT_SPACING)
+
+ kmodel_store = gtk.ListStore(gobject.TYPE_STRING, \
+ gobject.TYPE_STRING)
+ for description, name in self._keyboard_manager.get_models():
+ kmodel_store.append([name, description])
+
+ kmodel_combo = gtk.ComboBox(model = kmodel_store)
+ cell = gtk.CellRendererText()
+ cell.props.ellipsize = pango.ELLIPSIZE_MIDDLE
+ cell.props.ellipsize_set = True
+ kmodel_combo.pack_start(cell)
+ kmodel_combo.add_attribute(cell, 'text', 1)
+
+ self._kmodel = self._keyboard_manager.get_current_model()
+ if self._kmodel is not None:
+ for row in kmodel_store:
+ if self._kmodel in row[0]:
+ kmodel_combo.set_active_iter(row.iter)
+ break
+
+ box_kmodel.pack_start(kmodel_combo, expand = False)
+ self._vbox.pack_start(box_kmodel, expand=False)
+ box_kmodel.show_all()
+
+ kmodel_combo.connect('changed', self.__kmodel_changed_cb)
+
+ def __kmodel_changed_cb(self, combobox):
+ if self.__kmodel_sid is not None:
+ gobject.source_remove(self.__kmodel_sid)
+ self.__kmodel_sid = gobject.timeout_add(_APPLY_TIMEOUT,
+ self.__kmodel_timeout_cb, combobox)
+
+ def __kmodel_timeout_cb(self, combobox):
+ it = combobox.get_active_iter()
+ model = combobox.get_model()
+ self._selected_kmodel = model.get(it, 0)[0]
+ if self._selected_kmodel == self._keyboard_manager.get_current_model():
+ return
+ try:
+ self._keyboard_manager.set_model(self._selected_kmodel)
+ except Exception:
+ logging.exception('Could not set new keyboard model')
+
+ return False
+
+ def _setup_group_switch_option(self):
+ """Adds the controls for changing the group switch option of keyboard"""
+ separator_group_option = gtk.HSeparator()
+ self._vbox.pack_start(separator_group_option, expand=False)
+ separator_group_option.show_all()
+
+ label_group_option = gtk.Label(_('Key(s) to change layout'))
+ label_group_option.set_alignment(0, 0)
+ self._vbox.pack_start(label_group_option, expand=False)
+ label_group_option.show_all()
+
+ box_group_option = gtk.VBox()
+ box_group_option.set_border_width(style.DEFAULT_SPACING * 2)
+ box_group_option.set_spacing(style.DEFAULT_SPACING)
+
+ group_option_store = gtk.ListStore(gobject.TYPE_STRING, \
+ gobject.TYPE_STRING)
+ for description, name in self._keyboard_manager.get_options_group():
+ group_option_store.append([name, description])
+
+ group_option_combo = gtk.ComboBox(model = group_option_store)
+ cell = gtk.CellRendererText()
+ cell.props.ellipsize = pango.ELLIPSIZE_MIDDLE
+ cell.props.ellipsize_set = True
+ group_option_combo.pack_start(cell)
+ group_option_combo.add_attribute(cell, 'text', 1)
+
+ self._group_switch_option = \
+ self._keyboard_manager.get_current_option_group()
+ if not self._group_switch_option:
+ group_option_combo.set_active(0)
+ else:
+ found = False
+ for row in group_option_store:
+ if self._group_switch_option in row[0]:
+ group_option_combo.set_active_iter(row.iter)
+ found = True
+ break
+ if not found:
+ group_option_combo.set_active(0)
+
+ box_group_option.pack_start(group_option_combo, expand = False)
+ self._vbox.pack_start(box_group_option, expand=False)
+ box_group_option.show_all()
+
+ group_option_combo.connect('changed', \
+ self.__group_switch_changed_cb)
+
+ def __group_switch_changed_cb(self, combobox):
+ if self.__group_switch_sid is not None:
+ gobject.source_remove(self.__group_switch_sid)
+ self.__group_switch_sid = gobject.timeout_add(_APPLY_TIMEOUT,
+ self.__group_switch_timeout_cb, combobox)
+
+ def __group_switch_timeout_cb(self, combobox):
+ it = combobox.get_active_iter()
+ model = combobox.get_model()
+ self._selected_group_switch_option = model.get(it, 0)[0]
+ if self._selected_group_switch_option == \
+ self._keyboard_manager.get_current_option_group():
+ return
+ try:
+ self._keyboard_manager.set_option_group(\
+ self._selected_group_switch_option)
+ except Exception:
+ logging.exception('Could not set new keyboard group switch option')
+
+
+ return False
+
+ def _setup_layouts(self):
+ """Adds the controls for changing the keyboard layouts"""
+ separator_klayout = gtk.HSeparator()
+ self._vbox.pack_start(separator_klayout, expand=False)
+ separator_klayout.show_all()
+
+ label_klayout = gtk.Label(_('Keyboard Layout(s)'))
+ label_klayout.set_alignment(0, 0)
+ label_klayout.show_all()
+ self._vbox.pack_start(label_klayout, expand=False)
+
+ self._klayouts = self._keyboard_manager.get_current_layouts()
+ for i in range(0, self._keyboard_manager.get_max_layouts()):
+ add_remove_box = self.__create_add_remove_box()
+ self._layout_addremovebox_list.append(add_remove_box)
+ self._layout_table.attach(add_remove_box, 1, 2, i, i+1)
+
+ layout_combo = LayoutCombo(self._keyboard_manager, i)
+ layout_combo.connect('selection-changed', \
+ self.__layout_combo_selection_changed_cb)
+ self._layout_combo_list.append(layout_combo)
+ self._layout_table.attach(layout_combo, 0, 1, i, i+1)
+
+ if i < len(self._klayouts):
+ layout_combo.show_all()
+ layout_combo.select_layout(self._klayouts[i])
+
+ self._vbox.pack_start(self._layout_table, expand=False)
+ self._layout_table.set_size_request(self._vbox.size_request()[0], -1)
+ self._layout_table.show()
+ self._update_klayouts()
+
+ def __determine_add_remove_box_visibility(self):
+ i = 1
+ for box in self._layout_addremovebox_list:
+ if not i == len(self._selected_klayouts):
+ box.props.visible = False
+ else:
+ box.show_all()
+ if i == 1:
+ # First row - no need for showing remove btn
+ add, remove = box.get_children()
+ remove.props.visible = False
+ if i == self._keyboard_manager.get_max_layouts():
+ # Last row - no need for showing add btn
+ add, remove = box.get_children()
+ add.props.visible = False
+ i += 1
+
+ def __create_add_remove_box(self):
+ '''Creates gtk.Hbox with add/remove buttons'''
+ add_icon = Icon(icon_name='list-add')
+
+ add_button = gtk.Button()
+ add_button.set_image(add_icon)
+ add_button.connect('clicked',
+ self.__add_button_clicked_cb)
+
+ remove_icon = Icon(icon_name='list-remove')
+ remove_button = gtk.Button()
+ remove_button.set_image(remove_icon)
+ remove_button.connect('clicked',
+ self.__remove_button_clicked_cb)
+
+ add_remove_box = gtk.HButtonBox()
+ add_remove_box.set_layout(gtk.BUTTONBOX_START)
+ add_remove_box.set_spacing(10)
+ add_remove_box.pack_start(add_button)
+ add_remove_box.pack_start(remove_button)
+
+ return add_remove_box
+
+ def __layout_combo_selection_changed_cb(self, combo, layout, index):
+ self._update_klayouts()
+
+ def __add_button_clicked_cb(self, button):
+ self._layout_combo_list[len(self._selected_klayouts)].show_all()
+ self._update_klayouts()
+
+ def __remove_button_clicked_cb(self, button):
+ self._layout_combo_list[len(self._selected_klayouts) - 1].hide()
+ self._update_klayouts()
+
+ def _update_klayouts(self):
+ """Responds to any changes in the keyboard layout options"""
+ self._selected_klayouts = []
+ for combo in self._layout_combo_list:
+ if combo.props.visible:
+ self._selected_klayouts.append(combo.get_layout())
+
+ self.__determine_add_remove_box_visibility()
+
+ if self.__layout_sid is not None:
+ gobject.source_remove(self.__layout_sid)
+ self.__layout_sid = gobject.timeout_add(_APPLY_TIMEOUT,
+ self.__layout_timeout_cb)
+
+ def __layout_timeout_cb(self):
+ if self._selected_klayouts == \
+ self._keyboard_manager.get_current_layouts():
+ return
+ try:
+ self._keyboard_manager.set_layouts(self._selected_klayouts)
+ except Exception:
+ logging.exception('Could not set new keyboard layouts')
+
+ return False
+
+
+ def undo(self):
+ """Reverts back to the original keyboard configuration"""
+ self._keyboard_manager.set_model(self._kmodel)
+ self._keyboard_manager.set_layouts(self._klayouts)
+ self._keyboard_manager.set_option_group(self._group_switch_option)
+
diff --git a/shell/extensions/cpsection/language/Makefile.am b/shell/extensions/cpsection/language/Makefile.am
new file mode 100644
index 0000000..209fc32
--- /dev/null
+++ b/shell/extensions/cpsection/language/Makefile.am
@@ -0,0 +1,6 @@
+sugardir = $(pkgdatadir)/extensions/cpsection/language
+
+sugar_PYTHON = \
+ __init__.py \
+ model.py \
+ view.py
diff --git a/shell/extensions/cpsection/language/__init__.py b/shell/extensions/cpsection/language/__init__.py
new file mode 100644
index 0000000..a8f9f08
--- /dev/null
+++ b/shell/extensions/cpsection/language/__init__.py
@@ -0,0 +1,22 @@
+# Copyright (C) 2008, OLPC
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from gettext import gettext as _
+
+CLASS = 'Language'
+ICON = 'module-language'
+TITLE = _('Language')
+
diff --git a/shell/extensions/cpsection/language/model.py b/shell/extensions/cpsection/language/model.py
new file mode 100644
index 0000000..4f3686f
--- /dev/null
+++ b/shell/extensions/cpsection/language/model.py
@@ -0,0 +1,151 @@
+# Copyright (C) 2007, 2008 One Laptop Per Child
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+#
+# The language config is based on the system-config-language
+# (http://fedoraproject.org/wiki/SystemConfig/language) tool
+# Parts of the code were reused.
+#
+
+import os
+import locale
+from gettext import gettext as _
+import subprocess
+
+_default_lang = '%s.%s' % locale.getdefaultlocale()
+_standard_msg = _("Could not access ~/.i18n. Create standard settings.")
+
+def read_all_languages():
+ fdp = subprocess.Popen(['locale', '-av'], stdout=subprocess.PIPE)
+ lines = fdp.stdout.read().split('\n')
+ locales = []
+
+ for line in lines:
+ if line.find('locale:') != -1:
+ locale = line.split()[1]
+ elif line.find('language |') != -1:
+ lang = line.lstrip('language |')
+ elif line.find('territory |') != -1:
+ territory = line.lstrip('territory |')
+ if locale.endswith('utf8') and len(lang):
+ locales.append((lang, territory, locale))
+
+ #FIXME: This is a temporary workaround for locales that are essential to
+ # OLPC, but are not in Glibc yet.
+ locales.append(('Kreyol', 'Haiti', 'ht_HT.utf8'))
+ locales.append(('Dari', 'Afghanistan', 'fa_AF.utf8'))
+ locales.append(('Pashto', 'Afghanistan', 'ps_AF.utf8'))
+
+ locales.sort()
+ return locales
+
+def _initialize():
+ if set_languages.__doc__ is None:
+ # when running under 'python -OO', all __doc__ fields are None,
+ # so += would fail -- and this function would be unnecessary anyway.
+ return
+ languages = read_all_languages()
+ set_languages.__doc__ += '\n'
+ for lang in languages:
+ set_languages.__doc__ += '%s \n' % (lang[0].replace(' ', '_') + '/' +
+ lang[1].replace(' ', '_'))
+
+def _write_i18n(langs):
+ colon = ':'
+ langstr = colon.join(langs)
+ path = os.path.join(os.environ.get("HOME"), '.i18n')
+ if not os.access(path, os.W_OK):
+ print _standard_msg
+ fd = open(path, 'w')
+ fd.write('LANG="%s"\n' % _default_lang)
+ fd.write('LANGUAGE="%s"\n' % _default_lang)
+ fd.close()
+ else:
+ fd = open(path, 'w')
+ fd.write('LANG="%s"\n' % langs[0].strip("\n"))
+ fd.write('LANGUAGE="%s"\n' % langstr)
+ fd.close()
+
+def get_languages():
+ path = os.path.join(os.environ.get("HOME"), '.i18n')
+ if not os.access(path, os.R_OK):
+ print _standard_msg
+ fd = open(path, 'w')
+ fd.write('LANG="%s"\n' % _default_lang)
+ fd.write('LANGUAGE="%s"\n' % _default_lang)
+ fd.close()
+ return [_default_lang]
+
+ fd = open(path, "r")
+ lines = fd.readlines()
+ fd.close()
+
+ langlist = None
+
+ for line in lines:
+ if line.startswith("LANGUAGE="):
+ lang = line[9:].replace('"', '')
+ lang = lang.strip()
+ langlist = lang.split(':')
+ elif line.startswith("LANG="):
+ lang = line[5:].replace('"', '')
+
+ # There might be cases where .i18n may not contain a LANGUAGE field
+ if langlist == None:
+ return [lang]
+ else:
+ return langlist
+
+def print_languages():
+ codes = get_languages()
+
+ languages = read_all_languages()
+ for code in codes:
+ found_lang = False
+ for lang in languages:
+ if lang[2].split('.')[0] == code.split('.')[0]:
+ print lang[0].replace(' ', '_') + '/' + \
+ lang[1].replace(' ', '_')
+ found_lang = True
+ break
+ if not found_lang:
+ print (_("Language for code=%s could not be determined.") % code)
+
+def set_languages(languages):
+ """Set the system language.
+ languages :
+ """
+ if isinstance(languages, str):
+ # This came from the commandline
+ #TODO: Support multiple languages from the command line
+ if languages.endswith('utf8'):
+ _write_i18n(languages)
+ return 1
+ else:
+ langs = read_all_languages()
+ for lang, territory, locale in langs:
+ code = lang.replace(' ', '_') + '/' \
+ + territory.replace(' ', '_')
+ if code == languages:
+ _write_i18n(locale)
+ return 1
+ print (_("Sorry I do not speak \'%s\'.") % languages)
+ else:
+ _write_i18n(languages)
+
+# inilialize the docstrings for the language
+_initialize()
+
diff --git a/shell/extensions/cpsection/language/view.py b/shell/extensions/cpsection/language/view.py
new file mode 100644
index 0000000..d1a49cf
--- /dev/null
+++ b/shell/extensions/cpsection/language/view.py
@@ -0,0 +1,282 @@
+# Copyright (C) 2008, OLPC
+# Copyright (C) 2009, Simon Schampijer
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# 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 gtk
+import gobject
+import gettext
+
+from sugar.graphics import style
+from sugar.graphics.icon import Icon
+
+from jarabe.controlpanel.sectionview import SectionView
+from jarabe.controlpanel.inlinealert import InlineAlert
+
+_translate_language = lambda msg: gettext.dgettext('iso_639', msg)
+_translate_country = lambda msg: gettext.dgettext('iso_3166', msg)
+
+CLASS = 'Language'
+ICON = 'module-language'
+TITLE = gettext.gettext('Language')
+
+class Language(SectionView):
+ def __init__(self, model, alerts):
+ SectionView.__init__(self)
+
+ self._model = model
+ self.restart_alerts = alerts
+ self._lang_sid = 0
+ self._selected_lang_count = 0
+ self._labels = []
+ self._stores = []
+ self._comboboxes = []
+ self._add_remove_boxes = []
+ self._changed = False
+ self._cursor_change_handler = None
+
+ self._available_locales = self._model.read_all_languages()
+ self._selected_locales = self._model.get_languages()
+
+ self.set_border_width(style.DEFAULT_SPACING * 2)
+ self.set_spacing(style.DEFAULT_SPACING)
+
+ explanation = gettext.gettext("Add languages in the order you prefer." \
+ " If a translation is not available,"\
+ " the next in the list will be used.")
+ self._text = gtk.Label(explanation)
+ self._text.set_width_chars(100)
+ self._text.set_line_wrap(True)
+ self._text.set_alignment(0, 0)
+ self.pack_start(self._text, False)
+ self._text.show()
+
+ scrolled = gtk.ScrolledWindow()
+ scrolled.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+ scrolled.show()
+ self.pack_start(scrolled, expand=True)
+
+ self._table = gtk.Table(rows=1, columns=3, homogeneous=False)
+ self._table.set_border_width(style.DEFAULT_SPACING * 2)
+ self._table.show()
+ scrolled.add_with_viewport(self._table)
+
+ self._lang_alert_box = gtk.HBox(spacing=style.DEFAULT_SPACING)
+ self.pack_start(self._lang_alert_box, False)
+
+ self._lang_alert = InlineAlert()
+ self._lang_alert_box.pack_start(self._lang_alert)
+ if 'lang' in self.restart_alerts:
+ self._lang_alert.props.msg = self.restart_msg
+ self._lang_alert.show()
+ self._lang_alert_box.show()
+
+ self.setup()
+
+ def _add_row(self, locale_code=None):
+ '''Adds a row to the table'''
+
+ self._selected_lang_count += 1
+
+ self._table.resize(self._selected_lang_count, 3)
+
+ label = gtk.Label(str=str(self._selected_lang_count))
+ label.modify_fg(gtk.STATE_NORMAL,
+ style.COLOR_SELECTION_GREY.get_gdk_color())
+ self._labels.append(label)
+ self._attach_to_table(label, 0, 1, padding=1)
+ label.show()
+
+
+ store = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
+ for language, country, code in self._available_locales:
+ description = '%s (%s)' % (_translate_language(language), \
+ _translate_country(country))
+ store.append([code, description])
+
+ combobox = gtk.ComboBox(model=store)
+ cell = gtk.CellRendererText()
+ combobox.pack_start(cell)
+ combobox.add_attribute(cell, 'text', 1)
+
+ if locale_code:
+ for row in store:
+ lang = locale_code.split('.')[0]
+ lang_column = row[0].split('.')[0]
+ if lang in lang_column:
+ combobox.set_active_iter(row.iter)
+ break
+ else:
+ combobox.set_active(1)
+
+ combobox.connect('changed', self.__combobox_changed_cb)
+
+ self._stores.append(store)
+ self._comboboxes.append(combobox)
+ self._attach_to_table(combobox, 1, 2, yoptions=gtk.SHRINK)
+
+ add_remove_box = self._create_add_remove_box()
+ self._add_remove_boxes.append(add_remove_box)
+ self._attach_to_table(add_remove_box, 2, 3)
+
+ add_remove_box.show_all()
+
+ if self._selected_lang_count > 1:
+ previous_add_removes = self._add_remove_boxes[-2]
+ previous_add_removes.hide_all()
+
+ self._determine_add_remove_visibility()
+
+ combobox.show()
+
+ def _attach_to_table(self, widget, row, column, padding=20, \
+ yoptions=gtk.FILL):
+ self._table.attach(widget, row, column, \
+ self._selected_lang_count - 1, self._selected_lang_count, \
+ xoptions=gtk.FILL, yoptions=yoptions, xpadding=padding, \
+ ypadding=padding)
+
+ def _delete_last_row(self):
+ '''Deletes the last row of the table'''
+
+ self._selected_lang_count -= 1
+
+ label, add_remove_box, combobox, store_ = self._get_last_row()
+
+ label.destroy()
+ add_remove_box.destroy()
+ combobox.destroy()
+
+ self._table.resize(self._selected_lang_count, 3)
+
+ self._add_remove_boxes[-1].show_all()
+
+ def _get_last_row(self):
+ label = self._labels.pop()
+ add_remove_box = self._add_remove_boxes.pop()
+ combobox = self._comboboxes.pop()
+ store = self._stores.pop()
+
+ return label, add_remove_box, combobox, store
+
+ def setup(self):
+ for locale in self._selected_locales:
+ self._add_row(locale_code=locale)
+
+ def undo(self):
+ self._model.undo()
+ self._lang_alert.hide()
+
+ def _create_add_remove_box(self):
+ '''Creates gtk.Hbox with add/remove buttons'''
+ add_icon = Icon(icon_name='list-add')
+
+ add_button = gtk.Button()
+ add_button.set_image(add_icon)
+ add_button.connect('clicked',
+ self.__add_button_clicked_cb)
+
+ remove_icon = Icon(icon_name='list-remove')
+ remove_button = gtk.Button()
+ remove_button.set_image(remove_icon)
+ remove_button.connect('clicked',
+ self.__remove_button_clicked_cb)
+
+ add_remove_box = gtk.HButtonBox()
+ add_remove_box.set_layout(gtk.BUTTONBOX_START)
+ add_remove_box.set_spacing(10)
+ add_remove_box.pack_start(add_button)
+ add_remove_box.pack_start(remove_button)
+
+ return add_remove_box
+
+ def __add_button_clicked_cb(self, button):
+ self._add_row()
+ self._check_change()
+
+ def __remove_button_clicked_cb(self, button):
+ self._delete_last_row()
+ self._check_change()
+
+ def __combobox_changed_cb(self, button):
+ self._check_change()
+
+ def _check_change(self):
+ selected_langs = self._get_selected_langs()
+ last_lang = selected_langs[-1]
+
+ self._determine_add_remove_visibility(last_lang = last_lang)
+
+ self._changed = (selected_langs != self._selected_locales)
+
+ if self._changed == False:
+ # The user reverted back to the original config
+ self.needs_restart = False
+ if 'lang' in self.restart_alerts:
+ self.restart_alerts.remove('lang')
+ self._lang_alert.hide()
+ if self._lang_sid:
+ gobject.source_remove(self._lang_sid)
+ self._model.undo()
+ return False
+
+ if self._lang_sid:
+ gobject.source_remove(self._lang_sid)
+ self._lang_sid = gobject.timeout_add(self._APPLY_TIMEOUT,
+ self.__lang_timeout_cb,
+ selected_langs)
+
+ def _get_selected_langs(self):
+ new_codes = []
+ for combobox in self._comboboxes:
+ it = combobox.get_active_iter()
+ model = combobox.get_model()
+ lang_code = model.get(it, 0)[0]
+ new_codes.append(lang_code)
+
+ return new_codes
+
+ def _determine_add_remove_visibility(self, last_lang = None):
+ # We should not let users add fallback languages for English (USA)
+ # This is because the software is not usually _translated_ into English
+ # which means that the fallback gets selected automatically
+
+ if last_lang is None:
+ selected_langs = self._get_selected_langs()
+ last_lang = selected_langs[-1]
+
+ add_remove_box = self._add_remove_boxes[-1]
+ buttons = add_remove_box.get_children()
+ add_button, remove_button = buttons
+
+ if last_lang.startswith('en_US'):
+ add_button.props.visible = False
+ else:
+ add_button.props.visible = True
+
+ if self._selected_lang_count == 1:
+ remove_button.props.visible = False
+ else:
+ remove_button.props.visible = True
+
+
+ def __lang_timeout_cb(self, codes):
+ self._lang_sid = 0
+ self._model.set_languages(codes)
+ self.restart_alerts.append('lang')
+ self.needs_restart = True
+ self._lang_alert.props.msg = self.restart_msg
+ self._lang_alert.show()
+ return False
diff --git a/shell/extensions/cpsection/modemconfiguration/Makefile.am b/shell/extensions/cpsection/modemconfiguration/Makefile.am
new file mode 100644
index 0000000..3e2613e
--- /dev/null
+++ b/shell/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/shell/extensions/cpsection/modemconfiguration/__init__.py b/shell/extensions/cpsection/modemconfiguration/__init__.py
new file mode 100644
index 0000000..8a219dc
--- /dev/null
+++ b/shell/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/shell/extensions/cpsection/modemconfiguration/model.py b/shell/extensions/cpsection/modemconfiguration/model.py
new file mode 100755
index 0000000..2545ce1
--- /dev/null
+++ b/shell/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/shell/extensions/cpsection/modemconfiguration/view.py b/shell/extensions/cpsection/modemconfiguration/view.py
new file mode 100644
index 0000000..b236f3f
--- /dev/null
+++ b/shell/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/shell/extensions/cpsection/network/Makefile.am b/shell/extensions/cpsection/network/Makefile.am
new file mode 100644
index 0000000..35fd27c
--- /dev/null
+++ b/shell/extensions/cpsection/network/Makefile.am
@@ -0,0 +1,6 @@
+sugardir = $(pkgdatadir)/extensions/cpsection/network
+
+sugar_PYTHON = \
+ __init__.py \
+ model.py \
+ view.py
diff --git a/shell/extensions/cpsection/network/__init__.py b/shell/extensions/cpsection/network/__init__.py
new file mode 100644
index 0000000..8fea274
--- /dev/null
+++ b/shell/extensions/cpsection/network/__init__.py
@@ -0,0 +1,25 @@
+# Copyright (C) 2006-2007, Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from gettext import gettext as _
+
+CLASS = 'Network'
+ICON = 'module-network'
+TITLE = _('Network')
+KEYWORDS = ['network', 'jabber', 'radio', 'server']
+
+
+
diff --git a/shell/extensions/cpsection/network/model.py b/shell/extensions/cpsection/network/model.py
new file mode 100644
index 0000000..e1c3dab
--- /dev/null
+++ b/shell/extensions/cpsection/network/model.py
@@ -0,0 +1,141 @@
+# Copyright (C) 2008 One Laptop Per Child
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+import dbus
+from gettext import gettext as _
+import gconf
+
+_NM_SERVICE = 'org.freedesktop.NetworkManager'
+_NM_PATH = '/org/freedesktop/NetworkManager'
+_NM_IFACE = 'org.freedesktop.NetworkManager'
+
+KEYWORDS = ['network', 'jabber', 'radio', 'server']
+
+class ReadError(Exception):
+ def __init__(self, value):
+ self.value = value
+ def __str__(self):
+ return repr(self.value)
+
+def get_jabber():
+ client = gconf.client_get_default()
+ return client.get_string('/desktop/sugar/collaboration/jabber_server')
+
+def print_jabber():
+ print get_jabber()
+
+def set_jabber(server):
+ """Set the jabber server
+ server : e.g. 'olpc.collabora.co.uk'
+ """
+ client = gconf.client_get_default()
+ client.set_string('/desktop/sugar/collaboration/jabber_server', server)
+
+ _restart_jabber()
+ return 0
+
+def _restart_jabber():
+ """Call Sugar Presence Service to restart Telepathy CMs.
+
+ This allows restarting the jabber server connection when we change it.
+ """
+ _PS_SERVICE = "org.laptop.Sugar.Presence"
+ _PS_INTERFACE = "org.laptop.Sugar.Presence"
+ _PS_PATH = "/org/laptop/Sugar/Presence"
+ bus = dbus.SessionBus()
+ try:
+ ps = dbus.Interface(bus.get_object(_PS_SERVICE, _PS_PATH),
+ _PS_INTERFACE)
+ except dbus.DBusException:
+ raise ReadError('%s service not available' % _PS_SERVICE)
+ ps.RestartServerConnection()
+
+def get_radio():
+ try:
+ bus = dbus.SystemBus()
+ obj = bus.get_object(_NM_SERVICE, _NM_PATH)
+ nm_props = dbus.Interface(obj, 'org.freedesktop.DBus.Properties')
+ 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
+ else:
+ raise ReadError(_('State is unknown.'))
+
+def print_radio():
+ print ('off', 'on')[get_radio()]
+
+def set_radio(state):
+ """Turn Radio 'on' or 'off'
+ state : 'on/off'
+ """
+ if state == 'on' or state == 1:
+ try:
+ bus = dbus.SystemBus()
+ obj = bus.get_object(_NM_SERVICE, _NM_PATH)
+ nm_props = dbus.Interface(obj, 'org.freedesktop.DBus.Properties')
+ except dbus.DBusException:
+ raise ReadError('%s service not available' % _NM_SERVICE)
+ nm_props.Set(_NM_IFACE, 'WirelessEnabled', True)
+ elif state == 'off' or state == 0:
+ try:
+ bus = dbus.SystemBus()
+ 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)
+ else:
+ raise ValueError(_("Error in specified radio argument use on/off."))
+
+ return 0
+
+def clear_registration():
+ """Clear the registration with the schoolserver
+ """
+ client = gconf.client_get_default()
+ client.set_string('/desktop/sugar/backup_url', '')
+ return 1
+
+def clear_networks():
+ """Clear saved passwords and network configurations.
+ """
+ pass
+
+def get_publish_information():
+ client = gconf.client_get_default()
+ publish = client.get_bool('/desktop/sugar/collaboration/publish_gadget')
+ return publish
+
+def print_publish_information():
+ print get_publish_information()
+
+def set_publish_information(value):
+ """ If set to true, Sugar will make you searchable for
+ the other users of the Jabber server.
+ value: 0/1
+ """
+ try:
+ value = (False, True)[int(value)]
+ except:
+ raise ValueError(_("Error in specified argument use 0/1."))
+
+ client = gconf.client_get_default()
+ client.set_bool('/desktop/sugar/collaboration/publish_gadget', value)
+ return 0
diff --git a/shell/extensions/cpsection/network/view.py b/shell/extensions/cpsection/network/view.py
new file mode 100644
index 0000000..588daeb
--- /dev/null
+++ b/shell/extensions/cpsection/network/view.py
@@ -0,0 +1,250 @@
+# Copyright (C) 2008, OLPC
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import gtk
+import gobject
+from gettext import gettext as _
+
+from sugar.graphics import style
+
+from jarabe.controlpanel.sectionview import SectionView
+from jarabe.controlpanel.inlinealert import InlineAlert
+
+CLASS = 'Network'
+ICON = 'module-network'
+TITLE = _('Network')
+
+_APPLY_TIMEOUT = 3000
+
+class Network(SectionView):
+ def __init__(self, model, alerts):
+ SectionView.__init__(self)
+
+ self._model = model
+ self.restart_alerts = alerts
+ self._jabber_sid = 0
+ self._jabber_valid = True
+ self._radio_valid = True
+ self._jabber_change_handler = None
+ self._radio_change_handler = None
+ self._network_configuration_reset_handler = None
+
+ self.set_border_width(style.DEFAULT_SPACING * 2)
+ self.set_spacing(style.DEFAULT_SPACING)
+ group = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
+
+ self._radio_alert_box = gtk.HBox(spacing=style.DEFAULT_SPACING)
+ self._jabber_alert_box = gtk.HBox(spacing=style.DEFAULT_SPACING)
+
+ workspace = gtk.VBox()
+ workspace.show()
+
+ separator_wireless = gtk.HSeparator()
+ workspace.pack_start(separator_wireless, expand=False)
+ separator_wireless.show()
+
+ label_wireless = gtk.Label(_('Wireless'))
+ label_wireless.set_alignment(0, 0)
+ workspace.pack_start(label_wireless, expand=False)
+ label_wireless.show()
+ box_wireless = gtk.VBox()
+ box_wireless.set_border_width(style.DEFAULT_SPACING * 2)
+ box_wireless.set_spacing(style.DEFAULT_SPACING)
+
+ radio_info = gtk.Label(_("Turn off the wireless radio to save "
+ "battery life"))
+ radio_info.set_alignment(0, 0)
+ radio_info.set_line_wrap(True)
+ radio_info.show()
+ box_wireless.pack_start(radio_info, expand=False)
+
+ box_radio = gtk.HBox(spacing=style.DEFAULT_SPACING)
+ self._button = gtk.CheckButton()
+ self._button.set_alignment(0, 0)
+ box_radio.pack_start(self._button, expand=False)
+ self._button.show()
+
+ label_radio = gtk.Label(_('Radio'))
+ label_radio.set_alignment(0, 0.5)
+ box_radio.pack_start(label_radio, expand=False)
+ label_radio.show()
+
+ box_wireless.pack_start(box_radio, expand=False)
+ box_radio.show()
+
+ self._radio_alert = InlineAlert()
+ self._radio_alert_box.pack_start(self._radio_alert, expand=False)
+ box_radio.pack_end(self._radio_alert_box, expand=False)
+ self._radio_alert_box.show()
+ if 'radio' in self.restart_alerts:
+ self._radio_alert.props.msg = self.restart_msg
+ self._radio_alert.show()
+
+ history_info = gtk.Label(_("Discard network history if you "
+ "have trouble connecting to the network"))
+ history_info.set_alignment(0, 0)
+ history_info.set_line_wrap(True)
+ history_info.show()
+ box_wireless.pack_start(history_info, expand=False)
+
+ box_clear_history = gtk.HBox(spacing=style.DEFAULT_SPACING)
+ 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)
+ self._clear_history_button.show()
+ box_wireless.pack_start(box_clear_history, expand=False)
+ box_clear_history.show()
+
+ workspace.pack_start(box_wireless, expand=False)
+ box_wireless.show()
+
+ separator_mesh = gtk.HSeparator()
+ workspace.pack_start(separator_mesh, False)
+ separator_mesh.show()
+
+ label_mesh = gtk.Label(_('Collaboration'))
+ label_mesh.set_alignment(0, 0)
+ workspace.pack_start(label_mesh, expand=False)
+ label_mesh.show()
+ box_mesh = gtk.VBox()
+ box_mesh.set_border_width(style.DEFAULT_SPACING * 2)
+ box_mesh.set_spacing(style.DEFAULT_SPACING)
+
+ server_info = gtk.Label(_("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."))
+ server_info.set_alignment(0, 0)
+ server_info.set_line_wrap(True)
+ box_mesh.pack_start(server_info, expand=False)
+ server_info.show()
+
+ box_server = gtk.HBox(spacing=style.DEFAULT_SPACING)
+ label_server = gtk.Label(_('Server:'))
+ label_server.set_alignment(1, 0.5)
+ label_server.modify_fg(gtk.STATE_NORMAL,
+ style.COLOR_SELECTION_GREY.get_gdk_color())
+ box_server.pack_start(label_server, expand=False)
+ group.add_widget(label_server)
+ label_server.show()
+ self._entry = gtk.Entry()
+ self._entry.set_alignment(0)
+ self._entry.modify_bg(gtk.STATE_INSENSITIVE,
+ style.COLOR_WHITE.get_gdk_color())
+ self._entry.modify_base(gtk.STATE_INSENSITIVE,
+ style.COLOR_WHITE.get_gdk_color())
+ self._entry.set_size_request(int(gtk.gdk.screen_width() / 3), -1)
+ box_server.pack_start(self._entry, expand=False)
+ self._entry.show()
+ box_mesh.pack_start(box_server, expand=False)
+ box_server.show()
+
+ self._jabber_alert = InlineAlert()
+ label_jabber_error = gtk.Label()
+ group.add_widget(label_jabber_error)
+ self._jabber_alert_box.pack_start(label_jabber_error, expand=False)
+ label_jabber_error.show()
+ self._jabber_alert_box.pack_start(self._jabber_alert, expand=False)
+ box_mesh.pack_end(self._jabber_alert_box, expand=False)
+ self._jabber_alert_box.show()
+ if 'jabber' in self.restart_alerts:
+ self._jabber_alert.props.msg = self.restart_msg
+ self._jabber_alert.show()
+
+ workspace.pack_start(box_mesh, expand=False)
+ box_mesh.show()
+
+ scrolled = gtk.ScrolledWindow()
+ scrolled.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+ scrolled.add_with_viewport(workspace)
+ scrolled.show()
+ self.add(scrolled)
+
+ self.setup()
+
+ def setup(self):
+ self._entry.set_text(self._model.get_jabber())
+ try:
+ radio_state = self._model.get_radio()
+ except self._model.ReadError, detail:
+ self._radio_alert.props.msg = detail
+ self._radio_alert.show()
+ else:
+ self._button.set_active(radio_state)
+
+ self._jabber_valid = True
+ self._radio_valid = True
+ self.needs_restart = False
+ self._radio_change_handler = self._button.connect( \
+ 'toggled', self.__radio_toggled_cb)
+ self._jabber_change_handler = self._entry.connect( \
+ 'changed', self.__jabber_changed_cb)
+ self._network_configuration_reset_handler = \
+ self._clear_history_button.connect( \
+ 'clicked', self.__network_configuration_reset_cb)
+
+ def undo(self):
+ self._button.disconnect(self._radio_change_handler)
+ self._entry.disconnect(self._jabber_change_handler)
+ self._model.undo()
+ self._jabber_alert.hide()
+ self._radio_alert.hide()
+
+ def _validate(self):
+ if self._jabber_valid and self._radio_valid:
+ self.props.is_valid = True
+ else:
+ self.props.is_valid = False
+
+ def __radio_toggled_cb(self, widget, data=None):
+ radio_state = widget.get_active()
+ try:
+ self._model.set_radio(radio_state)
+ except self._model.ReadError, detail:
+ self._radio_alert.props.msg = detail
+ self._radio_valid = False
+ else:
+ self._radio_valid = True
+
+ self._validate()
+ return False
+
+ def __jabber_changed_cb(self, widget, data=None):
+ if self._jabber_sid:
+ gobject.source_remove(self._jabber_sid)
+ self._jabber_sid = gobject.timeout_add(_APPLY_TIMEOUT,
+ self.__jabber_timeout_cb, widget)
+
+ def __jabber_timeout_cb(self, widget):
+ self._jabber_sid = 0
+ if widget.get_text() == self._model.get_jabber:
+ return
+ try:
+ self._model.set_jabber(widget.get_text())
+ except self._model.ReadError, detail:
+ self._jabber_alert.props.msg = detail
+ self._jabber_valid = False
+ self._jabber_alert.show()
+ self.restart_alerts.append('jabber')
+ else:
+ self._jabber_valid = True
+ self._jabber_alert.hide()
+
+ self._validate()
+ return False
+
+ def __network_configuration_reset_cb(self, widget):
+ self._model.clear_networks()
diff --git a/shell/extensions/cpsection/power/Makefile.am b/shell/extensions/cpsection/power/Makefile.am
new file mode 100644
index 0000000..325260c
--- /dev/null
+++ b/shell/extensions/cpsection/power/Makefile.am
@@ -0,0 +1,6 @@
+sugardir = $(pkgdatadir)/extensions/cpsection/power
+
+sugar_PYTHON = \
+ __init__.py \
+ model.py \
+ view.py
diff --git a/shell/extensions/cpsection/power/__init__.py b/shell/extensions/cpsection/power/__init__.py
new file mode 100644
index 0000000..8b2e85f
--- /dev/null
+++ b/shell/extensions/cpsection/power/__init__.py
@@ -0,0 +1,23 @@
+# Copyright (C) 2008, OLPC
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from gettext import gettext as _
+
+CLASS = 'Power'
+ICON = 'module-power'
+TITLE = _('Power')
+KEYWORDS = ['automatic', 'extreme', 'power', 'suspend', 'battery']
+
diff --git a/shell/extensions/cpsection/power/model.py b/shell/extensions/cpsection/power/model.py
new file mode 100644
index 0000000..48d05de
--- /dev/null
+++ b/shell/extensions/cpsection/power/model.py
@@ -0,0 +1,116 @@
+# Copyright (C) 2008 One Laptop Per Child
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+import os
+from gettext import gettext as _
+import logging
+
+import gconf
+import dbus
+
+OHM_SERVICE_NAME = 'org.freedesktop.ohm'
+OHM_SERVICE_PATH = '/org/freedesktop/ohm/Keystore'
+OHM_SERVICE_IFACE = 'org.freedesktop.ohm.Keystore'
+
+POWERD_FLAG_DIR = '/etc/powerd/flags'
+POWERD_INHIBIT_FLAG = '/etc/powerd/flags/inhibit-suspend'
+
+_logger = logging.getLogger('ControlPanel - Power')
+
+
+class ReadError(Exception):
+ def __init__(self, value):
+ self.value = value
+ def __str__(self):
+ return repr(self.value)
+
+def using_powerd():
+ # directory exists if powerd running, and it's recent
+ # enough to be controllable.
+ return os.access(POWERD_FLAG_DIR, os.W_OK)
+
+def get_automatic_pm():
+ if using_powerd():
+ return not os.access(POWERD_INHIBIT_FLAG, os.R_OK)
+
+ # ohmd
+ client = gconf.client_get_default()
+ return client.get_bool('/desktop/sugar/power/automatic')
+
+def print_automatic_pm():
+ print ('off', 'on')[get_automatic_pm()]
+
+def set_automatic_pm(enabled):
+ """Automatic suspends on/off."""
+
+ if using_powerd():
+ # powerd
+ if enabled == 'off' or enabled == 0:
+ try:
+ fd = open(POWERD_INHIBIT_FLAG, 'w')
+ except IOError:
+ _logger.debug('File %s is not writeable' % POWERD_INHIBIT_FLAG)
+ else:
+ fd.close()
+ else:
+ os.unlink(POWERD_INHIBIT_FLAG)
+ return
+
+ # ohmd
+ bus = dbus.SystemBus()
+ proxy = bus.get_object(OHM_SERVICE_NAME, OHM_SERVICE_PATH)
+ keystore = dbus.Interface(proxy, OHM_SERVICE_IFACE)
+
+ if enabled == 'on' or enabled == 1:
+ keystore.SetKey("suspend.automatic_pm", 1)
+ enabled = True
+ elif enabled == 'off' or enabled == 0:
+ keystore.SetKey("suspend.automatic_pm", 0)
+ enabled = False
+ else:
+ raise ValueError(_("Error in automatic pm argument, use on/off."))
+
+ client = gconf.client_get_default()
+ client.set_bool('/desktop/sugar/power/automatic', enabled)
+ return
+
+def get_extreme_pm():
+ client = gconf.client_get_default()
+ return client.get_bool('/desktop/sugar/power/extreme')
+
+def print_extreme_pm():
+ print ('off', 'on')[get_extreme_pm()]
+
+def set_extreme_pm(enabled):
+ """Extreme power management on/off."""
+
+ bus = dbus.SystemBus()
+ proxy = bus.get_object(OHM_SERVICE_NAME, OHM_SERVICE_PATH)
+ keystore = dbus.Interface(proxy, OHM_SERVICE_IFACE)
+
+ if enabled == 'on' or enabled == 1:
+ keystore.SetKey("suspend.extreme_pm", 1)
+ 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/shell/extensions/cpsection/power/view.py b/shell/extensions/cpsection/power/view.py
new file mode 100644
index 0000000..8f1ed56
--- /dev/null
+++ b/shell/extensions/cpsection/power/view.py
@@ -0,0 +1,177 @@
+# Copyright (C) 2008, OLPC
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import gtk
+from gettext import gettext as _
+
+from sugar.graphics import style
+
+from jarabe.controlpanel.sectionview import SectionView
+from jarabe.controlpanel.inlinealert import InlineAlert
+
+class Power(SectionView):
+ def __init__(self, model, alerts):
+ SectionView.__init__(self)
+
+ 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)
+ self.set_spacing(style.DEFAULT_SPACING)
+ 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)
+ separator_pm.show()
+
+ label_pm = gtk.Label(_('Power management'))
+ label_pm.set_alignment(0, 0)
+ self.pack_start(label_pm, expand=False)
+ label_pm.show()
+ box_pm = gtk.VBox()
+ box_pm.set_border_width(style.DEFAULT_SPACING * 2)
+ box_pm.set_spacing(style.DEFAULT_SPACING)
+
+ box_automatic_pm = gtk.HBox(spacing=style.DEFAULT_SPACING)
+ label_automatic_pm = gtk.Label(
+ _('Automatic power management (increases battery life)'))
+ label_automatic_pm.set_alignment(0, 0.5)
+ self._automatic_button = gtk.CheckButton()
+ self._automatic_button.set_alignment(0, 0)
+ box_automatic_pm.pack_start(self._automatic_button, expand=False)
+ box_automatic_pm.pack_start(label_automatic_pm, expand=False)
+ self._automatic_button.show()
+ label_automatic_pm.show()
+ group.add_widget(label_automatic_pm)
+ box_pm.pack_start(box_automatic_pm, expand=False)
+ box_automatic_pm.show()
+
+ self._automatic_pm_alert = InlineAlert()
+ label_automatic_pm_error = gtk.Label()
+ group.add_widget(label_automatic_pm_error)
+ self._automatic_pm_alert_box.pack_start(label_automatic_pm_error,
+ expand=False)
+ label_automatic_pm_error.show()
+ self._automatic_pm_alert_box.pack_start(self._automatic_pm_alert,
+ expand=False)
+ box_pm.pack_end(self._automatic_pm_alert_box, expand=False)
+ self._automatic_pm_alert_box.show()
+ if 'automatic_pm' in self.restart_alerts:
+ 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()
+
+ self.setup()
+
+ 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
+
+ def __automatic_pm_toggled_cb(self, widget, data=None):
+ state = widget.get_active()
+ try:
+ self._model.set_automatic_pm(state)
+ except Exception, detail:
+ print detail
+ self._automatic_pm_alert.props.msg = detail
+ else:
+ self._automatic_pm_valid = True
+
+ 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/shell/extensions/cpsection/updater/Makefile.am b/shell/extensions/cpsection/updater/Makefile.am
new file mode 100644
index 0000000..897dbf3
--- /dev/null
+++ b/shell/extensions/cpsection/updater/Makefile.am
@@ -0,0 +1,8 @@
+SUBDIRS = backends
+
+sugardir = $(pkgdatadir)/extensions/cpsection/updater
+
+sugar_PYTHON = \
+ __init__.py \
+ model.py \
+ view.py
diff --git a/shell/extensions/cpsection/updater/__init__.py b/shell/extensions/cpsection/updater/__init__.py
new file mode 100644
index 0000000..6010615
--- /dev/null
+++ b/shell/extensions/cpsection/updater/__init__.py
@@ -0,0 +1,22 @@
+# Copyright (C) 2008, OLPC
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from gettext import gettext as _
+
+CLASS = 'ActivityUpdater'
+ICON = 'module-updater'
+TITLE = _('Software update')
+KEYWORDS = ['software', 'activity', 'update']
diff --git a/shell/extensions/cpsection/updater/backends/Makefile.am b/shell/extensions/cpsection/updater/backends/Makefile.am
new file mode 100644
index 0000000..e280a07
--- /dev/null
+++ b/shell/extensions/cpsection/updater/backends/Makefile.am
@@ -0,0 +1,5 @@
+sugardir = $(pkgdatadir)/extensions/cpsection/updater/backends
+
+sugar_PYTHON = \
+ aslo.py \
+ __init__.py
diff --git a/shell/extensions/cpsection/updater/backends/__init__.py b/shell/extensions/cpsection/updater/backends/__init__.py
new file mode 100644
index 0000000..0dd0174
--- /dev/null
+++ b/shell/extensions/cpsection/updater/backends/__init__.py
@@ -0,0 +1,16 @@
+#!/usr/bin/python
+# Copyright (C) 2009, Sugar Labs
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
diff --git a/shell/extensions/cpsection/updater/backends/aslo.py b/shell/extensions/cpsection/updater/backends/aslo.py
new file mode 100644
index 0000000..5f257f9
--- /dev/null
+++ b/shell/extensions/cpsection/updater/backends/aslo.py
@@ -0,0 +1,161 @@
+#!/usr/bin/python
+# Copyright (C) 2009, Sugar Labs
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+'''Activity information microformat parser.
+
+Activity information is embedded in HTML/XHTML/XML pages using a
+Resource Description Framework (RDF) http://www.w3.org/RDF/ .
+
+An example::
+
+<?xml version="1.0" encoding="UTF-8"?>
+<RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+<RDF:Description about="urn:mozilla:extension:bounce">
+ <em:updates>
+ <RDF:Seq>
+ <RDF:li resource="urn:mozilla:extension:bounce:7"/>
+ </RDF:Seq>
+ </em:updates>
+</RDF:Description>
+
+<RDF:Description about="urn:mozilla:extension:bounce:7">
+ <em:version>7</em:version>
+ <em:targetApplication>
+ <RDF:Description>
+ <em:id>{3ca105e0-2280-4897-99a0-c277d1b733d2}</em:id>
+ <em:minVersion>0.82</em:minVersion>
+ <em:maxVersion>0.84</em:maxVersion>
+ <em:updateLink>http://foo.xo</em:updateLink>
+ <em:updateSize>7</em:updateSize>
+ <em:updateHash>sha256:816a7c43b4f1ea4769c61c03ea4..</em:updateHash>
+ </RDF:Description>
+ </em:targetApplication>
+</RDF:Description></RDF:RDF>
+'''
+
+import logging
+from xml.etree.ElementTree import XML
+import traceback
+
+import gio
+
+from jarabe import config
+
+_FIND_DESCRIPTION = \
+ './/{http://www.w3.org/1999/02/22-rdf-syntax-ns#}Description'
+_FIND_VERSION = './/{http://www.mozilla.org/2004/em-rdf#}version'
+_FIND_LINK = './/{http://www.mozilla.org/2004/em-rdf#}updateLink'
+_FIND_SIZE = './/{http://www.mozilla.org/2004/em-rdf#}updateSize'
+
+_UPDATE_PATH = 'http://activities.sugarlabs.org/services/update-aslo.php'
+
+_fetcher = None
+
+
+class _UpdateFetcher(object):
+
+ _CHUNK_SIZE = 10240
+
+ def __init__(self, bundle, completion_cb):
+ # ASLO knows only about stable SP releases
+ major, minor = config.version.split('.')[0:2]
+ sp_version = '%s.%s' % (major, int(minor) + int(minor) % 2)
+
+ url = '%s?id=%s&appVersion=%s' % \
+ (_UPDATE_PATH, bundle.get_bundle_id(), sp_version)
+
+ logging.debug('Fetch %s', url)
+
+ self._completion_cb = completion_cb
+ self._file = gio.File(url)
+ self._stream = None
+ self._xml_data = ''
+ self._bundle = bundle
+
+ self._file.read_async(self.__file_read_async_cb)
+
+ def __file_read_async_cb(self, gfile, result):
+ try:
+ self._stream = self._file.read_finish(result)
+ except:
+ global _fetcher
+ _fetcher = None
+ self._completion_cb(None, None, None, None, traceback.format_exc())
+ return
+
+ self._stream.read_async(self._CHUNK_SIZE, self.__stream_read_async_cb)
+
+ def __stream_read_async_cb(self, stream, result):
+ xml_data = self._stream.read_finish(result)
+ if xml_data is None:
+ global _fetcher
+ _fetcher = None
+ self._completion_cb(self._bundle, None, None, None,
+ 'Error reading update information for %s from '
+ 'server.' % self._bundle.get_bundle_id())
+ return
+ elif not xml_data:
+ self._process_result()
+ else:
+ self._xml_data += xml_data
+ self._stream.read_async(self._CHUNK_SIZE,
+ self.__stream_read_async_cb)
+
+ def _process_result(self):
+ document = XML(self._xml_data)
+
+ if document.find(_FIND_DESCRIPTION) is None:
+ logging.debug('Bundle %s not available in the server for the '
+ 'version %s', self._bundle.get_bundle_id(), config.version)
+ version = None
+ link = None
+ size = None
+ else:
+ try:
+ version = int(document.find(_FIND_VERSION).text)
+ except ValueError:
+ logging.error(traceback.format_exc())
+ version = 0
+
+ link = document.find(_FIND_LINK).text
+
+ try:
+ size = long(document.find(_FIND_SIZE).text) * 1024
+ except ValueError:
+ logging.error(traceback.format_exc())
+ size = 0
+
+ global _fetcher
+ _fetcher = None
+ self._completion_cb(self._bundle, version, link, size, None)
+
+
+def fetch_update_info(bundle, completion_cb):
+ '''Queries the server for a newer version of the ActivityBundle.
+
+ completion_cb receives bundle, version, link, size and possibly an error
+ message:
+
+ def completion_cb(bundle, version, link, size, error_message):
+ '''
+ global _fetcher
+
+ if _fetcher is not None:
+ raise RuntimeError('Multiple simultaneous requests are not supported')
+
+ _fetcher = _UpdateFetcher(bundle, completion_cb)
diff --git a/shell/extensions/cpsection/updater/model.py b/shell/extensions/cpsection/updater/model.py
new file mode 100755
index 0000000..9845371
--- /dev/null
+++ b/shell/extensions/cpsection/updater/model.py
@@ -0,0 +1,346 @@
+# Copyright (C) 2009, Sugar Labs
+# Copyright (C) 2009, Tomeu Vizoso
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+'''Sugar bundle updater: model.
+
+This module implements the non-GUI portions of the bundle updater, including
+list of installed bundls, whether updates are needed, and the URL at which to
+find the bundle updated.
+'''
+
+import os
+import logging
+import tempfile
+from urlparse import urlparse
+import traceback
+
+import gobject
+import gio
+
+from sugar import env
+from sugar.datastore import datastore
+from sugar.bundle.activitybundle import ActivityBundle
+
+from jarabe.model import bundleregistry
+
+from backends import aslo
+
+
+class UpdateModel(gobject.GObject):
+ __gtype_name__ = 'SugarUpdateModel'
+
+ __gsignals__ = {
+ 'progress': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([int, str, float, int])),
+ }
+
+ ACTION_CHECKING = 0
+ ACTION_UPDATING = 1
+ ACTION_DOWNLOADING = 2
+
+ def __init__(self):
+ gobject.GObject.__init__(self)
+
+ self.updates = None
+ self._bundles_to_check = None
+ self._bundles_to_update = None
+ self._total_bundles_to_update = 0
+ self._downloader = None
+ self._cancelling = False
+
+ def check_updates(self):
+ self.updates = []
+ self._bundles_to_check = \
+ [bundle for bundle in bundleregistry.get_registry()]
+ self._check_next_update()
+
+ def _check_next_update(self):
+ total = len(bundleregistry.get_registry())
+ current = total - len(self._bundles_to_check)
+
+ bundle = self._bundles_to_check.pop()
+ self.emit('progress', UpdateModel.ACTION_CHECKING, bundle.get_name(),
+ current, total)
+
+ aslo.fetch_update_info(bundle, self.__check_completed_cb)
+
+ def __check_completed_cb(self, bundle, version, link, size, error_message):
+ if error_message is not None:
+ logging.error('Error getting update information from server:\n'
+ '%s' % error_message)
+
+ if version is not None and version > bundle.get_activity_version():
+ self.updates.append(BundleUpdate(bundle, version, link, size))
+
+ if self._cancelling:
+ self._cancel_checking()
+ elif self._bundles_to_check:
+ gobject.idle_add(self._check_next_update)
+ else:
+ total = len(bundleregistry.get_registry())
+ if bundle is None:
+ name = ''
+ else:
+ name = bundle.get_name()
+ self.emit('progress', UpdateModel.ACTION_CHECKING, name, total,
+ total)
+
+ def update(self, bundle_ids):
+ self._bundles_to_update = []
+ for bundle_update in self.updates:
+ if bundle_update.bundle.get_bundle_id() in bundle_ids:
+ self._bundles_to_update.append(bundle_update)
+
+ self._total_bundles_to_update = len(self._bundles_to_update)
+ self._download_next_update()
+
+ def _download_next_update(self):
+ if self._cancelling:
+ self._cancel_updating()
+ return
+
+ bundle_update = self._bundles_to_update.pop()
+
+ total = self._total_bundles_to_update * 2
+ current = total - len(self._bundles_to_update) * 2 - 2
+
+ self.emit('progress', UpdateModel.ACTION_DOWNLOADING,
+ bundle_update.bundle.get_name(), current, total)
+
+ self._downloader = _Downloader(bundle_update)
+ self._downloader.connect('progress', self.__downloader_progress_cb)
+ self._downloader.connect('error', self.__downloader_error_cb)
+
+ def __downloader_progress_cb(self, downloader, progress):
+ logging.debug('__downloader_progress_cb %r', progress)
+
+ if self._cancelling:
+ self._cancel_updating()
+ return
+
+ total = self._total_bundles_to_update * 2
+ current = total - len(self._bundles_to_update) * 2 - 2 + progress
+
+ self.emit('progress', UpdateModel.ACTION_DOWNLOADING,
+ self._downloader.bundle_update.bundle.get_name(),
+ current, total)
+
+ if progress == 1:
+ self._install_update(self._downloader.bundle_update,
+ self._downloader.get_local_file_path())
+ self._downloader = None
+
+ def __downloader_error_cb(self, downloader, error_message):
+ logging.error('Error downloading update:\n%s', error_message)
+
+ if self._cancelling:
+ self._cancel_updating()
+ return
+
+ total = self._total_bundles_to_update
+ current = total - len(self._bundles_to_update)
+ self.emit('progress', UpdateModel.ACTION_UPDATING, '', current, total)
+
+ if self._bundles_to_update:
+ # do it in idle so the UI has a chance to refresh
+ gobject.idle_add(self._download_next_update)
+
+ def _install_update(self, bundle_update, local_file_path):
+
+ total = self._total_bundles_to_update
+ current = total - len(self._bundles_to_update) - 0.5
+
+ self.emit('progress', UpdateModel.ACTION_UPDATING,
+ bundle_update.bundle.get_name(),
+ current, total)
+
+ # TODO: Should we first expand the zip async so we can provide progress
+ # and only then copy to the journal?
+ jobject = datastore.create()
+ try:
+ title = '%s-%s' % (bundle_update.bundle.get_name(),
+ bundle_update.version)
+ jobject.metadata['title'] = title
+ jobject.metadata['mime_type'] = ActivityBundle.MIME_TYPE
+ jobject.file_path = local_file_path
+ datastore.write(jobject, transfer_ownership=True)
+ finally:
+ jobject.destroy()
+
+ self.emit('progress', UpdateModel.ACTION_UPDATING,
+ bundle_update.bundle.get_name(),
+ current + 0.5, total)
+
+ if self._bundles_to_update:
+ # do it in idle so the UI has a chance to refresh
+ gobject.idle_add(self._download_next_update)
+
+ def cancel(self):
+ self._cancelling = True
+
+ def _cancel_checking(self):
+ logging.debug('UpdateModel._cancel_checking')
+ total = len(bundleregistry.get_registry())
+ current = total - len(self._bundles_to_check)
+ self.emit('progress', UpdateModel.ACTION_CHECKING, '', current, current)
+ self._bundles_to_check = None
+ self._cancelling = False
+
+ def _cancel_updating(self):
+ logging.debug('UpdateModel._cancel_updating')
+ current = self._total_bundles_to_update - len(self._bundles_to_update) - 1
+ self.emit('progress', UpdateModel.ACTION_UPDATING, '', current, current)
+
+ if self._downloader is not None:
+ self._downloader.cancel()
+ file_path = self._downloader.get_local_file_path()
+ if file_path is not None and os.path.exists(file_path):
+ os.unlink(file_path)
+ self._downloader = None
+
+ self._total_bundles_to_update = 0
+ self._bundles_to_update = None
+ self._cancelling = False
+
+class BundleUpdate(object):
+
+ def __init__(self, bundle, version, link, size):
+ self.bundle = bundle
+ self.version = version
+ self.link = link
+ self.size = size
+
+
+class _Downloader(gobject.GObject):
+ _CHUNK_SIZE = 10240 # 10K
+ __gsignals__ = {
+ 'progress': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([float])),
+ 'error': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([str])),
+ }
+
+ def __init__(self, bundle_update):
+ gobject.GObject.__init__(self)
+
+ self.bundle_update = bundle_update
+ self._input_stream = None
+ self._output_stream = None
+ self._pending_buffers = []
+ self._input_file = gio.File(bundle_update.link)
+ self._output_file = None
+ self._downloaded_size = 0
+ self._cancelling = False
+
+ self._input_file.read_async(self.__file_read_async_cb)
+
+ def cancel(self):
+ self._cancelling = True
+
+ def __file_read_async_cb(self, gfile, result):
+ if self._cancelling:
+ return
+
+ try:
+ self._input_stream = self._input_file.read_finish(result)
+ except:
+ self.emit('error', traceback.format_exc())
+ return
+
+ temp_file_path = self._get_temp_file_path(self.bundle_update.link)
+ self._output_file = gio.File(temp_file_path)
+ self._output_stream = self._output_file.create()
+
+ self._input_stream.read_async(self._CHUNK_SIZE, self.__read_async_cb,
+ gobject.PRIORITY_LOW)
+
+ def __read_async_cb(self, input_stream, result):
+ if self._cancelling:
+ return
+
+ data = input_stream.read_finish(result)
+
+ if data is None:
+ # TODO
+ pass
+ elif not data:
+ logging.debug('closing input stream')
+ self._input_stream.close()
+ self._check_if_finished_writing()
+ else:
+ self._pending_buffers.append(data)
+ self._input_stream.read_async(self._CHUNK_SIZE,
+ self.__read_async_cb,
+ gobject.PRIORITY_LOW)
+
+ self._write_next_buffer()
+
+ def __write_async_cb(self, output_stream, result, user_data):
+ if self._cancelling:
+ return
+
+ count = output_stream.write_finish(result)
+
+ self._downloaded_size += count
+ progress = self._downloaded_size / float(self.bundle_update.size)
+ self.emit('progress', progress)
+
+ self._check_if_finished_writing()
+
+ if self._pending_buffers:
+ self._write_next_buffer()
+
+ def _write_next_buffer(self):
+ if self._pending_buffers and not self._output_stream.has_pending():
+ data = self._pending_buffers.pop(0)
+ # TODO: we pass the buffer as user_data because of
+ # http://bugzilla.gnome.org/show_bug.cgi?id=564102
+ self._output_stream.write_async(data, self.__write_async_cb,
+ gobject.PRIORITY_LOW,
+ user_data=data)
+
+ def _get_temp_file_path(self, uri):
+ # TODO: Should we use the HTTP headers for the file name?
+ scheme_, netloc_, path, params_, query_, fragment_ = \
+ urlparse(uri)
+ path = os.path.basename(path)
+
+ if not os.path.exists(env.get_user_activities_path()):
+ os.makedirs(env.get_user_activities_path())
+
+ base_name, extension_ = os.path.splitext(path)
+ fd, file_path = tempfile.mkstemp(dir=env.get_user_activities_path(),
+ prefix=base_name, suffix='.xo')
+ os.close(fd)
+ os.unlink(file_path)
+
+ return file_path
+
+ def get_local_file_path(self):
+ return self._output_file.get_path()
+
+ def _check_if_finished_writing(self):
+ if not self._pending_buffers and \
+ not self._output_stream.has_pending() and \
+ self._input_stream.is_closed():
+
+ logging.debug('closing output stream')
+ self._output_stream.close()
+
+ self.emit('progress', 1.0)
diff --git a/shell/extensions/cpsection/updater/view.py b/shell/extensions/cpsection/updater/view.py
new file mode 100644
index 0000000..2164c0b
--- /dev/null
+++ b/shell/extensions/cpsection/updater/view.py
@@ -0,0 +1,391 @@
+# Copyright (C) 2008, One Laptop Per Child
+# Copyright (C) 2009, Tomeu Vizoso
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from gettext import gettext as _
+from gettext import ngettext
+import locale
+import logging
+
+import gobject
+import gtk
+
+from sugar.graphics import style
+from sugar.graphics.icon import Icon, CellRendererIcon
+
+from jarabe.controlpanel.sectionview import SectionView
+
+from model import UpdateModel
+
+_DEBUG_VIEW_ALL = True
+
+
+class ActivityUpdater(SectionView):
+
+ def __init__(self, model, alerts):
+ SectionView.__init__(self)
+
+ self._model = UpdateModel()
+ self._model.connect('progress', self.__progress_cb)
+
+ self.set_spacing(style.DEFAULT_SPACING)
+ self.set_border_width(style.DEFAULT_SPACING * 2)
+
+ self._top_label = gtk.Label()
+ self._top_label.set_line_wrap(True)
+ self._top_label.set_justify(gtk.JUSTIFY_LEFT)
+ self._top_label.props.xalign = 0
+ self.pack_start(self._top_label, expand=False)
+ self._top_label.show()
+
+ separator = gtk.HSeparator()
+ self.pack_start(separator, expand=False)
+ separator.show()
+
+ bottom_label = gtk.Label()
+ bottom_label.set_line_wrap(True)
+ bottom_label.set_justify(gtk.JUSTIFY_LEFT)
+ bottom_label.props.xalign = 0
+ bottom_label.set_markup(
+ _('Software updates correct errors, eliminate security ' \
+ 'vulnerabilities, and provide new features.'))
+ self.pack_start(bottom_label, expand=False)
+ bottom_label.show()
+
+ self._update_box = None
+ self._progress_pane = None
+
+ self._refresh()
+
+ def _switch_to_update_box(self):
+ if self._update_box in self.get_children():
+ return
+
+ if self._progress_pane in self.get_children():
+ self.remove(self._progress_pane)
+ self._progress_pane = None
+
+ if self._update_box is None:
+ self._update_box = UpdateBox(self._model)
+ self._update_box.refresh_button.connect('clicked',
+ self.__refresh_button_clicked_cb)
+ self._update_box.install_button.connect('clicked',
+ self.__install_button_clicked_cb)
+
+ self.pack_start(self._update_box, expand=True, fill=True)
+ self._update_box.show()
+
+ def _switch_to_progress_pane(self):
+ if self._progress_pane in self.get_children():
+ return
+
+ if self._update_box in self.get_children():
+ self.remove(self._update_box)
+ self._update_box = None
+
+ if self._progress_pane is None:
+ self._progress_pane = ProgressPane()
+ self._progress_pane.cancel_button.connect('clicked',
+ self.__cancel_button_clicked_cb)
+
+ self.pack_start(self._progress_pane, expand=True, fill=False)
+ self._progress_pane.show()
+
+ def _clear_center(self):
+ if self._progress_pane in self.get_children():
+ self.remove(self._progress_pane)
+ self._progress_pane = None
+
+ if self._update_box in self.get_children():
+ self.remove(self._update_box)
+ self._update_box = None
+
+ def __progress_cb(self, model, action, bundle_name, current, total):
+ if current == total and action == UpdateModel.ACTION_CHECKING:
+ self._finished_checking()
+ return
+ elif current == total:
+ self._finished_updating(int(current))
+ return
+
+ if action == UpdateModel.ACTION_CHECKING:
+ message = _('Checking %s...') % bundle_name
+ elif action == UpdateModel.ACTION_DOWNLOADING:
+ message = _('Downloading %s...') % bundle_name
+ elif action == UpdateModel.ACTION_UPDATING:
+ message = _('Updating %s...') % bundle_name
+
+ self._switch_to_progress_pane()
+ self._progress_pane.set_message(message)
+ self._progress_pane.set_progress(current / float(total))
+
+ def _finished_checking(self):
+ logging.debug('ActivityUpdater._finished_checking')
+ available_updates = len(self._model.updates)
+ if not available_updates:
+ top_message = _('Your software is up-to-date')
+ else:
+ top_message = ngettext('You can install %s update',
+ 'You can install %s updates',
+ available_updates)
+ top_message = top_message % available_updates
+ top_message = gobject.markup_escape_text(top_message)
+
+ self._top_label.set_markup('<big>%s</big>' % top_message)
+
+ if not available_updates:
+ self._clear_center()
+ else:
+ self._switch_to_update_box()
+ self._update_box.refresh()
+
+ def __refresh_button_clicked_cb(self, button):
+ self._refresh()
+
+ def _refresh(self):
+ top_message = _('Checking for updates...')
+ self._top_label.set_markup('<big>%s</big>' % top_message)
+ self._model.check_updates()
+
+ def __install_button_clicked_cb(self, button):
+ self._top_label.set_markup('<big>%s</big>' % _('Installing updates...'))
+ self._model.update(self._update_box.get_bundles_to_update())
+
+ def __cancel_button_clicked_cb(self, button):
+ self._model.cancel()
+
+ def _finished_updating(self, installed_updates):
+ logging.debug('ActivityUpdater._finished_updating')
+ top_message = ngettext('%s update was installed',
+ '%s updates were installed', installed_updates)
+ top_message = top_message % installed_updates
+ top_message = gobject.markup_escape_text(top_message)
+ self._top_label.set_markup('<big>%s</big>' % top_message)
+ self._clear_center()
+
+ def undo(self):
+ self._model.cancel()
+
+class ProgressPane(gtk.VBox):
+ '''Container which replaces the `ActivityPane` during refresh or
+ install.'''
+
+ def __init__(self):
+ gtk.VBox.__init__(self)
+ self.set_spacing(style.DEFAULT_PADDING)
+ self.set_border_width(style.DEFAULT_SPACING * 2)
+
+ self._progress = gtk.ProgressBar()
+ self.pack_start(self._progress)
+ self._progress.show()
+
+ self._label = gtk.Label()
+ self._label.set_line_wrap(True)
+ self._label.set_property('xalign', 0.5)
+ self._label.modify_fg(gtk.STATE_NORMAL,
+ style.COLOR_BUTTON_GREY.get_gdk_color())
+ self.pack_start(self._label)
+ self._label.show()
+
+ alignment_box = gtk.Alignment(xalign=0.5, yalign=0.5)
+ self.pack_start(alignment_box)
+ alignment_box.show()
+
+ self.cancel_button = gtk.Button(stock=gtk.STOCK_CANCEL)
+ alignment_box.add(self.cancel_button)
+ self.cancel_button.show()
+
+ def set_message(self, message):
+ self._label.set_text(message)
+
+ def set_progress(self, fraction):
+ self._progress.props.fraction = fraction
+
+
+class UpdateBox(gtk.VBox):
+
+ def __init__(self, model):
+ gtk.VBox.__init__(self)
+
+ self._model = model
+
+ self.set_spacing(style.DEFAULT_PADDING)
+
+ scrolled_window = gtk.ScrolledWindow()
+ scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ self.pack_start(scrolled_window)
+ scrolled_window.show()
+
+ self._update_list = UpdateList(model)
+ self._update_list.props.model.connect('row-changed',
+ self.__row_changed_cb)
+ scrolled_window.add(self._update_list)
+ self._update_list.show()
+
+ bottom_box = gtk.HBox()
+ bottom_box.set_spacing(style.DEFAULT_SPACING)
+ self.pack_start(bottom_box, expand=False)
+ bottom_box.show()
+
+ self._size_label = gtk.Label()
+ self._size_label.props.xalign = 0
+ self._size_label.set_justify(gtk.JUSTIFY_LEFT)
+ bottom_box.pack_start(self._size_label, expand=True)
+ self._size_label.show()
+
+ self.refresh_button = gtk.Button(stock=gtk.STOCK_REFRESH)
+ bottom_box.pack_start(self.refresh_button, expand=False)
+ self.refresh_button.show()
+
+ self.install_button = gtk.Button(_('Install selected'))
+ self.install_button.props.image = Icon(icon_name='emblem-downloads',
+ icon_size=gtk.ICON_SIZE_BUTTON)
+ bottom_box.pack_start(self.install_button, expand=False)
+ self.install_button.show()
+
+ self._update_total_size_label()
+
+ def refresh(self):
+ self._update_list.refresh()
+
+ def __row_changed_cb(self, list_model, path, iterator):
+ self._update_total_size_label()
+ self._update_install_button()
+
+ def _update_total_size_label(self):
+ total_size = 0
+ for row in self._update_list.props.model:
+ if row[UpdateListModel.SELECTED]:
+ total_size += row[UpdateListModel.SIZE]
+
+ markup = _('Download size: %s') % _format_size(total_size)
+ self._size_label.set_markup(markup)
+
+ def _update_install_button(self):
+ for row in self._update_list.props.model:
+ if row[UpdateListModel.SELECTED]:
+ self.install_button.props.sensitive = True
+ return
+ self.install_button.props.sensitive = False
+
+ def get_bundles_to_update(self):
+ bundles_to_update = []
+ for row in self._update_list.props.model:
+ if row[UpdateListModel.SELECTED]:
+ bundles_to_update.append(row[UpdateListModel.BUNDLE_ID])
+ return bundles_to_update
+
+
+class UpdateList(gtk.TreeView):
+
+ def __init__(self, model):
+ list_model = UpdateListModel(model)
+ gtk.TreeView.__init__(self, list_model)
+
+ self.set_reorderable(False)
+ self.set_enable_search(False)
+ self.set_headers_visible(False)
+
+ toggle_renderer = gtk.CellRendererToggle()
+ toggle_renderer.props.activatable = True
+ toggle_renderer.props.xpad = style.DEFAULT_PADDING
+ toggle_renderer.props.indicator_size = style.zoom(26)
+ toggle_renderer.connect('toggled', self.__toggled_cb)
+
+ toggle_column = gtk.TreeViewColumn()
+ toggle_column.pack_start(toggle_renderer)
+ toggle_column.add_attribute(toggle_renderer, 'active',
+ UpdateListModel.SELECTED)
+ self.append_column(toggle_column)
+
+ icon_renderer = CellRendererIcon(self)
+ icon_renderer.props.width = style.STANDARD_ICON_SIZE
+ icon_renderer.props.height = style.STANDARD_ICON_SIZE
+ icon_renderer.props.size = style.STANDARD_ICON_SIZE
+ icon_renderer.props.xpad = style.DEFAULT_PADDING
+ icon_renderer.props.ypad = style.DEFAULT_PADDING
+ icon_renderer.props.stroke_color = style.COLOR_TOOLBAR_GREY.get_svg()
+ icon_renderer.props.fill_color = style.COLOR_TRANSPARENT.get_svg()
+
+ icon_column = gtk.TreeViewColumn()
+ icon_column.pack_start(icon_renderer)
+ icon_column.add_attribute(icon_renderer, 'file-name',
+ UpdateListModel.ICON_FILE_NAME)
+ self.append_column(icon_column)
+
+ text_renderer = gtk.CellRendererText()
+
+ description_column = gtk.TreeViewColumn()
+ description_column.pack_start(text_renderer)
+ description_column.add_attribute(text_renderer, 'markup',
+ UpdateListModel.DESCRIPTION)
+ self.append_column(description_column)
+
+ def __toggled_cb(self, cell_renderer, path):
+ row = self.props.model[path]
+ row[UpdateListModel.SELECTED] = not row[UpdateListModel.SELECTED]
+
+ def refresh(self):
+ pass
+
+
+class UpdateListModel(gtk.ListStore):
+
+ BUNDLE_ID = 0
+ SELECTED = 1
+ ICON_FILE_NAME = 2
+ DESCRIPTION = 3
+ SIZE = 4
+
+ def __init__(self, model):
+ gtk.ListStore.__init__(self, str, bool, str, str, int)
+
+ for bundle_update in model.updates:
+ row = [None] * 5
+ row[self.BUNDLE_ID] = bundle_update.bundle.get_bundle_id()
+ row[self.SELECTED] = True
+ row[self.ICON_FILE_NAME] = bundle_update.bundle.get_icon()
+
+ details = _('From version %(current)d to %(new)s (Size: %(size)s)')
+ details = details % \
+ {'current': bundle_update.bundle.get_activity_version(),
+ 'new': bundle_update.version,
+ 'size': _format_size(bundle_update.size)}
+
+ row[self.DESCRIPTION] = '<b>%s</b>\n%s' % \
+ (bundle_update.bundle.get_name(), details)
+
+ row[self.SIZE] = bundle_update.size
+
+ self.append(row)
+
+
+def _format_size(size):
+ '''
+ Convert a given size in bytes to a nicer better readable unit
+ '''
+ if size == 0:
+ # TRANS: download size is 0
+ return _('None')
+ elif size < 1024:
+ # TRANS: download size of very small updates
+ return _('1 KB')
+ elif size < 1024 * 1024:
+ # TRANS: download size of small updates, e.g. '250 KB'
+ return locale.format(_('%.0f KB'), size / 1024.0)
+ else:
+ # TRANS: download size of updates, e.g. '2.3 MB'
+ return locale.format(_('%.1f MB'), size / 1024.0 / 1024)
diff --git a/shell/extensions/deviceicon/Makefile.am b/shell/extensions/deviceicon/Makefile.am
new file mode 100644
index 0000000..d46ddde
--- /dev/null
+++ b/shell/extensions/deviceicon/Makefile.am
@@ -0,0 +1,9 @@
+sugardir = $(pkgdatadir)/extensions/deviceicon
+
+sugar_PYTHON = \
+ __init__.py \
+ battery.py \
+ network.py \
+ speaker.py \
+ touchpad.py \
+ volume.py
diff --git a/shell/extensions/deviceicon/__init__.py b/shell/extensions/deviceicon/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/shell/extensions/deviceicon/__init__.py
diff --git a/shell/extensions/deviceicon/battery.py b/shell/extensions/deviceicon/battery.py
new file mode 100644
index 0000000..edfcce4
--- /dev/null
+++ b/shell/extensions/deviceicon/battery.py
@@ -0,0 +1,251 @@
+# Copyright (C) 2006-2007, Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import logging
+from gettext import gettext as _
+import gconf
+
+import gobject
+import gtk
+import dbus
+
+from sugar.graphics import style
+from sugar.graphics.icon import get_icon_state
+from sugar.graphics.tray import TrayIcon
+from sugar.graphics.palette import Palette
+from sugar.graphics.xocolor import XoColor
+
+from jarabe.frame.frameinvoker import FrameWidgetInvoker
+
+_ICON_NAME = 'battery'
+
+_STATUS_CHARGING = 0
+_STATUS_DISCHARGING = 1
+_STATUS_FULLY_CHARGED = 2
+_STATUS_NOT_PRESENT = 3
+
+_LEVEL_PROP = 'battery.charge_level.percentage'
+_CHARGING_PROP = 'battery.rechargeable.is_charging'
+_DISCHARGING_PROP = 'battery.rechargeable.is_discharging'
+_PRESENT_PROP = 'battery.present'
+
+class DeviceView(TrayIcon):
+
+ FRAME_POSITION_RELATIVE = 102
+
+ def __init__(self, udi):
+ client = gconf.client_get_default()
+ self._color = XoColor(client.get_string('/desktop/sugar/user/color'))
+
+ TrayIcon.__init__(self, icon_name=_ICON_NAME, xo_color=self._color)
+
+ self.set_palette_invoker(FrameWidgetInvoker(self))
+
+ self._model = DeviceModel(udi)
+ self.palette = BatteryPalette(_('My Battery'))
+ self.palette.set_group_id('frame')
+
+ self._model.connect('notify::level',
+ self._battery_status_changed_cb)
+ self._model.connect('notify::charging',
+ self._battery_status_changed_cb)
+ self._model.connect('notify::discharging',
+ self._battery_status_changed_cb)
+ self._model.connect('notify::present',
+ self._battery_status_changed_cb)
+ self._update_info()
+
+ def _update_info(self):
+ name = _ICON_NAME
+ current_level = self._model.props.level
+ xo_color = self._color
+ badge_name = None
+
+ if not self._model.props.present:
+ status = _STATUS_NOT_PRESENT
+ badge_name = None
+ xo_color = XoColor('%s,%s' % (style.COLOR_WHITE.get_svg(),
+ style.COLOR_WHITE.get_svg()))
+ elif self._model.props.charging:
+ status = _STATUS_CHARGING
+ name += '-charging'
+ xo_color = XoColor('%s,%s' % (style.COLOR_WHITE.get_svg(),
+ style.COLOR_WHITE.get_svg()))
+ elif self._model.props.discharging:
+ status = _STATUS_DISCHARGING
+ if current_level <= 15:
+ badge_name = 'emblem-warning'
+ else:
+ status = _STATUS_FULLY_CHARGED
+
+ self.icon.props.icon_name = get_icon_state(name, current_level, step=-5)
+ self.icon.props.xo_color = xo_color
+ self.icon.props.badge_name = badge_name
+
+ self.palette.set_level(current_level)
+ self.palette.set_status(status)
+
+ def _battery_status_changed_cb(self, pspec, param):
+ self._update_info()
+
+class BatteryPalette(Palette):
+
+ def __init__(self, primary_text):
+ Palette.__init__(self, primary_text)
+
+ self._level = 0
+ self._progress_bar = gtk.ProgressBar()
+ self._progress_bar.set_size_request(
+ style.zoom(style.GRID_CELL_SIZE * 4), -1)
+ self._progress_bar.show()
+ self._status_label = gtk.Label()
+ self._status_label.show()
+
+ vbox = gtk.VBox()
+ vbox.pack_start(self._progress_bar)
+ vbox.pack_start(self._status_label)
+ vbox.show()
+
+ self._progress_widget = vbox
+ self.set_content(self._progress_widget)
+
+ def set_level(self, percent):
+ self._level = percent
+ fraction = percent / 100.0
+ self._progress_bar.set_fraction(fraction)
+
+ def set_status(self, status):
+ current_level = self._level
+ secondary_text = ''
+ status_text = '%s%%' % current_level
+
+ progress_widget = self._progress_widget
+ if status == _STATUS_NOT_PRESENT:
+ secondary_text = _('Removed')
+ progress_widget = None
+ elif status == _STATUS_CHARGING:
+ secondary_text = _('Charging')
+ elif status == _STATUS_DISCHARGING:
+ if current_level <= 15:
+ secondary_text = _('Very little power remaining')
+ else:
+ #TODO: make this less of an wild/educated guess
+ minutes_remaining = int(current_level / 0.59)
+ remaining_hourpart = minutes_remaining / 60
+ remaining_minpart = minutes_remaining % 60
+ secondary_text = _('%(hour)d:%(min).2d remaining') % \
+ {'hour': remaining_hourpart, 'min': remaining_minpart}
+ else:
+ secondary_text = _('Charged')
+ self.set_content(progress_widget)
+
+ self.props.secondary_text = secondary_text
+ self._status_label.set_text(status_text)
+
+class DeviceModel(gobject.GObject):
+ __gproperties__ = {
+ 'level' : (int, None, None, 0, 100, 0,
+ gobject.PARAM_READABLE),
+ 'charging' : (bool, None, None, False,
+ gobject.PARAM_READABLE),
+ 'discharging' : (bool, None, None, False,
+ gobject.PARAM_READABLE),
+ 'present' : (bool, None, None, False,
+ gobject.PARAM_READABLE)
+ }
+
+ def __init__(self, udi):
+ gobject.GObject.__init__(self)
+
+ bus = dbus.Bus(dbus.Bus.TYPE_SYSTEM)
+ proxy = bus.get_object('org.freedesktop.Hal', udi,
+ follow_name_owner_changes=True)
+ self._battery = dbus.Interface(proxy, 'org.freedesktop.Hal.Device')
+ bus.add_signal_receiver(self._battery_changed,
+ 'PropertyModified',
+ 'org.freedesktop.Hal.Device',
+ 'org.freedesktop.Hal',
+ udi)
+
+ self._level = self._get_level()
+ self._charging = self._get_charging()
+ self._discharging = self._get_discharging()
+ self._present = self._get_present()
+
+ def _get_level(self):
+ try:
+ return self._battery.GetProperty(_LEVEL_PROP)
+ except dbus.DBusException:
+ logging.error('Cannot access %s', _LEVEL_PROP)
+ return 0
+
+ def _get_charging(self):
+ try:
+ return self._battery.GetProperty(_CHARGING_PROP)
+ except dbus.DBusException:
+ logging.error('Cannot access %s', _CHARGING_PROP)
+ return False
+
+ def _get_discharging(self):
+ try:
+ return self._battery.GetProperty(_DISCHARGING_PROP)
+ except dbus.DBusException:
+ logging.error('Cannot access %s', _DISCHARGING_PROP)
+ return False
+
+ def _get_present(self):
+ try:
+ return self._battery.GetProperty(_PRESENT_PROP)
+ except dbus.DBusException:
+ logging.error('Cannot access %s', _PRESENT_PROP)
+ return False
+
+ def do_get_property(self, pspec):
+ if pspec.name == 'level':
+ return self._level
+ if pspec.name == 'charging':
+ return self._charging
+ if pspec.name == 'discharging':
+ return self._discharging
+ if pspec.name == 'present':
+ return self._present
+
+ def get_type(self):
+ return 'battery'
+
+ def _battery_changed(self, num_changes, changes_list):
+ for change in changes_list:
+ if change[0] == _LEVEL_PROP:
+ self._level = self._get_level()
+ self.notify('level')
+ elif change[0] == _CHARGING_PROP:
+ self._charging = self._get_charging()
+ self.notify('charging')
+ elif change[0] == _DISCHARGING_PROP:
+ self._discharging = self._get_discharging()
+ self.notify('discharging')
+ elif change[0] == _PRESENT_PROP:
+ self._present = self._get_present()
+ self.notify('present')
+
+def setup(tray):
+ bus = dbus.Bus(dbus.Bus.TYPE_SYSTEM)
+ proxy = bus.get_object('org.freedesktop.Hal',
+ '/org/freedesktop/Hal/Manager')
+ hal_manager = dbus.Interface(proxy, 'org.freedesktop.Hal.Manager')
+
+ for udi in hal_manager.FindDeviceByCapability('battery'):
+ tray.add_device(DeviceView(udi))
diff --git a/shell/extensions/deviceicon/network.py b/shell/extensions/deviceicon/network.py
new file mode 100644
index 0000000..6171f39
--- /dev/null
+++ b/shell/extensions/deviceicon/network.py
@@ -0,0 +1,1006 @@
+#
+# Copyright (C) 2008 One Laptop Per Child
+# Copyright (C) 2009 Tomeu Vizoso, Simon Schampijer
+# Copyright (C) 2009 Paraguay Educa, Martin Abente
+# Copyright (C) 2010 Plan Ceibal, Daniel Castelo
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from gettext import gettext as _
+import logging
+import hashlib
+import socket
+import struct
+import re
+import datetime
+import time
+import gtk
+import glib
+import gobject
+import gconf
+import dbus
+
+from sugar.graphics.icon import get_icon_state
+from sugar.graphics import style
+from sugar.graphics.palette import Palette
+from sugar.graphics.toolbutton import ToolButton
+from sugar.graphics.tray import TrayIcon
+from sugar.graphics.menuitem import MenuItem
+from sugar.graphics.icon import Icon
+from sugar.graphics import xocolor
+from sugar.util import unique_id
+from sugar import profile
+
+from jarabe.model import network
+from jarabe.model.network import Settings
+from jarabe.model.network import IP4Config
+from jarabe.frame.frameinvoker import FrameWidgetInvoker
+from jarabe.view.pulsingicon import PulsingIcon
+
+IP_ADDRESS_TEXT_TEMPLATE = _("IP address: %s")
+
+_NM_SERVICE = 'org.freedesktop.NetworkManager'
+_NM_IFACE = 'org.freedesktop.NetworkManager'
+_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_SERIAL_IFACE = 'org.freedesktop.NetworkManager.Device.Serial'
+_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'
+
+_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, ([]))
+ }
+
+ def __init__(self, primary_text):
+ Palette.__init__(self, label=primary_text)
+
+ self._disconnect_item = None
+
+ self._channel_label = gtk.Label()
+ self._channel_label.props.xalign = 0.0
+ self._channel_label.show()
+
+ self._ip_address_label = gtk.Label()
+
+ self._info = gtk.VBox()
+
+ def _padded(child, xalign=0, yalign=0.5):
+ padder = gtk.Alignment(xalign=xalign, yalign=yalign,
+ xscale=1, yscale=0.33)
+ padder.set_padding(style.DEFAULT_SPACING,
+ style.DEFAULT_SPACING,
+ style.DEFAULT_SPACING,
+ style.DEFAULT_SPACING)
+ padder.add(child)
+ return padder
+
+ self._info.pack_start(_padded(self._channel_label))
+ self._info.pack_start(_padded(self._ip_address_label))
+ self._info.show_all()
+
+ self._disconnect_item = MenuItem(_('Disconnect...'))
+ icon = Icon(icon_size=gtk.ICON_SIZE_MENU, icon_name='media-eject')
+ self._disconnect_item.set_image(icon)
+ self._disconnect_item.connect('activate', self.__disconnect_activate_cb)
+ self.menu.append(self._disconnect_item)
+
+ def set_connecting(self):
+ self.props.secondary_text = _('Connecting...')
+
+ def _set_connected(self, iaddress):
+ self.set_content(self._info)
+ self.props.secondary_text = _('Connected')
+ 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 = ''
+ self._disconnect_item.hide()
+ self.set_content(None)
+
+ def __disconnect_activate_cb(self, menuitem):
+ self.emit('deactivate-connection')
+
+ def _set_frequency(self, frequency):
+ channel = network.frequency_to_channel(frequency)
+ 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):
+ if ip_address is not None:
+ ip_address_text = IP_ADDRESS_TEXT_TEMPLATE % \
+ socket.inet_ntoa(struct.pack('I', ip_address))
+ else:
+ ip_address_text = ""
+ self._ip_address_label.set_text(ip_address_text)
+
+
+class WiredPalette(Palette):
+ __gtype_name__ = 'SugarWiredPalette'
+
+ def __init__(self):
+ Palette.__init__(self, label=_('Wired Network'))
+
+ self._speed_label = gtk.Label()
+ self._speed_label.props.xalign = 0.0
+ self._speed_label.show()
+
+ self._ip_address_label = gtk.Label()
+
+ self._info = gtk.VBox()
+
+ def _padded(child, xalign=0, yalign=0.5):
+ padder = gtk.Alignment(xalign=xalign, yalign=yalign,
+ xscale=1, yscale=0.33)
+ padder.set_padding(style.DEFAULT_SPACING,
+ style.DEFAULT_SPACING,
+ style.DEFAULT_SPACING,
+ style.DEFAULT_SPACING)
+ padder.add(child)
+ return padder
+
+ self._info.pack_start(_padded(self._speed_label))
+ self._info.pack_start(_padded(self._ip_address_label))
+ self._info.show_all()
+
+ self.set_content(self._info)
+ self.props.secondary_text = _('Connected')
+
+ def set_connected(self, speed, iaddress):
+ self._speed_label.set_text('%s: %d Mb/s' % (_('Speed'), speed))
+ self._set_ip_address(iaddress)
+
+ def _inet_ntoa(self, iaddress):
+ address = ['%s' % ((iaddress >> i) % 256) for i in [0, 8, 16, 24]]
+ return ".".join(address)
+
+ def _set_ip_address(self, ip_address):
+ if ip_address is not None:
+ ip_address_text = IP_ADDRESS_TEXT_TEMPLATE % \
+ socket.inet_ntoa(struct.pack('I', ip_address))
+ else:
+ 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_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_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):
+
+ FRAME_POSITION_RELATIVE = 302
+
+ def __init__(self, device):
+ ToolButton.__init__(self)
+
+ self._bus = dbus.SystemBus()
+ self._device = device
+ self._device_props = None
+ self._flags = 0
+ self._name = ''
+ self._mode = network.NM_802_11_MODE_UNKNOWN
+ self._strength = 0
+ self._frequency = 0
+ self._device_state = None
+ self._color = None
+ self._active_ap_op = None
+
+ self._icon = PulsingIcon()
+ self._icon.props.icon_name = get_icon_state('network-wireless', 0)
+ self._inactive_color = xocolor.XoColor( \
+ "%s,%s" % (style.COLOR_BUTTON_GREY.get_svg(),
+ style.COLOR_TRANSPARENT.get_svg()))
+ self._icon.props.pulse_color = self._inactive_color
+ self._icon.props.base_color = self._inactive_color
+
+ self.set_icon_widget(self._icon)
+ self._icon.show()
+
+ self.set_palette_invoker(FrameWidgetInvoker(self))
+ self._palette = WirelessPalette(self._name)
+ self._palette.connect('deactivate-connection',
+ self.__deactivate_connection_cb)
+ self.set_palette(self._palette)
+ self._palette.set_group_id('frame')
+
+ self._device_props = dbus.Interface(self._device,
+ 'org.freedesktop.DBus.Properties')
+ self._device_props.GetAll(_NM_DEVICE_IFACE, byte_arrays=True,
+ reply_handler=self.__get_device_props_reply_cb,
+ error_handler=self.__get_device_props_error_cb)
+
+ self._device_props.Get(_NM_WIRELESS_IFACE, 'ActiveAccessPoint',
+ reply_handler=self.__get_active_ap_reply_cb,
+ error_handler=self.__get_active_ap_error_cb)
+
+ self._bus.add_signal_receiver(self.__state_changed_cb,
+ signal_name='StateChanged',
+ path=self._device.object_path,
+ dbus_interface=_NM_DEVICE_IFACE)
+
+ def disconnect(self):
+ self._bus.remove_signal_receiver(self.__state_changed_cb,
+ signal_name='StateChanged',
+ path=self._device.object_path,
+ dbus_interface=_NM_DEVICE_IFACE)
+
+ def __get_device_props_reply_cb(self, properties):
+ if 'State' in properties:
+ self._device_state = properties['State']
+ self._update_state()
+
+ def __get_device_props_error_cb(self, err):
+ logging.error('Error getting the device properties: %s', err)
+
+ def __get_active_ap_reply_cb(self, active_ap_op):
+ if self._active_ap_op != active_ap_op:
+ if self._active_ap_op is not None:
+ self._bus.remove_signal_receiver(
+ self.__ap_properties_changed_cb,
+ signal_name='PropertiesChanged',
+ path=self._active_ap_op,
+ dbus_interface=_NM_ACCESSPOINT_IFACE)
+ if active_ap_op == '/':
+ self._active_ap_op = None
+ 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.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)
+
+ self._bus.add_signal_receiver(self.__ap_properties_changed_cb,
+ signal_name='PropertiesChanged',
+ path=self._active_ap_op,
+ dbus_interface=_NM_ACCESSPOINT_IFACE)
+
+ def __get_active_ap_error_cb(self, err):
+ logging.error('Error getting the active access point: %s', err)
+
+ def __state_changed_cb(self, new_state, old_state, reason):
+ self._device_state = new_state
+ self._update_state()
+ self._device_props.Get(_NM_WIRELESS_IFACE, 'ActiveAccessPoint',
+ reply_handler=self.__get_active_ap_reply_cb,
+ error_handler=self.__get_active_ap_error_cb)
+
+ def __ap_properties_changed_cb(self, properties):
+ self._update_properties(properties)
+
+ def _update_properties(self, properties):
+ if 'Mode' in properties:
+ self._mode = properties['Mode']
+ self._color = None
+ if 'Ssid' in properties:
+ self._name = properties['Ssid']
+ self._color = None
+ if 'Strength' in properties:
+ self._strength = properties['Strength']
+ if 'Flags' in properties:
+ self._flags = properties['Flags']
+ if 'Frequency' in properties:
+ self._frequency = properties['Frequency']
+
+ if self._color == None:
+ if self._mode == network.NM_802_11_MODE_ADHOC and \
+ network.is_sugar_adhoc_network(self._name):
+ self._color = profile.get_color()
+ else:
+ sha_hash = hashlib.sha1()
+ data = self._name + hex(self._flags)
+ sha_hash.update(data)
+ digest = hash(sha_hash.digest())
+ index = digest % len(xocolor.colors)
+
+ self._color = xocolor.XoColor('%s,%s' %
+ (xocolor.colors[index][0],
+ xocolor.colors[index][1]))
+ self._update()
+
+ def __get_all_ap_props_reply_cb(self, properties):
+ self._update_properties(properties)
+
+ def __get_all_ap_props_error_cb(self, err):
+ logging.error('Error getting the access point properties: %s', err)
+
+ def _update(self):
+ if self._flags == network.NM_802_11_AP_FLAGS_PRIVACY:
+ self._icon.props.badge_name = "emblem-locked"
+ else:
+ self._icon.props.badge_name = None
+
+ self._palette.props.primary_text = glib.markup_escape_text(self._name)
+
+ self._update_state()
+ self._update_color()
+
+ def _update_state(self):
+ if self._active_ap_op is not None:
+ state = self._device_state
+ else:
+ state = network.DEVICE_STATE_UNKNOWN
+
+ if self._mode != network.NM_802_11_MODE_ADHOC and \
+ network.is_sugar_adhoc_network(self._name) == False:
+ if state == network.DEVICE_STATE_ACTIVATED:
+ icon_name = '%s-connected' % 'network-wireless'
+ else:
+ icon_name = 'network-wireless'
+
+ icon_name = get_icon_state(icon_name, self._strength)
+ if icon_name:
+ self._icon.props.icon_name = icon_name
+ else:
+ channel = network.frequency_to_channel(self._frequency)
+ if state == network.DEVICE_STATE_ACTIVATED:
+ self._icon.props.icon_name = 'network-adhoc-%s-connected' \
+ % channel
+ else:
+ self._icon.props.icon_name = 'network-adhoc-%s' % channel
+ self._icon.props.base_color = profile.get_color()
+
+ if state == network.DEVICE_STATE_PREPARE or \
+ state == network.DEVICE_STATE_CONFIG or \
+ state == network.DEVICE_STATE_NEED_AUTH or \
+ state == network.DEVICE_STATE_IP_CONFIG:
+ 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_frequency(self._frequency, address)
+ self._icon.props.pulsing = False
+ else:
+ self._icon.props.badge_name = None
+ self._icon.props.pulsing = False
+ self._icon.props.pulse_color = self._inactive_color
+ self._icon.props.base_color = self._inactive_color
+ self._palette.set_disconnected()
+
+ def _update_color(self):
+ self._icon.props.base_color = self._color
+
+ def __deactivate_connection_cb(self, palette, data=None):
+ if self._active_ap_op is not None:
+ obj = self._bus.get_object(_NM_SERVICE, _NM_PATH)
+ netmgr = dbus.Interface(obj, _NM_IFACE)
+ netmgr_props = dbus.Interface(
+ netmgr, 'org.freedesktop.DBus.Properties')
+ active_connections_o = netmgr_props.Get(_NM_IFACE,
+ 'ActiveConnections')
+
+ for conn_o in active_connections_o:
+ obj = self._bus.get_object(_NM_IFACE, conn_o)
+ props = dbus.Interface(obj, 'org.freedesktop.DBus.Properties')
+ ap_op = props.Get(_NM_ACTIVE_CONN_IFACE, 'SpecificObject')
+ if ap_op == self._active_ap_op:
+ netmgr.DeactivateConnection(conn_o)
+ break
+
+ def __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):
+ channel = str(self._channel)
+ text = _("Mesh Network %s") % glib.markup_escape_text(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'
+ FRAME_POSITION_RELATIVE = 301
+
+ def __init__(self, speed, address):
+ 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.set_palette_invoker(FrameWidgetInvoker(self))
+ self._palette = WiredPalette()
+ self.set_palette(self._palette)
+ self._palette.set_group_id('frame')
+ 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._palette = None
+ self.set_palette_invoker(FrameWidgetInvoker(self))
+
+ 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_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)
+
+ self._palette = palette
+
+ 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('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 is 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_seconds( \
+ 1, self.__connection_timecount_cb)
+ self._update_stats(0, 0)
+ self._update_connection_time()
+ self._palette.info_box.show()
+
+ elif state is network.DEVICE_STATE_DISCONNECTED:
+ gsm_state = _GSM_STATE_DISCONNECTED
+ self._connection_timestamp = 0
+ if self._connection_time_handler is not None:
+ gobject.source_remove(self._connection_time_handler)
+ self._palette.info_box.hide()
+
+ 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_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)
+
+ def disconnect(self):
+ self._device_view.disconnect()
+ self._tray.remove_device(self._device_view)
+ del self._device_view
+ 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()
+ self._device = device
+ self._device_state = None
+ self._device_view = None
+ self._tray = tray
+
+ props = dbus.Interface(self._device, 'org.freedesktop.DBus.Properties')
+ 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 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 __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 == network.DEVICE_STATE_ACTIVATED:
+ props = dbus.Interface(self._device,
+ 'org.freedesktop.DBus.Properties')
+ address = props.Get(_NM_DEVICE_IFACE, 'Ip4Address')
+ speed = props.Get(_NM_WIRED_IFACE, 'Speed')
+ self._device_view = WiredDeviceView(speed, address)
+ self._tray.add_device(self._device_view)
+ else:
+ if self._device_view is not None:
+ self._tray.remove_device(self._device_view)
+ 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):
+ self._bus = dbus.SystemBus()
+ self._devices = {}
+ self._netmgr = None
+ self._tray = tray
+
+ try:
+ obj = self._bus.get_object(_NM_SERVICE, _NM_PATH)
+ self._netmgr = dbus.Interface(obj, _NM_IFACE)
+ except dbus.DBusException:
+ logging.error('%s service not available', _NM_SERVICE)
+ return
+
+ self._netmgr.GetDevices(reply_handler=self.__get_devices_reply_cb,
+ error_handler=self.__get_devices_error_cb)
+
+ self._bus.add_signal_receiver(self.__device_added_cb,
+ signal_name='DeviceAdded',
+ dbus_interface=_NM_IFACE)
+ self._bus.add_signal_receiver(self.__device_removed_cb,
+ signal_name='DeviceRemoved',
+ dbus_interface=_NM_IFACE)
+
+ def __get_devices_reply_cb(self, devices):
+ for device_op in devices:
+ self._check_device(device_op)
+
+ def __get_devices_error_cb(self, err):
+ logging.error('Failed to get devices: %s', err)
+
+ 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')
+
+ device_type = props.Get(_NM_DEVICE_IFACE, 'DeviceType')
+ if device_type == network.DEVICE_TYPE_802_3_ETHERNET:
+ device = WiredDeviceObserver(nm_device, self._tray)
+ self._devices[device_op] = device
+ elif device_type == 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)
+
+ def __device_removed_cb(self, device_op):
+ if device_op in self._devices:
+ device = self._devices[device_op]
+ device.disconnect()
+ del self._devices[device_op]
+
+def setup(tray):
+ device_observer = NetworkManagerObserver(tray)
diff --git a/shell/extensions/deviceicon/speaker.py b/shell/extensions/deviceicon/speaker.py
new file mode 100644
index 0000000..3a54464
--- /dev/null
+++ b/shell/extensions/deviceicon/speaker.py
@@ -0,0 +1,216 @@
+# Copyright (C) 2008 Martin Dengler
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from gettext import gettext as _
+import gconf
+
+import gobject
+import gtk
+
+from sugar.graphics import style
+from sugar.graphics.icon import get_icon_state, Icon
+from sugar.graphics.menuitem import MenuItem
+from sugar.graphics.tray import TrayIcon
+from sugar.graphics.palette import Palette
+from sugar.graphics.xocolor import XoColor
+
+from jarabe.frame.frameinvoker import FrameWidgetInvoker
+from jarabe.model import sound
+
+_ICON_NAME = 'speaker'
+
+class DeviceView(TrayIcon):
+
+ FRAME_POSITION_RELATIVE = 103
+
+ def __init__(self):
+ client = gconf.client_get_default()
+ self._color = XoColor(client.get_string('/desktop/sugar/user/color'))
+
+ TrayIcon.__init__(self, icon_name=_ICON_NAME, xo_color=self._color)
+
+ self.set_palette_invoker(FrameWidgetInvoker(self))
+
+ self._model = DeviceModel()
+ self._model.connect('notify::level', self.__speaker_status_changed_cb)
+ self._model.connect('notify::muted', self.__speaker_status_changed_cb)
+
+ self.connect('expose-event', self.__expose_event_cb)
+
+ self._icon_widget.connect('button-release-event',
+ self.__button_release_event_cb)
+
+ self._update_info()
+
+ def create_palette(self):
+ palette = SpeakerPalette(_('My Speakers'), model=self._model)
+ palette.set_group_id('frame')
+ return palette
+
+ def _update_info(self):
+ name = _ICON_NAME
+ current_level = self._model.props.level
+ xo_color = self._color
+
+ if self._model.props.muted:
+ name += '-muted'
+ xo_color = XoColor('%s,%s' % (style.COLOR_WHITE.get_svg(),
+ style.COLOR_WHITE.get_svg()))
+
+ self.icon.props.icon_name = get_icon_state(name, current_level, step=-1)
+ self.icon.props.xo_color = xo_color
+
+ def __button_release_event_cb(self, widget, event):
+ if event.button == 1:
+ self._model.props.muted = not self._model.props.muted
+ return True
+ else:
+ return False
+
+ def __expose_event_cb(self, *args):
+ self._update_info()
+
+ def __speaker_status_changed_cb(self, pspec_, param_):
+ self._update_info()
+
+class SpeakerPalette(Palette):
+
+ def __init__(self, primary_text, model):
+ Palette.__init__(self, label=primary_text)
+
+ self._model = model
+
+ vbox = gtk.VBox()
+ self.set_content(vbox)
+ vbox.show()
+
+ vol_step = sound.VOLUME_STEP
+ self._adjustment = gtk.Adjustment(value=self._model.props.level,
+ lower=0,
+ upper=100 + vol_step,
+ step_incr=vol_step,
+ page_incr=vol_step,
+ page_size=vol_step)
+ self._hscale = gtk.HScale(self._adjustment)
+ self._hscale.set_digits(0)
+ self._hscale.set_draw_value(False)
+ vbox.add(self._hscale)
+ self._hscale.show()
+
+ self._mute_item = MenuItem('')
+ self._mute_icon = Icon(icon_size=gtk.ICON_SIZE_MENU)
+ self._mute_item.set_image(self._mute_icon)
+ self.menu.append(self._mute_item)
+ self._mute_item.show()
+
+ self._adjustment_handler_id = \
+ self._adjustment.connect('value_changed',
+ self.__adjustment_changed_cb)
+
+ self._model_notify_level_handler_id = \
+ self._model.connect('notify::level', self.__level_changed_cb)
+ self._model.connect('notify::muted', self.__muted_changed_cb)
+
+ self._mute_item.connect('activate', self.__mute_activate_cb)
+
+ self.connect('popup', self.__popup_cb)
+
+ def _update_muted(self):
+ if self._model.props.muted:
+ mute_item_text = _('Unmute')
+ mute_item_icon_name = 'dialog-ok'
+ else:
+ mute_item_text = _('Mute')
+ mute_item_icon_name = 'dialog-cancel'
+ self._mute_item.get_child().set_text(mute_item_text)
+ self._mute_icon.props.icon_name = mute_item_icon_name
+
+ def _update_level(self):
+ if self._adjustment.value != self._model.props.level:
+ self._adjustment.handler_block(self._adjustment_handler_id)
+ try:
+ self._adjustment.value = self._model.props.level
+ finally:
+ self._adjustment.handler_unblock(self._adjustment_handler_id)
+
+ def __adjustment_changed_cb(self, adj_):
+ self._model.handler_block(self._model_notify_level_handler_id)
+ try:
+ self._model.props.level = self._adjustment.value
+ finally:
+ self._model.handler_unblock(self._model_notify_level_handler_id)
+ self._model.props.muted = self._adjustment.value == 0
+
+ def __level_changed_cb(self, pspec_, param_):
+ self._update_level()
+
+ def __mute_activate_cb(self, menuitem_):
+ self._model.props.muted = not self._model.props.muted
+
+ def __muted_changed_cb(self, pspec_, param_):
+ self._update_muted()
+
+ def __popup_cb(self, palette_):
+ self._update_level()
+ self._update_muted()
+
+class DeviceModel(gobject.GObject):
+ __gproperties__ = {
+ 'level' : (int, None, None, 0, 100, 0, gobject.PARAM_READWRITE),
+ 'muted' : (bool, None, None, False, gobject.PARAM_READWRITE),
+ }
+
+ def __init__(self):
+ gobject.GObject.__init__(self)
+
+ sound.muted_changed.connect(self.__muted_changed_cb)
+ sound.volume_changed.connect(self.__volume_changed_cb)
+
+ def __muted_changed_cb(self, **kwargs):
+ self.notify('muted')
+
+ def __volume_changed_cb(self, **kwargs):
+ self.notify('level')
+
+ def _get_level(self):
+ return sound.get_volume()
+
+ def _set_level(self, new_volume):
+ sound.set_volume(new_volume)
+
+ def _get_muted(self):
+ return sound.get_muted()
+
+ def _set_muted(self, mute):
+ sound.set_muted(mute)
+
+ def get_type(self):
+ return 'speaker'
+
+ def do_get_property(self, pspec):
+ if pspec.name == "level":
+ return self._get_level()
+ elif pspec.name == "muted":
+ return self._get_muted()
+
+ def do_set_property(self, pspec, value):
+ if pspec.name == "level":
+ self._set_level(value)
+ elif pspec.name == "muted":
+ self._set_muted(value)
+
+def setup(tray):
+ tray.add_device(DeviceView())
diff --git a/shell/extensions/deviceicon/touchpad.py b/shell/extensions/deviceicon/touchpad.py
new file mode 100644
index 0000000..d9521c2
--- /dev/null
+++ b/shell/extensions/deviceicon/touchpad.py
@@ -0,0 +1,132 @@
+# Copyright (C) 2010, Walter Bender, Sugar Labs
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+from gettext import gettext as _
+import os
+
+import gtk
+import gconf
+
+import logging
+
+from sugar.graphics.tray import TrayIcon
+from sugar.graphics.xocolor import XoColor
+from sugar.graphics.palette import Palette
+from sugar.graphics import style
+
+from jarabe.frame.frameinvoker import FrameWidgetInvoker
+
+TOUCHPAD_MODE_CAPACITIVE = 'capacitive'
+TOUCHPAD_MODE_RESISTIVE = 'resistive'
+TOUCHPAD_MODES = [TOUCHPAD_MODE_CAPACITIVE, TOUCHPAD_MODE_RESISTIVE]
+STATUS_TEXT = {TOUCHPAD_MODE_CAPACITIVE: _('finger'),
+ TOUCHPAD_MODE_RESISTIVE: _('stylus')}
+STATUS_ICON = {TOUCHPAD_MODE_CAPACITIVE: 'touchpad-' + TOUCHPAD_MODE_CAPACITIVE,
+ TOUCHPAD_MODE_RESISTIVE: 'touchpad-' + TOUCHPAD_MODE_RESISTIVE}
+# NODE_PATH is used to communicate with the touchpad device.
+NODE_PATH = '/sys/devices/platform/i8042/serio1/ptmode'
+
+
+class DeviceView(TrayIcon):
+ """ Manage the touchpad mode from the device palette on the Frame. """
+
+ FRAME_POSITION_RELATIVE = 500
+
+ def __init__(self):
+ """ Create the icon that represents the touchpad. """
+ icon_name = STATUS_ICON[_read_touchpad_mode()]
+
+ client = gconf.client_get_default()
+ color = XoColor(client.get_string('/desktop/sugar/user/color'))
+ TrayIcon.__init__(self, icon_name=icon_name, xo_color=color)
+
+ self.set_palette_invoker(FrameWidgetInvoker(self))
+ self.connect('button-release-event', self.__button_release_event_cb)
+
+ def create_palette(self):
+ """ Create a palette for this icon; called by the Sugar framework
+ when a palette needs to be displayed. """
+ self.palette = ResourcePalette(_('My touchpad'), self.icon)
+ self.palette.set_group_id('frame')
+ return self.palette
+
+ def __button_release_event_cb(self, widget, event):
+ """ Callback for button release event; used to invoke touchpad-mode
+ change. """
+ self.palette.toggle_mode()
+ return True
+
+
+class ResourcePalette(Palette):
+ """ Palette attached to the decive icon that represents the touchpas. """
+
+ def __init__(self, primary_text, icon):
+ """ Create the palette and initilize with current touchpad status. """
+ Palette.__init__(self, label=primary_text)
+
+ self._icon = icon
+
+ vbox = gtk.VBox()
+ self.set_content(vbox)
+
+ self._status_text = gtk.Label()
+ vbox.pack_start(self._status_text, padding=style.DEFAULT_PADDING)
+ self._status_text.show()
+
+ vbox.show()
+
+ self._mode = _read_touchpad_mode()
+ self._update()
+
+ def _update(self):
+ """ Update the label and icon based on the current mode. """
+ self._status_text.set_label(STATUS_TEXT[self._mode])
+ self._icon.props.icon_name = STATUS_ICON[self._mode]
+
+ def toggle_mode(self):
+ """ Toggle the touchpad mode. """
+ self._mode = TOUCHPAD_MODES[1 - TOUCHPAD_MODES.index(self._mode)]
+ _write_touchpad_mode(self._mode)
+ self._update()
+
+
+def setup(tray):
+ """ Initialize the devic icon; called by the shell when initializing the
+ Frame. """
+ if os.path.exists(NODE_PATH):
+ tray.add_device(DeviceView())
+ _write_touchpad_mode(TOUCHPAD_MODE_CAPACITIVE)
+
+
+def _read_touchpad_mode():
+ """ Read the touchpad mode from the node path. """
+ node_file_handle = open(NODE_PATH, 'r')
+ text = node_file_handle.read()
+ node_file_handle.close()
+
+ return TOUCHPAD_MODES[int(text[0])]
+
+
+def _write_touchpad_mode(touchpad):
+ """ Write the touchpad mode to the node path. """
+ try:
+ node_file_handle = open(NODE_PATH, 'w')
+ except IOError, e:
+ logging.error('Error opening %s for writing: %s', NODE_PATH, e)
+ return
+ node_file_handle.write(str(TOUCHPAD_MODES.index(touchpad)))
+ node_file_handle.close()
diff --git a/shell/extensions/deviceicon/volume.py b/shell/extensions/deviceicon/volume.py
new file mode 100644
index 0000000..e7f62a2
--- /dev/null
+++ b/shell/extensions/deviceicon/volume.py
@@ -0,0 +1,121 @@
+# Copyright (C) 2008 One Laptop Per Child
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import logging
+
+import gobject
+import gio
+import gtk
+import gconf
+
+from sugar.graphics.tray import TrayIcon
+from sugar.graphics.xocolor import XoColor
+
+from jarabe.journal import journalactivity
+from jarabe.view.palettes import VolumePalette
+from jarabe.frame.frameinvoker import FrameWidgetInvoker
+
+_icons = {}
+
+class DeviceView(TrayIcon):
+
+ FRAME_POSITION_RELATIVE = 500
+
+ def __init__(self, mount):
+
+ self._mount = mount
+
+ icon_name = None
+ icon_theme = gtk.icon_theme_get_default()
+ for icon_name in self._mount.get_icon().props.names:
+ icon_info = icon_theme.lookup_icon(icon_name,
+ gtk.ICON_SIZE_LARGE_TOOLBAR, 0)
+ if icon_info is not None:
+ break
+
+ if icon_name is None:
+ icon_name = 'drive'
+
+ # TODO: retrieve the colors from the owner of the device
+ client = gconf.client_get_default()
+ color = XoColor(client.get_string('/desktop/sugar/user/color'))
+
+ TrayIcon.__init__(self, icon_name=icon_name, xo_color=color)
+
+ self.set_palette_invoker(FrameWidgetInvoker(self))
+
+ self.connect('button-release-event', self.__button_release_event_cb)
+
+ def create_palette(self):
+ palette = VolumePalette(self._mount)
+ palette.set_group_id('frame')
+ return palette
+
+ def __button_release_event_cb(self, widget, event):
+ journal = journalactivity.get_journal()
+ journal.set_active_volume(self._mount)
+ journal.reveal()
+ return True
+
+def setup(tray):
+ gobject.idle_add(_setup_volumes, tray)
+
+def _setup_volumes(tray):
+ volume_monitor = gio.volume_monitor_get()
+
+ for volume in volume_monitor.get_volumes():
+ _mount(volume, tray)
+
+ for mount in volume_monitor.get_mounts():
+ _add_device(mount, tray)
+
+ volume_monitor.connect('volume-added', _volume_added_cb, tray)
+ volume_monitor.connect('mount-added', _mount_added_cb, tray)
+ volume_monitor.connect('mount-removed', _mount_removed_cb, tray)
+
+def _volume_added_cb(volume_monitor, volume, tray):
+ _mount(volume, tray)
+
+def _mount(volume, tray):
+ # Follow Nautilus behaviour here
+ # since it has the same issue with removable device
+ # and it would be good to not invent our own workflow
+ if hasattr(volume, 'should_automount') and not volume.should_automount():
+ return
+
+ #TODO: should be done by some other process, like gvfs-hal-volume-monitor
+ #TODO: use volume.should_automount() when it gets into pygtk
+ if volume.get_mount() is None and volume.can_mount():
+ #TODO: pass None as mount_operation, or better, SugarMountOperation
+ volume.mount(gtk.MountOperation(tray.get_toplevel()), _mount_cb)
+
+def _mount_cb(volume, result):
+ logging.debug('_mount_cb %r %r', volume, result)
+ volume.mount_finish(result)
+
+def _mount_added_cb(volume_monitor, mount, tray):
+ _add_device(mount, tray)
+
+def _mount_removed_cb(volume_monitor, mount, tray):
+ icon = _icons[mount]
+ tray.remove_device(icon)
+ del _icons[mount]
+
+def _add_device(mount, tray):
+ icon = DeviceView(mount)
+ _icons[mount] = icon
+ tray.add_device(icon)
+
diff --git a/shell/extensions/globalkey/Makefile.am b/shell/extensions/globalkey/Makefile.am
new file mode 100644
index 0000000..69afac2
--- /dev/null
+++ b/shell/extensions/globalkey/Makefile.am
@@ -0,0 +1,6 @@
+sugardir = $(pkgdatadir)/extensions/globalkey
+
+sugar_PYTHON = \
+ __init__.py \
+ screenshot.py \
+ viewsource.py
diff --git a/shell/extensions/globalkey/__init__.py b/shell/extensions/globalkey/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/shell/extensions/globalkey/__init__.py
diff --git a/shell/extensions/globalkey/screenshot.py b/shell/extensions/globalkey/screenshot.py
new file mode 100644
index 0000000..8b4d4c2
--- /dev/null
+++ b/shell/extensions/globalkey/screenshot.py
@@ -0,0 +1,100 @@
+# Copyright (C) 2008 One Laptop Per Child
+# Copyright (C) 2009 Simon Schampijer, James Zaki
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import tempfile
+import time
+from gettext import gettext as _
+
+import gtk
+import gconf
+import dbus
+
+from sugar.datastore import datastore
+from sugar.graphics import style
+from sugar import env
+from jarabe.model import shell
+
+BOUND_KEYS = ['<alt>1', 'Print']
+
+def handle_key_press(key):
+ tmp_dir = os.path.join(env.get_profile_path(), 'data')
+ fd, file_path = tempfile.mkstemp(dir=tmp_dir)
+ os.close(fd)
+
+ window = gtk.gdk.get_default_root_window()
+ width, height = window.get_size()
+ x_orig, y_orig = window.get_origin()
+
+ screenshot = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, has_alpha=False,
+ bits_per_sample=8, width=width,
+ height=height)
+ screenshot.get_from_drawable(window, window.get_colormap(), x_orig,
+ y_orig, 0, 0, width, height)
+ screenshot.save(file_path, "png")
+
+ client = gconf.client_get_default()
+ color = client.get_string('/desktop/sugar/user/color')
+
+ content_title = None
+ shell_model = shell.get_model()
+ zoom_level = shell_model.zoom_level
+
+ # TRANS: Nouns of what a screenshot contains
+ if zoom_level == shell_model.ZOOM_MESH:
+ content_title = _('Mesh')
+ elif zoom_level == shell_model.ZOOM_GROUP:
+ content_title = _('Group')
+ elif zoom_level == shell_model.ZOOM_HOME:
+ content_title = _('Home')
+ elif zoom_level == shell_model.ZOOM_ACTIVITY:
+ activity = shell_model.get_active_activity()
+ if activity != None:
+ content_title = activity.get_title()
+ if content_title == None:
+ content_title = _('Activity')
+
+ if content_title is None:
+ title = _('Screenshot')
+ else:
+ title = _('Screenshot of \"%s\"') % content_title
+
+ jobject = datastore.create()
+ try:
+ jobject.metadata['title'] = title
+ jobject.metadata['keep'] = '0'
+ jobject.metadata['buddies'] = ''
+ jobject.metadata['preview'] = _get_preview_data(screenshot)
+ jobject.metadata['icon-color'] = color
+ jobject.metadata['mime_type'] = 'image/png'
+ jobject.file_path = file_path
+ datastore.write(jobject, transfer_ownership=True)
+ finally:
+ jobject.destroy()
+ del jobject
+
+def _get_preview_data(screenshot):
+ preview = screenshot.scale_simple(style.zoom(300), style.zoom(225),
+ gtk.gdk.INTERP_BILINEAR)
+ preview_data = []
+ def save_func(buf, data):
+ data.append(buf)
+
+ preview.save_to_callback(save_func, 'png', user_data=preview_data)
+
+ return dbus.ByteArray(''.join(preview_data))
+
diff --git a/shell/extensions/globalkey/viewsource.py b/shell/extensions/globalkey/viewsource.py
new file mode 100644
index 0000000..df3cd9e
--- /dev/null
+++ b/shell/extensions/globalkey/viewsource.py
@@ -0,0 +1,27 @@
+# Copyright (C) 2008 One Laptop Per Child
+# Copyright (C) 2009 Tomeu Vizoso, Simon Schampijer
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from jarabe.view.viewsource import setup_view_source
+from jarabe.model import shell
+
+BOUND_KEYS = ['0xEC', '<alt><shift>v']
+
+def handle_key_press(key):
+ shell_model = shell.get_model()
+ activity = shell_model.get_active_activity()
+
+ setup_view_source(activity)