diff --git a/bin/sugar-control-panel.in b/bin/sugar-control-panel.in index 922f95c..e70764b 100644 --- a/bin/sugar-control-panel.in +++ b/bin/sugar-control-panel.in @@ -17,10 +17,12 @@ import sys -sys.path.insert(0, '@prefix@/share/sugar/shell') +path = '@prefix@/share/sugar/shell' + +sys.path.insert(0, path) from controlpanel.cmd import main -main() +main(path) diff --git a/configure.ac b/configure.ac index a99fdf0..1981fbc 100644 --- a/configure.ac +++ b/configure.ac @@ -43,6 +43,9 @@ data/Makefile service/Makefile src/Makefile src/controlpanel/Makefile +src/controlpanel/icons/Makefile +src/controlpanel/model/Makefile +src/controlpanel/view/Makefile src/intro/Makefile src/hardware/Makefile src/view/Makefile diff --git a/src/controlpanel/Makefile.am b/src/controlpanel/Makefile.am index f89132c..e626af4 100644 --- a/src/controlpanel/Makefile.am +++ b/src/controlpanel/Makefile.am @@ -1,5 +1,10 @@ +SUBDIRS = model view icons + sugardir = $(pkgdatadir)/shell/controlpanel sugar_PYTHON = \ __init__.py \ cmd.py \ - control.py + gui.py \ + controltoolbar.py \ + detailview.py \ + inlinealert.py diff --git a/src/controlpanel/cmd.py b/src/controlpanel/cmd.py index 24653b2..db23559 100644 --- a/src/controlpanel/cmd.py +++ b/src/controlpanel/cmd.py @@ -1,4 +1,4 @@ -# Copyright (C) 2007, One Laptop Per Child +# 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 @@ -16,11 +16,12 @@ import sys import getopt +import os from gettext import gettext as _ -from controlpanel import control - + def cmd_help(): + '''Print the help to the screen''' print _('Usage: sugar-control-panel [ option ] key [ args ... ] \n\ Control for the sugar environment. \n\ Options: \n\ @@ -31,7 +32,28 @@ def cmd_help(): -s key set the current value for the key \n\ ') -def main(): +def note_restart(): + '''Instructions how to restart sugar''' + print _('To apply your changes you have to restart sugar.\n' + + 'Hit ctrl+alt+erase on the keyboard to trigger a restart.') + +def load_modules(path): + '''Build a list of pointers to available modules in the model directory + and load them. + ''' + subpath = ['controlpanel', 'model'] + names = os.listdir(os.path.join(path, '/'.join(subpath))) + + modules = [] + for name in names: + if name.endswith('.py') and name != '__init__.py': + tmp = name.strip('.py') + mod = __import__('.'.join(subpath) + '.' + + tmp, globals(), locals(), [tmp]) + modules.append(mod) + return modules + +def main(path): try: opts, args = getopt.getopt(sys.argv[1:], "h:s:g:l", []) except getopt.GetoptError: @@ -41,37 +63,49 @@ def main(): if not opts: cmd_help() sys.exit() - + + modules = load_modules(path) + for opt, key in opts: - if opt in ("-h"): - method = getattr(control, 'set_' + key, None) - if method is None: - print _("sugar-control-panel: key=%s not an available option" - % key) - sys.exit() - else: - print method.__doc__ - if opt in ("-l"): - elems = dir(control) - for elem in elems: - if elem.startswith('set_'): - print elem[4:] + if opt in ("-h"): + for module in modules: + method = getattr(module, 'set_' + key, None) + if method: + print method.__doc__ + sys.exit() + print _("sugar-control-panel: key=%s not an available option" + % key) + if opt in ("-l"): + for module in modules: + methods = dir(module) + print '%s:' % module.__name__.split('.')[-1] + for method in methods: + if method.startswith('get_'): + print ' %s' % method[4:] if opt in ("-g"): - method = getattr(control, 'print_' + key, None) - if method is None: - print _("sugar-control-panel: key=%s not an available option" - % key) - sys.exit() - else: - method() + for module in modules: + method = getattr(module, 'print_' + key, None) + if method: + try: + method() + except Exception, detail: + print _("sugar-control-panel: %s" + % detail) + sys.exit() + print _("sugar-control-panel: key=%s not an available option" + % key) if opt in ("-s"): - method = getattr(control, 'set_' + key, None) - if method is None: - print _("sugar-control-panel: key=%s not an available option" - % key) - sys.exit() - else: - try: - method(*args) - except Exception, e: - print _("sugar-control-panel: %s"% e) + for module in modules: + method = getattr(module, 'set_' + key, None) + if method: + note = 0 + try: + note = method(*args) + except Exception, detail: + print _("sugar-control-panel: %s" + % detail) + if note == 'RESTART': + note_restart() + sys.exit() + print _("sugar-control-panel: key=%s not an available option" + % key) diff --git a/src/controlpanel/controltoolbar.py b/src/controlpanel/controltoolbar.py new file mode 100644 index 0000000..2d54bdc --- /dev/null +++ b/src/controlpanel/controltoolbar.py @@ -0,0 +1,157 @@ +# 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 + +import gtk +import gettext +import gobject + +_ = lambda msg: gettext.dgettext('sugar', msg) + +from sugar.graphics.icon import Icon +from sugar.graphics.toolbutton import ToolButton +from sugar.graphics import iconentry +from sugar.graphics import style + +class MainToolbar(gtk.Toolbar): + """ Main + """ + __gtype_name__ = 'MainToolbar' + + __gsignals__ = { + 'stop-clicked': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([])), + 'search-changed': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([str])) + } + def __init__(self): + gtk.Toolbar.__init__(self) + + self._add_separator() + + tool_item = gtk.ToolItem() + self.insert(tool_item, -1) + tool_item.show() + self._search_entry = iconentry.IconEntry() + self._search_entry.set_icon_from_name(iconentry.ICON_ENTRY_PRIMARY, + 'system-search') + self._search_entry.add_clear_button() + self._search_entry.set_width_chars(25) + self._search_entry.connect('changed', self.__search_entry_changed_cb) + tool_item.add(self._search_entry) + self._search_entry.show() + + self._add_separator(True) + + self.stop = ToolButton(icon_name='dialog-cancel') + self.stop.set_tooltip(_('Done')) + self.stop.connect('clicked', self.__stop_clicked_cb) + self.stop.show() + self.insert(self.stop, -1) + self.stop.show() + + def get_entry(self): + return self._search_entry + + def _add_separator(self, expand=False): + separator = gtk.SeparatorToolItem() + separator.props.draw = False + if expand: + separator.set_expand(True) + else: + separator.set_size_request(style.DEFAULT_SPACING, -1) + self.insert(separator, -1) + separator.show() + + def __search_entry_changed_cb(self, search_entry): + self.emit('search-changed', search_entry.props.text) + + def __stop_clicked_cb(self, button): + self.emit('stop-clicked') + +class DetailToolbar(gtk.Toolbar): + """ Detail + """ + __gtype_name__ = 'DetailToolbar' + + __gsignals__ = { + 'cancel-clicked': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([])), + 'accept-clicked': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([])) + } + def __init__(self): + gtk.Toolbar.__init__(self) + + self._add_separator() + + self._icon = Icon() + self._add_widget(self._icon) + + self._add_separator() + + self._title = gtk.Label() + self._add_widget(self._title) + + self._add_separator(True) + + cancel_button = ToolButton('dialog-cancel') + cancel_button.set_tooltip(_('Cancel')) + cancel_button.connect('clicked', self.__cancel_button_clicked_cb) + self.insert(cancel_button, -1) + cancel_button.show() + + self.accept_button = ToolButton('dialog-ok') + self.accept_button.set_tooltip(_('Ok')) + self.accept_button.connect('clicked', self.__accept_button_clicked_cb) + self.insert(self.accept_button, -1) + self.accept_button.show() + + def get_icon(self): + return self._icon + + def get_title(self): + return self._title + + def _add_separator(self, expand=False): + separator = gtk.SeparatorToolItem() + separator.props.draw = False + if expand: + separator.set_expand(True) + else: + separator.set_size_request(style.DEFAULT_SPACING, -1) + self.insert(separator, -1) + separator.show() + + def _add_widget(self, widget, expand=False): + tool_item = gtk.ToolItem() + tool_item.set_expand(expand) + + tool_item.add(widget) + widget.show() + + self.insert(tool_item, -1) + tool_item.show() + + def __cancel_button_clicked_cb(self, widget, data=None): + self.emit('cancel-clicked') + + def __accept_button_clicked_cb(self, widget, data=None): + self.emit('accept-clicked') + diff --git a/src/controlpanel/detailview.py b/src/controlpanel/detailview.py new file mode 100644 index 0000000..abfe513 --- /dev/null +++ b/src/controlpanel/detailview.py @@ -0,0 +1,21 @@ +import gobject +import gtk +import gettext + +_ = lambda msg: gettext.dgettext('sugar', msg) + +class DetailView(gtk.VBox): + __gsignals__ = { + 'valid-section': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([bool])) + } + def __init__(self): + gtk.VBox.__init__(self) + self.restart = False + self.restart_alerts = [] + self._restart_msg = _('Changes require a sugar restart to take effect.') + + def undo(self): + '''Undo here the changes that have been made in this section.''' + pass diff --git a/src/controlpanel/gui.py b/src/controlpanel/gui.py new file mode 100644 index 0000000..98f93b6 --- /dev/null +++ b/src/controlpanel/gui.py @@ -0,0 +1,359 @@ +# 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 gtk +import gettext +import os +import gobject +import logging + +from sugar.graphics.icon import Icon +from sugar.graphics import style +from sugar.graphics.alert import Alert +import config + +from controlpanel.controltoolbar import MainToolbar +from controlpanel.controltoolbar import DetailToolbar + +_ = lambda msg: gettext.dgettext('sugar', msg) + + +class ControlPanel(gtk.Window): + __gtype_name__ = 'SugarControlPanel' + + def __init__(self): + gtk.Window.__init__(self) + + icons_path = os.path.join(config.prefix, + 'share/sugar/shell/controlpanel/icons') + gtk.icon_theme_get_default().append_search_path(icons_path) + + self.set_border_width(style.LINE_WIDTH) + offset = style.GRID_CELL_SIZE + width = gtk.gdk.screen_width() - offset * 2 + height = gtk.gdk.screen_height() - offset * 2 + self.set_size_request(width, height) + self.set_position(gtk.WIN_POS_CENTER_ALWAYS) + self.set_decorated(False) + self.set_resizable(False) + self.set_modal(True) + + self._toolbar = None + self._canvas = None + self._table = None + self._separator = None + self._detail_view = None + self._detail_toolbar = None + self._main_toolbar = None + + self._vbox = gtk.VBox() + self._hbox = gtk.HBox() + self._vbox.pack_start(self._hbox) + self._hbox.show() + + self._main_view = gtk.EventBox() + self._hbox.pack_start(self._main_view) + self._main_view.modify_bg(gtk.STATE_NORMAL, + style.COLOR_BLACK.get_gdk_color()) + self._main_view.show() + + self.add(self._vbox) + self._vbox.show() + + self.connect("realize", self.__realize_cb) + + self._options = {} + self._current_option = None + self._get_options() + self._setup_main() + self._setup_detail() + self._show_main_view() + + def _update_accept_focus(self): + self.window.set_accept_focus(True) + + def __realize_cb(self, widget): + self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG) + self._update_accept_focus() + + def _set_canvas(self, canvas): + if self._canvas: + self._main_view.remove(self._canvas) + if canvas: + self._main_view.add(canvas) + self._canvas = canvas + + def _set_toolbar(self, toolbar): + if self._toolbar: + self._vbox.remove(self._toolbar) + self._vbox.pack_start(toolbar, False) + self._vbox.reorder_child(toolbar, 0) + self._toolbar = toolbar + if not self._separator: + self._separator = gtk.HSeparator() + self._vbox.pack_start(self._separator, False) + self._vbox.reorder_child(self._separator, 1) + self._separator.show() + + def _setup_main(self): + self._main_toolbar = MainToolbar() + + self._table = gtk.Table() + #self._table.set_row_spacings(style.DEFAULT_SPACING) + self._table.set_col_spacings(style.GRID_CELL_SIZE) + self._table.set_border_width(style.GRID_CELL_SIZE) + self._setup_options() + self._main_toolbar.connect('stop-clicked', + self.__stop_clicked_cb) + self._main_toolbar.connect('search-changed', + self.__search_changed_cb) + + def _setup_options(self): + row = 0 + column = 0 + for option in self._options: + gridwidget = _GridWidget(icon_name=self._options[option]['icon'], + title=self._options[option]['title'], + xo_color = self._options[option]['color'], + pixel_size=style.GRID_CELL_SIZE) + gridwidget.connect('button_press_event', + self.__select_option_cb, option) + gridwidget.show() + + self._table.attach(gridwidget, column, column+1, row, row+1) + self._options[option]['button'] = gridwidget + + column += 1 + if column == 5: + column = 0 + row += 1 + + def _show_main_view(self): + self._set_toolbar(self._main_toolbar) + self._main_toolbar.show() + self._set_canvas(self._table) + self._main_view.modify_bg(gtk.STATE_NORMAL, + style.COLOR_BLACK.get_gdk_color()) + self._table.show() + entry = self._main_toolbar.get_entry() + entry.grab_focus() + entry.set_text('') + + def _update(self, query): + for option in self._options: + found = False + for key in self._options[option]['keywords']: + if query in key or query in key.upper() \ + or query in key.capitalize(): + self._options[option]['button'].set_sensitive(True) + found = True + break + if not found: + self._options[option]['button'].set_sensitive(False) + + def _setup_detail(self): + self._detail_toolbar = DetailToolbar() + self._detail_toolbar.connect('cancel-clicked', + self.__cancel_clicked_cb) + self._detail_toolbar.connect('accept-clicked', + self.__accept_clicked_cb) + + def _show_detail_view(self, option): + self._set_toolbar(self._detail_toolbar) + + icon = self._detail_toolbar.get_icon() + icon.set_from_icon_name(self._options[option]['icon'], + gtk.ICON_SIZE_LARGE_TOOLBAR) + icon.props.xo_color = self._options[option]['color'] + title = self._detail_toolbar.get_title() + title.set_text(self._options[option]['title']) + self._detail_toolbar.show() + self._detail_toolbar.accept_button.set_sensitive(True) + + self._current_option = option + class_pointer = self._options[option]['view'] + model = self._options[option]['model'] + self._detail_view = class_pointer(model, + self._options[option]['alerts']) + self._set_canvas(self._detail_view) + self._detail_view.show() + self._detail_view.connect('valid-section', self.__valid_section_cb) + self._main_view.modify_bg(gtk.STATE_NORMAL, + style.COLOR_WHITE.get_gdk_color()) + + def _get_options(self): + '''Get the available option information from the subfolders + model and view. + structure: + {'optionname': {'view', 'model', 'button', 'keywords', 'icon'} } + ''' + path = os.path.join(config.prefix, 'share/sugar/shell') + + subpath = ['controlpanel', 'view'] + names = os.listdir(os.path.join(path, '/'.join(subpath))) + for name in names: + if name.endswith('.py') and name != '__init__.py': + tmp = name.strip('.py') + mod = __import__('.'.join(subpath) + '.' + tmp, globals(), + locals(), [tmp]) + class_pointer = getattr(mod, tmp[0].capitalize() + + tmp[1:], None) + if class_pointer: + self._options[tmp] = {} + self._options[tmp]['alerts'] = [] + self._options[tmp]['view'] = class_pointer + self._options[tmp]['icon'] = getattr(mod, 'ICON', tmp) + self._options[tmp]['title'] = getattr(mod, 'TITLE', tmp) + self._options[tmp]['color'] = getattr(mod, 'COLOR', None) + + subpath = ['controlpanel', 'model'] + names = os.listdir(os.path.join(path, '/'.join(subpath))) + for name in names: + if name.endswith('.py') and name != '__init__.py': + tmp = name.strip('.py') + if tmp in self._options: + mod = __import__('.'.join(subpath) + '.' + tmp, + globals(), locals(), [tmp]) + keywords = getattr(mod, 'KEYWORDS', []) + keywords.append(self._options[tmp]['title'].lower()) + if tmp not in keywords: + keywords.append(tmp) + self._options[tmp]['model'] = mod + self._options[tmp]['keywords'] = keywords + + def __cancel_clicked_cb(self, widget, data=None): + self._detail_view.undo() + self._show_main_view() + + def __accept_clicked_cb(self, widget, data=None): + if self._detail_view.restart: + self._detail_toolbar.accept_button.set_sensitive(False) + alert = Alert() + alert.props.title = _('Warning') + alert.props.msg = _('Changes require restart to take effect') + + cancel_icon = Icon(icon_name='dialog-cancel') + alert.add_button(gtk.RESPONSE_CANCEL, _('Cancel'), cancel_icon) + cancel_icon.show() + + later_icon = Icon(icon_name='dialog-ok') + alert.add_button(gtk.RESPONSE_ACCEPT, _('Later'), later_icon) + later_icon.show() + + # TODO + # Handle restart + + self._vbox.pack_start(alert, False) + self._vbox.reorder_child(alert, 2) + alert.connect('response', self.__response_cb) + alert.show() + else: + self._show_main_view() + + def __response_cb(self, alert, response_id): + self._vbox.remove(alert) + if response_id is gtk.RESPONSE_CANCEL: + logging.debug('Cancel...') + self._detail_view.undo() + self._detail_toolbar.accept_button.set_sensitive(True) + elif response_id is gtk.RESPONSE_ACCEPT: + logging.debug('Later...') + self._options[self._current_option]['alerts'] = \ + self._detail_view.restart_alerts + self._show_main_view() + elif response_id is gtk.RESPONSE_APPLY: + logging.debug('Restart...') + + def __select_option_cb(self, button, event, option=None): + self._show_detail_view(option) + + def __search_changed_cb(self, maintoolbar, query): + self._update(query) + + def __stop_clicked_cb(self, widget, data=None): + self.destroy() + + def __valid_section_cb(self, widget, valid): + self._detail_toolbar.accept_button.set_sensitive(valid) + +class _GridWidget(gtk.EventBox): + __gtype_name__ = "SugarGridWidget" + + __gproperties__ = { + 'icon-name' : (str, None, None, None, + gobject.PARAM_READWRITE), + 'pixel-size' : (object, None, None, + gobject.PARAM_READWRITE), + 'xo-color' : (object, None, None, + gobject.PARAM_READWRITE), + 'title' : (str, None, None, None, + gobject.PARAM_READWRITE) + } + + def __init__(self, **kwargs): + self.icon_name = None + self.pixel_size = style.GRID_CELL_SIZE + self.xo_color = None + self.title = 'No Title' + + gobject.GObject.__init__(self, **kwargs) + + self._vbox = gtk.VBox() + self._icon = Icon(icon_name=self.icon_name, pixel_size=self.pixel_size, + xo_color=self.xo_color) + self._vbox.pack_start(self._icon, expand=False, fill=False) + + self._label = gtk.Label(self.title) + self._label.modify_fg(gtk.STATE_NORMAL, + style.COLOR_WHITE.get_gdk_color()) + self._vbox.pack_start(self._label, expand=False, fill=False) + + self._vbox.set_spacing(style.DEFAULT_SPACING) + self.set_visible_window(False) + self.set_app_paintable(True) + self.set_events(gtk.gdk.BUTTON_PRESS_MASK) + + self.add(self._vbox) + self._vbox.show() + self._label.show() + self._icon.show() + + def get_icon(self): + return self._icon + + def do_set_property(self, pspec, value): + if pspec.name == 'icon-name': + if self.icon_name != value: + self.icon_name = value + elif pspec.name == 'pixel-size': + if self.pixel_size != value: + self.pixel_size = value + elif pspec.name == 'xo-color': + if self.xo_color != value: + self.xo_color = value + elif pspec.name == 'title': + if self.title != value: + self.title = value + + def do_get_property(self, pspec): + if pspec.name == 'icon-name': + return self.icon_name + elif pspec.name == 'pixel-size': + return self.pixel_size + elif pspec.name == 'xo-color': + return self.xo_color + elif pspec.name == 'title': + return self.title diff --git a/src/controlpanel/icons/Makefile.am b/src/controlpanel/icons/Makefile.am new file mode 100644 index 0000000..1d5d478 --- /dev/null +++ b/src/controlpanel/icons/Makefile.am @@ -0,0 +1,10 @@ +sugardir = $(pkgdatadir)/shell/controlpanel/icons + +sugar_DATA = \ + module-about_me.svg \ + module-about_my_xo.svg \ + module-date_and_time.svg \ + module-language.svg \ + module-network.svg + +EXTRA_DIST = $(sugar_DATA) \ No newline at end of file diff --git a/src/controlpanel/icons/module-about_me.svg b/src/controlpanel/icons/module-about_me.svg new file mode 100644 index 0000000..7abe926 --- /dev/null +++ b/src/controlpanel/icons/module-about_me.svg @@ -0,0 +1,7 @@ + + +]> + + + \ No newline at end of file diff --git a/src/controlpanel/icons/module-about_my_xo.svg b/src/controlpanel/icons/module-about_my_xo.svg new file mode 100644 index 0000000..cf3528e --- /dev/null +++ b/src/controlpanel/icons/module-about_my_xo.svg @@ -0,0 +1,6 @@ + + +]> + + \ No newline at end of file diff --git a/src/controlpanel/icons/module-date_and_time.svg b/src/controlpanel/icons/module-date_and_time.svg new file mode 100644 index 0000000..605dbeb --- /dev/null +++ b/src/controlpanel/icons/module-date_and_time.svg @@ -0,0 +1,19 @@ + + +]> + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/controlpanel/icons/module-language.svg b/src/controlpanel/icons/module-language.svg new file mode 100644 index 0000000..ce04cb4 --- /dev/null +++ b/src/controlpanel/icons/module-language.svg @@ -0,0 +1,59 @@ + + +]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/controlpanel/icons/module-network.svg b/src/controlpanel/icons/module-network.svg new file mode 100644 index 0000000..a750a38 --- /dev/null +++ b/src/controlpanel/icons/module-network.svg @@ -0,0 +1,32 @@ + + +]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/controlpanel/inlinealert.py b/src/controlpanel/inlinealert.py new file mode 100644 index 0000000..ff14453 --- /dev/null +++ b/src/controlpanel/inlinealert.py @@ -0,0 +1,72 @@ +import gtk +import gobject +import pango + +from sugar.graphics import style + +class InlineAlert(gtk.EventBox): + """UI interface for Inline alerts + + Alerts are used inside the activity window instead of being a + separate popup window. They do not hide canvas content. You can + use add_alert(widget) and remove_alert(widget) inside your activity + to add and remove the alert. The position of the alert is below the + toolbox or top in fullscreen mode. + + Properties: + 'message': the message of the alert, + 'icon': the icon that appears at the far left + See __gproperties__ + """ + + __gtype_name__ = 'SugarInlineAlert' + + __gproperties__ = { + 'msg' : (str, None, None, None, + gobject.PARAM_READWRITE), + 'msg' : (str, None, None, None, + gobject.PARAM_READWRITE), + 'icon' : (object, None, None, + gobject.PARAM_WRITABLE) + } + + def __init__(self, **kwargs): + + self._msg = None + self._msg_color = None + self._icon = None + + self._hbox = gtk.HBox() + self._hbox.set_spacing(style.DEFAULT_SPACING) + + self._msg_label = gtk.Label() + self._msg_label.set_max_width_chars(50) + self._msg_label.set_ellipsize(pango.ELLIPSIZE_MIDDLE) + self._msg_label.set_alignment(0, 0.5) + self._msg_label.modify_fg(gtk.STATE_NORMAL, + style.COLOR_SELECTION_GREY.get_gdk_color()) + self._hbox.pack_start(self._msg_label, False) + + gobject.GObject.__init__(self, **kwargs) + + self.set_visible_window(True) + self.modify_bg(gtk.STATE_NORMAL, + style.COLOR_WHITE.get_gdk_color()) + self.add(self._hbox) + self._msg_label.show() + self._hbox.show() + + def do_set_property(self, pspec, value): + if pspec.name == 'msg': + if self._msg != value: + self._msg = value + self._msg_label.set_markup(self._msg) + elif pspec.name == 'icon': + if self._icon != value: + self._icon = value + self._hbox.pack_start(self._icon, False) + self._hbox.reorder_child(self._icon, 0) + + def do_get_property(self, pspec): + if pspec.name == 'msg': + return self._msg diff --git a/src/controlpanel/model/Makefile.am b/src/controlpanel/model/Makefile.am new file mode 100644 index 0000000..4bc3264 --- /dev/null +++ b/src/controlpanel/model/Makefile.am @@ -0,0 +1,8 @@ +sugardir = $(pkgdatadir)/shell/controlpanel/model +sugar_PYTHON = \ + __init__.py \ + network.py \ + aboutme.py \ + language.py \ + timezone.py \ + aboutxo.py diff --git a/src/controlpanel/model/__init__.py b/src/controlpanel/model/__init__.py new file mode 100644 index 0000000..2b0f269 --- /dev/null +++ b/src/controlpanel/model/__init__.py @@ -0,0 +1,17 @@ +# 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 +# + diff --git a/src/controlpanel/model/aboutme.py b/src/controlpanel/model/aboutme.py new file mode 100644 index 0000000..98726c6 --- /dev/null +++ b/src/controlpanel/model/aboutme.py @@ -0,0 +1,108 @@ +# 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 _ + +from sugar import profile +from sugar.graphics.xocolor import XoColor + +_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(): + return profile.get_nick_name() + +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.")) + pro = profile.get_profile() + pro.nick_name = nick + pro.save() + return 'RESTART' + +def get_color(): + return profile.get_color() + +def print_color(): + color = get_color().to_string() + tmp = color.split(',') + + stroke = None + fill = None + for color in _COLORS: + for hue in _COLORS[color]: + if _COLORS[color][hue] == tmp[0]: + stroke = (color, hue) + if _COLORS[color][hue] == tmp[1]: + fill = (color, hue) + + if stroke is not None: + print 'stroke: color=%s hue=%s' % (stroke[0], stroke[1]) + else: + print 'stroke: %s' % (tmp[0]) + if fill is not None: + print 'fill: color=%s hue=%s' % (fill[0], fill[1]) + else: + print 'fill: %s' % (tmp[1]) + +def set_color(stroke, fill, modstroke='medium', modfill='medium'): + """Set the system color by setting a fill and stroke color. + fill : [red, orange, yellow, blue, purple] + stroke : [red, orange, yellow, blue, purple] + hue stroke : [dark, medium, light] (optional) + hue fill : [dark, medium, light] (optional) + """ + + if modstroke not in _MODIFIERS or modfill 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 modstroke == modfill: + if modfill == 'medium': + modfill = 'light' + else: + modfill = 'medium' + + color = _COLORS[stroke][modstroke] + ',' + _COLORS[fill][modfill] + pro = profile.get_profile() + pro.color = XoColor(color) + pro.save() + return "RESTART" + +def set_color_xo(color): + """Set a color with an XoColor + """ + pro = profile.get_profile() + pro.color = color + pro.save() + return "RESTART" diff --git a/src/controlpanel/model/aboutxo.py b/src/controlpanel/model/aboutxo.py new file mode 100644 index 0000000..1a63f32 --- /dev/null +++ b/src/controlpanel/model/aboutxo.py @@ -0,0 +1,22 @@ +# 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 +# + +def get_aboutxo(): + return 'TODO: make info available here as well for the cml interface' + +def print_aboutxo(): + print get_aboutxo() diff --git a/src/controlpanel/model/language.py b/src/controlpanel/model/language.py new file mode 100644 index 0000000..49bac07 --- /dev/null +++ b/src/controlpanel/model/language.py @@ -0,0 +1,123 @@ +# 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 +from gettext import gettext as _ +import subprocess + +def readlocale(): + fdp = subprocess.Popen(['locale', '-av'], stdout=subprocess.PIPE) + lines = fdp.stdout.read().split('\n') + locales = [] + try: + for line in lines: + if line.find('locale:') != -1: + loc = line.lstrip('locale: ') + loc = loc.split('archive:')[0].strip() + elif line.find('language |') != -1: + lang = line.lstrip('language |') + elif line.find('territory |') != -1: + ter = line.lstrip('territory |') + if loc.endswith('utf8') and len(lang): + locales.append((lang, ter, loc)) + except Exception, error: + print "Error reading locale: %s" % error + locales.sort() + return locales + +def _initialize(): + languages = readlocale() + set_language.__doc__ += '\n' + for lang in languages: + set_language.__doc__ += '%s \n' % (lang[0].replace(' ', '_') + '/' + + lang[1].replace(' ', '_')) + +def _write_i18n(lang): + path = os.path.join(os.environ.get("HOME"), '.i18n') + if os.access(path, os.W_OK) == 0: + print(_("Could not access %s. Create standard settings.") % path) + fd = open(path, 'w') + fd.write('LANG="en_US.utf8"\n') + fd.close() + else: + fd = open(path, 'r') + lines = fd.readlines() + fd.close() + for i in range(len(lines)): + if lines[i][:5] == "LANG=": + lines[i] = 'LANG="' + lang + '"\n' + fd = open(path, 'w') + fd.writelines(lines) + fd.close() + +def get_language(): + path = os.path.join(os.environ.get("HOME"), '.i18n') + if os.access(path, os.R_OK) == 0: + print(_("Could not access %s. Create standard settings.") % path) + fd = open(path, 'w') + default = 'en_US.utf8' + fd.write('LANG="%s"\n' % default) + fd.close() + return default + + fd = open(path, "r") + lines = fd.readlines() + fd.close() + + lang = None + + for line in lines: + if line[:5] == "LANG=": + lang = line[5:].replace('"', '') + lang = lang.strip() + + return lang + +def print_language(): + code = get_language() + + languages = readlocale() + for lang in languages: + if lang[2] == code: + print lang[0].replace(' ', '_') + '/' + lang[1].replace(' ', '_') + return + print (_("Language for code=%s could not be determined.") % code) + +def set_language(language): + """Set the system language. + languages : + """ + if language.endswith('utf8'): + _write_i18n(language) + return "RESTART" + else: + languages = readlocale() + for lang in languages: + code = lang[0].replace(' ', '_') + '/' + lang[1].replace(' ', '_') + if code == language: + _write_i18n(lang[2]) + return "RESTART" + print (_("Sorry I do not speak \'%s\'.") % language) + +# inilialize the docstrings for the language +_initialize() + diff --git a/src/controlpanel/model/network.py b/src/controlpanel/model/network.py new file mode 100644 index 0000000..6190a4d --- /dev/null +++ b/src/controlpanel/model/network.py @@ -0,0 +1,86 @@ +# 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 _ + +from sugar import profile + +NM_SERVICE_NAME = 'org.freedesktop.NetworkManager' +NM_SERVICE_PATH = '/org/freedesktop/NetworkManager' +NM_SERVICE_IFACE = 'org.freedesktop.NetworkManager' +NM_ASLEEP = 1 + +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(): + pro = profile.get_profile() + return pro.jabber_server + +def print_jabber(): + print get_jabber() + +def set_jabber(server): + """Set the jabber server + server : e.g. 'olpc.collabora.co.uk' + """ + if not server: + raise ValueError(_("You must enter a server.")) + pro = profile.get_profile() + pro.jabber_server = server + pro.jabber_registered = False + pro.save() + return "RESTART" + +def get_radio(): + bus = dbus.SystemBus() + proxy = bus.get_object(NM_SERVICE_NAME, NM_SERVICE_PATH) + nm = dbus.Interface(proxy, NM_SERVICE_IFACE) + state = nm.getWirelessEnabled() + if state == 0: + return _('off') + elif state == 1: + return _('on') + else: + raise ReadError(_('State is unknown.')) + +def print_radio(): + print get_radio() + +def set_radio(state): + """Turn Radio 'on' or 'off' + state : 'on/off' + """ + if state == 'on': + bus = dbus.SystemBus() + proxy = bus.get_object(NM_SERVICE_NAME, NM_SERVICE_PATH) + nm = dbus.Interface(proxy, NM_SERVICE_IFACE) + nm.setWirelessEnabled(True) + elif state == 'off': + bus = dbus.SystemBus() + proxy = bus.get_object(NM_SERVICE_NAME, NM_SERVICE_PATH) + nm = dbus.Interface(proxy, NM_SERVICE_IFACE) + nm.setWirelessEnabled(False) + else: + raise ValueError(_("Error in specified radio argument use on/off.")) + diff --git a/src/controlpanel/model/timezone.py b/src/controlpanel/model/timezone.py new file mode 100644 index 0000000..5ebb9df --- /dev/null +++ b/src/controlpanel/model/timezone.py @@ -0,0 +1,80 @@ +# 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 _ + +from sugar import profile + +def _initialize(): + '''Initialize the docstring of the set function''' + timezones = read_timezones() + for timezone in timezones: + set_timezone.__doc__ += timezone + '\n' + +def read_timezones(fn='/usr/share/zoneinfo/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(-14, 13): + if offset < 0: + tz = 'GMT%d' % offset + elif offset > 0: + tz = 'GMT+%d' % offset + else: + tz = 'GMT' + timezones.append(tz) + return timezones + +def get_timezone(): + pro = profile.get_profile() + return pro.timezone + +def print_timezone(): + print get_timezone() + +def set_timezone(timezone): + """Set the system timezone + server : e.g. 'America/Los_Angeles' + """ + timezones = read_timezones() + if timezone in timezones: + os.environ['TZ'] = timezone + pro = profile.get_profile() + pro.timezone = timezone + pro.save() + else: + raise ValueError(_("Error timezone does not exist.")) + return 'RESTART' + +# inilialize the docstrings for the timezone +_initialize() + diff --git a/src/controlpanel/view/Makefile.am b/src/controlpanel/view/Makefile.am new file mode 100644 index 0000000..faffc20 --- /dev/null +++ b/src/controlpanel/view/Makefile.am @@ -0,0 +1,9 @@ +sugardir = $(pkgdatadir)/shell/controlpanel/view +sugar_PYTHON = \ + __init__.py \ + network.py \ + aboutme.py \ + language.py \ + timezone.py \ + aboutxo.py + diff --git a/src/controlpanel/view/__init__.py b/src/controlpanel/view/__init__.py new file mode 100644 index 0000000..2b0f269 --- /dev/null +++ b/src/controlpanel/view/__init__.py @@ -0,0 +1,17 @@ +# 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 +# + diff --git a/src/controlpanel/view/aboutme.py b/src/controlpanel/view/aboutme.py new file mode 100644 index 0000000..99e049f --- /dev/null +++ b/src/controlpanel/view/aboutme.py @@ -0,0 +1,231 @@ +# 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 gettext +import gobject + +_ = lambda msg: gettext.dgettext('sugar', msg) + +from sugar.graphics.icon import Icon +from sugar.graphics import style +from sugar.graphics.xocolor import XoColor +from sugar import profile + +from controlpanel.detailview import DetailView +from controlpanel.inlinealert import InlineAlert + +ICON = 'module-about_me' +COLOR = profile.get_color() +TITLE = _('About Me') + +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, xocolor): + EventIcon.__init__(self) + self.icon.props.xo_color = xocolor + self.icon.props.icon_name = 'computer-xo' + self.icon.props.pixel_size = style.XLARGE_ICON_SIZE + self.connect('button_press_event', self.__pressed_cb) + + def __pressed_cb(self, button, event): + self._set_random_colors() + + def _set_random_colors(self): + xocolor = XoColor() + self.icon.props.xo_color = xocolor + self.emit('color-changed', xocolor) + +class Aboutme(DetailView): + def __init__(self, model, alerts): + DetailView.__init__(self) + + self.emit('valid_section', True) + + self._model = model + self.restart_alerts = alerts + self._nick = self._model.get_nick() + self._xocolor = self._model.get_color() + + 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) + + group = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL) + + self._nick_box = gtk.HBox(spacing=style.DEFAULT_SPACING) + self._nick_alert_box = 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.pack_start(self._nick_box, False) + self.pack_start(self._nick_alert_box, False) + self.pack_start(self._color_box, False) + self.pack_start(self._color_alert_box, False) + + label_entry = gtk.Label(_('Name:')) + label_entry.modify_fg(gtk.STATE_NORMAL, + style.COLOR_SELECTION_GREY.get_gdk_color()) + group.add_widget(label_entry) + label_entry.set_alignment(1, 0.5) + self._nick_box.pack_start(label_entry, expand=False) + label_entry.show() + + label_entry_error = gtk.Label() + group.add_widget(label_entry_error) + self._nick_alert_box.pack_start(label_entry_error, expand=False) + label_entry_error.show() + icon = Icon(icon_name='emblem-warning', + fill_color=style.COLOR_SELECTION_GREY.get_svg(), + stroke_color=style.COLOR_WHITE.get_svg()) + self._nick_alert = InlineAlert(icon=icon) + icon.show() + 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._entry = gtk.Entry() + 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_text(self._nick) + self._entry.set_width_chars(25) + self._entry.connect('changed', self.__nick_changed_cb) + self._nick_box.pack_start(self._entry, expand=False) + self._entry.show() + + label_color = gtk.Label(_('Click to change your color:')) + label_color.modify_fg(gtk.STATE_NORMAL, + style.COLOR_SELECTION_GREY.get_gdk_color()) + group.add_widget(label_color) + self._color_box.pack_start(label_color, expand=False) + label_color.show() + + self._col = ColorPicker(self._xocolor) + self._col.connect('color-changed', self.__color_changed_cb) + self._color_box.pack_start(self._col, expand=False) + self._col.show() + + label_color_error = gtk.Label() + group.add_widget(label_color_error) + self._color_alert_box.pack_start(label_color_error, expand=False) + label_color_error.show() + icon = Icon(icon_name='emblem-warning', + fill_color=style.COLOR_SELECTION_GREY.get_svg(), + stroke_color=style.COLOR_WHITE.get_svg()) + self._color_alert = InlineAlert(icon=icon) + icon.show() + 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._nick_box.show() + self._color_box.show() + self._nick_alert_box.show() + self._color_alert_box.show() + + def undo(self): + self._model.set_nick(self._nick) + self._model.set_color_xo(self._xocolor) + + self._entry.set_text(self._nick) + self._col.icon.props.xo_color = self._xocolor + if self._color_alert.props.visible: + self._color_alert.hide() + + self._nick_valid = True + self._color_valid = True + self.restart = False + self.restart_alerts = [] + + def __nick_changed_cb(self, widget, data=None): + if self._nick_sid: + gobject.source_remove(self._nick_sid) + self._nick_sid = gobject.timeout_add(1000, self.__nick_timeout_cb, + widget) + + def __nick_timeout_cb(self, widget): + self._nick_sid = 0 + 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 + if widget.get_text() != self._nick: + self.restart = True + self.restart_alerts.append('nick') + else: + self.restart = False + + if self._nick_valid and self._color_valid: + self.emit('valid_section', True) + else: + self.emit('valid_section', False) + + if not self._nick_alert.props.visible or \ + widget.get_text() != self._nick: + self._nick_alert.show() + else: + self._nick_alert.hide() + + return False + + def __color_changed_cb(self, colorpicker, xocolor): + self._model.set_color_xo(xocolor) + self.restart = True + self._color_alert.props.msg = self._restart_msg + self._color_valid = True + self.restart_alerts.append('color') + + if self._nick_valid and self._color_valid: + self.emit('valid_section', True) + else: + self.emit('valid_section', False) + + if not self._color_alert.props.visible: + self._color_alert.show() + + + + + + diff --git a/src/controlpanel/view/aboutxo.py b/src/controlpanel/view/aboutxo.py new file mode 100644 index 0000000..0df0871 --- /dev/null +++ b/src/controlpanel/view/aboutxo.py @@ -0,0 +1,120 @@ +import re +import os +import gtk +import gettext +import logging +_ = lambda msg: gettext.dgettext('sugar', msg) + +from sugar.graphics import style + +from controlpanel.detailview import DetailView + +ICON = 'module-about_my_xo' +TITLE = _('About my XO') + +class Aboutxo(DetailView): + def __init__(self, model=None, alerts=None): + DetailView.__init__(self) + + self.set_border_width(style.DEFAULT_SPACING * 2) + self.set_spacing(style.DEFAULT_SPACING) + not_available = _('Not available') + group = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL) + + separator_identity = gtk.HSeparator() + self.pack_start(separator_identity, expand=False) + separator_identity.show() + label_identity = gtk.Label(_('Identity')) + label_identity.set_alignment(0, 0) + self.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) + group.add_widget(label_serial) + label_serial.show() + serial_no = self._read_file('/ofw/serial-number') + if serial_no is None: + serial_no = not_available + label_serial_no = gtk.Label(serial_no) + 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.pack_start(vbox_identity, expand=False) + vbox_identity.show() + + separator_software = gtk.HSeparator() + self.pack_start(separator_software, expand=False) + separator_software.show() + label_software = gtk.Label(_('Software')) + label_software.set_alignment(0, 0) + self.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) + group.add_widget(label_build) + label_build.show() + build_no = self._read_file('/boot/olpc_build') + if build_no is None: + build_no = not_available + label_build_no = gtk.Label(build_no) + 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_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) + group.add_widget(label_firmware) + label_firmware.show() + firmware_no = self._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] + label_firmware_no = gtk.Label(firmware_no) + 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() + self.pack_start(box_software, expand=False) + box_software.show() + + def _read_file(self, path): + if os.access(path, os.R_OK) == 0: + logging.error('read_file() No such file or directory: %s', path) + return None + + fd = open(path, 'r') + value = fd.read() + fd.close() + if value: + value = value.strip('\n') + return value + else: + logging.error('read_file() No information in file or directory: %s' + , path) + return None diff --git a/src/controlpanel/view/language.py b/src/controlpanel/view/language.py new file mode 100644 index 0000000..55b3f7f --- /dev/null +++ b/src/controlpanel/view/language.py @@ -0,0 +1,109 @@ +import gtk +import gobject +import gettext + +_ = lambda msg: gettext.dgettext('sugar', msg) + +from sugar.graphics import style +from sugar.graphics import iconentry + +from controlpanel.detailview import DetailView + +ICON = 'module-language' +TITLE = _('Language') + +class Language(DetailView): + def __init__(self, model, alerts): + DetailView.__init__(self) + self._model = model + + self.set_border_width(style.DEFAULT_SPACING * 2) + self.set_spacing(style.DEFAULT_SPACING) + + self.connect("realize", self.__realize_cb) + + self.restart = False + self._lang_sid = 0 + self._lang = self._model.get_language() + self._lang_set = self._lang + + 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, + gobject.TYPE_STRING) + locales = model.readlocale() + for locale in locales: + self._store.append([locale[2], '%s (%s)' % + (locale[0], locale[1])]) + + 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(1) + self._scrolled_window.add(self._treeview) + self._treeview.show() + + self._tvcolumn = gtk.TreeViewColumn(_('Language')) + self.cell = gtk.CellRendererText() + self._tvcolumn.pack_start(self.cell, True) + self._tvcolumn.add_attribute(self.cell, 'text', 1) + self._tvcolumn.set_sort_column_id(1) + self._treeview.append_column(self._tvcolumn) + + for row in self._store: + if self._lang in row[0]: + self._treeview.set_cursor(row.path, self._tvcolumn, False) + self._treeview.scroll_to_cell(row.path, self._tvcolumn, + True, 0.5, 0.5) + break + + self._treeview.connect("cursor-changed", self.__langchanged_cd) + + self.pack_start(self._scrolled_window) + self._scrolled_window.show() + + def undo(self): + self._model.set_language(self._lang) + self.restart = False + + def __realize_cb(self, widget): + self._entry.grab_focus() + + def _search(self, model, column_, key, iter_, data=None): + for row in model: + if key in row[1] or key.capitalize() in row[1]: + self._treeview.set_cursor(row.path, self._tvcolumn, False) + self._treeview.scroll_to_cell(row.path, self._tvcolumn, + True, 0.5, 0.5) + return True + return False + + def __langchanged_cd(self, treeview, data=None): + row = treeview.get_selection().get_selected() + if self._lang_set == self._store.get_value(row[1], 0): + return + + if self._lang_sid: + gobject.source_remove(self._lang_sid) + self._lang_sid = gobject.timeout_add(1000, self.__lang_timeout_cb, + self._store.get_value(row[1], 0)) + + def __lang_timeout_cb(self, code): + self._lang_sid = 0 + self._model.set_language(code) + self.restart = True + self._lang_set = code + return False diff --git a/src/controlpanel/view/network.py b/src/controlpanel/view/network.py new file mode 100644 index 0000000..c1814dd --- /dev/null +++ b/src/controlpanel/view/network.py @@ -0,0 +1,212 @@ +import gtk +import gobject +import gettext +_ = lambda msg: gettext.dgettext('sugar', msg) + +from sugar.graphics import style +from sugar.graphics.icon import Icon + +from controlpanel.detailview import DetailView +from controlpanel.inlinealert import InlineAlert + +ICON = 'module-network' +TITLE = _('Network') + +class Network(DetailView): + def __init__(self, model, alerts): + DetailView.__init__(self) + + self.emit('valid_section', True) + + self._jabber_sid = 0 + self._jabber_valid = True + self._radio_valid = True + self.restart_alerts = alerts + + self._model = model + self._jabber = self._model.get_jabber() + + 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) + + separator_wireless = gtk.HSeparator() + self.pack_start(separator_wireless, expand=False) + separator_wireless.show() + + label_wireless = gtk.Label(_('Wireless')) + label_wireless.set_alignment(0, 0) + self.pack_start(label_wireless, expand=False) + label_wireless.show() + box_wireless = gtk.VBox() + box_wireless.set_border_width(style.DEFAULT_SPACING * 2) + box_wireless.set_spacing(style.DEFAULT_SPACING) + box_radio = gtk.HBox(spacing=style.DEFAULT_SPACING) + label_radio = gtk.Label(_('Radio:')) + label_radio.set_alignment(1, 0.5) + label_radio.modify_fg(gtk.STATE_NORMAL, + style.COLOR_SELECTION_GREY.get_gdk_color()) + box_radio.pack_start(label_radio, expand=False) + group.add_widget(label_radio) + label_radio.show() + button = gtk.CheckButton() + button.set_alignment(0, 0) + button.connect('toggled', self.__radio_toggled_cb) + box_radio.pack_start(button, expand=False) + button.show() + box_wireless.pack_start(box_radio, expand=False) + box_radio.show() + + icon_radio = Icon(icon_name='emblem-warning', + fill_color=style.COLOR_SELECTION_GREY.get_svg(), + stroke_color=style.COLOR_WHITE.get_svg()) + self._radio_alert = InlineAlert(icon=icon_radio) + icon_radio.show() + label_radio_error = gtk.Label() + group.add_widget(label_radio_error) + self._radio_alert_box.pack_start(label_radio_error, expand=False) + label_radio_error.show() + self._radio_alert_box.pack_start(self._radio_alert, expand=False) + box_wireless.pack_end(self._radio_alert_box, expand=False) + self._radio_alert_box.show() + try: + self._radio_state = self._model.get_radio() + except Exception, detail: + self._radio_alert.props.msg = detail + self._radio_alert.show() + if 'radio' in self.restart_alerts: + self._radio_alert.props.msg = self._restart_msg + self._radio_alert.show() + + self.pack_start(box_wireless, expand=False) + box_wireless.show() + + separator_mesh = gtk.HSeparator() + self.pack_start(separator_mesh, False) + separator_mesh.show() + + label_mesh = gtk.Label(_('Mesh')) + label_mesh.set_alignment(0, 0) + self.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) + + 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) + self._entry.set_text(self._jabber) + self._entry.connect('changed', self.__jabber_changed_cb) + box_server.pack_start(self._entry, expand=False) + self._entry.show() + box_mesh.pack_start(box_server, expand=False) + box_server.show() + + icon_jabber = Icon(icon_name='emblem-warning', + fill_color=style.COLOR_SELECTION_GREY.get_svg(), + stroke_color=style.COLOR_WHITE.get_svg()) + self._jabber_alert = InlineAlert(icon=icon_jabber) + icon_jabber.show() + 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() + + self.pack_start(box_mesh, expand=False) + box_mesh.show() + + def undo(self): + self._model.set_jabber(self._jabber) + + self._entry.set_text(self._jabber) + + self._jabber_valid = True + self._radio_valid = True + self.restart = False + self.restart_alerts = [] + + def __radio_toggled_cb(self, widget, data=None): + radio_state = ('off', 'on')[widget.get_active()] + try: + self._model.set_radio(radio_state) + except Exception, detail: + self._radio_alert.props.msg = detail + self._radio_valid = False + else: + self._radio_alert.props.msg = self._restart_msg + self._radio_valid = True + if radio_state != self._radio_state: + self.restart = True + self.restart_alerts.append('radio') + else: + self.restart = False + + if self._radio_valid and self._jabber_valid: + self.emit('valid_section', True) + else: + self.emit('valid_section', False) + + if not self._radio_alert.props.visible or \ + radio_state != self._jabber: + self._radio_alert.show() + else: + self._radio_alert.hide() + + 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(1000, + self.__jabber_timeout_cb, widget) + + def __jabber_timeout_cb(self, widget): + self._jabber_sid = 0 + try: + self._model.set_jabber(widget.get_text()) + except ValueError, detail: + self._jabber_alert.props.msg = detail + self._jabber_valid = False + else: + self._jabber_alert.props.msg = self._restart_msg + self._jabber_valid = True + if widget.get_text() != self._jabber: + self.restart = True + self.restart_alerts.append('jabber') + else: + self.restart = False + + if self._jabber_valid and self._radio_valid: + self.emit('valid_section', True) + else: + self.emit('valid_section', False) + + if not self._jabber_alert.props.visible or \ + widget.get_text() != self._jabber: + self._jabber_alert.show() + else: + self._jabber_alert.hide() + + return False diff --git a/src/controlpanel/view/timezone.py b/src/controlpanel/view/timezone.py new file mode 100644 index 0000000..8c768cf --- /dev/null +++ b/src/controlpanel/view/timezone.py @@ -0,0 +1,108 @@ +import gtk +import gobject +import gettext +_ = lambda msg: gettext.dgettext('sugar', msg) + +from sugar.graphics import style +from sugar.graphics import iconentry + +from controlpanel.detailview import DetailView + +ICON = 'module-date_and_time' +TITLE = _('Date & Time') + +class Timezone(DetailView): + def __init__(self, model, alerts): + DetailView.__init__(self) + self._model = model + + self.set_border_width(style.DEFAULT_SPACING * 2) + self.set_spacing(style.DEFAULT_SPACING) + + self.connect("realize", self.__realize_cb) + + self.restart = False + self._zone_sid = 0 + self._zone = self._model.get_timezone() + self._zone_set = self._zone + + 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_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._tvcolumn = gtk.TreeViewColumn(_('Timezone')) + self.cell = gtk.CellRendererText() + self._tvcolumn.pack_start(self.cell, True) + self._tvcolumn.add_attribute(self.cell, 'text', 0) + self._tvcolumn.set_sort_column_id(0) + self._treeview.append_column(self._tvcolumn) + + for row in self._store: + if self._zone == row[0]: + self._treeview.set_cursor(row.path, self._tvcolumn, False) + self._treeview.scroll_to_cell(row.path, self._tvcolumn, + True, 0.5, 0.5) + break + + self._treeview.connect("cursor-changed", self.__zonechanged_cd) + + self.pack_start(self._scrolled_window) + self._scrolled_window.show() + + def undo(self): + self._model.set_timezone(self._zone) + self.restart = False + + def __realize_cb(self, widget): + self._entry.grab_focus() + + def _search(self, model, column_, key, iter_, data=None): + for row in model: + if key in row[0] or key.capitalize() in row[0]: + self._treeview.set_cursor(row.path, self._tvcolumn, False) + self._treeview.scroll_to_cell(row.path, self._tvcolumn, + True, 0.5, 0.5) + return True + return False + + def __zonechanged_cd(self, treeview, data=None): + list_, row = treeview.get_selection().get_selected() + if not row: + row = self._zone_set + if self._zone_set == self._store.get_value(row, 0): + return False + + if self._zone_sid: + gobject.source_remove(self._zone_sid) + self._zone_sid = gobject.timeout_add(1000, self.__lang_timeout_cb, row) + return True + + def __lang_timeout_cb(self, row): + self._zone_sid = 0 + self._model.set_timezone(self._store.get_value(row, 0)) + self.restart = True + self._zone_set = row + return False diff --git a/src/main.py b/src/main.py index 76d753f..3a88a37 100644 --- a/src/main.py +++ b/src/main.py @@ -110,6 +110,9 @@ def main(): win.show_all() gtk.main() + # set timezone + os.environ['TZ'] = get_profile().timezone + if os.environ.has_key("SUGAR_TP_DEBUG"): # Allow the user time to start up telepathy connection managers # using the Sugar DBus bus address diff --git a/src/view/home/activitiesring.py b/src/view/home/activitiesring.py index 6a4b22d..73b64e1 100644 --- a/src/view/home/activitiesring.py +++ b/src/view/home/activitiesring.py @@ -43,6 +43,7 @@ from view.home.MyIcon import MyIcon from model import shellmodel from model.shellmodel import ShellModel from hardware import schoolserver +from controlpanel.gui import ControlPanel _logger = logging.getLogger('ActivitiesRing') @@ -290,14 +291,14 @@ class _MyIcon(MyIcon): #secondary_text='Sample secondary label', icon=palette_icon) - item = MenuItem(_('About this XO')) + item = MenuItem(_('Control Panel')) icon = Icon(icon_name='computer-xo', icon_size=gtk.ICON_SIZE_MENU, xo_color=self._profile.color) item.set_image(icon) icon.show() - item.connect('activate', self._about_activate_cb) + item.connect('activate', self.__controlpanel_activate_cb) palette.menu.append(item) item.show() @@ -352,62 +353,13 @@ class _MyIcon(MyIcon): if self._profile.is_registered(): self.get_palette().menu.remove(menuitem) - def _about_activate_cb(self, menuitem): - dialog = gtk.Dialog(_('About this XO'), - self.palette, - gtk.DIALOG_MODAL | - gtk.DIALOG_DESTROY_WITH_PARENT, - (gtk.STOCK_OK, gtk.RESPONSE_OK)) - - not_available = _('Not available') - build = self._read_file('/boot/olpc_build') - if build is None: - build = not_available - label_build = gtk.Label('Build: %s' % build) - label_build.set_alignment(0, 0.5) - label_build.show() - vbox = dialog.get_child() - vbox.pack_start(label_build) - - firmware = self._read_file('/ofw/openprom/model') - if firmware is None: - firmware = not_available - else: - firmware = re.split(" +", firmware) - if len(firmware) == 3: - firmware = firmware[1] - label_firmware = gtk.Label('Firmware: %s' % firmware) - label_firmware.set_alignment(0, 0.5) - label_firmware.show() - vbox.pack_start(label_firmware) - - serial = self._read_file('/ofw/serial-number') - if serial is None: - serial = not_available - label_serial = gtk.Label('Serial Number: %s' % serial) - label_serial.set_alignment(0, 0.5) - label_serial.show() - vbox.pack_start(label_serial) - - dialog.set_default_response(gtk.RESPONSE_OK) - dialog.connect('response', self._response_cb) - dialog.show() - - def _read_file(self, path): - if os.access(path, os.R_OK) == 0: - _logger.error('read_file() No such file or directory: %s' % path) - return None - - fd = open(path, 'r') - value = fd.read() - fd.close() - if value: - value = value.strip('\n') - return value - else: - _logger.error('read_file() No information in file or directory: %s' - % path) - return None + def get_toplevel(self): + return hippo.get_canvas_for_item(self).get_toplevel() + + def __controlpanel_activate_cb(self, menuitem): + panel = ControlPanel() + panel.set_transient_for(self.get_toplevel()) + panel.show() def _response_cb(self, widget, response_id): if response_id == gtk.RESPONSE_OK: