diff options
author | Santiago Collazo <scollazo@activitycentral.com> | 2012-10-22 18:00:22 (GMT) |
---|---|---|
committer | Santiago Collazo <scollazo@activitycentral.com> | 2012-10-22 18:00:22 (GMT) |
commit | ead5abcab3c163ef4e26d254e68cffbc6e164e76 (patch) | |
tree | 6f117e0b74b6fa98a7ed54b33af04d3c1812843b | |
parent | 19fb32c44022f3d2acda9de587d8c1014748cd95 (diff) | |
parent | 2d75ff7d59d35a1db274ef36b29448ebcd4dddf3 (diff) |
Merge remote-tracking branch 'aklis/integration' into devel
26 files changed, 1088 insertions, 68 deletions
diff --git a/bin/sugar-session b/bin/sugar-session index 8475b9b..05493f2 100755 --- a/bin/sugar-session +++ b/bin/sugar-session @@ -263,6 +263,7 @@ def bootstrap(): GObject.idle_add(setup_notification_service_cb) GObject.idle_add(setup_file_transfer_cb) GObject.idle_add(show_software_updates_cb) + GObject.idle_add(setup_accessibility_cb) if _USE_XKL: GObject.idle_add(setup_keyboard_cb) @@ -283,6 +284,11 @@ def set_theme(): settings.set_property('gtk-theme-name', sugar_theme) settings.set_property('gtk-icon-theme-name', 'sugar') +def setup_accessibility_cb(): + from jarabe.model import accessibility + accessibility_manager = accessibility.AccessibilityManager() + accessibility_manager.setup_accessibility() + def main(): try: diff --git a/bin/sugar.in b/bin/sugar.in index 428cbf2..c889d6a 100644 --- a/bin/sugar.in +++ b/bin/sugar.in @@ -45,7 +45,18 @@ if test -z "$SUGAR_SCALING"; then export SUGAR_SCALING=100 fi -export GTK2_RC_FILES="@prefix@/share/sugar/data/sugar-$SUGAR_SCALING.gtkrc" +if gconftool-2 --dir-exists=/desktop/sugar/interface ; then + THEME="$(gconftool-2 --get /desktop/sugar/interface/gtk_theme)" + if [ "$THEME" = 'sugar-contrast' ]; then + GTK2_THEME="sugar-$SUGAR_SCALING-contrast" + else + GTK2_THEME="sugar-$SUGAR_SCALING" + fi +else + GTK2_THEME="sugar-$SUGAR_SCALING" +fi + +export GTK2_RC_FILES="@prefix@/share/sugar/data/$GTK2_THEME.gtkrc" # Needed for executing wpa_passphrase export PATH="$PATH":/sbin:/usr/sbin @@ -62,6 +73,27 @@ export LANGUAGE="${LANGUAGE:-${LANG}}" # Set Sugar's telepathy accounts directory export MC_ACCOUNT_DIR=$HOME/.sugar/$SUGAR_PROFILE/accounts +# Check if the keyring exists and create a default +# unencrypted keyring (OLPC #10290) +keyring_path=$HOME/.gnome2/keyrings +if [ ! -e $keyring_path/login.keyring ] && + [ ! -e $keyring_path/default.keyring ]; then + mkdir -p $keyring_path + echo 'default' > $keyring_path/default + TIME=$(/bin/date +%s) + cat >> $keyring_path/default.keyring.tmp <<EOF +[keyring] +display-name=default +ctime=$TIME +mtime=$TIME +lock-on-idle=false +lock-timeout=0 +EOF + +mv $keyring_path/default.keyring{.tmp,} +fi + + # Source language settings and debug definitions if [ -f ~/.i18n ]; then . ~/.i18n @@ -70,7 +102,15 @@ if [ -f ~/.sugar/debug ]; then . ~/.sugar/debug fi -echo Xcursor.theme: sugar | xrdb -merge + +if gconftool-2 --dir-exists=/desktop/sugar/peripherals/mouse ; then + CURSOR_THEME="$(gconftool-2 --get /desktop/sugar/peripherals/mouse/cursor_theme)" +else + CURSOR_THEME="sugar" +fi + + +echo Xcursor.theme: $CURSOR_THEME | xrdb -merge metacity --no-force-fullscreen -d $DISPLAY & exec sugar-session diff --git a/configure.ac b/configure.ac index 2052586..494445d 100644 --- a/configure.ac +++ b/configure.ac @@ -51,6 +51,7 @@ data/Makefile data/sugar-emulator.desktop extensions/cpsection/aboutcomputer/Makefile extensions/cpsection/aboutme/Makefile +extensions/cpsection/accessibility/Makefile extensions/cpsection/datetime/Makefile extensions/cpsection/frame/Makefile extensions/cpsection/keyboard/Makefile diff --git a/data/Makefile.am b/data/Makefile.am index 6a62d23..2a92ce1 100644 --- a/data/Makefile.am +++ b/data/Makefile.am @@ -8,6 +8,14 @@ sugar-100.gtkrc: gtkrc.em $(srcdir)/em.py -D scaling=\'100\' $(srcdir)/gtkrc.em > \ $(top_builddir)/data/sugar-100.gtkrc +sugar-100-contrast.gtkrc: gtkrc-contrast.em + $(srcdir)/em.py -D scaling=\'100\' $(srcdir)/gtkrc-contrast.em > \ + $(top_builddir)/data/sugar-100-contrast.gtkrc + +sugar-72-contrast.gtkrc: gtkrc-contrast.em + $(srcdir)/em.py -D scaling=\'100\' $(srcdir)/gtkrc-contrast.em > \ + $(top_builddir)/data/sugar-72-contrast.gtkrc + sugardir = $(pkgdatadir)/data sugar_DATA = \ activities.defaults \ @@ -18,7 +26,9 @@ sugar_DATA = \ GTKRC_FILES = \ sugar-72.gtkrc \ - sugar-100.gtkrc + sugar-100.gtkrc \ + sugar-100-contrast.gtkrc \ + sugar-72-contrast.gtkrc xsessionsdir = $(datadir)/xsessions xsessions_DATA = sugar.desktop @@ -64,5 +74,5 @@ endif icondir = $(datadir)/icons/hicolor/scalable/apps icon_DATA = sugar-xo.svg -EXTRA_DIST = $(sugar_DATA) $(xsessions_DATA) $(nmservice_DATA) $(mime_xml_in_files) em.py gtkrc.em $(schema_in_files) $(icon_DATA) +EXTRA_DIST = $(sugar_DATA) $(xsessions_DATA) $(nmservice_DATA) $(mime_xml_in_files) em.py gtkrc.em gtkrc-contrast.em $(schema_in_files) $(icon_DATA) CLEANFILES = $(GTKRC_FILES) $(mime_xml_files) $(schema_DATA) diff --git a/data/gtkrc-contrast.em b/data/gtkrc-contrast.em new file mode 100644 index 0000000..3ed4717 --- /dev/null +++ b/data/gtkrc-contrast.em @@ -0,0 +1,11 @@ +@{ +if scaling == '72': + icon_sizes = 'gtk-large-toolbar=40,40' +else: + icon_sizes = 'gtk-large-toolbar=55,55' +}@ +gtk-theme-name = "sugar-@scaling-contrast" +gtk-icon-theme-name = "sugar" +gtk-toolbar-style = GTK_TOOLBAR_ICONS +gtk-icon-sizes = "@icon_sizes" +gtk-cursor-blink-timeout = 3 diff --git a/data/gtkrc.em b/data/gtkrc.em index d4e1a7c..4459c42 100644 --- a/data/gtkrc.em +++ b/data/gtkrc.em @@ -6,7 +6,6 @@ else: }@ gtk-theme-name = "sugar-@scaling" gtk-icon-theme-name = "sugar" -gtk-cursor-theme-name = "sugar" gtk-toolbar-style = GTK_TOOLBAR_ICONS gtk-icon-sizes = "@icon_sizes" gtk-cursor-blink-timeout = 3 diff --git a/data/icons/Makefile.am b/data/icons/Makefile.am index a35643a..2497c4a 100644 --- a/data/icons/Makefile.am +++ b/data/icons/Makefile.am @@ -3,6 +3,7 @@ sugardir = $(pkgdatadir)/data/icons sugar_DATA = \ module-about_me.svg \ module-about_my_computer.svg \ + module-accessibility.svg \ module-date_and_time.svg \ module-frame.svg \ module-keyboard.svg \ diff --git a/data/icons/module-accessibility.svg b/data/icons/module-accessibility.svg new file mode 100644 index 0000000..245a47e --- /dev/null +++ b/data/icons/module-accessibility.svg @@ -0,0 +1,92 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + version="1.0" + width="55" + height="55" + id="svg795"> + <defs + id="defs797" /> + <g + id="layer1"> + <path + d="m -23.738584,30.440666 a 8.901969,11.301082 0 1 1 -17.803937,0 8.901969,11.301082 0 1 1 17.803937,0 z" + transform="matrix(1.2765958,0,0,1.0558659,19.192898,-13.127946)" + id="path12" + style="fill:none;stroke:#ffffff" /> + <path + d="m 30.145644,37.823484 c -0.583589,6.659344 -6.7511,11.52268 -13.775517,10.862567 C 9.3457054,48.02594 4.1243814,42.092348 4.7079694,35.433004 5.2915594,28.773661 20.67887,20.597949 14.885997,24.011638 -12.777062,40.313232 24.43362,62.068174 30.145644,37.823484 z" + id="path1405-0" + style="fill:none;stroke:#ffffff;stroke-width:1.44962609;stroke-opacity:1" /> + <rect + width="2.916333" + height="21.188446" + ry="1.3939767" + x="18.332197" + y="13.76814" + id="rect1438-8" + style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1.50547338;stroke-opacity:1" /> + <rect + width="2.8716421" + height="19.140917" + ry="1.259271" + x="-35.091366" + y="-38.207035" + transform="matrix(0,-1,-1,0,0,0)" + id="rect1438-1-87" + style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1.41987956;stroke-opacity:1" /> + <rect + width="2.5733202" + height="15.766372" + ry="1.0372615" + x="-5.4068875" + y="-65.997124" + transform="matrix(-0.55477829,0.83199823,-0.68762037,-0.72607041,0,0)" + id="rect1438-0-4" + style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1.21988189;stroke-opacity:1" /> + <rect + width="2.0588" + height="12.916228" + ry="0.84975183" + x="6.7647386" + y="-38.278324" + transform="matrix(-0.21998034,0.97550431,-0.88967727,-0.45658992,0,0)" + id="rect1438-0-1-8" + style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.98759735;stroke-opacity:1" /> + <rect + width="0.98179615" + height="13.275604" + ry="0.87339497" + x="-30.243309" + y="-41.495876" + transform="matrix(0,-1,-1,0,0,0)" + id="rect1438-1-8-5" + style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.69142139;stroke-opacity:1" /> + <rect + width="0.97070193" + height="11.568416" + ry="0.76108003" + x="50.822704" + y="-8.6089849" + transform="matrix(0.89791652,0.44016579,0.56230073,-0.82693282,0,0)" + id="rect1438-1-8-0-0" + style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.64177793;stroke-opacity:1" /> + <rect + width="1.6299056" + height="7.7739253" + ry="0.51144254" + x="72.759727" + y="35.364716" + transform="matrix(0.9888174,0.14913131,-0.60877492,0.79334299,0,0)" + id="rect1438-0-1-7-7" + style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.68172067;stroke-opacity:1" /> + <path + d="m 79.67578,18.634508 a 3.2829957,3.0304577 0 1 1 -6.565992,0 3.2829957,3.0304577 0 1 1 6.565992,0 z" + transform="matrix(1.5397454,0,0,1.4719643,-97.932275,-17.750188)" + id="path1603-2" + style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-opacity:1" /> + </g> +</svg> diff --git a/data/sugar.schemas.in b/data/sugar.schemas.in index 579d24b..82d2565 100644 --- a/data/sugar.schemas.in +++ b/data/sugar.schemas.in @@ -204,6 +204,18 @@ </schema> <schema> + <key>/schemas/desktop/sugar/show_register</key> + <applyto>/desktop/sugar/show_register</applyto> + <owner>sugar</owner> + <type>bool</type> + <default>true</default> + <locale name="C"> + <short>Show Register</short> + <long>If TRUE, Sugar will show a "Register" option.</long> + </locale> + </schema> + + <schema> <key>/schemas/desktop/sugar/peripherals/keyboard/layouts</key> <applyto>/desktop/sugar/peripherals/keyboard/layouts</applyto> <owner>sugar</owner> diff --git a/extensions/cpsection/Makefile.am b/extensions/cpsection/Makefile.am index a92b5dd..ee5897f 100644 --- a/extensions/cpsection/Makefile.am +++ b/extensions/cpsection/Makefile.am @@ -1,4 +1,4 @@ -SUBDIRS = aboutme aboutcomputer datetime frame keyboard language \ +SUBDIRS = aboutme aboutcomputer accessibility datetime frame keyboard language \ modemconfiguration network power updater sugardir = $(pkgdatadir)/extensions/cpsection diff --git a/extensions/cpsection/accessibility/Makefile.am b/extensions/cpsection/accessibility/Makefile.am new file mode 100644 index 0000000..70d26f4 --- /dev/null +++ b/extensions/cpsection/accessibility/Makefile.am @@ -0,0 +1,6 @@ +sugardir = $(pkgdatadir)/extensions/cpsection/accessibility + +sugar_PYTHON = \ + __init__.py \ + model.py \ + view.py diff --git a/extensions/cpsection/accessibility/__init__.py b/extensions/cpsection/accessibility/__init__.py new file mode 100644 index 0000000..c69acfc --- /dev/null +++ b/extensions/cpsection/accessibility/__init__.py @@ -0,0 +1,25 @@ +# Copyright (C) 2010 Plan Ceibal +# +# Author: Esteban Arias <earias@plan.ceibal.edu.uy> +# Contact information: comunidad@plan.ceibal.edu.uy +# Plan Ceibal http://www.ceibal.edu.uy +# +# 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 3 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, see <http://www.gnu.org/licenses/>. + +from gettext import gettext as _ + +CLASS = 'accessibility' +ICON = 'module-accessibility' +TITLE = _('Accessibility') + diff --git a/extensions/cpsection/accessibility/model.py b/extensions/cpsection/accessibility/model.py new file mode 100644 index 0000000..48b1636 --- /dev/null +++ b/extensions/cpsection/accessibility/model.py @@ -0,0 +1,89 @@ +# Copyright (C) 2010 Plan Ceibal +# +# Author: Esteban Arias <earias@plan.ceibal.edu.uy> +# Contact information: comunidad@plan.ceibal.edu.uy +# Plan Ceibal http://www.ceibal.edu.uy +# +# 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 3 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, see <http://www.gnu.org/licenses/>. + +from jarabe.model import accessibility + +keyboard = accessibility.Keyboard() +screen = accessibility.Screen() +mouse = accessibility.Mouse() + +KEYWORDS = ['mouse_keys', 'sticky_keys', 'bounce_keys', 'contrast', 'white_mouse', 'accel_mouse', 'capital_letters'] + +def get_mouse_keys(): + return keyboard.get_mouse_keys() + +def set_mouse_keys(activar): + keyboard.set_mouse_keys(activar) + +def print_mouse_keys(): + print str(get_mouse_keys()) + +def get_sticky_keys(): + return keyboard.get_sticky_keys() + +def set_sticky_keys(activar): + keyboard.set_sticky_keys(activar) + +def print_sticky_keys(): + print str(get_sticky_keys()) + +def get_bounce_keys(): + return keyboard.get_bounce_keys() + +def set_bounce_keys(activar): + keyboard.set_bounce_keys(activar) + +def print_bounce_keys(): + print str(get_bounce_keys()) + +def get_contrast(): + return screen.get_contrast() + +def set_contrast(activar): + screen.set_contrast(activar) + +def print_contrast(): + print str(get_contrast()) + +def get_capital_letters(): + return screen.get_capital_letters() + +def set_capital_letters(activar): + screen.set_capital_letters(activar) + +def print_capital_letters(): + print str(get_capital_letters()) + +def get_white_mouse(): + return mouse.get_white_mouse() + +def set_white_mouse(activar): + mouse.set_white_mouse(activar) + +def print_white_mouse(): + print str(get_white_mouse()) + +def get_accel_mouse(): + return mouse.get_accel_mouse() + +def set_accel_mouse(valor): + mouse.set_accel_mouse(valor) + +def print_accel_mouse(): + print str(get_accel_mouse()) diff --git a/extensions/cpsection/accessibility/view.py b/extensions/cpsection/accessibility/view.py new file mode 100644 index 0000000..e443cb4 --- /dev/null +++ b/extensions/cpsection/accessibility/view.py @@ -0,0 +1,302 @@ +# Copyright (C) 2010 Plan Ceibal +# +# Author: Esteban Arias <earias@plan.ceibal.edu.uy> +# Contact information: comunidad@plan.ceibal.edu.uy +# Plan Ceibal http://www.ceibal.edu.uy +# +# 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 3 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, see <http://www.gnu.org/licenses/>. + + +from gi.repository import Gtk +from gettext import gettext as _ + +from sugar3.graphics import style + +from jarabe.controlpanel.sectionview import SectionView +from jarabe.controlpanel.inlinealert import InlineAlert + +class accessibility(SectionView): + def __init__(self, model, alerts=None): + SectionView.__init__(self) + + self._model = model + self.restart_alerts = alerts + self.set_border_width(style.DEFAULT_SPACING * 2) + self.set_spacing(style.DEFAULT_SPACING) + scrollwindow = Gtk.ScrolledWindow() + scrollwindow.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) + self.pack_start(scrollwindow, True, True, 0) + scrollwindow.show() + + self._vbox_section = Gtk.VBox() + scrollwindow.add_with_viewport(self._vbox_section) + self._vbox_section.show() + + self._zone_alert_box = Gtk.HBox(spacing=style.DEFAULT_SPACING) + self.pack_start(self._zone_alert_box, False, False, 0) + + self._zone_alert = InlineAlert() + self._zone_alert_box.pack_start(self._zone_alert, True, True, 0) + if 'zone' in self.restart_alerts: + self._zone_alert.props.msg = self.restart_msg + self._zone_alert.show() + self._zone_alert_box.show() + + self.needs_restart = False + + self._view_keyboard_options() + self._view_screen_options() + self._view_mouse_options() + + + def _view_keyboard_options(self): + separator_pm_keyboard = Gtk.HSeparator() + self._vbox_section.pack_start(separator_pm_keyboard, False, False, 0) + separator_pm_keyboard.show() + + label_pm_keyboard = Gtk.Label(_('Keyboard')) + label_pm_keyboard.set_alignment(0, 0) + self._vbox_section.pack_start(label_pm_keyboard, False, False, 0) + label_pm_keyboard.show() + + self.box_pm_keyboard = Gtk.VBox() + self.box_pm_keyboard.set_border_width(style.DEFAULT_SPACING * 2) + self.box_pm_keyboard.set_spacing(style.DEFAULT_SPACING) + + self._view_mouse_keys() + self._view_sticky_keys() + self._view_bounce_keys() + + self._vbox_section.pack_start(self.box_pm_keyboard, False, False, 0) + self.box_pm_keyboard.show() + + def _view_screen_options(self): + separator_pm_screen = Gtk.HSeparator() + self._vbox_section.pack_start(separator_pm_screen, False, False, 0) + separator_pm_screen.show() + + label_pm_screen = Gtk.Label(_('Screen')) + label_pm_screen.set_alignment(0, 0) + self._vbox_section.pack_start(label_pm_screen, False, False, 0) + label_pm_screen.show() + + self.box_pm_screen = Gtk.VBox() + self.box_pm_screen.set_border_width(style.DEFAULT_SPACING * 2) + self.box_pm_screen.set_spacing(style.DEFAULT_SPACING) + + self._view_contrast() + self._view_letters() + + self._vbox_section.pack_start(self.box_pm_screen, False, False, 0) + self.box_pm_screen.show() + + def _view_mouse_options(self): + separator_pm_mouse = Gtk.HSeparator() + self._vbox_section.pack_start(separator_pm_mouse, False, False, 0) + separator_pm_mouse.show() + + label_pm_mouse = Gtk.Label(_('Mouse')) + label_pm_mouse.set_alignment(0, 0) + self._vbox_section.pack_start(label_pm_mouse, False, False, 0) + label_pm_mouse.show() + + self.box_pm_mouse = Gtk.VBox() + self.box_pm_mouse.set_border_width(style.DEFAULT_SPACING * 2) + self.box_pm_mouse.set_spacing(style.DEFAULT_SPACING) + + self._view_white_mouse() + self._view_acceleration_mouse() + + self._vbox_section.pack_start(self.box_pm_mouse, False, False, 0) + self.box_pm_mouse.show() + + def _set_mouse_keys(self, widget): + state = widget.get_active() + self._model.set_mouse_keys(state) + + def _set_sticky_keys(self, widget): + state = widget.get_active() + self._model.set_sticky_keys(state) + + def _set_bounce_keys(self, widget): + state = widget.get_active() + self._model.set_bounce_keys(state) + + def _set_contrast(self, widget): + state = widget.get_active() + self._model.set_contrast(state) + self.restart_alerts.append('zone') + self.needs_restart = True + self._zone_alert.props.msg = self.restart_msg + self._zone_alert.show() + + def _set_capital_letters(self, widget): + state = widget.get_active() + self._model.set_capital_letters(state) + self.restart_alerts.append('zone') + self.needs_restart = True + self._zone_alert.props.msg = self.restart_msg + self._zone_alert.show() + + def _set_white_mouse(self, widget): + state = widget.get_active() + self._model.set_white_mouse(state) + self.restart_alerts.append('zone') + self.needs_restart = True + self._zone_alert.props.msg = self.restart_msg + self._zone_alert.show() + + def cb_digits_scale_accel_mouse(self, adj): + self._model.set_accel_mouse(adj.get_value()) + + def undo(self): + self._model.set_mouse_keys(self.init_state_mouse_keys) + self._model.set_sticky_keys(self.init_state_sticky_keys) + self._model.set_bounce_keys(self.init_state_bounce_keys) + + + self._model.set_contrast(self.init_state_contrast) + self.btn_contrast.set_active(self.init_state_contrast) + + self._model.set_capital_letters(self.init_state_capital_letters) + self.btn_capital_letters.set_active(self.init_state_capital_letters) + + self._model.set_white_mouse(self.init_state_white_mouse) + self.btn_white_mouse.set_active(self.init_state_white_mouse) + + self.adj_accel_mouse.set_value(self.init_state_accel_mouse) + + self.needs_restart = False + self._zone_alert.hide() + + def _view_mouse_keys(self): + self.btn_mouse_keys = Gtk.CheckButton(_('Mouse Keys')) + self._mouse_pm_change_handler = self.btn_mouse_keys.connect("toggled", self._set_mouse_keys) + self.init_state_mouse_keys = self._model.get_mouse_keys() + self.btn_mouse_keys.set_active(self.init_state_mouse_keys) + self.box_pm_keyboard.pack_start(self.btn_mouse_keys, True, True, 2) + self.btn_mouse_keys.show() + + lbl_mouse = Gtk.Label(_('Move the mouse pointer with keyboard number.')) + lbl_mouse.set_alignment(0, 0) + self.box_pm_keyboard.pack_start(lbl_mouse, True, True, 2) + lbl_mouse.show() + + def _view_sticky_keys(self): + self.btn_sticky_keys = Gtk.CheckButton(_('Sticky Keys')) + self._sticky_pm_change_handler = self.btn_sticky_keys.connect("toggled", self._set_sticky_keys) + self.init_state_sticky_keys = self._model.get_sticky_keys() + self.btn_sticky_keys.set_active(self.init_state_sticky_keys) + self.box_pm_keyboard.pack_start(self.btn_sticky_keys, True, True, 2) + self.btn_sticky_keys.show() + + lbl_sticky = Gtk.Label(_('Instead of having to press two keys at once (such as CTRL + Q), you can press one key at a time.')) + lbl_sticky.set_line_wrap(True) + lbl_sticky.set_alignment(0, 0) + self.box_pm_keyboard.pack_start(lbl_sticky, True, True, 2) + lbl_sticky.show() + + def _view_bounce_keys(self): + self.btn_bounce_keys = Gtk.CheckButton(_('Bounce Keys')) + self._bounce_pm_change_handler = self.btn_bounce_keys.connect("toggled", self._set_bounce_keys) + self.init_state_bounce_keys = self._model.get_bounce_keys() + self.btn_bounce_keys.set_active(self.init_state_bounce_keys) + self.box_pm_keyboard.pack_start(self.btn_bounce_keys, True, True, 2) + self.btn_bounce_keys.show() + + lbl_bounce = Gtk.Label(_('Ignore rapid, repeated keypresses of the same key.')) + lbl_bounce.set_alignment(0, 0) + self.box_pm_keyboard.pack_start(lbl_bounce, True, True, 2) + lbl_bounce.show() + + def _view_contrast(self): + self.btn_contrast = Gtk.CheckButton(_('Contrast')) + self._contrast_pm_change_handler = self.btn_contrast.connect("toggled", self._set_contrast) + self.init_state_contrast = self._model.get_contrast() + if self.init_state_contrast: + self.btn_contrast.handler_block(self._contrast_pm_change_handler) + self.btn_contrast.set_active(True) + self.btn_contrast.handler_unblock(self._contrast_pm_change_handler) + else: + self.btn_contrast.set_active(False) + self.box_pm_screen.pack_start(self.btn_contrast, True, True, 2) + self.btn_contrast.show() + + lbl_contrast = Gtk.Label(_('Enables the color contrast of the graphic interface.')) + lbl_contrast.set_alignment(0, 0) + self.box_pm_screen.pack_start(lbl_contrast, True, True, 2) + lbl_contrast.show() + + def _view_letters(self): + self.btn_capital_letters = Gtk.CheckButton(_('Capital letters')) + self._capital_letters_pm_change_handler = self.btn_capital_letters.connect("toggled", self._set_capital_letters) + self.init_state_capital_letters = self._model.get_capital_letters() + if self.init_state_capital_letters: + self.btn_capital_letters.handler_block(self._capital_letters_pm_change_handler) + self.btn_capital_letters.set_active(True) + self.btn_capital_letters.handler_unblock(self._capital_letters_pm_change_handler) + else: + self.btn_capital_letters.set_active(False) + self.box_pm_screen.pack_start(self.btn_capital_letters, True, True, 2) + self.btn_capital_letters.show() + + lbl_capital_letters = Gtk.Label(_('Shows capital letters in the user interface.')) + lbl_capital_letters.set_alignment(0, 0) + self.box_pm_screen.pack_start(lbl_capital_letters, True, True, 2) + lbl_capital_letters.show() + + def _view_white_mouse(self): + self.btn_white_mouse = Gtk.CheckButton(_('White Mouse')) + self._white_mouse_pm_change_handler = self.btn_white_mouse.connect("toggled", self._set_white_mouse) + self.init_state_white_mouse = self._model.get_white_mouse() + if self.init_state_white_mouse: + self.btn_white_mouse.handler_block(self._white_mouse_pm_change_handler) + self.btn_white_mouse.set_active(True) + self.btn_white_mouse.handler_unblock(self._white_mouse_pm_change_handler) + else: + self.btn_white_mouse.set_active(False) + self.box_pm_mouse.pack_start(self.btn_white_mouse, True, True, 2) + self.btn_white_mouse.show() + + lbl_white_mouse = Gtk.Label(_('Show the mouse cursor white.')) + lbl_white_mouse.set_alignment(0, 0) + self.box_pm_mouse.pack_start(lbl_white_mouse, True, True, 2) + lbl_white_mouse.show() + + def _view_acceleration_mouse(self): + box_accel_mouse = Gtk.HBox(False, 0) + box_accel_mouse.set_border_width(0) + lbl_accel_mouse = Gtk.Label(_('Acceleration: ')) + lbl_accel_mouse.show() + box_accel_mouse.pack_start(lbl_accel_mouse, False, False, 0) + + self.init_state_accel_mouse = self._model.get_accel_mouse(); + self.adj_accel_mouse = Gtk.Adjustment(self.init_state_accel_mouse, 0.0, 5.0, 1.0, 1.0, 0.0) + self.adj_accel_mouse.connect("value_changed", self.cb_digits_scale_accel_mouse) + self.scale_accel_mouse = Gtk.HScale(adjustment=self.adj_accel_mouse) + self.scale_accel_mouse.set_digits(0) + self.scale_accel_mouse.show() + + box_accel_mouse.pack_start(self.scale_accel_mouse, True, True, 0) + box_accel_mouse.show() + + self.box_pm_mouse.pack_start(box_accel_mouse, True, True, 2) + + desc_accel_mouse = Gtk.Label(_('Controller acceleration mouse.')) + desc_accel_mouse.set_alignment(0, 0) + self.box_pm_mouse.pack_start(desc_accel_mouse, True, True, 2) + desc_accel_mouse.show() + + def setup(self): + pass diff --git a/extensions/globalkey/Makefile.am b/extensions/globalkey/Makefile.am index b6cbbd6..cab05ba 100644 --- a/extensions/globalkey/Makefile.am +++ b/extensions/globalkey/Makefile.am @@ -3,5 +3,6 @@ sugardir = $(pkgdatadir)/extensions/globalkey sugar_PYTHON = \ __init__.py \ screenshot.py \ + magnifier.py \ speech.py \ viewsource.py diff --git a/extensions/globalkey/magnifier.py b/extensions/globalkey/magnifier.py new file mode 100644 index 0000000..122e99b --- /dev/null +++ b/extensions/globalkey/magnifier.py @@ -0,0 +1,96 @@ +# Copyright (C) 2010 Plan Ceibal <comunidad@plan.ceibal.edu.uy> + +import os + +import ConfigParser +import logging +from gi.repository import GObject + +from jarabe.view.viewsource import setup_view_source +from sugar import env + + +PATH_VMG_CONFIG = os.environ['HOME'] + '/.magnifier.ini' +BOUND_KEYS = ['<shift>F13', 'F13', '<control>F13'] + +def handle_key_press(key): + logger = logging.getLogger('magnifier') + logger.setLevel(logging.DEBUG) + logger.debug("Ejecutando magnifier......" + key) + if (key=='<shift>F13'): + set_ruta_archivo() + set_GraphicsTools() + if (key=='<control>F13'): + set_ruta_archivo() + set_InvertColors() + _run_cmd_async('launchVmg') + +def get_GraphicsTools(): + return leer_config('General', 'GraphicsTools') + +def set_GraphicsTools(): + grabar_config_GraphicsTools('General', 'GraphicsTools') + +def grabar_config_GraphicsTools(encabezado, etiqueta): + parser = ConfigParser.ConfigParser() + parser.read(PATH_VMG_CONFIG) + val_old = get_GraphicsTools() + if val_old == '1': + parser.set(encabezado, etiqueta, '0') + arch = open(PATH_VMG_CONFIG, 'w') + parser.write(arch) + arch.close() + else: + parser.set(encabezado, etiqueta, '1') + arch = open(PATH_VMG_CONFIG, 'w') + parser.write(arch) + arch.close() + +def get_InvertColors(): + return leer_config('General', 'InvertColors') + +def set_InvertColors(): + grabar_config_InvertColors('General', 'InvertColors') + +def grabar_config_InvertColors(encabezado, etiqueta): + parser = ConfigParser.ConfigParser() + parser.read(PATH_VMG_CONFIG) + val_old = get_InvertColors() + if val_old == '1': + parser.set(encabezado, etiqueta, '0') + arch = open(PATH_VMG_CONFIG, 'w') + parser.write(arch) + arch.close() + else: + parser.set(encabezado, etiqueta, '1') + arch = open(PATH_VMG_CONFIG, 'w') + parser.write(arch) + arch.close() + +def leer_config(encabezado, etiqueta): + parser = ConfigParser.ConfigParser() + parser.read(PATH_VMG_CONFIG) + return parser.get(encabezado, etiqueta) + +def set_ruta_archivo(): + try: + f = file(PATH_VMG_CONFIG) + except: + PATH_VMG_CONFIG = "/root/.magnifier.ini" + +def _run_cmd_async(cmd): + logger = logging.getLogger('magnifier') + logger.setLevel(logging.DEBUG) + try: + GObject.spawn_async([find_and_absolutize('launchVmg')]) + logger.debug("Ejecuto magnifier") + except Exception, e: + logger.debug("Error ejecutando magnifier" + str(e)) + +def find_and_absolutize(script_name): + paths = env.os.environ['PATH'].split(':') + for path in paths: + looking_path = path + '/' + script_name + if env.os.path.isfile(looking_path): + return looking_path + return None diff --git a/src/jarabe/controlpanel/gui.py b/src/jarabe/controlpanel/gui.py index daa58e4..19dca00 100644 --- a/src/jarabe/controlpanel/gui.py +++ b/src/jarabe/controlpanel/gui.py @@ -1,4 +1,5 @@ # Copyright (C) 2008 One Laptop Per Child +# Copyright (C) 2010 Plan Ceibal <comunidad@plan.ceibal.edu.uy> # # 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 @@ -247,7 +248,7 @@ class ControlPanel(Gtk.Window): self._section_view.connect('request-close', self.__close_request_cb) self._main_view.modify_bg(Gtk.StateType.NORMAL, - style.COLOR_WHITE.get_gdk_color()) + style.COLOR_BG_CP.get_gdk_color()) def set_section_view_auto_close(self): """Automatically close the control panel if there is "nothing to do" @@ -289,6 +290,7 @@ class ControlPanel(Gtk.Window): return options def __cancel_clicked_cb(self, widget): + self._section_view.perform_cancel_actions() self._section_view.undo() self._options[self._current_option]['alerts'] = [] self._section_toolbar.accept_button.set_sensitive(True) diff --git a/src/jarabe/controlpanel/sectionview.py b/src/jarabe/controlpanel/sectionview.py index 074a8ae..bee64e0 100644 --- a/src/jarabe/controlpanel/sectionview.py +++ b/src/jarabe/controlpanel/sectionview.py @@ -53,6 +53,10 @@ class SectionView(Gtk.VBox): """Undo here the changes that have been made in this section.""" pass + def perform_cancel_actions(self): + """Perform additional actions, when the "Cancel" button is clicked.""" + pass + def perform_accept_actions(self): """Perform additional actions, when the "Ok" button is clicked.""" pass diff --git a/src/jarabe/desktop/activitieslist.py b/src/jarabe/desktop/activitieslist.py index 4adaf18..0194ec8 100644 --- a/src/jarabe/desktop/activitieslist.py +++ b/src/jarabe/desktop/activitieslist.py @@ -1,5 +1,6 @@ # Copyright (C) 2008 One Laptop Per Child # Copyright (C) 2009 Tomeu Vizoso +# Copyright (C) 2010 Plan Ceibal <comunidad@plan.ceibal.edu.uy> # # 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 @@ -139,7 +140,8 @@ class ActivitiesTreeView(Gtk.TreeView): registry = bundleregistry.get_registry() registry.set_bundle_favorite(row[ListModel.COLUMN_BUNDLE_ID], row[ListModel.COLUMN_VERSION], - not row[ListModel.COLUMN_FAVORITE]) + not row[ListModel.COLUMN_FAVORITE], + True) def __icon_clicked_cb(self, cell, path): row = self.get_model()[path] @@ -297,7 +299,7 @@ class CellRendererActivityIcon(CellRendererIcon): self.props.width = style.GRID_CELL_SIZE self.props.height = style.GRID_CELL_SIZE self.props.size = style.STANDARD_ICON_SIZE - self.props.stroke_color = style.COLOR_BUTTON_GREY.get_svg() + self.props.stroke_color = style.COLOR_DESKTOP_ICON.get_svg() self.props.fill_color = style.COLOR_TRANSPARENT.get_svg() self.props.mode = Gtk.CellRendererMode.ACTIVATABLE @@ -558,7 +560,8 @@ class ActivityListPalette(ActivityPalette): registry = bundleregistry.get_registry() registry.set_bundle_favorite(self._bundle_id, self._version, - not self._favorite) + not self._favorite, + True) def __activity_changed_cb(self, activity_registry, activity_info): if activity_info.get_bundle_id() == self._bundle_id and \ diff --git a/src/jarabe/desktop/favoritesview.py b/src/jarabe/desktop/favoritesview.py index 9c7a76e..0c5e202 100644 --- a/src/jarabe/desktop/favoritesview.py +++ b/src/jarabe/desktop/favoritesview.py @@ -1,5 +1,6 @@ # Copyright (C) 2006-2007 Red Hat, Inc. # Copyright (C) 2008 One Laptop Per Child +# Copyright (C) 2010 Plan Ceibal <comunidad@plan.ceibal.edu.uy> # # 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 @@ -271,11 +272,17 @@ class FavoritesView(ViewContainer): def _add_activity(self, activity_info): if activity_info.get_bundle_id() == 'org.laptop.JournalActivity': return - icon = ActivityIcon(activity_info) - icon.props.pixel_size = style.STANDARD_ICON_SIZE - #icon.set_resume_mode(self._resume_mode) - self.add(icon) - icon.show() + + # Add icon, if not already present (for the same combination of + # activity-id and activity-version) + icon = self._find_activity_icon(activity_info.get_bundle_id(), + activity_info.get_activity_version()) + if icon is None: + icon = ActivityIcon(activity_info) + icon.props.pixel_size = style.STANDARD_ICON_SIZE + #icon.set_resume_mode(self._resume_mode) + self.add(icon) + icon.show() def __activity_added_cb(self, activity_registry, activity_info): registry = bundleregistry.get_registry() @@ -325,7 +332,7 @@ class FavoritesView(ViewContainer): try: schoolserver.register_laptop() except RegisterError, e: - alert.props.title = _('Registration Failed') + alert.props.title = _('No School Server found on the network') alert.props.msg = '%s' % e else: alert.props.title = _('Registration Successful') @@ -499,7 +506,7 @@ class FavoritePalette(ActivityPalette): ActivityPalette.__init__(self, activity_info) if not journal_entries: - xo_color = XoColor('%s,%s' % (style.COLOR_BUTTON_GREY.get_svg(), + xo_color = XoColor('%s,%s' % (style.COLOR_WHITE.get_svg(), style.COLOR_TRANSPARENT.get_svg())) else: xo_color = misc.get_icon_color(journal_entries[0]) @@ -605,6 +612,14 @@ class OwnerIcon(BuddyIcon): palette = BuddyMenu(get_owner_instance()) client = GConf.Client.get_default() + show_register = client.get_bool('/desktop/sugar/show_register') + + if show_register: + self._create_register_menu(palette, client) + + return palette + + def _create_register_menu(self, palette, client): backup_url = client.get_string('/desktop/sugar/backup_url') if not backup_url: diff --git a/src/jarabe/desktop/homewindow.py b/src/jarabe/desktop/homewindow.py index a412b99..51873e9 100644 --- a/src/jarabe/desktop/homewindow.py +++ b/src/jarabe/desktop/homewindow.py @@ -16,6 +16,7 @@ from gettext import gettext as _ import logging +import dbus from gi.repository import GObject from gi.repository import Gtk @@ -32,6 +33,7 @@ from jarabe.desktop.transitionbox import TransitionBox from jarabe.desktop.viewtoolbar import ViewToolbar from jarabe.model.shell import ShellModel from jarabe.model import shell +from jarabe.model import notifications _HOME_PAGE = 0 @@ -39,6 +41,13 @@ _GROUP_PAGE = 1 _MESH_PAGE = 2 _TRANSITION_PAGE = 3 +_DBUS_SYSTEM_IFACE = 'org.sugarlabs.system' +_DBUS_SYSTEM_PATH = '/org/sugarlabs/system' +_SYSTEM_REBOOT_ID = -1 +_SYSTEM_RELOGIN_ID = -2 +_SYSTEM_TIMEOUT = 5 + + _instance = None @@ -99,6 +108,33 @@ class HomeWindow(Gtk.Window): shell.get_model().zoom_level_changed.connect( self.__zoom_level_changed_cb) + try: + systembus = dbus.SystemBus() + except dbus.DBusException: + logging.error('DBus SystemBus is not available') + else: + systembus.add_signal_receiver(self.__reboot_cb, 'Reboot', + _DBUS_SYSTEM_IFACE) + systembus.add_signal_receiver(self.__relogin_cb, 'Relogin', + _DBUS_SYSTEM_IFACE) + + def _system_alert(self, replaces_id, app_icon, message): + service = notifications.get_service() + service.notification_received.send(self,app_name='system', + replaces_id=replaces_id, app_icon=app_icon, + summary=_('System alert'), body=message, + actions=[], hints={}) + + def __reboot_cb(self): + self._system_alert(_SYSTEM_REBOOT_ID, 'system-restart', + _('Please, reboot your computer to take into account ' \ + 'new updates')) + + def __relogin_cb(self): + self._system_alert(_SYSTEM_RELOGIN_ID, 'system-logout', + _('Please, restart Sugar to take into account ' \ + 'new updates')) + def _deactivate_view(self, level): group = palettegroup.get_group('default') group.popdown() diff --git a/src/jarabe/desktop/schoolserver.py b/src/jarabe/desktop/schoolserver.py index cc26e56..2b8fa9a 100644 --- a/src/jarabe/desktop/schoolserver.py +++ b/src/jarabe/desktop/schoolserver.py @@ -140,7 +140,9 @@ def register_laptop(url=_REGISTER_URL): data = server.register(sn, nick, uuid_, profile.pubkey) except (xmlrpclib.Error, TypeError, socket.error): logging.exception('Registration: cannot connect to server') - raise RegisterError(_('Cannot connect to the server.')) + raise RegisterError(_('Please ensure that you are connected ' \ + 'to the correct network and that the ' \ + 'server is available.')) finally: socket.setdefaulttimeout(None) diff --git a/src/jarabe/journal/model.py b/src/jarabe/journal/model.py index c9e08ec..9b31ac0 100644 --- a/src/jarabe/journal/model.py +++ b/src/jarabe/journal/model.py @@ -289,6 +289,14 @@ class BaseResultSet(object): return self._cache[self._position - self._offset] + def is_favorite_compatible(self, metadata): + if self._favorite == '0': + return True + + return ((metadata is not None) and \ + ('keep' in metadata.keys()) and \ + (str(metadata['keep']) == '1')) + class DatastoreResultSet(BaseResultSet): """Encapsulates the result of a query on the datastore @@ -352,6 +360,8 @@ class InplaceResultSet(BaseResultSet): self._sort = query.get('order_by', ['+timestamp'])[0] + self._favorite = str(query.get('keep', 0)) + def setup(self): self._file_list = [] self._pending_directories = [self._mount_point] @@ -459,10 +469,13 @@ class InplaceResultSet(BaseResultSet): if S_IFMT(stat.st_mode) != S_IFREG: return + metadata = _get_file_metadata(full_path, stat, + fetch_preview=False) + + if not self.is_favorite_compatible(metadata): + return if self._regex is not None and \ not self._regex.match(full_path): - metadata = _get_file_metadata(full_path, stat, - fetch_preview=False) if not metadata: return add_to_list = False @@ -510,7 +523,7 @@ class InplaceResultSet(BaseResultSet): return -class RemoteShareResultSet(object): +class RemoteShareResultSet(BaseResultSet): def __init__(self, ip_address_or_dns_name, query): self._ip_address_or_dns_name = ip_address_or_dns_name self._file_list = [] @@ -544,6 +557,8 @@ class RemoteShareResultSet(object): self._sort = query.get('order_by', ['+timestamp'])[0] + self._favorite = str(query.get('keep', 0)) + def setup(self): try: metadata_list_complete = webdavmanager.get_remote_webdav_share_metadata(self._ip_address_or_dns_name) @@ -552,6 +567,9 @@ class RemoteShareResultSet(object): for metadata in metadata_list_complete: + if not self.is_favorite_compatible(metadata): + continue + add_to_list = False if self._regex is not None: for f in ['fulltext', 'title', @@ -1111,8 +1129,7 @@ def _write_entry_on_external_device(metadata, file_path, if destination_path != file_path: file_name = get_unique_file_name(metadata['mountpoint'], file_name) destination_path = os.path.join(metadata['mountpoint'], file_name) - clean_name, extension_ = os.path.splitext(file_name) - metadata['title'] = clean_name + metadata['title'] = file_name _write_metadata_and_preview_files_and_return_file_paths(metadata, file_name) @@ -1131,28 +1148,7 @@ def _write_entry_on_external_device(metadata, file_path, def get_file_name(title, mime_type): - file_name = title - - extension = mime.get_primary_extension(mime_type) - if extension is not None and extension: - extension = '.' + extension - if not file_name.endswith(extension): - file_name += extension - - # Invalid characters in VFAT filenames. From - # http://en.wikipedia.org/wiki/File_Allocation_Table - invalid_chars = ['/', '\\', ':', '*', '?', '"', '<', '>', '|', '\x7F'] - invalid_chars.extend([chr(x) for x in range(0, 32)]) - for char in invalid_chars: - file_name = file_name.replace(char, '_') - - # FAT limit is 255, leave some space for uniqueness - max_len = 250 - if len(file_name) > max_len: - name, extension = os.path.splitext(file_name) - file_name = name[0:max_len - len(extension)] + extension - - return file_name + return title def get_unique_file_name(mount_point, file_name): diff --git a/src/jarabe/model/Makefile.am b/src/jarabe/model/Makefile.am index d40fb8d..8b37ee7 100644 --- a/src/jarabe/model/Makefile.am +++ b/src/jarabe/model/Makefile.am @@ -1,6 +1,7 @@ sugardir = $(pythondir)/jarabe/model sugar_PYTHON = \ adhoc.py \ + accessibility.py \ __init__.py \ buddy.py \ bundleregistry.py \ diff --git a/src/jarabe/model/accessibility.py b/src/jarabe/model/accessibility.py new file mode 100644 index 0000000..6910b2b --- /dev/null +++ b/src/jarabe/model/accessibility.py @@ -0,0 +1,160 @@ +# Copyright (C) 2010 Plan Ceibal +# +# Author: Esteban Arias <earias@plan.ceibal.edu.uy> +# Contact information: comunidad@plan.ceibal.edu.uy +# Plan Ceibal http://www.ceibal.edu.uy +# +# 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 3 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, see <http://www.gnu.org/licenses/>. + +from gi.repository import Gtk +from gi.repository import GConf +import subprocess + +class Keyboard: + + def get_mouse_keys(self): + client = GConf.Client.get_default() + return client.get_bool("/desktop/sugar/accessibility/keyboard/mousekeys_enable") + + def set_mouse_keys(self, activar): + client = GConf.Client.get_default() + client.set_bool("/desktop/sugar/accessibility/keyboard/mousekeys_enable", activar) + self.run_config_keyboard() + + def get_sticky_keys(self): + client = GConf.Client.get_default() + return client.get_bool("/desktop/sugar/accessibility/keyboard/stickykeys_enable") + + def set_sticky_keys(self, activar): + client = GConf.Client.get_default() + client.set_bool("/desktop/sugar/accessibility/keyboard/stickykeys_enable", activar) + self.run_config_keyboard() + + def get_bounce_keys(self): + client = GConf.Client.get_default() + return client.get_bool("/desktop/sugar/accessibility/keyboard/bouncekeys_enable") + + def set_bounce_keys(self, activar): + client = GConf.Client.get_default() + client.set_bool("/desktop/sugar/accessibility/keyboard/bouncekeys_enable", activar) + self.run_config_keyboard() + + def run_config_keyboard(self): + cmd = ['ax'] + if self.get_sticky_keys(): + cmd.append('+stickykeys') + else: + cmd.append('-stickykeys') + if self.get_bounce_keys(): + cmd.append('+bouncekeys') + else: + cmd.append('-bouncekeys') + if self.get_mouse_keys(): + cmd += ['+mousekeys', 'mousemaxspeed', '3000', 'mousetimetomax', '1000', '-timeout', '-repeatkeys'] + else: + cmd += ['-mousekeys', 'mousemaxspeed', '3000', 'mousetimetomax', '1000', '+timeout', '+repeatkeys'] + subprocess.call(cmd) + +class Screen: + + DEFAULT_THEME = "sugar" + DEFAULT_FONT_SIZE = 7 + DEFAULT_FONT_FACE = "Sans Serif" + CONTRAST_THEME = "sugar-contrast" + CONTRAST_FONT_SIZE = 9.5 + CAPITAL_LETTERS_FONT_FACE = "Oracle" + + def get_contrast(self): + client = GConf.Client.get_default() + value = client.get_string("/desktop/sugar/interface/Gtk_theme") + return value==self.CONTRAST_THEME + + def set_contrast(self, activar): + client = GConf.Client.get_default() + if (activar): + client.set_string("/desktop/sugar/interface/Gtk_theme", self.CONTRAST_THEME) + client.set_float('/desktop/sugar/font/default_size', self.CONTRAST_FONT_SIZE) + else: + client.set_string("/desktop/sugar/interface/Gtk_theme", self.DEFAULT_THEME) + client.set_float('/desktop/sugar/font/default_size', self.DEFAULT_FONT_SIZE) + + def get_capital_letters(self): + client = GConf.Client.get_default() + value = client.get_string("/desktop/sugar/font/default_face") + return value==self.CAPITAL_LETTERS_FONT_FACE + + def set_capital_letters(self, activar): + client = GConf.Client.get_default() + if (activar): + client.set_string('/desktop/sugar/font/default_face', self.CAPITAL_LETTERS_FONT_FACE) + else: + client.set_string('/desktop/sugar/font/default_face', self.DEFAULT_FONT_FACE) + + +class Mouse: + + WHITE_CURSOR_THEME="FlatbedCursors.White.Huge" + DEFAULT_CURSOR_THEME="sugar" + DEFAULT_ACCEL_MOUSE=3 + + def get_white_mouse(self): + client = GConf.Client.get_default() + value = client.get_string("/desktop/sugar/peripherals/mouse/cursor_theme") + return value==self.WHITE_CURSOR_THEME + + def set_white_mouse(self, activar): + client = GConf.Client.get_default() + if (activar): + client.set_string("/desktop/sugar/peripherals/mouse/cursor_theme", self.WHITE_CURSOR_THEME) + else: + client.set_string("/desktop/sugar/peripherals/mouse/cursor_theme", self.DEFAULT_CURSOR_THEME) + + def _set_white_mouse_setting(self): + cursor_theme = self.DEFAULT_CURSOR_THEME + if (self.get_white_mouse()): + cursor_theme = self.WHITE_CURSOR_THEME + settings = Gtk.Settings.get_default() + settings.set_property("gtk-cursor-theme-name", "%s" % (cursor_theme)) + + def get_accel_mouse(self): + client = GConf.Client.get_default() + value = client.get_float("/desktop/sugar/peripherals/mouse/motion_acceleration") + return value + + def set_accel_mouse(self, value): + client = GConf.Client.get_default() + client.set_float("/desktop/sugar/peripherals/mouse/motion_acceleration", value) + self.run_config_mouse() + + def _set_accel_mouse_setting(self): + cmd = ['xset', 'm' , str(self.get_accel_mouse())] + subprocess.call(cmd) + + def run_config_mouse(self): + self._set_accel_mouse_setting() + self._set_white_mouse_setting() + +class AccessibilityManager: + def setup_accessibility(self): + client = GConf.Client.get_default() + is_accessibility = client.dir_exists("/desktop/sugar/accessibility") + mouse = Mouse() + if is_accessibility: + keyboard = Keyboard() + keyboard.run_config_keyboard() + mouse.run_config_mouse() + else: + mouse.set_accel_mouse(mouse.DEFAULT_ACCEL_MOUSE) + mouse.set_white_mouse(False) + mouse._set_accel_mouse_setting() diff --git a/src/jarabe/model/bundleregistry.py b/src/jarabe/model/bundleregistry.py index e441122..6fd4563 100644 --- a/src/jarabe/model/bundleregistry.py +++ b/src/jarabe/model/bundleregistry.py @@ -59,15 +59,18 @@ class BundleRegistry(GObject.GObject): self._bundles = [] # hold a reference to the monitors so they don't get disposed self._gio_monitors = [] + self._monitor = {} + self._handler_id = {} + self._allow_file_monitoring = True user_path = env.get_user_activities_path() for activity_dir in [user_path, config.activities_path]: self._scan_directory(activity_dir) directory = Gio.File.new_for_path(activity_dir) - monitor = directory.monitor_directory( \ - flags=Gio.FileMonitorFlags.NONE, cancellable=None) - monitor.connect('changed', self.__file_monitor_changed_cb) - self._gio_monitors.append(monitor) + self._monitor = directory.monitor_directory( \ + flags=Gio.FileMonitorFlags.NONE, cancellable=None) + self._handler_id = self._monitor.connect('changed', self.__file_monitor_changed_cb) + self._gio_monitors.append(self._monitor) self._last_defaults_mtime = -1 self._favorite_bundles = {} @@ -94,12 +97,31 @@ class BundleRegistry(GObject.GObject): def __file_monitor_changed_cb(self, monitor, one_file, other_file, event_type): - if not one_file.get_path().endswith('.activity'): - return - if event_type == Gio.FileMonitorEvent.CREATED: - self.add_bundle(one_file.get_path(), install_mime_type=True) - elif event_type == Gio.FileMonitorEvent.DELETED: - self.remove_bundle(one_file.get_path()) + if self._allow_file_monitoring: + if not one_file.get_path().endswith('.activity'): + return + if event_type == Gio.FileMonitorEvent.CREATED: + if self.get_bundle_by_path(one_file.get_path()) is None: + self.add_bundle(one_file.get_path(),install_mime_type=True) + elif event_type == Gio.FileMonitorEvent.DELETED: + self.remove_bundle(one_file.get_path()) + + # I tried 2 hours, trying to make the following mechanisms for + # event-blocking, work :: + # + # a. disconnect + # b. handler_disconnect + # c. handler_block/handler_unblock + # d. handler_block_by_func/handler_unblock_by_func + # + # I could not. + # + # In the end, I had to revert to using the oldy-goldy boolean flag. + def disable_directory_monitoring(self): + self._allow_file_monitoring = False + + def enable_directory_monitoring(self): + self._allow_file_monitoring = True def _load_mime_defaults(self): defaults = {} @@ -186,6 +208,12 @@ class BundleRegistry(GObject.GObject): return bundle return None + def get_bundle_by_path(self, bundle_path): + for bundle in self._bundles: + if bundle.get_path() == bundle_path: + return bundle + return None + def __iter__(self): return self._bundles.__iter__() @@ -213,7 +241,13 @@ class BundleRegistry(GObject.GObject): bundle_dirs.sort(lambda d1, d2: cmp(bundles[d1], bundles[d2])) for folder in bundle_dirs: try: - self._add_bundle(folder) + # sl#2818 + # + # Add meta-info, to indicate that this is a startup + # operation. + # + # See 'elif not startup' notes in 'self._add_bundle()' + self._add_bundle(folder, False, True) except: # pylint: disable=W0702 logging.exception('Error while processing installed activity' @@ -222,15 +256,13 @@ class BundleRegistry(GObject.GObject): def add_bundle(self, bundle_path, install_mime_type=False): bundle = self._add_bundle(bundle_path, install_mime_type) if bundle is not None: - self._set_bundle_favorite(bundle.get_bundle_id(), - bundle.get_activity_version(), - True) self.emit('bundle-added', bundle) return True else: return False - def _add_bundle(self, bundle_path, install_mime_type=False): + def _add_bundle(self, bundle_path, install_mime_type=False, + startup=False): logging.debug('STARTUP: Adding bundle %r', bundle_path) try: bundle = ActivityBundle(bundle_path) @@ -250,17 +282,90 @@ class BundleRegistry(GObject.GObject): return None else: logging.debug('Upgrade %s', bundle_id) + + # Query if the bundle is a favorite... + self._is_bundle_favorite = \ + self.is_bundle_favorite(installed.get_bundle_id(), + installed.get_activity_version()) + + # ...and then, remove the old bundle (we have the new + # one !!) self.remove_bundle(installed.get_path()) - self._bundles.append(bundle) + # Check, if this bundle-id is a favorite. + if self._is_bundle_favorite: + + # Mark the (new) bundle with this bundle-id, as + # favorite. + self.set_bundle_favorite(bundle.get_bundle_id(), + bundle.get_activity_version(), + True, + True) + + # This (new) bundle is new (!!), and is also a + # favorite. Add this to Favorites-View. + self.emit('bundle-added', bundle) + + elif not startup: + + # Ticket sl#2818 + + # Sub-case + # -------- + # The bundle is newly added; so set it as favorite as + # default, so that it is promptly available to the + # user. + # Note that, any newly added bundles during system + # startup, are not new-bundles-as-such. They were + # new, when they were first added, and their + # favorite-status set at that time. + # However, from code point of view, control reaches + # here, only if the bundle IS newly added by + # user-discretion. + self.set_bundle_favorite(bundle.get_bundle_id(), + bundle.get_activity_version(), + True, + True) + + # Emit 'bundle-added' (of course, this bundle is new !!), + # so that it is added in Favorites-View. + self.emit('bundle-added', bundle) + + + # In either case, + # a. Startup + # b. Upgrade of bundle + # c, Addition of new bundle, by user-discretion. + # add the bundle to bundles-list. + self.add_bundle_to_bundlelist(bundle) return bundle + def add_bundle_to_bundlelist(self, bundle): + for bundle_in_list in self._bundles: + if bundle_in_list.get_path() == \ + bundle.get_path(): + return False + + self._bundles.append(bundle) + return True + def remove_bundle(self, bundle_path): for bundle in self._bundles: if bundle.get_path() == bundle_path: + # This bundle is going. + # Remove it from bundles list... self._bundles.remove(bundle) + + # ... and remove it from Favorites-List... + self.set_bundle_favorite(bundle.get_bundle_id(), + bundle.get_activity_version(), + False, + True) + + # ...and remove its trace from Favorites-View. self.emit('bundle-removed', bundle) return True + return False def get_activities_for_type(self, mime_type): @@ -293,14 +398,22 @@ class BundleRegistry(GObject.GObject): if bundle.get_bundle_id() == bundle_id and \ bundle.get_activity_version() == version: return bundle - raise ValueError('No bundle %r with version %r exists.' % \ - (bundle_id, version)) + return None + + def set_bundle_favorite(self, bundle_id, version, favorite, + force=False): + # Return if file monitoring is not allowed, and the previous + # bundle is not a favorite. + if not force: + if not self._allow_file_monitoring: + if not self._is_bundle_favorite: + return - def set_bundle_favorite(self, bundle_id, version, favorite): changed = self._set_bundle_favorite(bundle_id, version, favorite) if changed: bundle = self._find_bundle(bundle_id, version) - self.emit('bundle-changed', bundle) + if bundle is not None: + self.emit('bundle-changed', bundle) def _set_bundle_favorite(self, bundle_id, version, favorite): key = self._get_favorite_key(bundle_id, version) @@ -426,9 +539,6 @@ class BundleRegistry(GObject.GObject): bundle.uninstall(install_path, force, delete_profile) - if not self.remove_bundle(install_path): - raise RegistrationException - def upgrade(self, bundle): act = self.get_bundle(bundle.get_bundle_id()) if act is None: |