diff --git a/NEWS b/NEWS index da1a6cc9..6ec29a9c 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,6 @@ * #4428 Revert to the trial-3 frame behavior (marco) +* Initial push for the sugar control panel (erikos) +* #4358: pydoc strings for sugar.activity.Activity Snapshot 176262f2e9 diff --git a/lib/sugar/activity/activity.py b/lib/sugar/activity/activity.py index 731a88aa..14cadd76 100644 --- a/lib/sugar/activity/activity.py +++ b/lib/sugar/activity/activity.py @@ -1,7 +1,31 @@ -"""Base class for Python-coded activities +"""Base class for activities written in Python -This is currently the only reference for what an +This is currently the only definitive reference for what an activity must do to participate in the Sugar desktop. + + A Basic Activity + +All activities must implement a class derived from 'Activity' in this class. +The convention is to call it ActivitynameActivity, but this is not required as +the activity.info file associated with your activity will tell the sugar-shell +which class to start. + +For example the most minimal Activity: + + + from sugar.activity import activity + + class ReadActivity(activity.Activity): + pass + +To get a real, working activity, you will at least have to implement: + __init__(), read_file() and write_file() + +Aditionally, you will probably need a at least a Toolbar so you can have some +interesting buttons for the user, like for example 'exit activity' + +See the methods of the Activity class below for more information on what you +will need for a real activity. """ # Copyright (C) 2006-2007 Red Hat, Inc. # @@ -49,6 +73,11 @@ SCOPE_INVITE_ONLY = "invite" # shouldn't be shown in UI, it's implicit when you SCOPE_NEIGHBORHOOD = "public" class ActivityToolbar(gtk.Toolbar): + """The Activity toolbar with the Journal entry title, sharing, Keep and Stop buttons + + All activities should have this toolbar. It is easiest to add it to your + Activity by using the ActivityToolbox. + """ def __init__(self, activity): gtk.Toolbar.__init__(self) @@ -169,6 +198,38 @@ class ActivityToolbar(gtk.Toolbar): self._update_share() class EditToolbar(gtk.Toolbar): + """Provides the standard edit toolbar for Activities. + + Members: + undo -- the undo button + redo -- the redo button + copy -- the copy button + paste -- the paste button + separator -- A separator between undo/redo and copy/paste + + This class only provides the 'edit' buttons in a standard layout, your activity + will need to either hide buttons which make no sense for your Activity, or you + need to connect the button events to your own callbacks: + + ## Example from Read.activity: + # Create the edit toolbar: + self._edit_toolbar = EditToolbar(self._view) + # Hide undo and redo, they're not needed + self._edit_toolbar.undo.props.visible = False + self._edit_toolbar.redo.props.visible = False + # Hide the separator too: + self._edit_toolbar.separator.props.visible = False + + # As long as nothing is selected, copy needs to be insensitive: + self._edit_toolbar.copy.set_sensitive(False) + # When the user clicks the button, call _edit_toolbar_copy_cb() + self._edit_toolbar.copy.connect('clicked', self._edit_toolbar_copy_cb) + + # Add the edit toolbar: + toolbox.add_toolbar(_('Edit'), self._edit_toolbar) + # And make it visible: + self._edit_toolbar.show() + """ def __init__(self): gtk.Toolbar.__init__(self) @@ -198,6 +259,23 @@ class EditToolbar(gtk.Toolbar): self.paste.show() class ActivityToolbox(Toolbox): + """Creates the Toolbox for the Activity + + By default, the toolbox contains only the ActivityToolbar. After creating the + toolbox, you can add your activity specific toolbars, for example the + EditToolbar. + + To add the ActivityToolbox to your Activity in MyActivity.__init__() do: + + # Create the Toolbar with the ActivityToolbar: + toolbox = activity.ActivityToolbox(self) + ... your code, inserting all other toolbars you need, like EditToolbar ... + + # Add the toolbox to the activity frame: + self.set_toolbox(toolbox) + # And make it visible: + toolbox.show() + """ def __init__(self, activity): Toolbox.__init__(self) @@ -209,7 +287,71 @@ class ActivityToolbox(Toolbox): return self._activity_toolbar class Activity(Window, gtk.Container): - """Base Activity class that all other Activities derive from.""" + """This is the base Activity class that all other Activities derive from. This is where your activity starts. + + To get a working Activity: + 0. Derive your Activity from this class: + class MyActivity(activity.Activity): + ... + + 1. implement an __init__() method for your Activity class. + + Use your init method to create your own ActivityToolbar which will + contain some standard buttons: + toolbox = activity.ActivityToolbox(self) + + Add extra Toolbars to your toolbox. + + You should setup Activity sharing here too. + + Finaly, your Activity may need some resources which you can claim + here too. + + The __init__() method is also used to make the distinction between + being resumed from the Journal, or starting with a blank document. + + 2. Implement read_file() and write_file() + Most activities revolve around creating and storing Journal entries. + For example, Write: You create a document, it is saved to the Journal + and then later you resume working on the document. + + read_file() and write_file() will be called by sugar to tell your + Activity that it should load or save the document the user is working + on. + + 3. Implement our Activity Toolbars. + The Toolbars are added to your Activity in step 1 (the toolbox), but + you need to implement them somewhere. Now is a good time. + + There are a number of standard Toolbars. The most basic one, the one + your almost absolutely MUST have is the ActivityToolbar. Without + this, you're not really making a proper Sugar Activity (which may be + okay, but you should really stop and think about why not!) You do + this with the ActivityToolbox(self) call in step 1. + + Usually, you will also need the standard EditToolbar. This is the one + which has the standard copy and paste buttons. You need to derive + your own EditToolbar class from sugar.EditToolbar: + class EditToolbar(activity.EditToolbar): + ... + + See EditToolbar for the methods you should implement in your class. + + Finaly, your Activity will very likely need some activity specific + buttons and options you can create your own toolbars by deriving a + class from gtk.Toolbar: + class MySpecialToolbar(gtk.Toolbar): + ... + + 4. Use your creativity. Make your Activity something special and share + it with your friends! + + Read through the methods of the Activity class below, to learn more about + how to make an Activity work. + + Hint: A good and simple Activity to learn from is the Read activity. To + create your own activity, you may want to copy it and use it as a template. + """ __gtype_name__ = 'SugarActivity' __gsignals__ = { @@ -248,6 +390,11 @@ class Activity(Window, gtk.Container): Creates an ActivityService (self._bus) servicing this application. + + Usage: + If your Activity implements __init__(), it should call + the base class __init()__ before doing Activity specific things. + """ Window.__init__(self) @@ -360,12 +507,25 @@ class Activity(Window, gtk.Container): return self._max_participants def get_id(self): + """Returns the activity id of the current instance of your activity. + + The activity id is sort-of-like the unix process id (PID). However, + unlike PIDs it is only different for each new instance (with + create_jobject = True set) and stays the same everytime a user + resumes an activity. This is also the identity of your Activity to other + XOs for use when sharing. + """ return self._activity_id def get_bundle_id(self): + """Returns the bundle_id from the activity.info file""" return os.environ['SUGAR_BUNDLE_ID'] def set_canvas(self, canvas): + """Sets the 'work area' of your activity with the canvas of your choice. + + One commonly used canvas is gtk.ScrolledWindow + """ Window.set_canvas(self, canvas) canvas.connect('map', self.__canvas_map_cb) @@ -380,10 +540,17 @@ class Activity(Window, gtk.Container): logging.debug("Error creating activity datastore object: %s" % err) def get_activity_root(self): - """ - Return the appropriate location in the fs where to store activity related - data that doesn't pertain to the current execution of the activity and - thus cannot go into the DataStore. + """Returns a path for saving Activity specific preferences, etc. + + Returns a path to the location in the filesystem where the activity can + store activity related data that doesn't pertain to the current + execution of the activity and thus cannot go into the DataStore. + + Currently, this will return something like ~/.sugar/default/MyActivityName/ + + Activities should ONLY save settings, user preferences and other data + which isn't specific to a journal item here. If (meta-)data is in anyway + specific to a journal entry, it MUST be stored in the DataStore. """ if os.environ.has_key('SUGAR_ACTIVITY_ROOT') and \ os.environ['SUGAR_ACTIVITY_ROOT']: @@ -395,6 +562,17 @@ class Activity(Window, gtk.Container): """ Subclasses implement this method if they support resuming objects from the journal. 'file_path' is the file to read from. + + You should immediately open the file from the file_path, because the + file_name will be deleted immediately after returning from read_file(). + Once the file has been opened, you do not have to read it immediately: + After you have opened it, the file will only be really gone when you + close it. + + Although not required, this is also a good time to read all meta-data: + the file itself cannot be changed externally, but the title, description + and other metadata['tags'] may change. So if it is important for you to + notice changes, this is the time to record the originals. """ raise NotImplementedError @@ -402,6 +580,17 @@ class Activity(Window, gtk.Container): """ Subclasses implement this method if they support saving data to objects in the journal. 'file_path' is the file to write to. + + If the user did make changes, you should create the file_path and save + all document data to it. + + Additionally, you should also write any metadata needed to resume your + activity. For example, the Read activity saves the current page and zoom + level, so it can display the page. + + Note: Currently, the file_path *WILL* be different from the one you + received in file_read(). Even if you kept the file_path from file_read() + open until now, you must still write the entire file to this file_path. """ raise NotImplementedError @@ -466,7 +655,13 @@ class Activity(Window, gtk.Container): self._preview = self._get_preview() def save(self): - """Request that the activity is saved to the Journal.""" + """Request that the activity is saved to the Journal. + + This method is called by the close() method below. In general, + activities should not override this method. This method is part of the + public API of an Acivity, and should behave in standard ways. Use your + own implementation of write_file() to save your Activity specific data. + """ logging.debug('Activity.save: %r' % self._jobject.object_id) @@ -507,6 +702,11 @@ class Activity(Window, gtk.Container): error_handler=self.__save_error_cb) def copy(self): + """Request that the activity 'Keep in Journal' the current state of the activity. + + Activities should not override this method. Instead, like save() do any + copy work that needs to be done in write_file() + """ logging.debug('Activity.copy: %r' % self._jobject.object_id) self._preview = self._get_preview() self.save() @@ -570,6 +770,12 @@ class Activity(Window, gtk.Container): logging.error('Cannot invite %s, no such buddy.' % buddy_key) def invite(self, buddy_key): + """Invite a buddy to join this Activity. + + Side Effects: + Calls self.share(True) to privately share the activity if it wasn't + shared before. + """ self._invites_queue.append(buddy_key) if (self._shared_activity is None @@ -598,6 +804,11 @@ class Activity(Window, gtk.Container): self._pservice.share_activity(self, private=private) def close(self): + """Request that the activity be stopped and saved to the Journal + + Activities should not override this method, but should implement write_file() to + do any state saving instead. + """ self.save() if self._shared_activity: @@ -617,6 +828,17 @@ class Activity(Window, gtk.Container): return True def get_metadata(self): + """Returns the jobject metadata or None if there is no jobject. + + Activities can set metadata in write_file() using: + self.metadata['MyKey'] = "Something" + + and retrieve metadata in read_file() using: + self.metadata.get('MyKey', 'aDefaultValue') + + Note: Make sure your activity works properly if one or more of the + metadata items is missing. Never assume they will all be present. + """ if self._jobject: return self._jobject.metadata else: @@ -625,12 +847,10 @@ class Activity(Window, gtk.Container): metadata = property(get_metadata, None) def get_bundle_name(): - """Return the bundle name for the current process' bundle - """ + """Return the bundle name for the current process' bundle""" return os.environ['SUGAR_BUNDLE_NAME'] def get_bundle_path(): - """Return the bundle path for the current process' bundle - """ + """Return the bundle path for the current process' bundle""" return os.environ['SUGAR_BUNDLE_PATH'] diff --git a/shell/controlpanel/control.py b/shell/controlpanel/control.py new file mode 100644 index 00000000..cf2b688b --- /dev/null +++ b/shell/controlpanel/control.py @@ -0,0 +1,425 @@ +# Copyright (C) 2007, One Laptop Per Child +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. +# +# +# The language config is based on the system-config-language +# (http://fedoraproject.org/wiki/SystemConfig/language) tool +# and the timezone config on the system-config-date +# (http://fedoraproject.org/wiki/SystemConfig/date) tool. +# Parts of the code were reused. +# + +import os +import string +import shutil +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') + +_TIMEZONE_CONFIG = '/etc/sysconfig/clock' + +_LANGUAGES = { + 'Afrikaans/South_Africa': ('af_ZA', 'lat0-sun16'), + 'Albanian': ('sq_AL.UTF-8', 'latarcyrheb-sun16'), + 'Arabic/Algeria': ('ar_DZ.UTF-8', 'latarcyrheb-sun16'), + 'Arabic/Bahrain': ('ar_BH.UTF-8', 'latarcyrheb-sun16'), + 'Arabic/Egypt': ('ar_EG.UTF-8', 'latarcyrheb-sun16'), + 'Arabic/India': ('ar_IN.UTF-8', 'latarcyrheb-sun16'), + 'Arabic/Iraq': ('ar_IQ.UTF-8', 'latarcyrheb-sun16'), + 'Arabic/Jordan': ('ar_JO.UTF-8', 'latarcyrheb-sun16'), + 'Arabic/Kuwait': ('ar_KW.UTF-8', 'latarcyrheb-sun16'), + 'Arabic/Lebanon': ('ar_LB.UTF-8', 'latarcyrheb-sun16'), + 'Arabic/Libyan_Arab_Jamahiriya': ('ar_LY.UTF-8', 'latarcyrheb-sun16'), + 'Arabic/Morocco': ('ar_MA.UTF-8', 'latarcyrheb-sun16'), + 'Arabic/Oman': ('ar_OM.UTF-8', 'latarcyrheb-sun16'), + 'Arabic/Qatar': ('ar_QA.UTF-8', 'latarcyrheb-sun16'), + 'Arabic/Saudi_Arabia': ('ar_SA.UTF-8', 'latarcyrheb-sun16'), + 'Arabic/Sudan': ('ar_SD.UTF-8', 'latarcyrheb-sun16'), + 'Arabic/Syrian_Arab_Republic': ('ar_SY.UTF-8', 'latarcyrheb-sun16'), + 'Arabic/Tunisia': ('ar_TN.UTF-8', 'latarcyrheb-sun16'), + 'Arabic/United_Arab_Emirates': ('ar_AE.UTF-8', 'latarcyrheb-sun16'), + 'Arabic/Yemen': ('ar_YE.UTF-8', 'latarcyrheb-sun16'), + 'Basque/Spain': ('eu_ES.UTF-8', 'latarcyrheb-sun16'), + 'Belarusian': ('be_BY.UTF-8', 'latarcyrheb-sun16'), + 'Bengali/BD': ('bn_BD.UTF-8', 'latarcyrheb-sun16'), + 'Bengali/India': ('bn_IN.UTF-8', 'latarcyrheb-sun16'), + 'Bosnian/Bosnia_and_Herzegowina': ('bs_BA', 'lat2-sun16'), + 'Breton/France': ('br_FR', 'lat0-sun16'), + 'Bulgarian': ('bg_BG.UTF-8', 'latarcyrheb-sun16'), + 'Catalan/Spain': ('ca_ES.UTF-8', 'latarcyrheb-sun16'), + 'Chinese/Hong_Kong': ('zh_HK.UTF-8', 'latarcyrheb-sun16'), + 'Chinese/P.R._of_China': ('zh_CN.UTF-8', 'lat0-sun16'), + 'Chinese/Taiwan': ('zh_TW.UTF-8', 'lat0-sun16'), + 'Cornish/Britain': ('kw_GB.UTF-8', 'latarcyrheb-sun16'), + 'Croatian': ('hr_HR.UTF-8', 'latarcyrheb-sun16'), + 'Czech': ('cs_CZ.UTF-8', 'latarcyrheb-sun16'), + 'Danish': ('da_DK.UTF-8', 'latarcyrheb-sun16'), + 'Dutch/Belgium': ('nl_BE.UTF-8', 'latarcyrheb-sun16'), + 'Dutch/Netherlands': ('nl_NL.UTF-8', 'latarcyrheb-sun16'), + 'English/Australia': ('en_AU.UTF-8', 'latarcyrheb-sun16'), + 'English/Botswana': ('en_BW.UTF-8', 'latarcyrheb-sun16'), + 'English/Canada': ('en_CA.UTF-8', 'latarcyrheb-sun16'), + 'English/Denmark': ('en_DK.UTF-8', 'latarcyrheb-sun16'), + 'English/Great_Britain': ('en_GB.UTF-8', 'latarcyrheb-sun16'), + 'English/Hong_Kong': ('en_HK.UTF-8', 'latarcyrheb-sun16'), + 'English/India': ('en_IN.UTF-8', 'latarcyrheb-sun16'), + 'English/Ireland': ('en_IE.UTF-8', 'latarcyrheb-sun16'), + 'English/New_Zealand': ('en_NZ.UTF-8', 'latarcyrheb-sun16'), + 'English/Philippines': ('en_PH.UTF-8', 'latarcyrheb-sun16'), + 'English/Singapore': ('en_SG.UTF-8', 'latarcyrheb-sun16'), + 'English/South_Africa': ('en_ZA.UTF-8', 'latarcyrheb-sun16'), + 'English/USA': ('en_US.UTF-8', 'latarcyrheb-sun16'), + 'English/Zimbabwe': ('en_ZW.UTF-8', 'latarcyrheb-sun16'), + 'Estonian': ('et_EE.UTF-8', 'latarcyrheb-sun16'), + 'Faroese/Faroe_Islands': ('fo_FO.UTF-8', 'latarcyrheb-sun16'), + 'Finnish': ('fi_FI.UTF-8', 'latarcyrheb-sun16'), + 'French/Belgium': ('fr_BE.UTF-8', 'latarcyrheb-sun16'), + 'French/Canada': ('fr_CA.UTF-8', 'latarcyrheb-sun16'), + 'French/France': ('fr_FR.UTF-8', 'latarcyrheb-sun16'), + 'French/Luxemburg': ('fr_LU.UTF-8', 'latarcyrheb-sun16'), + 'French/Switzerland': ('fr_CH.UTF-8', 'latarcyrheb-sun16'), + 'Galician/Spain': ('gl_ES.UTF-8', 'latarcyrheb-sun16'), + 'German/Austria': ('de_AT.UTF-8', 'latarcyrheb-sun16'), + 'German/Belgium': ('de_BE.UTF-8', 'latarcyrheb-sun16'), + 'German/Germany': ('de_DE.UTF-8', 'latarcyrheb-sun16'), + 'German/Luxemburg': ('de_LU.UTF-8', 'latarcyrheb-sun16'), + 'German/Switzerland': ('de_CH.UTF-8', 'latarcyrheb-sun16'), + 'Greek': ('el_GR.UTF-8', 'latarcyrheb-sun16'), + 'Greenlandic/Greenland': ('kl_GL.UTF-8', 'latarcyrheb-sun16'), + 'Gujarati/India': ('gu_IN.UTF-8', 'latarcyrheb-sun16'), + 'Hebrew/Israel': ('he_IL.UTF-8', 'latarcyrheb-sun16'), + 'Hindi/India': ('hi_IN.UTF-8', 'latarcyrheb-sun16'), + 'Hungarian': ('hu_HU.UTF-8', 'latarcyrheb-sun16'), + 'Icelandic': ('is_IS.UTF-8', 'latarcyrheb-sun16'), + 'Indonesian': ('id_ID.UTF-8', 'latarcyrheb-sun16'), + 'Irish': ('ga_IE.UTF-8', 'latarcyrheb-sun16'), + 'Italian/Italy': ('it_IT.UTF-8', 'latarcyrheb-sun16'), + 'Italian/Switzerland': ('it_CH.UTF-8', 'latarcyrheb-sun16'), + 'Japanese': ('ja_JP.UTF-8', 'lat0-sun16'), + 'Korean/Republic_of_Korea': ('ko_KR.UTF-8', 'lat0-sun16'), + 'Lao/Laos': ('lo_LA.UTF-8', 'latarcyrheb-sun16'), + 'Latvian/Latvia': ('lv_LV.UTF-8', 'latarcyrheb-sun16'), + 'Lithuanian': ('lt_LT.UTF-8', 'latarcyrheb-sun16'), + 'Macedonian': ('mk_MK.UTF-8', 'latarcyrheb-sun16'), + 'Malay/Malaysia': ('ms_MY.UTF-8', 'latarcyrheb-sun16'), + 'Maltese/malta': ('mt_MT.UTF-8', 'latarcyrheb-sun16'), + 'Manx/Britain': ('gv_GB.UTF-8', 'latarcyrheb-sun16'), + 'Marathi/India': ('mr_IN.UTF-8', 'latarcyrheb-sun16'), + 'Northern/Norway': ('se_NO', 'latarcyrheb-sun16'), + 'Norwegian': ('nb_NO.UTF-8', 'latarcyrheb-sun16'), + 'Norwegian,/Norway': ('nn_NO.UTF-8', 'latarcyrheb-sun16'), + 'Occitan/France': ('oc_FR', 'lat0-sun16'), + 'Oriya/India': ('or_IN.UTF-8', 'latarcyrheb-sun16'), + 'Persian/Iran': ('fa_IR.UTF-8', 'latarcyrheb-sun16'), + 'Polish': ('pl_PL.UTF-8', 'latarcyrheb-sun16'), + 'Portuguese/Brasil': ('pt_BR.UTF-8', 'latarcyrheb-sun16'), + 'Portuguese/Portugal': ('pt_PT.UTF-8', 'latarcyrheb-sun16'), + 'Punjabi/India': ('pa_IN.UTF-8', 'latarcyrheb-sun16'), + 'Romanian': ('ro_RO.UTF-8', 'latarcyrheb-sun16'), + 'Russian': ('ru_RU.UTF-8', 'latarcyrheb-sun16'), + 'Russian/Ukraine': ('ru_UA.UTF-8', 'latarcyrheb-sun16'), + 'Serbian': ('sr_CS.UTF-8', 'latarcyrheb-sun16'), + 'Serbian/Latin': ('sr_CS.UTF-8@Latn', 'latarcyrheb-sun16'), + 'Slovak': ('sk_SK.UTF-8', 'latarcyrheb-sun16'), + 'Slovenian/Slovenia': ('sl_SI.UTF-8', 'latarcyrheb-sun16'), + 'Spanish/Argentina': ('es_AR.UTF-8', 'latarcyrheb-sun16'), + 'Spanish/Bolivia': ('es_BO.UTF-8', 'latarcyrheb-sun16'), + 'Spanish/Chile': ('es_CL.UTF-8', 'latarcyrheb-sun16'), + 'Spanish/Colombia': ('es_CO.UTF-8', 'latarcyrheb-sun16'), + 'Spanish/Costa_Rica': ('es_CR.UTF-8', 'latarcyrheb-sun16'), + 'Spanish/Dominican_Republic': ('es_DO.UTF-8', 'latarcyrheb-sun16'), + 'Spanish/El_Salvador': ('es_SV.UTF-8', 'latarcyrheb-sun16'), + 'Spanish/Equador': ('es_EC.UTF-8', 'latarcyrheb-sun16'), + 'Spanish/Guatemala': ('es_GT.UTF-8', 'latarcyrheb-sun16'), + 'Spanish/Honduras': ('es_HN.UTF-8', 'latarcyrheb-sun16'), + 'Spanish/Mexico': ('es_MX.UTF-8', 'latarcyrheb-sun16'), + 'Spanish/Nicaragua': ('es_NI.UTF-8', 'latarcyrheb-sun16'), + 'Spanish/Panama': ('es_PA.UTF-8', 'latarcyrheb-sun16'), + 'Spanish/Paraguay': ('es_PY.UTF-8', 'latarcyrheb-sun16'), + 'Spanish/Peru': ('es_PE.UTF-8', 'latarcyrheb-sun16'), + 'Spanish/Puerto_Rico': ('es_PR.UTF-8', 'latarcyrheb-sun16'), + 'Spanish/Spain': ('es_ES.UTF-8', 'latarcyrheb-sun16'), + 'Spanish/USA': ('es_US.UTF-8', 'latarcyrheb-sun16'), + 'Spanish/Uruguay': ('es_UY.UTF-8', 'latarcyrheb-sun16'), + 'Spanish/Venezuela': ('es_VE.UTF-8', 'latarcyrheb-sun16'), + 'Swedish/Finland': ('sv_FI.UTF-8', 'latarcyrheb-sun16'), + 'Swedish/Sweden': ('sv_SE.UTF-8', 'latarcyrheb-sun16'), + 'Tagalog/Philippines': ('tl_PH', 'lat0-sun16'), + 'Tamil/India': ('ta_IN.UTF-8', 'latarcyrheb-sun16'), + 'Telugu/India': ('te_IN.UTF-8', 'latarcyrheb-sun16'), + 'Thai': ('th_TH.UTF-8', 'latarcyrheb-sun16'), + 'Turkish': ('tr_TR.UTF-8', 'latarcyrheb-sun16'), + 'Ukrainian': ('uk_UA.UTF-8', 'latarcyrheb-sun16'), + 'Urdu/Pakistan': ('ur_PK', 'latarcyrheb-sun16'), + 'Uzbek/Uzbekistan': ('uz_UZ', 'lat0-sun16'), + 'Walloon/Belgium': ('wa_BE@euro', 'lat0-sun16'), + 'Welsh/Great_Britain': ('cy_GB.UTF-8', 'latarcyrheb-sun16'), + 'Xhosa/South_Africa': ('xh_ZA.UTF-8', 'latarcyrheb-sun16'), + 'Zulu/South_Africa': ('zu_ZA.UTF-8', 'latarcyrheb-sun16') + } + +_timezones = [] + +def _initialize(): + _timezones = _read_zonetab() + j=0 + for timezone in _timezones: + set_timezone.__doc__ += timezone+', ' + j+=1 + if j%3 == 0: + set_timezone.__doc__ += '\n' + + if not os.access(_TIMEZONE_CONFIG, os.R_OK): + #Theres no /etc/sysconfig/clock file, so make one + fd = open(_TIMEZONE_CONFIG, 'w') + f.write(' The ZONE parameter is only evaluated by sugarcontrol.\n') + f.write('The timezone of the system' + + ' is defined by the contents of /etc/localtime.\n') + f.write('ZONE="America/NEW_York"\n') + f.close() + + keys = _LANGUAGES.keys() + keys.sort() + i = 0 + for key in keys: + set_language.__doc__ += key+', ' + i+=1 + if i%3 == 0: + set_language.__doc__ += '\n' + +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 : 'olpc.collabora.co.uk' + """ + pro = profile.get_profile() + pro.jabber_server = server + pro.save() + +def get_color(): + return profile.get_color() + +def print_color(): + print get_color().to_string() + +def set_color(stroke, fill, modstroke='medium', modfill='medium'): + """Set the system color. + fill : 'red, orange, yellow, blue, purple' + stroke : 'red, orange, yellow, blue, purple' + modstroke : 'dark, medium, light' + modfill : ''dark, medium, light' + """ + + 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() + +def get_nick(): + return profile.get_nick_name() + +def print_nick(): + print get_nick() + +def set_nick(nick): + """Set the nickname. + nick : 'erikos' + """ + pro = profile.get_profile() + pro.nick_name = nick + pro.save() + +def get_radio(state): + return '' + +def print_radio(self): + print get_radio() + +def set_radio(state): + """Turn Radio off + state : 'on/off' + """ + if state == 'on': + cmd = '/sbin/iwconfig eth0 txpower on' + handle = os.popen(cmd, 'r') + print string.join(handle.readlines()) + handle.close() + elif state == 'off': + cmd = '/sbin/iwconfig eth0 txpower off' + handle = os.popen(cmd, 'r') + print string.join(handle.readlines()) + handle.close() + else: + print (_("Error in specified radio argument use on/off.")) + +def get_timezone(): + fd = open(_TIMEZONE_CONFIG, "r") + lines = fd.readlines() + fd.close() + try: + for line in lines: + line = string.strip(line) + if len (line) and line[0] == '#': + continue + try: + tokens = string.split(line, "=") + if tokens[0] == "ZONE": + timezone = string.replace(tokens[1], '"', '') + return timezone + except Exception, e: + print (_("get_timezone: %s") % e) + except Exception, e: + print (_("get_timezone: %s") % e) + return None + +def print_timezone(): + timezone = get_timezone() + if timezone is None: + print (_("Error in reading timezone")) + else: + print timezone + +def set_timezone(timezone): + """Set the system timezone + timezone : + """ + if timezone in _timezones: + fromfile = os.path.join("/usr/share/zoneinfo/", timezone) + try: + shutil.copyfile(fromfile, "/etc/localtime") + except OSError, (errno, msg): + print (_("Error copying timezone (from %s): %s")%(fromfile, msg)) + return + try: + os.chmod("/etc/localtime", 0644) + except OSError, (errno, msg): + print (_("Changing permission of timezone: %s") % (msg)) + return + + # Write info to the /etc/sysconfig/clock file + fd = open(_TIMEZONE_CONFIG, "w") + fd.write('# The ZONE parameter is only evaluated by sugarcontrol.\n') + fd.write('# The timezone of the system ' + + 'is defined by the contents of /etc/localtime.\n') + fd.write('ZONE="%s"\n' % timezone) + fd.close() + else: + print (_("Error timezone does not exist.")) + +def _read_zonetab(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() + return timezones + +def _remove_encoding(lang): + if '.' in lang: + langBase = lang.split('.') + return langBase[0] + elif '@' in lang: + langBase = lang.split('@') + return langBase[0] + else: + return lang + +def _writeI18N(lang, sysfont): + path = '/etc/sysconfig/i18n' + if os.access(path, os.R_OK) == 0: + print(_("Could not access %s")%path) + else: + fd = open(path, 'w') + fd.write('LANG="' + lang + '"\n') + fd.write('SYSFONT="' + sysfont + '"\n') + fd.close() + +def get_language(): + originalFile = None + path = '/etc/sysconfig/i18n' + if os.access(path, os.R_OK) == 0: + return None + else: + fd = open(path, "r") + originalFile = fd.readlines() + fd.close() + + lang = None + + for line in originalFile: + if line[:5] == "LANG=": + lang = line[5:].replace('"', '') + lang = lang.strip() + + if lang: + lang = _remove_encoding(lang) + else: + lang = "en_US" + return lang + +def print_language(): + print get_language() + +def set_language(language): + """Set the system language. + languages : + """ + if language in _LANGUAGES: + _writeI18N(_LANGUAGES[language][0], _LANGUAGES[language][1]) + else: + print (_("Sorry I do not speak \'%s\'.")%language) + + +# inilialize the docstrings for the timezone and language +_initialize() diff --git a/shell/controlpanel/sugar-control b/shell/controlpanel/sugar-control new file mode 100755 index 00000000..4bee092d --- /dev/null +++ b/shell/controlpanel/sugar-control @@ -0,0 +1,71 @@ +#!/usr/bin/env python + +# Copyright (C) 2007, One Laptop Per Child +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +import getopt, sys + +import control + +def cmd_help(): + print 'Usage: sugar-control [ option ] key [ args ... ] \n\ + Control for the sugar environment. \n\ + Options: \n\ + -h, --help show this help message and exit \n\ + -h key show information about this key \n\ + -g key get the current value of the key \n\ + -s key set the current value for the key \n\ + ' + +def main(): + try: + opts, args = getopt.getopt(sys.argv[1:], "h:s:g:", ["help"]) + except getopt.GetoptError: + cmd_help() + sys.exit(2) + + output = None + verbose = False + + for opt, key in opts: + if opt in ("-h"): + method = getattr(control, 'set_' + key, None) + if method is None: + cmd_help() + sys.exit() + else: + print method.__doc__ + if opt in ("-g"): + method = getattr(control, 'print_' + key, None) + if method is None: + cmd_help() + sys.exit() + else: + method() + if opt in ("-s"): + method = getattr(control, 'set_' + key, None) + if method is None: + cmd_help() + sys.exit() + else: + try: + method(*args) + except Exception, e: + print "sugar-control: %s"% e + +if __name__ == '__main__': + main()