Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore35
-rw-r--r--datastore/.gitignore35
-rw-r--r--datastore/AUTHORS (renamed from AUTHORS)0
-rw-r--r--datastore/COPYING (copied from COPYING)0
-rw-r--r--datastore/Makefile.am (renamed from Makefile.am)0
-rw-r--r--datastore/NEWS (renamed from NEWS)0
-rw-r--r--datastore/README (renamed from README)0
-rwxr-xr-xdatastore/autogen.sh (renamed from autogen.sh)0
-rw-r--r--datastore/bin/Makefile.am (renamed from bin/Makefile.am)0
-rwxr-xr-xdatastore/bin/copy-from-journal (renamed from bin/copy-from-journal)0
-rwxr-xr-xdatastore/bin/copy-to-journal (renamed from bin/copy-to-journal)0
-rwxr-xr-xdatastore/bin/datastore-service (renamed from bin/datastore-service)0
-rw-r--r--datastore/configure.ac (renamed from configure.ac)0
-rw-r--r--datastore/etc/.gitignore (renamed from etc/.gitignore)0
-rw-r--r--datastore/etc/Makefile.am (renamed from etc/Makefile.am)0
-rw-r--r--datastore/etc/org.laptop.sugar.DataStore.service.in (renamed from etc/org.laptop.sugar.DataStore.service.in)0
-rw-r--r--datastore/m4/python.m4 (copied from m4/python.m4)0
-rwxr-xr-xdatastore/maint-helper.py (renamed from maint-helper.py)0
-rw-r--r--datastore/src/Makefile.am (renamed from src/Makefile.am)0
-rw-r--r--datastore/src/carquinyol/Makefile.am (renamed from src/carquinyol/Makefile.am)0
-rw-r--r--datastore/src/carquinyol/__init__.py (copied from src/carquinyol/__init__.py)0
-rw-r--r--datastore/src/carquinyol/datastore.py (renamed from src/carquinyol/datastore.py)0
-rw-r--r--datastore/src/carquinyol/filestore.py (renamed from src/carquinyol/filestore.py)0
-rw-r--r--datastore/src/carquinyol/indexstore.py (renamed from src/carquinyol/indexstore.py)0
-rw-r--r--datastore/src/carquinyol/layoutmanager.py (renamed from src/carquinyol/layoutmanager.py)0
-rw-r--r--datastore/src/carquinyol/metadatareader.c (renamed from src/carquinyol/metadatareader.c)0
-rw-r--r--datastore/src/carquinyol/metadatastore.py (renamed from src/carquinyol/metadatastore.py)0
-rw-r--r--datastore/src/carquinyol/migration.py (renamed from src/carquinyol/migration.py)0
-rw-r--r--datastore/src/carquinyol/optimizer.py (renamed from src/carquinyol/optimizer.py)0
-rw-r--r--shell/.gitignore59
-rw-r--r--shell/AUTHORS14
-rw-r--r--shell/COPYING (copied from COPYING)123
-rw-r--r--shell/MAINTAINERS9
-rw-r--r--shell/Makefile.am14
-rw-r--r--shell/README44
-rwxr-xr-xshell/autogen.sh4
-rw-r--r--shell/bin/Makefile.am14
-rw-r--r--shell/bin/sugar-activity21
-rw-r--r--shell/bin/sugar-control-panel28
-rwxr-xr-xshell/bin/sugar-emulator14
-rw-r--r--shell/bin/sugar-install-bundle20
-rw-r--r--shell/bin/sugar-launch89
-rwxr-xr-xshell/bin/sugar-session274
-rw-r--r--shell/bin/sugar-ui-check161
-rw-r--r--shell/bin/sugar.in76
-rw-r--r--shell/configure.ac84
-rw-r--r--shell/data/.gitignore2
-rw-r--r--shell/data/GPLv2 (renamed from COPYING)123
-rw-r--r--shell/data/Makefile.am68
-rw-r--r--shell/data/activities.defaults28
-rwxr-xr-xshell/data/em.py3302
-rw-r--r--shell/data/gtkrc.em12
-rw-r--r--shell/data/icons/Makefile.am15
-rw-r--r--shell/data/icons/module-about_me.svg7
-rw-r--r--shell/data/icons/module-about_my_computer.svg6
-rw-r--r--shell/data/icons/module-date_and_time.svg19
-rw-r--r--shell/data/icons/module-frame.svg13
-rw-r--r--shell/data/icons/module-keyboard.svg134
-rw-r--r--shell/data/icons/module-language.svg59
-rw-r--r--shell/data/icons/module-modemconfiguration.svg11
-rw-r--r--shell/data/icons/module-network.svg32
-rw-r--r--shell/data/icons/module-power.svg13
-rw-r--r--shell/data/icons/module-updater.svg16
-rw-r--r--shell/data/kbdconfig3
-rw-r--r--shell/data/mime.defaults24
-rw-r--r--shell/data/nm-user-settings.conf28
-rw-r--r--shell/data/sugar-emulator.desktop.in10
-rw-r--r--shell/data/sugar-xo.svg7
-rw-r--r--shell/data/sugar.desktop6
-rw-r--r--shell/data/sugar.schemas.in347
-rw-r--r--shell/data/sugar.xml.in11
-rw-r--r--shell/docs/GPL-C.txt18
-rw-r--r--shell/docs/GPL-python.txt16
-rw-r--r--shell/docs/LGPL-C.txt18
-rw-r--r--shell/docs/LGPL-python.txt17
-rw-r--r--shell/docs/controls.txt199
-rw-r--r--shell/docs/design.txt126
-rw-r--r--shell/docs/release_howto.txt73
-rw-r--r--shell/extensions/Makefile.am1
-rw-r--r--shell/extensions/cpsection/Makefile.am5
-rw-r--r--shell/extensions/cpsection/__init__.py (copied from src/carquinyol/__init__.py)0
-rw-r--r--shell/extensions/cpsection/aboutcomputer/Makefile.am6
-rw-r--r--shell/extensions/cpsection/aboutcomputer/__init__.py22
-rw-r--r--shell/extensions/cpsection/aboutcomputer/model.py138
-rw-r--r--shell/extensions/cpsection/aboutcomputer/view.py217
-rw-r--r--shell/extensions/cpsection/aboutme/Makefile.am6
-rw-r--r--shell/extensions/cpsection/aboutme/__init__.py28
-rw-r--r--shell/extensions/cpsection/aboutme/model.py115
-rw-r--r--shell/extensions/cpsection/aboutme/view.py340
-rw-r--r--shell/extensions/cpsection/datetime/Makefile.am6
-rw-r--r--shell/extensions/cpsection/datetime/__init__.py21
-rw-r--r--shell/extensions/cpsection/datetime/model.py92
-rw-r--r--shell/extensions/cpsection/datetime/view.py138
-rw-r--r--shell/extensions/cpsection/frame/Makefile.am6
-rw-r--r--shell/extensions/cpsection/frame/__init__.py21
-rw-r--r--shell/extensions/cpsection/frame/model.py63
-rw-r--r--shell/extensions/cpsection/frame/view.py232
-rw-r--r--shell/extensions/cpsection/keyboard/Makefile.am6
-rw-r--r--shell/extensions/cpsection/keyboard/__init__.py22
-rw-r--r--shell/extensions/cpsection/keyboard/model.py169
-rw-r--r--shell/extensions/cpsection/keyboard/view.py426
-rw-r--r--shell/extensions/cpsection/language/Makefile.am6
-rw-r--r--shell/extensions/cpsection/language/__init__.py22
-rw-r--r--shell/extensions/cpsection/language/model.py151
-rw-r--r--shell/extensions/cpsection/language/view.py282
-rw-r--r--shell/extensions/cpsection/modemconfiguration/Makefile.am6
-rw-r--r--shell/extensions/cpsection/modemconfiguration/__init__.py22
-rwxr-xr-xshell/extensions/cpsection/modemconfiguration/model.py70
-rw-r--r--shell/extensions/cpsection/modemconfiguration/view.py250
-rw-r--r--shell/extensions/cpsection/network/Makefile.am6
-rw-r--r--shell/extensions/cpsection/network/__init__.py25
-rw-r--r--shell/extensions/cpsection/network/model.py141
-rw-r--r--shell/extensions/cpsection/network/view.py250
-rw-r--r--shell/extensions/cpsection/power/Makefile.am6
-rw-r--r--shell/extensions/cpsection/power/__init__.py23
-rw-r--r--shell/extensions/cpsection/power/model.py116
-rw-r--r--shell/extensions/cpsection/power/view.py177
-rw-r--r--shell/extensions/cpsection/updater/Makefile.am8
-rw-r--r--shell/extensions/cpsection/updater/__init__.py22
-rw-r--r--shell/extensions/cpsection/updater/backends/Makefile.am5
-rw-r--r--shell/extensions/cpsection/updater/backends/__init__.py16
-rw-r--r--shell/extensions/cpsection/updater/backends/aslo.py161
-rwxr-xr-xshell/extensions/cpsection/updater/model.py346
-rw-r--r--shell/extensions/cpsection/updater/view.py391
-rw-r--r--shell/extensions/deviceicon/Makefile.am9
-rw-r--r--shell/extensions/deviceicon/__init__.py (copied from src/carquinyol/__init__.py)0
-rw-r--r--shell/extensions/deviceicon/battery.py251
-rw-r--r--shell/extensions/deviceicon/network.py1006
-rw-r--r--shell/extensions/deviceicon/speaker.py216
-rw-r--r--shell/extensions/deviceicon/touchpad.py132
-rw-r--r--shell/extensions/deviceicon/volume.py121
-rw-r--r--shell/extensions/globalkey/Makefile.am6
-rw-r--r--shell/extensions/globalkey/__init__.py (copied from src/carquinyol/__init__.py)0
-rw-r--r--shell/extensions/globalkey/screenshot.py100
-rw-r--r--shell/extensions/globalkey/viewsource.py27
-rw-r--r--shell/po/ChangeLog (copied from src/carquinyol/__init__.py)0
-rw-r--r--shell/po/POTFILES.in67
-rw-r--r--shell/po/POTFILES.skip1
-rw-r--r--shell/po/af.po522
-rw-r--r--shell/po/am.po420
-rw-r--r--shell/po/ar.po1549
-rw-r--r--shell/po/ay.po420
-rw-r--r--shell/po/bg.po454
-rw-r--r--shell/po/bi.po764
-rw-r--r--shell/po/bn.po419
-rw-r--r--shell/po/bn_IN.po419
-rw-r--r--shell/po/ca.po438
-rw-r--r--shell/po/cpp.po958
-rw-r--r--shell/po/cs.po764
-rw-r--r--shell/po/da.po1227
-rw-r--r--shell/po/de.po1711
-rw-r--r--shell/po/dz.po420
-rw-r--r--shell/po/el.po777
-rw-r--r--shell/po/en.po420
-rw-r--r--shell/po/es.po1573
-rw-r--r--shell/po/fa.po419
-rw-r--r--shell/po/fa_AF.po419
-rw-r--r--shell/po/ff.po420
-rw-r--r--shell/po/fil.po958
-rw-r--r--shell/po/fr.po1619
-rw-r--r--shell/po/gu.po365
-rw-r--r--shell/po/ha.po447
-rw-r--r--shell/po/he.po764
-rw-r--r--shell/po/hi.po1416
-rw-r--r--shell/po/ht.po583
-rw-r--r--shell/po/hu.po764
-rw-r--r--shell/po/id.po958
-rw-r--r--shell/po/ig.po448
-rw-r--r--shell/po/is.po420
-rw-r--r--shell/po/it.po1668
-rw-r--r--shell/po/ja.po1558
-rw-r--r--shell/po/km.po584
-rw-r--r--shell/po/ko.po420
-rw-r--r--shell/po/kos.po1211
-rw-r--r--shell/po/mg.po1213
-rw-r--r--shell/po/mi.po979
-rw-r--r--shell/po/mk.po432
-rw-r--r--shell/po/ml.po420
-rw-r--r--shell/po/mn.po1516
-rw-r--r--shell/po/mr.po651
-rw-r--r--shell/po/ms.po958
-rw-r--r--shell/po/mvo.po517
-rw-r--r--shell/po/nb.po621
-rw-r--r--shell/po/ne.po1267
-rw-r--r--shell/po/nl.po1556
-rw-r--r--shell/po/pa.po420
-rw-r--r--shell/po/pap.po535
-rw-r--r--shell/po/pis.po517
-rw-r--r--shell/po/pl.po485
-rw-r--r--shell/po/ps.po424
-rw-r--r--shell/po/pseudo.po517
-rw-r--r--shell/po/pt.po1481
-rw-r--r--shell/po/pt_BR.po438
-rw-r--r--shell/po/qu.po420
-rw-r--r--shell/po/ro.po419
-rw-r--r--shell/po/ru.po420
-rw-r--r--shell/po/rw.po596
-rw-r--r--shell/po/sd.po517
-rw-r--r--shell/po/si.po899
-rw-r--r--shell/po/sk.po764
-rw-r--r--shell/po/sl.po1098
-rw-r--r--shell/po/sq.po1215
-rw-r--r--shell/po/sv.po1339
-rw-r--r--shell/po/sw.po1222
-rw-r--r--shell/po/ta.po1260
-rw-r--r--shell/po/te.po763
-rw-r--r--shell/po/th.po420
-rw-r--r--shell/po/tpi.po517
-rw-r--r--shell/po/tr.po839
-rw-r--r--shell/po/tvl.po1188
-rw-r--r--shell/po/tzo.po1210
-rw-r--r--shell/po/ug.po979
-rw-r--r--shell/po/ur.po743
-rw-r--r--shell/po/vi.po1415
-rw-r--r--shell/po/wa.po764
-rw-r--r--shell/po/yo.po448
-rw-r--r--shell/po/zh_CN.po420
-rw-r--r--shell/po/zh_TW.po1543
-rw-r--r--shell/src/Makefile.am1
-rw-r--r--shell/src/jarabe/.gitignore1
-rw-r--r--shell/src/jarabe/Makefile.am16
-rw-r--r--shell/src/jarabe/__init__.py26
-rw-r--r--shell/src/jarabe/config.py.in26
-rw-r--r--shell/src/jarabe/controlpanel/Makefile.am10
-rw-r--r--shell/src/jarabe/controlpanel/__init__.py16
-rw-r--r--shell/src/jarabe/controlpanel/cmd.py158
-rw-r--r--shell/src/jarabe/controlpanel/gui.py431
-rw-r--r--shell/src/jarabe/controlpanel/inlinealert.py83
-rw-r--r--shell/src/jarabe/controlpanel/sectionview.py55
-rw-r--r--shell/src/jarabe/controlpanel/toolbar.py157
-rw-r--r--shell/src/jarabe/desktop/Makefile.am18
-rw-r--r--shell/src/jarabe/desktop/__init__.py16
-rw-r--r--shell/src/jarabe/desktop/activitieslist.py451
-rw-r--r--shell/src/jarabe/desktop/favoriteslayout.py488
-rw-r--r--shell/src/jarabe/desktop/favoritesview.py670
-rw-r--r--shell/src/jarabe/desktop/friendview.py87
-rw-r--r--shell/src/jarabe/desktop/grid.py201
-rw-r--r--shell/src/jarabe/desktop/groupbox.py92
-rw-r--r--shell/src/jarabe/desktop/homebox.py298
-rw-r--r--shell/src/jarabe/desktop/homewindow.py193
-rw-r--r--shell/src/jarabe/desktop/keydialog.py317
-rw-r--r--shell/src/jarabe/desktop/meshbox.py670
-rw-r--r--shell/src/jarabe/desktop/networkviews.py716
-rw-r--r--shell/src/jarabe/desktop/schoolserver.py127
-rw-r--r--shell/src/jarabe/desktop/snowflakelayout.py108
-rw-r--r--shell/src/jarabe/desktop/spreadlayout.py83
-rw-r--r--shell/src/jarabe/desktop/transitionbox.py96
-rw-r--r--shell/src/jarabe/frame/Makefile.am18
-rw-r--r--shell/src/jarabe/frame/__init__.py25
-rw-r--r--shell/src/jarabe/frame/activitiestray.py745
-rw-r--r--shell/src/jarabe/frame/clipboard.py149
-rw-r--r--shell/src/jarabe/frame/clipboardicon.py158
-rw-r--r--shell/src/jarabe/frame/clipboardmenu.py249
-rw-r--r--shell/src/jarabe/frame/clipboardobject.py142
-rw-r--r--shell/src/jarabe/frame/clipboardpanelwindow.py103
-rw-r--r--shell/src/jarabe/frame/clipboardtray.py216
-rw-r--r--shell/src/jarabe/frame/devicestray.py54
-rw-r--r--shell/src/jarabe/frame/eventarea.py151
-rw-r--r--shell/src/jarabe/frame/frame.py351
-rw-r--r--shell/src/jarabe/frame/frameinvoker.py36
-rw-r--r--shell/src/jarabe/frame/framewindow.py117
-rw-r--r--shell/src/jarabe/frame/friendstray.py118
-rw-r--r--shell/src/jarabe/frame/notification.py100
-rw-r--r--shell/src/jarabe/frame/zoomtoolbar.py89
-rw-r--r--shell/src/jarabe/intro/Makefile.am9
-rw-r--r--shell/src/jarabe/intro/__init__.py25
-rw-r--r--shell/src/jarabe/intro/colorpicker.py43
-rw-r--r--shell/src/jarabe/intro/default-picture.pngbin0 -> 10442 bytes
-rw-r--r--shell/src/jarabe/intro/window.py298
-rw-r--r--shell/src/jarabe/journal/Makefile.am17
-rw-r--r--shell/src/jarabe/journal/__init__.py15
-rw-r--r--shell/src/jarabe/journal/detailview.py117
-rw-r--r--shell/src/jarabe/journal/expandedentry.py429
-rw-r--r--shell/src/jarabe/journal/journalactivity.py371
-rw-r--r--shell/src/jarabe/journal/journalentrybundle.py94
-rw-r--r--shell/src/jarabe/journal/journaltoolbox.py458
-rw-r--r--shell/src/jarabe/journal/keepicon.py59
-rw-r--r--shell/src/jarabe/journal/listmodel.py201
-rw-r--r--shell/src/jarabe/journal/listview.py641
-rw-r--r--shell/src/jarabe/journal/misc.py262
-rw-r--r--shell/src/jarabe/journal/modalalert.py97
-rw-r--r--shell/src/jarabe/journal/model.py541
-rw-r--r--shell/src/jarabe/journal/objectchooser.py199
-rw-r--r--shell/src/jarabe/journal/palettes.py235
-rw-r--r--shell/src/jarabe/journal/volumestoolbar.py207
-rw-r--r--shell/src/jarabe/model/Makefile.am19
-rw-r--r--shell/src/jarabe/model/__init__.py16
-rw-r--r--shell/src/jarabe/model/adhoc.py292
-rw-r--r--shell/src/jarabe/model/buddy.py250
-rw-r--r--shell/src/jarabe/model/bundleregistry.py444
-rw-r--r--shell/src/jarabe/model/filetransfer.py374
-rw-r--r--shell/src/jarabe/model/friends.py122
-rw-r--r--shell/src/jarabe/model/invites.py239
-rw-r--r--shell/src/jarabe/model/mimeregistry.py49
-rw-r--r--shell/src/jarabe/model/neighborhood.py863
-rw-r--r--shell/src/jarabe/model/network.py751
-rw-r--r--shell/src/jarabe/model/notifications.py95
-rw-r--r--shell/src/jarabe/model/olpcmesh.py214
-rw-r--r--shell/src/jarabe/model/screen.py43
-rw-r--r--shell/src/jarabe/model/session.py89
-rw-r--r--shell/src/jarabe/model/shell.py641
-rw-r--r--shell/src/jarabe/model/sound.py58
-rw-r--r--shell/src/jarabe/model/telepathyclient.py100
-rw-r--r--shell/src/jarabe/util/Makefile.am7
-rw-r--r--shell/src/jarabe/util/__init__.py19
-rw-r--r--shell/src/jarabe/util/emulator.py177
-rw-r--r--shell/src/jarabe/util/telepathy/Makefile.am4
-rw-r--r--shell/src/jarabe/util/telepathy/__init__.py19
-rw-r--r--shell/src/jarabe/util/telepathy/connection_watcher.py118
-rw-r--r--shell/src/jarabe/view/Makefile.am12
-rw-r--r--shell/src/jarabe/view/__init__.py16
-rw-r--r--shell/src/jarabe/view/buddyicon.py61
-rw-r--r--shell/src/jarabe/view/buddymenu.py168
-rw-r--r--shell/src/jarabe/view/keyhandler.py242
-rw-r--r--shell/src/jarabe/view/launcher.py217
-rw-r--r--shell/src/jarabe/view/palettes.py250
-rw-r--r--shell/src/jarabe/view/pulsingicon.py229
-rw-r--r--shell/src/jarabe/view/service.py89
-rw-r--r--shell/src/jarabe/view/tabbinghandler.py148
-rw-r--r--shell/src/jarabe/view/viewsource.py464
-rw-r--r--toolkit/.gitignore24
-rw-r--r--toolkit/AUTHORS14
-rw-r--r--toolkit/COPYING504
-rw-r--r--toolkit/Makefile.am13
-rw-r--r--toolkit/README3
-rwxr-xr-xtoolkit/autogen.sh6
-rw-r--r--toolkit/configure.ac48
-rw-r--r--toolkit/examples/radiopalette.py74
-rw-r--r--toolkit/examples/toolbar.py50
-rw-r--r--toolkit/m4/.gitignore3
-rw-r--r--toolkit/m4/gnome-compiler-flags.m4141
-rw-r--r--toolkit/m4/python.m4 (renamed from m4/python.m4)0
-rw-r--r--toolkit/po/.gitignore4
-rw-r--r--toolkit/po/ChangeLog (renamed from src/carquinyol/__init__.py)0
-rw-r--r--toolkit/po/POTFILES.in7
-rw-r--r--toolkit/po/POTFILES.skip6
-rw-r--r--toolkit/po/af.po153
-rw-r--r--toolkit/po/am.po153
-rw-r--r--toolkit/po/ar.po630
-rw-r--r--toolkit/po/ay.po153
-rw-r--r--toolkit/po/bg.po153
-rw-r--r--toolkit/po/bi.po153
-rw-r--r--toolkit/po/bn.po153
-rw-r--r--toolkit/po/bn_IN.po153
-rw-r--r--toolkit/po/ca.po153
-rw-r--r--toolkit/po/cpp.po186
-rw-r--r--toolkit/po/cs.po160
-rw-r--r--toolkit/po/da.po208
-rw-r--r--toolkit/po/de.po228
-rw-r--r--toolkit/po/dz.po153
-rw-r--r--toolkit/po/el.po189
-rw-r--r--toolkit/po/en.po153
-rw-r--r--toolkit/po/es.po691
-rw-r--r--toolkit/po/fa.po153
-rw-r--r--toolkit/po/fa_AF.po153
-rw-r--r--toolkit/po/ff.po153
-rw-r--r--toolkit/po/fil.po186
-rw-r--r--toolkit/po/fr.po214
-rw-r--r--toolkit/po/gu.po153
-rw-r--r--toolkit/po/ha.po153
-rw-r--r--toolkit/po/he.po153
-rw-r--r--toolkit/po/hi.po206
-rw-r--r--toolkit/po/ht.po153
-rw-r--r--toolkit/po/hu.po153
-rw-r--r--toolkit/po/id.po186
-rw-r--r--toolkit/po/ig.po153
-rw-r--r--toolkit/po/is.po153
-rw-r--r--toolkit/po/it.po219
-rw-r--r--toolkit/po/ja.po207
-rw-r--r--toolkit/po/km.po153
-rw-r--r--toolkit/po/ko.po153
-rw-r--r--toolkit/po/kos.po207
-rw-r--r--toolkit/po/mg.po210
-rw-r--r--toolkit/po/mi.po186
-rw-r--r--toolkit/po/mk.po153
-rw-r--r--toolkit/po/ml.po153
-rw-r--r--toolkit/po/mn.po214
-rw-r--r--toolkit/po/mr.po152
-rw-r--r--toolkit/po/ms.po186
-rw-r--r--toolkit/po/mvo.po153
-rw-r--r--toolkit/po/nb.po152
-rw-r--r--toolkit/po/ne.po189
-rw-r--r--toolkit/po/nl.po218
-rw-r--r--toolkit/po/pa.po153
-rw-r--r--toolkit/po/pap.po153
-rw-r--r--toolkit/po/pis.po153
-rw-r--r--toolkit/po/pl.po153
-rw-r--r--toolkit/po/ps.po153
-rw-r--r--toolkit/po/pseudo.po153
-rw-r--r--toolkit/po/pt.po214
-rw-r--r--toolkit/po/pt_BR.po153
-rw-r--r--toolkit/po/qu.po153
-rw-r--r--toolkit/po/ro.po153
-rw-r--r--toolkit/po/ru.po161
-rw-r--r--toolkit/po/rw.po154
-rw-r--r--toolkit/po/sd.po153
-rw-r--r--toolkit/po/si.po152
-rw-r--r--toolkit/po/sk.po160
-rw-r--r--toolkit/po/sl.po201
-rw-r--r--toolkit/po/sq.po208
-rw-r--r--toolkit/po/sv.po189
-rw-r--r--toolkit/po/sw.po152
-rw-r--r--toolkit/po/ta.po210
-rw-r--r--toolkit/po/te.po154
-rw-r--r--toolkit/po/th.po153
-rw-r--r--toolkit/po/tpi.po153
-rw-r--r--toolkit/po/tr.po153
-rw-r--r--toolkit/po/tvl.po206
-rw-r--r--toolkit/po/tzo.po206
-rw-r--r--toolkit/po/ug.po186
-rw-r--r--toolkit/po/ur.po152
-rw-r--r--toolkit/po/vi.po208
-rw-r--r--toolkit/po/wa.po153
-rw-r--r--toolkit/po/yo.po153
-rw-r--r--toolkit/po/zh_CN.po153
-rw-r--r--toolkit/po/zh_TW.po208
-rw-r--r--toolkit/src/Makefile.am1
-rw-r--r--toolkit/src/sugar/.gitignore4
-rw-r--r--toolkit/src/sugar/.license1
-rw-r--r--toolkit/src/sugar/Makefile.am87
-rw-r--r--toolkit/src/sugar/__init__.py14
-rw-r--r--toolkit/src/sugar/_sugarext.defs422
-rw-r--r--toolkit/src/sugar/_sugarext.override81
-rw-r--r--toolkit/src/sugar/_sugarextmodule.c50
-rw-r--r--toolkit/src/sugar/acme-volume-alsa.c317
-rw-r--r--toolkit/src/sugar/acme-volume-alsa.h47
-rw-r--r--toolkit/src/sugar/acme-volume.c127
-rw-r--r--toolkit/src/sugar/acme-volume.h63
-rw-r--r--toolkit/src/sugar/activity/Makefile.am12
-rw-r--r--toolkit/src/sugar/activity/__init__.py55
-rw-r--r--toolkit/src/sugar/activity/activity.py986
-rw-r--r--toolkit/src/sugar/activity/activityfactory.py374
-rw-r--r--toolkit/src/sugar/activity/activityhandle.py75
-rw-r--r--toolkit/src/sugar/activity/activityservice.py83
-rw-r--r--toolkit/src/sugar/activity/bundlebuilder.py424
-rw-r--r--toolkit/src/sugar/activity/i18n.py144
-rw-r--r--toolkit/src/sugar/activity/main.py159
-rw-r--r--toolkit/src/sugar/activity/namingalert.py355
-rw-r--r--toolkit/src/sugar/activity/widgets.py353
-rw-r--r--toolkit/src/sugar/bundle/Makefile.am6
-rw-r--r--toolkit/src/sugar/bundle/__init__.py16
-rw-r--r--toolkit/src/sugar/bundle/activitybundle.py428
-rw-r--r--toolkit/src/sugar/bundle/bundle.py205
-rw-r--r--toolkit/src/sugar/bundle/contentbundle.py239
-rw-r--r--toolkit/src/sugar/datastore/Makefile.am4
-rw-r--r--toolkit/src/sugar/datastore/__init__.py16
-rw-r--r--toolkit/src/sugar/datastore/datastore.py549
-rw-r--r--toolkit/src/sugar/eggaccelerators.c702
-rw-r--r--toolkit/src/sugar/eggaccelerators.h89
-rw-r--r--toolkit/src/sugar/eggdesktopfile.c1437
-rw-r--r--toolkit/src/sugar/eggdesktopfile.h156
-rw-r--r--toolkit/src/sugar/eggsmclient-private.h56
-rw-r--r--toolkit/src/sugar/eggsmclient-xsmp.c1359
-rw-r--r--toolkit/src/sugar/eggsmclient.c392
-rw-r--r--toolkit/src/sugar/eggsmclient.h112
-rw-r--r--toolkit/src/sugar/env.py65
-rw-r--r--toolkit/src/sugar/graphics/Makefile.am30
-rw-r--r--toolkit/src/sugar/graphics/__init__.py18
-rw-r--r--toolkit/src/sugar/graphics/alert.py479
-rw-r--r--toolkit/src/sugar/graphics/animator.py151
-rw-r--r--toolkit/src/sugar/graphics/canvastextview.py41
-rw-r--r--toolkit/src/sugar/graphics/colorbutton.py536
-rw-r--r--toolkit/src/sugar/graphics/combobox.py170
-rw-r--r--toolkit/src/sugar/graphics/entry.py41
-rw-r--r--toolkit/src/sugar/graphics/icon.py1176
-rw-r--r--toolkit/src/sugar/graphics/iconentry.py106
-rw-r--r--toolkit/src/sugar/graphics/menuitem.py95
-rw-r--r--toolkit/src/sugar/graphics/notebook.py151
-rw-r--r--toolkit/src/sugar/graphics/objectchooser.py132
-rw-r--r--toolkit/src/sugar/graphics/palette.py446
-rw-r--r--toolkit/src/sugar/graphics/palettegroup.py106
-rw-r--r--toolkit/src/sugar/graphics/palettewindow.py976
-rw-r--r--toolkit/src/sugar/graphics/panel.py30
-rw-r--r--toolkit/src/sugar/graphics/radiopalette.py104
-rw-r--r--toolkit/src/sugar/graphics/radiotoolbutton.py182
-rw-r--r--toolkit/src/sugar/graphics/roundbox.py71
-rw-r--r--toolkit/src/sugar/graphics/style.py148
-rw-r--r--toolkit/src/sugar/graphics/toggletoolbutton.py91
-rw-r--r--toolkit/src/sugar/graphics/toolbarbox.py323
-rw-r--r--toolkit/src/sugar/graphics/toolbox.py101
-rw-r--r--toolkit/src/sugar/graphics/toolbutton.py162
-rw-r--r--toolkit/src/sugar/graphics/toolcombobox.py64
-rw-r--r--toolkit/src/sugar/graphics/tray.py468
-rw-r--r--toolkit/src/sugar/graphics/window.py297
-rw-r--r--toolkit/src/sugar/graphics/xocolor.py278
-rw-r--r--toolkit/src/sugar/gsm-app.c396
-rw-r--r--toolkit/src/sugar/gsm-app.h70
-rw-r--r--toolkit/src/sugar/gsm-client-xsmp.c828
-rw-r--r--toolkit/src/sugar/gsm-client-xsmp.h70
-rw-r--r--toolkit/src/sugar/gsm-client.c251
-rw-r--r--toolkit/src/sugar/gsm-client.h111
-rw-r--r--toolkit/src/sugar/gsm-session.c509
-rw-r--r--toolkit/src/sugar/gsm-session.h97
-rw-r--r--toolkit/src/sugar/gsm-xsmp.c535
-rw-r--r--toolkit/src/sugar/gsm-xsmp.h29
-rw-r--r--toolkit/src/sugar/network.py302
-rw-r--r--toolkit/src/sugar/presence/Makefile.am10
-rw-r--r--toolkit/src/sugar/presence/__init__.py24
-rw-r--r--toolkit/src/sugar/presence/activity.py716
-rw-r--r--toolkit/src/sugar/presence/buddy.py246
-rw-r--r--toolkit/src/sugar/presence/connectionmanager.py116
-rw-r--r--toolkit/src/sugar/presence/presenceservice.py375
-rw-r--r--toolkit/src/sugar/presence/sugartubeconn.py63
-rw-r--r--toolkit/src/sugar/presence/test_presence.txt26
-rw-r--r--toolkit/src/sugar/presence/tubeconn.py114
-rw-r--r--toolkit/src/sugar/profile.py227
-rw-r--r--toolkit/src/sugar/session.py54
-rw-r--r--toolkit/src/sugar/sexy-icon-entry.c984
-rw-r--r--toolkit/src/sugar/sexy-icon-entry.h104
-rw-r--r--toolkit/src/sugar/sugar-address-entry.c576
-rw-r--r--toolkit/src/sugar/sugar-address-entry.h54
-rw-r--r--toolkit/src/sugar/sugar-grid.c120
-rw-r--r--toolkit/src/sugar/sugar-grid.h63
-rw-r--r--toolkit/src/sugar/sugar-key-grabber.c287
-rw-r--r--toolkit/src/sugar/sugar-key-grabber.h69
-rw-r--r--toolkit/src/sugar/sugar-marshal.list1
-rw-r--r--toolkit/src/sugar/sugar-menu.c63
-rw-r--r--toolkit/src/sugar/sugar-menu.h57
-rw-r--r--toolkit/src/sugar/util.py347
-rw-r--r--toolkit/src/sugar/wm.py86
-rw-r--r--toolkit/tests/graphics/common.py55
-rw-r--r--toolkit/tests/graphics/hipposcalability.py50
-rw-r--r--toolkit/tests/graphics/iconcache.py69
-rw-r--r--toolkit/tests/graphics/iconwidget.py87
-rw-r--r--toolkit/tests/graphics/ticket2855.py59
-rw-r--r--toolkit/tests/graphics/ticket2999.py38
-rw-r--r--toolkit/tests/graphics/ticket3000.py48
-rw-r--r--toolkit/tests/graphics/toolbarpalettes.py65
-rw-r--r--toolkit/tests/graphics/tray.py82
-rw-r--r--toolkit/tests/lib/runall.py28
-rw-r--r--toolkit/tests/lib/test_mime.py81
531 files changed, 138263 insertions, 41 deletions
diff --git a/.gitignore b/.gitignore
index 12965e2..e69de29 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,35 +0,0 @@
-# Generic
-
-*.pyc
-*~
-*.deps
-*.libs
-*.la
-*.lo
-*.loT
-*.service
-
-# Absolute
-Makefile
-Makefile.in
-aclocal.m4
-autom4te.cache
-compile
-config.guess
-config.log
-config.status
-config.sub
-configure
-depcomp
-install-sh
-libtool
-ltmain.sh
-missing
-mkinstalldirs
-py-compile
-stamp-h1
-m4/libtool.m4
-m4/ltoptions.m4
-m4/ltsugar.m4
-m4/ltversion.m4
-m4/lt~obsolete.m4
diff --git a/datastore/.gitignore b/datastore/.gitignore
new file mode 100644
index 0000000..12965e2
--- /dev/null
+++ b/datastore/.gitignore
@@ -0,0 +1,35 @@
+# Generic
+
+*.pyc
+*~
+*.deps
+*.libs
+*.la
+*.lo
+*.loT
+*.service
+
+# Absolute
+Makefile
+Makefile.in
+aclocal.m4
+autom4te.cache
+compile
+config.guess
+config.log
+config.status
+config.sub
+configure
+depcomp
+install-sh
+libtool
+ltmain.sh
+missing
+mkinstalldirs
+py-compile
+stamp-h1
+m4/libtool.m4
+m4/ltoptions.m4
+m4/ltsugar.m4
+m4/ltversion.m4
+m4/lt~obsolete.m4
diff --git a/AUTHORS b/datastore/AUTHORS
index a2c2720..a2c2720 100644
--- a/AUTHORS
+++ b/datastore/AUTHORS
diff --git a/COPYING b/datastore/COPYING
index ba9543b..ba9543b 100644
--- a/COPYING
+++ b/datastore/COPYING
diff --git a/Makefile.am b/datastore/Makefile.am
index bfebefe..bfebefe 100644
--- a/Makefile.am
+++ b/datastore/Makefile.am
diff --git a/NEWS b/datastore/NEWS
index 0a765be..0a765be 100644
--- a/NEWS
+++ b/datastore/NEWS
diff --git a/README b/datastore/README
index b29cebb..b29cebb 100644
--- a/README
+++ b/datastore/README
diff --git a/autogen.sh b/datastore/autogen.sh
index 1cd5db4..1cd5db4 100755
--- a/autogen.sh
+++ b/datastore/autogen.sh
diff --git a/bin/Makefile.am b/datastore/bin/Makefile.am
index c583cbe..c583cbe 100644
--- a/bin/Makefile.am
+++ b/datastore/bin/Makefile.am
diff --git a/bin/copy-from-journal b/datastore/bin/copy-from-journal
index 7a10bfd..7a10bfd 100755
--- a/bin/copy-from-journal
+++ b/datastore/bin/copy-from-journal
diff --git a/bin/copy-to-journal b/datastore/bin/copy-to-journal
index ca6f872..ca6f872 100755
--- a/bin/copy-to-journal
+++ b/datastore/bin/copy-to-journal
diff --git a/bin/datastore-service b/datastore/bin/datastore-service
index 06b6517..06b6517 100755
--- a/bin/datastore-service
+++ b/datastore/bin/datastore-service
diff --git a/configure.ac b/datastore/configure.ac
index 6ccea2e..6ccea2e 100644
--- a/configure.ac
+++ b/datastore/configure.ac
diff --git a/etc/.gitignore b/datastore/etc/.gitignore
index c0ede5e..c0ede5e 100644
--- a/etc/.gitignore
+++ b/datastore/etc/.gitignore
diff --git a/etc/Makefile.am b/datastore/etc/Makefile.am
index a9b28b1..a9b28b1 100644
--- a/etc/Makefile.am
+++ b/datastore/etc/Makefile.am
diff --git a/etc/org.laptop.sugar.DataStore.service.in b/datastore/etc/org.laptop.sugar.DataStore.service.in
index 1b5b270..1b5b270 100644
--- a/etc/org.laptop.sugar.DataStore.service.in
+++ b/datastore/etc/org.laptop.sugar.DataStore.service.in
diff --git a/m4/python.m4 b/datastore/m4/python.m4
index e1c5266..e1c5266 100644
--- a/m4/python.m4
+++ b/datastore/m4/python.m4
diff --git a/maint-helper.py b/datastore/maint-helper.py
index 5ffd7e0..5ffd7e0 100755
--- a/maint-helper.py
+++ b/datastore/maint-helper.py
diff --git a/src/Makefile.am b/datastore/src/Makefile.am
index 434face..434face 100644
--- a/src/Makefile.am
+++ b/datastore/src/Makefile.am
diff --git a/src/carquinyol/Makefile.am b/datastore/src/carquinyol/Makefile.am
index 7c56174..7c56174 100644
--- a/src/carquinyol/Makefile.am
+++ b/datastore/src/carquinyol/Makefile.am
diff --git a/src/carquinyol/__init__.py b/datastore/src/carquinyol/__init__.py
index e69de29..e69de29 100644
--- a/src/carquinyol/__init__.py
+++ b/datastore/src/carquinyol/__init__.py
diff --git a/src/carquinyol/datastore.py b/datastore/src/carquinyol/datastore.py
index 82a6207..82a6207 100644
--- a/src/carquinyol/datastore.py
+++ b/datastore/src/carquinyol/datastore.py
diff --git a/src/carquinyol/filestore.py b/datastore/src/carquinyol/filestore.py
index 9724397..9724397 100644
--- a/src/carquinyol/filestore.py
+++ b/datastore/src/carquinyol/filestore.py
diff --git a/src/carquinyol/indexstore.py b/datastore/src/carquinyol/indexstore.py
index 62b843b..62b843b 100644
--- a/src/carquinyol/indexstore.py
+++ b/datastore/src/carquinyol/indexstore.py
diff --git a/src/carquinyol/layoutmanager.py b/datastore/src/carquinyol/layoutmanager.py
index 5c67203..5c67203 100644
--- a/src/carquinyol/layoutmanager.py
+++ b/datastore/src/carquinyol/layoutmanager.py
diff --git a/src/carquinyol/metadatareader.c b/datastore/src/carquinyol/metadatareader.c
index 454c8c3..454c8c3 100644
--- a/src/carquinyol/metadatareader.c
+++ b/datastore/src/carquinyol/metadatareader.c
diff --git a/src/carquinyol/metadatastore.py b/datastore/src/carquinyol/metadatastore.py
index 5967017..5967017 100644
--- a/src/carquinyol/metadatastore.py
+++ b/datastore/src/carquinyol/metadatastore.py
diff --git a/src/carquinyol/migration.py b/datastore/src/carquinyol/migration.py
index 95ee391..95ee391 100644
--- a/src/carquinyol/migration.py
+++ b/datastore/src/carquinyol/migration.py
diff --git a/src/carquinyol/optimizer.py b/datastore/src/carquinyol/optimizer.py
index 2b6ce29..2b6ce29 100644
--- a/src/carquinyol/optimizer.py
+++ b/datastore/src/carquinyol/optimizer.py
diff --git a/shell/.gitignore b/shell/.gitignore
new file mode 100644
index 0000000..047a849
--- /dev/null
+++ b/shell/.gitignore
@@ -0,0 +1,59 @@
+# Generic
+
+*.pyc
+*~
+Makefile
+Makefile.in
+*.deps
+*.libs
+*.la
+*.o
+*.lo
+*.loT
+.*.sw?
+*.service
+stamp-*
+
+# Absolute
+
+aclocal.m4
+autom4te.cache
+config.h
+config.h.in
+config.log
+config.status
+configure
+compile
+install-sh
+missing
+py-compile
+dbus-installed.conf
+intltool-extract
+intltool-extract.in
+intltool-merge
+intltool-merge.in
+intltool-update
+intltool-update.in
+mkinstalldirs
+po/Makefile.in.in
+po/POTFILES
+po/*.gmo
+po/.intltool-merge-cache
+sugar/__installed__.py
+tools/sugar-setup-activity
+threadframe
+config.guess
+config.sub
+depcomp
+libtool
+ltmain.sh
+m4/intltool.m4
+sugar/browser/_sugarbrowser.c
+browser/sugar-marshal.c
+browser/sugar-marshal.h
+bin/sugar
+shell/extensions/_extensions.c
+data/sugar.gtkrc
+data/sugar.xml
+data/sugar-xo.gtkrc
+data/sugar-emulator.desktop
diff --git a/shell/AUTHORS b/shell/AUTHORS
new file mode 100644
index 0000000..8cd5dac
--- /dev/null
+++ b/shell/AUTHORS
@@ -0,0 +1,14 @@
+Marco Pesenti Gritti <mpg@redhat.com>
+Dan Williams <dcbw@redhat.com>
+Tomeu Vizoso <tomeu@tomeuvizoso.net>
+Dan Winship <dwinship@redhat.com>
+Benjamin Berg <benjamin@sipsolutions.net>
+Eduardo Silva <edsiper@gmail.com>
+Simon Schampijer <simon@schampijer.de>
+Bert Freudenberg <bert@freudenbergs.de>
+Guillaume Desmottes <guillaume.desmottes@collabora.co.uk>
+Dafydd Harries <daf@rhydd.org>
+John (J5) Palmieri <johnp@redhat.com>
+Morgan Collett <morgan.collett@gmail.com>
+Simon McVittie <simon.mcvittie@collabora.co.uk>
+Owen Williams <owen@ywwg.com>
diff --git a/COPYING b/shell/COPYING
index ba9543b..d511905 100644
--- a/COPYING
+++ b/shell/COPYING
@@ -1,4 +1,62 @@
GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
@@ -52,7 +110,7 @@ above, provided that you also meet all of these conditions:
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
-
+
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
@@ -110,7 +168,7 @@ access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
-
+
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
@@ -167,7 +225,7 @@ impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
-
+
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
@@ -220,3 +278,62 @@ PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/shell/MAINTAINERS b/shell/MAINTAINERS
new file mode 100644
index 0000000..9c63644
--- /dev/null
+++ b/shell/MAINTAINERS
@@ -0,0 +1,9 @@
+== Current maintainers ==
+
+Aleksey Lim <alsroot@member.fsf.org> -- Journal
+Tomeu Vizoso <tomeu@sugarlabs.org>
+
+== Past maintainers ==
+
+Marco Pesenti Gritti <marcopg@sugarlabs.org>
+
diff --git a/shell/Makefile.am b/shell/Makefile.am
new file mode 100644
index 0000000..9e252af
--- /dev/null
+++ b/shell/Makefile.am
@@ -0,0 +1,14 @@
+SUBDIRS = bin data po src extensions
+
+DISTCLEANFILES = \
+ intltool-extract \
+ intltool-merge \
+ intltool-update
+
+EXTRA_DIST = \
+ $(bin_SCRIPTS) \
+ intltool-merge.in \
+ intltool-update.in \
+ intltool-extract.in
+
+DISTCHECK_CONFIGURE_FLAGS = --disable-update-mimedb
diff --git a/shell/README b/shell/README
new file mode 100644
index 0000000..1f89810
--- /dev/null
+++ b/shell/README
@@ -0,0 +1,44 @@
+Building
+========
+
+For details see: http://wiki.sugarlabs.org/go/Development_Team/Jhbuild
+
+Sugar-jhbuild will automatically download the latest of Sugar's
+dependencies as well as Sugar itself directly from their source
+repositories, rather than relying on source packages that may have
+become stale. These are generic instructions on how to use jhbuild
+to get up and running with Sugar.
+
+ $ cd sugar-jhbuild
+ $ ./sugar-jhbuild update
+ $ ./sugar-jhbuild depscheck
+ $ ./sugar-jhbuild buildgit
+
+Running multiple instances on the same machine
+==============================================
+
+You can use the SUGAR_PROFILE command line options.
+For example:
+
+SUGAR_PROFILE=profile-1 sugar
+SUGAR_PROFILE=profile-2 sugar
+...
+
+
+Emulator key bindings
+=====================
+
+F1 Mesh zoom level
+F2 Friends zoom level
+F3 Home zoom level
+F4 Activity zoom level
+
+Alt+f Show the frame
+Alt+r Rotate the screen
+Alt+o Toggle overlay visibility
+Alt+= Open the developer console
+Alt+0 Open the developer console
+Alt+q Quit the emulator
+
+Ctrl+s Activate sketch mode in chat
+
diff --git a/shell/autogen.sh b/shell/autogen.sh
new file mode 100755
index 0000000..a71e202
--- /dev/null
+++ b/shell/autogen.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+intltoolize
+autoreconf -i
+./configure --enable-maintainer-mode "$@"
diff --git a/shell/bin/Makefile.am b/shell/bin/Makefile.am
new file mode 100644
index 0000000..05a9215
--- /dev/null
+++ b/shell/bin/Makefile.am
@@ -0,0 +1,14 @@
+python_scripts = \
+ sugar-activity \
+ sugar-control-panel \
+ sugar-emulator \
+ sugar-install-bundle \
+ sugar-launch \
+ sugar-session \
+ sugar-ui-check
+
+bin_SCRIPTS = \
+ sugar \
+ $(python_scripts)
+
+EXTRA_DIST = $(python_scripts) sugar.in
diff --git a/shell/bin/sugar-activity b/shell/bin/sugar-activity
new file mode 100644
index 0000000..4abdd80
--- /dev/null
+++ b/shell/bin/sugar-activity
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+
+# Copyright (C) 2006-2008, Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from sugar.activity import main
+
+main.main()
diff --git a/shell/bin/sugar-control-panel b/shell/bin/sugar-control-panel
new file mode 100644
index 0000000..a97e3c0
--- /dev/null
+++ b/shell/bin/sugar-control-panel
@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+# Copyright (C) 2008, Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import sys
+
+from jarabe import config
+
+sys.path.append(config.ext_path)
+
+from jarabe.controlpanel.cmd import main
+
+main()
+
+
diff --git a/shell/bin/sugar-emulator b/shell/bin/sugar-emulator
new file mode 100755
index 0000000..308aac7
--- /dev/null
+++ b/shell/bin/sugar-emulator
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+if [ "$(id -u)" -eq 0 -o "$(id -ru)" -eq 0 ] ; then
+ echo Refusing to run as root.
+ exit 3
+fi
+
+# Source debug definitions
+if [ -f ~/.sugar/debug ]; then
+ . ~/.sugar/debug
+fi
+
+# Start emulator
+python -c "import sys; from jarabe.util import emulator; sys.argv[0]='$0'; emulator.main()" "$@"
diff --git a/shell/bin/sugar-install-bundle b/shell/bin/sugar-install-bundle
new file mode 100644
index 0000000..52deada
--- /dev/null
+++ b/shell/bin/sugar-install-bundle
@@ -0,0 +1,20 @@
+#!/usr/bin/env python
+import sys
+
+from sugar.bundle.activitybundle import ActivityBundle
+
+from dbus.mainloop.glib import DBusGMainLoop
+DBusGMainLoop(set_as_default=True)
+
+def cmd_help():
+ print 'Usage: sugar-install-bundle [ bundlename ] \n\n\
+ Install an activity bundle (.xo). \n'
+
+if len(sys.argv) != 2:
+ cmd_help()
+ sys.exit(2)
+
+bundle = ActivityBundle(sys.argv[1])
+bundle.install()
+
+print "%s: '%s' installed." % (sys.argv[0], sys.argv[1])
diff --git a/shell/bin/sugar-launch b/shell/bin/sugar-launch
new file mode 100644
index 0000000..7297a8e
--- /dev/null
+++ b/shell/bin/sugar-launch
@@ -0,0 +1,89 @@
+#!/usr/bin/env python
+
+# Copyright (C) 2007, Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import sys
+import dbus
+from optparse import OptionParser
+
+from sugar.activity import activityfactory
+from sugar.bundle.activitybundle import ActivityBundle
+
+usage = "usage: %prog [options] activity"
+parser = OptionParser(usage)
+parser.add_option("-d", "--debug", action="store_true", dest="debug",
+ help="launch activity inside gdb")
+(options, args) = parser.parse_args()
+
+if len(args) == 0:
+ print 'You need to specify the activity bundle_id.'
+ sys.exit(1)
+
+bus = dbus.SessionBus()
+proxy = bus.get_object('org.laptop.Shell', '/org/laptop/Shell')
+path = dbus.Interface(proxy, 'org.laptop.Shell').GetBundlePath(args[0])
+if not path:
+ print 'Cannot find %s bundle.' % args[0]
+ sys.exit(1)
+
+activity = ActivityBundle(path)
+cmd_args = activityfactory.get_command(activity)
+
+def _which(exec_file):
+ if 'PATH' in os.environ:
+ envpath = os.environ['PATH']
+ else:
+ return None
+
+ for path in envpath.split(os.pathsep):
+ fullname = os.path.join(path, exec_file)
+ if os.path.exists(fullname):
+ return fullname
+
+ return None
+
+def _get_interpreter(exec_file):
+ if os.path.exists(exec_file):
+ abs_path = exec_file
+ else:
+ abs_path = _which(exec_file)
+ if not abs_path:
+ return exec_file
+
+ f = open(abs_path)
+ line = f.readline(100)
+ if line.startswith('#!'):
+ cmds = line[2:].strip().split(' ')
+ cmds.append(abs_path)
+
+ if '/usr/bin/env' in cmds:
+ cmds.remove('/usr/bin/env')
+
+ return cmds
+
+ return exec_file
+
+if options.debug:
+ act_args = cmd_args
+ cmd_args = ['gdb', '--args']
+ cmd_args.extend(_get_interpreter(act_args.pop(0)))
+ cmd_args.extend(act_args)
+
+os.chdir(str(activity.get_path()))
+os.execvpe(cmd_args[0], cmd_args, activityfactory.get_environment(activity))
+
diff --git a/shell/bin/sugar-session b/shell/bin/sugar-session
new file mode 100755
index 0000000..b1f0086
--- /dev/null
+++ b/shell/bin/sugar-session
@@ -0,0 +1,274 @@
+#!/usr/bin/env python
+# Copyright (C) 2006, Red Hat, Inc.
+# Copyright (C) 2009, One Laptop Per Child Association Inc
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import sys
+import time
+import subprocess
+import shutil
+
+if os.environ.get('SUGAR_LOGGER_LEVEL', '') == 'debug':
+ print '%r STARTUP: Starting the shell' % time.time()
+ sys.stdout.flush()
+
+import gettext
+import logging
+
+import gconf
+import gtk
+import gobject
+import dbus.glib
+import wnck
+
+try:
+ import xklavier
+except ImportError:
+ logging.debug('Could not load xklavier for keyboard configuration')
+
+gtk.gdk.threads_init()
+dbus.glib.threads_init()
+
+def cleanup_logs(logs_dir):
+ """Clean up the log directory, moving old logs into a numbered backup
+ directory. We only keep `_MAX_BACKUP_DIRS` of these backup directories
+ around; the rest are removed."""
+ if not os.path.isdir(logs_dir):
+ os.makedirs(logs_dir)
+
+ backup_logs = []
+ backup_dirs = []
+ for f in os.listdir(logs_dir):
+ path = os.path.join(logs_dir, f)
+ if os.path.isfile(path):
+ backup_logs.append(f)
+ elif os.path.isdir(path):
+ backup_dirs.append(path)
+
+ if len(backup_dirs) > 3:
+ backup_dirs.sort()
+ root = backup_dirs[0]
+ for f in os.listdir(root):
+ os.remove(os.path.join(root, f))
+ os.rmdir(root)
+
+ if len(backup_logs) > 0:
+ name = str(int(time.time()))
+ backup_dir = os.path.join(logs_dir, name)
+ os.mkdir(backup_dir)
+ for log in backup_logs:
+ source_path = os.path.join(logs_dir, log)
+ dest_path = os.path.join(backup_dir, log)
+ os.rename(source_path, dest_path)
+
+def start_ui_service():
+ from jarabe.view.service import UIService
+
+ ui_service = UIService()
+
+def start_session_manager():
+ from jarabe.model.session import get_session_manager
+
+ session_manager = get_session_manager()
+ session_manager.start()
+
+def unfreeze_dcon_cb():
+ logging.debug('STARTUP: unfreeze_dcon_cb')
+ from jarabe.model import screen
+
+ screen.set_dcon_freeze(0)
+
+def setup_frame_cb():
+ logging.debug('STARTUP: setup_frame_cb')
+ from jarabe import frame
+ frame.get_view()
+
+def setup_keyhandler_cb():
+ logging.debug('STARTUP: setup_keyhandler_cb')
+ from jarabe.view import keyhandler
+ from jarabe import frame
+ keyhandler.setup(frame.get_view())
+
+def setup_journal_cb():
+ logging.debug('STARTUP: setup_journal_cb')
+ from jarabe.journal import journalactivity
+ journalactivity.start()
+
+def show_software_updates_cb():
+ logging.debug('STARTUP: show_software_updates_cb')
+ if os.path.isfile(os.path.expanduser('~/.sugar-update')):
+ from jarabe.desktop import homewindow
+ home_window = homewindow.get_instance()
+ home_window.get_home_box().show_software_updates_alert()
+
+def setup_notification_service_cb():
+ from jarabe.model import notifications
+ notifications.init()
+
+def setup_file_transfer_cb():
+ from jarabe.model import filetransfer
+ filetransfer.init()
+
+def setup_keyboard_cb():
+ logging.debug('STARTUP: setup_keyboard_cb')
+
+ gconf_client = gconf.client_get_default()
+
+ try:
+ display = gtk.gdk.display_get_default()
+ if display is not None:
+ engine = xklavier.Engine(display)
+ else:
+ logging.debug('setup_keyboard_cb: Could not get default display.')
+ return
+
+ configrec = xklavier.ConfigRec()
+ configrec.get_from_server(engine)
+
+ layouts = gconf_client.get_list(\
+ '/desktop/sugar/peripherals/keyboard/layouts', gconf.VALUE_STRING)
+ layouts_list = []
+ variants_list = []
+ for layout in layouts:
+ layouts_list.append(layout.split('(')[0])
+ variants_list.append(layout.split('(')[1][:-1])
+
+ if layouts_list is not None and layouts_list is not [] \
+ and variants_list is not None and variants_list is not []:
+ configrec.set_layouts(layouts_list)
+ configrec.set_variants(variants_list)
+
+ model = gconf_client.get_string(\
+ '/desktop/sugar/peripherals/keyboard/model')
+ if model:
+ configrec.set_model(model)
+
+ options = gconf_client.get_list(\
+ '/desktop/sugar/peripherals/keyboard/options', gconf.VALUE_STRING)
+ if options is not [] and options is not None:
+ configrec.set_options(options)
+
+ configrec.activate(engine)
+ except Exception:
+ logging.exception('Error during keyboard configuration')
+
+def setup_window_manager():
+ logging.debug('STARTUP: window_manager')
+
+ # have to reset cursor(metacity sets it on startup)
+ if subprocess.call('echo $DISPLAY; xsetroot -cursor_name left_ptr', shell=True):
+ logging.warning('Can not reset cursor')
+
+ if subprocess.call('metacity-message disable-keybindings',
+ shell=True):
+ logging.warning('Can not disable metacity keybindings')
+
+def bootstrap():
+ setup_window_manager()
+
+ from jarabe.view import launcher
+ launcher.setup()
+
+ gobject.idle_add(setup_frame_cb)
+ gobject.idle_add(setup_keyhandler_cb)
+ gobject.idle_add(setup_journal_cb)
+ gobject.idle_add(setup_notification_service_cb)
+ gobject.idle_add(setup_file_transfer_cb)
+ gobject.idle_add(show_software_updates_cb)
+
+ if sys.modules.has_key('xklavier'):
+ gobject.idle_add(setup_keyboard_cb)
+
+def set_fonts():
+ client = gconf.client_get_default()
+ face = client.get_string('/desktop/sugar/font/default_face')
+ size = client.get_float('/desktop/sugar/font/default_size')
+ settings = gtk.settings_get_default()
+ settings.set_property("gtk-font-name", "%s %f" % (face, size))
+
+def main():
+ try:
+ from sugar import env
+ # Remove temporary files. See http://bugs.sugarlabs.org/ticket/1876
+ data_dir = os.path.join(env.get_profile_path(), 'data')
+ shutil.rmtree(data_dir, ignore_errors=True)
+ os.makedirs(data_dir)
+ cleanup_logs(env.get_logs_path())
+ except OSError, e:
+ # logs cleanup is not critical; it should not prevent sugar from
+ # starting if (for example) the disk is full or read-only.
+ print 'logs cleanup failed: %s' % e
+
+ from sugar import logger
+ # NOTE: This needs to happen so early because some modules register translatable
+ # strings in the module scope.
+ from jarabe import config
+ gettext.bindtextdomain('sugar', config.locale_path)
+ gettext.bindtextdomain('sugar-toolkit', config.locale_path)
+ gettext.textdomain('sugar')
+
+ from jarabe.desktop import homewindow
+ from jarabe.model import sound
+ from jarabe import intro
+
+ logger.start('shell')
+
+ client = gconf.client_get_default()
+ client.set_string('/apps/metacity/general/mouse_button_modifier',
+ 'disabled')
+
+ timezone = client.get_string('/desktop/sugar/date/timezone')
+ if timezone is not None and timezone:
+ os.environ['TZ'] = timezone
+
+ set_fonts()
+
+ # this must be added early, so that it executes and unfreezes the screen
+ # even when we initially get blocked on the intro screen
+ gobject.idle_add(unfreeze_dcon_cb)
+
+ intro.check_profile()
+
+ start_ui_service()
+ start_session_manager()
+
+ sound.restore()
+
+ sys.path.append(config.ext_path)
+
+ icons_path = os.path.join(config.data_path, 'icons')
+ gtk.icon_theme_get_default().append_search_path(icons_path)
+
+ # open homewindow before window_manager to let desktop appear fast
+ home_window = homewindow.get_instance()
+ home_window.show()
+
+ screen = wnck.screen_get_default()
+ screen.connect('window-manager-changed', __window_manager_changed_cb)
+
+ try:
+ gtk.main()
+ except KeyboardInterrupt:
+ print 'Ctrl+C pressed, exiting...'
+
+def __window_manager_changed_cb(screen):
+ wm_name = screen.get_window_manager_name()
+ if wm_name is not None:
+ screen.disconnect_by_func(__window_manager_changed_cb)
+ bootstrap()
+
+main()
diff --git a/shell/bin/sugar-ui-check b/shell/bin/sugar-ui-check
new file mode 100644
index 0000000..4d71796
--- /dev/null
+++ b/shell/bin/sugar-ui-check
@@ -0,0 +1,161 @@
+#!/usr/bin/env python
+# Copyright (C) 2008, Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import logging
+import os
+import sys
+import subprocess
+import time
+
+import dbus
+import gobject
+import gtk
+import wnck
+
+from sugar import wm
+
+from jarabe.model.shell import get_sugar_window_type
+from sugar.bundle.activitybundle import ActivityBundle
+from jarabe import config
+
+checks_queue = []
+checks_failed = []
+checks_succeeded = []
+
+def get_dbus_version():
+ p = subprocess.Popen(['dbus-daemon', '--version'], stdout=subprocess.PIPE)
+
+ output = p.communicate()[0]
+ first_line = output.split('\n')[0]
+
+ return first_line.split(' ')[-1]
+
+class Check(object):
+ def __init__(self):
+ self.name = None
+ self.succeeded = False
+ self.start_time = None
+ self.max_time = None
+ self.timeout = None
+
+ def start(self):
+ logging.info('Start %s check.' % self.name)
+
+ self.start_time = time.time()
+
+ def get_failed(self):
+ if self.max_time and self.start_time:
+ if time.time() - self.start_time > self.max_time:
+ return True
+ return False
+
+ failed = property(get_failed)
+
+class ShellCheck(Check):
+ def __init__(self):
+ Check.__init__(self)
+
+ self.name = 'Shell'
+ self.max_time = 30
+
+ def start(self):
+ Check.start(self)
+
+ screen = wnck.screen_get_default()
+ screen.connect('window-opened', self._window_opened_cb)
+
+ def _window_opened_cb(self, screen, window):
+ if window.get_window_type() == wnck.WINDOW_DESKTOP:
+ self.succeeded = True
+
+class ActivityCheck(Check):
+ def __init__(self, bundle_id):
+ Check.__init__(self)
+
+ self.name = bundle_id
+ self.max_time = 120
+
+ def start(self):
+ Check.start(self)
+
+ self.launch_activity()
+
+ screen = wnck.screen_get_default()
+ screen.connect('window-opened', self._window_opened_cb)
+
+ def launch_activity(self):
+ from sugar.activity import activityfactory
+
+ bus = dbus.SessionBus()
+ proxy = bus.get_object('org.laptop.Shell', '/org/laptop/Shell')
+ iface = dbus.Interface(proxy, 'org.laptop.Shell')
+ path = iface.GetBundlePath(self.name)
+
+ if path:
+ activityfactory.create(ActivityBundle(path))
+ else:
+ logging.error('Cannot find activity %s.' % self.name)
+
+ def _window_opened_cb(self, screen, window):
+ if wm.get_bundle_id(window) == self.name and \
+ get_sugar_window_type(window) != 'launcher':
+ self.succeeded = True
+
+def _timeout_cb():
+ check = checks_queue[0]
+ if check.failed:
+ logging.info('%s check failed.' % (check.name))
+ checks_failed.append(checks_queue.pop(0))
+ elif check.succeeded:
+ logging.info('%s check succeeded.' % (check.name))
+ checks_succeeded.append(checks_queue.pop(0))
+ else:
+ return True
+
+ if len(checks_queue) > 0:
+ checks_queue[0].start()
+ else:
+ gtk.main_quit()
+
+ return True
+
+def main():
+ os.environ['GTK2_RC_FILES'] = os.path.join(config.data_path, 'sugar-100.gtkrc')
+
+ logging.basicConfig(level=logging.INFO,
+ format='%(asctime)s %(levelname)s %(message)s')
+
+ checks_queue.append(ShellCheck())
+
+ if get_dbus_version() >= '1.2.1':
+ # FIXME needs to get a list of the installed activities
+ checks_queue.append(ActivityCheck('org.laptop.Log'))
+ checks_queue.append(ActivityCheck('org.laptop.Chat'))
+ checks_queue.append(ActivityCheck('org.laptop.WebActivity'))
+ checks_queue.append(ActivityCheck('org.laptop.Pippy'))
+ checks_queue.append(ActivityCheck('org.laptop.Terminal'))
+ checks_queue.append(ActivityCheck('org.laptop.AbiWordActivity'))
+
+ checks_queue[0].start()
+ gobject.timeout_add(500, _timeout_cb)
+
+ gtk.main()
+
+ if len(checks_failed) > 0:
+ sys.exit(1)
+
+main()
diff --git a/shell/bin/sugar.in b/shell/bin/sugar.in
new file mode 100644
index 0000000..2df0ab8
--- /dev/null
+++ b/shell/bin/sugar.in
@@ -0,0 +1,76 @@
+#!/bin/sh
+
+if [ "$(id -u)" -eq 0 -o "$(id -ru)" -eq 0 ] ; then
+ echo Refusing to run as root.
+ exit 3
+fi
+
+usage() {
+ cat <<EOF
+Usage: sugar [OPTION]..
+
+Start Sugar window manager.
+
+Optional arguments.
+ -d, --display DISPLAY Display to start sugar
+ -s, --scaling SCALING Scale Sugar theme
+ Supported values: 72, 100
+EOF
+ exit 0
+}
+
+while [ $# -ne 0 ] ; do
+ case "$1" in
+ -d | --display)
+ shift
+ export DISPLAY="$1"
+ ;;
+ -s | --scaling)
+ shift
+ export SUGAR_SCALING="$1"
+ ;;
+ -h | --help)
+ usage
+ ;;
+ esac
+ shift
+done
+
+# Set default profile dir
+if test -z "$SUGAR_PROFILE"; then
+ export SUGAR_PROFILE=default
+fi
+
+if test -z "$SUGAR_SCALING"; then
+ export SUGAR_SCALING=72
+fi
+
+export GTK2_RC_FILES="@prefix@/share/sugar/data/sugar-$SUGAR_SCALING.gtkrc"
+
+# Needed for executing wpa_passphrase
+export PATH="$PATH":/sbin:/usr/sbin
+
+if ! test -f "$GTK2_RC_FILES"; then
+ echo "sugar: ERROR: Gtk theme for scaling $SUGAR_SCALING not available in path $GTK2_RC_FILES"
+ exit 1
+fi
+
+# Set default language
+export LANG="${LANG:-en_US.utf8}"
+export LANGUAGE="${LANGUAGE:-${LANG}}"
+
+# Set Sugar's telepathy accounts directory
+export MC_ACCOUNT_DIR=$HOME/.sugar/$SUGAR_PROFILE/accounts
+
+# Source language settings and debug definitions
+if [ -f ~/.i18n ]; then
+ . ~/.i18n
+fi
+if [ -f ~/.sugar/debug ]; then
+ . ~/.sugar/debug
+fi
+
+echo Xcursor.theme: sugar | xrdb -merge
+metacity --no-force-fullscreen -d $DISPLAY &
+
+exec sugar-session
diff --git a/shell/configure.ac b/shell/configure.ac
new file mode 100644
index 0000000..238aacd
--- /dev/null
+++ b/shell/configure.ac
@@ -0,0 +1,84 @@
+AC_INIT([Sugar],[0.89.3],[],[sugar])
+
+AC_PREREQ([2.59])
+
+AC_CONFIG_MACRO_DIR([m4])
+AC_CONFIG_SRCDIR([configure.ac])
+
+SUCROSE_VERSION="0.89.3"
+AC_SUBST(SUCROSE_VERSION)
+
+AM_INIT_AUTOMAKE([1.9 foreign dist-bzip2 no-dist-gzip])
+
+AM_MAINTAINER_MODE
+
+AM_PATH_PYTHON
+
+PKG_CHECK_MODULES(SHELL, pygtk-2.0 gtk+-2.0 gconf-2.0)
+
+# Setup GETTEXT
+#
+ALL_LINGUAS="af am ar ay bg bi bn_IN bn ca cpp cs da de dz el en es fa_AF fa ff fil fr gu ha he hi ht hu id ig is it ja km ko kos mg mi mk ml mn mr ms mvo nb ne nl pa pap pis pl ps pt_BR pt qu ro ru rw sd si sk sl sq sv sw ta te th tpi tr tvl tzo ug ur vi wa yo zh_CN zh_TW"
+
+GETTEXT_PACKAGE=sugar
+AC_PROG_INTLTOOL([0.33])
+AC_SUBST(GETTEXT_PACKAGE)
+AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE, "$GETTEXT_PACKAGE", [Gettext package])
+AM_GLIB_GNU_GETTEXT
+
+AC_ARG_ENABLE(update-mimedb,
+ AC_HELP_STRING([--disable-update-mimedb],
+ [disable the update-mime-database after install [default=no]]),,
+ enable_update_mimedb=yes)
+AM_CONDITIONAL(ENABLE_UPDATE_MIMEDB, test x$enable_update_mimedb = xyes)
+
+# Verify that gconftool is installed
+#
+AC_PATH_PROG(GCONFTOOL, gconftool-2, no)
+
+if test "$GCONFTOOL" = no; then
+ AC_MSG_ERROR([gconftool-2 executable not found in your path - should be installed with GConf])
+fi
+
+AM_GCONF_SOURCE_2
+
+
+AC_CONFIG_FILES([
+bin/Makefile
+bin/sugar
+data/icons/Makefile
+data/Makefile
+data/sugar-emulator.desktop
+extensions/cpsection/aboutcomputer/Makefile
+extensions/cpsection/aboutme/Makefile
+extensions/cpsection/datetime/Makefile
+extensions/cpsection/frame/Makefile
+extensions/cpsection/keyboard/Makefile
+extensions/cpsection/language/Makefile
+extensions/cpsection/modemconfiguration/Makefile
+extensions/cpsection/Makefile
+extensions/cpsection/network/Makefile
+extensions/cpsection/power/Makefile
+extensions/cpsection/updater/backends/Makefile
+extensions/cpsection/updater/Makefile
+extensions/deviceicon/Makefile
+extensions/globalkey/Makefile
+extensions/Makefile
+Makefile
+po/Makefile.in
+src/jarabe/config.py
+src/jarabe/controlpanel/Makefile
+src/jarabe/desktop/Makefile
+src/jarabe/frame/Makefile
+src/jarabe/intro/Makefile
+src/jarabe/journal/Makefile
+src/jarabe/Makefile
+src/jarabe/model/Makefile
+src/jarabe/util/Makefile
+src/jarabe/util/telepathy/Makefile
+src/jarabe/view/Makefile
+src/Makefile
+])
+
+AC_OUTPUT
+
diff --git a/shell/data/.gitignore b/shell/data/.gitignore
new file mode 100644
index 0000000..8263f12
--- /dev/null
+++ b/shell/data/.gitignore
@@ -0,0 +1,2 @@
+*.gtkrc
+sugar.schemas
diff --git a/COPYING b/shell/data/GPLv2
index ba9543b..d511905 100644
--- a/COPYING
+++ b/shell/data/GPLv2
@@ -1,4 +1,62 @@
GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
@@ -52,7 +110,7 @@ above, provided that you also meet all of these conditions:
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
-
+
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
@@ -110,7 +168,7 @@ access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
-
+
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
@@ -167,7 +225,7 @@ impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
-
+
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
@@ -220,3 +278,62 @@ PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/shell/data/Makefile.am b/shell/data/Makefile.am
new file mode 100644
index 0000000..6a62d23
--- /dev/null
+++ b/shell/data/Makefile.am
@@ -0,0 +1,68 @@
+SUBDIRS = icons
+
+sugar-72.gtkrc: gtkrc.em
+ $(srcdir)/em.py -D scaling=\'72\' $(srcdir)/gtkrc.em > \
+ $(top_builddir)/data/sugar-72.gtkrc
+
+sugar-100.gtkrc: gtkrc.em
+ $(srcdir)/em.py -D scaling=\'100\' $(srcdir)/gtkrc.em > \
+ $(top_builddir)/data/sugar-100.gtkrc
+
+sugardir = $(pkgdatadir)/data
+sugar_DATA = \
+ activities.defaults \
+ kbdconfig \
+ mime.defaults \
+ GPLv2 \
+ $(GTKRC_FILES)
+
+GTKRC_FILES = \
+ sugar-72.gtkrc \
+ sugar-100.gtkrc
+
+xsessionsdir = $(datadir)/xsessions
+xsessions_DATA = sugar.desktop
+
+applicationsdir = $(datadir)/applications
+applications_DATA = sugar-emulator.desktop
+
+mime_xml_in_files = sugar.xml.in
+mime_xml_files = $(mime_xml_in_files:.xml.in=.xml)
+@INTLTOOL_XML_RULE@
+
+mimedir = $(datadir)/mime/packages
+mime_DATA = $(mime_xml_files)
+
+nmservicedir = $(sysconfdir)/dbus-1/system.d/
+nmservice_DATA = nm-user-settings.conf
+
+install-data-hook:
+if ENABLE_UPDATE_MIMEDB
+ if [ -z "$$DESTDIR" ]; then \
+ update-mime-database "$(datadir)/mime"; \
+ fi
+endif
+
+uninstall-hook:
+if ENABLE_UPDATE_MIMEDB
+ if [ -z "$$DESTDIR" ]; then \
+ update-mime-database "$(datadir)/mime"; \
+ fi
+endif
+
+@INTLTOOL_SCHEMAS_RULE@
+
+schemadir = $(GCONF_SCHEMA_FILE_DIR)
+schema_in_files = sugar.schemas.in
+schema_DATA = $(schema_in_files:.schemas.in=.schemas)
+
+install-data-local: $(schema_DATA)
+if GCONF_SCHEMAS_INSTALL
+ GCONF_CONFIG_SOURCE=$(GCONF_SCHEMA_CONFIG_SOURCE) $(GCONFTOOL) --makefile-install-rule sugar.schemas 2>&1 > /dev/null
+endif
+
+icondir = $(datadir)/icons/hicolor/scalable/apps
+icon_DATA = sugar-xo.svg
+
+EXTRA_DIST = $(sugar_DATA) $(xsessions_DATA) $(nmservice_DATA) $(mime_xml_in_files) em.py gtkrc.em $(schema_in_files) $(icon_DATA)
+CLEANFILES = $(GTKRC_FILES) $(mime_xml_files) $(schema_DATA)
diff --git a/shell/data/activities.defaults b/shell/data/activities.defaults
new file mode 100644
index 0000000..c294b99
--- /dev/null
+++ b/shell/data/activities.defaults
@@ -0,0 +1,28 @@
+# Activities to be automatically added to the ring after an upgrade
+
+com.garycmartin.Moon
+com.jotaro.ImplodeActivity
+com.laptop.Ruler
+edu.mit.media.ScratchActivity
+org.laptop.AbiWordActivity
+org.laptop.AcousticMeasure
+org.laptop.Analyze
+org.laptop.Calculate
+org.laptop.Chat
+org.laptop.HelpActivity
+org.laptop.MeasureActivity
+org.laptop.Memorize
+org.laptop.Oficina
+org.laptop.Pippy
+org.laptop.RecordActivity
+org.laptop.TamTamEdit
+org.laptop.TamTamJam
+org.laptop.TamTamMini
+org.laptop.TamTamSynthLab
+org.laptop.TurtleArtActivity
+org.laptop.WebActivity
+org.laptop.WikipediaActivityEN
+org.laptop.sugar.ReadActivity
+org.vpri.EtoysActivity
+vu.lux.olpc.Maze
+vu.lux.olpc.Speak
diff --git a/shell/data/em.py b/shell/data/em.py
new file mode 100755
index 0000000..165d29e
--- /dev/null
+++ b/shell/data/em.py
@@ -0,0 +1,3302 @@
+#!/usr/bin/env python
+#
+# $Id: //projects/empy/em.py#146 $ $Date: 2003/10/27 $
+
+# 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
+
+"""
+A system for processing Python as markup embedded in text.
+"""
+
+
+__program__ = 'empy'
+__version__ = '3.3'
+__url__ = 'http://www.alcyone.com/software/empy/'
+__author__ = 'Erik Max Francis <max@alcyone.com>'
+__copyright__ = 'Copyright (C) 2002-2003 Erik Max Francis'
+__license__ = 'LGPL'
+
+
+import copy
+import getopt
+import os
+import re
+import string
+import sys
+import types
+
+try:
+ # The equivalent of import cStringIO as StringIO.
+ import cStringIO
+ StringIO = cStringIO
+ del cStringIO
+except ImportError:
+ import StringIO
+
+# For backward compatibility, we can't assume these are defined.
+False, True = 0, 1
+
+# Some basic defaults.
+FAILURE_CODE = 1
+DEFAULT_PREFIX = '@'
+DEFAULT_PSEUDOMODULE_NAME = 'empy'
+DEFAULT_SCRIPT_NAME = '?'
+SIGNIFICATOR_RE_SUFFIX = r"%(\S+)\s*(.*)\s*$"
+SIGNIFICATOR_RE_STRING = DEFAULT_PREFIX + SIGNIFICATOR_RE_SUFFIX
+BANGPATH = '#!'
+DEFAULT_CHUNK_SIZE = 8192
+DEFAULT_ERRORS = 'strict'
+
+# Character information.
+IDENTIFIER_FIRST_CHARS = '_abcdefghijklmnopqrstuvwxyz' \
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+IDENTIFIER_CHARS = IDENTIFIER_FIRST_CHARS + '0123456789.'
+ENDING_CHARS = {'(': ')', '[': ']', '{': '}'}
+
+# Environment variable names.
+OPTIONS_ENV = 'EMPY_OPTIONS'
+PREFIX_ENV = 'EMPY_PREFIX'
+PSEUDO_ENV = 'EMPY_PSEUDO'
+FLATTEN_ENV = 'EMPY_FLATTEN'
+RAW_ENV = 'EMPY_RAW_ERRORS'
+INTERACTIVE_ENV = 'EMPY_INTERACTIVE'
+BUFFERED_ENV = 'EMPY_BUFFERED_OUTPUT'
+NO_OVERRIDE_ENV = 'EMPY_NO_OVERRIDE'
+UNICODE_ENV = 'EMPY_UNICODE'
+INPUT_ENCODING_ENV = 'EMPY_UNICODE_INPUT_ENCODING'
+OUTPUT_ENCODING_ENV = 'EMPY_UNICODE_OUTPUT_ENCODING'
+INPUT_ERRORS_ENV = 'EMPY_UNICODE_INPUT_ERRORS'
+OUTPUT_ERRORS_ENV = 'EMPY_UNICODE_OUTPUT_ERRORS'
+
+# Interpreter options.
+BANGPATH_OPT = 'processBangpaths' # process bangpaths as comments?
+BUFFERED_OPT = 'bufferedOutput' # fully buffered output?
+RAW_OPT = 'rawErrors' # raw errors?
+EXIT_OPT = 'exitOnError' # exit on error?
+FLATTEN_OPT = 'flatten' # flatten pseudomodule namespace?
+OVERRIDE_OPT = 'override' # override sys.stdout with proxy?
+CALLBACK_OPT = 'noCallbackError' # is no custom callback an error?
+
+# Usage info.
+OPTION_INFO = [
+("-V --version", "Print version and exit"),
+("-h --help", "Print usage and exit"),
+("-H --extended-help", "Print extended usage and exit"),
+("-k --suppress-errors", "Do not exit on errors; go interactive"),
+("-p --prefix=<char>", "Change prefix to something other than @"),
+(" --no-prefix", "Do not do any markup processing at all"),
+("-m --module=<name>", "Change the internal pseudomodule name"),
+("-f --flatten", "Flatten the members of pseudmodule to start"),
+("-r --raw-errors", "Show raw Python errors"),
+("-i --interactive", "Go into interactive mode after processing"),
+("-n --no-override-stdout", "Do not override sys.stdout with proxy"),
+("-o --output=<filename>", "Specify file for output as write"),
+("-a --append=<filename>", "Specify file for output as append"),
+("-b --buffered-output", "Fully buffer output including open"),
+(" --binary", "Treat the file as a binary"),
+(" --chunk-size=<chunk>", "Use this chunk size for reading binaries"),
+("-P --preprocess=<filename>", "Interpret EmPy file before main processing"),
+("-I --import=<modules>", "Import Python modules before processing"),
+("-D --define=<definition>", "Execute Python assignment statement"),
+("-E --execute=<statement>", "Execute Python statement before processing"),
+("-F --execute-file=<filename>", "Execute Python file before processing"),
+(" --pause-at-end", "Prompt at the ending of processing"),
+(" --relative-path", "Add path of EmPy script to sys.path"),
+(" --no-callback-error", "Custom markup without callback is error"),
+(" --no-bangpath-processing", "Suppress bangpaths as comments"),
+("-u --unicode", "Enable Unicode subsystem (Python 2+ only)"),
+(" --unicode-encoding=<e>", "Set both input and output encodings"),
+(" --unicode-input-encoding=<e>", "Set input encoding"),
+(" --unicode-output-encoding=<e>", "Set output encoding"),
+(" --unicode-errors=<E>", "Set both input and output error handler"),
+(" --unicode-input-errors=<E>", "Set input error handler"),
+(" --unicode-output-errors=<E>", "Set output error handler"),
+]
+
+USAGE_NOTES = """\
+Notes: Whitespace immediately inside parentheses of @(...) are
+ignored. Whitespace immediately inside braces of @{...} are ignored,
+unless ... spans multiple lines. Use @{ ... }@ to suppress newline
+following expansion. Simple expressions ignore trailing dots; `@x.'
+means `@(x).'. A #! at the start of a file is treated as a @#
+comment."""
+
+MARKUP_INFO = [
+("@# ... NL", "Comment; remove everything up to newline"),
+("@? NAME NL", "Set the current context name"),
+("@! INTEGER NL", "Set the current context line number"),
+("@ WHITESPACE", "Remove following whitespace; line continuation"),
+("@\\ ESCAPE_CODE", "A C-style escape sequence"),
+("@@", "Literal @; @ is escaped (duplicated prefix)"),
+("@), @], @}", "Literal close parenthesis, bracket, brace"),
+("@ STRING_LITERAL", "Replace with string literal contents"),
+("@( EXPRESSION )", "Evaluate expression and substitute with str"),
+("@( TEST [? THEN [! ELSE]] )", "If test is true, evaluate then, otherwise else"),
+("@( TRY $ CATCH )", "Expand try expression, or catch if it raises"),
+("@ SIMPLE_EXPRESSION", "Evaluate simple expression and substitute;\n"
+ "e.g., @x, @x.y, @f(a, b), @l[i], etc."),
+("@` EXPRESSION `", "Evaluate expression and substitute with repr"),
+("@: EXPRESSION : [DUMMY] :", "Evaluates to @:...:expansion:"),
+("@{ STATEMENTS }", "Statements are executed for side effects"),
+("@[ CONTROL ]", "Control markups: if E; elif E; for N in E;\n"
+ "while E; try; except E, N; finally; continue;\n"
+ "break; end X"),
+("@%% KEY WHITESPACE VALUE NL", "Significator form of __KEY__ = VALUE"),
+("@< CONTENTS >", "Custom markup; meaning provided by user"),
+]
+
+ESCAPE_INFO = [
+("@\\0", "NUL, null"),
+("@\\a", "BEL, bell"),
+("@\\b", "BS, backspace"),
+("@\\dDDD", "three-digit decimal code DDD"),
+("@\\e", "ESC, escape"),
+("@\\f", "FF, form feed"),
+("@\\h", "DEL, delete"),
+("@\\n", "LF, linefeed, newline"),
+("@\\N{NAME}", "Unicode character named NAME"),
+("@\\oOOO", "three-digit octal code OOO"),
+("@\\qQQQQ", "four-digit quaternary code QQQQ"),
+("@\\r", "CR, carriage return"),
+("@\\s", "SP, space"),
+("@\\t", "HT, horizontal tab"),
+("@\\uHHHH", "16-bit hexadecimal Unicode HHHH"),
+("@\\UHHHHHHHH", "32-bit hexadecimal Unicode HHHHHHHH"),
+("@\\v", "VT, vertical tab"),
+("@\\xHH", "two-digit hexadecimal code HH"),
+("@\\z", "EOT, end of transmission"),
+]
+
+PSEUDOMODULE_INFO = [
+("VERSION", "String representing EmPy version"),
+("SIGNIFICATOR_RE_STRING", "Regular expression matching significators"),
+("SIGNIFICATOR_RE_SUFFIX", "The above stub, lacking the prefix"),
+("interpreter", "Currently-executing interpreter instance"),
+("argv", "The EmPy script name and command line arguments"),
+("args", "The command line arguments only"),
+("identify()", "Identify top context as name, line"),
+("setContextName(name)", "Set the name of the current context"),
+("setContextLine(line)", "Set the line number of the current context"),
+("atExit(callable)", "Invoke no-argument function at shutdown"),
+("getGlobals()", "Retrieve this interpreter's globals"),
+("setGlobals(dict)", "Set this interpreter's globals"),
+("updateGlobals(dict)", "Merge dictionary into interpreter's globals"),
+("clearGlobals()", "Start globals over anew"),
+("saveGlobals([deep])", "Save a copy of the globals"),
+("restoreGlobals([pop])", "Restore the most recently saved globals"),
+("defined(name, [loc])", "Find if the name is defined"),
+("evaluate(expression, [loc])", "Evaluate the expression"),
+("serialize(expression, [loc])", "Evaluate and serialize the expression"),
+("execute(statements, [loc])", "Execute the statements"),
+("single(source, [loc])", "Execute the 'single' object"),
+("atomic(name, value, [loc])", "Perform an atomic assignment"),
+("assign(name, value, [loc])", "Perform an arbitrary assignment"),
+("significate(key, [value])", "Significate the given key, value pair"),
+("include(file, [loc])", "Include filename or file-like object"),
+("expand(string, [loc])", "Explicitly expand string and return"),
+("string(data, [name], [loc])", "Process string-like object"),
+("quote(string)", "Quote prefixes in provided string and return"),
+("flatten([keys])", "Flatten module contents into globals namespace"),
+("getPrefix()", "Get current prefix"),
+("setPrefix(char)", "Set new prefix"),
+("stopDiverting()", "Stop diverting; data sent directly to output"),
+("createDiversion(name)", "Create a diversion but do not divert to it"),
+("retrieveDiversion(name)", "Retrieve the actual named diversion object"),
+("startDiversion(name)", "Start diverting to given diversion"),
+("playDiversion(name)", "Recall diversion and then eliminate it"),
+("replayDiversion(name)", "Recall diversion but retain it"),
+("purgeDiversion(name)", "Erase diversion"),
+("playAllDiversions()", "Stop diverting and play all diversions in order"),
+("replayAllDiversions()", "Stop diverting and replay all diversions"),
+("purgeAllDiversions()", "Stop diverting and purge all diversions"),
+("getFilter()", "Get current filter"),
+("resetFilter()", "Reset filter; no filtering"),
+("nullFilter()", "Install null filter"),
+("setFilter(shortcut)", "Install new filter or filter chain"),
+("attachFilter(shortcut)", "Attach single filter to end of current chain"),
+("areHooksEnabled()", "Return whether or not hooks are enabled"),
+("enableHooks()", "Enable hooks (default)"),
+("disableHooks()", "Disable hook invocation"),
+("getHooks()", "Get all the hooks"),
+("clearHooks()", "Clear all hooks"),
+("addHook(hook, [i])", "Register the hook (optionally insert)"),
+("removeHook(hook)", "Remove an already-registered hook from name"),
+("invokeHook(name_, ...)", "Manually invoke hook"),
+("getCallback()", "Get interpreter callback"),
+("registerCallback(callback)", "Register callback with interpreter"),
+("deregisterCallback()", "Deregister callback from interpreter"),
+("invokeCallback(contents)", "Invoke the callback directly"),
+("Interpreter", "The interpreter class"),
+]
+
+ENVIRONMENT_INFO = [
+(OPTIONS_ENV, "Specified options will be included"),
+(PREFIX_ENV, "Specify the default prefix: -p <value>"),
+(PSEUDO_ENV, "Specify name of pseudomodule: -m <value>"),
+(FLATTEN_ENV, "Flatten empy pseudomodule if defined: -f"),
+(RAW_ENV, "Show raw errors if defined: -r"),
+(INTERACTIVE_ENV, "Enter interactive mode if defined: -i"),
+(BUFFERED_ENV, "Fully buffered output if defined: -b"),
+(NO_OVERRIDE_ENV, "Do not override sys.stdout if defined: -n"),
+(UNICODE_ENV, "Enable Unicode subsystem: -n"),
+(INPUT_ENCODING_ENV, "Unicode input encoding"),
+(OUTPUT_ENCODING_ENV, "Unicode output encoding"),
+(INPUT_ERRORS_ENV, "Unicode input error handler"),
+(OUTPUT_ERRORS_ENV, "Unicode output error handler"),
+]
+
+class Error(Exception):
+ """The base class for all EmPy errors."""
+ pass
+
+EmpyError = EmPyError = Error # DEPRECATED
+
+class DiversionError(Error):
+ """An error related to diversions."""
+ pass
+
+class FilterError(Error):
+ """An error related to filters."""
+ pass
+
+class StackUnderflowError(Error):
+ """A stack underflow."""
+ pass
+
+class SubsystemError(Error):
+ """An error associated with the Unicode subsystem."""
+ pass
+
+class FlowError(Error):
+ """An exception related to control flow."""
+ pass
+
+class ContinueFlow(FlowError):
+ """A continue control flow."""
+ pass
+
+class BreakFlow(FlowError):
+ """A break control flow."""
+ pass
+
+class ParseError(Error):
+ """A parse error occurred."""
+ pass
+
+class TransientParseError(ParseError):
+ """A parse error occurred which may be resolved by feeding more data.
+ Such an error reaching the toplevel is an unexpected EOF error."""
+ pass
+
+
+class MetaError(Exception):
+
+ """A wrapper around a real Python exception for including a copy of
+ the context."""
+
+ def __init__(self, contexts, exc):
+ Exception.__init__(self, exc)
+ self.contexts = contexts
+ self.exc = exc
+
+ def __str__(self):
+ backtrace = map(lambda x: str(x), self.contexts)
+ return "%s: %s (%s)" % (self.exc.__class__, self.exc, \
+ (string.join(backtrace, ', ')))
+
+
+class Subsystem:
+
+ """The subsystem class defers file creation so that it can create
+ Unicode-wrapped files if desired (and possible)."""
+
+ def __init__(self):
+ self.useUnicode = False
+ self.inputEncoding = None
+ self.outputEncoding = None
+ self.errors = None
+
+ def initialize(self, inputEncoding=None, outputEncoding=None, \
+ inputErrors=None, outputErrors=None):
+ self.useUnicode = True
+ try:
+ unicode
+ import codecs
+ except (NameError, ImportError):
+ raise SubsystemError, "Unicode subsystem unavailable"
+ defaultEncoding = sys.getdefaultencoding()
+ if inputEncoding is None:
+ inputEncoding = defaultEncoding
+ self.inputEncoding = inputEncoding
+ if outputEncoding is None:
+ outputEncoding = defaultEncoding
+ self.outputEncoding = outputEncoding
+ if inputErrors is None:
+ inputErrors = DEFAULT_ERRORS
+ self.inputErrors = inputErrors
+ if outputErrors is None:
+ outputErrors = DEFAULT_ERRORS
+ self.outputErrors = outputErrors
+
+ def assertUnicode(self):
+ if not self.useUnicode:
+ raise SubsystemError, "Unicode subsystem unavailable"
+
+ def open(self, name, mode=None):
+ if self.useUnicode:
+ return self.unicodeOpen(name, mode)
+ else:
+ return self.defaultOpen(name, mode)
+
+ def defaultOpen(self, name, mode=None):
+ if mode is None:
+ mode = 'r'
+ return open(name, mode)
+
+ def unicodeOpen(self, name, mode=None):
+ import codecs
+ if mode is None:
+ mode = 'rb'
+ if mode.find('w') >= 0 or mode.find('a') >= 0:
+ encoding = self.outputEncoding
+ errors = self.outputErrors
+ else:
+ encoding = self.inputEncoding
+ errors = self.inputErrors
+ return codecs.open(name, mode, encoding, errors)
+
+theSubsystem = Subsystem()
+
+
+class Stack:
+
+ """A simple stack that behaves as a sequence (with 0 being the top
+ of the stack, not the bottom)."""
+
+ def __init__(self, seq=None):
+ if seq is None:
+ seq = []
+ self.data = seq
+
+ def top(self):
+ """Access the top element on the stack."""
+ try:
+ return self.data[-1]
+ except IndexError:
+ raise StackUnderflowError, "stack is empty for top"
+
+ def pop(self):
+ """Pop the top element off the stack and return it."""
+ try:
+ return self.data.pop()
+ except IndexError:
+ raise StackUnderflowError, "stack is empty for pop"
+
+ def push(self, object):
+ """Push an element onto the top of the stack."""
+ self.data.append(object)
+
+ def filter(self, function):
+ """Filter the elements of the stack through the function."""
+ self.data = filter(function, self.data)
+
+ def purge(self):
+ """Purge the stack."""
+ self.data = []
+
+ def clone(self):
+ """Create a duplicate of this stack."""
+ return self.__class__(self.data[:])
+
+ def __nonzero__(self): return len(self.data) != 0
+ def __len__(self): return len(self.data)
+ def __getitem__(self, index): return self.data[-(index + 1)]
+
+ def __repr__(self):
+ return '<%s instance at 0x%x [%s]>' % \
+ (self.__class__, id(self), \
+ string.join(map(repr, self.data), ', '))
+
+
+class AbstractFile:
+
+ """An abstracted file that, when buffered, will totally buffer the
+ file, including even the file open."""
+
+ def __init__(self, filename, mode='w', buffered=False):
+ # The calls below might throw, so start off by marking this
+ # file as "done." This way destruction of a not-completely-
+ # initialized AbstractFile will generate no further errors.
+ self.done = True
+ self.filename = filename
+ self.mode = mode
+ self.buffered = buffered
+ if buffered:
+ self.bufferFile = StringIO.StringIO()
+ else:
+ self.bufferFile = theSubsystem.open(filename, mode)
+ # Okay, we got this far, so the AbstractFile is initialized.
+ # Flag it as "not done."
+ self.done = False
+
+ def __del__(self):
+ self.close()
+
+ def write(self, data):
+ self.bufferFile.write(data)
+
+ def writelines(self, data):
+ self.bufferFile.writelines(data)
+
+ def flush(self):
+ self.bufferFile.flush()
+
+ def close(self):
+ if not self.done:
+ self.commit()
+ self.done = True
+
+ def commit(self):
+ if self.buffered:
+ file = theSubsystem.open(self.filename, self.mode)
+ file.write(self.bufferFile.getvalue())
+ file.close()
+ else:
+ self.bufferFile.close()
+
+ def abort(self):
+ if self.buffered:
+ self.bufferFile = None
+ else:
+ self.bufferFile.close()
+ self.bufferFile = None
+ self.done = True
+
+
+class Diversion:
+
+ """The representation of an active diversion. Diversions act as
+ (writable) file objects, and then can be recalled either as pure
+ strings or (readable) file objects."""
+
+ def __init__(self):
+ self.file = StringIO.StringIO()
+
+ # These methods define the writable file-like interface for the
+ # diversion.
+
+ def write(self, data):
+ self.file.write(data)
+
+ def writelines(self, lines):
+ for line in lines:
+ self.write(line)
+
+ def flush(self):
+ self.file.flush()
+
+ def close(self):
+ self.file.close()
+
+ # These methods are specific to diversions.
+
+ def asString(self):
+ """Return the diversion as a string."""
+ return self.file.getvalue()
+
+ def asFile(self):
+ """Return the diversion as a file."""
+ return StringIO.StringIO(self.file.getvalue())
+
+
+class Stream:
+
+ """A wrapper around an (output) file object which supports
+ diversions and filtering."""
+
+ def __init__(self, file):
+ self.file = file
+ self.currentDiversion = None
+ self.diversions = {}
+ self.filter = file
+ self.done = False
+
+ def write(self, data):
+ if self.currentDiversion is None:
+ self.filter.write(data)
+ else:
+ self.diversions[self.currentDiversion].write(data)
+
+ def writelines(self, lines):
+ for line in lines:
+ self.write(line)
+
+ def flush(self):
+ self.filter.flush()
+
+ def close(self):
+ if not self.done:
+ self.undivertAll(True)
+ self.filter.close()
+ self.done = True
+
+ def shortcut(self, shortcut):
+ """Take a filter shortcut and translate it into a filter, returning
+ it. Sequences don't count here; these should be detected
+ independently."""
+ if shortcut == 0:
+ return NullFilter()
+ elif type(shortcut) is types.FunctionType or \
+ type(shortcut) is types.BuiltinFunctionType or \
+ type(shortcut) is types.BuiltinMethodType or \
+ type(shortcut) is types.LambdaType:
+ return FunctionFilter(shortcut)
+ elif type(shortcut) is types.StringType:
+ return StringFilter(filter)
+ elif type(shortcut) is types.DictType:
+ raise NotImplementedError, "mapping filters not yet supported"
+ else:
+ # Presume it's a plain old filter.
+ return shortcut
+
+ def last(self):
+ """Find the last filter in the current filter chain, or None if
+ there are no filters installed."""
+ if self.filter is None:
+ return None
+ thisFilter, lastFilter = self.filter, None
+ while thisFilter is not None and thisFilter is not self.file:
+ lastFilter = thisFilter
+ thisFilter = thisFilter.next()
+ return lastFilter
+
+ def install(self, shortcut=None):
+ """Install a new filter; None means no filter. Handle all the
+ special shortcuts for filters here."""
+ # Before starting, execute a flush.
+ self.filter.flush()
+ if shortcut is None or shortcut == [] or shortcut == ():
+ # Shortcuts for "no filter."
+ self.filter = self.file
+ else:
+ if type(shortcut) in (types.ListType, types.TupleType):
+ shortcuts = list(shortcut)
+ else:
+ shortcuts = [shortcut]
+ # Run through the shortcut filter names, replacing them with
+ # full-fledged instances of Filter.
+ filters = []
+ for shortcut in shortcuts:
+ filters.append(self.shortcut(shortcut))
+ if len(filters) > 1:
+ # If there's more than one filter provided, chain them
+ # together.
+ lastFilter = None
+ for filter in filters:
+ if lastFilter is not None:
+ lastFilter.attach(filter)
+ lastFilter = filter
+ lastFilter.attach(self.file)
+ self.filter = filters[0]
+ else:
+ # If there's only one filter, assume that it's alone or it's
+ # part of a chain that has already been manually chained;
+ # just find the end.
+ filter = filters[0]
+ lastFilter = filter.last()
+ lastFilter.attach(self.file)
+ self.filter = filter
+
+ def attach(self, shortcut):
+ """Attached a solitary filter (no sequences allowed here) at the
+ end of the current filter chain."""
+ lastFilter = self.last()
+ if lastFilter is None:
+ # Just install it from scratch if there is no active filter.
+ self.install(shortcut)
+ else:
+ # Attach the last filter to this one, and this one to the file.
+ filter = self.shortcut(shortcut)
+ lastFilter.attach(filter)
+ filter.attach(self.file)
+
+ def revert(self):
+ """Reset any current diversions."""
+ self.currentDiversion = None
+
+ def create(self, name):
+ """Create a diversion if one does not already exist, but do not
+ divert to it yet."""
+ if name is None:
+ raise DiversionError, "diversion name must be non-None"
+ if not self.diversions.has_key(name):
+ self.diversions[name] = Diversion()
+
+ def retrieve(self, name):
+ """Retrieve the given diversion."""
+ if name is None:
+ raise DiversionError, "diversion name must be non-None"
+ if self.diversions.has_key(name):
+ return self.diversions[name]
+ else:
+ raise DiversionError, "nonexistent diversion: %s" % name
+
+ def divert(self, name):
+ """Start diverting."""
+ if name is None:
+ raise DiversionError, "diversion name must be non-None"
+ self.create(name)
+ self.currentDiversion = name
+
+ def undivert(self, name, purgeAfterwards=False):
+ """Undivert a particular diversion."""
+ if name is None:
+ raise DiversionError, "diversion name must be non-None"
+ if self.diversions.has_key(name):
+ diversion = self.diversions[name]
+ self.filter.write(diversion.asString())
+ if purgeAfterwards:
+ self.purge(name)
+ else:
+ raise DiversionError, "nonexistent diversion: %s" % name
+
+ def purge(self, name):
+ """Purge the specified diversion."""
+ if name is None:
+ raise DiversionError, "diversion name must be non-None"
+ if self.diversions.has_key(name):
+ del self.diversions[name]
+ if self.currentDiversion == name:
+ self.currentDiversion = None
+
+ def undivertAll(self, purgeAfterwards=True):
+ """Undivert all pending diversions."""
+ if self.diversions:
+ self.revert() # revert before undiverting!
+ names = self.diversions.keys()
+ names.sort()
+ for name in names:
+ self.undivert(name)
+ if purgeAfterwards:
+ self.purge(name)
+
+ def purgeAll(self):
+ """Eliminate all existing diversions."""
+ if self.diversions:
+ self.diversions = {}
+ self.currentDiversion = None
+
+
+class NullFile:
+
+ """A simple class that supports all the file-like object methods
+ but simply does nothing at all."""
+
+ def __init__(self): pass
+ def write(self, data): pass
+ def writelines(self, lines): pass
+ def flush(self): pass
+ def close(self): pass
+
+
+class UncloseableFile:
+
+ """A simple class which wraps around a delegate file-like object
+ and lets everything through except close calls."""
+
+ def __init__(self, delegate):
+ self.delegate = delegate
+
+ def write(self, data):
+ self.delegate.write(data)
+
+ def writelines(self, lines):
+ self.delegate.writelines(data)
+
+ def flush(self):
+ self.delegate.flush()
+
+ def close(self):
+ """Eat this one."""
+ pass
+
+
+class ProxyFile:
+
+ """The proxy file object that is intended to take the place of
+ sys.stdout. The proxy can manage a stack of file objects it is
+ writing to, and an underlying raw file object."""
+
+ def __init__(self, bottom):
+ self.stack = Stack()
+ self.bottom = bottom
+
+ def current(self):
+ """Get the current stream to write to."""
+ if self.stack:
+ return self.stack[-1][1]
+ else:
+ return self.bottom
+
+ def push(self, interpreter):
+ self.stack.push((interpreter, interpreter.stream()))
+
+ def pop(self, interpreter):
+ result = self.stack.pop()
+ assert interpreter is result[0]
+
+ def clear(self, interpreter):
+ self.stack.filter(lambda x, i=interpreter: x[0] is not i)
+
+ def write(self, data):
+ self.current().write(data)
+
+ def writelines(self, lines):
+ self.current().writelines(lines)
+
+ def flush(self):
+ self.current().flush()
+
+ def close(self):
+ """Close the current file. If the current file is the bottom, then
+ close it and dispose of it."""
+ current = self.current()
+ if current is self.bottom:
+ self.bottom = None
+ current.close()
+
+ def _testProxy(self): pass
+
+
+class Filter:
+
+ """An abstract filter."""
+
+ def __init__(self):
+ if self.__class__ is Filter:
+ raise NotImplementedError
+ self.sink = None
+
+ def next(self):
+ """Return the next filter/file-like object in the sequence, or None."""
+ return self.sink
+
+ def write(self, data):
+ """The standard write method; this must be overridden in subclasses."""
+ raise NotImplementedError
+
+ def writelines(self, lines):
+ """Standard writelines wrapper."""
+ for line in lines:
+ self.write(line)
+
+ def _flush(self):
+ """The _flush method should always flush the sink and should not
+ be overridden."""
+ self.sink.flush()
+
+ def flush(self):
+ """The flush method can be overridden."""
+ self._flush()
+
+ def close(self):
+ """Close the filter. Do an explicit flush first, then close the
+ sink."""
+ self.flush()
+ self.sink.close()
+
+ def attach(self, filter):
+ """Attach a filter to this one."""
+ if self.sink is not None:
+ # If it's already attached, detach it first.
+ self.detach()
+ self.sink = filter
+
+ def detach(self):
+ """Detach a filter from its sink."""
+ self.flush()
+ self._flush() # do a guaranteed flush to just to be safe
+ self.sink = None
+
+ def last(self):
+ """Find the last filter in this chain."""
+ this, last = self, self
+ while this is not None:
+ last = this
+ this = this.next()
+ return last
+
+class NullFilter(Filter):
+
+ """A filter that never sends any output to its sink."""
+
+ def write(self, data): pass
+
+class FunctionFilter(Filter):
+
+ """A filter that works simply by pumping its input through a
+ function which maps strings into strings."""
+
+ def __init__(self, function):
+ Filter.__init__(self)
+ self.function = function
+
+ def write(self, data):
+ self.sink.write(self.function(data))
+
+class StringFilter(Filter):
+
+ """A filter that takes a translation string (256 characters) and
+ filters any incoming data through it."""
+
+ def __init__(self, table):
+ if not (type(table) == types.StringType and len(table) == 256):
+ raise FilterError, "table must be 256-character string"
+ Filter.__init__(self)
+ self.table = table
+
+ def write(self, data):
+ self.sink.write(string.translate(data, self.table))
+
+class BufferedFilter(Filter):
+
+ """A buffered filter is one that doesn't modify the source data
+ sent to the sink, but instead holds it for a time. The standard
+ variety only sends the data along when it receives a flush
+ command."""
+
+ def __init__(self):
+ Filter.__init__(self)
+ self.buffer = ''
+
+ def write(self, data):
+ self.buffer = self.buffer + data
+
+ def flush(self):
+ if self.buffer:
+ self.sink.write(self.buffer)
+ self._flush()
+
+class SizeBufferedFilter(BufferedFilter):
+
+ """A size-buffered filter only in fixed size chunks (excepting the
+ final chunk)."""
+
+ def __init__(self, bufferSize):
+ BufferedFilter.__init__(self)
+ self.bufferSize = bufferSize
+
+ def write(self, data):
+ BufferedFilter.write(self, data)
+ while len(self.buffer) > self.bufferSize:
+ chunk, self.buffer = \
+ self.buffer[:self.bufferSize], self.buffer[self.bufferSize:]
+ self.sink.write(chunk)
+
+class LineBufferedFilter(BufferedFilter):
+
+ """A line-buffered filter only lets data through when it sees
+ whole lines."""
+
+ def __init__(self):
+ BufferedFilter.__init__(self)
+
+ def write(self, data):
+ BufferedFilter.write(self, data)
+ chunks = string.split(self.buffer, '\n')
+ for chunk in chunks[:-1]:
+ self.sink.write(chunk + '\n')
+ self.buffer = chunks[-1]
+
+class MaximallyBufferedFilter(BufferedFilter):
+
+ """A maximally-buffered filter only lets its data through on the final
+ close. It ignores flushes."""
+
+ def __init__(self):
+ BufferedFilter.__init__(self)
+
+ def flush(self): pass
+
+ def close(self):
+ if self.buffer:
+ BufferedFilter.flush(self)
+ self.sink.close()
+
+
+class Context:
+
+ """An interpreter context, which encapsulates a name, an input
+ file object, and a parser object."""
+
+ DEFAULT_UNIT = 'lines'
+
+ def __init__(self, name, line=0, units=DEFAULT_UNIT):
+ self.name = name
+ self.line = line
+ self.units = units
+ self.pause = False
+
+ def bump(self, quantity=1):
+ if self.pause:
+ self.pause = False
+ else:
+ self.line = self.line + quantity
+
+ def identify(self):
+ return self.name, self.line
+
+ def __str__(self):
+ if self.units == self.DEFAULT_UNIT:
+ return "%s:%s" % (self.name, self.line)
+ else:
+ return "%s:%s[%s]" % (self.name, self.line, self.units)
+
+
+class Hook:
+
+ """The base class for implementing hooks."""
+
+ def __init__(self):
+ self.interpreter = None
+
+ def register(self, interpreter):
+ self.interpreter = interpreter
+
+ def deregister(self, interpreter):
+ if interpreter is not self.interpreter:
+ raise Error, "hook not associated with this interpreter"
+ self.interpreter = None
+
+ def push(self):
+ self.interpreter.push()
+
+ def pop(self):
+ self.interpreter.pop()
+
+ def null(self): pass
+
+ def atStartup(self): pass
+ def atReady(self): pass
+ def atFinalize(self): pass
+ def atShutdown(self): pass
+ def atParse(self, scanner, locals): pass
+ def atToken(self, token): pass
+ def atHandle(self, meta): pass
+ def atInteract(self): pass
+
+ def beforeInclude(self, name, file, locals): pass
+ def afterInclude(self): pass
+
+ def beforeExpand(self, string, locals): pass
+ def afterExpand(self, result): pass
+
+ def beforeFile(self, name, file, locals): pass
+ def afterFile(self): pass
+
+ def beforeBinary(self, name, file, chunkSize, locals): pass
+ def afterBinary(self): pass
+
+ def beforeString(self, name, string, locals): pass
+ def afterString(self): pass
+
+ def beforeQuote(self, string): pass
+ def afterQuote(self, result): pass
+
+ def beforeEscape(self, string, more): pass
+ def afterEscape(self, result): pass
+
+ def beforeControl(self, type, rest, locals): pass
+ def afterControl(self): pass
+
+ def beforeSignificate(self, key, value, locals): pass
+ def afterSignificate(self): pass
+
+ def beforeAtomic(self, name, value, locals): pass
+ def afterAtomic(self): pass
+
+ def beforeMulti(self, name, values, locals): pass
+ def afterMulti(self): pass
+
+ def beforeImport(self, name, locals): pass
+ def afterImport(self): pass
+
+ def beforeClause(self, catch, locals): pass
+ def afterClause(self, exception, variable): pass
+
+ def beforeSerialize(self, expression, locals): pass
+ def afterSerialize(self): pass
+
+ def beforeDefined(self, name, locals): pass
+ def afterDefined(self, result): pass
+
+ def beforeLiteral(self, text): pass
+ def afterLiteral(self): pass
+
+ def beforeEvaluate(self, expression, locals): pass
+ def afterEvaluate(self, result): pass
+
+ def beforeExecute(self, statements, locals): pass
+ def afterExecute(self): pass
+
+ def beforeSingle(self, source, locals): pass
+ def afterSingle(self): pass
+
+class VerboseHook(Hook):
+
+ """A verbose hook that reports all information received by the
+ hook interface. This class dynamically scans the Hook base class
+ to ensure that all hook methods are properly represented."""
+
+ EXEMPT_ATTRIBUTES = ['register', 'deregister', 'push', 'pop']
+
+ def __init__(self, output=sys.stderr):
+ Hook.__init__(self)
+ self.output = output
+ self.indent = 0
+
+ class FakeMethod:
+ """This is a proxy method-like object."""
+ def __init__(self, hook, name):
+ self.hook = hook
+ self.name = name
+
+ def __call__(self, **keywords):
+ self.hook.output.write("%s%s: %s\n" % \
+ (' ' * self.hook.indent, \
+ self.name, repr(keywords)))
+
+ for attribute in dir(Hook):
+ if attribute[:1] != '_' and \
+ attribute not in self.EXEMPT_ATTRIBUTES:
+ self.__dict__[attribute] = FakeMethod(self, attribute)
+
+
+class Token:
+
+ """An element of expansion."""
+
+ def run(self, interpreter, locals):
+ raise NotImplementedError
+
+ def string(self):
+ raise NotImplementedError
+
+ def __str__(self): return self.string()
+
+class NullToken(Token):
+ """A chunk of data not containing markups."""
+ def __init__(self, data):
+ self.data = data
+
+ def run(self, interpreter, locals):
+ interpreter.write(self.data)
+
+ def string(self):
+ return self.data
+
+class ExpansionToken(Token):
+ """A token that involves an expansion."""
+ def __init__(self, prefix, first):
+ self.prefix = prefix
+ self.first = first
+
+ def scan(self, scanner):
+ pass
+
+ def run(self, interpreter, locals):
+ pass
+
+class WhitespaceToken(ExpansionToken):
+ """A whitespace markup."""
+ def string(self):
+ return '%s%s' % (self.prefix, self.first)
+
+class LiteralToken(ExpansionToken):
+ """A literal markup."""
+ def run(self, interpreter, locals):
+ interpreter.write(self.first)
+
+ def string(self):
+ return '%s%s' % (self.prefix, self.first)
+
+class PrefixToken(ExpansionToken):
+ """A prefix markup."""
+ def run(self, interpreter, locals):
+ interpreter.write(interpreter.prefix)
+
+ def string(self):
+ return self.prefix * 2
+
+class CommentToken(ExpansionToken):
+ """A comment markup."""
+ def scan(self, scanner):
+ loc = scanner.find('\n')
+ if loc >= 0:
+ self.comment = scanner.chop(loc, 1)
+ else:
+ raise TransientParseError, "comment expects newline"
+
+ def string(self):
+ return '%s#%s\n' % (self.prefix, self.comment)
+
+class ContextNameToken(ExpansionToken):
+ """A context name change markup."""
+ def scan(self, scanner):
+ loc = scanner.find('\n')
+ if loc >= 0:
+ self.name = string.strip(scanner.chop(loc, 1))
+ else:
+ raise TransientParseError, "context name expects newline"
+
+ def run(self, interpreter, locals):
+ context = interpreter.context()
+ context.name = self.name
+
+class ContextLineToken(ExpansionToken):
+ """A context line change markup."""
+ def scan(self, scanner):
+ loc = scanner.find('\n')
+ if loc >= 0:
+ try:
+ self.line = int(scanner.chop(loc, 1))
+ except ValueError:
+ raise ParseError, "context line requires integer"
+ else:
+ raise TransientParseError, "context line expects newline"
+
+ def run(self, interpreter, locals):
+ context = interpreter.context()
+ context.line = self.line
+ context.pause = True
+
+class EscapeToken(ExpansionToken):
+ """An escape markup."""
+ def scan(self, scanner):
+ try:
+ code = scanner.chop(1)
+ result = None
+ if code in '()[]{}\'\"\\': # literals
+ result = code
+ elif code == '0': # NUL
+ result = '\x00'
+ elif code == 'a': # BEL
+ result = '\x07'
+ elif code == 'b': # BS
+ result = '\x08'
+ elif code == 'd': # decimal code
+ decimalCode = scanner.chop(3)
+ result = chr(string.atoi(decimalCode, 10))
+ elif code == 'e': # ESC
+ result = '\x1b'
+ elif code == 'f': # FF
+ result = '\x0c'
+ elif code == 'h': # DEL
+ result = '\x7f'
+ elif code == 'n': # LF (newline)
+ result = '\x0a'
+ elif code == 'N': # Unicode character name
+ theSubsystem.assertUnicode()
+ import unicodedata
+ if scanner.chop(1) != '{':
+ raise ParseError, r"Unicode name escape should be \N{...}"
+ i = scanner.find('}')
+ name = scanner.chop(i, 1)
+ try:
+ result = unicodedata.lookup(name)
+ except KeyError:
+ raise SubsystemError, \
+ "unknown Unicode character name: %s" % name
+ elif code == 'o': # octal code
+ octalCode = scanner.chop(3)
+ result = chr(string.atoi(octalCode, 8))
+ elif code == 'q': # quaternary code
+ quaternaryCode = scanner.chop(4)
+ result = chr(string.atoi(quaternaryCode, 4))
+ elif code == 'r': # CR
+ result = '\x0d'
+ elif code in 's ': # SP
+ result = ' '
+ elif code == 't': # HT
+ result = '\x09'
+ elif code in 'u': # Unicode 16-bit hex literal
+ theSubsystem.assertUnicode()
+ hexCode = scanner.chop(4)
+ result = unichr(string.atoi(hexCode, 16))
+ elif code in 'U': # Unicode 32-bit hex literal
+ theSubsystem.assertUnicode()
+ hexCode = scanner.chop(8)
+ result = unichr(string.atoi(hexCode, 16))
+ elif code == 'v': # VT
+ result = '\x0b'
+ elif code == 'x': # hexadecimal code
+ hexCode = scanner.chop(2)
+ result = chr(string.atoi(hexCode, 16))
+ elif code == 'z': # EOT
+ result = '\x04'
+ elif code == '^': # control character
+ controlCode = string.upper(scanner.chop(1))
+ if controlCode >= '@' and controlCode <= '`':
+ result = chr(ord(controlCode) - ord('@'))
+ elif controlCode == '?':
+ result = '\x7f'
+ else:
+ raise ParseError, "invalid escape control code"
+ else:
+ raise ParseError, "unrecognized escape code"
+ assert result is not None
+ self.code = result
+ except ValueError:
+ raise ParseError, "invalid numeric escape code"
+
+ def run(self, interpreter, locals):
+ interpreter.write(self.code)
+
+ def string(self):
+ return '%s\\x%02x' % (self.prefix, ord(self.code))
+
+class SignificatorToken(ExpansionToken):
+ """A significator markup."""
+ def scan(self, scanner):
+ loc = scanner.find('\n')
+ if loc >= 0:
+ line = scanner.chop(loc, 1)
+ if not line:
+ raise ParseError, "significator must have nonblank key"
+ if line[0] in ' \t\v\n':
+ raise ParseError, "no whitespace between % and key"
+ # Work around a subtle CPython-Jython difference by stripping
+ # the string before splitting it: 'a '.split(None, 1) has two
+ # elements in Jython 2.1).
+ fields = string.split(string.strip(line), None, 1)
+ if len(fields) == 2 and fields[1] == '':
+ fields.pop()
+ self.key = fields[0]
+ if len(fields) < 2:
+ fields.append(None)
+ self.key, self.valueCode = fields
+ else:
+ raise TransientParseError, "significator expects newline"
+
+ def run(self, interpreter, locals):
+ value = self.valueCode
+ if value is not None:
+ value = interpreter.evaluate(string.strip(value), locals)
+ interpreter.significate(self.key, value)
+
+ def string(self):
+ if self.valueCode is None:
+ return '%s%%%s\n' % (self.prefix, self.key)
+ else:
+ return '%s%%%s %s\n' % (self.prefix, self.key, self.valueCode)
+
+class ExpressionToken(ExpansionToken):
+ """An expression markup."""
+ def scan(self, scanner):
+ z = scanner.complex('(', ')', 0)
+ try:
+ q = scanner.next('$', 0, z, True)
+ except ParseError:
+ q = z
+ try:
+ i = scanner.next('?', 0, q, True)
+ try:
+ j = scanner.next('!', i, q, True)
+ except ParseError:
+ try:
+ j = scanner.next(':', i, q, True) # DEPRECATED
+ except ParseError:
+ j = q
+ except ParseError:
+ i = j = q
+ code = scanner.chop(z, 1)
+ self.testCode = code[:i]
+ self.thenCode = code[i + 1:j]
+ self.elseCode = code[j + 1:q]
+ self.exceptCode = code[q + 1:z]
+
+ def run(self, interpreter, locals):
+ try:
+ result = interpreter.evaluate(self.testCode, locals)
+ if self.thenCode:
+ if result:
+ result = interpreter.evaluate(self.thenCode, locals)
+ else:
+ if self.elseCode:
+ result = interpreter.evaluate(self.elseCode, locals)
+ else:
+ result = None
+ except SyntaxError:
+ # Don't catch syntax errors; let them through.
+ raise
+ except:
+ if self.exceptCode:
+ result = interpreter.evaluate(self.exceptCode, locals)
+ else:
+ raise
+ if result is not None:
+ interpreter.write(str(result))
+
+ def string(self):
+ result = self.testCode
+ if self.thenCode:
+ result = result + '?' + self.thenCode
+ if self.elseCode:
+ result = result + '!' + self.elseCode
+ if self.exceptCode:
+ result = result + '$' + self.exceptCode
+ return '%s(%s)' % (self.prefix, result)
+
+class StringLiteralToken(ExpansionToken):
+ """A string token markup."""
+ def scan(self, scanner):
+ scanner.retreat()
+ assert scanner[0] == self.first
+ i = scanner.quote()
+ self.literal = scanner.chop(i)
+
+ def run(self, interpreter, locals):
+ interpreter.literal(self.literal)
+
+ def string(self):
+ return '%s%s' % (self.prefix, self.literal)
+
+class SimpleExpressionToken(ExpansionToken):
+ """A simple expression markup."""
+ def scan(self, scanner):
+ i = scanner.simple()
+ self.code = self.first + scanner.chop(i)
+
+ def run(self, interpreter, locals):
+ interpreter.serialize(self.code, locals)
+
+ def string(self):
+ return '%s%s' % (self.prefix, self.code)
+
+class ReprToken(ExpansionToken):
+ """A repr markup."""
+ def scan(self, scanner):
+ i = scanner.next('`', 0)
+ self.code = scanner.chop(i, 1)
+
+ def run(self, interpreter, locals):
+ interpreter.write(repr(interpreter.evaluate(self.code, locals)))
+
+ def string(self):
+ return '%s`%s`' % (self.prefix, self.code)
+
+class InPlaceToken(ExpansionToken):
+ """An in-place markup."""
+ def scan(self, scanner):
+ i = scanner.next(':', 0)
+ j = scanner.next(':', i + 1)
+ self.code = scanner.chop(i, j - i + 1)
+
+ def run(self, interpreter, locals):
+ interpreter.write("%s:%s:" % (interpreter.prefix, self.code))
+ try:
+ interpreter.serialize(self.code, locals)
+ finally:
+ interpreter.write(":")
+
+ def string(self):
+ return '%s:%s::' % (self.prefix, self.code)
+
+class StatementToken(ExpansionToken):
+ """A statement markup."""
+ def scan(self, scanner):
+ i = scanner.complex('{', '}', 0)
+ self.code = scanner.chop(i, 1)
+
+ def run(self, interpreter, locals):
+ interpreter.execute(self.code, locals)
+
+ def string(self):
+ return '%s{%s}' % (self.prefix, self.code)
+
+class CustomToken(ExpansionToken):
+ """A custom markup."""
+ def scan(self, scanner):
+ i = scanner.complex('<', '>', 0)
+ self.contents = scanner.chop(i, 1)
+
+ def run(self, interpreter, locals):
+ interpreter.invokeCallback(self.contents)
+
+ def string(self):
+ return '%s<%s>' % (self.prefix, self.contents)
+
+class ControlToken(ExpansionToken):
+
+ """A control token."""
+
+ PRIMARY_TYPES = ['if', 'for', 'while', 'try', 'def']
+ SECONDARY_TYPES = ['elif', 'else', 'except', 'finally']
+ TERTIARY_TYPES = ['continue', 'break']
+ GREEDY_TYPES = ['if', 'elif', 'for', 'while', 'def', 'end']
+ END_TYPES = ['end']
+
+ IN_RE = re.compile(r"\bin\b")
+
+ def scan(self, scanner):
+ scanner.acquire()
+ i = scanner.complex('[', ']', 0)
+ self.contents = scanner.chop(i, 1)
+ fields = string.split(string.strip(self.contents), ' ', 1)
+ if len(fields) > 1:
+ self.type, self.rest = fields
+ else:
+ self.type = fields[0]
+ self.rest = None
+ self.subtokens = []
+ if self.type in self.GREEDY_TYPES and self.rest is None:
+ raise ParseError, "control '%s' needs arguments" % self.type
+ if self.type in self.PRIMARY_TYPES:
+ self.subscan(scanner, self.type)
+ self.kind = 'primary'
+ elif self.type in self.SECONDARY_TYPES:
+ self.kind = 'secondary'
+ elif self.type in self.TERTIARY_TYPES:
+ self.kind = 'tertiary'
+ elif self.type in self.END_TYPES:
+ self.kind = 'end'
+ else:
+ raise ParseError, "unknown control markup: '%s'" % self.type
+ scanner.release()
+
+ def subscan(self, scanner, primary):
+ """Do a subscan for contained tokens."""
+ while True:
+ token = scanner.one()
+ if token is None:
+ raise TransientParseError, \
+ "control '%s' needs more tokens" % primary
+ if isinstance(token, ControlToken) and \
+ token.type in self.END_TYPES:
+ if token.rest != primary:
+ raise ParseError, \
+ "control must end with 'end %s'" % primary
+ break
+ self.subtokens.append(token)
+
+ def build(self, allowed=None):
+ """Process the list of subtokens and divide it into a list of
+ 2-tuples, consisting of the dividing tokens and the list of
+ subtokens that follow them. If allowed is specified, it will
+ represent the list of the only secondary markup types which
+ are allowed."""
+ if allowed is None:
+ allowed = SECONDARY_TYPES
+ result = []
+ latest = []
+ result.append((self, latest))
+ for subtoken in self.subtokens:
+ if isinstance(subtoken, ControlToken) and \
+ subtoken.kind == 'secondary':
+ if subtoken.type not in allowed:
+ raise ParseError, \
+ "control unexpected secondary: '%s'" % subtoken.type
+ latest = []
+ result.append((subtoken, latest))
+ else:
+ latest.append(subtoken)
+ return result
+
+ def run(self, interpreter, locals):
+ interpreter.invoke('beforeControl', type=self.type, rest=self.rest, \
+ locals=locals)
+ if self.type == 'if':
+ info = self.build(['elif', 'else'])
+ elseTokens = None
+ if info[-1][0].type == 'else':
+ elseTokens = info.pop()[1]
+ for secondary, subtokens in info:
+ if secondary.type not in ('if', 'elif'):
+ raise ParseError, \
+ "control 'if' unexpected secondary: '%s'" % secondary.type
+ if interpreter.evaluate(secondary.rest, locals):
+ self.subrun(subtokens, interpreter, locals)
+ break
+ else:
+ if elseTokens:
+ self.subrun(elseTokens, interpreter, locals)
+ elif self.type == 'for':
+ sides = self.IN_RE.split(self.rest, 1)
+ if len(sides) != 2:
+ raise ParseError, "control expected 'for x in seq'"
+ iterator, sequenceCode = sides
+ info = self.build(['else'])
+ elseTokens = None
+ if info[-1][0].type == 'else':
+ elseTokens = info.pop()[1]
+ if len(info) != 1:
+ raise ParseError, "control 'for' expects at most one 'else'"
+ sequence = interpreter.evaluate(sequenceCode, locals)
+ for element in sequence:
+ try:
+ interpreter.assign(iterator, element, locals)
+ self.subrun(info[0][1], interpreter, locals)
+ except ContinueFlow:
+ continue
+ except BreakFlow:
+ break
+ else:
+ if elseTokens:
+ self.subrun(elseTokens, interpreter, locals)
+ elif self.type == 'while':
+ testCode = self.rest
+ info = self.build(['else'])
+ elseTokens = None
+ if info[-1][0].type == 'else':
+ elseTokens = info.pop()[1]
+ if len(info) != 1:
+ raise ParseError, "control 'while' expects at most one 'else'"
+ atLeastOnce = False
+ while True:
+ try:
+ if not interpreter.evaluate(testCode, locals):
+ break
+ atLeastOnce = True
+ self.subrun(info[0][1], interpreter, locals)
+ except ContinueFlow:
+ continue
+ except BreakFlow:
+ break
+ if not atLeastOnce and elseTokens:
+ self.subrun(elseTokens, interpreter, locals)
+ elif self.type == 'try':
+ info = self.build(['except', 'finally'])
+ if len(info) == 1:
+ raise ParseError, "control 'try' needs 'except' or 'finally'"
+ type = info[-1][0].type
+ if type == 'except':
+ for secondary, _tokens in info[1:]:
+ if secondary.type != 'except':
+ raise ParseError, \
+ "control 'try' cannot have 'except' and 'finally'"
+ else:
+ assert type == 'finally'
+ if len(info) != 2:
+ raise ParseError, \
+ "control 'try' can only have one 'finally'"
+ if type == 'except':
+ try:
+ self.subrun(info[0][1], interpreter, locals)
+ except FlowError:
+ raise
+ except Exception, e:
+ for secondary, tokens in info[1:]:
+ exception, variable = interpreter.clause(secondary.rest)
+ if variable is not None:
+ interpreter.assign(variable, e)
+ if isinstance(e, exception):
+ self.subrun(tokens, interpreter, locals)
+ break
+ else:
+ raise
+ else:
+ try:
+ self.subrun(info[0][1], interpreter, locals)
+ finally:
+ self.subrun(info[1][1], interpreter, locals)
+ elif self.type == 'continue':
+ raise ContinueFlow, "control 'continue' without 'for', 'while'"
+ elif self.type == 'break':
+ raise BreakFlow, "control 'break' without 'for', 'while'"
+ elif self.type == 'def':
+ signature = self.rest
+ definition = self.substring()
+ code = 'def %s:\n' \
+ ' r"""%s"""\n' \
+ ' return %s.expand(r"""%s""", locals())\n' % \
+ (signature, definition, interpreter.pseudo, definition)
+ interpreter.execute(code, locals)
+ elif self.type == 'end':
+ raise ParseError, "control 'end' requires primary markup"
+ else:
+ raise ParseError, \
+ "control '%s' cannot be at this level" % self.type
+ interpreter.invoke('afterControl')
+
+ def subrun(self, tokens, interpreter, locals):
+ """Execute a sequence of tokens."""
+ for token in tokens:
+ token.run(interpreter, locals)
+
+ def substring(self):
+ return string.join(map(str, self.subtokens), '')
+
+ def string(self):
+ if self.kind == 'primary':
+ return '%s[%s]%s%s[end %s]' % \
+ (self.prefix, self.contents, self.substring(), \
+ self.prefix, self.type)
+ else:
+ return '%s[%s]' % (self.prefix, self.contents)
+
+
+class Scanner:
+
+ """A scanner holds a buffer for lookahead parsing and has the
+ ability to scan for special symbols and indicators in that
+ buffer."""
+
+ # This is the token mapping table that maps first characters to
+ # token classes.
+ TOKEN_MAP = [
+ (None, PrefixToken),
+ (' \t\v\r\n', WhitespaceToken),
+ (')]}', LiteralToken),
+ ('\\', EscapeToken),
+ ('#', CommentToken),
+ ('?', ContextNameToken),
+ ('!', ContextLineToken),
+ ('%', SignificatorToken),
+ ('(', ExpressionToken),
+ (IDENTIFIER_FIRST_CHARS, SimpleExpressionToken),
+ ('\'\"', StringLiteralToken),
+ ('`', ReprToken),
+ (':', InPlaceToken),
+ ('[', ControlToken),
+ ('{', StatementToken),
+ ('<', CustomToken),
+ ]
+
+ def __init__(self, prefix, data=''):
+ self.prefix = prefix
+ self.pointer = 0
+ self.buffer = data
+ self.lock = 0
+
+ def __nonzero__(self): return self.pointer < len(self.buffer)
+ def __len__(self): return len(self.buffer) - self.pointer
+ def __getitem__(self, index): return self.buffer[self.pointer + index]
+
+ def __getslice__(self, start, stop):
+ if stop > len(self):
+ stop = len(self)
+ return self.buffer[self.pointer + start:self.pointer + stop]
+
+ def advance(self, count=1):
+ """Advance the pointer count characters."""
+ self.pointer = self.pointer + count
+
+ def retreat(self, count=1):
+ self.pointer = self.pointer - count
+ if self.pointer < 0:
+ raise ParseError, "can't retreat back over synced out chars"
+
+ def set(self, data):
+ """Start the scanner digesting a new batch of data; start the pointer
+ over from scratch."""
+ self.pointer = 0
+ self.buffer = data
+
+ def feed(self, data):
+ """Feed some more data to the scanner."""
+ self.buffer = self.buffer + data
+
+ def chop(self, count=None, slop=0):
+ """Chop the first count + slop characters off the front, and return
+ the first count. If count is not specified, then return
+ everything."""
+ if count is None:
+ assert slop == 0
+ count = len(self)
+ if count > len(self):
+ raise TransientParseError, "not enough data to read"
+ result = self[:count]
+ self.advance(count + slop)
+ return result
+
+ def acquire(self):
+ """Lock the scanner so it doesn't destroy data on sync."""
+ self.lock = self.lock + 1
+
+ def release(self):
+ """Unlock the scanner."""
+ self.lock = self.lock - 1
+
+ def sync(self):
+ """Sync up the buffer with the read head."""
+ if self.lock == 0 and self.pointer != 0:
+ self.buffer = self.buffer[self.pointer:]
+ self.pointer = 0
+
+ def unsync(self):
+ """Undo changes; reset the read head."""
+ if self.pointer != 0:
+ self.lock = 0
+ self.pointer = 0
+
+ def rest(self):
+ """Get the remainder of the buffer."""
+ return self[:]
+
+ def read(self, i=0, count=1):
+ """Read count chars starting from i; raise a transient error if
+ there aren't enough characters remaining."""
+ if len(self) < i + count:
+ raise TransientParseError, "need more data to read"
+ else:
+ return self[i:i + count]
+
+ def check(self, i, archetype=None):
+ """Scan for the next single or triple quote, with the specified
+ archetype. Return the found quote or None."""
+ quote = None
+ if self[i] in '\'\"':
+ quote = self[i]
+ if len(self) - i < 3:
+ for j in range(i, len(self)):
+ if self[i] == quote:
+ return quote
+ else:
+ raise TransientParseError, "need to scan for rest of quote"
+ if self[i + 1] == self[i + 2] == quote:
+ quote = quote * 3
+ if quote is not None:
+ if archetype is None:
+ return quote
+ else:
+ if archetype == quote:
+ return quote
+ elif len(archetype) < len(quote) and archetype[0] == quote[0]:
+ return archetype
+ else:
+ return None
+ else:
+ return None
+
+ def find(self, sub, start=0, end=None):
+ """Find the next occurrence of the character, or return -1."""
+ if end is not None:
+ return string.find(self.rest(), sub, start, end)
+ else:
+ return string.find(self.rest(), sub, start)
+
+ def last(self, char, start=0, end=None):
+ """Find the first character that is _not_ the specified character."""
+ if end is None:
+ end = len(self)
+ i = start
+ while i < end:
+ if self[i] != char:
+ return i
+ i = i + 1
+ else:
+ raise TransientParseError, "expecting other than %s" % char
+
+ def next(self, target, start=0, end=None, mandatory=False):
+ """Scan for the next occurrence of one of the characters in
+ the target string; optionally, make the scan mandatory."""
+ if mandatory:
+ assert end is not None
+ quote = None
+ if end is None:
+ end = len(self)
+ i = start
+ while i < end:
+ newQuote = self.check(i, quote)
+ if newQuote:
+ if newQuote == quote:
+ quote = None
+ else:
+ quote = newQuote
+ i = i + len(newQuote)
+ else:
+ c = self[i]
+ if quote:
+ if c == '\\':
+ i = i + 1
+ else:
+ if c in target:
+ return i
+ i = i + 1
+ else:
+ if mandatory:
+ raise ParseError, "expecting %s, not found" % target
+ else:
+ raise TransientParseError, "expecting ending character"
+
+ def quote(self, start=0, end=None, mandatory=False):
+ """Scan for the end of the next quote."""
+ assert self[start] in '\'\"'
+ quote = self.check(start)
+ if end is None:
+ end = len(self)
+ i = start + len(quote)
+ while i < end:
+ newQuote = self.check(i, quote)
+ if newQuote:
+ i = i + len(newQuote)
+ if newQuote == quote:
+ return i
+ else:
+ c = self[i]
+ if c == '\\':
+ i = i + 1
+ i = i + 1
+ else:
+ if mandatory:
+ raise ParseError, "expecting end of string literal"
+ else:
+ raise TransientParseError, "expecting end of string literal"
+
+ def nested(self, enter, exit, start=0, end=None):
+ """Scan from i for an ending sequence, respecting entries and exits
+ only."""
+ depth = 0
+ if end is None:
+ end = len(self)
+ i = start
+ while i < end:
+ c = self[i]
+ if c == enter:
+ depth = depth + 1
+ elif c == exit:
+ depth = depth - 1
+ if depth < 0:
+ return i
+ i = i + 1
+ else:
+ raise TransientParseError, "expecting end of complex expression"
+
+ def complex(self, enter, exit, start=0, end=None, skip=None):
+ """Scan from i for an ending sequence, respecting quotes,
+ entries and exits."""
+ quote = None
+ depth = 0
+ if end is None:
+ end = len(self)
+ last = None
+ i = start
+ while i < end:
+ newQuote = self.check(i, quote)
+ if newQuote:
+ if newQuote == quote:
+ quote = None
+ else:
+ quote = newQuote
+ i = i + len(newQuote)
+ else:
+ c = self[i]
+ if quote:
+ if c == '\\':
+ i = i + 1
+ else:
+ if skip is None or last != skip:
+ if c == enter:
+ depth = depth + 1
+ elif c == exit:
+ depth = depth - 1
+ if depth < 0:
+ return i
+ last = c
+ i = i + 1
+ else:
+ raise TransientParseError, "expecting end of complex expression"
+
+ def word(self, start=0):
+ """Scan from i for a simple word."""
+ length = len(self)
+ i = start
+ while i < length:
+ if not self[i] in IDENTIFIER_CHARS:
+ return i
+ i = i + 1
+ else:
+ raise TransientParseError, "expecting end of word"
+
+ def phrase(self, start=0):
+ """Scan from i for a phrase (e.g., 'word', 'f(a, b, c)', 'a[i]', or
+ combinations like 'x[i](a)'."""
+ # Find the word.
+ i = self.word(start)
+ while i < len(self) and self[i] in '([{':
+ enter = self[i]
+ if enter == '{':
+ raise ParseError, "curly braces can't open simple expressions"
+ exit = ENDING_CHARS[enter]
+ i = self.complex(enter, exit, i + 1) + 1
+ return i
+
+ def simple(self, start=0):
+ """Scan from i for a simple expression, which consists of one
+ more phrases separated by dots."""
+ i = self.phrase(start)
+ length = len(self)
+ while i < length and self[i] == '.':
+ i = self.phrase(i)
+ # Make sure we don't end with a trailing dot.
+ while i > 0 and self[i - 1] == '.':
+ i = i - 1
+ return i
+
+ def one(self):
+ """Parse and return one token, or None if the scanner is empty."""
+ if not self:
+ return None
+ if not self.prefix:
+ loc = -1
+ else:
+ loc = self.find(self.prefix)
+ if loc < 0:
+ # If there's no prefix in the buffer, then set the location to
+ # the end so the whole thing gets processed.
+ loc = len(self)
+ if loc == 0:
+ # If there's a prefix at the beginning of the buffer, process
+ # an expansion.
+ prefix = self.chop(1)
+ assert prefix == self.prefix
+ first = self.chop(1)
+ if first == self.prefix:
+ first = None
+ for firsts, factory in self.TOKEN_MAP:
+ if firsts is None:
+ if first is None:
+ break
+ elif first in firsts:
+ break
+ else:
+ raise ParseError, "unknown markup: %s%s" % (self.prefix, first)
+ token = factory(self.prefix, first)
+ try:
+ token.scan(self)
+ except TransientParseError:
+ # If a transient parse error occurs, reset the buffer pointer
+ # so we can (conceivably) try again later.
+ self.unsync()
+ raise
+ else:
+ # Process everything up to loc as a null token.
+ data = self.chop(loc)
+ token = NullToken(data)
+ self.sync()
+ return token
+
+
+class Interpreter:
+
+ """An interpreter can process chunks of EmPy code."""
+
+ # Constants.
+
+ VERSION = __version__
+ SIGNIFICATOR_RE_SUFFIX = SIGNIFICATOR_RE_SUFFIX
+ SIGNIFICATOR_RE_STRING = None
+
+ # Types.
+
+ Interpreter = None # define this below to prevent a circular reference
+ Hook = Hook # DEPRECATED
+ Filter = Filter # DEPRECATED
+ NullFilter = NullFilter # DEPRECATED
+ FunctionFilter = FunctionFilter # DEPRECATED
+ StringFilter = StringFilter # DEPRECATED
+ BufferedFilter = BufferedFilter # DEPRECATED
+ SizeBufferedFilter = SizeBufferedFilter # DEPRECATED
+ LineBufferedFilter = LineBufferedFilter # DEPRECATED
+ MaximallyBufferedFilter = MaximallyBufferedFilter # DEPRECATED
+
+ # Tables.
+
+ ESCAPE_CODES = {0x00: '0', 0x07: 'a', 0x08: 'b', 0x1b: 'e', 0x0c: 'f', \
+ 0x7f: 'h', 0x0a: 'n', 0x0d: 'r', 0x09: 't', 0x0b: 'v', \
+ 0x04: 'z'}
+
+ ASSIGN_TOKEN_RE = re.compile(r"[_a-zA-Z][_a-zA-Z0-9]*|\(|\)|,")
+
+ DEFAULT_OPTIONS = {BANGPATH_OPT: True,
+ BUFFERED_OPT: False,
+ RAW_OPT: False,
+ EXIT_OPT: True,
+ FLATTEN_OPT: False,
+ OVERRIDE_OPT: True,
+ CALLBACK_OPT: False}
+
+ _wasProxyInstalled = False # was a proxy installed?
+
+ # Construction, initialization, destruction.
+
+ def __init__(self, output=None, argv=None, prefix=DEFAULT_PREFIX, \
+ pseudo=None, options=None, globals=None, hooks=None):
+ self.interpreter = self # DEPRECATED
+ # Set up the stream.
+ if output is None:
+ output = UncloseableFile(sys.__stdout__)
+ self.output = output
+ self.prefix = prefix
+ if pseudo is None:
+ pseudo = DEFAULT_PSEUDOMODULE_NAME
+ self.pseudo = pseudo
+ if argv is None:
+ argv = [DEFAULT_SCRIPT_NAME]
+ self.argv = argv
+ self.args = argv[1:]
+ if options is None:
+ options = {}
+ self.options = options
+ # Initialize any hooks.
+ self.hooksEnabled = None # special sentinel meaning "false until added"
+ self.hooks = []
+ if hooks is None:
+ hooks = []
+ for hook in hooks:
+ self.register(hook)
+ # Initialize callback.
+ self.callback = None
+ # Finalizers.
+ self.finals = []
+ # The interpreter stacks.
+ self.contexts = Stack()
+ self.streams = Stack()
+ # Now set up the globals.
+ self.globals = globals
+ self.fix()
+ self.history = Stack()
+ # Install a proxy stdout if one hasn't been already.
+ self.installProxy()
+ # Finally, reset the state of all the stacks.
+ self.reset()
+ # Okay, now flatten the namespaces if that option has been set.
+ if self.options.get(FLATTEN_OPT, False):
+ self.flatten()
+ # Set up old pseudomodule attributes.
+ if prefix is None:
+ self.SIGNIFICATOR_RE_STRING = None
+ else:
+ self.SIGNIFICATOR_RE_STRING = prefix + self.SIGNIFICATOR_RE_SUFFIX
+ self.Interpreter = self.__class__
+ # Done. Now declare that we've started up.
+ self.invoke('atStartup')
+
+ def __del__(self):
+ self.shutdown()
+
+ def __repr__(self):
+ return '<%s pseudomodule/interpreter at 0x%x>' % \
+ (self.pseudo, id(self))
+
+ def ready(self):
+ """Declare the interpreter ready for normal operations."""
+ self.invoke('atReady')
+
+ def fix(self):
+ """Reset the globals, stamping in the pseudomodule."""
+ if self.globals is None:
+ self.globals = {}
+ # Make sure that there is no collision between two interpreters'
+ # globals.
+ if self.globals.has_key(self.pseudo):
+ if self.globals[self.pseudo] is not self:
+ raise Error, "interpreter globals collision"
+ self.globals[self.pseudo] = self
+
+ def unfix(self):
+ """Remove the pseudomodule (if present) from the globals."""
+ UNWANTED_KEYS = [self.pseudo, '__builtins__']
+ for unwantedKey in UNWANTED_KEYS:
+ if self.globals.has_key(unwantedKey):
+ del self.globals[unwantedKey]
+
+ def update(self, other):
+ """Update the current globals dictionary with another dictionary."""
+ self.globals.update(other)
+ self.fix()
+
+ def clear(self):
+ """Clear out the globals dictionary with a brand new one."""
+ self.globals = {}
+ self.fix()
+
+ def save(self, deep=True):
+ if deep:
+ copyMethod = copy.deepcopy
+ else:
+ copyMethod = copy.copy
+ """Save a copy of the current globals on the history stack."""
+ self.unfix()
+ self.history.push(copyMethod(self.globals))
+ self.fix()
+
+ def restore(self, destructive=True):
+ """Restore the topmost historic globals."""
+ if destructive:
+ fetchMethod = self.history.pop
+ else:
+ fetchMethod = self.history.top
+ self.unfix()
+ self.globals = fetchMethod()
+ self.fix()
+
+ def shutdown(self):
+ """Declare this interpreting session over; close the stream file
+ object. This method is idempotent."""
+ if self.streams is not None:
+ try:
+ self.finalize()
+ self.invoke('atShutdown')
+ while self.streams:
+ stream = self.streams.pop()
+ stream.close()
+ finally:
+ self.streams = None
+
+ def ok(self):
+ """Is the interpreter still active?"""
+ return self.streams is not None
+
+ # Writeable file-like methods.
+
+ def write(self, data):
+ self.stream().write(data)
+
+ def writelines(self, stuff):
+ self.stream().writelines(stuff)
+
+ def flush(self):
+ self.stream().flush()
+
+ def close(self):
+ self.shutdown()
+
+ # Stack-related activity.
+
+ def context(self):
+ return self.contexts.top()
+
+ def stream(self):
+ return self.streams.top()
+
+ def reset(self):
+ self.contexts.purge()
+ self.streams.purge()
+ self.streams.push(Stream(self.output))
+ if self.options.get(OVERRIDE_OPT, True):
+ sys.stdout.clear(self)
+
+ def push(self):
+ if self.options.get(OVERRIDE_OPT, True):
+ sys.stdout.push(self)
+
+ def pop(self):
+ if self.options.get(OVERRIDE_OPT, True):
+ sys.stdout.pop(self)
+
+ # Higher-level operations.
+
+ def include(self, fileOrFilename, locals=None):
+ """Do an include pass on a file or filename."""
+ if type(fileOrFilename) is types.StringType:
+ # Either it's a string representing a filename ...
+ filename = fileOrFilename
+ name = filename
+ file = theSubsystem.open(filename, 'r')
+ else:
+ # ... or a file object.
+ file = fileOrFilename
+ name = "<%s>" % str(file.__class__)
+ self.invoke('beforeInclude', name=name, file=file, locals=locals)
+ self.file(file, name, locals)
+ self.invoke('afterInclude')
+
+ def expand(self, data, locals=None):
+ """Do an explicit expansion on a subordinate stream."""
+ outFile = StringIO.StringIO()
+ stream = Stream(outFile)
+ self.invoke('beforeExpand', string=data, locals=locals)
+ self.streams.push(stream)
+ try:
+ self.string(data, '<expand>', locals)
+ stream.flush()
+ expansion = outFile.getvalue()
+ self.invoke('afterExpand', result=expansion)
+ return expansion
+ finally:
+ self.streams.pop()
+
+ def quote(self, data):
+ """Quote the given string so that if it were expanded it would
+ evaluate to the original."""
+ self.invoke('beforeQuote', string=data)
+ scanner = Scanner(self.prefix, data)
+ result = []
+ i = 0
+ try:
+ j = scanner.next(self.prefix, i)
+ result.append(data[i:j])
+ result.append(self.prefix * 2)
+ i = j + 1
+ except TransientParseError:
+ pass
+ result.append(data[i:])
+ result = string.join(result, '')
+ self.invoke('afterQuote', result=result)
+ return result
+
+ def escape(self, data, more=''):
+ """Escape a string so that nonprintable characters are replaced
+ with compatible EmPy expansions."""
+ self.invoke('beforeEscape', string=data, more=more)
+ result = []
+ for char in data:
+ if char < ' ' or char > '~':
+ charOrd = ord(char)
+ if Interpreter.ESCAPE_CODES.has_key(charOrd):
+ result.append(self.prefix + '\\' + \
+ Interpreter.ESCAPE_CODES[charOrd])
+ else:
+ result.append(self.prefix + '\\x%02x' % charOrd)
+ elif char in more:
+ result.append(self.prefix + '\\' + char)
+ else:
+ result.append(char)
+ result = string.join(result, '')
+ self.invoke('afterEscape', result=result)
+ return result
+
+ # Processing.
+
+ def wrap(self, callable, args):
+ """Wrap around an application of a callable and handle errors.
+ Return whether no error occurred."""
+ try:
+ apply(callable, args)
+ self.reset()
+ return True
+ except KeyboardInterrupt, e:
+ # Handle keyboard interrupts specially: we should always exit
+ # from these.
+ self.fail(e, True)
+ except Exception, e:
+ # A standard exception (other than a keyboard interrupt).
+ self.fail(e)
+ except:
+ # If we get here, then either it's an exception not derived from
+ # Exception or it's a string exception, so get the error type
+ # from the sys module.
+ e = sys.exc_type
+ self.fail(e)
+ # An error occurred if we leak through to here, so do cleanup.
+ self.reset()
+ return False
+
+ def interact(self):
+ """Perform interaction."""
+ self.invoke('atInteract')
+ done = False
+ while not done:
+ result = self.wrap(self.file, (sys.stdin, '<interact>'))
+ if self.options.get(EXIT_OPT, True):
+ done = True
+ else:
+ if result:
+ done = True
+ else:
+ self.reset()
+
+ def fail(self, error, fatal=False):
+ """Handle an actual error that occurred."""
+ if self.options.get(BUFFERED_OPT, False):
+ try:
+ self.output.abort()
+ except AttributeError:
+ # If the output file object doesn't have an abort method,
+ # something got mismatched, but it's too late to do
+ # anything about it now anyway, so just ignore it.
+ pass
+ meta = self.meta(error)
+ self.handle(meta)
+ if self.options.get(RAW_OPT, False):
+ raise
+ if fatal or self.options.get(EXIT_OPT, True):
+ sys.exit(FAILURE_CODE)
+
+ def file(self, file, name='<file>', locals=None):
+ """Parse the entire contents of a file-like object, line by line."""
+ context = Context(name)
+ self.contexts.push(context)
+ self.invoke('beforeFile', name=name, file=file, locals=locals)
+ scanner = Scanner(self.prefix)
+ first = True
+ done = False
+ while not done:
+ self.context().bump()
+ line = file.readline()
+ if first:
+ if self.options.get(BANGPATH_OPT, True) and self.prefix:
+ # Replace a bangpath at the beginning of the first line
+ # with an EmPy comment.
+ if string.find(line, BANGPATH) == 0:
+ line = self.prefix + '#' + line[2:]
+ first = False
+ if line:
+ scanner.feed(line)
+ else:
+ done = True
+ self.safe(scanner, done, locals)
+ self.invoke('afterFile')
+ self.contexts.pop()
+
+ def binary(self, file, name='<binary>', chunkSize=0, locals=None):
+ """Parse the entire contents of a file-like object, in chunks."""
+ if chunkSize <= 0:
+ chunkSize = DEFAULT_CHUNK_SIZE
+ context = Context(name, units='bytes')
+ self.contexts.push(context)
+ self.invoke('beforeBinary', name=name, file=file, \
+ chunkSize=chunkSize, locals=locals)
+ scanner = Scanner(self.prefix)
+ done = False
+ while not done:
+ chunk = file.read(chunkSize)
+ if chunk:
+ scanner.feed(chunk)
+ else:
+ done = True
+ self.safe(scanner, done, locals)
+ self.context().bump(len(chunk))
+ self.invoke('afterBinary')
+ self.contexts.pop()
+
+ def string(self, data, name='<string>', locals=None):
+ """Parse a string."""
+ context = Context(name)
+ self.contexts.push(context)
+ self.invoke('beforeString', name=name, string=data, locals=locals)
+ context.bump()
+ scanner = Scanner(self.prefix, data)
+ self.safe(scanner, True, locals)
+ self.invoke('afterString')
+ self.contexts.pop()
+
+ def safe(self, scanner, final=False, locals=None):
+ """Do a protected parse. Catch transient parse errors; if
+ final is true, then make a final pass with a terminator,
+ otherwise ignore the transient parse error (more data is
+ pending)."""
+ try:
+ self.parse(scanner, locals)
+ except TransientParseError:
+ if final:
+ # If the buffer doesn't end with a newline, try tacking on
+ # a dummy terminator.
+ buffer = scanner.rest()
+ if buffer and buffer[-1] != '\n':
+ scanner.feed(self.prefix + '\n')
+ # A TransientParseError thrown from here is a real parse
+ # error.
+ self.parse(scanner, locals)
+
+ def parse(self, scanner, locals=None):
+ """Parse and run as much from this scanner as possible."""
+ self.invoke('atParse', scanner=scanner, locals=locals)
+ while True:
+ token = scanner.one()
+ if token is None:
+ break
+ self.invoke('atToken', token=token)
+ token.run(self, locals)
+
+ # Medium-level evaluation and execution.
+
+ def tokenize(self, name):
+ """Take an lvalue string and return a name or a (possibly recursive)
+ list of names."""
+ result = []
+ stack = [result]
+ for garbage in self.ASSIGN_TOKEN_RE.split(name):
+ garbage = string.strip(garbage)
+ if garbage:
+ raise ParseError, "unexpected assignment token: '%s'" % garbage
+ tokens = self.ASSIGN_TOKEN_RE.findall(name)
+ # While processing, put a None token at the start of any list in which
+ # commas actually appear.
+ for token in tokens:
+ if token == '(':
+ stack.append([])
+ elif token == ')':
+ top = stack.pop()
+ if len(top) == 1:
+ top = top[0] # no None token means that it's not a 1-tuple
+ elif top[0] is None:
+ del top[0] # remove the None token for real tuples
+ stack[-1].append(top)
+ elif token == ',':
+ if len(stack[-1]) == 1:
+ stack[-1].insert(0, None)
+ else:
+ stack[-1].append(token)
+ # If it's a 1-tuple at the top level, turn it into a real subsequence.
+ if result and result[0] is None:
+ result = [result[1:]]
+ if len(result) == 1:
+ return result[0]
+ else:
+ return result
+
+ def significate(self, key, value=None, locals=None):
+ """Declare a significator."""
+ self.invoke('beforeSignificate', key=key, value=value, locals=locals)
+ name = '__%s__' % key
+ self.atomic(name, value, locals)
+ self.invoke('afterSignificate')
+
+ def atomic(self, name, value, locals=None):
+ """Do an atomic assignment."""
+ self.invoke('beforeAtomic', name=name, value=value, locals=locals)
+ if locals is None:
+ self.globals[name] = value
+ else:
+ locals[name] = value
+ self.invoke('afterAtomic')
+
+ def multi(self, names, values, locals=None):
+ """Do a (potentially recursive) assignment."""
+ self.invoke('beforeMulti', names=names, values=values, locals=locals)
+ # No zip in 1.5, so we have to do it manually.
+ i = 0
+ try:
+ values = tuple(values)
+ except TypeError:
+ raise TypeError, "unpack non-sequence"
+ if len(names) != len(values):
+ raise ValueError, "unpack tuple of wrong size"
+ for i in range(len(names)):
+ name = names[i]
+ if type(name) is types.StringType:
+ self.atomic(name, values[i], locals)
+ else:
+ self.multi(name, values[i], locals)
+ self.invoke('afterMulti')
+
+ def assign(self, name, value, locals=None):
+ """Do a potentially complex (including tuple unpacking) assignment."""
+ left = self.tokenize(name)
+ # The return value of tokenize can either be a string or a list of
+ # (lists of) strings.
+ if type(left) is types.StringType:
+ self.atomic(left, value, locals)
+ else:
+ self.multi(left, value, locals)
+
+ def import_(self, name, locals=None):
+ """Do an import."""
+ self.invoke('beforeImport', name=name, locals=locals)
+ self.execute('import %s' % name, locals)
+ self.invoke('afterImport')
+
+ def clause(self, catch, locals=None):
+ """Given the string representation of an except clause, turn it into
+ a 2-tuple consisting of the class name, and either a variable name
+ or None."""
+ self.invoke('beforeClause', catch=catch, locals=locals)
+ if catch is None:
+ exceptionCode, variable = None, None
+ elif string.find(catch, ',') >= 0:
+ exceptionCode, variable = string.split(string.strip(catch), ',', 1)
+ variable = string.strip(variable)
+ else:
+ exceptionCode, variable = string.strip(catch), None
+ if not exceptionCode:
+ exception = Exception
+ else:
+ exception = self.evaluate(exceptionCode, locals)
+ self.invoke('afterClause', exception=exception, variable=variable)
+ return exception, variable
+
+ def serialize(self, expression, locals=None):
+ """Do an expansion, involving evaluating an expression, then
+ converting it to a string and writing that string to the
+ output if the evaluation is not None."""
+ self.invoke('beforeSerialize', expression=expression, locals=locals)
+ result = self.evaluate(expression, locals)
+ if result is not None:
+ self.write(str(result))
+ self.invoke('afterSerialize')
+
+ def defined(self, name, locals=None):
+ """Return a Boolean indicating whether or not the name is
+ defined either in the locals or the globals."""
+ self.invoke('beforeDefined', name=name, local=local)
+ if locals is not None:
+ if locals.has_key(name):
+ result = True
+ else:
+ result = False
+ elif self.globals.has_key(name):
+ result = True
+ else:
+ result = False
+ self.invoke('afterDefined', result=result)
+
+ def literal(self, text):
+ """Process a string literal."""
+ self.invoke('beforeLiteral', text=text)
+ self.serialize(text)
+ self.invoke('afterLiteral')
+
+ # Low-level evaluation and execution.
+
+ def evaluate(self, expression, locals=None):
+ """Evaluate an expression."""
+ if expression in ('1', 'True'): return True
+ if expression in ('0', 'False'): return False
+ self.push()
+ try:
+ self.invoke('beforeEvaluate', \
+ expression=expression, locals=locals)
+ if locals is not None:
+ result = eval(expression, self.globals, locals)
+ else:
+ result = eval(expression, self.globals)
+ self.invoke('afterEvaluate', result=result)
+ return result
+ finally:
+ self.pop()
+
+ def execute(self, statements, locals=None):
+ """Execute a statement."""
+ # If there are any carriage returns (as opposed to linefeeds/newlines)
+ # in the statements code, then remove them. Even on DOS/Windows
+ # platforms,
+ if string.find(statements, '\r') >= 0:
+ statements = string.replace(statements, '\r', '')
+ # If there are no newlines in the statements code, then strip any
+ # leading or trailing whitespace.
+ if string.find(statements, '\n') < 0:
+ statements = string.strip(statements)
+ self.push()
+ try:
+ self.invoke('beforeExecute', \
+ statements=statements, locals=locals)
+ if locals is not None:
+ exec statements in self.globals, locals
+ else:
+ exec statements in self.globals
+ self.invoke('afterExecute')
+ finally:
+ self.pop()
+
+ def single(self, source, locals=None):
+ """Execute an expression or statement, just as if it were
+ entered into the Python interactive interpreter."""
+ self.push()
+ try:
+ self.invoke('beforeSingle', \
+ source=source, locals=locals)
+ code = compile(source, '<single>', 'single')
+ if locals is not None:
+ exec code in self.globals, locals
+ else:
+ exec code in self.globals
+ self.invoke('afterSingle')
+ finally:
+ self.pop()
+
+ # Hooks.
+
+ def register(self, hook, prepend=False):
+ """Register the provided hook."""
+ hook.register(self)
+ if self.hooksEnabled is None:
+ # A special optimization so that hooks can be effectively
+ # disabled until one is added or they are explicitly turned on.
+ self.hooksEnabled = True
+ if prepend:
+ self.hooks.insert(0, hook)
+ else:
+ self.hooks.append(hook)
+
+ def deregister(self, hook):
+ """Remove an already registered hook."""
+ hook.deregister(self)
+ self.hooks.remove(hook)
+
+ def invoke(self, _name, **keywords):
+ """Invoke the hook(s) associated with the hook name, should they
+ exist."""
+ if self.hooksEnabled:
+ for hook in self.hooks:
+ hook.push()
+ try:
+ method = getattr(hook, _name)
+ apply(method, (), keywords)
+ finally:
+ hook.pop()
+
+ def finalize(self):
+ """Execute any remaining final routines."""
+ self.push()
+ self.invoke('atFinalize')
+ try:
+ # Pop them off one at a time so they get executed in reverse
+ # order and we remove them as they're executed in case something
+ # bad happens.
+ while self.finals:
+ final = self.finals.pop()
+ final()
+ finally:
+ self.pop()
+
+ # Error handling.
+
+ def meta(self, exc=None):
+ """Construct a MetaError for the interpreter's current state."""
+ return MetaError(self.contexts.clone(), exc)
+
+ def handle(self, meta):
+ """Handle a MetaError."""
+ first = True
+ self.invoke('atHandle', meta=meta)
+ for context in meta.contexts:
+ if first:
+ if meta.exc is not None:
+ desc = "error: %s: %s" % (meta.exc.__class__, meta.exc)
+ else:
+ desc = "error"
+ else:
+ desc = "from this context"
+ first = False
+ sys.stderr.write('%s: %s\n' % (context, desc))
+
+ def installProxy(self):
+ """Install a proxy if necessary."""
+ # Unfortunately, there's no surefire way to make sure that installing
+ # a sys.stdout proxy is idempotent, what with different interpreters
+ # running from different modules. The best we can do here is to try
+ # manipulating the proxy's test function ...
+ try:
+ sys.stdout._testProxy()
+ except AttributeError:
+ # ... if the current stdout object doesn't have one, then check
+ # to see if we think _this_ particularly Interpreter class has
+ # installed it before ...
+ if Interpreter._wasProxyInstalled:
+ # ... and if so, we have a proxy problem.
+ raise Error, "interpreter stdout proxy lost"
+ else:
+ # Otherwise, install the proxy and set the flag.
+ sys.stdout = ProxyFile(sys.stdout)
+ Interpreter._wasProxyInstalled = True
+
+ #
+ # Pseudomodule routines.
+ #
+
+ # Identification.
+
+ def identify(self):
+ """Identify the topmost context with a 2-tuple of the name and
+ line number."""
+ return self.context().identify()
+
+ def atExit(self, callable):
+ """Register a function to be called at exit."""
+ self.finals.append(callable)
+
+ # Context manipulation.
+
+ def pushContext(self, name='<unnamed>', line=0):
+ """Create a new context and push it."""
+ self.contexts.push(Context(name, line))
+
+ def popContext(self):
+ """Pop the top context."""
+ self.contexts.pop()
+
+ def setContextName(self, name):
+ """Set the name of the topmost context."""
+ context = self.context()
+ context.name = name
+
+ def setContextLine(self, line):
+ """Set the name of the topmost context."""
+ context = self.context()
+ context.line = line
+
+ setName = setContextName # DEPRECATED
+ setLine = setContextLine # DEPRECATED
+
+ # Globals manipulation.
+
+ def getGlobals(self):
+ """Retrieve the globals."""
+ return self.globals
+
+ def setGlobals(self, globals):
+ """Set the globals to the specified dictionary."""
+ self.globals = globals
+ self.fix()
+
+ def updateGlobals(self, otherGlobals):
+ """Merge another mapping object into this interpreter's globals."""
+ self.update(otherGlobals)
+
+ def clearGlobals(self):
+ """Clear out the globals with a brand new dictionary."""
+ self.clear()
+
+ def saveGlobals(self, deep=True):
+ """Save a copy of the globals off onto the history stack."""
+ self.save(deep)
+
+ def restoreGlobals(self, destructive=True):
+ """Restore the most recently saved copy of the globals."""
+ self.restore(destructive)
+
+ # Hook support.
+
+ def areHooksEnabled(self):
+ """Return whether or not hooks are presently enabled."""
+ if self.hooksEnabled is None:
+ return True
+ else:
+ return self.hooksEnabled
+
+ def enableHooks(self):
+ """Enable hooks."""
+ self.hooksEnabled = True
+
+ def disableHooks(self):
+ """Disable hooks."""
+ self.hooksEnabled = False
+
+ def getHooks(self):
+ """Get the current hooks."""
+ return self.hooks[:]
+
+ def clearHooks(self):
+ """Clear all hooks."""
+ self.hooks = []
+
+ def addHook(self, hook, prepend=False):
+ """Add a new hook; optionally insert it rather than appending it."""
+ self.register(hook, prepend)
+
+ def removeHook(self, hook):
+ """Remove a preexisting hook."""
+ self.deregister(hook)
+
+ def invokeHook(self, _name, **keywords):
+ """Manually invoke a hook."""
+ apply(self.invoke, (_name,), keywords)
+
+ # Callbacks.
+
+ def getCallback(self):
+ """Get the callback registered with this interpreter, or None."""
+ return self.callback
+
+ def registerCallback(self, callback):
+ """Register a custom markup callback with this interpreter."""
+ self.callback = callback
+
+ def deregisterCallback(self):
+ """Remove any previously registered callback with this interpreter."""
+ self.callback = None
+
+ def invokeCallback(self, contents):
+ """Invoke the callback."""
+ if self.callback is None:
+ if self.options.get(CALLBACK_OPT, False):
+ raise Error, "custom markup invoked with no defined callback"
+ else:
+ self.callback(contents)
+
+ # Pseudomodule manipulation.
+
+ def flatten(self, keys=None):
+ """Flatten the contents of the pseudo-module into the globals
+ namespace."""
+ if keys is None:
+ keys = self.__dict__.keys() + self.__class__.__dict__.keys()
+ dict = {}
+ for key in keys:
+ # The pseudomodule is really a class instance, so we need to
+ # fumble use getattr instead of simply fumbling through the
+ # instance's __dict__.
+ dict[key] = getattr(self, key)
+ # Stomp everything into the globals namespace.
+ self.globals.update(dict)
+
+ # Prefix.
+
+ def getPrefix(self):
+ """Get the current prefix."""
+ return self.prefix
+
+ def setPrefix(self, prefix):
+ """Set the prefix."""
+ self.prefix = prefix
+
+ # Diversions.
+
+ def stopDiverting(self):
+ """Stop any diverting."""
+ self.stream().revert()
+
+ def createDiversion(self, name):
+ """Create a diversion (but do not divert to it) if it does not
+ already exist."""
+ self.stream().create(name)
+
+ def retrieveDiversion(self, name):
+ """Retrieve the diversion object associated with the name."""
+ return self.stream().retrieve(name)
+
+ def startDiversion(self, name):
+ """Start diverting to the given diversion name."""
+ self.stream().divert(name)
+
+ def playDiversion(self, name):
+ """Play the given diversion and then purge it."""
+ self.stream().undivert(name, True)
+
+ def replayDiversion(self, name):
+ """Replay the diversion without purging it."""
+ self.stream().undivert(name, False)
+
+ def purgeDiversion(self, name):
+ """Eliminate the given diversion."""
+ self.stream().purge(name)
+
+ def playAllDiversions(self):
+ """Play all existing diversions and then purge them."""
+ self.stream().undivertAll(True)
+
+ def replayAllDiversions(self):
+ """Replay all existing diversions without purging them."""
+ self.stream().undivertAll(False)
+
+ def purgeAllDiversions(self):
+ """Purge all existing diversions."""
+ self.stream().purgeAll()
+
+ def getCurrentDiversion(self):
+ """Get the name of the current diversion."""
+ return self.stream().currentDiversion
+
+ def getAllDiversions(self):
+ """Get the names of all existing diversions."""
+ names = self.stream().diversions.keys()
+ names.sort()
+ return names
+
+ # Filter.
+
+ def resetFilter(self):
+ """Reset the filter so that it does no filtering."""
+ self.stream().install(None)
+
+ def nullFilter(self):
+ """Install a filter that will consume all text."""
+ self.stream().install(0)
+
+ def getFilter(self):
+ """Get the current filter."""
+ filter = self.stream().filter
+ if filter is self.stream().file:
+ return None
+ else:
+ return filter
+
+ def setFilter(self, shortcut):
+ """Set the filter."""
+ self.stream().install(shortcut)
+
+ def attachFilter(self, shortcut):
+ """Attach a single filter to the end of the current filter chain."""
+ self.stream().attach(shortcut)
+
+
+class Document:
+
+ """A representation of an individual EmPy document, as used by a
+ processor."""
+
+ def __init__(self, ID, filename):
+ self.ID = ID
+ self.filename = filename
+ self.significators = {}
+
+
+class Processor:
+
+ """An entity which is capable of processing a hierarchy of EmPy
+ files and building a dictionary of document objects associated
+ with them describing their significator contents."""
+
+ DEFAULT_EMPY_EXTENSIONS = ('.em',)
+ SIGNIFICATOR_RE = re.compile(SIGNIFICATOR_RE_STRING)
+
+ def __init__(self, factory=Document):
+ self.factory = factory
+ self.documents = {}
+
+ def identifier(self, pathname, filename): return filename
+
+ def clear(self):
+ self.documents = {}
+
+ def scan(self, basename, extensions=DEFAULT_EMPY_EXTENSIONS):
+ if type(extensions) is types.StringType:
+ extensions = (extensions,)
+ def _noCriteria(x):
+ return True
+ def _extensionsCriteria(pathname, extensions=extensions):
+ if extensions:
+ for extension in extensions:
+ if pathname[-len(extension):] == extension:
+ return True
+ return False
+ else:
+ return True
+ self.directory(basename, _noCriteria, _extensionsCriteria, None)
+ self.postprocess()
+
+ def postprocess(self):
+ pass
+
+ def directory(self, basename, dirCriteria, fileCriteria, depth=None):
+ if depth is not None:
+ if depth <= 0:
+ return
+ else:
+ depth = depth - 1
+ filenames = os.listdir(basename)
+ for filename in filenames:
+ pathname = os.path.join(basename, filename)
+ if os.path.isdir(pathname):
+ if dirCriteria(pathname):
+ self.directory(pathname, dirCriteria, fileCriteria, depth)
+ elif os.path.isfile(pathname):
+ if fileCriteria(pathname):
+ documentID = self.identifier(pathname, filename)
+ document = self.factory(documentID, pathname)
+ self.file(document, open(pathname))
+ self.documents[documentID] = document
+
+ def file(self, document, file):
+ while True:
+ line = file.readline()
+ if not line:
+ break
+ self.line(document, line)
+
+ def line(self, document, line):
+ match = self.SIGNIFICATOR_RE.search(line)
+ if match:
+ key, valueS = match.groups()
+ valueS = string.strip(valueS)
+ if valueS:
+ value = eval(valueS)
+ else:
+ value = None
+ document.significators[key] = value
+
+
+def expand(_data, _globals=None, \
+ _argv=None, _prefix=DEFAULT_PREFIX, _pseudo=None, _options=None, \
+ **_locals):
+ """Do an atomic expansion of the given source data, creating and
+ shutting down an interpreter dedicated to the task. The sys.stdout
+ object is saved off and then replaced before this function
+ returns."""
+ if len(_locals) == 0:
+ # If there were no keyword arguments specified, don't use a locals
+ # dictionary at all.
+ _locals = None
+ output = NullFile()
+ interpreter = Interpreter(output, argv=_argv, prefix=_prefix, \
+ pseudo=_pseudo, options=_options, \
+ globals=_globals)
+ if interpreter.options.get(OVERRIDE_OPT, True):
+ oldStdout = sys.stdout
+ try:
+ result = interpreter.expand(_data, _locals)
+ finally:
+ interpreter.shutdown()
+ if _globals is not None:
+ interpreter.unfix() # remove pseudomodule to prevent clashes
+ if interpreter.options.get(OVERRIDE_OPT, True):
+ sys.stdout = oldStdout
+ return result
+
+def environment(name, default=None):
+ """Get data from the current environment. If the default is True
+ or False, then presume that we're only interested in the existence
+ or non-existence of the environment variable."""
+ if os.environ.has_key(name):
+ # Do the True/False test by value for future compatibility.
+ if default == False or default == True:
+ return True
+ else:
+ return os.environ[name]
+ else:
+ return default
+
+def info(table):
+ DEFAULT_LEFT = 28
+ maxLeft = 0
+ maxRight = 0
+ for left, right in table:
+ if len(left) > maxLeft:
+ maxLeft = len(left)
+ if len(right) > maxRight:
+ maxRight = len(right)
+ FORMAT = ' %%-%ds %%s\n' % max(maxLeft, DEFAULT_LEFT)
+ for left, right in table:
+ if right.find('\n') >= 0:
+ for right in right.split('\n'):
+ sys.stderr.write(FORMAT % (left, right))
+ left = ''
+ else:
+ sys.stderr.write(FORMAT % (left, right))
+
+def usage(verbose=True):
+ """Print usage information."""
+ programName = sys.argv[0]
+ def warn(line=''):
+ sys.stderr.write("%s\n" % line)
+ warn("""\
+Usage: %s [options] [<filename, or '-' for stdin> [<argument>...]]
+Welcome to EmPy version %s.""" % (programName, __version__))
+ warn()
+ warn("Valid options:")
+ info(OPTION_INFO)
+ if verbose:
+ warn()
+ warn("The following markups are supported:")
+ info(MARKUP_INFO)
+ warn()
+ warn("Valid escape sequences are:")
+ info(ESCAPE_INFO)
+ warn()
+ warn("The %s pseudomodule contains the following attributes:" % \
+ DEFAULT_PSEUDOMODULE_NAME)
+ info(PSEUDOMODULE_INFO)
+ warn()
+ warn("The following environment variables are recognized:")
+ info(ENVIRONMENT_INFO)
+ warn()
+ warn(USAGE_NOTES)
+ else:
+ warn()
+ warn("Type %s -H for more extensive help." % programName)
+
+def invoke(args):
+ """Run a standalone instance of an EmPy interpeter."""
+ # Initialize the options.
+ _output = None
+ _options = {BUFFERED_OPT: environment(BUFFERED_ENV, False),
+ RAW_OPT: environment(RAW_ENV, False),
+ EXIT_OPT: True,
+ FLATTEN_OPT: environment(FLATTEN_ENV, False),
+ OVERRIDE_OPT: not environment(NO_OVERRIDE_ENV, False),
+ CALLBACK_OPT: False}
+ _preprocessing = []
+ _prefix = environment(PREFIX_ENV, DEFAULT_PREFIX)
+ _pseudo = environment(PSEUDO_ENV, None)
+ _interactive = environment(INTERACTIVE_ENV, False)
+ _extraArguments = environment(OPTIONS_ENV)
+ _binary = -1 # negative for not, 0 for default size, positive for size
+ _unicode = environment(UNICODE_ENV, False)
+ _unicodeInputEncoding = environment(INPUT_ENCODING_ENV, None)
+ _unicodeOutputEncoding = environment(OUTPUT_ENCODING_ENV, None)
+ _unicodeInputErrors = environment(INPUT_ERRORS_ENV, None)
+ _unicodeOutputErrors = environment(OUTPUT_ERRORS_ENV, None)
+ _hooks = []
+ _pauseAtEnd = False
+ _relativePath = False
+ if _extraArguments is not None:
+ _extraArguments = string.split(_extraArguments)
+ args = _extraArguments + args
+ # Parse the arguments.
+ pairs, remainder = getopt.getopt(args, 'VhHvkp:m:frino:a:buBP:I:D:E:F:', ['version', 'help', 'extended-help', 'verbose', 'null-hook', 'suppress-errors', 'prefix=', 'no-prefix', 'module=', 'flatten', 'raw-errors', 'interactive', 'no-override-stdout', 'binary', 'chunk-size=', 'output=' 'append=', 'preprocess=', 'import=', 'define=', 'execute=', 'execute-file=', 'buffered-output', 'pause-at-end', 'relative-path', 'no-callback-error', 'no-bangpath-processing', 'unicode', 'unicode-encoding=', 'unicode-input-encoding=', 'unicode-output-encoding=', 'unicode-errors=', 'unicode-input-errors=', 'unicode-output-errors='])
+ for option, argument in pairs:
+ if option in ('-V', '--version'):
+ sys.stderr.write("%s version %s\n" % (__program__, __version__))
+ return
+ elif option in ('-h', '--help'):
+ usage(False)
+ return
+ elif option in ('-H', '--extended-help'):
+ usage(True)
+ return
+ elif option in ('-v', '--verbose'):
+ _hooks.append(VerboseHook())
+ elif option in ('--null-hook',):
+ _hooks.append(Hook())
+ elif option in ('-k', '--suppress-errors'):
+ _options[EXIT_OPT] = False
+ _interactive = True # suppress errors implies interactive mode
+ elif option in ('-m', '--module'):
+ _pseudo = argument
+ elif option in ('-f', '--flatten'):
+ _options[FLATTEN_OPT] = True
+ elif option in ('-p', '--prefix'):
+ _prefix = argument
+ elif option in ('--no-prefix',):
+ _prefix = None
+ elif option in ('-r', '--raw-errors'):
+ _options[RAW_OPT] = True
+ elif option in ('-i', '--interactive'):
+ _interactive = True
+ elif option in ('-n', '--no-override-stdout'):
+ _options[OVERRIDE_OPT] = False
+ elif option in ('-o', '--output'):
+ _output = argument, 'w', _options[BUFFERED_OPT]
+ elif option in ('-a', '--append'):
+ _output = argument, 'a', _options[BUFFERED_OPT]
+ elif option in ('-b', '--buffered-output'):
+ _options[BUFFERED_OPT] = True
+ elif option in ('-B',): # DEPRECATED
+ _options[BUFFERED_OPT] = True
+ elif option in ('--binary',):
+ _binary = 0
+ elif option in ('--chunk-size',):
+ _binary = int(argument)
+ elif option in ('-P', '--preprocess'):
+ _preprocessing.append(('pre', argument))
+ elif option in ('-I', '--import'):
+ for module in string.split(argument, ','):
+ module = string.strip(module)
+ _preprocessing.append(('import', module))
+ elif option in ('-D', '--define'):
+ _preprocessing.append(('define', argument))
+ elif option in ('-E', '--execute'):
+ _preprocessing.append(('exec', argument))
+ elif option in ('-F', '--execute-file'):
+ _preprocessing.append(('file', argument))
+ elif option in ('-u', '--unicode'):
+ _unicode = True
+ elif option in ('--pause-at-end',):
+ _pauseAtEnd = True
+ elif option in ('--relative-path',):
+ _relativePath = True
+ elif option in ('--no-callback-error',):
+ _options[CALLBACK_OPT] = True
+ elif option in ('--no-bangpath-processing',):
+ _options[BANGPATH_OPT] = False
+ elif option in ('--unicode-encoding',):
+ _unicodeInputEncoding = _unicodeOutputEncoding = argument
+ elif option in ('--unicode-input-encoding',):
+ _unicodeInputEncoding = argument
+ elif option in ('--unicode-output-encoding',):
+ _unicodeOutputEncoding = argument
+ elif option in ('--unicode-errors',):
+ _unicodeInputErrors = _unicodeOutputErrors = argument
+ elif option in ('--unicode-input-errors',):
+ _unicodeInputErrors = argument
+ elif option in ('--unicode-output-errors',):
+ _unicodeOutputErrors = argument
+ # Set up the Unicode subsystem if required.
+ if _unicode or \
+ _unicodeInputEncoding or _unicodeOutputEncoding or \
+ _unicodeInputErrors or _unicodeOutputErrors:
+ theSubsystem.initialize(_unicodeInputEncoding, \
+ _unicodeOutputEncoding, \
+ _unicodeInputErrors, _unicodeOutputErrors)
+ # Now initialize the output file if something has already been selected.
+ if _output is not None:
+ _output = apply(AbstractFile, _output)
+ # Set up the main filename and the argument.
+ if not remainder:
+ remainder.append('-')
+ filename, arguments = remainder[0], remainder[1:]
+ # Set up the interpreter.
+ if _options[BUFFERED_OPT] and _output is None:
+ raise ValueError, "-b only makes sense with -o or -a arguments"
+ if _prefix == 'None':
+ _prefix = None
+ if _prefix and type(_prefix) is types.StringType and len(_prefix) != 1:
+ raise Error, "prefix must be single-character string"
+ interpreter = Interpreter(output=_output, \
+ argv=remainder, \
+ prefix=_prefix, \
+ pseudo=_pseudo, \
+ options=_options, \
+ hooks=_hooks)
+ try:
+ # Execute command-line statements.
+ i = 0
+ for which, thing in _preprocessing:
+ if which == 'pre':
+ command = interpreter.file
+ target = theSubsystem.open(thing, 'r')
+ name = thing
+ elif which == 'define':
+ command = interpreter.string
+ if string.find(thing, '=') >= 0:
+ target = '%s{%s}' % (_prefix, thing)
+ else:
+ target = '%s{%s = None}' % (_prefix, thing)
+ name = '<define:%d>' % i
+ elif which == 'exec':
+ command = interpreter.string
+ target = '%s{%s}' % (_prefix, thing)
+ name = '<exec:%d>' % i
+ elif which == 'file':
+ command = interpreter.string
+ name = '<file:%d (%s)>' % (i, thing)
+ target = '%s{execfile("""%s""")}' % (_prefix, thing)
+ elif which == 'import':
+ command = interpreter.string
+ name = '<import:%d>' % i
+ target = '%s{import %s}' % (_prefix, thing)
+ else:
+ assert 0
+ interpreter.wrap(command, (target, name))
+ i = i + 1
+ # Now process the primary file.
+ interpreter.ready()
+ if filename == '-':
+ if not _interactive:
+ name = '<stdin>'
+ path = ''
+ file = sys.stdin
+ else:
+ name, file = None, None
+ else:
+ name = filename
+ file = theSubsystem.open(filename, 'r')
+ path = os.path.split(filename)[0]
+ if _relativePath:
+ sys.path.insert(0, path)
+ if file is not None:
+ if _binary < 0:
+ interpreter.wrap(interpreter.file, (file, name))
+ else:
+ chunkSize = _binary
+ interpreter.wrap(interpreter.binary, (file, name, chunkSize))
+ # If we're supposed to go interactive afterwards, do it.
+ if _interactive:
+ interpreter.interact()
+ finally:
+ interpreter.shutdown()
+ # Finally, if we should pause at the end, do it.
+ if _pauseAtEnd:
+ try:
+ raw_input()
+ except EOFError:
+ pass
+
+def main():
+ invoke(sys.argv[1:])
+
+if __name__ == '__main__': main()
diff --git a/shell/data/gtkrc.em b/shell/data/gtkrc.em
new file mode 100644
index 0000000..d4e1a7c
--- /dev/null
+++ b/shell/data/gtkrc.em
@@ -0,0 +1,12 @@
+@{
+if scaling == '72':
+ icon_sizes = 'gtk-large-toolbar=40,40'
+else:
+ icon_sizes = 'gtk-large-toolbar=55,55'
+}@
+gtk-theme-name = "sugar-@scaling"
+gtk-icon-theme-name = "sugar"
+gtk-cursor-theme-name = "sugar"
+gtk-toolbar-style = GTK_TOOLBAR_ICONS
+gtk-icon-sizes = "@icon_sizes"
+gtk-cursor-blink-timeout = 3
diff --git a/shell/data/icons/Makefile.am b/shell/data/icons/Makefile.am
new file mode 100644
index 0000000..a35643a
--- /dev/null
+++ b/shell/data/icons/Makefile.am
@@ -0,0 +1,15 @@
+sugardir = $(pkgdatadir)/data/icons
+
+sugar_DATA = \
+ module-about_me.svg \
+ module-about_my_computer.svg \
+ module-date_and_time.svg \
+ module-frame.svg \
+ module-keyboard.svg \
+ module-language.svg \
+ module-modemconfiguration.svg \
+ module-network.svg \
+ module-power.svg \
+ module-updater.svg
+
+EXTRA_DIST = $(sugar_DATA)
diff --git a/shell/data/icons/module-about_me.svg b/shell/data/icons/module-about_me.svg
new file mode 100644
index 0000000..7abe926
--- /dev/null
+++ b/shell/data/icons/module-about_me.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [
+ <!ENTITY stroke_color "#666666">
+ <!ENTITY fill_color "#ffffff">
+]><svg enable-background="new 0 0 55 55" height="55px" id="Layer_1" version="1.1" viewBox="0 0 55 55" width="55px" x="0px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"><g display="block" id="module-about_x5F_me_1_">
+ <path d="M33.359,35.101L43.46,45.201c0.752,0.75,1.217,1.784,1.217,2.932 c0,2.287-1.855,4.143-4.146,4.143c-1.145,0-2.178-0.463-2.932-1.211L27.498,40.963l-10.1,10.1c-0.75,0.75-1.787,1.211-2.933,1.211 c-2.285,0-4.143-1.854-4.143-4.141c0-1.146,0.465-2.184,1.212-2.934l10.104-10.101L11.535,24.997 c-0.747-0.749-1.212-1.785-1.212-2.93c0-2.289,1.854-4.145,4.146-4.145c1.143,0,2.18,0.465,2.93,1.214l10.099,10.101l10.101-10.102 c0.754-0.749,1.787-1.214,2.934-1.214c2.289,0,4.146,1.856,4.146,4.145c0,1.145-0.467,2.179-1.217,2.93L33.359,35.101z" fill="&fill_color;" stroke="&stroke_color;" stroke-width="3.5"/>
+ <circle cx="27.497" cy="10.849" fill="&fill_color;" r="8.122" stroke="&stroke_color;" stroke-width="3.5"/>
+</g></svg> \ No newline at end of file
diff --git a/shell/data/icons/module-about_my_computer.svg b/shell/data/icons/module-about_my_computer.svg
new file mode 100644
index 0000000..cf3528e
--- /dev/null
+++ b/shell/data/icons/module-about_my_computer.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [
+ <!ENTITY stroke_color "#666666">
+ <!ENTITY fill_color "#ffffff">
+]><svg enable-background="new 0 0 55 55" height="55px" id="Layer_1" version="1.1" viewBox="0 0 55 55" width="55px" x="0px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"><g display="block" id="module-about_x5F_my_x5F_xo_1_">
+ <path d="M52.957,40.602h0.002l-0.025-0.017 c-0.152-0.11-0.315-0.21-0.483-0.302l-12.204-7.605V8.667c0-1.624-1.316-2.943-2.941-2.943H6.291c-1.625,0-2.942,1.319-2.942,2.943 V35.08c0,1.1,0.61,2.045,1.503,2.551l-0.019,0.004L19.49,46.77c0.694,0.436,1.534,0.691,2.438,0.691h28.319 c2.362,0,4.296-1.74,4.296-3.865C54.543,42.391,53.923,41.312,52.957,40.602z M9.072,12.392c0-0.619,0.506-1.124,1.124-1.124H33.4 c0.617,0,1.123,0.505,1.123,1.124v16.561c0,0.617-0.506,1.126-1.123,1.126H10.196c-0.617,0-1.124-0.509-1.124-1.126V12.392z" display="inline" fill="&fill_color;" id="module-about_x5F_my_x5F_xo"/>
+</g></svg> \ No newline at end of file
diff --git a/shell/data/icons/module-date_and_time.svg b/shell/data/icons/module-date_and_time.svg
new file mode 100644
index 0000000..605dbeb
--- /dev/null
+++ b/shell/data/icons/module-date_and_time.svg
@@ -0,0 +1,19 @@
+<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [
+ <!ENTITY stroke_color "#666666">
+ <!ENTITY fill_color "#ffffff">
+]><svg enable-background="new 0 0 55 55" height="55px" id="Layer_1" version="1.1" viewBox="0 0 55 55" width="55px" x="0px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"><g display="block" id="module-date_x5F_and_x5F_time">
+ <g display="inline">
+ <defs>
+ <path d="M29.891,31.641h17.255c0.346-1.563,0.534-3.187,0.534-4.854c0-12.35-10.013-22.362-22.362-22.362 c-12.351,0-22.362,10.012-22.362,22.362c0,12.351,10.011,22.362,22.362,22.362c1.567,0,3.097-0.163,4.573-0.47V31.641z M26.286,28.242c-0.034,0.022-0.071,0.038-0.107,0.058c-0.064,0.037-0.127,0.075-0.196,0.104 c-0.039,0.016-0.079,0.023-0.118,0.036c-0.069,0.023-0.138,0.049-0.21,0.062c-0.048,0.01-0.098,0.01-0.147,0.015 c-0.063,0.007-0.126,0.02-0.191,0.02c-0.071,0-0.139-0.013-0.208-0.021c-0.043-0.006-0.086-0.005-0.129-0.014 c-0.078-0.015-0.152-0.041-0.226-0.066c-0.034-0.012-0.069-0.019-0.102-0.032c-0.077-0.031-0.147-0.072-0.217-0.113 c-0.028-0.017-0.059-0.028-0.086-0.047c-0.193-0.129-0.359-0.294-0.487-0.487c-0.028-0.042-0.047-0.086-0.071-0.13 c-0.031-0.057-0.065-0.111-0.09-0.171c-0.023-0.056-0.037-0.115-0.054-0.173c-0.015-0.051-0.035-0.101-0.045-0.153 c-0.023-0.114-0.035-0.229-0.035-0.344V10.349c0-0.966,0.783-1.75,1.75-1.75s1.75,0.784,1.75,1.75v12.212l3.973-3.973 c0.684-0.683,1.792-0.683,2.476,0c0.683,0.683,0.683,1.792,0,2.475l-6.96,6.96c-0.001,0.002-0.003,0.003-0.005,0.004 C26.469,28.107,26.381,28.179,26.286,28.242z" id="SVGID_5_"/>
+ </defs>
+ <clipPath id="SVGID_6_">
+ <use overflow="visible" xlink:href="#SVGID_5_"/>
+ </clipPath>
+ <circle clip-path="url(#SVGID_6_)" cx="25.318" cy="26.786" fill="&fill_color;" r="22.362"/>
+ </g>
+ <rect display="inline" fill="none" height="19.319" stroke="&fill_color;" stroke-width="2" width="21.064" x="29.891" y="31.641"/>
+ <g display="inline">
+ <path d="M39.056,44.155c0.527,0,0.936,0.239,0.936,0.792c0,0.551-0.408,0.791-0.864,0.791h-4.006 c-0.527,0-0.936-0.24-0.936-0.791c0-0.252,0.156-0.469,0.276-0.612c0.995-1.188,2.075-2.267,2.986-3.586 c0.216-0.312,0.42-0.684,0.42-1.115c0-0.491-0.372-0.924-0.863-0.924c-1.38,0-0.72,1.943-1.871,1.943 c-0.575,0-0.876-0.408-0.876-0.876c0-1.511,1.344-2.723,2.818-2.723c1.476,0,2.663,0.972,2.663,2.495 c0,1.667-1.858,3.322-2.878,4.605H39.056z" fill="&fill_color;"/>
+ <path d="M46.339,39.25c0,0.756-0.323,1.415-0.983,1.835c0.863,0.396,1.463,1.199,1.463,2.146 c0,1.439-1.318,2.651-3.021,2.651c-1.775,0-2.879-1.309-2.879-2.256c0-0.467,0.492-0.803,0.924-0.803 c0.815,0,0.623,1.402,1.979,1.402c0.623,0,1.127-0.479,1.127-1.115c0-1.679-2.039-0.443-2.039-1.858 c0-1.259,1.703-0.408,1.703-1.739c0-0.455-0.323-0.804-0.863-0.804c-1.139,0-0.982,1.176-1.799,1.176 c-0.492,0-0.779-0.444-0.779-0.888c0-0.936,1.283-1.943,2.614-1.943C45.512,37.055,46.339,38.314,46.339,39.25z" fill="&fill_color;"/>
+ </g>
+</g></svg> \ No newline at end of file
diff --git a/shell/data/icons/module-frame.svg b/shell/data/icons/module-frame.svg
new file mode 100644
index 0000000..11ccee4
--- /dev/null
+++ b/shell/data/icons/module-frame.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [
+ <!ENTITY stroke_color "#666666">
+ <!ENTITY fill_color "#ffffff">
+]><svg enable-background="new 0 0 55 55" height="55px" id="Layer_1" version="1.1" viewBox="0 0 55 55" width="55px" x="0px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"><g display="block" id="module-frame">
+ <g display="inline">
+ <g>
+ <path d="M2.934,8.664v38.209h49.391V8.664H2.934z M49.115,43.833H6.144V11.704h42.971V43.833z" fill="&fill_color;"/>
+ </g>
+ </g>
+ <g display="inline" id="Left_Pointer_1_">
+ <path d="M12.533,16.981l-2.359-2.359h1.597c0.2,0,0.401-0.077,0.555-0.23c0.307-0.306,0.307-0.804,0-1.11 c-0.153-0.154-0.355-0.23-0.555-0.231H7.49l0.001,4.28c0,0.201,0.076,0.401,0.23,0.555c0.306,0.307,0.804,0.307,1.111,0 c0.154-0.153,0.229-0.354,0.229-0.555v-1.597l2.36,2.358c0.142,0.142,0.338,0.23,0.556,0.23c0.434,0,0.785-0.353,0.785-0.786 C12.763,17.319,12.674,17.123,12.533,16.981z" fill="&fill_color;"/>
+ </g>
+</g></svg>
diff --git a/shell/data/icons/module-keyboard.svg b/shell/data/icons/module-keyboard.svg
new file mode 100644
index 0000000..43bbc57
--- /dev/null
+++ b/shell/data/icons/module-keyboard.svg
@@ -0,0 +1,134 @@
+<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [
+ <!ENTITY stroke_color "#666666">
+ <!ENTITY fill_color "#ffffff">
+]><svg enable-background="new 0 0 55 55" height="55px" id="Layer_1" version="1.1" viewBox="0 0 55 55" width="55px" x="0px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"><g display="block" id="module-keyboard">
+ <rect display="inline" fill="&fill_color;" height="23.326" width="54" x="0.5" y="15.837"/>
+ <g display="inline">
+ <path d="M52.61,28.081c0,0.55-0.45,1-1,1h-5.443c-0.55,0-1-0.45-1-1v-5.444c0-0.55,0.45-1,1-1h5.443c0.55,0,1,0.45,1,1V28.081z"/>
+ </g>
+ <g display="inline">
+ <path d="M52.61,19.941c0,0.55-0.45,1-1,1h-5.412c-0.55,0-1-0.45-1-1v-1.368c0-0.55,0.45-1,1-1h5.412c0.55,0,1,0.45,1,1V19.941z"/>
+ </g>
+ <g display="inline">
+ <path d="M52.61,32.267c0,0.55-0.45,1-1,1h-5.412c-0.55,0-1-0.45-1-1v-1.367c0-0.55,0.45-1,1-1h5.412c0.55,0,1,0.45,1,1V32.267z"/>
+ </g>
+ <g display="inline">
+ <path d="M39.11,36.403c0,0.55-0.45,1-1,1H13.176c-0.55,0-1-0.45-1-1v-1.366c0-0.55,0.45-1,1-1H38.11c0.55,0,1,0.45,1,1V36.403z"/>
+ </g>
+ <g display="inline">
+ <g>
+ <path d="M5.889,19.977c0,0.55-0.45,1-1,1H3.549c-0.55,0-1-0.45-1-1v-1.339c0-0.55,0.45-1,1-1h1.339c0.55,0,1,0.45,1,1V19.977z"/>
+ </g>
+ <g>
+ <path d="M10.65,19.977c0,0.55-0.45,1-1,1H8.311c-0.55,0-1-0.45-1-1v-1.339c0-0.55,0.45-1,1-1H9.65c0.55,0,1,0.45,1,1V19.977z"/>
+ </g>
+ <g>
+ <path d="M15.411,19.977c0,0.55-0.45,1-1,1h-1.339c-0.55,0-1-0.45-1-1v-1.339c0-0.55,0.45-1,1-1h1.339c0.55,0,1,0.45,1,1V19.977z"/>
+ </g>
+ <g>
+ <path d="M20.173,19.978c0,0.55-0.45,1-1,1h-1.34c-0.55,0-1-0.45-1-1v-1.34c0-0.55,0.45-1,1-1h1.34c0.55,0,1,0.45,1,1V19.978z"/>
+ </g>
+ <g>
+ <path d="M24.934,19.977c0,0.55-0.45,1-1,1h-1.34c-0.55,0-1-0.45-1-1v-1.339c0-0.55,0.45-1,1-1h1.34c0.55,0,1,0.45,1,1V19.977z"/>
+ </g>
+ <g>
+ <path d="M29.695,19.978c0,0.55-0.45,1-1,1h-1.34c-0.55,0-1-0.45-1-1v-1.34c0-0.55,0.45-1,1-1h1.34c0.55,0,1,0.45,1,1V19.978z"/>
+ </g>
+ <g>
+ <path d="M34.457,19.977c0,0.55-0.45,1-1,1h-1.341c-0.55,0-1-0.45-1-1v-1.339c0-0.55,0.45-1,1-1h1.341c0.55,0,1,0.45,1,1V19.977z"/>
+ </g>
+ <g>
+ <path d="M39.218,19.977c0,0.55-0.45,1-1,1h-1.341c-0.55,0-1-0.45-1-1v-1.339c0-0.55,0.45-1,1-1h1.341c0.55,0,1,0.45,1,1V19.977z"/>
+ </g>
+ <g>
+ <path d="M43.979,19.977c0,0.55-0.45,1-1,1h-1.341c-0.55,0-1-0.45-1-1v-1.339c0-0.55,0.45-1,1-1h1.341c0.55,0,1,0.45,1,1V19.977z"/>
+ </g>
+ </g>
+ <g display="inline">
+ <g>
+ <path d="M5.889,24.038c0,0.55-0.45,1-1,1H3.549c-0.55,0-1-0.45-1-1v-1.339c0-0.55,0.45-1,1-1h1.339c0.55,0,1,0.45,1,1V24.038z"/>
+ </g>
+ <g>
+ <path d="M10.65,24.038c0,0.55-0.45,1-1,1H8.311c-0.55,0-1-0.45-1-1v-1.339c0-0.55,0.45-1,1-1H9.65c0.55,0,1,0.45,1,1V24.038z"/>
+ </g>
+ <g>
+ <path d="M15.411,24.038c0,0.55-0.45,1-1,1h-1.339c-0.55,0-1-0.45-1-1v-1.339c0-0.55,0.45-1,1-1h1.339c0.55,0,1,0.45,1,1V24.038z"/>
+ </g>
+ <g>
+ <path d="M20.173,24.038c0,0.55-0.45,1-1,1h-1.34c-0.55,0-1-0.45-1-1v-1.34c0-0.55,0.45-1,1-1h1.34c0.55,0,1,0.45,1,1V24.038z"/>
+ </g>
+ <g>
+ <path d="M24.934,24.038c0,0.55-0.45,1-1,1h-1.34c-0.55,0-1-0.45-1-1v-1.339c0-0.55,0.45-1,1-1h1.34c0.55,0,1,0.45,1,1V24.038z"/>
+ </g>
+ <g>
+ <path d="M29.695,24.038c0,0.55-0.45,1-1,1h-1.34c-0.55,0-1-0.45-1-1v-1.34c0-0.55,0.45-1,1-1h1.34c0.55,0,1,0.45,1,1V24.038z"/>
+ </g>
+ <g>
+ <path d="M34.457,24.038c0,0.55-0.45,1-1,1h-1.341c-0.55,0-1-0.45-1-1v-1.339c0-0.55,0.45-1,1-1h1.341c0.55,0,1,0.45,1,1V24.038z"/>
+ </g>
+ <g>
+ <path d="M39.218,24.038c0,0.55-0.45,1-1,1h-1.341c-0.55,0-1-0.45-1-1v-1.339c0-0.55,0.45-1,1-1h1.341c0.55,0,1,0.45,1,1V24.038z"/>
+ </g>
+ <g>
+ <path d="M43.979,24.038c0,0.55-0.45,1-1,1h-1.341c-0.55,0-1-0.45-1-1v-1.339c0-0.55,0.45-1,1-1h1.341c0.55,0,1,0.45,1,1V24.038z"/>
+ </g>
+ </g>
+ <g display="inline">
+ <g>
+ <path d="M5.889,28.099c0,0.55-0.45,1-1,1.001H3.549c-0.55,0.001-1-0.449-1-0.999V26.76c0-0.55,0.45-1,1-1h1.339 c0.55,0,1,0.45,1,1V28.099z"/>
+ </g>
+ <g>
+ <path d="M10.65,28.099c0,0.55-0.45,1-1,1.001H8.311c-0.55,0.001-1-0.449-1-0.999V26.76c0-0.55,0.45-1,1-1H9.65c0.55,0,1,0.45,1,1 V28.099z"/>
+ </g>
+ <g>
+ <path d="M15.411,28.099c0,0.55-0.45,1-1,1L13.072,29.1c-0.55,0-1-0.45-1-1v-1.34c0-0.55,0.45-1,1-1h1.339c0.55,0,1,0.45,1,1 V28.099z"/>
+ </g>
+ <g>
+ <path d="M20.173,28.099c0,0.55-0.45,1-1,1l-1.34,0.001c-0.55,0-1-0.45-1-1v-1.34c0-0.55,0.45-1,1-1h1.34c0.55,0,1,0.45,1,1 V28.099z"/>
+ </g>
+ <g>
+ <path d="M24.934,28.099c0,0.55-0.45,1-1,1l-1.34,0.001c-0.55,0-1-0.45-1-1v-1.34c0-0.55,0.45-1,1-1h1.34c0.55,0,1,0.45,1,1 V28.099z"/>
+ </g>
+ <g>
+ <path d="M29.695,28.099c0,0.55-0.45,1-1,1l-1.34,0.001c-0.55,0-1-0.45-1-1v-1.34c0-0.55,0.45-1,1-1h1.34c0.55,0,1,0.45,1,1 V28.099z"/>
+ </g>
+ <g>
+ <path d="M34.457,28.099c0,0.55-0.45,1-1,1L32.116,29.1c-0.55,0-1-0.45-1-1v-1.34c0-0.55,0.45-1,1-1h1.341c0.55,0,1,0.45,1,1 V28.099z"/>
+ </g>
+ <g>
+ <path d="M39.218,28.099c0,0.55-0.45,1-1,1h-1.341c-0.55,0-1-0.45-1-1V26.76c0-0.55,0.45-1,1-1h1.341c0.55,0,1,0.45,1,1V28.099z"/>
+ </g>
+ <g>
+ <path d="M43.979,28.099c0,0.55-0.45,1-1,1h-1.341c-0.55,0-1-0.45-1-1V26.76c0-0.55,0.45-1,1-1h1.341c0.55,0,1,0.45,1,1V28.099z"/>
+ </g>
+ </g>
+ <g display="inline">
+ <g>
+ <path d="M5.889,32.161c0,0.55-0.45,1-1,1H3.549c-0.55,0-1-0.45-1-1v-1.34c0-0.55,0.45-1,1-1h1.339c0.55,0,1,0.45,1,1V32.161z"/>
+ </g>
+ <g>
+ <path d="M10.65,32.161c0,0.55-0.45,1-1,1H8.311c-0.55,0-1-0.45-1-1v-1.34c0-0.55,0.45-1,1-1H9.65c0.55,0,1,0.45,1,1V32.161z"/>
+ </g>
+ <g>
+ <path d="M15.411,32.161c0,0.55-0.45,1-1,1h-1.339c-0.55,0-1-0.45-1-1v-1.34c0-0.55,0.45-1,1-1h1.339c0.55,0,1,0.45,1,1V32.161z"/>
+ </g>
+ <g>
+ <path d="M20.173,32.161c0,0.55-0.45,1-1,1h-1.34c-0.55,0-1-0.45-1-1v-1.34c0-0.55,0.45-1,1-1h1.34c0.55,0,1,0.45,1,1V32.161z"/>
+ </g>
+ <g>
+ <path d="M24.934,32.161c0,0.55-0.45,1-1,1h-1.34c-0.55,0-1-0.45-1-1v-1.34c0-0.55,0.45-1,1-1h1.34c0.55,0,1,0.45,1,1V32.161z"/>
+ </g>
+ <g>
+ <path d="M29.695,32.161c0,0.55-0.45,1-1,1h-1.34c-0.55,0-1-0.45-1-1v-1.34c0-0.55,0.45-1,1-1h1.34c0.55,0,1,0.45,1,1V32.161z"/>
+ </g>
+ <g>
+ <path d="M34.457,32.161c0,0.55-0.45,1-1,1h-1.341c-0.55,0-1-0.45-1-1v-1.34c0-0.55,0.45-1,1-1h1.341c0.55,0,1,0.45,1,1V32.161z"/>
+ </g>
+ <g>
+ <path d="M39.218,32.161c0,0.55-0.45,1-1,1h-1.341c-0.55,0-1-0.45-1-1v-1.34c0-0.55,0.45-1,1-1h1.341c0.55,0,1,0.45,1,1V32.161z"/>
+ </g>
+ <g>
+ <path d="M43.979,32.161c0,0.55-0.45,1-1,1h-1.341c-0.55,0-1-0.45-1-1v-1.34c0-0.55,0.45-1,1-1h1.341c0.55,0,1,0.45,1,1V32.161z"/>
+ </g>
+ </g>
+</g></svg> \ No newline at end of file
diff --git a/shell/data/icons/module-language.svg b/shell/data/icons/module-language.svg
new file mode 100644
index 0000000..ce04cb4
--- /dev/null
+++ b/shell/data/icons/module-language.svg
@@ -0,0 +1,59 @@
+<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [
+ <!ENTITY stroke_color "#666666">
+ <!ENTITY fill_color "#ffffff">
+]><svg enable-background="new 0 0 55 55" height="55px" id="Layer_1" version="1.1" viewBox="0 0 55 55" width="55px" x="0px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"><g display="block" id="module-language">
+ <g display="inline">
+ <g>
+ <path clip-rule="evenodd" d="M35.805,13.962c-0.346-0.084-0.779-0.072-0.725-0.6 c0.24,0,0.482,0,0.725,0l-0.092,0.301L35.805,13.962z" fill="&fill_color;" fill-rule="evenodd"/>
+ <path clip-rule="evenodd" d="M36.438,14.063l0.09-0.3c1.146-0.067,1.871,0.331,2.533,0.8 c3.684,0.596,9.051-0.669,10.676,2.2c-0.164,0.485-0.727,0.53-1.268,0.6c0.531,0.215,0.637,0.898,0.543,1.8 c-0.928-0.174-1.295-0.968-1.447-2c-0.553,0.389-1.396,0.457-2.352,0.4c-0.121,0.867,0.84,0.539,1.447,0.6 c0.057,0.472,0.629,0.373,0.543,1l-0.271,0.1l0.09,0.3c-0.549,0.658-0.812,1.635-0.723,3c-0.551,0.01-0.703-0.422-1.086-0.6 c0.26,1.426,0.908,1.89,0.723,3.4c-1.1-1.013-1.793,1.516-1.99,2.799c-0.627,0.161-0.693-0.297-1.266-0.199l-0.182-0.1l-0.18,0.1 c-0.475-1.659-2.096-3.383-3.076-1c0,0.4,0,0.8,0,1.199l-0.271-0.1l0.09,0.301c-1.533,0.073-0.889-3.936-3.438-3.4 c-0.357,1.338-1.098,2.254-2.535,2.4c-0.164,0.784,0.955,0.145,1.086,0.6c-0.146,1.638-1.449,1.998-1.99,3.2 c0.646,2.376-0.838,2.551-0.902,4.601c-0.834,0.479-0.881,1.828-1.811,2.2c-3.492-0.651-1.328-6.379-3.258-8.601 c-0.848-0.263-1.984-0.205-3.075-0.2c-2.032-1.712-0.716-5.163,0.724-6.8c-0.372-0.323-0.419-1.003-0.362-1.801 c0.481-0.002,1.034,0.076,1.267-0.2c-0.148-0.703-0.412-1.279-1.267-1.201c0.081-0.912,0.522-1.423,1.267-1.6 c0.269,0.237,0.282,0.755,0.543,1c0.512,0.165,0.469-0.282,0.904-0.201c0.191-0.877-0.611-0.657-0.543-1.4 c0.357-0.828,1.436-1.889,2.354-2c1.213-0.147,1.916,0.831,3.617,0.6c0.695-0.094,1.172-0.681,2.172-1 c0.594-0.19,1.852,0.085,2.354-0.601L36.438,14.063z M27.842,17.963c0.438-0.392,0.416-1-0.18-1.2 C27.766,17.115,27.441,17.938,27.842,17.963z M27.48,21.563c-0.186-0.944-2.604-0.928-2.353,0.4c0.483,0,0.966,0,1.448,0 l0.09,0.3l0.271-0.101c0,0.066,0,0.134,0,0.201c0.442,1.067,3.484,1.27,3.98,0c-1.439,0.273-2.379-0.674-3.256-0.4l0.09-0.301 L27.48,21.563z M32.365,26.764c0.031-0.501-0.277-0.626-0.541-0.8C31.791,26.464,32.102,26.59,32.365,26.764z" fill="&fill_color;" fill-rule="evenodd"/>
+ <path clip-rule="evenodd" d="M48.018,21.863l0.09-0.3c0.316,0.184,0.4,0.625,0.361,1.2 c-0.547,0.261-1.043,0.579-1.627,0.8c-0.492-1.168,0.654-0.949,0.904-1.8L48.018,21.863z" fill="&fill_color;" fill-rule="evenodd"/>
+ <path clip-rule="evenodd" d="M42.77,28.864l0.092-0.301c0.662,0.467,0.711,1.615,0.904,2.601 l-0.332-0.1l-0.211,0.3c-0.619-0.582-0.996-1.434-1.268-2.4c0.146,0.105,0.295,0.209,0.543,0.2c0-0.134,0-0.267,0-0.399 L42.77,28.864z" fill="&fill_color;" fill-rule="evenodd"/>
+ <path clip-rule="evenodd" d="M11.376,25.163c0.423,0.067,0.78,0.206,0.905,0.601 l-0.271,0.233l0.09,0.367c-0.21-0.033-0.322,0.044-0.361,0.199c-0.465-0.02-0.291-0.744-0.543-1l0.271-0.1L11.376,25.163z" fill="&fill_color;" fill-rule="evenodd"/>
+ <path clip-rule="evenodd" d="M46.299,27.764c-0.281-0.29-0.381-0.78-0.363-1.399 c0.182,0,0.363,0,0.545,0c0.104,0.418,0.447,0.57,0.361,1.199l-0.332-0.1L46.299,27.764z" fill="&fill_color;" fill-rule="evenodd"/>
+ <path clip-rule="evenodd" d="M45.936,30.364c0.131,0.877-0.602,0.8-1.266,0.8 c-0.264-0.44-0.549-0.858-0.543-1.601c0.738-0.051,0.895-0.744,1.809-0.6c-0.057,0.465,0.029,0.768,0.182,1l-0.271,0.101 L45.936,30.364z" fill="&fill_color;" fill-rule="evenodd"/>
+ <path clip-rule="evenodd" d="M46.389,30.265l-0.09-0.301c0.42-0.063,0.693,0.033,0.904,0.2 c-0.332,0.501-0.145,1.574-1.086,1.4c0-0.4,0-0.8,0-1.2L46.389,30.265z" fill="&fill_color;" fill-rule="evenodd"/>
+ <path clip-rule="evenodd" d="M48.65,31.164c-0.553,0.146-0.531-0.345-1.086-0.199 c-0.086-0.716,1.123-1.144,1.268-0.4l-0.271,0.233L48.65,31.164z" fill="&fill_color;" fill-rule="evenodd"/>
+ <path clip-rule="evenodd" d="M49.104,30.931l-0.092-0.366c0.926-0.487,1.51,0.948,2.715,0.8 c-0.617,0.261-0.084,0.855-0.361,1.199" fill="&fill_color;" fill-rule="evenodd"/>
+ <path clip-rule="evenodd" d="M49.918,32.765c0.291,0.117,1.43,2.391,1.447,3 c0.045,1.674-1.633,3.377-2.715,3.201c-0.922-0.152-0.688-0.955-1.447-1.601c-0.98,0.12-1.854,0.957-2.895,0.399 c-0.109-1.053,0.133-1.718,0.18-2.6c1.658-0.205,3.107-3.646,4.705-1.4C49.775,33.807,49.633,33.051,49.918,32.765z" fill="&fill_color;" fill-rule="evenodd"/>
+ <path clip-rule="evenodd" d="M33.451,33.164c0.725,1.004,0.084,2.368-0.725,2.8 C32.002,34.962,32.742,33.707,33.451,33.164z" fill="&fill_color;" fill-rule="evenodd"/>
+ <path clip-rule="evenodd" d="M48.65,39.165c0.383,0.062,0.039,0.948-0.361,0.8 C48.082,39.336,48.572,39.479,48.65,39.165z" fill="&fill_color;" fill-rule="evenodd"/>
+ <path clip-rule="evenodd" d="M16.805,12.962c2.05,0,4.102,0,6.15,0 c0.007,0.195,0.223,0.155,0.363,0.2c0.324,0.701-0.65,1.677-1.084,2.2c-1.523,0.317-2.302,1.457-3.621,2 c-0.971-0.425-0.523-1.94-0.724-2.8c-0.439-0.464-2.139-0.687-2.532,0c1.822-0.026,1.732,2.399,0.18,2.6 c1.181-0.023,1.775,2.452,0.543,2.8l0.091-0.3l-0.272-0.1c-0.099-0.657-1.186,0.234-0.541,0.4l-0.092,0.3l0.271,0.101 c-2.093,0.085-2.461,2.079-3.98,2.799c0,0.201,0,0.4,0,0.601l-0.332-0.1l-0.21,0.3c-0.761-0.482-2.09-0.11-2.534,0.399 c0,0.534,0,1.067,0,1.601c0.622,0.076,1.287-0.828,1.628-0.4c-0.564,1.371,0.746,1.471,0.906,2.6l-0.272,0.201l0.272,0.199 c-0.433,0.062-1.751-0.617-2.534-1.199C7.746,26.816,7,25.966,6.673,25.163c-0.38-0.229-0.707,0.123-1.086-0.399 c-0.229-1.306-0.774-2.063-0.724-3.2c0.032-0.728,0.597-0.782,0.724-1.601c0.033-0.217-0.352-0.856-0.362-1.399 c-0.01-0.526,0.43-0.783,0-1.201c-1.454-0.274-1.845,0.627-3.076,0.6l0.09-0.199l-0.09-0.2c-0.231-0.521-0.397-0.604-0.182-1.2 c1.83-1.044,4.094-1.609,7.057-1.4c0.688-0.04,0.687-0.841,1.448-0.8c1.325-0.132,2.296,0.13,3.257,0.4 c0.705-0.021,0.311-1.256,0.904-1.4C15.303,13.035,16.436,13.422,16.805,12.962z M14.452,15.963 c0.286-0.02,0.628,0.027,0.542-0.4c-0.119,0-0.24,0-0.361,0C14.633,15.764,14.486,15.801,14.452,15.963z M11.92,17.763 c0.789-0.139,0.803,0.577,1.447,0.6c0.266-0.505,0.639-0.893,0.723-1.6C13.093,16.545,11.914,16.393,11.92,17.763z" fill="&fill_color;" fill-rule="evenodd"/>
+ <path clip-rule="evenodd" d="M11.467,28.364l-0.091-0.201 c1.866-1.496,4.541,0.503,5.247,2.201c0.822,0.426,2.057,0.393,2.352,1.4c-0.152,1.164-0.635,1.965-0.723,3.199 c-0.226,0.484-0.742,0.648-1.268,0.801c-0.201,2.777-2.827,3.017-2.17,6.201c0.453-0.035,0.566,0.307,0.723,0.6 c-0.36,0-0.723,0-1.085,0c-1.601-1.566-1.526-4.982-1.81-8.001c-1.498-1.305-2.468-3.841-1.267-6.001L11.467,28.364z" fill="&fill_color;" fill-rule="evenodd"/>
+ <g>
+
+ <path clip-rule="evenodd" d=" M48.018,21.863" fill="&fill_color;" fill-rule="evenodd" stroke="#000000" stroke-linecap="round" stroke-width="2.1637"/>
+
+ <path clip-rule="evenodd" d=" M46.932,19.263" fill="&fill_color;" fill-rule="evenodd" stroke="#000000" stroke-linecap="round" stroke-width="2.1637"/>
+
+ <path clip-rule="evenodd" d=" M43.434,31.064" fill="&fill_color;" fill-rule="evenodd" stroke="#000000" stroke-linecap="round" stroke-width="2.8752"/>
+
+ <path clip-rule="evenodd" d=" M42.498,27.864" fill="&fill_color;" fill-rule="evenodd" stroke="#000000" stroke-linecap="round" stroke-width="2.1506"/>
+
+ <path clip-rule="evenodd" d=" M38.971,28.063" fill="&fill_color;" fill-rule="evenodd" stroke="#000000" stroke-linecap="round" stroke-width="1.2247"/>
+
+ <path clip-rule="evenodd" d=" M26.851,21.793" fill="&fill_color;" fill-rule="evenodd" stroke="#000000" stroke-linecap="round" stroke-width="2.1448"/>
+
+ <path clip-rule="evenodd" d=" M12.01,25.997" fill="&fill_color;" fill-rule="evenodd" stroke="#000000" stroke-linecap="round" stroke-width="2.8087"/>
+
+ <path clip-rule="evenodd" d=" M46.51,27.464" fill="&fill_color;" fill-rule="evenodd" stroke="#000000" stroke-linecap="round" stroke-width="3.5214"/>
+
+ <path clip-rule="evenodd" d=" M46.752,28.264" fill="&fill_color;" fill-rule="evenodd" stroke="#000000" stroke-linecap="round" stroke-width="3.5214"/>
+
+ <path clip-rule="evenodd" d=" M45.846,30.064" fill="&fill_color;" fill-rule="evenodd" stroke="#000000" stroke-linecap="round" stroke-width="2.1602"/>
+
+ <path clip-rule="evenodd" d=" M49.104,30.931" fill="&fill_color;" fill-rule="evenodd" stroke="#000000" stroke-linecap="round" stroke-width="3.1091"/>
+
+ <path clip-rule="evenodd" d=" M46.752,32.165" fill="&fill_color;" fill-rule="evenodd" stroke="#000000" stroke-linecap="round" stroke-width="2.1381"/>
+
+ <path clip-rule="evenodd" d=" M15.266,20.263" fill="&fill_color;" fill-rule="evenodd" stroke="#000000" stroke-linecap="round" stroke-width="1.6279"/>
+
+ <path clip-rule="evenodd" d=" M11.225,23.664" fill="&fill_color;" fill-rule="evenodd" stroke="#000000" stroke-linecap="round" stroke-width="2.9155"/>
+
+ <path clip-rule="evenodd" d=" M11.467,28.364" fill="&fill_color;" fill-rule="evenodd" stroke="#000000" stroke-linecap="round" stroke-width="2.0976"/>
+
+ <path clip-rule="evenodd" d=" M10.743,28.364" fill="&fill_color;" fill-rule="evenodd" stroke="#000000" stroke-linecap="round" stroke-width="2.0976"/>
+ </g>
+ </g>
+ </g>
+ <rect display="inline" fill="none" height="30.334" stroke="&fill_color;" stroke-width="3.5" width="50.67" x="2.25" y="12.625"/>
+</g></svg> \ No newline at end of file
diff --git a/shell/data/icons/module-modemconfiguration.svg b/shell/data/icons/module-modemconfiguration.svg
new file mode 100644
index 0000000..02ccc81
--- /dev/null
+++ b/shell/data/icons/module-modemconfiguration.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [
+ <!ENTITY stroke_color "#ffffff">
+ <!ENTITY fill_color "none">
+]><svg enable-background="new 0 0 56.167 55" height="55px" version="1.1" viewBox="0 0 56.167 55" width="56.167px" x="0px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"><g display="block" id="network-gsm">
+ <g>
+ <path d="M13.759,11.28 v28.937h0.002c0,0.004-0.002,0.008-0.002,0.014c0,7.05,5.715,12.763,12.764,12.763c7.047,0,12.762-5.713,12.762-12.763v-0.014 V11.28H13.759z" fill="&fill_color;" stroke="&stroke_color;" stroke-linecap="round" stroke-linejoin="round" stroke-width="3.5"/>
+ <rect fill="&stroke_color;" height="9.702" width="14.063" x="19.43" y="16.902"/>
+
+ <line fill="&fill_color;" stroke="&stroke_color;" stroke-linecap="round" stroke-linejoin="round" stroke-width="3.5" x1="39.286" x2="39.286" y1="11.28" y2="1.993"/>
+ </g>
+</g></svg>
diff --git a/shell/data/icons/module-network.svg b/shell/data/icons/module-network.svg
new file mode 100644
index 0000000..a750a38
--- /dev/null
+++ b/shell/data/icons/module-network.svg
@@ -0,0 +1,32 @@
+<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [
+ <!ENTITY stroke_color "#666666">
+ <!ENTITY fill_color "#ffffff">
+]><svg enable-background="new 0 0 55 55" height="55px" id="Layer_1" version="1.1" viewBox="0 0 55 55" width="55px" x="0px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"><g display="block" id="module-network">
+ <g display="inline">
+ <defs>
+ <path d="M14.897,34.339c0-10.144,8.224-18.367,18.367-18.367c3.929,0,7.562,1.244,10.549,3.345V0H0v43.812h17.55 C15.877,41.044,14.897,37.81,14.897,34.339z" id="SVGID_1_"/>
+ </defs>
+ <clipPath id="SVGID_2_">
+ <use overflow="visible" xlink:href="#SVGID_1_"/>
+ </clipPath>
+ <g clip-path="url(#SVGID_2_)">
+
+ <circle cx="21.47" cy="21.073" fill="none" r="18.368" stroke="&fill_color;" stroke-linecap="round" stroke-linejoin="round" stroke-width="4"/>
+
+ <circle cx="21.469" cy="21.073" fill="none" r="10.476" stroke="&fill_color;" stroke-linecap="round" stroke-linejoin="round" stroke-width="4"/>
+ <circle cx="21.469" cy="21.073" fill="&fill_color;" r="3.966"/>
+ </g>
+ </g>
+ <g display="inline">
+ <g>
+ <defs>
+ <rect height="18.367" id="SVGID_3_" width="36.736" x="14.897" y="34.339"/>
+ </defs>
+ <clipPath id="SVGID_4_">
+ <use overflow="visible" xlink:href="#SVGID_3_"/>
+ </clipPath>
+ <circle clip-path="url(#SVGID_4_)" cx="33.265" cy="34.339" fill="&fill_color;" r="18.368"/>
+ </g>
+ <circle cx="33.265" cy="34.339" fill="none" r="18.368" stroke="&fill_color;" stroke-width="3.5"/>
+ </g>
+</g></svg> \ No newline at end of file
diff --git a/shell/data/icons/module-power.svg b/shell/data/icons/module-power.svg
new file mode 100644
index 0000000..4db57ea
--- /dev/null
+++ b/shell/data/icons/module-power.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [
+ <!ENTITY stroke_color "#666666">
+ <!ENTITY fill_color "#ffffff">
+]><svg enable-background="new 0 0 55 55" height="55px" id="Layer_1" version="1.1" viewBox="0 0 55 55" width="55px" x="0px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"><g display="block" id="module-energy">
+ <g display="inline" id="device-battery-charging-050">
+ <g>
+ <g>
+ <path d="M2.215,15.271v24.833l33.094,0.002V15.271H2.215z M20.817,26.089l-6.599,9.903l2.339-8.285h-2.472 l1.85-7.121h5.411l-2.773,5.503H20.817z" fill="&fill_color;"/>
+ </g>
+ </g>
+ <polygon fill="none" points="2.215,39.896 47.602,39.896 47.602,34.607 52.611,34.607 52.611,20.565 47.602,20.565 47.602,15.271 2.215,15.271 " stroke="&fill_color;" stroke-linecap="round" stroke-linejoin="round" stroke-width="3.5"/>
+ </g>
+</g></svg> \ No newline at end of file
diff --git a/shell/data/icons/module-updater.svg b/shell/data/icons/module-updater.svg
new file mode 100644
index 0000000..a521f61
--- /dev/null
+++ b/shell/data/icons/module-updater.svg
@@ -0,0 +1,16 @@
+<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [
+ <!ENTITY stroke_color "#000">
+ <!ENTITY fill_color "#fff">
+]><svg height="55px" viewBox="0 0 55 55" width="55px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <g>
+ <g>
+ <path d="M 31.752 7.088 C 41.935 9.118 49.609 18.107 49.609 28.887 C 49.609 41.173 39.65 51.129 27.374 51.129 C 15.086 51.129 5.133 41.173 5.133 28.887 C 5.133 19.648 10.768 11.723 18.801 8.365 " fill="none" stroke="&fill_color;" stroke-linecap="round" stroke-linejoin="round" stroke-width="3.5" />
+ <path d="M 36.134 15.154 L 31.752 7.088 L 40.439 4.13 " fill="none" stroke="&fill_color;" stroke-linecap="round" stroke-linejoin="round" stroke-width="3.5" />
+ </g>
+ <g>
+ <g>
+ <path d="M 38.57 25.886 C 37.597 25.886 36.718 26.282 36.082 26.918 L 31.021 31.979 L 31.02 17.022 C 31.018 16.124 30.675 15.221 29.99 14.533 C 28.613 13.159 26.383 13.159 25.01 14.533 C 24.321 15.222 23.98 16.122 23.979 17.023 L 23.977 31.978 L 18.918 26.918 C 18.281 26.281 17.4 25.886 16.429 25.887 C 14.484 25.885 12.908 27.465 12.908 29.408 C 12.907 30.381 13.304 31.263 13.936 31.899 L 27.5 45.463 L 41.062 31.898 C 41.697 31.262 42.09 30.382 42.093 29.41 C 42.094 27.463 40.516 25.885 38.57 25.886 Z " fill="&fill_color;" stroke="none" />
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/shell/data/kbdconfig b/shell/data/kbdconfig
new file mode 100644
index 0000000..03c288a
--- /dev/null
+++ b/shell/data/kbdconfig
@@ -0,0 +1,3 @@
+# This is the sugar keyboard configuration for matchbox
+
+<Alt>return=fullscreen
diff --git a/shell/data/mime.defaults b/shell/data/mime.defaults
new file mode 100644
index 0000000..f07e22c
--- /dev/null
+++ b/shell/data/mime.defaults
@@ -0,0 +1,24 @@
+# MIME Activity service name
+
+application/pdf org.laptop.sugar.ReadActivity
+
+text/rtf org.laptop.AbiWordActivity
+text/plain org.laptop.AbiWordActivity
+application/x-abiword org.laptop.AbiWordActivity
+text/x-xml-abiword org.laptop.AbiWordActivity
+application/msword org.laptop.AbiWordActivity
+application/rtf org.laptop.AbiWordActivity
+
+image/png org.laptop.ImageViewerActivity
+image/gif org.laptop.ImageViewerActivity
+image/jpeg org.laptop.ImageViewerActivity
+text/html org.laptop.WebActivity
+application/xhtml+xml org.laptop.WebActivity
+application/xml org.laptop.WebActivity
+application/rss+xml org.laptop.WebActivity
+application/ogg org.laptop.sugar.Jukebox
+audio/ogg org.laptop.sugar.Jukebox
+video/ogg org.laptop.sugar.Jukebox
+
+text/x-python org.laptop.PippyActivity
+
diff --git a/shell/data/nm-user-settings.conf b/shell/data/nm-user-settings.conf
new file mode 100644
index 0000000..16e71e4
--- /dev/null
+++ b/shell/data/nm-user-settings.conf
@@ -0,0 +1,28 @@
+<!DOCTYPE busconfig PUBLIC
+ "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+<busconfig>
+ <policy user="root">
+ <allow own="org.freedesktop.NetworkManagerUserSettings"/>
+
+ <allow send_destination="org.freedesktop.NetworkManagerUserSettings"/>
+ </policy>
+ <policy at_console="true">
+ <allow own="org.freedesktop.NetworkManagerUserSettings"/>
+
+ <allow send_destination="org.freedesktop.NetworkManagerUserSettings"/>
+
+ <!-- Only root can get secrets -->
+ <deny send_destination="org.freedesktop.NetworkManagerUserSettings"
+ send_interface="org.freedesktop.NetworkManagerSettings.Connection.Secrets"/>
+ </policy>
+ <policy context="default">
+ <deny send_destination="org.freedesktop.NetworkManagerUserSettings"/>
+
+ <allow send_destination="org.freedesktop.NetworkManagerUserSettings"
+ send_interface="org.freedesktop.DBus.Introspectable"/>
+ </policy>
+
+ <limit name="max_replies_per_connection">512</limit>
+</busconfig>
+
diff --git a/shell/data/sugar-emulator.desktop.in b/shell/data/sugar-emulator.desktop.in
new file mode 100644
index 0000000..6247bd7
--- /dev/null
+++ b/shell/data/sugar-emulator.desktop.in
@@ -0,0 +1,10 @@
+[Desktop Entry]
+Encoding=UTF-8
+Name=Sugar
+GenericName=Sugar Emulator
+Comment=The emulator for the Sugar Desktop Environment
+Exec=@prefix@/bin/sugar-emulator
+Terminal=false
+Type=Application
+Icon=sugar-xo
+Categories=Education;Teaching;
diff --git a/shell/data/sugar-xo.svg b/shell/data/sugar-xo.svg
new file mode 100644
index 0000000..b673179
--- /dev/null
+++ b/shell/data/sugar-xo.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [
+ <!ENTITY stroke_color "#010101">
+ <!ENTITY fill_color "#FFFFFF">
+]><svg enable-background="new 0 0 55 55" height="55px" version="1.1" viewBox="0 0 55 55" width="55px" x="0px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"><g display="block" id="stock-xo_1_">
+ <path d="M33.233,35.1l10.102,10.1c0.752,0.75,1.217,1.783,1.217,2.932 c0,2.287-1.855,4.143-4.146,4.143c-1.145,0-2.178-0.463-2.932-1.211L27.372,40.961l-10.1,10.1c-0.75,0.75-1.787,1.211-2.934,1.211 c-2.284,0-4.143-1.854-4.143-4.141c0-1.146,0.465-2.184,1.212-2.934l10.104-10.102L11.409,24.995 c-0.747-0.748-1.212-1.785-1.212-2.93c0-2.289,1.854-4.146,4.146-4.146c1.143,0,2.18,0.465,2.93,1.214l10.099,10.102l10.102-10.103 c0.754-0.749,1.787-1.214,2.934-1.214c2.289,0,4.146,1.856,4.146,4.145c0,1.146-0.467,2.18-1.217,2.932L33.233,35.1z" fill="&fill_color;" stroke="&stroke_color;" stroke-width="3.5"/>
+ <circle cx="27.371" cy="10.849" fill="&fill_color;" r="8.122" stroke="&stroke_color;" stroke-width="3.5"/>
+</g></svg> \ No newline at end of file
diff --git a/shell/data/sugar.desktop b/shell/data/sugar.desktop
new file mode 100644
index 0000000..2d7133f
--- /dev/null
+++ b/shell/data/sugar.desktop
@@ -0,0 +1,6 @@
+[Desktop Entry]
+Encoding=UTF-8
+Name=Sugar
+GenericName=Sugar
+Exec=sugar
+Type=Application
diff --git a/shell/data/sugar.schemas.in b/shell/data/sugar.schemas.in
new file mode 100644
index 0000000..2e6b820
--- /dev/null
+++ b/shell/data/sugar.schemas.in
@@ -0,0 +1,347 @@
+<?xml version="1.0"?>
+<gconfschemafile>
+ <schemalist>
+ <schema>
+ <key>/schemas/desktop/sugar/user/nick</key>
+ <applyto>/desktop/sugar/user/nick</applyto>
+ <owner>sugar</owner>
+ <type>string</type>
+ <default></default>
+ <locale name="C">
+ <short>User Name</short>
+ <long>User name that is used throughout the desktop.</long>
+ </locale>
+ </schema>
+ <schema>
+ <key>/schemas/desktop/sugar/user/default_nick</key>
+ <applyto>/desktop/sugar/user/default_nick</applyto>
+ <owner>sugar</owner>
+ <type>string</type>
+ <default>system</default>
+ <locale name="C">
+ <short>Default nick</short>
+ <long>"disabled" to ask nick on initialization; "system" to reuse UNIX account long name.</long>
+ </locale>
+ </schema>
+ <schema>
+ <key>/schemas/desktop/sugar/user/color</key>
+ <applyto>/desktop/sugar/user/color</applyto>
+ <owner>sugar</owner>
+ <type>string</type>
+ <default></default>
+ <locale name="C">
+ <short>User Color</short>
+ <long>Color for the XO icon that is used throughout the
+ desktop. The string is composed of the stroke color and fill
+ color, format is that of rbg colors. Example: #AC32FF,#9A5200
+ </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/desktop/sugar/sound/volume</key>
+ <applyto>/desktop/sugar/sound/volume</applyto>
+ <owner>sugar</owner>
+ <type>int</type>
+ <default>80</default>
+ <locale name="C">
+ <short>Volume Level</short>
+ <long>Volume level for the sound device.</long>
+ </locale>
+ </schema>
+ <schema>
+ <key>/schemas/desktop/sugar/sound/mute</key>
+ <applyto>/desktop/sugar/sound/mute</applyto>
+ <owner>sugar</owner>
+ <type>bool</type>
+ <default>false</default>
+ <locale name="C">
+ <short>Sound Muted</short>
+ <long>Setting for muting the sound device.</long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/desktop/sugar/backup_url</key>
+ <applyto>/desktop/sugar/backup_url</applyto>
+ <owner>sugar</owner>
+ <type>string</type>
+ <default></default>
+ <locale name="C">
+ <short>Backup URL</short>
+ <long>Url where the backup is saved to.</long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/desktop/sugar/date/timezone</key>
+ <applyto>/desktop/sugar/date/timezone</applyto>
+ <owner>sugar</owner>
+ <type>string</type>
+ <default>UTC</default>
+ <locale name="C">
+ <short>Timezone</short>
+ <long>Timezone setting for the system.</long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/desktop/sugar/desktop/favorites_layout</key>
+ <applyto>/desktop/sugar/desktop/favorites_layout</applyto>
+ <owner>sugar</owner>
+ <type>string</type>
+ <default>ring-layout</default>
+ <locale name="C">
+ <short>Favorites Layout</short>
+ <long>Layout of the favorites view.</long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/desktop/sugar/desktop/favorites_mode</key>
+ <applyto>/desktop/sugar/desktop/favorites_mode</applyto>
+ <owner>sugar</owner>
+ <type>bool</type>
+ <default>false</default>
+ <locale name="C">
+ <short>Favorites resume mode</short>
+ <long>When in resume mode, clicking on a favorite icon will cause the last entry for that activity to be resumed.</long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/desktop/sugar/frame/edge_delay</key>
+ <applyto>/desktop/sugar/frame/edge_delay</applyto>
+ <owner>sugar</owner>
+ <type>int</type>
+ <default>1000</default>
+ <locale name="C">
+ <short>Edge Delay</short>
+ <long>Delay for the activation of the frame using the edges.</long>
+ </locale>
+ </schema>
+ <schema>
+ <key>/schemas/desktop/sugar/frame/corner_delay</key>
+ <applyto>/desktop/sugar/frame/corner_delay</applyto>
+ <owner>sugar</owner>
+ <type>int</type>
+ <default>0</default>
+ <locale name="C">
+ <short>Corner Delay</short>
+ <long>Delay for the activation of the frame using the corners.</long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/desktop/sugar/collaboration/jabber_server</key>
+ <applyto>/desktop/sugar/collaboration/jabber_server</applyto>
+ <owner>sugar</owner>
+ <type>string</type>
+ <default>jabber.sugarlabs.org</default>
+ <locale name="C">
+ <short>Jabber Server</short>
+ <long>Url of the jabber server to use.</long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/desktop/sugar/power/automatic</key>
+ <applyto>/desktop/sugar/power/automatic</applyto>
+ <owner>sugar</owner>
+ <type>bool</type>
+ <default>false</default>
+ <locale name="C">
+ <short>Power Automatic</short>
+ <long>Power Automatic.</long>
+ </locale>
+ </schema>
+ <schema>
+ <key>/schemas/desktop/sugar/power/extreme</key>
+ <applyto>/desktop/sugar/power/extreme</applyto>
+ <owner>sugar</owner>
+ <type>bool</type>
+ <default>false</default>
+ <locale name="C">
+ <short>Power Extreme</short>
+ <long>Power Extreme. </long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/desktop/sugar/collaboration/publish_gadget</key>
+ <applyto>/desktop/sugar/collaboration/publish_gadget</applyto>
+ <owner>sugar</owner>
+ <type>bool</type>
+ <default>true</default>
+ <locale name="C">
+ <short>Publish to Gadget</short>
+ <long>If TRUE, Sugar will make us searchable for the other users of the Jabber server.</long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/desktop/sugar/show_logout</key>
+ <applyto>/desktop/sugar/show_logout</applyto>
+ <owner>sugar</owner>
+ <type>bool</type>
+ <default>true</default>
+ <locale name="C">
+ <short>Show Log out</short>
+ <long>If TRUE, Sugar will show a "Log out" option.</long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/desktop/sugar/peripherals/keyboard/layouts</key>
+ <applyto>/desktop/sugar/peripherals/keyboard/layouts</applyto>
+ <owner>sugar</owner>
+ <type>list</type>
+ <list_type>string</list_type>
+ <locale name="C">
+ <short>Keyboard layouts</short>
+ <long>List of keyboard layouts. Each entry should be in the form layout(variant)</long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/desktop/sugar/peripherals/keyboard/options</key>
+ <applyto>/desktop/sugar/peripherals/keyboard/options</applyto>
+ <owner>sugar</owner>
+ <type>list</type>
+ <list_type>string</list_type>
+ <locale name="C">
+ <short>Keyboard options</short>
+ <long>List of keyboard options.</long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/desktop/sugar/peripherals/keyboard/model</key>
+ <applyto>/desktop/sugar/peripherals/keyboard/model</applyto>
+ <owner>sugar</owner>
+ <type>string</type>
+ <default>evdev</default>
+ <locale name="C">
+ <short>Keyboard model</short>
+ <long>The keyboard model to be used</long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/desktop/sugar/font/default_face</key>
+ <applyto>/desktop/sugar/font/default_face</applyto>
+ <owner>sugar</owner>
+ <type>string</type>
+ <default>Sans Serif</default>
+ <locale name="C">
+ <short>Default font face</short>
+ <long>Font face that is used throughout the desktop.</long>
+ </locale>
+ </schema>
+ <schema>
+ <key>/schemas/desktop/sugar/font/default_size</key>
+ <applyto>/desktop/sugar/font/default_size</applyto>
+ <owner>sugar</owner>
+ <type>float</type>
+ <default>10</default>
+ <locale name="C">
+ <short>Default font size</short>
+ <long>Font size that is used throughout the desktop.</long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/desktop/sugar/i18n/langpackdir</key>
+ <applyto>/desktop/sugar/i18n/langpackdir</applyto>
+ <owner>sugar</owner>
+ <type>string</type>
+ <default></default>
+ <locale name="C">
+ <short>Directory to search for translations</short>
+ <long>Additional directories which can contain updated translations.</long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/desktop/sugar/network/gsm/username</key>
+ <applyto>/desktop/sugar/network/gsm/username</applyto>
+ <owner>sugar</owner>
+ <type>string</type>
+ <default></default>
+ <locale name="C">
+ <short>GSM network username</short>
+ <long>GSM network username configuration</long>
+ </locale>
+ </schema>
+ <schema>
+ <key>/schemas/desktop/sugar/network/gsm/password</key>
+ <applyto>/desktop/sugar/network/gsm/password</applyto>
+ <owner>sugar</owner>
+ <type>string</type>
+ <default></default>
+ <locale name="C">
+ <short>GSM network password</short>
+ <long>GSM network password configuration</long>
+ </locale>
+ </schema>
+ <schema>
+ <key>/schemas/desktop/sugar/network/gsm/number</key>
+ <applyto>/desktop/sugar/network/gsm/number</applyto>
+ <owner>sugar</owner>
+ <type>string</type>
+ <default>*99#</default>
+ <locale name="C">
+ <short>GSM network number</short>
+ <long>GSM network telephone number configuration</long>
+ </locale>
+ </schema>
+ <schema>
+ <key>/schemas/desktop/sugar/network/gsm/apn</key>
+ <applyto>/desktop/sugar/network/gsm/apn</applyto>
+ <owner>sugar</owner>
+ <type>string</type>
+ <default></default>
+ <locale name="C">
+ <short>GSM network APN</short>
+ <long>GSM network access point name configuration</long>
+ </locale>
+ </schema>
+ <schema>
+ <key>/schemas/desktop/sugar/network/gsm/pin</key>
+ <applyto>/desktop/sugar/network/gsm/pin</applyto>
+ <owner>sugar</owner>
+ <type>string</type>
+ <default></default>
+ <locale name="C">
+ <short>GSM network PIN</short>
+ <long>GSM network personal identification number configuration</long>
+ </locale>
+ </schema>
+ <schema>
+ <key>/schemas/desktop/sugar/network/gsm/puk</key>
+ <applyto>/desktop/sugar/network/gsm/puk</applyto>
+ <owner>sugar</owner>
+ <type>string</type>
+ <default></default>
+ <locale name="C">
+ <short>GSM network PUK</short>
+ <long>GSM network personal unlock key configuration</long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/desktop/sugar/network/adhoc</key>
+ <applyto>/desktop/sugar/network/adhoc</applyto>
+ <owner>sugar</owner>
+ <type>bool</type>
+ <default>true</default>
+ <locale name="C">
+ <short>Show Sugar Ad-hoc networks</short>
+ <long>If TRUE, Sugar will show default Ad-hoc networks for
+ channel 1,6 and 11. If Sugar sees no "known" network when
+ it starts, it does autoconnect to an Ad-hoc network.</long>
+ </locale>
+ </schema>
+
+ </schemalist>
+</gconfschemafile>
diff --git a/shell/data/sugar.xml.in b/shell/data/sugar.xml.in
new file mode 100644
index 0000000..6a7f253
--- /dev/null
+++ b/shell/data/sugar.xml.in
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
+ <mime-type type="application/vnd.olpc-sugar">
+ <_comment>Sugar activity bundle</_comment>
+ <glob pattern="*.xo"/>
+ </mime-type>
+ <mime-type type="application/vnd.olpc-content">
+ <_comment>Sugar content bundle</_comment>
+ <glob pattern="*.xol"/>
+ </mime-type>
+</mime-info> \ No newline at end of file
diff --git a/shell/docs/GPL-C.txt b/shell/docs/GPL-C.txt
new file mode 100644
index 0000000..aa909b4
--- /dev/null
+++ b/shell/docs/GPL-C.txt
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2006, Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
diff --git a/shell/docs/GPL-python.txt b/shell/docs/GPL-python.txt
new file mode 100644
index 0000000..96e81d1
--- /dev/null
+++ b/shell/docs/GPL-python.txt
@@ -0,0 +1,16 @@
+# Copyright (C) 2006, Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
diff --git a/shell/docs/LGPL-C.txt b/shell/docs/LGPL-C.txt
new file mode 100644
index 0000000..88798f8
--- /dev/null
+++ b/shell/docs/LGPL-C.txt
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2006, Red Hat, Inc.
+ *
+ * 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.
+ */
diff --git a/shell/docs/LGPL-python.txt b/shell/docs/LGPL-python.txt
new file mode 100644
index 0000000..1db1ea4
--- /dev/null
+++ b/shell/docs/LGPL-python.txt
@@ -0,0 +1,17 @@
+# Copyright (C) 2006, Red Hat, Inc.
+#
+# 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.
+
diff --git a/shell/docs/controls.txt b/shell/docs/controls.txt
new file mode 100644
index 0000000..52591ea
--- /dev/null
+++ b/shell/docs/controls.txt
@@ -0,0 +1,199 @@
+Colors
+
+Black - palettes, popups
+Toolbar Grey #262626 - toolbars, expanded palette
+Button Grey #808080 - buttons
+Selection Grey #A6A6A6 - selection, expanded panels
+Panel Grey #C0C0C0 - panel, desktop
+Text field Grey #E5E5E5 - text field background
+White - pressed states and multiline text areas
+
+States
+
+Default - gtk.STATE_NORMAL
+Focused - gtk.STATE_SELECTED
+Pressed - gtk.STATE_ACTIVE
+Hover - gtk.STATE_PRELIGHT
+Inactive - gtk.STATE_INSENSITIVE
+
+gtk.Button
+
+* The image should work the same of the image button
+* Need to write a theme to match the visual style
+* Cancel should never be default because you can always activate it with Esc
+* Radius should be 1/2 of the control height
+* Write a list of stock icons people should use and replace them in the theme to match our visual style
+
+sugar.Icon
+
+* Used in canvas-like views so probably an Hippo item.
+* Svg Only.
+* It should support xo colors easily.
+* Rollovers with a focus mark.
+
+sugar.IconButton
+
+* Support for SVG and png.
+* Icons should be grey scale. But might be coloured with the XO colors (svg only)
+* Size of the button is 75 pixels, size of the icon canvas is 55 and suggested icon size is around 45.
+* States, defaults:
+ Hover : Black
+ Pressed : Rounded rectangle 61 pixels, 10 pixels of radius, filled in selection grey
+ Focused : Rounded rectangle 61 pixels, 10 pixels of radius, stroked in white 2.25 points
+ Inactive. Fallbacks if no inactive icon is specified.
+ Svg: Remove the fill and render the stroke in button grey
+ Png: just do some effect on the pixbuf, which also work for grey icons
+* You can set an icon for each states which replace the default except for the Hover state of buttons which has rollover.
+* "palette" boolean property. If true show an arrow active immediately on click (but also on hover)
+
+sugar.ToolButton (support for rollovers)
+
+* Contains IconButton
+* There is no palette but a tooltip.
+* Normal: Button grey rounded filled rectangle
+* Inactive: Button grey rounded stroked rectangle
+
+sugar.ToggleIconButton
+
+* Toggled should be like Pressed
+* Inconsistent should be the same of Default (the action depend on the cases)
+* Pressed state and Toggled state is Selection grey
+
+sugar.ToolIconButton
+
+* Contains a ToggleIconButton
+
+gtk.CheckButton
+
+* Match the visual design, shoul be possible with just theme changes
+
+gtk.RadioButton
+
+* Exactly like CheckButton just a different indicator
+
+gtk.OptionMenu
+
+* Match the visual style. Hopefully only theme changes.
+* Add the scroll thing.
+* Groups. Either by a normal separator or a titled separator.
+* Optional support for showing just the icon from the menu (maybe, low priority)
+* Allow fixed sizing of the "button" and ellipsize the label
+
+sugar.Entry
+
+* Support for packing icons before and after the entry. Extend gtk.Entry.
+* Activate/Cancel functionality.
+ Two buttons at the end to the entry and key bindings (Esc and Enter). They are visible only when there are changes.
+ The icons appear only when the field is focused and the content is changed since it gained focus.
+ When hitting escape revert and select all the text.
+
+gtk.ComboxBox
+
+* We miss accept/cancel functionality. Probably patch gtk to allow to replace the entry in the combo box with sugar.Entry.
+
+sugar.SearchEntry
+
+* Use sugar.Entry
+* Search button on the left. Clicking should focus the entry.
+* Cancel button (Esc) on the right, always visible when there is text in the entry. Clicking it will clear the text and focus the textfield.
+* Activate button (Enter) on the right displayed when the content of the text field changed from the last focus or activation.
+* While activating:
+ the Activate button becomes a Spinner.
+ clicking the close button also cancel the search.
+* When activation is completed:
+ The spinner goes away.
+ We *don't* clear the entry but we select the text.
+* Search can either be incremental or on activation. For incremental there is no Accept button. start_spinning and stop_spinning to control the spin icon. start would only spin for an amount of time decided by the widget itself (and documented).
+* The suggestions list is provided by the application. Need to figure out which api to use, either model or signal based.
+* Default implementation of suggestions which automatically save the latest searches.
+
+sugar.DateSelector
+sugar.DateComboBox (lower priority)
+
+* Pluggable calendar implementation to support different kind of calendars (localization).
+* Might reuse gtk.Calendar. We should unify month/year selectors and accellerate the movement gradually.
+
+gtk.SpinButton
+
+* Make it match the visual design, hopefully just theme changes
+
+sugar.ToolItem
+
+* Optional label, either text or icon
+* Used for example to have a label near a SpinButton. Clicking on the label should focus the spin button.
+
+gtk.ProgressBar
+
+* Make it match the visual design, hopefully just theme changes.
+* For determinate progress bars should we always pulse to show that there is activity (power consumption? necessary feedback?)
+* Do not use text inside the progress bar
+
+sugar.Spinner
+
+* pulse() call to keep it running with a timeout
+* stop()
+
+gtk.Range (or sugar.Slider?)
+
+* Property to show the fill in white color, probably default on.
+* Draw the discrete steps.
+* For colored sliders, subclass gtk.Range and add a gradient.
+
+sugar.LevelIndicator
+
+* Set the number of blocks
+* Set the level as percentage
+* Property for discrete or not
+* We can probably use a GtkAdjustment for most of the above.Rollovers
+
+
+gtk.TextView
+
+gtk.ScrolledWindow
+
+* Theme it to match the visual.
+
+sugar.ScrolledWindow
+
+* Support for markers. Line as default and optional support for other shapes (star for bookmarks, circles for xos...). Generic way of add marks and keep them updated (observer?)
+
+gtk.Expander
+
+gtk.Separator
+
+sugar.GroupBox
+
+* just a container
+* set_title and set_title_widget (checkbox, radiobutton...)
+* different color and separator under title
+
+gtk.TreeView
+
+gtk.Notebook
+
+* Expand to fill the whole space by default but property to turn it off
+* Switching tabs with the little arrows should page
+
+Palettes in ToolIconButton, IconButton
+* Inmediately on rollover, show the black background.
+* After a very short delay, show the primary state (name of the action and key shortcut).
+* After a bigger delay, show the popup secondary state.
+* Could be animated.
+* Menu Items would go on the top and then the free-form rollover content.
+* The popup would be a gtk.Window that contains a Label, a MenuShell, an hippo.Canvas (or whatever) and finally a button bar (OK/Cancel).
+* The popup will have a setPrimaryState(label, accelerator) method. For action buttons would be a MenuItem, for the others would only be a Label.
+* The primary state should already have the same width as the secondary state and the expandable areas.
+* Primary states appear and disappear automatically (with a short delay). A click outside makes it disappear instantly.
+* Secondary states appear after a delay, or with a single click on the icon.
+* Secondary disappears with the esc key, clicking outside the popup or clicking on a button inside.
+
+Toolbox
+* When an activity opens, the activity tab should be opened and the focus on the activity title.
+* We must provide an activity tab in the toolbox and would be good to also provide an standard Edit tab.
+
+Grab key
+* We probably will need the grab mode.
+* Highlight the scrollbar in the view the pointer is (the view that will scroll when moving the pointer).
+
+Clipboard
+* Window manager to handle in an invisible window in every corner and forward the events when they are not in the corner, or use XEvIE (X Event Interception Extension).
diff --git a/shell/docs/design.txt b/shell/docs/design.txt
new file mode 100644
index 0000000..37061af
--- /dev/null
+++ b/shell/docs/design.txt
@@ -0,0 +1,126 @@
+= Frame =
+
+== Activation and deactivation ==
+
+* Immediately access the frame by hitting any corner pixel (the exact corner point)
+* Hitting any of the screen edges activate the frame after 0.5s delay
+* Pressing and holding the frame key activate the frame until the key is released.
+* Pressing the frame key momentarily toggle the frame. To deactivate it another key press is necessary.
+
+= IRC logs =
+
+Frame
+
+eliason marcopg: First, you can immediately access the frame by hitting any corner pixel.
+dcbw marcopg: I think that's the issue, yes
+eliason Second, you can activate it from any edge, but there is a half second delay. In addition to the delay, the timer on the delay only ticks when the mouse is on an edge pixel and also below some threshold velocity, so if the mouse is moving it will not show up.
+eliason marcopg: Third, there will be a frame key. Pressing and holding this key will invoke the frame until the key is released. Pressing this key momentarily, however, will toggle the frame and keep it in view until the key is pressed again.
+eliason marcopg: All of these things are implemented in Flash (The frame key is 8, I think...), with the exception of the mouse velocity threshold on the edges.
+eliason marcopg: The only other minor usability issue I could see is adding a hit area in the corners. That is, once the frame is out, have an invisible triangle in each corner that is still considered part of the frame, so that rolling out of the frame to get from one edge to another doesn't hide the frame, by accident. In fact, doing this would make the delay on hide unnecessary.
+eliason marcopg: Oh, and other important edges case to think about on the frame: 1) When someone is dragginan object (XO, file, image, etc) the frame should come out prematurely and without delay (maybe once within a large grid cell of the edge) and highlight to indicate where the object could be dropped. 2) When the search field is active, the frame should remain out even if the mouse isn't over it. Only when the search is cleared should it hide again.
+eliason marcopg: The rollover states are as follows: 1) Immediate rollover is a black square in the frame cell itself. 2) Very shortly thereafter (about 1/4 sec) Is the Primary information label and 3) a bit longer after that (about 1/2 sec) the extended panel appears with additional info.
+
+Menu
+
+eliason marcopg: Deactivation is instantaneous at the moment, but again, based on testing with the software we may want to add a very short delay. Also, we may want again to have a invisible triangles between the grid cell in the frame and the extended panel.
+eliason Delay between mouse rollover and showing black square: 0
+eliason Delay before primary info animation begins: 1/10 sec
+eliason Duration of primary info extension animation: 1/5 sec
+eliason Delay from end of primary info animation until secondary info: 1/2 sec
+Duration of secondary info animation: 1/5 sec
+
+Text layout
+
+eliason marcopg: Well, first of all, in the latest screens which we haven't sent out yet, the primary info rollover is designed to fit the text to the nearest microgrid, regardless of the size of the secondary info panel. The second animation segment will both extend the width and height to fit.
+eliason marcopg: As far as cutoff goes, I think that's a reasonable solution. We should allow the primary info text to be as long as the secondary info panel, and beyond that an ellipsis would be a good way to go, though hopefully designers are strongly encouraged NOT to let that happen.
+eliason marcopg: However, there may be cases when we don't want that to happen. I can think of one, which you haven't seen yet: "Invite with X X X X X." The new activity/invite rollover might have the XOs who you are implicitly inviting to start an activity with listed, and we couldn't cut that off (Though we CANget around it and list, say, a max of three people and then indicate that there are 5 more not shown, etc.)
+eliason marcopg: Right. I think that, in most cases, there should be some template for it. If the designer is creating a GUI for an application and making toolsets (such as the color chooser), then we know how big all of those are. Things like "just text" will mostly show up in OS applications like the clippings and such, so we can just pick a preferred size that shows enough text to be meaningful without compromising too much of the screen.
+
+Mesh view
+
+eliason marcopg: also important regarding the results: the fill color should be the background color, so they look like outlines, and the line color should be near enough to the background to make them less contrasty. Also, all XOs in the result set should be z-sorted to the front of their groups. That's key.
+eliason marcopg: The activity icons and XOs are treated separately, so it may be the case that several XOs in a group match the search but the activity their in doesn't, and they would be grayed out independently.
+eliason marcopg: yes, only the color change. Colored stuff is the result. We don't want to actually make things disappear.
+
+
+Mesh discussion
+
+eliason marcopg: Well, the terminology we're now using for the zoom level labels is: My Activities, My Friends, My Neighbors.
+eliason marcopg: Alright, true, so the working terminology (not the labels for the search field, but still used by all of us) is: home, friends, mesh
+eliason marcopg: If you treat each frieeliason marcopg: If you treat each friend or group as a node in a graph, and each edge in the graph as a spring, you can let the system reach an equilibrium using a basic physics simulation that will spread the nodes out evenly.nd or group as a node in a graph, and each edge in the graph as a spring, you can let the system reach an equilibrium using a basic physics simulation that will spread the nodes out evenly.
+eliason marcopg: If you treat each friend or group as a node in a graph, and each edge in the graph as a spring, you can let the system reach an equilibrium using a basic physics simulation that will spread the nodes out evenly.
+eliason marcopg: There's lots of info on this type of thing online. To do it right within a given box, you also have to add anchored springs at the corners and such to stretch the whole thing out to fill the space.
+eliason OK, so another basic idea is to keep a very rough downsampled grayscale image around....
+eliason The grayscale value would map to the number of XOs within the grid cell on screen.
+eliason marcopg: Then, when placing a new one you could just find the best place to place it , though now that I think about it that would probably require a simulated annealing algorithm or something else clever to do...
+marcopg eliason, do you mean macro cell here?
+marcopg i.e. every buddy would be placed in one macro cell
+eliason marcopg: Well, actually I guess you'd kind of want a blurred brayscale image, where each object placed represents a white circle, but with a gaussian blur so that it acts like a topographic map of the mesh.
+eliason marcopg: The white spots would be dense, like hills, the black spots would be void of people, like holes. Then to place a new one, you kind of roll a simulated marble around until you find a good hole to place it in...
+eliason marcopg: And, yet another idea, which could be used in conjunction with or independent of the above. You could simply place a small repulsive force between the objects, so that they push each other apart slightly. You could just place at random and have them adjust accordingly. This works very much like the spring model, though.
+eliason Of course, the drawback of the third idea is that it's n^2 in the number of groups on the mesh, but that number should never be that large.
+eliason The spring model is a little more complicated, but you only have to place springs between nearby objects, which reduces that somewhat.
+eliason marcopg: But actually, I think #3 might be the easiest to implement, and work just as well. It works nicely since it could still allow you to drag them around and the rest of the mesh would react naturally. In the spring model, you'd have to dynamically add and break spring connections when a node is moved around.
+marcopg eliason, mm was just thinking about this would interact with custom placed nodes...
+marcopg eliason, I never done something similar before so I will really need to play a bit with it...
+eliason marcopg: Also, #3 is more forgiving: In the spring model, placing one new node will move EVERY node in the mesh somewhat, if even just a small amount, since the whole system adjusts. With the repulsion force, only nearby nodes would be affected, except of course through a chain reaction...
+eliason marcopg: I'm sure we can find documentation. Basically, you calculate the distance between the two nodes (pythag) and if that distance is less than some threshold you compute the angle between them (atan2) and then you give each one a small repulsive acceleration, and update each frame by treating each with its own acceleration, velocity, and position.
+eliason marcopg: radiusConstant + (numXos*radiusScale) + ((index%3)*offsetScale) <-- that gets pretty close to what I'm working with now.
+eliason marcopg: One addition: When the groups are small, we don't want to add the snowflake effect, since they fit in neat geometric shapes around the ring. So we get....actually, here's the ugly line of code: radius = 25 + .3*(Math.max(participants.length-10,0)) + (i%3)*.5*(Math.max(participants.length-10,0));
+eliason marcopg: The added ugliness is a bit of code that zeros out the 2nd two quantities if there are less than 10 people in the group. You could just place them in an if block actually, but I'd done it this way in case I wanted to change the thresholds independently...eliason marcopg: The added ugliness is a bit of code that zeros out the 2nd two quantities if there are less than 10 people in the group. You could just place them in an if block actually, but I'd done it this way in case I wanted to change the thresholds independently...
+eliason marcopg: OK, cool. The animation is a simple easing algorithm: position += (targetPostition - position)*percentage.
+eliason marcopg: Well, that's a function of the radius and the angle computed from above. targetX = radius*cos(angle), targetY = radius*sin(angle), for each XO.
+marcopg eliason, aaah I see now
+
+eliason marcopg: The basic problem is that people are crossing an index that is a multiple of the mod, so everyone shifts in and out. What if, instead, we adjusted the indeces of every XO with an index of modValue*c greater than the XO that left, and subtract modValue from each of their indices?
+eliason With mod 3, we basically have 3 levels of the ring. This solution would just shift ONE of those rings couter-clockwise one XO position, leaving the other two rings intact as is. We move many fewer XOs than before, but we minimize the movement any given XO has to make by spreading it across the ring level instead of moving the last XO all the way across the circle...eliason marcopg: The basic problem is that people are crossing an index that is a multiple of the mod, so everyone shifts in and out. What if, instead, we adjusted the indeces of every XO with an index of modValue*c greater than the XO that left, and subtract modValue from each of their indices?
+eliason With mod 3, we basically have 3 levels of the ring. This solution would just shift ONE of those rings couter-clockwise one XO position, leaving the other two rings intact as is. We move many fewer XOs than before, but we minimize the movement any given XO has to make by spreading it across the ring level instead of moving the last XO all the way across the circle...
+eliason marcopg: The final detail of that, beyond shifting each index down by the mod value, is to shift the very last group of people in the ring down to fill all the holes, if you get my meaning...
+
+Activity startup feedback
+
+I agree we certainly need feedback for this sort of thing. For
+starters, the icon of the selected activity should immediately appear
+in the ring. Perhaps we can apply some small animation to this
+activity icon to indicate that it is starting up. Marco, could you
+use HSV for the icon color, and modulate the S(aturation) on a sin
+curve so the color pulses betwen, say, 0 and 192 until the activity
+starts, at which point it slides up to 255?
+
+Clipboard
+
+<marcopg> eliason, when should the frame be automatically showed? when
+starting the drag or when hitting the corner
+<marcopg> eliason, (the first one would be tricky to implement)
+<eliason> marcopg: Initially we wanted it to happen on drag, but after
+thinking about this more, it's a bad idea, since it would hide the toolbars,
+and some activities may have toolbars that support drag and drop also. As
+such, we probably just keep the hot corners as is, and perhaps implement the
+"warm edges" too, but only when dragging...
+<marcopg> eliason, which reminds me... the plan is to completely drop edges at
+this point? or at least give it a try? (for the not dragging case)
+<eliason> I still think it's worth trying, but I think the general consensus
+was that we should drop it. That's also the easier option, so for now, I
+guess it's fine.
+<marcopg> ok
+<marcopg> eliason, what should be the title of the clipboard rollover? (we are
+trying to get text objects right for now, but if you have general ideas...)
+<eliason> marcopg: Well, it would depend on what is in the clipboard. For
+instance, if I copy some text, it should say "text clipping", or if I copy an
+image it should say "picture clipping"
+<marcopg> eliason, so [name of the object] + clipping?
+<eliason> marcopg: If it is an activity object, it should be the name of the
+object i.e. "Eben's Shark Drawing", etc.
+<marcopg> oh ok
+<eliason> It's only a clipping if it is part of another larger context.
+<eliason> If the whole thing is an object itself, we use the object's name.
+<marcopg> eliason, I see now
+<marcopg> eliason, we want a preview of the objectm right? For text, should we
+show part of the text in the rollover?
+<eliason> marcopg: yeah, probably the first n characters of it, with an
+ellipsis at the end.
+<marcopg> eliason, on multiple lines I guess?
+<marcopg> something like, 4 lines of text on a 2 grid cells large menu, and
+ellipsize after that
+<marcopg> (the exact numbers doesn't matter, just the logic
+<eliason> marcopg: Right.
diff --git a/shell/docs/release_howto.txt b/shell/docs/release_howto.txt
new file mode 100644
index 0000000..db877e0
--- /dev/null
+++ b/shell/docs/release_howto.txt
@@ -0,0 +1,73 @@
+''' This is the release process of the sugar tarballs sugar(shell),
+sugar-toolkit and sugar-base described in a pytish way and
+instructions for sugar packagers
+'''
+
+# Release sugar tarballs
+
+for package in [sugar, sugar-toolkit, sugar-base, sugar-artwork]:
+ # Release a new version in git
+ Pull the latest sources.
+ Increase the version number in configure.ac
+ # this will create you a tarball and does a check if it builds fine
+ # e.g. it will check if all the files containing translations are
+ # in po/POTFILES.in
+ make distcheck
+
+ if that succeed:
+ # commit the change, log it as "Release [version_number]" (e.g. 0.79.1)
+ git commit -a
+ # Tag the release:
+ git tag v[version_number]
+ # Then push both the tag and the change:
+ git push --tags
+ git push
+ else:
+ break
+
+ # Upload the package
+ Upload the tarball to
+ shell.sugarlabs.org:/pub/sugarlabs/sources/sucrose/glucose/$name/$name-$version
+
+ # Verify the upload of the package
+ Check that the package has been uploaded fine: \
+ http://download.sugarlabs.org/sources/sucrose/glucose/$name/$name-$version
+
+# Package sugar for Fedora
+# - For announcements of the Sucrose release subscribe at the sugar-devel
+# mailing list; you can filter for the [ANNOUNCE] tag
+# - Uploaded tarballs can be found at:
+# glucose: http://download.sugarlabs.org/sources/sucrose/glucose/$name/$name-$version
+# fructose: http://download.sugarlabs.org/sources/sucrose/fructose/$name/$name-$version
+# more about the taxonomy: http://sugarlabs.org/go/Taxonomy
+
+# more info on fedora packaging:
+# http://fedoraproject.org/wiki/PackageMaintainers/UpdatingPackageHowTo
+# request permissions to contribute to the fedora package:
+# https://admin.fedoraproject.org/pkgdb/packages/name/[package]
+
+if not cvs_package:
+ # Get sugar from fedora cvs:
+ CVSROOT=:ext:erikos@cvs.fedoraproject.org:/cvs/pkgs cvs co [package]
+ cd cvs_package
+else:
+ cd cvs_package
+ cvs update
+
+cd current release
+make new-sources FILES="[tarball-created-with-make_distcheck]"
+
+# Change the version in the spec
+Bump the release number
+Edit the Changelog
+# verify your changes
+cvs diff -u
+make srpm
+
+make clog
+cvs commit -F clog
+
+make tag
+make build
+
+# Do the same for the other branches e.g. devel
diff --git a/shell/extensions/Makefile.am b/shell/extensions/Makefile.am
new file mode 100644
index 0000000..d4ab534
--- /dev/null
+++ b/shell/extensions/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = cpsection deviceicon globalkey
diff --git a/shell/extensions/cpsection/Makefile.am b/shell/extensions/cpsection/Makefile.am
new file mode 100644
index 0000000..a92b5dd
--- /dev/null
+++ b/shell/extensions/cpsection/Makefile.am
@@ -0,0 +1,5 @@
+SUBDIRS = aboutme aboutcomputer datetime frame keyboard language \
+ modemconfiguration network power updater
+
+sugardir = $(pkgdatadir)/extensions/cpsection
+sugar_PYTHON = __init__.py
diff --git a/src/carquinyol/__init__.py b/shell/extensions/cpsection/__init__.py
index e69de29..e69de29 100644
--- a/src/carquinyol/__init__.py
+++ b/shell/extensions/cpsection/__init__.py
diff --git a/shell/extensions/cpsection/aboutcomputer/Makefile.am b/shell/extensions/cpsection/aboutcomputer/Makefile.am
new file mode 100644
index 0000000..a3bdec8
--- /dev/null
+++ b/shell/extensions/cpsection/aboutcomputer/Makefile.am
@@ -0,0 +1,6 @@
+sugardir = $(pkgdatadir)/extensions/cpsection/aboutcomputer
+
+sugar_PYTHON = \
+ __init__.py \
+ model.py \
+ view.py
diff --git a/shell/extensions/cpsection/aboutcomputer/__init__.py b/shell/extensions/cpsection/aboutcomputer/__init__.py
new file mode 100644
index 0000000..ceb515a
--- /dev/null
+++ b/shell/extensions/cpsection/aboutcomputer/__init__.py
@@ -0,0 +1,22 @@
+# Copyright (C) 2008, OLPC
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from gettext import gettext as _
+
+CLASS = 'AboutComputer'
+ICON = 'module-about_my_computer'
+TITLE = _('About my Computer')
+
diff --git a/shell/extensions/cpsection/aboutcomputer/model.py b/shell/extensions/cpsection/aboutcomputer/model.py
new file mode 100644
index 0000000..898d79c
--- /dev/null
+++ b/shell/extensions/cpsection/aboutcomputer/model.py
@@ -0,0 +1,138 @@
+# Copyright (C) 2008 One Laptop Per Child
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+import os
+import logging
+import re
+import subprocess
+from gettext import gettext as _
+import errno
+
+from jarabe import config
+
+_logger = logging.getLogger('ControlPanel - AboutComputer')
+_not_available = _('Not available')
+
+def get_aboutcomputer():
+ msg = 'Serial Number: %s \nBuild Number: %s \nFirmware Number: %s \n' \
+ % (get_serial_number(), get_build_number(), get_firmware_number())
+ return msg
+
+def print_aboutcomputer():
+ print get_aboutcomputer()
+
+def get_serial_number():
+ serial_no = _read_file('/ofw/serial-number')
+ if serial_no is None:
+ serial_no = _not_available
+ return serial_no
+
+def print_serial_number():
+ serial_no = get_serial_number()
+ if serial_no is None:
+ serial_no = _not_available
+ print serial_no
+
+def get_build_number():
+ build_no = _read_file('/boot/olpc_build')
+
+ if build_no is None:
+ build_no = _read_file('/etc/redhat-release')
+
+ if build_no is None:
+ try:
+ popen = subprocess.Popen(['lsb_release', '-ds'],
+ stdout=subprocess.PIPE)
+ except OSError, e:
+ if e.errno != errno.ENOENT:
+ raise
+ else:
+ build_no, stderr_ = popen.communicate()
+
+ if build_no is None or not build_no:
+ build_no = _not_available
+
+ return build_no
+
+def print_build_number():
+ print get_build_number()
+
+def get_firmware_number():
+ firmware_no = _read_file('/ofw/openprom/model')
+ if firmware_no is None:
+ firmware_no = _not_available
+ else:
+ firmware_no = re.split(" +", firmware_no)
+ if len(firmware_no) == 3:
+ firmware_no = firmware_no[1]
+ return firmware_no
+
+def print_firmware_number():
+ print get_firmware_number()
+
+def get_wireless_firmware():
+ try:
+ info = subprocess.Popen(["/usr/sbin/ethtool", "-i", "eth0"],
+ stdout=subprocess.PIPE).stdout.readlines()
+ except OSError:
+ return _not_available
+ try:
+ wireless_firmware = [line for line in info
+ if line.startswith('firmware')][0].split()[1]
+ except IndexError:
+ wireless_firmware = _not_available
+ return wireless_firmware
+
+def print_wireless_firmware():
+ print get_wireless_firmware()
+
+def _read_file(path):
+ if os.access(path, os.R_OK) == 0:
+ return None
+
+ fd = open(path, 'r')
+ value = fd.read()
+ fd.close()
+ if value:
+ value = value.strip('\n')
+ return value
+ else:
+ _logger.debug('No information in file or directory: %s', path)
+ return None
+
+def get_license():
+ license_file = os.path.join(config.data_path, 'GPLv2')
+ lang = os.environ['LANG']
+ if lang.endswith("UTF-8"):
+ lang = lang[:-6]
+
+ try_file = license_file + "." + lang
+ if os.path.isfile(try_file):
+ license_file = try_file
+ else:
+ try_file = license_file + "." + lang.split("_")[0]
+ if os.path.isfile(try_file):
+ license_file = try_file
+
+ try:
+ fd = open(license_file)
+ # remove 0x0c page breaks which can't be rendered in text views
+ license_text = fd.read().replace('\x0c', '')
+ fd.close()
+ except IOError:
+ license_text = _not_available
+ return license_text
diff --git a/shell/extensions/cpsection/aboutcomputer/view.py b/shell/extensions/cpsection/aboutcomputer/view.py
new file mode 100644
index 0000000..b6ff43f
--- /dev/null
+++ b/shell/extensions/cpsection/aboutcomputer/view.py
@@ -0,0 +1,217 @@
+# coding=utf-8
+# Copyright (C) 2008, OLPC
+# Copyright (C) 2009 Simon Schampijer
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+from gettext import gettext as _
+
+import gtk
+
+from sugar.graphics import style
+
+from jarabe import config
+from jarabe.controlpanel.sectionview import SectionView
+
+class AboutComputer(SectionView):
+ def __init__(self, model, alerts=None):
+ SectionView.__init__(self)
+
+ self._model = model
+
+ self.set_border_width(style.DEFAULT_SPACING * 2)
+ self.set_spacing(style.DEFAULT_SPACING)
+
+ self._group = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
+
+ scrollwindow = gtk.ScrolledWindow()
+ scrollwindow.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+ self.pack_start(scrollwindow, expand=True)
+ scrollwindow.show()
+
+ self._vbox = gtk.VBox()
+ scrollwindow.add_with_viewport(self._vbox)
+ self._vbox.show()
+
+ if os.path.exists('/ofw'):
+ self._setup_identity()
+
+ self._setup_software()
+ self._setup_copyright()
+
+ def _setup_identity(self):
+ separator_identity = gtk.HSeparator()
+ self._vbox.pack_start(separator_identity, expand=False)
+ separator_identity.show()
+
+ label_identity = gtk.Label(_('Identity'))
+ label_identity.set_alignment(0, 0)
+ self._vbox.pack_start(label_identity, expand=False)
+ label_identity.show()
+ vbox_identity = gtk.VBox()
+ vbox_identity.set_border_width(style.DEFAULT_SPACING * 2)
+ vbox_identity.set_spacing(style.DEFAULT_SPACING)
+
+ box_identity = gtk.HBox(spacing=style.DEFAULT_SPACING)
+ label_serial = gtk.Label(_('Serial Number:'))
+ label_serial.set_alignment(1, 0)
+ label_serial.modify_fg(gtk.STATE_NORMAL,
+ style.COLOR_SELECTION_GREY.get_gdk_color())
+ box_identity.pack_start(label_serial, expand=False)
+ self._group.add_widget(label_serial)
+ label_serial.show()
+ label_serial_no = gtk.Label(self._model.get_serial_number())
+ label_serial_no.set_alignment(0, 0)
+ box_identity.pack_start(label_serial_no, expand=False)
+ label_serial_no.show()
+ vbox_identity.pack_start(box_identity, expand=False)
+ box_identity.show()
+
+ self._vbox.pack_start(vbox_identity, expand=False)
+ vbox_identity.show()
+
+ def _setup_software(self):
+ separator_software = gtk.HSeparator()
+ self._vbox.pack_start(separator_software, expand=False)
+ separator_software.show()
+
+ label_software = gtk.Label(_('Software'))
+ label_software.set_alignment(0, 0)
+ self._vbox.pack_start(label_software, expand=False)
+ label_software.show()
+ box_software = gtk.VBox()
+ box_software.set_border_width(style.DEFAULT_SPACING * 2)
+ box_software.set_spacing(style.DEFAULT_SPACING)
+
+ box_build = gtk.HBox(spacing=style.DEFAULT_SPACING)
+ label_build = gtk.Label(_('Build:'))
+ label_build.set_alignment(1, 0)
+ label_build.modify_fg(gtk.STATE_NORMAL,
+ style.COLOR_SELECTION_GREY.get_gdk_color())
+ box_build.pack_start(label_build, expand=False)
+ self._group.add_widget(label_build)
+ label_build.show()
+ label_build_no = gtk.Label(self._model.get_build_number())
+ label_build_no.set_alignment(0, 0)
+ box_build.pack_start(label_build_no, expand=False)
+ label_build_no.show()
+ box_software.pack_start(box_build, expand=False)
+ box_build.show()
+
+ box_sugar = gtk.HBox(spacing=style.DEFAULT_SPACING)
+ label_sugar = gtk.Label(_('Sugar:'))
+ label_sugar.set_alignment(1, 0)
+ label_sugar.modify_fg(gtk.STATE_NORMAL,
+ style.COLOR_SELECTION_GREY.get_gdk_color())
+ box_sugar.pack_start(label_sugar, expand=False)
+ self._group.add_widget(label_sugar)
+ label_sugar.show()
+ label_sugar_ver = gtk.Label(config.version)
+ label_sugar_ver.set_alignment(0, 0)
+ box_sugar.pack_start(label_sugar_ver, expand=False)
+ label_sugar_ver.show()
+ box_software.pack_start(box_sugar, expand=False)
+ box_sugar.show()
+
+ if os.path.exists('/ofw'):
+ box_firmware = gtk.HBox(spacing=style.DEFAULT_SPACING)
+ label_firmware = gtk.Label(_('Firmware:'))
+ label_firmware.set_alignment(1, 0)
+ label_firmware.modify_fg(gtk.STATE_NORMAL,
+ style.COLOR_SELECTION_GREY.get_gdk_color())
+ box_firmware.pack_start(label_firmware, expand=False)
+ self._group.add_widget(label_firmware)
+ label_firmware.show()
+ label_firmware_no = gtk.Label(self._model.get_firmware_number())
+ label_firmware_no.set_alignment(0, 0)
+ box_firmware.pack_start(label_firmware_no, expand=False)
+ label_firmware_no.show()
+ box_software.pack_start(box_firmware, expand=False)
+ box_firmware.show()
+
+ box_wireless_fw = gtk.HBox(spacing=style.DEFAULT_SPACING)
+ label_wireless_fw = gtk.Label(_('Wireless Firmware:'))
+ label_wireless_fw.set_alignment(1, 0)
+ label_wireless_fw.modify_fg(gtk.STATE_NORMAL,
+ style.COLOR_SELECTION_GREY.get_gdk_color())
+ box_wireless_fw.pack_start(label_wireless_fw, expand=False)
+ self._group.add_widget(label_wireless_fw)
+ label_wireless_fw.show()
+ wireless_fw_no = self._model.get_wireless_firmware()
+ label_wireless_fw_no = gtk.Label(wireless_fw_no)
+ label_wireless_fw_no.set_alignment(0, 0)
+ box_wireless_fw.pack_start(label_wireless_fw_no, expand=False)
+ label_wireless_fw_no.show()
+ box_software.pack_start(box_wireless_fw, expand=False)
+ box_wireless_fw.show()
+
+ self._vbox.pack_start(box_software, expand=False)
+ box_software.show()
+
+ def _setup_copyright(self):
+ separator_copyright = gtk.HSeparator()
+ self._vbox.pack_start(separator_copyright, expand=False)
+ separator_copyright.show()
+
+ label_copyright = gtk.Label(_('Copyright and License'))
+ label_copyright.set_alignment(0, 0)
+ self._vbox.pack_start(label_copyright, expand=False)
+ label_copyright.show()
+ vbox_copyright = gtk.VBox()
+ vbox_copyright.set_border_width(style.DEFAULT_SPACING * 2)
+ vbox_copyright.set_spacing(style.DEFAULT_SPACING)
+
+ label_copyright = gtk.Label("© 2006-2010 One Laptop per Child "
+ "Association Inc, Sugar Labs Inc, "
+ "Red Hat Inc, Collabora Ltd "
+ "and Contributors.")
+ label_copyright.set_alignment(0, 0)
+ label_copyright.set_size_request(gtk.gdk.screen_width() / 2, -1)
+ label_copyright.set_line_wrap(True)
+ label_copyright.show()
+ vbox_copyright.pack_start(label_copyright, expand=False)
+
+ label_info = gtk.Label(_("Sugar is the graphical user interface that "
+ "you are looking at. Sugar is free software, "
+ "covered by the GNU General Public License, "
+ "and you are welcome to change it and/or "
+ "distribute copies of it under certain "
+ "conditions described therein."))
+ label_info.set_alignment(0, 0)
+ label_info.set_line_wrap(True)
+ label_info.set_size_request(gtk.gdk.screen_width() / 2, -1)
+ label_info.show()
+ vbox_copyright.pack_start(label_info, expand=False)
+
+ expander = gtk.Expander(_("Full license:"))
+ expander.connect("notify::expanded", self.license_expander_cb)
+ expander.show()
+ vbox_copyright.pack_start(expander, expand=True)
+
+ self._vbox.pack_start(vbox_copyright, expand=True)
+ vbox_copyright.show()
+
+ def license_expander_cb(self, expander, param_spec):
+ # load/destroy the license viewer on-demand, to avoid storing the
+ # GPL in memory at all times
+ if expander.get_expanded():
+ view_license = gtk.TextView()
+ view_license.set_editable(False)
+ view_license.get_buffer().set_text(self._model.get_license())
+ view_license.show()
+ expander.add(view_license)
+ else:
+ expander.get_child().destroy()
diff --git a/shell/extensions/cpsection/aboutme/Makefile.am b/shell/extensions/cpsection/aboutme/Makefile.am
new file mode 100644
index 0000000..9ca91d2
--- /dev/null
+++ b/shell/extensions/cpsection/aboutme/Makefile.am
@@ -0,0 +1,6 @@
+sugardir = $(pkgdatadir)/extensions/cpsection/aboutme
+
+sugar_PYTHON = \
+ __init__.py \
+ model.py \
+ view.py
diff --git a/shell/extensions/cpsection/aboutme/__init__.py b/shell/extensions/cpsection/aboutme/__init__.py
new file mode 100644
index 0000000..98843e1
--- /dev/null
+++ b/shell/extensions/cpsection/aboutme/__init__.py
@@ -0,0 +1,28 @@
+# Copyright (C) 2008, OLPC
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from gettext import gettext as _
+import gconf
+
+from sugar.graphics.xocolor import XoColor
+
+CLASS = 'AboutMe'
+ICON = 'module-about_me'
+TITLE = _('About Me')
+client = gconf.client_get_default()
+COLOR = XoColor(client.get_string('/desktop/sugar/user/color'))
+
+
diff --git a/shell/extensions/cpsection/aboutme/model.py b/shell/extensions/cpsection/aboutme/model.py
new file mode 100644
index 0000000..8500799
--- /dev/null
+++ b/shell/extensions/cpsection/aboutme/model.py
@@ -0,0 +1,115 @@
+# Copyright (C) 2008 One Laptop Per Child
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+from gettext import gettext as _
+import gconf
+
+_COLORS = {'red': {'dark':'#b20008', 'medium':'#e6000a', 'light':'#ffadce'},
+ 'orange': {'dark':'#9a5200', 'medium':'#c97e00', 'light':'#ffc169'},
+ 'yellow': {'dark':'#807500', 'medium':'#be9e00', 'light':'#fffa00'},
+ 'green': {'dark':'#008009', 'medium':'#00b20d', 'light':'#8bff7a'},
+ 'blue': {'dark':'#00588c', 'medium':'#005fe4', 'light':'#bccdff'},
+ 'purple': {'dark':'#5e008c', 'medium':'#7f00bf', 'light':'#d1a3ff'}
+ }
+
+_MODIFIERS = ('dark', 'medium', 'light')
+
+def get_nick():
+ client = gconf.client_get_default()
+ return client.get_string("/desktop/sugar/user/nick")
+
+def print_nick():
+ print get_nick()
+
+def set_nick(nick):
+ """Set the nickname.
+ nick : e.g. 'walter'
+ """
+ if not nick:
+ raise ValueError(_("You must enter a name."))
+ if not isinstance(nick, unicode):
+ nick = unicode(nick, 'utf-8')
+ client = gconf.client_get_default()
+ client.set_string("/desktop/sugar/user/nick", nick)
+ return 1
+
+def get_color():
+ client = gconf.client_get_default()
+ return client.get_string("/desktop/sugar/user/color")
+
+def print_color():
+ color_string = get_color()
+ tmp = color_string.split(',')
+
+ stroke_tuple = None
+ fill_tuple = None
+ for color in _COLORS:
+ for hue in _COLORS[color]:
+ if _COLORS[color][hue] == tmp[0]:
+ stroke_tuple = (color, hue)
+ if _COLORS[color][hue] == tmp[1]:
+ fill_tuple = (color, hue)
+
+ if stroke_tuple is not None:
+ print _('stroke: color=%s hue=%s') % (stroke_tuple[0],
+ stroke_tuple[1])
+ else:
+ print _('stroke: %s') % (tmp[0])
+ if fill_tuple is not None:
+ print _('fill: color=%s hue=%s') % (fill_tuple[0], fill_tuple[1])
+ else:
+ print _('fill: %s') % (tmp[1])
+
+def set_color(stroke, fill, stroke_modifier='medium', fill_modifier='medium'):
+ """Set the system color by setting a fill and stroke color.
+ fill : [red, orange, yellow, blue, green, purple]
+ stroke : [red, orange, yellow, blue, green, purple]
+ hue stroke : [dark, medium, light] (optional)
+ hue fill : [dark, medium, light] (optional)
+ """
+
+ if stroke_modifier not in _MODIFIERS or fill_modifier not in _MODIFIERS:
+ print (_("Error in specified color modifiers."))
+ return
+ if stroke not in _COLORS or fill not in _COLORS:
+ print (_("Error in specified colors."))
+ return
+
+ if stroke_modifier == fill_modifier:
+ if fill_modifier == 'medium':
+ fill_modifier = 'light'
+ else:
+ fill_modifier = 'medium'
+
+ color = _COLORS[stroke][stroke_modifier] + ',' \
+ + _COLORS[fill][fill_modifier]
+
+ client = gconf.client_get_default()
+ client.set_string("/desktop/sugar/user/color", color)
+ return 1
+
+def get_color_xo():
+ client = gconf.client_get_default()
+ return client.get_string("/desktop/sugar/user/color")
+
+def set_color_xo(color):
+ """Set a color with an XoColor
+ This method is used by the graphical user interface
+ """
+ client = gconf.client_get_default()
+ client.set_string("/desktop/sugar/user/color", color)
+ return 1
diff --git a/shell/extensions/cpsection/aboutme/view.py b/shell/extensions/cpsection/aboutme/view.py
new file mode 100644
index 0000000..95314a1
--- /dev/null
+++ b/shell/extensions/cpsection/aboutme/view.py
@@ -0,0 +1,340 @@
+# Copyright (C) 2008, OLPC
+# Copyright (C) 2010, Sugar Labs
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import gtk
+import gobject
+from gettext import gettext as _
+
+from sugar.graphics.icon import Icon
+from sugar.graphics import style
+from sugar.graphics.xocolor import XoColor, colors
+
+from jarabe.controlpanel.sectionview import SectionView
+from jarabe.controlpanel.inlinealert import InlineAlert
+
+_STROKE_COLOR = 0
+_FILL_COLOR = 1
+
+
+def _get_next_stroke_color(color):
+ """ Return the next color pair in the list that shares the same fill
+ as color. """
+ current_index = _get_current_index(color)
+ if current_index == -1:
+ return "%s,%s" % (color.stroke, color.fill)
+ next_index = _next_index(current_index)
+ while(colors[next_index][_FILL_COLOR] != \
+ colors[current_index][_FILL_COLOR]):
+ next_index = _next_index(next_index)
+ return "%s,%s" % (colors[next_index][_STROKE_COLOR],
+ colors[next_index][_FILL_COLOR])
+
+
+def _get_previous_stroke_color(color):
+ """ Return the previous color pair in the list that shares the same fill
+ as color. """
+ current_index = _get_current_index(color)
+ if current_index == -1:
+ return "%s,%s" % (color.stroke, color.fill)
+ previous_index = _previous_index(current_index)
+ while (colors[previous_index][_FILL_COLOR] != \
+ colors[current_index][_FILL_COLOR]):
+ previous_index = _previous_index(previous_index)
+ return "%s,%s" % (colors[previous_index][_STROKE_COLOR],
+ colors[previous_index][_FILL_COLOR])
+
+
+def _get_next_fill_color(color):
+ """ Return the next color pair in the list that shares the same stroke
+ as color. """
+ current_index = _get_current_index(color)
+ if current_index == -1:
+ return "%s,%s" % (color.stroke, color.fill)
+ next_index = _next_index(current_index)
+ while (colors[next_index][_STROKE_COLOR] != \
+ colors[current_index][_STROKE_COLOR]):
+ next_index = _next_index(next_index)
+ return "%s,%s" % (colors[next_index][_STROKE_COLOR],
+ colors[next_index][_FILL_COLOR])
+
+
+def _get_previous_fill_color(color):
+ """ Return the previous color pair in the list that shares the same stroke
+ as color. """
+ current_index = _get_current_index(color)
+ if current_index == -1:
+ return "%s,%s" % (color.stroke, color.fill)
+ previous_index = _previous_index(current_index)
+ while (colors[previous_index][_STROKE_COLOR] != \
+ colors[current_index][_STROKE_COLOR]):
+ previous_index = _previous_index(previous_index)
+ return "%s,%s" % (colors[previous_index][_STROKE_COLOR],
+ colors[previous_index][_FILL_COLOR])
+
+
+def _next_index(current_index):
+ next_index = current_index + 1
+ if next_index == len(colors):
+ next_index = 0
+ return next_index
+
+
+def _previous_index(current_index):
+ previous_index = current_index - 1
+ if previous_index < 0:
+ previous_index = len(colors)-1
+ return previous_index
+
+
+def _get_current_index(color):
+ return colors.index([color.stroke, color.fill])
+
+
+_PREVIOUS_FILL_COLOR = 0
+_NEXT_FILL_COLOR = 1
+_CURRENT_COLOR = 2
+_NEXT_STROKE_COLOR = 3
+_PREVIOUS_STROKE_COLOR = 4
+
+
+class EventIcon(gtk.EventBox):
+ __gtype_name__ = "SugarEventIcon"
+
+ def __init__(self, **kwargs):
+ gtk.EventBox.__init__(self)
+
+ self.icon = Icon(pixel_size=style.XLARGE_ICON_SIZE, **kwargs)
+
+ self.set_visible_window(False)
+ self.set_app_paintable(True)
+ self.set_events(gtk.gdk.BUTTON_PRESS_MASK)
+
+ self.add(self.icon)
+ self.icon.show()
+
+
+class ColorPicker(EventIcon):
+ __gsignals__ = {
+ 'color-changed': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([object]))
+ }
+
+ def __init__(self, picker):
+ EventIcon.__init__(self)
+
+ self.icon.props.icon_name = 'computer-xo'
+ self._picker = picker
+ self._color = None
+
+ self.icon.props.pixel_size = style.XLARGE_ICON_SIZE
+
+ self.connect('button_press_event', self.__pressed_cb, picker)
+
+ def update(self, color):
+ if self._picker == _PREVIOUS_FILL_COLOR:
+ self._color = XoColor(_get_previous_fill_color(color))
+ elif self._picker == _PREVIOUS_STROKE_COLOR:
+ self._color = XoColor(_get_previous_stroke_color(color))
+ elif self._picker == _NEXT_FILL_COLOR:
+ self._color = XoColor(_get_next_fill_color(color))
+ elif self._picker == _NEXT_STROKE_COLOR:
+ self._color = XoColor(_get_next_stroke_color(color))
+ else:
+ self._color = color
+ self.icon.props.xo_color = self._color
+
+ def __pressed_cb(self, button, event, picker):
+ if picker != _CURRENT_COLOR:
+ self.emit('color-changed', self._color)
+
+
+class AboutMe(SectionView):
+
+ def __init__(self, model, alerts):
+ SectionView.__init__(self)
+
+ self._model = model
+ self.restart_alerts = alerts
+ self._nick_sid = 0
+ self._color_valid = True
+ self._nick_valid = True
+
+ self.set_border_width(style.DEFAULT_SPACING * 2)
+ self.set_spacing(style.DEFAULT_SPACING)
+ self._group = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
+
+ self._color_label = gtk.HBox(spacing=style.DEFAULT_SPACING)
+ self._color_box = gtk.HBox(spacing=style.DEFAULT_SPACING)
+ self._color_alert_box = gtk.HBox(spacing=style.DEFAULT_SPACING)
+ self._color_alert = None
+
+ self._pickers = {
+ _PREVIOUS_FILL_COLOR: ColorPicker(_PREVIOUS_FILL_COLOR),
+ _NEXT_FILL_COLOR: ColorPicker(_NEXT_FILL_COLOR),
+ _CURRENT_COLOR: ColorPicker(_CURRENT_COLOR),
+ _NEXT_STROKE_COLOR: ColorPicker(_NEXT_STROKE_COLOR),
+ _PREVIOUS_STROKE_COLOR: ColorPicker(_PREVIOUS_STROKE_COLOR)
+ }
+
+ self._setup_color()
+ initial_color = XoColor(self._model.get_color_xo())
+ self._update_pickers(initial_color)
+
+ self._nick_box = gtk.HBox(spacing=style.DEFAULT_SPACING)
+ self._nick_alert_box = gtk.HBox(spacing=style.DEFAULT_SPACING)
+ self._nick_entry = None
+ self._nick_alert = None
+ self._setup_nick()
+ self.setup()
+
+ def _setup_nick(self):
+ self._nick_entry = gtk.Entry()
+ self._nick_entry.modify_bg(gtk.STATE_INSENSITIVE,
+ style.COLOR_WHITE.get_gdk_color())
+ self._nick_entry.modify_base(gtk.STATE_INSENSITIVE,
+ style.COLOR_WHITE.get_gdk_color())
+ self._nick_entry.set_width_chars(25)
+ self._nick_box.pack_start(self._nick_entry, expand=False)
+ self._nick_entry.show()
+
+ label_entry_error = gtk.Label()
+ self._group.add_widget(label_entry_error)
+ self._nick_alert_box.pack_start(label_entry_error, expand=False)
+ label_entry_error.show()
+
+ self._nick_alert = InlineAlert()
+ self._nick_alert_box.pack_start(self._nick_alert)
+ if 'nick' in self.restart_alerts:
+ self._nick_alert.props.msg = self.restart_msg
+ self._nick_alert.show()
+
+ self._center_in_panel = gtk.Alignment(0.5)
+ self._center_in_panel.add(self._nick_box)
+ self.pack_start(self._center_in_panel, False)
+ self.pack_start(self._nick_alert_box, False)
+ self._nick_box.show()
+ self._nick_alert_box.show()
+ self._center_in_panel.show()
+
+ def _setup_color(self):
+ label_color = gtk.Label(_('Click to change your color:'))
+ label_color.modify_fg(gtk.STATE_NORMAL,
+ style.COLOR_SELECTION_GREY.get_gdk_color())
+ self._group.add_widget(label_color)
+ self._color_label.pack_start(label_color, expand=False)
+ label_color.show()
+
+ for picker_index in sorted(self._pickers.keys()):
+ if picker_index == _CURRENT_COLOR:
+ left_separator = gtk.SeparatorToolItem()
+ left_separator.show()
+ self._color_box.pack_start(left_separator, expand=False)
+
+ picker = self._pickers[picker_index]
+ picker.show()
+ self._color_box.pack_start(picker, expand=False)
+
+ if picker_index == _CURRENT_COLOR:
+ right_separator = gtk.SeparatorToolItem()
+ right_separator.show()
+ self._color_box.pack_start(right_separator, expand=False)
+
+ label_color_error = gtk.Label()
+ self._group.add_widget(label_color_error)
+ self._color_alert_box.pack_start(label_color_error, expand=False)
+ label_color_error.show()
+
+ self._color_alert = InlineAlert()
+ self._color_alert_box.pack_start(self._color_alert)
+ if 'color' in self.restart_alerts:
+ self._color_alert.props.msg = self.restart_msg
+ self._color_alert.show()
+
+ self._center_in_panel = gtk.Alignment(0.5)
+ self._center_in_panel.add(self._color_box)
+ self.pack_start(self._color_label, False)
+ self.pack_start(self._center_in_panel, False)
+ self.pack_start(self._color_alert_box, False)
+ self._color_label.show()
+ self._color_box.show()
+ self._color_alert_box.show()
+ self._center_in_panel.show()
+
+ def setup(self):
+ self._nick_entry.set_text(self._model.get_nick())
+ self._color_valid = True
+ self._nick_valid = True
+ self.needs_restart = False
+
+ self._nick_entry.connect('changed', self.__nick_changed_cb)
+ for picker in self._pickers.values():
+ picker.connect('color-changed', self.__color_changed_cb)
+
+ def undo(self):
+ self._model.undo()
+ self._nick_alert.hide()
+ self._color_alert.hide()
+
+ def _update_pickers(self, color):
+ for picker in self._pickers.values():
+ picker.update(color)
+
+ def _validate(self):
+ if self._nick_valid and self._color_valid:
+ self.props.is_valid = True
+ else:
+ self.props.is_valid = False
+
+ def __nick_changed_cb(self, widget, data=None):
+ if self._nick_sid:
+ gobject.source_remove(self._nick_sid)
+ self._nick_sid = gobject.timeout_add(self._APPLY_TIMEOUT,
+ self.__nick_timeout_cb, widget)
+
+ def __nick_timeout_cb(self, widget):
+ self._nick_sid = 0
+
+ if widget.get_text() == self._model.get_nick():
+ return False
+ try:
+ self._model.set_nick(widget.get_text())
+ except ValueError, detail:
+ self._nick_alert.props.msg = detail
+ self._nick_valid = False
+ else:
+ self._nick_alert.props.msg = self.restart_msg
+ self._nick_valid = True
+ self.needs_restart = True
+ self.restart_alerts.append('nick')
+ self._validate()
+ self._nick_alert.show()
+ return False
+
+ def __color_changed_cb(self, colorpicker, color):
+ self._model.set_color_xo(color.to_string())
+ self.needs_restart = True
+ self._color_alert.props.msg = self.restart_msg
+ self._color_valid = True
+ self.restart_alerts.append('color')
+
+ self._validate()
+ self._color_alert.show()
+
+ self._update_pickers(color)
+
+ return False
diff --git a/shell/extensions/cpsection/datetime/Makefile.am b/shell/extensions/cpsection/datetime/Makefile.am
new file mode 100644
index 0000000..b5b518e
--- /dev/null
+++ b/shell/extensions/cpsection/datetime/Makefile.am
@@ -0,0 +1,6 @@
+sugardir = $(pkgdatadir)/extensions/cpsection/datetime
+
+sugar_PYTHON = \
+ __init__.py \
+ model.py \
+ view.py
diff --git a/shell/extensions/cpsection/datetime/__init__.py b/shell/extensions/cpsection/datetime/__init__.py
new file mode 100644
index 0000000..fc9be45
--- /dev/null
+++ b/shell/extensions/cpsection/datetime/__init__.py
@@ -0,0 +1,21 @@
+# Copyright (C) 2008, OLPC
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from gettext import gettext as _
+
+CLASS = 'TimeZone'
+ICON = 'module-date_and_time'
+TITLE = _('Date & Time')
diff --git a/shell/extensions/cpsection/datetime/model.py b/shell/extensions/cpsection/datetime/model.py
new file mode 100644
index 0000000..76064e4
--- /dev/null
+++ b/shell/extensions/cpsection/datetime/model.py
@@ -0,0 +1,92 @@
+# Copyright (C) 2007, 2008 One Laptop Per Child
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+#
+# The timezone config is based on the system-config-date
+# (http://fedoraproject.org/wiki/SystemConfig/date) tool.
+# Parts of the code were reused.
+#
+
+import os
+from gettext import gettext as _
+import gconf
+
+_zone_tab = '/usr/share/zoneinfo/zone.tab'
+
+def _initialize():
+ '''Initialize the docstring of the set function'''
+ if set_timezone.__doc__ is None:
+ # when running under 'python -OO', all __doc__ fields are None,
+ # so += would fail -- and this function would be unnecessary anyway.
+ return
+ timezones = read_all_timezones()
+ for timezone in timezones:
+ set_timezone.__doc__ += timezone + '\n'
+
+def read_all_timezones(fn=_zone_tab):
+ fd = open (fn, 'r')
+ lines = fd.readlines()
+ fd.close()
+ timezones = []
+ for line in lines:
+ if line.startswith('#'):
+ continue
+ line = line.split()
+ if len(line) > 1:
+ timezones.append(line[2])
+ timezones.sort()
+
+ for offset in xrange(-12, 13):
+ if offset < 0:
+ tz = 'GMT%d' % offset
+ elif offset > 0:
+ tz = 'GMT+%d' % offset
+ else:
+ tz = 'GMT'
+ timezones.append(tz)
+ for offset in xrange(-12, 13):
+ if offset < 0:
+ tz = 'UTC%d' % offset
+ elif offset > 0:
+ tz = 'UTC+%d' % offset
+ else:
+ tz = 'UTC'
+ timezones.append(tz)
+ return timezones
+
+def get_timezone():
+ client = gconf.client_get_default()
+ return client.get_string('/desktop/sugar/date/timezone')
+
+def print_timezone():
+ print get_timezone()
+
+def set_timezone(timezone):
+ """Set the system timezone
+ timezone : e.g. 'America/Los_Angeles'
+ """
+ timezones = read_all_timezones()
+ if timezone in timezones:
+ os.environ['TZ'] = timezone
+ client = gconf.client_get_default()
+ client.set_string('/desktop/sugar/date/timezone', timezone)
+ else:
+ raise ValueError(_("Error timezone does not exist."))
+ return 1
+
+# inilialize the docstrings for the timezone
+_initialize()
+
diff --git a/shell/extensions/cpsection/datetime/view.py b/shell/extensions/cpsection/datetime/view.py
new file mode 100644
index 0000000..58719b4
--- /dev/null
+++ b/shell/extensions/cpsection/datetime/view.py
@@ -0,0 +1,138 @@
+# Copyright (C) 2008, OLPC
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import gtk
+import gobject
+from gettext import gettext as _
+
+from sugar.graphics import style
+from sugar.graphics import iconentry
+
+from jarabe.controlpanel.sectionview import SectionView
+from jarabe.controlpanel.inlinealert import InlineAlert
+
+class TimeZone(SectionView):
+ def __init__(self, model, alerts):
+ SectionView.__init__(self)
+
+ self._model = model
+ self.restart_alerts = alerts
+ self._zone_sid = 0
+ self._cursor_change_handler = None
+
+ self.set_border_width(style.DEFAULT_SPACING * 2)
+ self.set_spacing(style.DEFAULT_SPACING)
+
+ self.connect("realize", self.__realize_cb)
+
+ self._entry = iconentry.IconEntry()
+ self._entry.set_icon_from_name(iconentry.ICON_ENTRY_PRIMARY,
+ 'system-search')
+ self._entry.add_clear_button()
+ self._entry.modify_bg(gtk.STATE_INSENSITIVE,
+ style.COLOR_WHITE.get_gdk_color())
+ self._entry.modify_base(gtk.STATE_INSENSITIVE,
+ style.COLOR_WHITE.get_gdk_color())
+ self.pack_start(self._entry, False)
+ self._entry.show()
+
+ self._scrolled_window = gtk.ScrolledWindow()
+ self._scrolled_window.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+ self._scrolled_window.set_shadow_type(gtk.SHADOW_IN)
+
+ self._store = gtk.ListStore(gobject.TYPE_STRING)
+ zones = model.read_all_timezones()
+ for zone in zones:
+ self._store.append([zone])
+
+ self._treeview = gtk.TreeView(self._store)
+ self._treeview.set_search_entry(self._entry)
+ self._treeview.set_search_equal_func(self._search)
+ self._treeview.set_search_column(0)
+ self._scrolled_window.add(self._treeview)
+ self._treeview.show()
+
+ self._timezone_column = gtk.TreeViewColumn(_('Timezone'))
+ self._cell = gtk.CellRendererText()
+ self._timezone_column.pack_start(self._cell, True)
+ self._timezone_column.add_attribute(self._cell, 'text', 0)
+ self._timezone_column.set_sort_column_id(0)
+ self._treeview.append_column(self._timezone_column)
+
+ self.pack_start(self._scrolled_window)
+ self._scrolled_window.show()
+
+ self._zone_alert_box = gtk.HBox(spacing=style.DEFAULT_SPACING)
+ self.pack_start(self._zone_alert_box, False)
+
+ self._zone_alert = InlineAlert()
+ self._zone_alert_box.pack_start(self._zone_alert)
+ if 'zone' in self.restart_alerts:
+ self._zone_alert.props.msg = self.restart_msg
+ self._zone_alert.show()
+ self._zone_alert_box.show()
+
+ self.setup()
+
+ def setup(self):
+ zone = self._model.get_timezone()
+ for row in self._store:
+ if zone == row[0]:
+ self._treeview.set_cursor(row.path, self._timezone_column,
+ False)
+ self._treeview.scroll_to_cell(row.path, self._timezone_column,
+ True, 0.5, 0.5)
+ break
+
+ self.needs_restart = False
+ self._cursor_change_handler = self._treeview.connect( \
+ "cursor-changed", self.__zone_changed_cd)
+
+ def undo(self):
+ self._treeview.disconnect(self._cursor_change_handler)
+ self._model.undo()
+ self._zone_alert.hide()
+
+ def __realize_cb(self, widget):
+ self._entry.grab_focus()
+
+ def _search(self, model, column, key, iterator, data=None):
+ value = model.get_value(iterator, column)
+ if key.lower() in value.lower():
+ return False
+ return True
+
+ def __zone_changed_cd(self, treeview, data=None):
+ list_, row = treeview.get_selection().get_selected()
+ if not row:
+ return False
+ if self._model.get_timezone() == self._store.get_value(row, 0):
+ return False
+
+ if self._zone_sid:
+ gobject.source_remove(self._zone_sid)
+ self._zone_sid = gobject.timeout_add(self._APPLY_TIMEOUT,
+ self.__zone_timeout_cb, row)
+ return True
+
+ def __zone_timeout_cb(self, row):
+ self._zone_sid = 0
+ self._model.set_timezone(self._store.get_value(row, 0))
+ self.restart_alerts.append('zone')
+ self.needs_restart = True
+ self._zone_alert.props.msg = self.restart_msg
+ self._zone_alert.show()
+ return False
diff --git a/shell/extensions/cpsection/frame/Makefile.am b/shell/extensions/cpsection/frame/Makefile.am
new file mode 100644
index 0000000..1e09c04
--- /dev/null
+++ b/shell/extensions/cpsection/frame/Makefile.am
@@ -0,0 +1,6 @@
+sugardir = $(pkgdatadir)/extensions/cpsection/frame
+
+sugar_PYTHON = \
+ __init__.py \
+ model.py \
+ view.py
diff --git a/shell/extensions/cpsection/frame/__init__.py b/shell/extensions/cpsection/frame/__init__.py
new file mode 100644
index 0000000..a93f9c7
--- /dev/null
+++ b/shell/extensions/cpsection/frame/__init__.py
@@ -0,0 +1,21 @@
+# Copyright (C) 2008, OLPC
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from gettext import gettext as _
+
+CLASS = 'Frame'
+ICON = 'module-frame'
+TITLE = _('Frame')
diff --git a/shell/extensions/cpsection/frame/model.py b/shell/extensions/cpsection/frame/model.py
new file mode 100644
index 0000000..9eea9ad
--- /dev/null
+++ b/shell/extensions/cpsection/frame/model.py
@@ -0,0 +1,63 @@
+# Copyright (C) 2008 One Laptop Per Child
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+from gettext import gettext as _
+import gconf
+
+def get_corner_delay():
+ client = gconf.client_get_default()
+ corner_delay = client.get_int('/desktop/sugar/frame/corner_delay')
+ return corner_delay
+
+def print_corner_delay():
+ print get_corner_delay()
+
+def set_corner_delay(delay):
+ """Set a delay for the activation of the frame using hot corners.
+ instantaneous: 0 (0 milliseconds)
+ delay: 100 (100 milliseconds)
+ never: 1000 (disable activation)
+ """
+ try:
+ int(delay)
+ except ValueError:
+ raise ValueError(_("Value must be an integer."))
+ client = gconf.client_get_default()
+ client.set_int('/desktop/sugar/frame/corner_delay', int(delay))
+ return 0
+
+def get_edge_delay():
+ client = gconf.client_get_default()
+ edge_delay = client.get_int('/desktop/sugar/frame/edge_delay')
+ return edge_delay
+
+def print_edge_delay():
+ print get_edge_delay()
+
+def set_edge_delay(delay):
+ """Set a delay for the activation of the frame using warm edges.
+ instantaneous: 0 (0 milliseconds)
+ delay: 100 (100 milliseconds)
+ never: 1000 (disable activation)
+ """
+ try:
+ int(delay)
+ except ValueError:
+ raise ValueError(_("Value must be an integer."))
+ client = gconf.client_get_default()
+ client.set_int('/desktop/sugar/frame/edge_delay', int(delay))
+ return 0
diff --git a/shell/extensions/cpsection/frame/view.py b/shell/extensions/cpsection/frame/view.py
new file mode 100644
index 0000000..cbe43bb
--- /dev/null
+++ b/shell/extensions/cpsection/frame/view.py
@@ -0,0 +1,232 @@
+# Copyright (C) 2008, OLPC
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import gtk
+import gobject
+from gettext import gettext as _
+
+from sugar.graphics import style
+
+from jarabe.controlpanel.sectionview import SectionView
+from jarabe.controlpanel.inlinealert import InlineAlert
+
+_never = _('never')
+_instantaneous = _('instantaneous')
+_seconds_label = _('%s seconds')
+_MAX_DELAY = 1000
+
+class Frame(SectionView):
+ def __init__(self, model, alerts):
+ SectionView.__init__(self)
+
+ self._model = model
+ self._corner_delay_sid = 0
+ self._corner_delay_is_valid = True
+ self._corner_delay_change_handler = None
+ self._edge_delay_sid = 0
+ self._edge_delay_is_valid = True
+ self._edge_delay_change_handler = None
+ self.restart_alerts = alerts
+
+ self.set_border_width(style.DEFAULT_SPACING * 2)
+ self.set_spacing(style.DEFAULT_SPACING)
+ self._group = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
+
+ separator = gtk.HSeparator()
+ self.pack_start(separator, expand=False)
+ separator.show()
+
+ label_activation = gtk.Label(_('Activation Delay'))
+ label_activation.set_alignment(0, 0)
+ self.pack_start(label_activation, expand=False)
+ label_activation.show()
+
+ self._box_sliders = gtk.VBox()
+ self._box_sliders.set_border_width(style.DEFAULT_SPACING * 2)
+ self._box_sliders.set_spacing(style.DEFAULT_SPACING)
+
+ self._corner_delay_slider = None
+ self._corner_delay_alert = None
+ self._setup_corner()
+
+ self._edge_delay_slider = None
+ self._edge_delay_alert = None
+ self._setup_edge()
+
+ self.pack_start(self._box_sliders, expand=False)
+ self._box_sliders.show()
+
+ self.setup()
+
+ def _setup_corner(self):
+ box_delay = gtk.HBox(spacing=style.DEFAULT_SPACING)
+ label_delay = gtk.Label(_('Corner'))
+ label_delay.set_alignment(1, 0.75)
+ label_delay.modify_fg(gtk.STATE_NORMAL,
+ style.COLOR_SELECTION_GREY.get_gdk_color())
+ box_delay.pack_start(label_delay, expand=False)
+ self._group.add_widget(label_delay)
+ label_delay.show()
+
+ adj = gtk.Adjustment(value=100, lower=0, upper=_MAX_DELAY,
+ step_incr=100, page_incr=100, page_size=0)
+ self._corner_delay_slider = gtk.HScale(adj)
+ self._corner_delay_slider.set_digits(0)
+ self._corner_delay_slider.connect('format-value',
+ self.__corner_delay_format_cb)
+ box_delay.pack_start(self._corner_delay_slider)
+ self._corner_delay_slider.show()
+ self._box_sliders.pack_start(box_delay, expand=False)
+ box_delay.show()
+
+ self._corner_delay_alert = InlineAlert()
+ label_delay_error = gtk.Label()
+ self._group.add_widget(label_delay_error)
+
+ delay_alert_box = gtk.HBox(spacing=style.DEFAULT_SPACING)
+ delay_alert_box.pack_start(label_delay_error, expand=False)
+ label_delay_error.show()
+ delay_alert_box.pack_start(self._corner_delay_alert, expand=False)
+ self._box_sliders.pack_start(delay_alert_box, expand=False)
+ delay_alert_box.show()
+ if 'corner_delay' in self.restart_alerts:
+ self._corner_delay_alert.props.msg = self.restart_msg
+ self._corner_delay_alert.show()
+
+ def _setup_edge(self):
+ box_delay = gtk.HBox(spacing=style.DEFAULT_SPACING)
+ label_delay = gtk.Label(_('Edge'))
+ label_delay.set_alignment(1, 0.75)
+ label_delay.modify_fg(gtk.STATE_NORMAL,
+ style.COLOR_SELECTION_GREY.get_gdk_color())
+ box_delay.pack_start(label_delay, expand=False)
+ self._group.add_widget(label_delay)
+ label_delay.show()
+
+ adj = gtk.Adjustment(value=100, lower=0, upper=_MAX_DELAY,
+ step_incr=100, page_incr=100, page_size=0)
+ self._edge_delay_slider = gtk.HScale(adj)
+ self._edge_delay_slider.set_digits(0)
+ self._edge_delay_slider.connect('format-value',
+ self.__edge_delay_format_cb)
+ box_delay.pack_start(self._edge_delay_slider)
+ self._edge_delay_slider.show()
+ self._box_sliders.pack_start(box_delay, expand=False)
+ box_delay.show()
+
+ self._edge_delay_alert = InlineAlert()
+ label_delay_error = gtk.Label()
+ self._group.add_widget(label_delay_error)
+
+ delay_alert_box = gtk.HBox(spacing=style.DEFAULT_SPACING)
+ delay_alert_box.pack_start(label_delay_error, expand=False)
+ label_delay_error.show()
+ delay_alert_box.pack_start(self._edge_delay_alert, expand=False)
+ self._box_sliders.pack_start(delay_alert_box, expand=False)
+ delay_alert_box.show()
+ if 'edge_delay' in self.restart_alerts:
+ self._edge_delay_alert.props.msg = self.restart_msg
+ self._edge_delay_alert.show()
+
+ def setup(self):
+ self._corner_delay_slider.set_value(self._model.get_corner_delay())
+ self._edge_delay_slider.set_value(self._model.get_edge_delay())
+ self._corner_delay_is_valid = True
+ self._edge_delay_is_valid = True
+ self.needs_restart = False
+ self._corner_delay_change_handler = self._corner_delay_slider.connect( \
+ 'value-changed', self.__corner_delay_changed_cb)
+ self._edge_delay_change_handler = self._edge_delay_slider.connect( \
+ 'value-changed', self.__edge_delay_changed_cb)
+
+ def undo(self):
+ self._corner_delay_slider.disconnect(self._corner_delay_change_handler)
+ self._edge_delay_slider.disconnect(self._edge_delay_change_handler)
+ self._model.undo()
+ self._corner_delay_alert.hide()
+ self._edge_delay_alert.hide()
+
+ def _validate(self):
+ if self._edge_delay_is_valid and self._corner_delay_is_valid:
+ self.props.is_valid = True
+ else:
+ self.props.is_valid = False
+
+ def __corner_delay_changed_cb(self, scale, data=None):
+ if self._corner_delay_sid:
+ gobject.source_remove(self._corner_delay_sid)
+ self._corner_delay_sid = gobject.timeout_add( \
+ self._APPLY_TIMEOUT, self.__corner_delay_timeout_cb, scale)
+
+ def __corner_delay_timeout_cb(self, scale):
+ self._corner_delay_sid = 0
+ if scale.get_value() == self._model.get_corner_delay():
+ return
+ try:
+ self._model.set_corner_delay(scale.get_value())
+ except ValueError, detail:
+ self._corner_delay_alert.props.msg = detail
+ self._corner_delay_is_valid = False
+ else:
+ self._corner_delay_alert.props.msg = self.restart_msg
+ self._corner_delay_is_valid = True
+ self.needs_restart = True
+ self.restart_alerts.append('corner_delay')
+
+ self._validate()
+ self._corner_delay_alert.show()
+ return False
+
+ def __corner_delay_format_cb(self, scale, value):
+ if value == _MAX_DELAY:
+ return _never
+ elif value == 0:
+ return _instantaneous
+ else:
+ return _seconds_label % (value / _MAX_DELAY)
+
+ def __edge_delay_changed_cb(self, scale, data=None):
+ if self._edge_delay_sid:
+ gobject.source_remove(self._edge_delay_sid)
+ self._edge_delay_sid = gobject.timeout_add( \
+ self._APPLY_TIMEOUT, self.__edge_delay_timeout_cb, scale)
+
+ def __edge_delay_timeout_cb(self, scale):
+ self._edge_delay_sid = 0
+ if scale.get_value() == self._model.get_edge_delay():
+ return
+ try:
+ self._model.set_edge_delay(scale.get_value())
+ except ValueError, detail:
+ self._edge_delay_alert.props.msg = detail
+ self._edge_delay_is_valid = False
+ else:
+ self._edge_delay_alert.props.msg = self.restart_msg
+ self._edge_delay_is_valid = True
+ self.needs_restart = True
+ self.restart_alerts.append('edge_delay')
+
+ self._validate()
+ self._edge_delay_alert.show()
+ return False
+
+ def __edge_delay_format_cb(self, scale, value):
+ if value == _MAX_DELAY:
+ return _never
+ elif value == 0:
+ return _instantaneous
+ else:
+ return _seconds_label % (value / _MAX_DELAY)
diff --git a/shell/extensions/cpsection/keyboard/Makefile.am b/shell/extensions/cpsection/keyboard/Makefile.am
new file mode 100644
index 0000000..7a95da5
--- /dev/null
+++ b/shell/extensions/cpsection/keyboard/Makefile.am
@@ -0,0 +1,6 @@
+sugardir = $(pkgdatadir)/extensions/cpsection/keyboard
+
+sugar_PYTHON = \
+ __init__.py \
+ model.py \
+ view.py
diff --git a/shell/extensions/cpsection/keyboard/__init__.py b/shell/extensions/cpsection/keyboard/__init__.py
new file mode 100644
index 0000000..568e7a5
--- /dev/null
+++ b/shell/extensions/cpsection/keyboard/__init__.py
@@ -0,0 +1,22 @@
+# Copyright (C) 2009, OLPC
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from gettext import gettext as _
+
+CLASS = 'Keyboard'
+ICON = 'module-keyboard'
+TITLE = _('Keyboard')
+
diff --git a/shell/extensions/cpsection/keyboard/model.py b/shell/extensions/cpsection/keyboard/model.py
new file mode 100644
index 0000000..089c2ea
--- /dev/null
+++ b/shell/extensions/cpsection/keyboard/model.py
@@ -0,0 +1,169 @@
+# Copyright (C) 2009 OLPC
+# Author: Sayamindu Dasgupta <sayamindu@laptop.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+import xklavier
+import gconf
+
+
+_GROUP_NAME = 'grp' # The XKB name for group switch options
+
+_LAYOUTS_KEY = '/desktop/sugar/peripherals/keyboard/layouts'
+_OPTIONS_KEY = '/desktop/sugar/peripherals/keyboard/options'
+_MODEL_KEY = '/desktop/sugar/peripherals/keyboard/model'
+
+class KeyboardManager(object):
+ def __init__(self, display):
+ self._engine = xklavier.Engine(display)
+ self._configregistry = xklavier.ConfigRegistry(self._engine)
+ self._configregistry.load(False)
+ self._configrec = xklavier.ConfigRec()
+ self._configrec.get_from_server(self._engine)
+
+ self._gconf_client = gconf.client_get_default()
+
+ def _populate_one(self, config_registry, item, store):
+ store.append([item.get_description(), item.get_name()])
+
+ def _populate_two(self, config_registry, item, subitem, store):
+ layout = item.get_name()
+ if subitem:
+ description = '%s, %s' % (subitem.get_description(), \
+ item.get_description())
+ variant = subitem.get_name()
+ else:
+ description = 'Default layout, %s' % item.get_description()
+ variant = ''
+
+ store.append([description, ('%s(%s)' % (layout, variant))])
+
+ def get_models(self):
+ """Return list of supported keyboard models"""
+ models = []
+ self._configregistry.foreach_model(self._populate_one, models)
+ models.sort()
+ return models
+
+ def get_languages(self):
+ """Return list of supported keyboard languages"""
+ languages = []
+ self._configregistry.foreach_language(self._populate_one, languages)
+ languages.sort()
+ return languages
+
+ def get_layouts_for_language(self, language):
+ """Return list of supported keyboard layouts for a given language"""
+ layouts = []
+ self._configregistry.foreach_language_variant(language, \
+ self._populate_two, layouts)
+ layouts.sort()
+ return layouts
+
+ def get_options_group(self):
+ """Return list of supported options for switching keyboard group"""
+ options = []
+ self._configregistry.foreach_option(_GROUP_NAME, self._populate_one, \
+ options)
+ options.sort()
+ return options
+
+ def get_current_model(self):
+ """Return the enabled keyboard model"""
+ model = self._gconf_client.get_string(_MODEL_KEY)
+ if model:
+ return model
+ else:
+ model = self._configrec.get_model()
+ self.set_model(model)
+ return model
+
+ def get_current_layouts(self):
+ """Return the enabled keyboard layouts with variants"""
+ layouts = self._gconf_client.get_list(_LAYOUTS_KEY, gconf.VALUE_STRING)
+ if layouts:
+ return layouts
+
+ layouts = self._configrec.get_layouts()
+ variants = self._configrec.get_variants()
+
+ layout_list = []
+ i = 0
+ for layout in layouts:
+ if len(variants) <= i or variants[i] == '':
+ layout_list.append('%s(%s)' % (layout, ''))
+ else:
+ layout_list.append('%s(%s)' % (layout, variants[i]))
+ i += 1
+
+ self.set_layouts(layout_list)
+
+ return layout_list
+
+ def get_current_option_group(self):
+ """Return the enabled option for switching keyboard group"""
+ options = self._gconf_client.get_list(_OPTIONS_KEY, gconf.VALUE_STRING)
+
+ if not options:
+ options = self._configrec.get_options()
+ self.set_option_group(options)
+
+ for option in options:
+ if option.startswith(_GROUP_NAME):
+ return option
+
+ return None
+
+ def get_max_layouts(self):
+ """Return the maximum number of layouts supported simultaneously"""
+ return self._engine.get_max_num_groups()
+
+ def set_model(self, model):
+ """Sets the supplied keyboard model"""
+ if model is None or not model:
+ return
+ self._gconf_client.set_string(_MODEL_KEY, model)
+ self._configrec.set_model(model)
+ self._configrec.activate(self._engine)
+
+ def set_option_group(self, option_group):
+ """Sets the supplied option for switching keyboard group"""
+ #XXX: Merge, not overwrite previous options
+ if not option_group:
+ options = ['']
+ elif isinstance(option_group, list):
+ options = option_group
+ else:
+ options = [option_group]
+ self._gconf_client.set_list(_OPTIONS_KEY, gconf.VALUE_STRING, options)
+ self._configrec.set_options(options)
+ self._configrec.activate(self._engine)
+
+ def set_layouts(self, layouts):
+ """Sets the supplied keyboard layouts (with variants)"""
+ if layouts is None or not layouts:
+ return
+ self._gconf_client.set_list(_LAYOUTS_KEY, gconf.VALUE_STRING, layouts)
+ layouts_list = []
+ variants_list = []
+ for layout in layouts:
+ layouts_list.append(layout.split('(')[0])
+ variants_list.append(layout.split('(')[1][:-1])
+
+ self._configrec.set_layouts(layouts_list)
+ self._configrec.set_variants(variants_list)
+ self._configrec.activate(self._engine)
+
diff --git a/shell/extensions/cpsection/keyboard/view.py b/shell/extensions/cpsection/keyboard/view.py
new file mode 100644
index 0000000..dd62a85
--- /dev/null
+++ b/shell/extensions/cpsection/keyboard/view.py
@@ -0,0 +1,426 @@
+# Copyright (C) 2009, OLPC
+# Author: Sayamindu Dasgupta <sayamindu@laptop.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import gtk
+import gobject
+import pango
+import logging
+from gettext import gettext as _
+
+from sugar.graphics import style
+from sugar.graphics.icon import Icon
+
+from jarabe.controlpanel.sectionview import SectionView
+
+CLASS = 'Language'
+ICON = 'module-keyboard'
+TITLE = _('Keyboard')
+
+_APPLY_TIMEOUT = 500
+
+#TODO: This cpsection adds checks for xklavier in bin/sugar-session and
+# src/jarabe/controlpanel/gui.py. We should get rid of these checks
+# once python-xklavier has been packaged for all major distributions
+# For more information, see: http://dev.sugarlabs.org/ticket/407
+
+class LayoutCombo(gtk.HBox):
+
+ """
+ Custom GTK widget with two comboboxes side by side, one for layout, and
+ the other for variants for the selected layout.
+ """
+
+ __gsignals__ = {
+ 'selection-changed' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
+ (gobject.TYPE_STRING, gobject.TYPE_INT))
+ }
+
+ def __init__(self, keyboard_manager, n):
+ gtk.HBox.__init__(self)
+ self._keyboard_manager = keyboard_manager
+ self._index = n
+
+ self.set_border_width(style.DEFAULT_SPACING)
+ self.set_spacing(style.DEFAULT_SPACING)
+
+ label = gtk.Label(' <b>%s</b> ' % str(n+1))
+ label.set_use_markup(True)
+ label.modify_fg(gtk.STATE_NORMAL,
+ style.COLOR_SELECTION_GREY.get_gdk_color())
+ label.set_alignment(0.5, 0.5)
+ self.pack_start(label, expand=False)
+
+ self._klang_store = gtk.ListStore(gobject.TYPE_STRING, \
+ gobject.TYPE_STRING)
+ for description, name in self._keyboard_manager.get_languages():
+ self._klang_store.append([name, description])
+
+ self._klang_combo = gtk.ComboBox(model = self._klang_store)
+ self._klang_combo_changed_id = \
+ self._klang_combo.connect('changed', self._klang_combo_changed_cb)
+ cell = gtk.CellRendererText()
+ cell.props.ellipsize = pango.ELLIPSIZE_MIDDLE
+ cell.props.ellipsize_set = True
+ self._klang_combo.pack_start(cell)
+ self._klang_combo.add_attribute(cell, 'text', 1)
+ self.pack_start(self._klang_combo, expand=True, fill = True)
+
+ self._kvariant_store = None
+ self._kvariant_combo = gtk.ComboBox(model = None)
+ self._kvariant_combo_changed_id = \
+ self._kvariant_combo.connect('changed', \
+ self._kvariant_combo_changed_cb)
+ cell = gtk.CellRendererText()
+ cell.props.ellipsize = pango.ELLIPSIZE_MIDDLE
+ cell.props.ellipsize_set = True
+ self._kvariant_combo.pack_start(cell)
+ self._kvariant_combo.add_attribute(cell, 'text', 1)
+ self.pack_start(self._kvariant_combo, expand=True, fill = True)
+
+ self._klang_combo.set_active(self._index)
+
+ def select_layout(self, layout):
+ """Select a given keyboard layout and show appropriate variants"""
+ self._kvariant_combo.handler_block(self._kvariant_combo_changed_id)
+ for i in range(0, len(self._klang_store)):
+ self._klang_combo.set_active(i)
+ for j in range(0, len(self._kvariant_store)):
+ if self._kvariant_store[j][0] == layout:
+ self._kvariant_combo.set_active(j)
+ self._kvariant_combo.handler_unblock(\
+ self._kvariant_combo_changed_id)
+ return True
+
+ self._kvariant_combo.handler_unblock(self._kvariant_combo_changed_id)
+ self._klang_combo.set_active(0)
+ return False
+
+ def get_layout(self):
+ """Gets the selected layout (with variant)"""
+ it = self._kvariant_combo.get_active_iter()
+ model = self._kvariant_combo.get_model()
+ return model.get(it, 0)[0]
+
+ def _set_kvariant_store(self, lang):
+ self._kvariant_store = gtk.ListStore(gobject.TYPE_STRING, \
+ gobject.TYPE_STRING)
+ layouts = self._keyboard_manager.get_layouts_for_language(lang)
+ for description, name in layouts:
+ self._kvariant_store.append([name, description])
+ self._kvariant_combo.set_model(self._kvariant_store)
+ self._kvariant_combo.set_active(0)
+
+ def _klang_combo_changed_cb(self, combobox):
+ it = combobox.get_active_iter()
+ model = combobox.get_model()
+ lang = model.get(it, 0)[0]
+ self._set_kvariant_store(lang)
+
+ def _kvariant_combo_changed_cb(self, combobox):
+ it = combobox.get_active_iter()
+ model = combobox.get_model()
+ layout = model.get(it, 0)[0]
+ self.emit('selection-changed', layout, self._index)
+
+
+class Keyboard(SectionView):
+ def __init__(self, model, alerts):
+ SectionView.__init__(self)
+
+ self._model = model
+
+ self._kmodel = None
+ self._selected_kmodel = None
+
+ self._klayouts = []
+ self._selected_klayouts = []
+
+ self._group_switch_option = None
+ self._selected_group_switch_option = None
+
+ self.set_border_width(style.DEFAULT_SPACING * 2)
+ self.set_spacing(style.DEFAULT_SPACING)
+
+ self._layout_table = gtk.Table(rows = 4, columns = 2, \
+ homogeneous = False)
+
+ self._keyboard_manager = model.KeyboardManager(self.get_display())
+ self._layout_combo_list = []
+ self._layout_addremovebox_list = []
+
+ scrollwindow = gtk.ScrolledWindow()
+ scrollwindow.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+ self.pack_start(scrollwindow, expand=True)
+ scrollwindow.show()
+
+ self._vbox = gtk.VBox()
+ scrollwindow.add_with_viewport(self._vbox)
+
+ self.__kmodel_sid = None
+ self.__layout_sid = None
+ self.__group_switch_sid = None
+
+ self._setup_kmodel()
+ self._setup_layouts()
+ self._setup_group_switch_option()
+
+ self._vbox.show()
+
+ def _setup_kmodel(self):
+ """Adds the controls for changing the keyboard model"""
+ separator_kmodel = gtk.HSeparator()
+ self._vbox.pack_start(separator_kmodel, expand=False)
+ separator_kmodel.show_all()
+
+ label_kmodel = gtk.Label(_('Keyboard Model'))
+ label_kmodel.set_alignment(0, 0)
+ self._vbox.pack_start(label_kmodel, expand=False)
+ label_kmodel.show_all()
+
+ box_kmodel = gtk.VBox()
+ box_kmodel.set_border_width(style.DEFAULT_SPACING * 2)
+ box_kmodel.set_spacing(style.DEFAULT_SPACING)
+
+ kmodel_store = gtk.ListStore(gobject.TYPE_STRING, \
+ gobject.TYPE_STRING)
+ for description, name in self._keyboard_manager.get_models():
+ kmodel_store.append([name, description])
+
+ kmodel_combo = gtk.ComboBox(model = kmodel_store)
+ cell = gtk.CellRendererText()
+ cell.props.ellipsize = pango.ELLIPSIZE_MIDDLE
+ cell.props.ellipsize_set = True
+ kmodel_combo.pack_start(cell)
+ kmodel_combo.add_attribute(cell, 'text', 1)
+
+ self._kmodel = self._keyboard_manager.get_current_model()
+ if self._kmodel is not None:
+ for row in kmodel_store:
+ if self._kmodel in row[0]:
+ kmodel_combo.set_active_iter(row.iter)
+ break
+
+ box_kmodel.pack_start(kmodel_combo, expand = False)
+ self._vbox.pack_start(box_kmodel, expand=False)
+ box_kmodel.show_all()
+
+ kmodel_combo.connect('changed', self.__kmodel_changed_cb)
+
+ def __kmodel_changed_cb(self, combobox):
+ if self.__kmodel_sid is not None:
+ gobject.source_remove(self.__kmodel_sid)
+ self.__kmodel_sid = gobject.timeout_add(_APPLY_TIMEOUT,
+ self.__kmodel_timeout_cb, combobox)
+
+ def __kmodel_timeout_cb(self, combobox):
+ it = combobox.get_active_iter()
+ model = combobox.get_model()
+ self._selected_kmodel = model.get(it, 0)[0]
+ if self._selected_kmodel == self._keyboard_manager.get_current_model():
+ return
+ try:
+ self._keyboard_manager.set_model(self._selected_kmodel)
+ except Exception:
+ logging.exception('Could not set new keyboard model')
+
+ return False
+
+ def _setup_group_switch_option(self):
+ """Adds the controls for changing the group switch option of keyboard"""
+ separator_group_option = gtk.HSeparator()
+ self._vbox.pack_start(separator_group_option, expand=False)
+ separator_group_option.show_all()
+
+ label_group_option = gtk.Label(_('Key(s) to change layout'))
+ label_group_option.set_alignment(0, 0)
+ self._vbox.pack_start(label_group_option, expand=False)
+ label_group_option.show_all()
+
+ box_group_option = gtk.VBox()
+ box_group_option.set_border_width(style.DEFAULT_SPACING * 2)
+ box_group_option.set_spacing(style.DEFAULT_SPACING)
+
+ group_option_store = gtk.ListStore(gobject.TYPE_STRING, \
+ gobject.TYPE_STRING)
+ for description, name in self._keyboard_manager.get_options_group():
+ group_option_store.append([name, description])
+
+ group_option_combo = gtk.ComboBox(model = group_option_store)
+ cell = gtk.CellRendererText()
+ cell.props.ellipsize = pango.ELLIPSIZE_MIDDLE
+ cell.props.ellipsize_set = True
+ group_option_combo.pack_start(cell)
+ group_option_combo.add_attribute(cell, 'text', 1)
+
+ self._group_switch_option = \
+ self._keyboard_manager.get_current_option_group()
+ if not self._group_switch_option:
+ group_option_combo.set_active(0)
+ else:
+ found = False
+ for row in group_option_store:
+ if self._group_switch_option in row[0]:
+ group_option_combo.set_active_iter(row.iter)
+ found = True
+ break
+ if not found:
+ group_option_combo.set_active(0)
+
+ box_group_option.pack_start(group_option_combo, expand = False)
+ self._vbox.pack_start(box_group_option, expand=False)
+ box_group_option.show_all()
+
+ group_option_combo.connect('changed', \
+ self.__group_switch_changed_cb)
+
+ def __group_switch_changed_cb(self, combobox):
+ if self.__group_switch_sid is not None:
+ gobject.source_remove(self.__group_switch_sid)
+ self.__group_switch_sid = gobject.timeout_add(_APPLY_TIMEOUT,
+ self.__group_switch_timeout_cb, combobox)
+
+ def __group_switch_timeout_cb(self, combobox):
+ it = combobox.get_active_iter()
+ model = combobox.get_model()
+ self._selected_group_switch_option = model.get(it, 0)[0]
+ if self._selected_group_switch_option == \
+ self._keyboard_manager.get_current_option_group():
+ return
+ try:
+ self._keyboard_manager.set_option_group(\
+ self._selected_group_switch_option)
+ except Exception:
+ logging.exception('Could not set new keyboard group switch option')
+
+
+ return False
+
+ def _setup_layouts(self):
+ """Adds the controls for changing the keyboard layouts"""
+ separator_klayout = gtk.HSeparator()
+ self._vbox.pack_start(separator_klayout, expand=False)
+ separator_klayout.show_all()
+
+ label_klayout = gtk.Label(_('Keyboard Layout(s)'))
+ label_klayout.set_alignment(0, 0)
+ label_klayout.show_all()
+ self._vbox.pack_start(label_klayout, expand=False)
+
+ self._klayouts = self._keyboard_manager.get_current_layouts()
+ for i in range(0, self._keyboard_manager.get_max_layouts()):
+ add_remove_box = self.__create_add_remove_box()
+ self._layout_addremovebox_list.append(add_remove_box)
+ self._layout_table.attach(add_remove_box, 1, 2, i, i+1)
+
+ layout_combo = LayoutCombo(self._keyboard_manager, i)
+ layout_combo.connect('selection-changed', \
+ self.__layout_combo_selection_changed_cb)
+ self._layout_combo_list.append(layout_combo)
+ self._layout_table.attach(layout_combo, 0, 1, i, i+1)
+
+ if i < len(self._klayouts):
+ layout_combo.show_all()
+ layout_combo.select_layout(self._klayouts[i])
+
+ self._vbox.pack_start(self._layout_table, expand=False)
+ self._layout_table.set_size_request(self._vbox.size_request()[0], -1)
+ self._layout_table.show()
+ self._update_klayouts()
+
+ def __determine_add_remove_box_visibility(self):
+ i = 1
+ for box in self._layout_addremovebox_list:
+ if not i == len(self._selected_klayouts):
+ box.props.visible = False
+ else:
+ box.show_all()
+ if i == 1:
+ # First row - no need for showing remove btn
+ add, remove = box.get_children()
+ remove.props.visible = False
+ if i == self._keyboard_manager.get_max_layouts():
+ # Last row - no need for showing add btn
+ add, remove = box.get_children()
+ add.props.visible = False
+ i += 1
+
+ def __create_add_remove_box(self):
+ '''Creates gtk.Hbox with add/remove buttons'''
+ add_icon = Icon(icon_name='list-add')
+
+ add_button = gtk.Button()
+ add_button.set_image(add_icon)
+ add_button.connect('clicked',
+ self.__add_button_clicked_cb)
+
+ remove_icon = Icon(icon_name='list-remove')
+ remove_button = gtk.Button()
+ remove_button.set_image(remove_icon)
+ remove_button.connect('clicked',
+ self.__remove_button_clicked_cb)
+
+ add_remove_box = gtk.HButtonBox()
+ add_remove_box.set_layout(gtk.BUTTONBOX_START)
+ add_remove_box.set_spacing(10)
+ add_remove_box.pack_start(add_button)
+ add_remove_box.pack_start(remove_button)
+
+ return add_remove_box
+
+ def __layout_combo_selection_changed_cb(self, combo, layout, index):
+ self._update_klayouts()
+
+ def __add_button_clicked_cb(self, button):
+ self._layout_combo_list[len(self._selected_klayouts)].show_all()
+ self._update_klayouts()
+
+ def __remove_button_clicked_cb(self, button):
+ self._layout_combo_list[len(self._selected_klayouts) - 1].hide()
+ self._update_klayouts()
+
+ def _update_klayouts(self):
+ """Responds to any changes in the keyboard layout options"""
+ self._selected_klayouts = []
+ for combo in self._layout_combo_list:
+ if combo.props.visible:
+ self._selected_klayouts.append(combo.get_layout())
+
+ self.__determine_add_remove_box_visibility()
+
+ if self.__layout_sid is not None:
+ gobject.source_remove(self.__layout_sid)
+ self.__layout_sid = gobject.timeout_add(_APPLY_TIMEOUT,
+ self.__layout_timeout_cb)
+
+ def __layout_timeout_cb(self):
+ if self._selected_klayouts == \
+ self._keyboard_manager.get_current_layouts():
+ return
+ try:
+ self._keyboard_manager.set_layouts(self._selected_klayouts)
+ except Exception:
+ logging.exception('Could not set new keyboard layouts')
+
+ return False
+
+
+ def undo(self):
+ """Reverts back to the original keyboard configuration"""
+ self._keyboard_manager.set_model(self._kmodel)
+ self._keyboard_manager.set_layouts(self._klayouts)
+ self._keyboard_manager.set_option_group(self._group_switch_option)
+
diff --git a/shell/extensions/cpsection/language/Makefile.am b/shell/extensions/cpsection/language/Makefile.am
new file mode 100644
index 0000000..209fc32
--- /dev/null
+++ b/shell/extensions/cpsection/language/Makefile.am
@@ -0,0 +1,6 @@
+sugardir = $(pkgdatadir)/extensions/cpsection/language
+
+sugar_PYTHON = \
+ __init__.py \
+ model.py \
+ view.py
diff --git a/shell/extensions/cpsection/language/__init__.py b/shell/extensions/cpsection/language/__init__.py
new file mode 100644
index 0000000..a8f9f08
--- /dev/null
+++ b/shell/extensions/cpsection/language/__init__.py
@@ -0,0 +1,22 @@
+# Copyright (C) 2008, OLPC
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from gettext import gettext as _
+
+CLASS = 'Language'
+ICON = 'module-language'
+TITLE = _('Language')
+
diff --git a/shell/extensions/cpsection/language/model.py b/shell/extensions/cpsection/language/model.py
new file mode 100644
index 0000000..4f3686f
--- /dev/null
+++ b/shell/extensions/cpsection/language/model.py
@@ -0,0 +1,151 @@
+# Copyright (C) 2007, 2008 One Laptop Per Child
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+#
+# The language config is based on the system-config-language
+# (http://fedoraproject.org/wiki/SystemConfig/language) tool
+# Parts of the code were reused.
+#
+
+import os
+import locale
+from gettext import gettext as _
+import subprocess
+
+_default_lang = '%s.%s' % locale.getdefaultlocale()
+_standard_msg = _("Could not access ~/.i18n. Create standard settings.")
+
+def read_all_languages():
+ fdp = subprocess.Popen(['locale', '-av'], stdout=subprocess.PIPE)
+ lines = fdp.stdout.read().split('\n')
+ locales = []
+
+ for line in lines:
+ if line.find('locale:') != -1:
+ locale = line.split()[1]
+ elif line.find('language |') != -1:
+ lang = line.lstrip('language |')
+ elif line.find('territory |') != -1:
+ territory = line.lstrip('territory |')
+ if locale.endswith('utf8') and len(lang):
+ locales.append((lang, territory, locale))
+
+ #FIXME: This is a temporary workaround for locales that are essential to
+ # OLPC, but are not in Glibc yet.
+ locales.append(('Kreyol', 'Haiti', 'ht_HT.utf8'))
+ locales.append(('Dari', 'Afghanistan', 'fa_AF.utf8'))
+ locales.append(('Pashto', 'Afghanistan', 'ps_AF.utf8'))
+
+ locales.sort()
+ return locales
+
+def _initialize():
+ if set_languages.__doc__ is None:
+ # when running under 'python -OO', all __doc__ fields are None,
+ # so += would fail -- and this function would be unnecessary anyway.
+ return
+ languages = read_all_languages()
+ set_languages.__doc__ += '\n'
+ for lang in languages:
+ set_languages.__doc__ += '%s \n' % (lang[0].replace(' ', '_') + '/' +
+ lang[1].replace(' ', '_'))
+
+def _write_i18n(langs):
+ colon = ':'
+ langstr = colon.join(langs)
+ path = os.path.join(os.environ.get("HOME"), '.i18n')
+ if not os.access(path, os.W_OK):
+ print _standard_msg
+ fd = open(path, 'w')
+ fd.write('LANG="%s"\n' % _default_lang)
+ fd.write('LANGUAGE="%s"\n' % _default_lang)
+ fd.close()
+ else:
+ fd = open(path, 'w')
+ fd.write('LANG="%s"\n' % langs[0].strip("\n"))
+ fd.write('LANGUAGE="%s"\n' % langstr)
+ fd.close()
+
+def get_languages():
+ path = os.path.join(os.environ.get("HOME"), '.i18n')
+ if not os.access(path, os.R_OK):
+ print _standard_msg
+ fd = open(path, 'w')
+ fd.write('LANG="%s"\n' % _default_lang)
+ fd.write('LANGUAGE="%s"\n' % _default_lang)
+ fd.close()
+ return [_default_lang]
+
+ fd = open(path, "r")
+ lines = fd.readlines()
+ fd.close()
+
+ langlist = None
+
+ for line in lines:
+ if line.startswith("LANGUAGE="):
+ lang = line[9:].replace('"', '')
+ lang = lang.strip()
+ langlist = lang.split(':')
+ elif line.startswith("LANG="):
+ lang = line[5:].replace('"', '')
+
+ # There might be cases where .i18n may not contain a LANGUAGE field
+ if langlist == None:
+ return [lang]
+ else:
+ return langlist
+
+def print_languages():
+ codes = get_languages()
+
+ languages = read_all_languages()
+ for code in codes:
+ found_lang = False
+ for lang in languages:
+ if lang[2].split('.')[0] == code.split('.')[0]:
+ print lang[0].replace(' ', '_') + '/' + \
+ lang[1].replace(' ', '_')
+ found_lang = True
+ break
+ if not found_lang:
+ print (_("Language for code=%s could not be determined.") % code)
+
+def set_languages(languages):
+ """Set the system language.
+ languages :
+ """
+ if isinstance(languages, str):
+ # This came from the commandline
+ #TODO: Support multiple languages from the command line
+ if languages.endswith('utf8'):
+ _write_i18n(languages)
+ return 1
+ else:
+ langs = read_all_languages()
+ for lang, territory, locale in langs:
+ code = lang.replace(' ', '_') + '/' \
+ + territory.replace(' ', '_')
+ if code == languages:
+ _write_i18n(locale)
+ return 1
+ print (_("Sorry I do not speak \'%s\'.") % languages)
+ else:
+ _write_i18n(languages)
+
+# inilialize the docstrings for the language
+_initialize()
+
diff --git a/shell/extensions/cpsection/language/view.py b/shell/extensions/cpsection/language/view.py
new file mode 100644
index 0000000..d1a49cf
--- /dev/null
+++ b/shell/extensions/cpsection/language/view.py
@@ -0,0 +1,282 @@
+# Copyright (C) 2008, OLPC
+# Copyright (C) 2009, Simon Schampijer
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import gtk
+import gobject
+import gettext
+
+from sugar.graphics import style
+from sugar.graphics.icon import Icon
+
+from jarabe.controlpanel.sectionview import SectionView
+from jarabe.controlpanel.inlinealert import InlineAlert
+
+_translate_language = lambda msg: gettext.dgettext('iso_639', msg)
+_translate_country = lambda msg: gettext.dgettext('iso_3166', msg)
+
+CLASS = 'Language'
+ICON = 'module-language'
+TITLE = gettext.gettext('Language')
+
+class Language(SectionView):
+ def __init__(self, model, alerts):
+ SectionView.__init__(self)
+
+ self._model = model
+ self.restart_alerts = alerts
+ self._lang_sid = 0
+ self._selected_lang_count = 0
+ self._labels = []
+ self._stores = []
+ self._comboboxes = []
+ self._add_remove_boxes = []
+ self._changed = False
+ self._cursor_change_handler = None
+
+ self._available_locales = self._model.read_all_languages()
+ self._selected_locales = self._model.get_languages()
+
+ self.set_border_width(style.DEFAULT_SPACING * 2)
+ self.set_spacing(style.DEFAULT_SPACING)
+
+ explanation = gettext.gettext("Add languages in the order you prefer." \
+ " If a translation is not available,"\
+ " the next in the list will be used.")
+ self._text = gtk.Label(explanation)
+ self._text.set_width_chars(100)
+ self._text.set_line_wrap(True)
+ self._text.set_alignment(0, 0)
+ self.pack_start(self._text, False)
+ self._text.show()
+
+ scrolled = gtk.ScrolledWindow()
+ scrolled.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+ scrolled.show()
+ self.pack_start(scrolled, expand=True)
+
+ self._table = gtk.Table(rows=1, columns=3, homogeneous=False)
+ self._table.set_border_width(style.DEFAULT_SPACING * 2)
+ self._table.show()
+ scrolled.add_with_viewport(self._table)
+
+ self._lang_alert_box = gtk.HBox(spacing=style.DEFAULT_SPACING)
+ self.pack_start(self._lang_alert_box, False)
+
+ self._lang_alert = InlineAlert()
+ self._lang_alert_box.pack_start(self._lang_alert)
+ if 'lang' in self.restart_alerts:
+ self._lang_alert.props.msg = self.restart_msg
+ self._lang_alert.show()
+ self._lang_alert_box.show()
+
+ self.setup()
+
+ def _add_row(self, locale_code=None):
+ '''Adds a row to the table'''
+
+ self._selected_lang_count += 1
+
+ self._table.resize(self._selected_lang_count, 3)
+
+ label = gtk.Label(str=str(self._selected_lang_count))
+ label.modify_fg(gtk.STATE_NORMAL,
+ style.COLOR_SELECTION_GREY.get_gdk_color())
+ self._labels.append(label)
+ self._attach_to_table(label, 0, 1, padding=1)
+ label.show()
+
+
+ store = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
+ for language, country, code in self._available_locales:
+ description = '%s (%s)' % (_translate_language(language), \
+ _translate_country(country))
+ store.append([code, description])
+
+ combobox = gtk.ComboBox(model=store)
+ cell = gtk.CellRendererText()
+ combobox.pack_start(cell)
+ combobox.add_attribute(cell, 'text', 1)
+
+ if locale_code:
+ for row in store:
+ lang = locale_code.split('.')[0]
+ lang_column = row[0].split('.')[0]
+ if lang in lang_column:
+ combobox.set_active_iter(row.iter)
+ break
+ else:
+ combobox.set_active(1)
+
+ combobox.connect('changed', self.__combobox_changed_cb)
+
+ self._stores.append(store)
+ self._comboboxes.append(combobox)
+ self._attach_to_table(combobox, 1, 2, yoptions=gtk.SHRINK)
+
+ add_remove_box = self._create_add_remove_box()
+ self._add_remove_boxes.append(add_remove_box)
+ self._attach_to_table(add_remove_box, 2, 3)
+
+ add_remove_box.show_all()
+
+ if self._selected_lang_count > 1:
+ previous_add_removes = self._add_remove_boxes[-2]
+ previous_add_removes.hide_all()
+
+ self._determine_add_remove_visibility()
+
+ combobox.show()
+
+ def _attach_to_table(self, widget, row, column, padding=20, \
+ yoptions=gtk.FILL):
+ self._table.attach(widget, row, column, \
+ self._selected_lang_count - 1, self._selected_lang_count, \
+ xoptions=gtk.FILL, yoptions=yoptions, xpadding=padding, \
+ ypadding=padding)
+
+ def _delete_last_row(self):
+ '''Deletes the last row of the table'''
+
+ self._selected_lang_count -= 1
+
+ label, add_remove_box, combobox, store_ = self._get_last_row()
+
+ label.destroy()
+ add_remove_box.destroy()
+ combobox.destroy()
+
+ self._table.resize(self._selected_lang_count, 3)
+
+ self._add_remove_boxes[-1].show_all()
+
+ def _get_last_row(self):
+ label = self._labels.pop()
+ add_remove_box = self._add_remove_boxes.pop()
+ combobox = self._comboboxes.pop()
+ store = self._stores.pop()
+
+ return label, add_remove_box, combobox, store
+
+ def setup(self):
+ for locale in self._selected_locales:
+ self._add_row(locale_code=locale)
+
+ def undo(self):
+ self._model.undo()
+ self._lang_alert.hide()
+
+ def _create_add_remove_box(self):
+ '''Creates gtk.Hbox with add/remove buttons'''
+ add_icon = Icon(icon_name='list-add')
+
+ add_button = gtk.Button()
+ add_button.set_image(add_icon)
+ add_button.connect('clicked',
+ self.__add_button_clicked_cb)
+
+ remove_icon = Icon(icon_name='list-remove')
+ remove_button = gtk.Button()
+ remove_button.set_image(remove_icon)
+ remove_button.connect('clicked',
+ self.__remove_button_clicked_cb)
+
+ add_remove_box = gtk.HButtonBox()
+ add_remove_box.set_layout(gtk.BUTTONBOX_START)
+ add_remove_box.set_spacing(10)
+ add_remove_box.pack_start(add_button)
+ add_remove_box.pack_start(remove_button)
+
+ return add_remove_box
+
+ def __add_button_clicked_cb(self, button):
+ self._add_row()
+ self._check_change()
+
+ def __remove_button_clicked_cb(self, button):
+ self._delete_last_row()
+ self._check_change()
+
+ def __combobox_changed_cb(self, button):
+ self._check_change()
+
+ def _check_change(self):
+ selected_langs = self._get_selected_langs()
+ last_lang = selected_langs[-1]
+
+ self._determine_add_remove_visibility(last_lang = last_lang)
+
+ self._changed = (selected_langs != self._selected_locales)
+
+ if self._changed == False:
+ # The user reverted back to the original config
+ self.needs_restart = False
+ if 'lang' in self.restart_alerts:
+ self.restart_alerts.remove('lang')
+ self._lang_alert.hide()
+ if self._lang_sid:
+ gobject.source_remove(self._lang_sid)
+ self._model.undo()
+ return False
+
+ if self._lang_sid:
+ gobject.source_remove(self._lang_sid)
+ self._lang_sid = gobject.timeout_add(self._APPLY_TIMEOUT,
+ self.__lang_timeout_cb,
+ selected_langs)
+
+ def _get_selected_langs(self):
+ new_codes = []
+ for combobox in self._comboboxes:
+ it = combobox.get_active_iter()
+ model = combobox.get_model()
+ lang_code = model.get(it, 0)[0]
+ new_codes.append(lang_code)
+
+ return new_codes
+
+ def _determine_add_remove_visibility(self, last_lang = None):
+ # We should not let users add fallback languages for English (USA)
+ # This is because the software is not usually _translated_ into English
+ # which means that the fallback gets selected automatically
+
+ if last_lang is None:
+ selected_langs = self._get_selected_langs()
+ last_lang = selected_langs[-1]
+
+ add_remove_box = self._add_remove_boxes[-1]
+ buttons = add_remove_box.get_children()
+ add_button, remove_button = buttons
+
+ if last_lang.startswith('en_US'):
+ add_button.props.visible = False
+ else:
+ add_button.props.visible = True
+
+ if self._selected_lang_count == 1:
+ remove_button.props.visible = False
+ else:
+ remove_button.props.visible = True
+
+
+ def __lang_timeout_cb(self, codes):
+ self._lang_sid = 0
+ self._model.set_languages(codes)
+ self.restart_alerts.append('lang')
+ self.needs_restart = True
+ self._lang_alert.props.msg = self.restart_msg
+ self._lang_alert.show()
+ return False
diff --git a/shell/extensions/cpsection/modemconfiguration/Makefile.am b/shell/extensions/cpsection/modemconfiguration/Makefile.am
new file mode 100644
index 0000000..3e2613e
--- /dev/null
+++ b/shell/extensions/cpsection/modemconfiguration/Makefile.am
@@ -0,0 +1,6 @@
+sugardir = $(pkgdatadir)/extensions/cpsection/modemconfiguration
+
+sugar_PYTHON = \
+ __init__.py \
+ model.py \
+ view.py
diff --git a/shell/extensions/cpsection/modemconfiguration/__init__.py b/shell/extensions/cpsection/modemconfiguration/__init__.py
new file mode 100644
index 0000000..8a219dc
--- /dev/null
+++ b/shell/extensions/cpsection/modemconfiguration/__init__.py
@@ -0,0 +1,22 @@
+# Copyright (C) 2009 Paraguay Educa, Martin Abente
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 US
+
+from gettext import gettext as _
+
+CLASS = 'ModemConfiguration'
+ICON = 'module-modemconfiguration'
+TITLE = _('Modem Configuration')
+
diff --git a/shell/extensions/cpsection/modemconfiguration/model.py b/shell/extensions/cpsection/modemconfiguration/model.py
new file mode 100755
index 0000000..2545ce1
--- /dev/null
+++ b/shell/extensions/cpsection/modemconfiguration/model.py
@@ -0,0 +1,70 @@
+# Copyright (C) 2009 Paraguay Educa, Martin Abente
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 US
+
+import gconf
+
+from jarabe.model.network import GSM_USERNAME_PATH, GSM_PASSWORD_PATH, \
+ GSM_NUMBER_PATH, GSM_APN_PATH, GSM_PIN_PATH, \
+ GSM_PUK_PATH
+
+def get_username():
+ client = gconf.client_get_default()
+ return client.get_string(GSM_USERNAME_PATH) or ''
+
+def get_password():
+ client = gconf.client_get_default()
+ return client.get_string(GSM_PASSWORD_PATH) or ''
+
+def get_number():
+ client = gconf.client_get_default()
+ return client.get_string(GSM_NUMBER_PATH) or ''
+
+def get_apn():
+ client = gconf.client_get_default()
+ return client.get_string(GSM_APN_PATH) or ''
+
+def get_pin():
+ client = gconf.client_get_default()
+ return client.get_string(GSM_PIN_PATH) or ''
+
+def get_puk():
+ client = gconf.client_get_default()
+ return client.get_string(GSM_PUK_PATH) or ''
+
+def set_username(username):
+ client = gconf.client_get_default()
+ client.set_string(GSM_USERNAME_PATH, username)
+
+def set_password(password):
+ client = gconf.client_get_default()
+ client.set_string(GSM_PASSWORD_PATH, password)
+
+def set_number(number):
+ client = gconf.client_get_default()
+ client.set_string(GSM_NUMBER_PATH, number)
+
+def set_apn(apn):
+ client = gconf.client_get_default()
+ client.set_string(GSM_APN_PATH, apn)
+
+def set_pin(pin):
+ client = gconf.client_get_default()
+ client.set_string(GSM_PIN_PATH, pin)
+
+def set_puk(puk):
+ client = gconf.client_get_default()
+ client.set_string(GSM_PUK_PATH, puk)
+
diff --git a/shell/extensions/cpsection/modemconfiguration/view.py b/shell/extensions/cpsection/modemconfiguration/view.py
new file mode 100644
index 0000000..b236f3f
--- /dev/null
+++ b/shell/extensions/cpsection/modemconfiguration/view.py
@@ -0,0 +1,250 @@
+# Copyright (C) 2009 Paraguay Educa, Martin Abente
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 US
+
+import os
+import logging
+from gettext import gettext as _
+
+import gtk
+import gobject
+
+from sugar.graphics import style
+
+from jarabe.controlpanel.sectionview import SectionView
+
+APPLY_TIMEOUT = 1000
+
+class EntryWithLabel(gtk.HBox):
+ __gtype_name__ = "SugarEntryWithLabel"
+
+ def __init__(self, label_text):
+ gtk.HBox.__init__(self, spacing=style.DEFAULT_SPACING)
+
+ self._timeout_sid = 0
+ self._changed_handler = None
+ self._is_valid = True
+
+ self.label = gtk.Label(label_text)
+ self.label.modify_fg(gtk.STATE_NORMAL,
+ style.COLOR_SELECTION_GREY.get_gdk_color())
+ self.label.set_alignment(1, 0.5)
+ self.pack_start(self.label, expand=False)
+ self.label.show()
+
+ self._entry = gtk.Entry(25)
+ self._entry.connect('changed', self.__entry_changed_cb)
+ self._entry.set_width_chars(25)
+ self.pack_start(self._entry, expand=False)
+ self._entry.show()
+
+ def __entry_changed_cb(self, widget, data=None):
+ if self._timeout_sid:
+ gobject.source_remove(self._timeout_sid)
+ self._timeout_sid = gobject.timeout_add(APPLY_TIMEOUT,
+ self.__timeout_cb)
+
+ def __timeout_cb(self):
+ self._timeout_sid = 0
+
+ if self._entry.get_text() == self.get_value():
+ return False
+
+ try:
+ self.set_value(self._entry.get_text())
+ except ValueError:
+ self._is_valid = False
+ else:
+ self._is_valid = True
+
+ self.notify('is-valid')
+
+ return False
+
+ def set_text_from_model(self):
+ self._entry.set_text(self.get_value())
+
+ def get_value(self):
+ raise NotImplementedError
+
+ def set_value(self):
+ raise NotImplementedError
+
+ def _get_is_valid(self):
+ return self._is_valid
+ is_valid = gobject.property(type=bool, getter=_get_is_valid, default=True)
+
+class UsernameEntry(EntryWithLabel):
+ def __init__(self, model):
+ EntryWithLabel.__init__(self, _('Username:'))
+ self._model = model
+
+ def get_value(self):
+ return self._model.get_username()
+
+ def set_value(self, username):
+ self._model.set_username(username)
+
+class PasswordEntry(EntryWithLabel):
+ def __init__(self, model):
+ EntryWithLabel.__init__(self, _('Password:'))
+ self._model = model
+
+ def get_value(self):
+ return self._model.get_password()
+
+ def set_value(self, password):
+ self._model.set_password(password)
+
+class NumberEntry(EntryWithLabel):
+ def __init__(self, model):
+ EntryWithLabel.__init__(self, _('Number:'))
+ self._model = model
+
+ def get_value(self):
+ return self._model.get_number()
+
+ def set_value(self, number):
+ self._model.set_number(number)
+
+class ApnEntry(EntryWithLabel):
+ def __init__(self, model):
+ EntryWithLabel.__init__(self, _('Access Point Name (APN):'))
+ self._model = model
+
+ def get_value(self):
+ return self._model.get_apn()
+
+ def set_value(self, apn):
+ self._model.set_apn(apn)
+
+class PinEntry(EntryWithLabel):
+ def __init__(self, model):
+ EntryWithLabel.__init__(self, _('Personal Identity Number (PIN):'))
+ self._model = model
+
+ def get_value(self):
+ return self._model.get_pin()
+
+ def set_value(self, pin):
+ self._model.set_pin(pin)
+
+class PukEntry(EntryWithLabel):
+ def __init__(self, model):
+ EntryWithLabel.__init__(self, _('Personal Unblocking Key (PUK):'))
+ self._model = model
+
+ def get_value(self):
+ return self._model.get_puk()
+
+ def set_value(self, puk):
+ self._model.set_puk(puk)
+
+
+class ModemConfiguration(SectionView):
+ def __init__(self, model, alerts=None):
+ SectionView.__init__(self)
+
+ self._model = model
+ self.restart_alerts = alerts
+
+ self.set_border_width(style.DEFAULT_SPACING)
+ self.set_spacing(style.DEFAULT_SPACING)
+ self._group = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
+
+ explanation = _("You will need to provide the following " \
+ "information to set up a mobile " \
+ "broadband connection to a cellular "\
+ "(3G) network.")
+ self._text = gtk.Label(explanation)
+ self._text.set_width_chars(100)
+ self._text.set_line_wrap(True)
+ self._text.set_alignment(0, 0)
+ self.pack_start(self._text, False)
+ self._text.show()
+
+ self._username_entry = UsernameEntry(model)
+ self._username_entry.connect('notify::is-valid',
+ self.__notify_is_valid_cb)
+ self._group.add_widget(self._username_entry.label)
+ self.pack_start(self._username_entry, expand=False)
+ self._username_entry.show()
+
+ self._password_entry = PasswordEntry(model)
+ self._password_entry.connect('notify::is-valid',
+ self.__notify_is_valid_cb)
+ self._group.add_widget(self._password_entry.label)
+ self.pack_start(self._password_entry, expand=False)
+ self._password_entry.show()
+
+ self._number_entry = NumberEntry(model)
+ self._number_entry.connect('notify::is-valid',
+ self.__notify_is_valid_cb)
+ self._group.add_widget(self._number_entry.label)
+ self.pack_start(self._number_entry, expand=False)
+ self._number_entry.show()
+
+ self._apn_entry = ApnEntry(model)
+ self._apn_entry.connect('notify::is-valid',
+ self.__notify_is_valid_cb)
+ self._group.add_widget(self._apn_entry.label)
+ self.pack_start(self._apn_entry, expand=False)
+ self._apn_entry.show()
+
+ self._pin_entry = PinEntry(model)
+ self._pin_entry.connect('notify::is-valid',
+ self.__notify_is_valid_cb)
+ self._group.add_widget(self._pin_entry.label)
+ self.pack_start(self._pin_entry, expand=False)
+ self._pin_entry.show()
+
+ self._puk_entry = PukEntry(model)
+ self._puk_entry.connect('notify::is-valid',
+ self.__notify_is_valid_cb)
+ self._group.add_widget(self._puk_entry.label)
+ self.pack_start(self._puk_entry, expand=False)
+ self._puk_entry.show()
+
+ self.setup()
+
+ def setup(self):
+ self._username_entry.set_text_from_model()
+ self._password_entry.set_text_from_model()
+ self._number_entry.set_text_from_model()
+ self._apn_entry.set_text_from_model()
+ self._pin_entry.set_text_from_model()
+ self._puk_entry.set_text_from_model()
+
+ self.needs_restart = False
+
+ def undo(self):
+ self._model.undo()
+
+ def _validate(self):
+ if self._username_entry.is_valid and \
+ self._password_entry.is_valid and \
+ self._number_entry.is_valid and \
+ self._apn_entry.is_valid and \
+ self._pin_entry.is_valid and \
+ self._puk_entry.is_valid:
+ self.props.is_valid = True
+ else:
+ self.props.is_valid = False
+
+ def __notify_is_valid_cb(self, entry, pspec):
+ if entry.is_valid:
+ self.needs_restart = True
+ self._validate()
+
diff --git a/shell/extensions/cpsection/network/Makefile.am b/shell/extensions/cpsection/network/Makefile.am
new file mode 100644
index 0000000..35fd27c
--- /dev/null
+++ b/shell/extensions/cpsection/network/Makefile.am
@@ -0,0 +1,6 @@
+sugardir = $(pkgdatadir)/extensions/cpsection/network
+
+sugar_PYTHON = \
+ __init__.py \
+ model.py \
+ view.py
diff --git a/shell/extensions/cpsection/network/__init__.py b/shell/extensions/cpsection/network/__init__.py
new file mode 100644
index 0000000..8fea274
--- /dev/null
+++ b/shell/extensions/cpsection/network/__init__.py
@@ -0,0 +1,25 @@
+# Copyright (C) 2006-2007, Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from gettext import gettext as _
+
+CLASS = 'Network'
+ICON = 'module-network'
+TITLE = _('Network')
+KEYWORDS = ['network', 'jabber', 'radio', 'server']
+
+
+
diff --git a/shell/extensions/cpsection/network/model.py b/shell/extensions/cpsection/network/model.py
new file mode 100644
index 0000000..e1c3dab
--- /dev/null
+++ b/shell/extensions/cpsection/network/model.py
@@ -0,0 +1,141 @@
+# Copyright (C) 2008 One Laptop Per Child
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+import dbus
+from gettext import gettext as _
+import gconf
+
+_NM_SERVICE = 'org.freedesktop.NetworkManager'
+_NM_PATH = '/org/freedesktop/NetworkManager'
+_NM_IFACE = 'org.freedesktop.NetworkManager'
+
+KEYWORDS = ['network', 'jabber', 'radio', 'server']
+
+class ReadError(Exception):
+ def __init__(self, value):
+ self.value = value
+ def __str__(self):
+ return repr(self.value)
+
+def get_jabber():
+ client = gconf.client_get_default()
+ return client.get_string('/desktop/sugar/collaboration/jabber_server')
+
+def print_jabber():
+ print get_jabber()
+
+def set_jabber(server):
+ """Set the jabber server
+ server : e.g. 'olpc.collabora.co.uk'
+ """
+ client = gconf.client_get_default()
+ client.set_string('/desktop/sugar/collaboration/jabber_server', server)
+
+ _restart_jabber()
+ return 0
+
+def _restart_jabber():
+ """Call Sugar Presence Service to restart Telepathy CMs.
+
+ This allows restarting the jabber server connection when we change it.
+ """
+ _PS_SERVICE = "org.laptop.Sugar.Presence"
+ _PS_INTERFACE = "org.laptop.Sugar.Presence"
+ _PS_PATH = "/org/laptop/Sugar/Presence"
+ bus = dbus.SessionBus()
+ try:
+ ps = dbus.Interface(bus.get_object(_PS_SERVICE, _PS_PATH),
+ _PS_INTERFACE)
+ except dbus.DBusException:
+ raise ReadError('%s service not available' % _PS_SERVICE)
+ ps.RestartServerConnection()
+
+def get_radio():
+ try:
+ bus = dbus.SystemBus()
+ obj = bus.get_object(_NM_SERVICE, _NM_PATH)
+ nm_props = dbus.Interface(obj, 'org.freedesktop.DBus.Properties')
+ except dbus.DBusException:
+ raise ReadError('%s service not available' % _NM_SERVICE)
+
+ state = nm_props.Get(_NM_IFACE, 'WirelessEnabled')
+ if state in (0, 1):
+ return state
+ else:
+ raise ReadError(_('State is unknown.'))
+
+def print_radio():
+ print ('off', 'on')[get_radio()]
+
+def set_radio(state):
+ """Turn Radio 'on' or 'off'
+ state : 'on/off'
+ """
+ if state == 'on' or state == 1:
+ try:
+ bus = dbus.SystemBus()
+ obj = bus.get_object(_NM_SERVICE, _NM_PATH)
+ nm_props = dbus.Interface(obj, 'org.freedesktop.DBus.Properties')
+ except dbus.DBusException:
+ raise ReadError('%s service not available' % _NM_SERVICE)
+ nm_props.Set(_NM_IFACE, 'WirelessEnabled', True)
+ elif state == 'off' or state == 0:
+ try:
+ bus = dbus.SystemBus()
+ obj = bus.get_object(_NM_SERVICE, _NM_PATH)
+ nm_props = dbus.Interface(obj, 'org.freedesktop.DBus.Properties')
+ except dbus.DBusException:
+ raise ReadError('%s service not available' % _NM_SERVICE)
+ nm_props.Set(_NM_IFACE, 'WirelessEnabled', False)
+ else:
+ raise ValueError(_("Error in specified radio argument use on/off."))
+
+ return 0
+
+def clear_registration():
+ """Clear the registration with the schoolserver
+ """
+ client = gconf.client_get_default()
+ client.set_string('/desktop/sugar/backup_url', '')
+ return 1
+
+def clear_networks():
+ """Clear saved passwords and network configurations.
+ """
+ pass
+
+def get_publish_information():
+ client = gconf.client_get_default()
+ publish = client.get_bool('/desktop/sugar/collaboration/publish_gadget')
+ return publish
+
+def print_publish_information():
+ print get_publish_information()
+
+def set_publish_information(value):
+ """ If set to true, Sugar will make you searchable for
+ the other users of the Jabber server.
+ value: 0/1
+ """
+ try:
+ value = (False, True)[int(value)]
+ except:
+ raise ValueError(_("Error in specified argument use 0/1."))
+
+ client = gconf.client_get_default()
+ client.set_bool('/desktop/sugar/collaboration/publish_gadget', value)
+ return 0
diff --git a/shell/extensions/cpsection/network/view.py b/shell/extensions/cpsection/network/view.py
new file mode 100644
index 0000000..588daeb
--- /dev/null
+++ b/shell/extensions/cpsection/network/view.py
@@ -0,0 +1,250 @@
+# Copyright (C) 2008, OLPC
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import gtk
+import gobject
+from gettext import gettext as _
+
+from sugar.graphics import style
+
+from jarabe.controlpanel.sectionview import SectionView
+from jarabe.controlpanel.inlinealert import InlineAlert
+
+CLASS = 'Network'
+ICON = 'module-network'
+TITLE = _('Network')
+
+_APPLY_TIMEOUT = 3000
+
+class Network(SectionView):
+ def __init__(self, model, alerts):
+ SectionView.__init__(self)
+
+ self._model = model
+ self.restart_alerts = alerts
+ self._jabber_sid = 0
+ self._jabber_valid = True
+ self._radio_valid = True
+ self._jabber_change_handler = None
+ self._radio_change_handler = None
+ self._network_configuration_reset_handler = None
+
+ self.set_border_width(style.DEFAULT_SPACING * 2)
+ self.set_spacing(style.DEFAULT_SPACING)
+ group = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
+
+ self._radio_alert_box = gtk.HBox(spacing=style.DEFAULT_SPACING)
+ self._jabber_alert_box = gtk.HBox(spacing=style.DEFAULT_SPACING)
+
+ workspace = gtk.VBox()
+ workspace.show()
+
+ separator_wireless = gtk.HSeparator()
+ workspace.pack_start(separator_wireless, expand=False)
+ separator_wireless.show()
+
+ label_wireless = gtk.Label(_('Wireless'))
+ label_wireless.set_alignment(0, 0)
+ workspace.pack_start(label_wireless, expand=False)
+ label_wireless.show()
+ box_wireless = gtk.VBox()
+ box_wireless.set_border_width(style.DEFAULT_SPACING * 2)
+ box_wireless.set_spacing(style.DEFAULT_SPACING)
+
+ radio_info = gtk.Label(_("Turn off the wireless radio to save "
+ "battery life"))
+ radio_info.set_alignment(0, 0)
+ radio_info.set_line_wrap(True)
+ radio_info.show()
+ box_wireless.pack_start(radio_info, expand=False)
+
+ box_radio = gtk.HBox(spacing=style.DEFAULT_SPACING)
+ self._button = gtk.CheckButton()
+ self._button.set_alignment(0, 0)
+ box_radio.pack_start(self._button, expand=False)
+ self._button.show()
+
+ label_radio = gtk.Label(_('Radio'))
+ label_radio.set_alignment(0, 0.5)
+ box_radio.pack_start(label_radio, expand=False)
+ label_radio.show()
+
+ box_wireless.pack_start(box_radio, expand=False)
+ box_radio.show()
+
+ self._radio_alert = InlineAlert()
+ self._radio_alert_box.pack_start(self._radio_alert, expand=False)
+ box_radio.pack_end(self._radio_alert_box, expand=False)
+ self._radio_alert_box.show()
+ if 'radio' in self.restart_alerts:
+ self._radio_alert.props.msg = self.restart_msg
+ self._radio_alert.show()
+
+ history_info = gtk.Label(_("Discard network history if you "
+ "have trouble connecting to the network"))
+ history_info.set_alignment(0, 0)
+ history_info.set_line_wrap(True)
+ history_info.show()
+ box_wireless.pack_start(history_info, expand=False)
+
+ box_clear_history = gtk.HBox(spacing=style.DEFAULT_SPACING)
+ self._clear_history_button = gtk.Button()
+ self._clear_history_button.set_label(_('Discard network history'))
+ box_clear_history.pack_start(self._clear_history_button, expand=False)
+ self._clear_history_button.show()
+ box_wireless.pack_start(box_clear_history, expand=False)
+ box_clear_history.show()
+
+ workspace.pack_start(box_wireless, expand=False)
+ box_wireless.show()
+
+ separator_mesh = gtk.HSeparator()
+ workspace.pack_start(separator_mesh, False)
+ separator_mesh.show()
+
+ label_mesh = gtk.Label(_('Collaboration'))
+ label_mesh.set_alignment(0, 0)
+ workspace.pack_start(label_mesh, expand=False)
+ label_mesh.show()
+ box_mesh = gtk.VBox()
+ box_mesh.set_border_width(style.DEFAULT_SPACING * 2)
+ box_mesh.set_spacing(style.DEFAULT_SPACING)
+
+ server_info = gtk.Label(_("The server is the equivalent of what"
+ " room you are in; people on the same server"
+ " will be able to see each other, even when"
+ " they aren't on the same network."))
+ server_info.set_alignment(0, 0)
+ server_info.set_line_wrap(True)
+ box_mesh.pack_start(server_info, expand=False)
+ server_info.show()
+
+ box_server = gtk.HBox(spacing=style.DEFAULT_SPACING)
+ label_server = gtk.Label(_('Server:'))
+ label_server.set_alignment(1, 0.5)
+ label_server.modify_fg(gtk.STATE_NORMAL,
+ style.COLOR_SELECTION_GREY.get_gdk_color())
+ box_server.pack_start(label_server, expand=False)
+ group.add_widget(label_server)
+ label_server.show()
+ self._entry = gtk.Entry()
+ self._entry.set_alignment(0)
+ self._entry.modify_bg(gtk.STATE_INSENSITIVE,
+ style.COLOR_WHITE.get_gdk_color())
+ self._entry.modify_base(gtk.STATE_INSENSITIVE,
+ style.COLOR_WHITE.get_gdk_color())
+ self._entry.set_size_request(int(gtk.gdk.screen_width() / 3), -1)
+ box_server.pack_start(self._entry, expand=False)
+ self._entry.show()
+ box_mesh.pack_start(box_server, expand=False)
+ box_server.show()
+
+ self._jabber_alert = InlineAlert()
+ label_jabber_error = gtk.Label()
+ group.add_widget(label_jabber_error)
+ self._jabber_alert_box.pack_start(label_jabber_error, expand=False)
+ label_jabber_error.show()
+ self._jabber_alert_box.pack_start(self._jabber_alert, expand=False)
+ box_mesh.pack_end(self._jabber_alert_box, expand=False)
+ self._jabber_alert_box.show()
+ if 'jabber' in self.restart_alerts:
+ self._jabber_alert.props.msg = self.restart_msg
+ self._jabber_alert.show()
+
+ workspace.pack_start(box_mesh, expand=False)
+ box_mesh.show()
+
+ scrolled = gtk.ScrolledWindow()
+ scrolled.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+ scrolled.add_with_viewport(workspace)
+ scrolled.show()
+ self.add(scrolled)
+
+ self.setup()
+
+ def setup(self):
+ self._entry.set_text(self._model.get_jabber())
+ try:
+ radio_state = self._model.get_radio()
+ except self._model.ReadError, detail:
+ self._radio_alert.props.msg = detail
+ self._radio_alert.show()
+ else:
+ self._button.set_active(radio_state)
+
+ self._jabber_valid = True
+ self._radio_valid = True
+ self.needs_restart = False
+ self._radio_change_handler = self._button.connect( \
+ 'toggled', self.__radio_toggled_cb)
+ self._jabber_change_handler = self._entry.connect( \
+ 'changed', self.__jabber_changed_cb)
+ self._network_configuration_reset_handler = \
+ self._clear_history_button.connect( \
+ 'clicked', self.__network_configuration_reset_cb)
+
+ def undo(self):
+ self._button.disconnect(self._radio_change_handler)
+ self._entry.disconnect(self._jabber_change_handler)
+ self._model.undo()
+ self._jabber_alert.hide()
+ self._radio_alert.hide()
+
+ def _validate(self):
+ if self._jabber_valid and self._radio_valid:
+ self.props.is_valid = True
+ else:
+ self.props.is_valid = False
+
+ def __radio_toggled_cb(self, widget, data=None):
+ radio_state = widget.get_active()
+ try:
+ self._model.set_radio(radio_state)
+ except self._model.ReadError, detail:
+ self._radio_alert.props.msg = detail
+ self._radio_valid = False
+ else:
+ self._radio_valid = True
+
+ self._validate()
+ return False
+
+ def __jabber_changed_cb(self, widget, data=None):
+ if self._jabber_sid:
+ gobject.source_remove(self._jabber_sid)
+ self._jabber_sid = gobject.timeout_add(_APPLY_TIMEOUT,
+ self.__jabber_timeout_cb, widget)
+
+ def __jabber_timeout_cb(self, widget):
+ self._jabber_sid = 0
+ if widget.get_text() == self._model.get_jabber:
+ return
+ try:
+ self._model.set_jabber(widget.get_text())
+ except self._model.ReadError, detail:
+ self._jabber_alert.props.msg = detail
+ self._jabber_valid = False
+ self._jabber_alert.show()
+ self.restart_alerts.append('jabber')
+ else:
+ self._jabber_valid = True
+ self._jabber_alert.hide()
+
+ self._validate()
+ return False
+
+ def __network_configuration_reset_cb(self, widget):
+ self._model.clear_networks()
diff --git a/shell/extensions/cpsection/power/Makefile.am b/shell/extensions/cpsection/power/Makefile.am
new file mode 100644
index 0000000..325260c
--- /dev/null
+++ b/shell/extensions/cpsection/power/Makefile.am
@@ -0,0 +1,6 @@
+sugardir = $(pkgdatadir)/extensions/cpsection/power
+
+sugar_PYTHON = \
+ __init__.py \
+ model.py \
+ view.py
diff --git a/shell/extensions/cpsection/power/__init__.py b/shell/extensions/cpsection/power/__init__.py
new file mode 100644
index 0000000..8b2e85f
--- /dev/null
+++ b/shell/extensions/cpsection/power/__init__.py
@@ -0,0 +1,23 @@
+# Copyright (C) 2008, OLPC
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from gettext import gettext as _
+
+CLASS = 'Power'
+ICON = 'module-power'
+TITLE = _('Power')
+KEYWORDS = ['automatic', 'extreme', 'power', 'suspend', 'battery']
+
diff --git a/shell/extensions/cpsection/power/model.py b/shell/extensions/cpsection/power/model.py
new file mode 100644
index 0000000..48d05de
--- /dev/null
+++ b/shell/extensions/cpsection/power/model.py
@@ -0,0 +1,116 @@
+# Copyright (C) 2008 One Laptop Per Child
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+import os
+from gettext import gettext as _
+import logging
+
+import gconf
+import dbus
+
+OHM_SERVICE_NAME = 'org.freedesktop.ohm'
+OHM_SERVICE_PATH = '/org/freedesktop/ohm/Keystore'
+OHM_SERVICE_IFACE = 'org.freedesktop.ohm.Keystore'
+
+POWERD_FLAG_DIR = '/etc/powerd/flags'
+POWERD_INHIBIT_FLAG = '/etc/powerd/flags/inhibit-suspend'
+
+_logger = logging.getLogger('ControlPanel - Power')
+
+
+class ReadError(Exception):
+ def __init__(self, value):
+ self.value = value
+ def __str__(self):
+ return repr(self.value)
+
+def using_powerd():
+ # directory exists if powerd running, and it's recent
+ # enough to be controllable.
+ return os.access(POWERD_FLAG_DIR, os.W_OK)
+
+def get_automatic_pm():
+ if using_powerd():
+ return not os.access(POWERD_INHIBIT_FLAG, os.R_OK)
+
+ # ohmd
+ client = gconf.client_get_default()
+ return client.get_bool('/desktop/sugar/power/automatic')
+
+def print_automatic_pm():
+ print ('off', 'on')[get_automatic_pm()]
+
+def set_automatic_pm(enabled):
+ """Automatic suspends on/off."""
+
+ if using_powerd():
+ # powerd
+ if enabled == 'off' or enabled == 0:
+ try:
+ fd = open(POWERD_INHIBIT_FLAG, 'w')
+ except IOError:
+ _logger.debug('File %s is not writeable' % POWERD_INHIBIT_FLAG)
+ else:
+ fd.close()
+ else:
+ os.unlink(POWERD_INHIBIT_FLAG)
+ return
+
+ # ohmd
+ bus = dbus.SystemBus()
+ proxy = bus.get_object(OHM_SERVICE_NAME, OHM_SERVICE_PATH)
+ keystore = dbus.Interface(proxy, OHM_SERVICE_IFACE)
+
+ if enabled == 'on' or enabled == 1:
+ keystore.SetKey("suspend.automatic_pm", 1)
+ enabled = True
+ elif enabled == 'off' or enabled == 0:
+ keystore.SetKey("suspend.automatic_pm", 0)
+ enabled = False
+ else:
+ raise ValueError(_("Error in automatic pm argument, use on/off."))
+
+ client = gconf.client_get_default()
+ client.set_bool('/desktop/sugar/power/automatic', enabled)
+ return
+
+def get_extreme_pm():
+ client = gconf.client_get_default()
+ return client.get_bool('/desktop/sugar/power/extreme')
+
+def print_extreme_pm():
+ print ('off', 'on')[get_extreme_pm()]
+
+def set_extreme_pm(enabled):
+ """Extreme power management on/off."""
+
+ bus = dbus.SystemBus()
+ proxy = bus.get_object(OHM_SERVICE_NAME, OHM_SERVICE_PATH)
+ keystore = dbus.Interface(proxy, OHM_SERVICE_IFACE)
+
+ if enabled == 'on' or enabled == 1:
+ keystore.SetKey("suspend.extreme_pm", 1)
+ enabled = True
+ elif enabled == 'off' or enabled == 0:
+ keystore.SetKey("suspend.extreme_pm", 0)
+ enabled = False
+ else:
+ raise ValueError(_("Error in extreme pm argument, use on/off."))
+
+ client = gconf.client_get_default()
+ client.set_bool('/desktop/sugar/power/extreme', enabled)
+ return 0
diff --git a/shell/extensions/cpsection/power/view.py b/shell/extensions/cpsection/power/view.py
new file mode 100644
index 0000000..8f1ed56
--- /dev/null
+++ b/shell/extensions/cpsection/power/view.py
@@ -0,0 +1,177 @@
+# Copyright (C) 2008, OLPC
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import gtk
+from gettext import gettext as _
+
+from sugar.graphics import style
+
+from jarabe.controlpanel.sectionview import SectionView
+from jarabe.controlpanel.inlinealert import InlineAlert
+
+class Power(SectionView):
+ def __init__(self, model, alerts):
+ SectionView.__init__(self)
+
+ self._model = model
+ self.restart_alerts = alerts
+ self._automatic_pm_valid = True
+ self._extreme_pm_valid = True
+ self._extreme_pm_change_handler = None
+ self._automatic_pm_change_handler = None
+
+ self.set_border_width(style.DEFAULT_SPACING * 2)
+ self.set_spacing(style.DEFAULT_SPACING)
+ group = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
+
+ self._automatic_pm_alert_box = gtk.HBox(spacing=style.DEFAULT_SPACING)
+ self._extreme_pm_alert_box = gtk.HBox(spacing=style.DEFAULT_SPACING)
+
+ separator_pm = gtk.HSeparator()
+ self.pack_start(separator_pm, expand=False)
+ separator_pm.show()
+
+ label_pm = gtk.Label(_('Power management'))
+ label_pm.set_alignment(0, 0)
+ self.pack_start(label_pm, expand=False)
+ label_pm.show()
+ box_pm = gtk.VBox()
+ box_pm.set_border_width(style.DEFAULT_SPACING * 2)
+ box_pm.set_spacing(style.DEFAULT_SPACING)
+
+ box_automatic_pm = gtk.HBox(spacing=style.DEFAULT_SPACING)
+ label_automatic_pm = gtk.Label(
+ _('Automatic power management (increases battery life)'))
+ label_automatic_pm.set_alignment(0, 0.5)
+ self._automatic_button = gtk.CheckButton()
+ self._automatic_button.set_alignment(0, 0)
+ box_automatic_pm.pack_start(self._automatic_button, expand=False)
+ box_automatic_pm.pack_start(label_automatic_pm, expand=False)
+ self._automatic_button.show()
+ label_automatic_pm.show()
+ group.add_widget(label_automatic_pm)
+ box_pm.pack_start(box_automatic_pm, expand=False)
+ box_automatic_pm.show()
+
+ self._automatic_pm_alert = InlineAlert()
+ label_automatic_pm_error = gtk.Label()
+ group.add_widget(label_automatic_pm_error)
+ self._automatic_pm_alert_box.pack_start(label_automatic_pm_error,
+ expand=False)
+ label_automatic_pm_error.show()
+ self._automatic_pm_alert_box.pack_start(self._automatic_pm_alert,
+ expand=False)
+ box_pm.pack_end(self._automatic_pm_alert_box, expand=False)
+ self._automatic_pm_alert_box.show()
+ if 'automatic_pm' in self.restart_alerts:
+ self._automatic_pm_alert.props.msg = self.restart_msg
+ self._automatic_pm_alert.show()
+
+ box_extreme_pm = gtk.HBox(spacing=style.DEFAULT_SPACING)
+ label_extreme_pm = gtk.Label(
+ _('Extreme power management (disables' \
+ 'wireless radio, increases battery life)'))
+ label_extreme_pm.set_alignment(0, 0.5)
+ self._extreme_button = gtk.CheckButton()
+ self._extreme_button.set_alignment(0, 0)
+ box_extreme_pm.pack_start(self._extreme_button, expand=False)
+ self._extreme_button.show()
+ box_extreme_pm.pack_start(label_extreme_pm, expand=False)
+ group.add_widget(label_extreme_pm)
+ label_extreme_pm.show()
+ box_pm.pack_start(box_extreme_pm, expand=False)
+ box_extreme_pm.show()
+
+ self._extreme_pm_alert = InlineAlert()
+ label_extreme_pm_error = gtk.Label()
+ group.add_widget(label_extreme_pm_error)
+ self._extreme_pm_alert_box.pack_start(label_extreme_pm_error,
+ expand=False)
+ label_extreme_pm_error.show()
+ self._extreme_pm_alert_box.pack_start(self._extreme_pm_alert,
+ expand=False)
+ box_pm.pack_end(self._extreme_pm_alert_box, expand=False)
+ self._extreme_pm_alert_box.show()
+ if 'extreme_pm' in self.restart_alerts:
+ self._extreme_pm_alert.props.msg = self.restart_msg
+ self._extreme_pm_alert.show()
+
+ self.pack_start(box_pm, expand=False)
+ box_pm.show()
+
+ self.setup()
+
+ def setup(self):
+ try:
+ automatic_state = self._model.get_automatic_pm()
+ extreme_state = self._model.get_extreme_pm()
+
+ except Exception, detail:
+ self._automatic_pm_alert.props.msg = detail
+ self._automatic_pm_alert.show()
+
+ self._extreme_pm_alert.props.msg = detail
+ self._extreme_pm_alert.show()
+ else:
+ self._automatic_button.set_active(automatic_state)
+ self._extreme_button.set_active(extreme_state)
+
+ self._extreme_pm_valid = True
+ self._automatic_pm_valid = True
+ self.needs_restart = False
+ self._automatic_pm_change_handler = self._automatic_button.connect( \
+ 'toggled', self.__automatic_pm_toggled_cb)
+ self._extreme_pm_change_handler = self._extreme_button.connect( \
+ 'toggled', self.__extreme_pm_toggled_cb)
+
+ def undo(self):
+ self._automatic_button.disconnect(self._automatic_pm_change_handler)
+ self._extreme_button.disconnect(self._extreme_pm_change_handler)
+ self._model.undo()
+ self._extreme_pm_alert.hide()
+ self._automatic_pm_alert.hide()
+
+ def _validate(self):
+ if self._extreme_pm_valid and self._automatic_pm_valid:
+ self.props.is_valid = True
+ else:
+ self.props.is_valid = False
+
+ def __automatic_pm_toggled_cb(self, widget, data=None):
+ state = widget.get_active()
+ try:
+ self._model.set_automatic_pm(state)
+ except Exception, detail:
+ print detail
+ self._automatic_pm_alert.props.msg = detail
+ else:
+ self._automatic_pm_valid = True
+
+ self._validate()
+ return False
+
+ def __extreme_pm_toggled_cb(self, widget, data=None):
+ state = widget.get_active()
+ try:
+ self._model.set_extreme_pm(state)
+ except Exception, detail:
+ print detail
+ self._extreme_pm_alert.props.msg = detail
+ else:
+ self._extreme_pm_valid = True
+
+ self._validate()
+ return False
diff --git a/shell/extensions/cpsection/updater/Makefile.am b/shell/extensions/cpsection/updater/Makefile.am
new file mode 100644
index 0000000..897dbf3
--- /dev/null
+++ b/shell/extensions/cpsection/updater/Makefile.am
@@ -0,0 +1,8 @@
+SUBDIRS = backends
+
+sugardir = $(pkgdatadir)/extensions/cpsection/updater
+
+sugar_PYTHON = \
+ __init__.py \
+ model.py \
+ view.py
diff --git a/shell/extensions/cpsection/updater/__init__.py b/shell/extensions/cpsection/updater/__init__.py
new file mode 100644
index 0000000..6010615
--- /dev/null
+++ b/shell/extensions/cpsection/updater/__init__.py
@@ -0,0 +1,22 @@
+# Copyright (C) 2008, OLPC
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from gettext import gettext as _
+
+CLASS = 'ActivityUpdater'
+ICON = 'module-updater'
+TITLE = _('Software update')
+KEYWORDS = ['software', 'activity', 'update']
diff --git a/shell/extensions/cpsection/updater/backends/Makefile.am b/shell/extensions/cpsection/updater/backends/Makefile.am
new file mode 100644
index 0000000..e280a07
--- /dev/null
+++ b/shell/extensions/cpsection/updater/backends/Makefile.am
@@ -0,0 +1,5 @@
+sugardir = $(pkgdatadir)/extensions/cpsection/updater/backends
+
+sugar_PYTHON = \
+ aslo.py \
+ __init__.py
diff --git a/shell/extensions/cpsection/updater/backends/__init__.py b/shell/extensions/cpsection/updater/backends/__init__.py
new file mode 100644
index 0000000..0dd0174
--- /dev/null
+++ b/shell/extensions/cpsection/updater/backends/__init__.py
@@ -0,0 +1,16 @@
+#!/usr/bin/python
+# Copyright (C) 2009, Sugar Labs
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
diff --git a/shell/extensions/cpsection/updater/backends/aslo.py b/shell/extensions/cpsection/updater/backends/aslo.py
new file mode 100644
index 0000000..5f257f9
--- /dev/null
+++ b/shell/extensions/cpsection/updater/backends/aslo.py
@@ -0,0 +1,161 @@
+#!/usr/bin/python
+# Copyright (C) 2009, Sugar Labs
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+'''Activity information microformat parser.
+
+Activity information is embedded in HTML/XHTML/XML pages using a
+Resource Description Framework (RDF) http://www.w3.org/RDF/ .
+
+An example::
+
+<?xml version="1.0" encoding="UTF-8"?>
+<RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+<RDF:Description about="urn:mozilla:extension:bounce">
+ <em:updates>
+ <RDF:Seq>
+ <RDF:li resource="urn:mozilla:extension:bounce:7"/>
+ </RDF:Seq>
+ </em:updates>
+</RDF:Description>
+
+<RDF:Description about="urn:mozilla:extension:bounce:7">
+ <em:version>7</em:version>
+ <em:targetApplication>
+ <RDF:Description>
+ <em:id>{3ca105e0-2280-4897-99a0-c277d1b733d2}</em:id>
+ <em:minVersion>0.82</em:minVersion>
+ <em:maxVersion>0.84</em:maxVersion>
+ <em:updateLink>http://foo.xo</em:updateLink>
+ <em:updateSize>7</em:updateSize>
+ <em:updateHash>sha256:816a7c43b4f1ea4769c61c03ea4..</em:updateHash>
+ </RDF:Description>
+ </em:targetApplication>
+</RDF:Description></RDF:RDF>
+'''
+
+import logging
+from xml.etree.ElementTree import XML
+import traceback
+
+import gio
+
+from jarabe import config
+
+_FIND_DESCRIPTION = \
+ './/{http://www.w3.org/1999/02/22-rdf-syntax-ns#}Description'
+_FIND_VERSION = './/{http://www.mozilla.org/2004/em-rdf#}version'
+_FIND_LINK = './/{http://www.mozilla.org/2004/em-rdf#}updateLink'
+_FIND_SIZE = './/{http://www.mozilla.org/2004/em-rdf#}updateSize'
+
+_UPDATE_PATH = 'http://activities.sugarlabs.org/services/update-aslo.php'
+
+_fetcher = None
+
+
+class _UpdateFetcher(object):
+
+ _CHUNK_SIZE = 10240
+
+ def __init__(self, bundle, completion_cb):
+ # ASLO knows only about stable SP releases
+ major, minor = config.version.split('.')[0:2]
+ sp_version = '%s.%s' % (major, int(minor) + int(minor) % 2)
+
+ url = '%s?id=%s&appVersion=%s' % \
+ (_UPDATE_PATH, bundle.get_bundle_id(), sp_version)
+
+ logging.debug('Fetch %s', url)
+
+ self._completion_cb = completion_cb
+ self._file = gio.File(url)
+ self._stream = None
+ self._xml_data = ''
+ self._bundle = bundle
+
+ self._file.read_async(self.__file_read_async_cb)
+
+ def __file_read_async_cb(self, gfile, result):
+ try:
+ self._stream = self._file.read_finish(result)
+ except:
+ global _fetcher
+ _fetcher = None
+ self._completion_cb(None, None, None, None, traceback.format_exc())
+ return
+
+ self._stream.read_async(self._CHUNK_SIZE, self.__stream_read_async_cb)
+
+ def __stream_read_async_cb(self, stream, result):
+ xml_data = self._stream.read_finish(result)
+ if xml_data is None:
+ global _fetcher
+ _fetcher = None
+ self._completion_cb(self._bundle, None, None, None,
+ 'Error reading update information for %s from '
+ 'server.' % self._bundle.get_bundle_id())
+ return
+ elif not xml_data:
+ self._process_result()
+ else:
+ self._xml_data += xml_data
+ self._stream.read_async(self._CHUNK_SIZE,
+ self.__stream_read_async_cb)
+
+ def _process_result(self):
+ document = XML(self._xml_data)
+
+ if document.find(_FIND_DESCRIPTION) is None:
+ logging.debug('Bundle %s not available in the server for the '
+ 'version %s', self._bundle.get_bundle_id(), config.version)
+ version = None
+ link = None
+ size = None
+ else:
+ try:
+ version = int(document.find(_FIND_VERSION).text)
+ except ValueError:
+ logging.error(traceback.format_exc())
+ version = 0
+
+ link = document.find(_FIND_LINK).text
+
+ try:
+ size = long(document.find(_FIND_SIZE).text) * 1024
+ except ValueError:
+ logging.error(traceback.format_exc())
+ size = 0
+
+ global _fetcher
+ _fetcher = None
+ self._completion_cb(self._bundle, version, link, size, None)
+
+
+def fetch_update_info(bundle, completion_cb):
+ '''Queries the server for a newer version of the ActivityBundle.
+
+ completion_cb receives bundle, version, link, size and possibly an error
+ message:
+
+ def completion_cb(bundle, version, link, size, error_message):
+ '''
+ global _fetcher
+
+ if _fetcher is not None:
+ raise RuntimeError('Multiple simultaneous requests are not supported')
+
+ _fetcher = _UpdateFetcher(bundle, completion_cb)
diff --git a/shell/extensions/cpsection/updater/model.py b/shell/extensions/cpsection/updater/model.py
new file mode 100755
index 0000000..9845371
--- /dev/null
+++ b/shell/extensions/cpsection/updater/model.py
@@ -0,0 +1,346 @@
+# Copyright (C) 2009, Sugar Labs
+# Copyright (C) 2009, Tomeu Vizoso
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+'''Sugar bundle updater: model.
+
+This module implements the non-GUI portions of the bundle updater, including
+list of installed bundls, whether updates are needed, and the URL at which to
+find the bundle updated.
+'''
+
+import os
+import logging
+import tempfile
+from urlparse import urlparse
+import traceback
+
+import gobject
+import gio
+
+from sugar import env
+from sugar.datastore import datastore
+from sugar.bundle.activitybundle import ActivityBundle
+
+from jarabe.model import bundleregistry
+
+from backends import aslo
+
+
+class UpdateModel(gobject.GObject):
+ __gtype_name__ = 'SugarUpdateModel'
+
+ __gsignals__ = {
+ 'progress': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([int, str, float, int])),
+ }
+
+ ACTION_CHECKING = 0
+ ACTION_UPDATING = 1
+ ACTION_DOWNLOADING = 2
+
+ def __init__(self):
+ gobject.GObject.__init__(self)
+
+ self.updates = None
+ self._bundles_to_check = None
+ self._bundles_to_update = None
+ self._total_bundles_to_update = 0
+ self._downloader = None
+ self._cancelling = False
+
+ def check_updates(self):
+ self.updates = []
+ self._bundles_to_check = \
+ [bundle for bundle in bundleregistry.get_registry()]
+ self._check_next_update()
+
+ def _check_next_update(self):
+ total = len(bundleregistry.get_registry())
+ current = total - len(self._bundles_to_check)
+
+ bundle = self._bundles_to_check.pop()
+ self.emit('progress', UpdateModel.ACTION_CHECKING, bundle.get_name(),
+ current, total)
+
+ aslo.fetch_update_info(bundle, self.__check_completed_cb)
+
+ def __check_completed_cb(self, bundle, version, link, size, error_message):
+ if error_message is not None:
+ logging.error('Error getting update information from server:\n'
+ '%s' % error_message)
+
+ if version is not None and version > bundle.get_activity_version():
+ self.updates.append(BundleUpdate(bundle, version, link, size))
+
+ if self._cancelling:
+ self._cancel_checking()
+ elif self._bundles_to_check:
+ gobject.idle_add(self._check_next_update)
+ else:
+ total = len(bundleregistry.get_registry())
+ if bundle is None:
+ name = ''
+ else:
+ name = bundle.get_name()
+ self.emit('progress', UpdateModel.ACTION_CHECKING, name, total,
+ total)
+
+ def update(self, bundle_ids):
+ self._bundles_to_update = []
+ for bundle_update in self.updates:
+ if bundle_update.bundle.get_bundle_id() in bundle_ids:
+ self._bundles_to_update.append(bundle_update)
+
+ self._total_bundles_to_update = len(self._bundles_to_update)
+ self._download_next_update()
+
+ def _download_next_update(self):
+ if self._cancelling:
+ self._cancel_updating()
+ return
+
+ bundle_update = self._bundles_to_update.pop()
+
+ total = self._total_bundles_to_update * 2
+ current = total - len(self._bundles_to_update) * 2 - 2
+
+ self.emit('progress', UpdateModel.ACTION_DOWNLOADING,
+ bundle_update.bundle.get_name(), current, total)
+
+ self._downloader = _Downloader(bundle_update)
+ self._downloader.connect('progress', self.__downloader_progress_cb)
+ self._downloader.connect('error', self.__downloader_error_cb)
+
+ def __downloader_progress_cb(self, downloader, progress):
+ logging.debug('__downloader_progress_cb %r', progress)
+
+ if self._cancelling:
+ self._cancel_updating()
+ return
+
+ total = self._total_bundles_to_update * 2
+ current = total - len(self._bundles_to_update) * 2 - 2 + progress
+
+ self.emit('progress', UpdateModel.ACTION_DOWNLOADING,
+ self._downloader.bundle_update.bundle.get_name(),
+ current, total)
+
+ if progress == 1:
+ self._install_update(self._downloader.bundle_update,
+ self._downloader.get_local_file_path())
+ self._downloader = None
+
+ def __downloader_error_cb(self, downloader, error_message):
+ logging.error('Error downloading update:\n%s', error_message)
+
+ if self._cancelling:
+ self._cancel_updating()
+ return
+
+ total = self._total_bundles_to_update
+ current = total - len(self._bundles_to_update)
+ self.emit('progress', UpdateModel.ACTION_UPDATING, '', current, total)
+
+ if self._bundles_to_update:
+ # do it in idle so the UI has a chance to refresh
+ gobject.idle_add(self._download_next_update)
+
+ def _install_update(self, bundle_update, local_file_path):
+
+ total = self._total_bundles_to_update
+ current = total - len(self._bundles_to_update) - 0.5
+
+ self.emit('progress', UpdateModel.ACTION_UPDATING,
+ bundle_update.bundle.get_name(),
+ current, total)
+
+ # TODO: Should we first expand the zip async so we can provide progress
+ # and only then copy to the journal?
+ jobject = datastore.create()
+ try:
+ title = '%s-%s' % (bundle_update.bundle.get_name(),
+ bundle_update.version)
+ jobject.metadata['title'] = title
+ jobject.metadata['mime_type'] = ActivityBundle.MIME_TYPE
+ jobject.file_path = local_file_path
+ datastore.write(jobject, transfer_ownership=True)
+ finally:
+ jobject.destroy()
+
+ self.emit('progress', UpdateModel.ACTION_UPDATING,
+ bundle_update.bundle.get_name(),
+ current + 0.5, total)
+
+ if self._bundles_to_update:
+ # do it in idle so the UI has a chance to refresh
+ gobject.idle_add(self._download_next_update)
+
+ def cancel(self):
+ self._cancelling = True
+
+ def _cancel_checking(self):
+ logging.debug('UpdateModel._cancel_checking')
+ total = len(bundleregistry.get_registry())
+ current = total - len(self._bundles_to_check)
+ self.emit('progress', UpdateModel.ACTION_CHECKING, '', current, current)
+ self._bundles_to_check = None
+ self._cancelling = False
+
+ def _cancel_updating(self):
+ logging.debug('UpdateModel._cancel_updating')
+ current = self._total_bundles_to_update - len(self._bundles_to_update) - 1
+ self.emit('progress', UpdateModel.ACTION_UPDATING, '', current, current)
+
+ if self._downloader is not None:
+ self._downloader.cancel()
+ file_path = self._downloader.get_local_file_path()
+ if file_path is not None and os.path.exists(file_path):
+ os.unlink(file_path)
+ self._downloader = None
+
+ self._total_bundles_to_update = 0
+ self._bundles_to_update = None
+ self._cancelling = False
+
+class BundleUpdate(object):
+
+ def __init__(self, bundle, version, link, size):
+ self.bundle = bundle
+ self.version = version
+ self.link = link
+ self.size = size
+
+
+class _Downloader(gobject.GObject):
+ _CHUNK_SIZE = 10240 # 10K
+ __gsignals__ = {
+ 'progress': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([float])),
+ 'error': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([str])),
+ }
+
+ def __init__(self, bundle_update):
+ gobject.GObject.__init__(self)
+
+ self.bundle_update = bundle_update
+ self._input_stream = None
+ self._output_stream = None
+ self._pending_buffers = []
+ self._input_file = gio.File(bundle_update.link)
+ self._output_file = None
+ self._downloaded_size = 0
+ self._cancelling = False
+
+ self._input_file.read_async(self.__file_read_async_cb)
+
+ def cancel(self):
+ self._cancelling = True
+
+ def __file_read_async_cb(self, gfile, result):
+ if self._cancelling:
+ return
+
+ try:
+ self._input_stream = self._input_file.read_finish(result)
+ except:
+ self.emit('error', traceback.format_exc())
+ return
+
+ temp_file_path = self._get_temp_file_path(self.bundle_update.link)
+ self._output_file = gio.File(temp_file_path)
+ self._output_stream = self._output_file.create()
+
+ self._input_stream.read_async(self._CHUNK_SIZE, self.__read_async_cb,
+ gobject.PRIORITY_LOW)
+
+ def __read_async_cb(self, input_stream, result):
+ if self._cancelling:
+ return
+
+ data = input_stream.read_finish(result)
+
+ if data is None:
+ # TODO
+ pass
+ elif not data:
+ logging.debug('closing input stream')
+ self._input_stream.close()
+ self._check_if_finished_writing()
+ else:
+ self._pending_buffers.append(data)
+ self._input_stream.read_async(self._CHUNK_SIZE,
+ self.__read_async_cb,
+ gobject.PRIORITY_LOW)
+
+ self._write_next_buffer()
+
+ def __write_async_cb(self, output_stream, result, user_data):
+ if self._cancelling:
+ return
+
+ count = output_stream.write_finish(result)
+
+ self._downloaded_size += count
+ progress = self._downloaded_size / float(self.bundle_update.size)
+ self.emit('progress', progress)
+
+ self._check_if_finished_writing()
+
+ if self._pending_buffers:
+ self._write_next_buffer()
+
+ def _write_next_buffer(self):
+ if self._pending_buffers and not self._output_stream.has_pending():
+ data = self._pending_buffers.pop(0)
+ # TODO: we pass the buffer as user_data because of
+ # http://bugzilla.gnome.org/show_bug.cgi?id=564102
+ self._output_stream.write_async(data, self.__write_async_cb,
+ gobject.PRIORITY_LOW,
+ user_data=data)
+
+ def _get_temp_file_path(self, uri):
+ # TODO: Should we use the HTTP headers for the file name?
+ scheme_, netloc_, path, params_, query_, fragment_ = \
+ urlparse(uri)
+ path = os.path.basename(path)
+
+ if not os.path.exists(env.get_user_activities_path()):
+ os.makedirs(env.get_user_activities_path())
+
+ base_name, extension_ = os.path.splitext(path)
+ fd, file_path = tempfile.mkstemp(dir=env.get_user_activities_path(),
+ prefix=base_name, suffix='.xo')
+ os.close(fd)
+ os.unlink(file_path)
+
+ return file_path
+
+ def get_local_file_path(self):
+ return self._output_file.get_path()
+
+ def _check_if_finished_writing(self):
+ if not self._pending_buffers and \
+ not self._output_stream.has_pending() and \
+ self._input_stream.is_closed():
+
+ logging.debug('closing output stream')
+ self._output_stream.close()
+
+ self.emit('progress', 1.0)
diff --git a/shell/extensions/cpsection/updater/view.py b/shell/extensions/cpsection/updater/view.py
new file mode 100644
index 0000000..2164c0b
--- /dev/null
+++ b/shell/extensions/cpsection/updater/view.py
@@ -0,0 +1,391 @@
+# Copyright (C) 2008, One Laptop Per Child
+# Copyright (C) 2009, Tomeu Vizoso
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from gettext import gettext as _
+from gettext import ngettext
+import locale
+import logging
+
+import gobject
+import gtk
+
+from sugar.graphics import style
+from sugar.graphics.icon import Icon, CellRendererIcon
+
+from jarabe.controlpanel.sectionview import SectionView
+
+from model import UpdateModel
+
+_DEBUG_VIEW_ALL = True
+
+
+class ActivityUpdater(SectionView):
+
+ def __init__(self, model, alerts):
+ SectionView.__init__(self)
+
+ self._model = UpdateModel()
+ self._model.connect('progress', self.__progress_cb)
+
+ self.set_spacing(style.DEFAULT_SPACING)
+ self.set_border_width(style.DEFAULT_SPACING * 2)
+
+ self._top_label = gtk.Label()
+ self._top_label.set_line_wrap(True)
+ self._top_label.set_justify(gtk.JUSTIFY_LEFT)
+ self._top_label.props.xalign = 0
+ self.pack_start(self._top_label, expand=False)
+ self._top_label.show()
+
+ separator = gtk.HSeparator()
+ self.pack_start(separator, expand=False)
+ separator.show()
+
+ bottom_label = gtk.Label()
+ bottom_label.set_line_wrap(True)
+ bottom_label.set_justify(gtk.JUSTIFY_LEFT)
+ bottom_label.props.xalign = 0
+ bottom_label.set_markup(
+ _('Software updates correct errors, eliminate security ' \
+ 'vulnerabilities, and provide new features.'))
+ self.pack_start(bottom_label, expand=False)
+ bottom_label.show()
+
+ self._update_box = None
+ self._progress_pane = None
+
+ self._refresh()
+
+ def _switch_to_update_box(self):
+ if self._update_box in self.get_children():
+ return
+
+ if self._progress_pane in self.get_children():
+ self.remove(self._progress_pane)
+ self._progress_pane = None
+
+ if self._update_box is None:
+ self._update_box = UpdateBox(self._model)
+ self._update_box.refresh_button.connect('clicked',
+ self.__refresh_button_clicked_cb)
+ self._update_box.install_button.connect('clicked',
+ self.__install_button_clicked_cb)
+
+ self.pack_start(self._update_box, expand=True, fill=True)
+ self._update_box.show()
+
+ def _switch_to_progress_pane(self):
+ if self._progress_pane in self.get_children():
+ return
+
+ if self._update_box in self.get_children():
+ self.remove(self._update_box)
+ self._update_box = None
+
+ if self._progress_pane is None:
+ self._progress_pane = ProgressPane()
+ self._progress_pane.cancel_button.connect('clicked',
+ self.__cancel_button_clicked_cb)
+
+ self.pack_start(self._progress_pane, expand=True, fill=False)
+ self._progress_pane.show()
+
+ def _clear_center(self):
+ if self._progress_pane in self.get_children():
+ self.remove(self._progress_pane)
+ self._progress_pane = None
+
+ if self._update_box in self.get_children():
+ self.remove(self._update_box)
+ self._update_box = None
+
+ def __progress_cb(self, model, action, bundle_name, current, total):
+ if current == total and action == UpdateModel.ACTION_CHECKING:
+ self._finished_checking()
+ return
+ elif current == total:
+ self._finished_updating(int(current))
+ return
+
+ if action == UpdateModel.ACTION_CHECKING:
+ message = _('Checking %s...') % bundle_name
+ elif action == UpdateModel.ACTION_DOWNLOADING:
+ message = _('Downloading %s...') % bundle_name
+ elif action == UpdateModel.ACTION_UPDATING:
+ message = _('Updating %s...') % bundle_name
+
+ self._switch_to_progress_pane()
+ self._progress_pane.set_message(message)
+ self._progress_pane.set_progress(current / float(total))
+
+ def _finished_checking(self):
+ logging.debug('ActivityUpdater._finished_checking')
+ available_updates = len(self._model.updates)
+ if not available_updates:
+ top_message = _('Your software is up-to-date')
+ else:
+ top_message = ngettext('You can install %s update',
+ 'You can install %s updates',
+ available_updates)
+ top_message = top_message % available_updates
+ top_message = gobject.markup_escape_text(top_message)
+
+ self._top_label.set_markup('<big>%s</big>' % top_message)
+
+ if not available_updates:
+ self._clear_center()
+ else:
+ self._switch_to_update_box()
+ self._update_box.refresh()
+
+ def __refresh_button_clicked_cb(self, button):
+ self._refresh()
+
+ def _refresh(self):
+ top_message = _('Checking for updates...')
+ self._top_label.set_markup('<big>%s</big>' % top_message)
+ self._model.check_updates()
+
+ def __install_button_clicked_cb(self, button):
+ self._top_label.set_markup('<big>%s</big>' % _('Installing updates...'))
+ self._model.update(self._update_box.get_bundles_to_update())
+
+ def __cancel_button_clicked_cb(self, button):
+ self._model.cancel()
+
+ def _finished_updating(self, installed_updates):
+ logging.debug('ActivityUpdater._finished_updating')
+ top_message = ngettext('%s update was installed',
+ '%s updates were installed', installed_updates)
+ top_message = top_message % installed_updates
+ top_message = gobject.markup_escape_text(top_message)
+ self._top_label.set_markup('<big>%s</big>' % top_message)
+ self._clear_center()
+
+ def undo(self):
+ self._model.cancel()
+
+class ProgressPane(gtk.VBox):
+ '''Container which replaces the `ActivityPane` during refresh or
+ install.'''
+
+ def __init__(self):
+ gtk.VBox.__init__(self)
+ self.set_spacing(style.DEFAULT_PADDING)
+ self.set_border_width(style.DEFAULT_SPACING * 2)
+
+ self._progress = gtk.ProgressBar()
+ self.pack_start(self._progress)
+ self._progress.show()
+
+ self._label = gtk.Label()
+ self._label.set_line_wrap(True)
+ self._label.set_property('xalign', 0.5)
+ self._label.modify_fg(gtk.STATE_NORMAL,
+ style.COLOR_BUTTON_GREY.get_gdk_color())
+ self.pack_start(self._label)
+ self._label.show()
+
+ alignment_box = gtk.Alignment(xalign=0.5, yalign=0.5)
+ self.pack_start(alignment_box)
+ alignment_box.show()
+
+ self.cancel_button = gtk.Button(stock=gtk.STOCK_CANCEL)
+ alignment_box.add(self.cancel_button)
+ self.cancel_button.show()
+
+ def set_message(self, message):
+ self._label.set_text(message)
+
+ def set_progress(self, fraction):
+ self._progress.props.fraction = fraction
+
+
+class UpdateBox(gtk.VBox):
+
+ def __init__(self, model):
+ gtk.VBox.__init__(self)
+
+ self._model = model
+
+ self.set_spacing(style.DEFAULT_PADDING)
+
+ scrolled_window = gtk.ScrolledWindow()
+ scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ self.pack_start(scrolled_window)
+ scrolled_window.show()
+
+ self._update_list = UpdateList(model)
+ self._update_list.props.model.connect('row-changed',
+ self.__row_changed_cb)
+ scrolled_window.add(self._update_list)
+ self._update_list.show()
+
+ bottom_box = gtk.HBox()
+ bottom_box.set_spacing(style.DEFAULT_SPACING)
+ self.pack_start(bottom_box, expand=False)
+ bottom_box.show()
+
+ self._size_label = gtk.Label()
+ self._size_label.props.xalign = 0
+ self._size_label.set_justify(gtk.JUSTIFY_LEFT)
+ bottom_box.pack_start(self._size_label, expand=True)
+ self._size_label.show()
+
+ self.refresh_button = gtk.Button(stock=gtk.STOCK_REFRESH)
+ bottom_box.pack_start(self.refresh_button, expand=False)
+ self.refresh_button.show()
+
+ self.install_button = gtk.Button(_('Install selected'))
+ self.install_button.props.image = Icon(icon_name='emblem-downloads',
+ icon_size=gtk.ICON_SIZE_BUTTON)
+ bottom_box.pack_start(self.install_button, expand=False)
+ self.install_button.show()
+
+ self._update_total_size_label()
+
+ def refresh(self):
+ self._update_list.refresh()
+
+ def __row_changed_cb(self, list_model, path, iterator):
+ self._update_total_size_label()
+ self._update_install_button()
+
+ def _update_total_size_label(self):
+ total_size = 0
+ for row in self._update_list.props.model:
+ if row[UpdateListModel.SELECTED]:
+ total_size += row[UpdateListModel.SIZE]
+
+ markup = _('Download size: %s') % _format_size(total_size)
+ self._size_label.set_markup(markup)
+
+ def _update_install_button(self):
+ for row in self._update_list.props.model:
+ if row[UpdateListModel.SELECTED]:
+ self.install_button.props.sensitive = True
+ return
+ self.install_button.props.sensitive = False
+
+ def get_bundles_to_update(self):
+ bundles_to_update = []
+ for row in self._update_list.props.model:
+ if row[UpdateListModel.SELECTED]:
+ bundles_to_update.append(row[UpdateListModel.BUNDLE_ID])
+ return bundles_to_update
+
+
+class UpdateList(gtk.TreeView):
+
+ def __init__(self, model):
+ list_model = UpdateListModel(model)
+ gtk.TreeView.__init__(self, list_model)
+
+ self.set_reorderable(False)
+ self.set_enable_search(False)
+ self.set_headers_visible(False)
+
+ toggle_renderer = gtk.CellRendererToggle()
+ toggle_renderer.props.activatable = True
+ toggle_renderer.props.xpad = style.DEFAULT_PADDING
+ toggle_renderer.props.indicator_size = style.zoom(26)
+ toggle_renderer.connect('toggled', self.__toggled_cb)
+
+ toggle_column = gtk.TreeViewColumn()
+ toggle_column.pack_start(toggle_renderer)
+ toggle_column.add_attribute(toggle_renderer, 'active',
+ UpdateListModel.SELECTED)
+ self.append_column(toggle_column)
+
+ icon_renderer = CellRendererIcon(self)
+ icon_renderer.props.width = style.STANDARD_ICON_SIZE
+ icon_renderer.props.height = style.STANDARD_ICON_SIZE
+ icon_renderer.props.size = style.STANDARD_ICON_SIZE
+ icon_renderer.props.xpad = style.DEFAULT_PADDING
+ icon_renderer.props.ypad = style.DEFAULT_PADDING
+ icon_renderer.props.stroke_color = style.COLOR_TOOLBAR_GREY.get_svg()
+ icon_renderer.props.fill_color = style.COLOR_TRANSPARENT.get_svg()
+
+ icon_column = gtk.TreeViewColumn()
+ icon_column.pack_start(icon_renderer)
+ icon_column.add_attribute(icon_renderer, 'file-name',
+ UpdateListModel.ICON_FILE_NAME)
+ self.append_column(icon_column)
+
+ text_renderer = gtk.CellRendererText()
+
+ description_column = gtk.TreeViewColumn()
+ description_column.pack_start(text_renderer)
+ description_column.add_attribute(text_renderer, 'markup',
+ UpdateListModel.DESCRIPTION)
+ self.append_column(description_column)
+
+ def __toggled_cb(self, cell_renderer, path):
+ row = self.props.model[path]
+ row[UpdateListModel.SELECTED] = not row[UpdateListModel.SELECTED]
+
+ def refresh(self):
+ pass
+
+
+class UpdateListModel(gtk.ListStore):
+
+ BUNDLE_ID = 0
+ SELECTED = 1
+ ICON_FILE_NAME = 2
+ DESCRIPTION = 3
+ SIZE = 4
+
+ def __init__(self, model):
+ gtk.ListStore.__init__(self, str, bool, str, str, int)
+
+ for bundle_update in model.updates:
+ row = [None] * 5
+ row[self.BUNDLE_ID] = bundle_update.bundle.get_bundle_id()
+ row[self.SELECTED] = True
+ row[self.ICON_FILE_NAME] = bundle_update.bundle.get_icon()
+
+ details = _('From version %(current)d to %(new)s (Size: %(size)s)')
+ details = details % \
+ {'current': bundle_update.bundle.get_activity_version(),
+ 'new': bundle_update.version,
+ 'size': _format_size(bundle_update.size)}
+
+ row[self.DESCRIPTION] = '<b>%s</b>\n%s' % \
+ (bundle_update.bundle.get_name(), details)
+
+ row[self.SIZE] = bundle_update.size
+
+ self.append(row)
+
+
+def _format_size(size):
+ '''
+ Convert a given size in bytes to a nicer better readable unit
+ '''
+ if size == 0:
+ # TRANS: download size is 0
+ return _('None')
+ elif size < 1024:
+ # TRANS: download size of very small updates
+ return _('1 KB')
+ elif size < 1024 * 1024:
+ # TRANS: download size of small updates, e.g. '250 KB'
+ return locale.format(_('%.0f KB'), size / 1024.0)
+ else:
+ # TRANS: download size of updates, e.g. '2.3 MB'
+ return locale.format(_('%.1f MB'), size / 1024.0 / 1024)
diff --git a/shell/extensions/deviceicon/Makefile.am b/shell/extensions/deviceicon/Makefile.am
new file mode 100644
index 0000000..d46ddde
--- /dev/null
+++ b/shell/extensions/deviceicon/Makefile.am
@@ -0,0 +1,9 @@
+sugardir = $(pkgdatadir)/extensions/deviceicon
+
+sugar_PYTHON = \
+ __init__.py \
+ battery.py \
+ network.py \
+ speaker.py \
+ touchpad.py \
+ volume.py
diff --git a/src/carquinyol/__init__.py b/shell/extensions/deviceicon/__init__.py
index e69de29..e69de29 100644
--- a/src/carquinyol/__init__.py
+++ b/shell/extensions/deviceicon/__init__.py
diff --git a/shell/extensions/deviceicon/battery.py b/shell/extensions/deviceicon/battery.py
new file mode 100644
index 0000000..edfcce4
--- /dev/null
+++ b/shell/extensions/deviceicon/battery.py
@@ -0,0 +1,251 @@
+# Copyright (C) 2006-2007, Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import logging
+from gettext import gettext as _
+import gconf
+
+import gobject
+import gtk
+import dbus
+
+from sugar.graphics import style
+from sugar.graphics.icon import get_icon_state
+from sugar.graphics.tray import TrayIcon
+from sugar.graphics.palette import Palette
+from sugar.graphics.xocolor import XoColor
+
+from jarabe.frame.frameinvoker import FrameWidgetInvoker
+
+_ICON_NAME = 'battery'
+
+_STATUS_CHARGING = 0
+_STATUS_DISCHARGING = 1
+_STATUS_FULLY_CHARGED = 2
+_STATUS_NOT_PRESENT = 3
+
+_LEVEL_PROP = 'battery.charge_level.percentage'
+_CHARGING_PROP = 'battery.rechargeable.is_charging'
+_DISCHARGING_PROP = 'battery.rechargeable.is_discharging'
+_PRESENT_PROP = 'battery.present'
+
+class DeviceView(TrayIcon):
+
+ FRAME_POSITION_RELATIVE = 102
+
+ def __init__(self, udi):
+ client = gconf.client_get_default()
+ self._color = XoColor(client.get_string('/desktop/sugar/user/color'))
+
+ TrayIcon.__init__(self, icon_name=_ICON_NAME, xo_color=self._color)
+
+ self.set_palette_invoker(FrameWidgetInvoker(self))
+
+ self._model = DeviceModel(udi)
+ self.palette = BatteryPalette(_('My Battery'))
+ self.palette.set_group_id('frame')
+
+ self._model.connect('notify::level',
+ self._battery_status_changed_cb)
+ self._model.connect('notify::charging',
+ self._battery_status_changed_cb)
+ self._model.connect('notify::discharging',
+ self._battery_status_changed_cb)
+ self._model.connect('notify::present',
+ self._battery_status_changed_cb)
+ self._update_info()
+
+ def _update_info(self):
+ name = _ICON_NAME
+ current_level = self._model.props.level
+ xo_color = self._color
+ badge_name = None
+
+ if not self._model.props.present:
+ status = _STATUS_NOT_PRESENT
+ badge_name = None
+ xo_color = XoColor('%s,%s' % (style.COLOR_WHITE.get_svg(),
+ style.COLOR_WHITE.get_svg()))
+ elif self._model.props.charging:
+ status = _STATUS_CHARGING
+ name += '-charging'
+ xo_color = XoColor('%s,%s' % (style.COLOR_WHITE.get_svg(),
+ style.COLOR_WHITE.get_svg()))
+ elif self._model.props.discharging:
+ status = _STATUS_DISCHARGING
+ if current_level <= 15:
+ badge_name = 'emblem-warning'
+ else:
+ status = _STATUS_FULLY_CHARGED
+
+ self.icon.props.icon_name = get_icon_state(name, current_level, step=-5)
+ self.icon.props.xo_color = xo_color
+ self.icon.props.badge_name = badge_name
+
+ self.palette.set_level(current_level)
+ self.palette.set_status(status)
+
+ def _battery_status_changed_cb(self, pspec, param):
+ self._update_info()
+
+class BatteryPalette(Palette):
+
+ def __init__(self, primary_text):
+ Palette.__init__(self, primary_text)
+
+ self._level = 0
+ self._progress_bar = gtk.ProgressBar()
+ self._progress_bar.set_size_request(
+ style.zoom(style.GRID_CELL_SIZE * 4), -1)
+ self._progress_bar.show()
+ self._status_label = gtk.Label()
+ self._status_label.show()
+
+ vbox = gtk.VBox()
+ vbox.pack_start(self._progress_bar)
+ vbox.pack_start(self._status_label)
+ vbox.show()
+
+ self._progress_widget = vbox
+ self.set_content(self._progress_widget)
+
+ def set_level(self, percent):
+ self._level = percent
+ fraction = percent / 100.0
+ self._progress_bar.set_fraction(fraction)
+
+ def set_status(self, status):
+ current_level = self._level
+ secondary_text = ''
+ status_text = '%s%%' % current_level
+
+ progress_widget = self._progress_widget
+ if status == _STATUS_NOT_PRESENT:
+ secondary_text = _('Removed')
+ progress_widget = None
+ elif status == _STATUS_CHARGING:
+ secondary_text = _('Charging')
+ elif status == _STATUS_DISCHARGING:
+ if current_level <= 15:
+ secondary_text = _('Very little power remaining')
+ else:
+ #TODO: make this less of an wild/educated guess
+ minutes_remaining = int(current_level / 0.59)
+ remaining_hourpart = minutes_remaining / 60
+ remaining_minpart = minutes_remaining % 60
+ secondary_text = _('%(hour)d:%(min).2d remaining') % \
+ {'hour': remaining_hourpart, 'min': remaining_minpart}
+ else:
+ secondary_text = _('Charged')
+ self.set_content(progress_widget)
+
+ self.props.secondary_text = secondary_text
+ self._status_label.set_text(status_text)
+
+class DeviceModel(gobject.GObject):
+ __gproperties__ = {
+ 'level' : (int, None, None, 0, 100, 0,
+ gobject.PARAM_READABLE),
+ 'charging' : (bool, None, None, False,
+ gobject.PARAM_READABLE),
+ 'discharging' : (bool, None, None, False,
+ gobject.PARAM_READABLE),
+ 'present' : (bool, None, None, False,
+ gobject.PARAM_READABLE)
+ }
+
+ def __init__(self, udi):
+ gobject.GObject.__init__(self)
+
+ bus = dbus.Bus(dbus.Bus.TYPE_SYSTEM)
+ proxy = bus.get_object('org.freedesktop.Hal', udi,
+ follow_name_owner_changes=True)
+ self._battery = dbus.Interface(proxy, 'org.freedesktop.Hal.Device')
+ bus.add_signal_receiver(self._battery_changed,
+ 'PropertyModified',
+ 'org.freedesktop.Hal.Device',
+ 'org.freedesktop.Hal',
+ udi)
+
+ self._level = self._get_level()
+ self._charging = self._get_charging()
+ self._discharging = self._get_discharging()
+ self._present = self._get_present()
+
+ def _get_level(self):
+ try:
+ return self._battery.GetProperty(_LEVEL_PROP)
+ except dbus.DBusException:
+ logging.error('Cannot access %s', _LEVEL_PROP)
+ return 0
+
+ def _get_charging(self):
+ try:
+ return self._battery.GetProperty(_CHARGING_PROP)
+ except dbus.DBusException:
+ logging.error('Cannot access %s', _CHARGING_PROP)
+ return False
+
+ def _get_discharging(self):
+ try:
+ return self._battery.GetProperty(_DISCHARGING_PROP)
+ except dbus.DBusException:
+ logging.error('Cannot access %s', _DISCHARGING_PROP)
+ return False
+
+ def _get_present(self):
+ try:
+ return self._battery.GetProperty(_PRESENT_PROP)
+ except dbus.DBusException:
+ logging.error('Cannot access %s', _PRESENT_PROP)
+ return False
+
+ def do_get_property(self, pspec):
+ if pspec.name == 'level':
+ return self._level
+ if pspec.name == 'charging':
+ return self._charging
+ if pspec.name == 'discharging':
+ return self._discharging
+ if pspec.name == 'present':
+ return self._present
+
+ def get_type(self):
+ return 'battery'
+
+ def _battery_changed(self, num_changes, changes_list):
+ for change in changes_list:
+ if change[0] == _LEVEL_PROP:
+ self._level = self._get_level()
+ self.notify('level')
+ elif change[0] == _CHARGING_PROP:
+ self._charging = self._get_charging()
+ self.notify('charging')
+ elif change[0] == _DISCHARGING_PROP:
+ self._discharging = self._get_discharging()
+ self.notify('discharging')
+ elif change[0] == _PRESENT_PROP:
+ self._present = self._get_present()
+ self.notify('present')
+
+def setup(tray):
+ bus = dbus.Bus(dbus.Bus.TYPE_SYSTEM)
+ proxy = bus.get_object('org.freedesktop.Hal',
+ '/org/freedesktop/Hal/Manager')
+ hal_manager = dbus.Interface(proxy, 'org.freedesktop.Hal.Manager')
+
+ for udi in hal_manager.FindDeviceByCapability('battery'):
+ tray.add_device(DeviceView(udi))
diff --git a/shell/extensions/deviceicon/network.py b/shell/extensions/deviceicon/network.py
new file mode 100644
index 0000000..6171f39
--- /dev/null
+++ b/shell/extensions/deviceicon/network.py
@@ -0,0 +1,1006 @@
+#
+# Copyright (C) 2008 One Laptop Per Child
+# Copyright (C) 2009 Tomeu Vizoso, Simon Schampijer
+# Copyright (C) 2009 Paraguay Educa, Martin Abente
+# Copyright (C) 2010 Plan Ceibal, Daniel Castelo
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from gettext import gettext as _
+import logging
+import hashlib
+import socket
+import struct
+import re
+import datetime
+import time
+import gtk
+import glib
+import gobject
+import gconf
+import dbus
+
+from sugar.graphics.icon import get_icon_state
+from sugar.graphics import style
+from sugar.graphics.palette import Palette
+from sugar.graphics.toolbutton import ToolButton
+from sugar.graphics.tray import TrayIcon
+from sugar.graphics.menuitem import MenuItem
+from sugar.graphics.icon import Icon
+from sugar.graphics import xocolor
+from sugar.util import unique_id
+from sugar import profile
+
+from jarabe.model import network
+from jarabe.model.network import Settings
+from jarabe.model.network import IP4Config
+from jarabe.frame.frameinvoker import FrameWidgetInvoker
+from jarabe.view.pulsingicon import PulsingIcon
+
+IP_ADDRESS_TEXT_TEMPLATE = _("IP address: %s")
+
+_NM_SERVICE = 'org.freedesktop.NetworkManager'
+_NM_IFACE = 'org.freedesktop.NetworkManager'
+_NM_PATH = '/org/freedesktop/NetworkManager'
+_NM_DEVICE_IFACE = 'org.freedesktop.NetworkManager.Device'
+_NM_WIRED_IFACE = 'org.freedesktop.NetworkManager.Device.Wired'
+_NM_WIRELESS_IFACE = 'org.freedesktop.NetworkManager.Device.Wireless'
+_NM_SERIAL_IFACE = 'org.freedesktop.NetworkManager.Device.Serial'
+_NM_OLPC_MESH_IFACE = 'org.freedesktop.NetworkManager.Device.OlpcMesh'
+_NM_ACCESSPOINT_IFACE = 'org.freedesktop.NetworkManager.AccessPoint'
+_NM_ACTIVE_CONN_IFACE = 'org.freedesktop.NetworkManager.Connection.Active'
+
+_GSM_STATE_NOT_READY = 0
+_GSM_STATE_DISCONNECTED = 1
+_GSM_STATE_CONNECTING = 2
+_GSM_STATE_CONNECTED = 3
+_GSM_STATE_NEED_AUTH = 4
+
+
+class WirelessPalette(Palette):
+ __gtype_name__ = 'SugarWirelessPalette'
+
+ __gsignals__ = {
+ 'deactivate-connection' : (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([]))
+ }
+
+ def __init__(self, primary_text):
+ Palette.__init__(self, label=primary_text)
+
+ self._disconnect_item = None
+
+ self._channel_label = gtk.Label()
+ self._channel_label.props.xalign = 0.0
+ self._channel_label.show()
+
+ self._ip_address_label = gtk.Label()
+
+ self._info = gtk.VBox()
+
+ def _padded(child, xalign=0, yalign=0.5):
+ padder = gtk.Alignment(xalign=xalign, yalign=yalign,
+ xscale=1, yscale=0.33)
+ padder.set_padding(style.DEFAULT_SPACING,
+ style.DEFAULT_SPACING,
+ style.DEFAULT_SPACING,
+ style.DEFAULT_SPACING)
+ padder.add(child)
+ return padder
+
+ self._info.pack_start(_padded(self._channel_label))
+ self._info.pack_start(_padded(self._ip_address_label))
+ self._info.show_all()
+
+ self._disconnect_item = MenuItem(_('Disconnect...'))
+ icon = Icon(icon_size=gtk.ICON_SIZE_MENU, icon_name='media-eject')
+ self._disconnect_item.set_image(icon)
+ self._disconnect_item.connect('activate', self.__disconnect_activate_cb)
+ self.menu.append(self._disconnect_item)
+
+ def set_connecting(self):
+ self.props.secondary_text = _('Connecting...')
+
+ def _set_connected(self, iaddress):
+ self.set_content(self._info)
+ self.props.secondary_text = _('Connected')
+ self._set_ip_address(iaddress)
+ self._disconnect_item.show()
+
+ def set_connected_with_frequency(self, frequency, iaddress):
+ self._set_connected(iaddress)
+ self._set_frequency(frequency)
+
+ def set_connected_with_channel(self, channel, iaddress):
+ self._set_connected(iaddress)
+ self._set_channel(channel)
+
+ def set_disconnected(self):
+ self.props.primary_text = ''
+ self.props.secondary_text = ''
+ self._disconnect_item.hide()
+ self.set_content(None)
+
+ def __disconnect_activate_cb(self, menuitem):
+ self.emit('deactivate-connection')
+
+ def _set_frequency(self, frequency):
+ channel = network.frequency_to_channel(frequency)
+ self._set_channel(channel)
+
+ def _set_channel(self, channel):
+ self._channel_label.set_text("%s: %d" % (_("Channel"), channel))
+
+ def _set_ip_address(self, ip_address):
+ if ip_address is not None:
+ ip_address_text = IP_ADDRESS_TEXT_TEMPLATE % \
+ socket.inet_ntoa(struct.pack('I', ip_address))
+ else:
+ ip_address_text = ""
+ self._ip_address_label.set_text(ip_address_text)
+
+
+class WiredPalette(Palette):
+ __gtype_name__ = 'SugarWiredPalette'
+
+ def __init__(self):
+ Palette.__init__(self, label=_('Wired Network'))
+
+ self._speed_label = gtk.Label()
+ self._speed_label.props.xalign = 0.0
+ self._speed_label.show()
+
+ self._ip_address_label = gtk.Label()
+
+ self._info = gtk.VBox()
+
+ def _padded(child, xalign=0, yalign=0.5):
+ padder = gtk.Alignment(xalign=xalign, yalign=yalign,
+ xscale=1, yscale=0.33)
+ padder.set_padding(style.DEFAULT_SPACING,
+ style.DEFAULT_SPACING,
+ style.DEFAULT_SPACING,
+ style.DEFAULT_SPACING)
+ padder.add(child)
+ return padder
+
+ self._info.pack_start(_padded(self._speed_label))
+ self._info.pack_start(_padded(self._ip_address_label))
+ self._info.show_all()
+
+ self.set_content(self._info)
+ self.props.secondary_text = _('Connected')
+
+ def set_connected(self, speed, iaddress):
+ self._speed_label.set_text('%s: %d Mb/s' % (_('Speed'), speed))
+ self._set_ip_address(iaddress)
+
+ def _inet_ntoa(self, iaddress):
+ address = ['%s' % ((iaddress >> i) % 256) for i in [0, 8, 16, 24]]
+ return ".".join(address)
+
+ def _set_ip_address(self, ip_address):
+ if ip_address is not None:
+ ip_address_text = IP_ADDRESS_TEXT_TEMPLATE % \
+ socket.inet_ntoa(struct.pack('I', ip_address))
+ else:
+ ip_address_text = ""
+ self._ip_address_label.set_text(ip_address_text)
+
+class GsmPalette(Palette):
+ __gtype_name__ = 'SugarGsmPalette'
+
+ __gsignals__ = {
+ 'gsm-connect' : (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([])),
+ 'gsm-disconnect' : (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([])),
+ }
+
+ def __init__(self):
+
+ Palette.__init__(self, label=_('Wireless modem'))
+
+ self._current_state = None
+
+ self._toggle_state_item = gtk.MenuItem('')
+ self._toggle_state_item.connect('activate', self.__toggle_state_cb)
+ self.menu.append(self._toggle_state_item)
+ self._toggle_state_item.show()
+
+ self.set_state(_GSM_STATE_NOT_READY)
+
+ self.info_box = gtk.VBox()
+
+ self.data_label = gtk.Label()
+ self.data_label.props.xalign = 0.0
+ label_alignment = self._add_widget_with_padding(self.data_label)
+ self.info_box.pack_start(label_alignment)
+ self.data_label.show()
+ label_alignment.show()
+
+ self.connection_time_label = gtk.Label()
+ self.connection_time_label.props.xalign = 0.0
+ label_alignment = self._add_widget_with_padding( \
+ self.connection_time_label)
+ self.info_box.pack_start(label_alignment)
+ self.connection_time_label.show()
+ label_alignment.show()
+
+ self.info_box.show()
+ self.set_content(self.info_box)
+
+ def _add_widget_with_padding(self, child, xalign=0, yalign=0.5):
+ alignment = gtk.Alignment(xalign=xalign, yalign=yalign,
+ xscale=1, yscale=0.33)
+ alignment.set_padding(style.DEFAULT_SPACING,
+ style.DEFAULT_SPACING,
+ style.DEFAULT_SPACING,
+ style.DEFAULT_SPACING)
+ alignment.add(child)
+ return alignment
+
+ def set_state(self, state):
+ self._current_state = state
+ self._update_label_and_text()
+
+ def _update_label_and_text(self):
+ if self._current_state == _GSM_STATE_NOT_READY:
+ self._toggle_state_item.get_child().set_label('...')
+ self.props.secondary_text = _('Please wait...')
+
+ elif self._current_state == _GSM_STATE_DISCONNECTED:
+ self._toggle_state_item.get_child().set_label(_('Connect'))
+ self.props.secondary_text = _('Disconnected')
+
+ elif self._current_state == _GSM_STATE_CONNECTING:
+ self._toggle_state_item.get_child().set_label(_('Cancel'))
+ self.props.secondary_text = _('Connecting...')
+
+ elif self._current_state == _GSM_STATE_CONNECTED:
+ self._toggle_state_item.get_child().set_label(_('Disconnect'))
+ self.props.secondary_text = _('Connected')
+
+ elif self._current_state == _GSM_STATE_NEED_AUTH:
+ self._toggle_state_item.get_child().set_label(_('Sim requires Pin/Puk'))
+ self.props.secondary_text = _('Authentication Error')
+
+ else:
+ raise ValueError('Invalid GSM state while updating label and ' \
+ 'text, %s' % str(self._current_state))
+
+ def __toggle_state_cb(self, menuitem):
+ if self._current_state == _GSM_STATE_NOT_READY:
+ pass
+ elif self._current_state == _GSM_STATE_DISCONNECTED:
+ self.emit('gsm-connect')
+ elif self._current_state == _GSM_STATE_CONNECTING:
+ self.emit('gsm-disconnect')
+ elif self._current_state == _GSM_STATE_CONNECTED:
+ self.emit('gsm-disconnect')
+ elif self._current_state == _GSM_STATE_NEED_AUTH:
+ self.emit('gsm-disconnect')
+ else:
+ raise ValueError('Invalid GSM state while emitting signal, %s' % \
+ str(self._current_state))
+
+
+class WirelessDeviceView(ToolButton):
+
+ FRAME_POSITION_RELATIVE = 302
+
+ def __init__(self, device):
+ ToolButton.__init__(self)
+
+ self._bus = dbus.SystemBus()
+ self._device = device
+ self._device_props = None
+ self._flags = 0
+ self._name = ''
+ self._mode = network.NM_802_11_MODE_UNKNOWN
+ self._strength = 0
+ self._frequency = 0
+ self._device_state = None
+ self._color = None
+ self._active_ap_op = None
+
+ self._icon = PulsingIcon()
+ self._icon.props.icon_name = get_icon_state('network-wireless', 0)
+ self._inactive_color = xocolor.XoColor( \
+ "%s,%s" % (style.COLOR_BUTTON_GREY.get_svg(),
+ style.COLOR_TRANSPARENT.get_svg()))
+ self._icon.props.pulse_color = self._inactive_color
+ self._icon.props.base_color = self._inactive_color
+
+ self.set_icon_widget(self._icon)
+ self._icon.show()
+
+ self.set_palette_invoker(FrameWidgetInvoker(self))
+ self._palette = WirelessPalette(self._name)
+ self._palette.connect('deactivate-connection',
+ self.__deactivate_connection_cb)
+ self.set_palette(self._palette)
+ self._palette.set_group_id('frame')
+
+ self._device_props = dbus.Interface(self._device,
+ 'org.freedesktop.DBus.Properties')
+ self._device_props.GetAll(_NM_DEVICE_IFACE, byte_arrays=True,
+ reply_handler=self.__get_device_props_reply_cb,
+ error_handler=self.__get_device_props_error_cb)
+
+ self._device_props.Get(_NM_WIRELESS_IFACE, 'ActiveAccessPoint',
+ reply_handler=self.__get_active_ap_reply_cb,
+ error_handler=self.__get_active_ap_error_cb)
+
+ self._bus.add_signal_receiver(self.__state_changed_cb,
+ signal_name='StateChanged',
+ path=self._device.object_path,
+ dbus_interface=_NM_DEVICE_IFACE)
+
+ def disconnect(self):
+ self._bus.remove_signal_receiver(self.__state_changed_cb,
+ signal_name='StateChanged',
+ path=self._device.object_path,
+ dbus_interface=_NM_DEVICE_IFACE)
+
+ def __get_device_props_reply_cb(self, properties):
+ if 'State' in properties:
+ self._device_state = properties['State']
+ self._update_state()
+
+ def __get_device_props_error_cb(self, err):
+ logging.error('Error getting the device properties: %s', err)
+
+ def __get_active_ap_reply_cb(self, active_ap_op):
+ if self._active_ap_op != active_ap_op:
+ if self._active_ap_op is not None:
+ self._bus.remove_signal_receiver(
+ self.__ap_properties_changed_cb,
+ signal_name='PropertiesChanged',
+ path=self._active_ap_op,
+ dbus_interface=_NM_ACCESSPOINT_IFACE)
+ if active_ap_op == '/':
+ self._active_ap_op = None
+ return
+ self._active_ap_op = active_ap_op
+ active_ap = self._bus.get_object(_NM_SERVICE, active_ap_op)
+ props = dbus.Interface(active_ap, 'org.freedesktop.DBus.Properties')
+
+ props.GetAll(_NM_ACCESSPOINT_IFACE, byte_arrays=True,
+ reply_handler=self.__get_all_ap_props_reply_cb,
+ error_handler=self.__get_all_ap_props_error_cb)
+
+ self._bus.add_signal_receiver(self.__ap_properties_changed_cb,
+ signal_name='PropertiesChanged',
+ path=self._active_ap_op,
+ dbus_interface=_NM_ACCESSPOINT_IFACE)
+
+ def __get_active_ap_error_cb(self, err):
+ logging.error('Error getting the active access point: %s', err)
+
+ def __state_changed_cb(self, new_state, old_state, reason):
+ self._device_state = new_state
+ self._update_state()
+ self._device_props.Get(_NM_WIRELESS_IFACE, 'ActiveAccessPoint',
+ reply_handler=self.__get_active_ap_reply_cb,
+ error_handler=self.__get_active_ap_error_cb)
+
+ def __ap_properties_changed_cb(self, properties):
+ self._update_properties(properties)
+
+ def _update_properties(self, properties):
+ if 'Mode' in properties:
+ self._mode = properties['Mode']
+ self._color = None
+ if 'Ssid' in properties:
+ self._name = properties['Ssid']
+ self._color = None
+ if 'Strength' in properties:
+ self._strength = properties['Strength']
+ if 'Flags' in properties:
+ self._flags = properties['Flags']
+ if 'Frequency' in properties:
+ self._frequency = properties['Frequency']
+
+ if self._color == None:
+ if self._mode == network.NM_802_11_MODE_ADHOC and \
+ network.is_sugar_adhoc_network(self._name):
+ self._color = profile.get_color()
+ else:
+ sha_hash = hashlib.sha1()
+ data = self._name + hex(self._flags)
+ sha_hash.update(data)
+ digest = hash(sha_hash.digest())
+ index = digest % len(xocolor.colors)
+
+ self._color = xocolor.XoColor('%s,%s' %
+ (xocolor.colors[index][0],
+ xocolor.colors[index][1]))
+ self._update()
+
+ def __get_all_ap_props_reply_cb(self, properties):
+ self._update_properties(properties)
+
+ def __get_all_ap_props_error_cb(self, err):
+ logging.error('Error getting the access point properties: %s', err)
+
+ def _update(self):
+ if self._flags == network.NM_802_11_AP_FLAGS_PRIVACY:
+ self._icon.props.badge_name = "emblem-locked"
+ else:
+ self._icon.props.badge_name = None
+
+ self._palette.props.primary_text = glib.markup_escape_text(self._name)
+
+ self._update_state()
+ self._update_color()
+
+ def _update_state(self):
+ if self._active_ap_op is not None:
+ state = self._device_state
+ else:
+ state = network.DEVICE_STATE_UNKNOWN
+
+ if self._mode != network.NM_802_11_MODE_ADHOC and \
+ network.is_sugar_adhoc_network(self._name) == False:
+ if state == network.DEVICE_STATE_ACTIVATED:
+ icon_name = '%s-connected' % 'network-wireless'
+ else:
+ icon_name = 'network-wireless'
+
+ icon_name = get_icon_state(icon_name, self._strength)
+ if icon_name:
+ self._icon.props.icon_name = icon_name
+ else:
+ channel = network.frequency_to_channel(self._frequency)
+ if state == network.DEVICE_STATE_ACTIVATED:
+ self._icon.props.icon_name = 'network-adhoc-%s-connected' \
+ % channel
+ else:
+ self._icon.props.icon_name = 'network-adhoc-%s' % channel
+ self._icon.props.base_color = profile.get_color()
+
+ if state == network.DEVICE_STATE_PREPARE or \
+ state == network.DEVICE_STATE_CONFIG or \
+ state == network.DEVICE_STATE_NEED_AUTH or \
+ state == network.DEVICE_STATE_IP_CONFIG:
+ self._palette.set_connecting()
+ self._icon.props.pulsing = True
+ elif state == network.DEVICE_STATE_ACTIVATED:
+ address = self._device_props.Get(_NM_DEVICE_IFACE, 'Ip4Address')
+ self._palette.set_connected_with_frequency(self._frequency, address)
+ self._icon.props.pulsing = False
+ else:
+ self._icon.props.badge_name = None
+ self._icon.props.pulsing = False
+ self._icon.props.pulse_color = self._inactive_color
+ self._icon.props.base_color = self._inactive_color
+ self._palette.set_disconnected()
+
+ def _update_color(self):
+ self._icon.props.base_color = self._color
+
+ def __deactivate_connection_cb(self, palette, data=None):
+ if self._active_ap_op is not None:
+ obj = self._bus.get_object(_NM_SERVICE, _NM_PATH)
+ netmgr = dbus.Interface(obj, _NM_IFACE)
+ netmgr_props = dbus.Interface(
+ netmgr, 'org.freedesktop.DBus.Properties')
+ active_connections_o = netmgr_props.Get(_NM_IFACE,
+ 'ActiveConnections')
+
+ for conn_o in active_connections_o:
+ obj = self._bus.get_object(_NM_IFACE, conn_o)
+ props = dbus.Interface(obj, 'org.freedesktop.DBus.Properties')
+ ap_op = props.Get(_NM_ACTIVE_CONN_IFACE, 'SpecificObject')
+ if ap_op == self._active_ap_op:
+ netmgr.DeactivateConnection(conn_o)
+ break
+
+ def __activate_reply_cb(self, connection):
+ logging.debug('Network created: %s', connection)
+
+ def __activate_error_cb(self, err):
+ logging.debug('Failed to create network: %s', err)
+
+
+class OlpcMeshDeviceView(ToolButton):
+ _ICON_NAME = 'network-mesh'
+ FRAME_POSITION_RELATIVE = 302
+
+ def __init__(self, device, state):
+ ToolButton.__init__(self)
+
+ self._bus = dbus.SystemBus()
+ self._device = device
+ self._device_props = None
+ self._device_state = None
+ self._channel = 0
+
+ self._icon = PulsingIcon(icon_name=self._ICON_NAME)
+ self._inactive_color = xocolor.XoColor( \
+ "%s,%s" % (style.COLOR_BUTTON_GREY.get_svg(),
+ style.COLOR_TRANSPARENT.get_svg()))
+ self._icon.props.pulse_color = profile.get_color()
+ self._icon.props.base_color = self._inactive_color
+
+ self.set_icon_widget(self._icon)
+ self._icon.show()
+
+ self.set_palette_invoker(FrameWidgetInvoker(self))
+ self._palette = WirelessPalette(_("Mesh Network"))
+ self._palette.connect('deactivate-connection',
+ self.__deactivate_connection)
+ self.set_palette(self._palette)
+ self._palette.set_group_id('frame')
+
+ self.update_state(state)
+
+ self._device_props = dbus.Interface(self._device,
+ 'org.freedesktop.DBus.Properties')
+ self._device_props.Get(_NM_OLPC_MESH_IFACE, 'ActiveChannel',
+ reply_handler=self.__get_active_channel_reply_cb,
+ error_handler=self.__get_active_channel_error_cb)
+
+ self._bus.add_signal_receiver(self.__wireless_properties_changed_cb,
+ signal_name='PropertiesChanged',
+ path=device.object_path,
+ dbus_interface=_NM_OLPC_MESH_IFACE)
+
+ def disconnect(self):
+ self._bus.remove_signal_receiver(self.__wireless_properties_changed_cb,
+ signal_name='PropertiesChanged',
+ path=self._device.object_path,
+ dbus_interface=_NM_OLPC_MESH_IFACE)
+
+ def __get_active_channel_reply_cb(self, channel):
+ self._channel = channel
+ self._update_text()
+
+ def __get_active_channel_error_cb(self, err):
+ logging.error('Error getting the active channel: %s', err)
+
+ def __state_changed_cb(self, new_state, old_state, reason):
+ self._device_state = new_state
+ self._update()
+
+ def __wireless_properties_changed_cb(self, properties):
+ if 'ActiveChannel' in properties:
+ self._channel = properties['ActiveChannel']
+ self._update_text()
+
+ def _update_text(self):
+ channel = str(self._channel)
+ text = _("Mesh Network %s") % glib.markup_escape_text(channel)
+ self._palette.props.primary_text = text
+
+ def _update(self):
+ state = self._device_state
+
+ if state in [network.DEVICE_STATE_PREPARE,
+ network.DEVICE_STATE_CONFIG,
+ network.DEVICE_STATE_NEED_AUTH,
+ network.DEVICE_STATE_IP_CONFIG]:
+ self._icon.props.base_color = self._inactive_color
+ self._icon.props.pulse_color = profile.get_color()
+ self._palette.set_connecting()
+ self._icon.props.pulsing = True
+ elif state == network.DEVICE_STATE_ACTIVATED:
+ address = self._device_props.Get(_NM_DEVICE_IFACE, 'Ip4Address')
+ self._palette.set_connected_with_channel(self._channel, address)
+ self._icon.props.base_color = profile.get_color()
+ self._icon.props.pulsing = False
+ self._update_text()
+
+ def update_state(self, state):
+ self._device_state = state
+ self._update()
+
+ def __deactivate_connection(self, palette, data=None):
+ obj = self._bus.get_object(_NM_SERVICE, _NM_PATH)
+ netmgr = dbus.Interface(obj, _NM_IFACE)
+ netmgr_props = dbus.Interface(netmgr, 'org.freedesktop.DBus.Properties')
+ active_connections_o = netmgr_props.Get(_NM_IFACE,
+ 'ActiveConnections')
+
+ for conn_o in active_connections_o:
+ # The connection path for a mesh connection is the device itself.
+ obj = self._bus.get_object(_NM_IFACE, conn_o)
+ props = dbus.Interface(obj, 'org.freedesktop.DBus.Properties')
+ ap_op = props.Get(_NM_ACTIVE_CONN_IFACE, 'SpecificObject')
+
+ try:
+ obj = self._bus.get_object(_NM_IFACE, ap_op)
+ props = dbus.Interface(obj, 'org.freedesktop.DBus.Properties')
+ type = props.Get(_NM_DEVICE_IFACE, 'DeviceType')
+ if type == network.DEVICE_TYPE_802_11_OLPC_MESH:
+ netmgr.DeactivateConnection(conn_o)
+ break
+ except dbus.exceptions.DBusException:
+ pass
+
+
+class WiredDeviceView(TrayIcon):
+
+ _ICON_NAME = 'network-wired'
+ FRAME_POSITION_RELATIVE = 301
+
+ def __init__(self, speed, address):
+ client = gconf.client_get_default()
+ color = xocolor.XoColor(client.get_string('/desktop/sugar/user/color'))
+
+ TrayIcon.__init__(self, icon_name=self._ICON_NAME, xo_color=color)
+
+ self.set_palette_invoker(FrameWidgetInvoker(self))
+ self._palette = WiredPalette()
+ self.set_palette(self._palette)
+ self._palette.set_group_id('frame')
+ self._palette.set_connected(speed, address)
+
+
+class GsmDeviceView(TrayIcon):
+
+ _ICON_NAME = 'network-gsm'
+ FRAME_POSITION_RELATIVE = 303
+
+ def __init__(self, device):
+ self._connection_time_handler = None
+ self._connection_timestamp = 0
+
+ client = gconf.client_get_default()
+ color = xocolor.XoColor(client.get_string('/desktop/sugar/user/color'))
+
+ TrayIcon.__init__(self, icon_name=self._ICON_NAME, xo_color=color)
+
+ self._bus = dbus.SystemBus()
+ self._device = device
+ self._palette = None
+ self.set_palette_invoker(FrameWidgetInvoker(self))
+
+ self._bus.add_signal_receiver(self.__state_changed_cb,
+ signal_name='StateChanged',
+ path=self._device.object_path,
+ dbus_interface=_NM_DEVICE_IFACE)
+ self._bus.add_signal_receiver(self.__ppp_stats_changed_cb,
+ signal_name='PppStats',
+ path=self._device.object_path,
+ dbus_interface=_NM_SERIAL_IFACE)
+ def create_palette(self):
+ palette = GsmPalette()
+
+ palette.set_group_id('frame')
+ palette.connect('gsm-connect', self.__gsm_connect_cb)
+ palette.connect('gsm-disconnect', self.__gsm_disconnect_cb)
+
+ self._palette = palette
+
+ props = dbus.Interface(self._device, 'org.freedesktop.DBus.Properties')
+ props.GetAll(_NM_DEVICE_IFACE, byte_arrays=True,
+ reply_handler=self.__current_state_check_cb,
+ error_handler=self.__current_state_check_error_cb)
+
+ return palette
+
+ def __gsm_connect_cb(self, palette, data=None):
+ connection = network.find_gsm_connection()
+ if connection is not None:
+ obj = self._bus.get_object(_NM_SERVICE, _NM_PATH)
+ netmgr = dbus.Interface(obj, _NM_IFACE)
+ netmgr.ActivateConnection(network.SETTINGS_SERVICE,
+ connection.path,
+ self._device.object_path,
+ '/',
+ reply_handler=self.__connect_cb,
+ error_handler=self.__connect_error_cb)
+
+ def __connect_cb(self, active_connection):
+ logging.debug('Connected successfully to gsm device, %s',
+ active_connection)
+
+ def __connect_error_cb(self, error):
+ raise RuntimeError('Error when connecting to gsm device, %s' % error)
+
+ def __gsm_disconnect_cb(self, palette, data=None):
+ obj = self._bus.get_object(_NM_SERVICE, _NM_PATH)
+ netmgr = dbus.Interface(obj, _NM_IFACE)
+ netmgr_props = dbus.Interface(netmgr, 'org.freedesktop.DBus.Properties')
+ active_connections_o = netmgr_props.Get(_NM_IFACE, 'ActiveConnections')
+
+ for conn_o in active_connections_o:
+ obj = self._bus.get_object(_NM_IFACE, conn_o)
+ props = dbus.Interface(obj, 'org.freedesktop.DBus.Properties')
+ devices = props.Get(_NM_ACTIVE_CONN_IFACE, 'Devices')
+ if self._device.object_path in devices:
+ netmgr.DeactivateConnection(
+ conn_o,
+ reply_handler=self.__disconnect_cb,
+ error_handler=self.__disconnect_error_cb)
+ break
+
+ def __disconnect_cb(self):
+ logging.debug('Disconnected successfully gsm device')
+
+ def __disconnect_error_cb(self, error):
+ raise RuntimeError('Error when disconnecting gsm device, %s' % error)
+
+ def __state_changed_cb(self, new_state, old_state, reason):
+ logging.debug('State: %s to %s, reason %s', old_state, new_state, reason)
+ self._update_state(int(new_state))
+
+ def __current_state_check_cb(self, properties):
+ self._update_state(int(properties['State']))
+
+ def __current_state_check_error_cb(self, error):
+ raise RuntimeError('Error when checking gsm device state, %s' % error)
+
+ def _update_state(self, state):
+ gsm_state = None
+
+ if state is network.DEVICE_STATE_ACTIVATED:
+ gsm_state = _GSM_STATE_CONNECTED
+ connection = network.find_gsm_connection()
+ if connection is not None:
+ connection.set_connected()
+ self._connection_timestamp = time.time() - \
+ connection.get_settings().connection.timestamp
+ self._connection_time_handler = gobject.timeout_add_seconds( \
+ 1, self.__connection_timecount_cb)
+ self._update_stats(0, 0)
+ self._update_connection_time()
+ self._palette.info_box.show()
+
+ elif state is network.DEVICE_STATE_DISCONNECTED:
+ gsm_state = _GSM_STATE_DISCONNECTED
+ self._connection_timestamp = 0
+ if self._connection_time_handler is not None:
+ gobject.source_remove(self._connection_time_handler)
+ self._palette.info_box.hide()
+
+ elif state in [network.DEVICE_STATE_UNMANAGED,
+ network.DEVICE_STATE_UNAVAILABLE,
+ network.DEVICE_STATE_UNKNOWN]:
+ gsm_state = _GSM_STATE_NOT_READY
+
+ elif state in [network.DEVICE_STATE_PREPARE,
+ network.DEVICE_STATE_CONFIG,
+ network.DEVICE_STATE_IP_CONFIG]:
+ gsm_state = _GSM_STATE_CONNECTING
+
+ elif state in [network.DEVICE_STATE_NEED_AUTH]:
+ gsm_state = _GSM_STATE_NEED_AUTH
+
+ if self._palette is not None:
+ self._palette.set_state(gsm_state)
+
+ def disconnect(self):
+ self._bus.remove_signal_receiver(self.__state_changed_cb,
+ signal_name='StateChanged',
+ path=self._device.object_path,
+ dbus_interface=_NM_DEVICE_IFACE)
+
+ def __ppp_stats_changed_cb(self, in_bytes, out_bytes):
+ self._update_stats(in_bytes, out_bytes)
+
+ def _update_stats(self, in_bytes, out_bytes):
+ in_KBytes = in_bytes / 1024
+ out_KBytes = out_bytes / 1024
+ text = _("Data sent %d KB / received %d KB") % (out_KBytes, in_KBytes)
+ self._palette.data_label.set_text(text)
+
+ def __connection_timecount_cb(self):
+ self._connection_timestamp = self._connection_timestamp + 1
+ self._update_connection_time()
+ return True
+
+ def _update_connection_time(self):
+ connection_time = datetime.datetime.fromtimestamp( \
+ self._connection_timestamp)
+ text = _("Connection time ") + connection_time.strftime('%H : %M : %S')
+ self._palette.connection_time_label.set_text(text)
+
+class WirelessDeviceObserver(object):
+ def __init__(self, device, tray):
+ self._device = device
+ self._device_view = None
+ self._tray = tray
+ self._device_view = WirelessDeviceView(self._device)
+ self._tray.add_device(self._device_view)
+
+ def disconnect(self):
+ self._device_view.disconnect()
+ self._tray.remove_device(self._device_view)
+ del self._device_view
+ self._device_view = None
+
+
+class MeshDeviceObserver(object):
+ def __init__(self, device, tray):
+ self._bus = dbus.SystemBus()
+ self._device = device
+ self._device_view = None
+ self._tray = tray
+
+ props = dbus.Interface(self._device, dbus.PROPERTIES_IFACE)
+ props.GetAll(_NM_DEVICE_IFACE, byte_arrays=True,
+ reply_handler=self.__get_device_props_reply_cb,
+ error_handler=self.__get_device_props_error_cb)
+
+ self._bus.add_signal_receiver(self.__state_changed_cb,
+ signal_name='StateChanged',
+ path=self._device.object_path,
+ dbus_interface=_NM_DEVICE_IFACE)
+
+ def _remove_device_view(self):
+ self._device_view.disconnect()
+ self._tray.remove_device(self._device_view)
+ self._device_view = None
+
+ def disconnect(self):
+ if self._device_view is not None:
+ self._remove_device_view()
+
+ self._bus.remove_signal_receiver(self.__state_changed_cb,
+ signal_name='StateChanged',
+ path=self._device.object_path,
+ dbus_interface=_NM_DEVICE_IFACE)
+
+ def __get_device_props_reply_cb(self, properties):
+ if 'State' in properties:
+ self._update_state(properties['State'])
+
+ def __get_device_props_error_cb(self, err):
+ logging.error('Error getting the device properties: %s', err)
+
+ def __state_changed_cb(self, new_state, old_state, reason):
+ self._update_state(new_state)
+
+ def _update_state(self, state):
+ if state in (network.DEVICE_STATE_PREPARE, network.DEVICE_STATE_CONFIG,
+ network.DEVICE_STATE_NEED_AUTH,
+ network.DEVICE_STATE_IP_CONFIG,
+ network.DEVICE_STATE_ACTIVATED):
+ if self._device_view is not None:
+ self._device_view.update_state(state)
+ return
+
+ self._device_view = OlpcMeshDeviceView(self._device, state)
+ self._tray.add_device(self._device_view)
+ else:
+ if self._device_view is not None:
+ self._remove_device_view()
+
+
+class WiredDeviceObserver(object):
+ def __init__(self, device, tray):
+ self._bus = dbus.SystemBus()
+ self._device = device
+ self._device_state = None
+ self._device_view = None
+ self._tray = tray
+
+ props = dbus.Interface(self._device, 'org.freedesktop.DBus.Properties')
+ props.GetAll(_NM_DEVICE_IFACE, byte_arrays=True,
+ reply_handler=self.__get_device_props_reply_cb,
+ error_handler=self.__get_device_props_error_cb)
+
+ self._bus.add_signal_receiver(self.__state_changed_cb,
+ signal_name='StateChanged',
+ path=self._device.object_path,
+ dbus_interface=_NM_DEVICE_IFACE)
+
+ def disconnect(self):
+ self._bus.remove_signal_receiver(self.__state_changed_cb,
+ signal_name='StateChanged',
+ path=self._device.object_path,
+ dbus_interface=_NM_DEVICE_IFACE)
+
+ def __get_device_props_reply_cb(self, properties):
+ if 'State' in properties:
+ self._update_state(properties['State'])
+
+ def __get_device_props_error_cb(self, err):
+ logging.error('Error getting the device properties: %s', err)
+
+ def __state_changed_cb(self, new_state, old_state, reason):
+ self._update_state(new_state)
+
+ def _update_state(self, state):
+ if state == network.DEVICE_STATE_ACTIVATED:
+ props = dbus.Interface(self._device,
+ 'org.freedesktop.DBus.Properties')
+ address = props.Get(_NM_DEVICE_IFACE, 'Ip4Address')
+ speed = props.Get(_NM_WIRED_IFACE, 'Speed')
+ self._device_view = WiredDeviceView(speed, address)
+ self._tray.add_device(self._device_view)
+ else:
+ if self._device_view is not None:
+ self._tray.remove_device(self._device_view)
+ del self._device_view
+ self._device_view = None
+
+class GsmDeviceObserver(object):
+ def __init__(self, device, tray):
+ self._device = device
+ self._device_view = None
+ self._tray = tray
+
+ self._device_view = GsmDeviceView(device)
+ self._tray.add_device(self._device_view)
+
+ def disconnect(self):
+ self._device_view.disconnect()
+ self._tray.remove_device(self._device_view)
+ self._device_view = None
+
+class NetworkManagerObserver(object):
+ def __init__(self, tray):
+ self._bus = dbus.SystemBus()
+ self._devices = {}
+ self._netmgr = None
+ self._tray = tray
+
+ try:
+ obj = self._bus.get_object(_NM_SERVICE, _NM_PATH)
+ self._netmgr = dbus.Interface(obj, _NM_IFACE)
+ except dbus.DBusException:
+ logging.error('%s service not available', _NM_SERVICE)
+ return
+
+ self._netmgr.GetDevices(reply_handler=self.__get_devices_reply_cb,
+ error_handler=self.__get_devices_error_cb)
+
+ self._bus.add_signal_receiver(self.__device_added_cb,
+ signal_name='DeviceAdded',
+ dbus_interface=_NM_IFACE)
+ self._bus.add_signal_receiver(self.__device_removed_cb,
+ signal_name='DeviceRemoved',
+ dbus_interface=_NM_IFACE)
+
+ def __get_devices_reply_cb(self, devices):
+ for device_op in devices:
+ self._check_device(device_op)
+
+ def __get_devices_error_cb(self, err):
+ logging.error('Failed to get devices: %s', err)
+
+ def _check_device(self, device_op):
+ nm_device = self._bus.get_object(_NM_SERVICE, device_op)
+ props = dbus.Interface(nm_device, 'org.freedesktop.DBus.Properties')
+
+ device_type = props.Get(_NM_DEVICE_IFACE, 'DeviceType')
+ if device_type == network.DEVICE_TYPE_802_3_ETHERNET:
+ device = WiredDeviceObserver(nm_device, self._tray)
+ self._devices[device_op] = device
+ elif device_type == network.DEVICE_TYPE_802_11_WIRELESS:
+ device = WirelessDeviceObserver(nm_device, self._tray)
+ self._devices[device_op] = device
+ elif device_type == network.DEVICE_TYPE_802_11_OLPC_MESH:
+ device = MeshDeviceObserver(nm_device, self._tray)
+ self._devices[device_op] = device
+ elif device_type == network.DEVICE_TYPE_GSM_MODEM:
+ device = GsmDeviceObserver(nm_device, self._tray)
+ self._devices[device_op] = device
+
+ def __device_added_cb(self, device_op):
+ self._check_device(device_op)
+
+ def __device_removed_cb(self, device_op):
+ if device_op in self._devices:
+ device = self._devices[device_op]
+ device.disconnect()
+ del self._devices[device_op]
+
+def setup(tray):
+ device_observer = NetworkManagerObserver(tray)
diff --git a/shell/extensions/deviceicon/speaker.py b/shell/extensions/deviceicon/speaker.py
new file mode 100644
index 0000000..3a54464
--- /dev/null
+++ b/shell/extensions/deviceicon/speaker.py
@@ -0,0 +1,216 @@
+# Copyright (C) 2008 Martin Dengler
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from gettext import gettext as _
+import gconf
+
+import gobject
+import gtk
+
+from sugar.graphics import style
+from sugar.graphics.icon import get_icon_state, Icon
+from sugar.graphics.menuitem import MenuItem
+from sugar.graphics.tray import TrayIcon
+from sugar.graphics.palette import Palette
+from sugar.graphics.xocolor import XoColor
+
+from jarabe.frame.frameinvoker import FrameWidgetInvoker
+from jarabe.model import sound
+
+_ICON_NAME = 'speaker'
+
+class DeviceView(TrayIcon):
+
+ FRAME_POSITION_RELATIVE = 103
+
+ def __init__(self):
+ client = gconf.client_get_default()
+ self._color = XoColor(client.get_string('/desktop/sugar/user/color'))
+
+ TrayIcon.__init__(self, icon_name=_ICON_NAME, xo_color=self._color)
+
+ self.set_palette_invoker(FrameWidgetInvoker(self))
+
+ self._model = DeviceModel()
+ self._model.connect('notify::level', self.__speaker_status_changed_cb)
+ self._model.connect('notify::muted', self.__speaker_status_changed_cb)
+
+ self.connect('expose-event', self.__expose_event_cb)
+
+ self._icon_widget.connect('button-release-event',
+ self.__button_release_event_cb)
+
+ self._update_info()
+
+ def create_palette(self):
+ palette = SpeakerPalette(_('My Speakers'), model=self._model)
+ palette.set_group_id('frame')
+ return palette
+
+ def _update_info(self):
+ name = _ICON_NAME
+ current_level = self._model.props.level
+ xo_color = self._color
+
+ if self._model.props.muted:
+ name += '-muted'
+ xo_color = XoColor('%s,%s' % (style.COLOR_WHITE.get_svg(),
+ style.COLOR_WHITE.get_svg()))
+
+ self.icon.props.icon_name = get_icon_state(name, current_level, step=-1)
+ self.icon.props.xo_color = xo_color
+
+ def __button_release_event_cb(self, widget, event):
+ if event.button == 1:
+ self._model.props.muted = not self._model.props.muted
+ return True
+ else:
+ return False
+
+ def __expose_event_cb(self, *args):
+ self._update_info()
+
+ def __speaker_status_changed_cb(self, pspec_, param_):
+ self._update_info()
+
+class SpeakerPalette(Palette):
+
+ def __init__(self, primary_text, model):
+ Palette.__init__(self, label=primary_text)
+
+ self._model = model
+
+ vbox = gtk.VBox()
+ self.set_content(vbox)
+ vbox.show()
+
+ vol_step = sound.VOLUME_STEP
+ self._adjustment = gtk.Adjustment(value=self._model.props.level,
+ lower=0,
+ upper=100 + vol_step,
+ step_incr=vol_step,
+ page_incr=vol_step,
+ page_size=vol_step)
+ self._hscale = gtk.HScale(self._adjustment)
+ self._hscale.set_digits(0)
+ self._hscale.set_draw_value(False)
+ vbox.add(self._hscale)
+ self._hscale.show()
+
+ self._mute_item = MenuItem('')
+ self._mute_icon = Icon(icon_size=gtk.ICON_SIZE_MENU)
+ self._mute_item.set_image(self._mute_icon)
+ self.menu.append(self._mute_item)
+ self._mute_item.show()
+
+ self._adjustment_handler_id = \
+ self._adjustment.connect('value_changed',
+ self.__adjustment_changed_cb)
+
+ self._model_notify_level_handler_id = \
+ self._model.connect('notify::level', self.__level_changed_cb)
+ self._model.connect('notify::muted', self.__muted_changed_cb)
+
+ self._mute_item.connect('activate', self.__mute_activate_cb)
+
+ self.connect('popup', self.__popup_cb)
+
+ def _update_muted(self):
+ if self._model.props.muted:
+ mute_item_text = _('Unmute')
+ mute_item_icon_name = 'dialog-ok'
+ else:
+ mute_item_text = _('Mute')
+ mute_item_icon_name = 'dialog-cancel'
+ self._mute_item.get_child().set_text(mute_item_text)
+ self._mute_icon.props.icon_name = mute_item_icon_name
+
+ def _update_level(self):
+ if self._adjustment.value != self._model.props.level:
+ self._adjustment.handler_block(self._adjustment_handler_id)
+ try:
+ self._adjustment.value = self._model.props.level
+ finally:
+ self._adjustment.handler_unblock(self._adjustment_handler_id)
+
+ def __adjustment_changed_cb(self, adj_):
+ self._model.handler_block(self._model_notify_level_handler_id)
+ try:
+ self._model.props.level = self._adjustment.value
+ finally:
+ self._model.handler_unblock(self._model_notify_level_handler_id)
+ self._model.props.muted = self._adjustment.value == 0
+
+ def __level_changed_cb(self, pspec_, param_):
+ self._update_level()
+
+ def __mute_activate_cb(self, menuitem_):
+ self._model.props.muted = not self._model.props.muted
+
+ def __muted_changed_cb(self, pspec_, param_):
+ self._update_muted()
+
+ def __popup_cb(self, palette_):
+ self._update_level()
+ self._update_muted()
+
+class DeviceModel(gobject.GObject):
+ __gproperties__ = {
+ 'level' : (int, None, None, 0, 100, 0, gobject.PARAM_READWRITE),
+ 'muted' : (bool, None, None, False, gobject.PARAM_READWRITE),
+ }
+
+ def __init__(self):
+ gobject.GObject.__init__(self)
+
+ sound.muted_changed.connect(self.__muted_changed_cb)
+ sound.volume_changed.connect(self.__volume_changed_cb)
+
+ def __muted_changed_cb(self, **kwargs):
+ self.notify('muted')
+
+ def __volume_changed_cb(self, **kwargs):
+ self.notify('level')
+
+ def _get_level(self):
+ return sound.get_volume()
+
+ def _set_level(self, new_volume):
+ sound.set_volume(new_volume)
+
+ def _get_muted(self):
+ return sound.get_muted()
+
+ def _set_muted(self, mute):
+ sound.set_muted(mute)
+
+ def get_type(self):
+ return 'speaker'
+
+ def do_get_property(self, pspec):
+ if pspec.name == "level":
+ return self._get_level()
+ elif pspec.name == "muted":
+ return self._get_muted()
+
+ def do_set_property(self, pspec, value):
+ if pspec.name == "level":
+ self._set_level(value)
+ elif pspec.name == "muted":
+ self._set_muted(value)
+
+def setup(tray):
+ tray.add_device(DeviceView())
diff --git a/shell/extensions/deviceicon/touchpad.py b/shell/extensions/deviceicon/touchpad.py
new file mode 100644
index 0000000..d9521c2
--- /dev/null
+++ b/shell/extensions/deviceicon/touchpad.py
@@ -0,0 +1,132 @@
+# Copyright (C) 2010, Walter Bender, Sugar Labs
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+from gettext import gettext as _
+import os
+
+import gtk
+import gconf
+
+import logging
+
+from sugar.graphics.tray import TrayIcon
+from sugar.graphics.xocolor import XoColor
+from sugar.graphics.palette import Palette
+from sugar.graphics import style
+
+from jarabe.frame.frameinvoker import FrameWidgetInvoker
+
+TOUCHPAD_MODE_CAPACITIVE = 'capacitive'
+TOUCHPAD_MODE_RESISTIVE = 'resistive'
+TOUCHPAD_MODES = [TOUCHPAD_MODE_CAPACITIVE, TOUCHPAD_MODE_RESISTIVE]
+STATUS_TEXT = {TOUCHPAD_MODE_CAPACITIVE: _('finger'),
+ TOUCHPAD_MODE_RESISTIVE: _('stylus')}
+STATUS_ICON = {TOUCHPAD_MODE_CAPACITIVE: 'touchpad-' + TOUCHPAD_MODE_CAPACITIVE,
+ TOUCHPAD_MODE_RESISTIVE: 'touchpad-' + TOUCHPAD_MODE_RESISTIVE}
+# NODE_PATH is used to communicate with the touchpad device.
+NODE_PATH = '/sys/devices/platform/i8042/serio1/ptmode'
+
+
+class DeviceView(TrayIcon):
+ """ Manage the touchpad mode from the device palette on the Frame. """
+
+ FRAME_POSITION_RELATIVE = 500
+
+ def __init__(self):
+ """ Create the icon that represents the touchpad. """
+ icon_name = STATUS_ICON[_read_touchpad_mode()]
+
+ client = gconf.client_get_default()
+ color = XoColor(client.get_string('/desktop/sugar/user/color'))
+ TrayIcon.__init__(self, icon_name=icon_name, xo_color=color)
+
+ self.set_palette_invoker(FrameWidgetInvoker(self))
+ self.connect('button-release-event', self.__button_release_event_cb)
+
+ def create_palette(self):
+ """ Create a palette for this icon; called by the Sugar framework
+ when a palette needs to be displayed. """
+ self.palette = ResourcePalette(_('My touchpad'), self.icon)
+ self.palette.set_group_id('frame')
+ return self.palette
+
+ def __button_release_event_cb(self, widget, event):
+ """ Callback for button release event; used to invoke touchpad-mode
+ change. """
+ self.palette.toggle_mode()
+ return True
+
+
+class ResourcePalette(Palette):
+ """ Palette attached to the decive icon that represents the touchpas. """
+
+ def __init__(self, primary_text, icon):
+ """ Create the palette and initilize with current touchpad status. """
+ Palette.__init__(self, label=primary_text)
+
+ self._icon = icon
+
+ vbox = gtk.VBox()
+ self.set_content(vbox)
+
+ self._status_text = gtk.Label()
+ vbox.pack_start(self._status_text, padding=style.DEFAULT_PADDING)
+ self._status_text.show()
+
+ vbox.show()
+
+ self._mode = _read_touchpad_mode()
+ self._update()
+
+ def _update(self):
+ """ Update the label and icon based on the current mode. """
+ self._status_text.set_label(STATUS_TEXT[self._mode])
+ self._icon.props.icon_name = STATUS_ICON[self._mode]
+
+ def toggle_mode(self):
+ """ Toggle the touchpad mode. """
+ self._mode = TOUCHPAD_MODES[1 - TOUCHPAD_MODES.index(self._mode)]
+ _write_touchpad_mode(self._mode)
+ self._update()
+
+
+def setup(tray):
+ """ Initialize the devic icon; called by the shell when initializing the
+ Frame. """
+ if os.path.exists(NODE_PATH):
+ tray.add_device(DeviceView())
+ _write_touchpad_mode(TOUCHPAD_MODE_CAPACITIVE)
+
+
+def _read_touchpad_mode():
+ """ Read the touchpad mode from the node path. """
+ node_file_handle = open(NODE_PATH, 'r')
+ text = node_file_handle.read()
+ node_file_handle.close()
+
+ return TOUCHPAD_MODES[int(text[0])]
+
+
+def _write_touchpad_mode(touchpad):
+ """ Write the touchpad mode to the node path. """
+ try:
+ node_file_handle = open(NODE_PATH, 'w')
+ except IOError, e:
+ logging.error('Error opening %s for writing: %s', NODE_PATH, e)
+ return
+ node_file_handle.write(str(TOUCHPAD_MODES.index(touchpad)))
+ node_file_handle.close()
diff --git a/shell/extensions/deviceicon/volume.py b/shell/extensions/deviceicon/volume.py
new file mode 100644
index 0000000..e7f62a2
--- /dev/null
+++ b/shell/extensions/deviceicon/volume.py
@@ -0,0 +1,121 @@
+# Copyright (C) 2008 One Laptop Per Child
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import logging
+
+import gobject
+import gio
+import gtk
+import gconf
+
+from sugar.graphics.tray import TrayIcon
+from sugar.graphics.xocolor import XoColor
+
+from jarabe.journal import journalactivity
+from jarabe.view.palettes import VolumePalette
+from jarabe.frame.frameinvoker import FrameWidgetInvoker
+
+_icons = {}
+
+class DeviceView(TrayIcon):
+
+ FRAME_POSITION_RELATIVE = 500
+
+ def __init__(self, mount):
+
+ self._mount = mount
+
+ icon_name = None
+ icon_theme = gtk.icon_theme_get_default()
+ for icon_name in self._mount.get_icon().props.names:
+ icon_info = icon_theme.lookup_icon(icon_name,
+ gtk.ICON_SIZE_LARGE_TOOLBAR, 0)
+ if icon_info is not None:
+ break
+
+ if icon_name is None:
+ icon_name = 'drive'
+
+ # TODO: retrieve the colors from the owner of the device
+ client = gconf.client_get_default()
+ color = XoColor(client.get_string('/desktop/sugar/user/color'))
+
+ TrayIcon.__init__(self, icon_name=icon_name, xo_color=color)
+
+ self.set_palette_invoker(FrameWidgetInvoker(self))
+
+ self.connect('button-release-event', self.__button_release_event_cb)
+
+ def create_palette(self):
+ palette = VolumePalette(self._mount)
+ palette.set_group_id('frame')
+ return palette
+
+ def __button_release_event_cb(self, widget, event):
+ journal = journalactivity.get_journal()
+ journal.set_active_volume(self._mount)
+ journal.reveal()
+ return True
+
+def setup(tray):
+ gobject.idle_add(_setup_volumes, tray)
+
+def _setup_volumes(tray):
+ volume_monitor = gio.volume_monitor_get()
+
+ for volume in volume_monitor.get_volumes():
+ _mount(volume, tray)
+
+ for mount in volume_monitor.get_mounts():
+ _add_device(mount, tray)
+
+ volume_monitor.connect('volume-added', _volume_added_cb, tray)
+ volume_monitor.connect('mount-added', _mount_added_cb, tray)
+ volume_monitor.connect('mount-removed', _mount_removed_cb, tray)
+
+def _volume_added_cb(volume_monitor, volume, tray):
+ _mount(volume, tray)
+
+def _mount(volume, tray):
+ # Follow Nautilus behaviour here
+ # since it has the same issue with removable device
+ # and it would be good to not invent our own workflow
+ if hasattr(volume, 'should_automount') and not volume.should_automount():
+ return
+
+ #TODO: should be done by some other process, like gvfs-hal-volume-monitor
+ #TODO: use volume.should_automount() when it gets into pygtk
+ if volume.get_mount() is None and volume.can_mount():
+ #TODO: pass None as mount_operation, or better, SugarMountOperation
+ volume.mount(gtk.MountOperation(tray.get_toplevel()), _mount_cb)
+
+def _mount_cb(volume, result):
+ logging.debug('_mount_cb %r %r', volume, result)
+ volume.mount_finish(result)
+
+def _mount_added_cb(volume_monitor, mount, tray):
+ _add_device(mount, tray)
+
+def _mount_removed_cb(volume_monitor, mount, tray):
+ icon = _icons[mount]
+ tray.remove_device(icon)
+ del _icons[mount]
+
+def _add_device(mount, tray):
+ icon = DeviceView(mount)
+ _icons[mount] = icon
+ tray.add_device(icon)
+
diff --git a/shell/extensions/globalkey/Makefile.am b/shell/extensions/globalkey/Makefile.am
new file mode 100644
index 0000000..69afac2
--- /dev/null
+++ b/shell/extensions/globalkey/Makefile.am
@@ -0,0 +1,6 @@
+sugardir = $(pkgdatadir)/extensions/globalkey
+
+sugar_PYTHON = \
+ __init__.py \
+ screenshot.py \
+ viewsource.py
diff --git a/src/carquinyol/__init__.py b/shell/extensions/globalkey/__init__.py
index e69de29..e69de29 100644
--- a/src/carquinyol/__init__.py
+++ b/shell/extensions/globalkey/__init__.py
diff --git a/shell/extensions/globalkey/screenshot.py b/shell/extensions/globalkey/screenshot.py
new file mode 100644
index 0000000..8b4d4c2
--- /dev/null
+++ b/shell/extensions/globalkey/screenshot.py
@@ -0,0 +1,100 @@
+# Copyright (C) 2008 One Laptop Per Child
+# Copyright (C) 2009 Simon Schampijer, James Zaki
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import tempfile
+import time
+from gettext import gettext as _
+
+import gtk
+import gconf
+import dbus
+
+from sugar.datastore import datastore
+from sugar.graphics import style
+from sugar import env
+from jarabe.model import shell
+
+BOUND_KEYS = ['<alt>1', 'Print']
+
+def handle_key_press(key):
+ tmp_dir = os.path.join(env.get_profile_path(), 'data')
+ fd, file_path = tempfile.mkstemp(dir=tmp_dir)
+ os.close(fd)
+
+ window = gtk.gdk.get_default_root_window()
+ width, height = window.get_size()
+ x_orig, y_orig = window.get_origin()
+
+ screenshot = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, has_alpha=False,
+ bits_per_sample=8, width=width,
+ height=height)
+ screenshot.get_from_drawable(window, window.get_colormap(), x_orig,
+ y_orig, 0, 0, width, height)
+ screenshot.save(file_path, "png")
+
+ client = gconf.client_get_default()
+ color = client.get_string('/desktop/sugar/user/color')
+
+ content_title = None
+ shell_model = shell.get_model()
+ zoom_level = shell_model.zoom_level
+
+ # TRANS: Nouns of what a screenshot contains
+ if zoom_level == shell_model.ZOOM_MESH:
+ content_title = _('Mesh')
+ elif zoom_level == shell_model.ZOOM_GROUP:
+ content_title = _('Group')
+ elif zoom_level == shell_model.ZOOM_HOME:
+ content_title = _('Home')
+ elif zoom_level == shell_model.ZOOM_ACTIVITY:
+ activity = shell_model.get_active_activity()
+ if activity != None:
+ content_title = activity.get_title()
+ if content_title == None:
+ content_title = _('Activity')
+
+ if content_title is None:
+ title = _('Screenshot')
+ else:
+ title = _('Screenshot of \"%s\"') % content_title
+
+ jobject = datastore.create()
+ try:
+ jobject.metadata['title'] = title
+ jobject.metadata['keep'] = '0'
+ jobject.metadata['buddies'] = ''
+ jobject.metadata['preview'] = _get_preview_data(screenshot)
+ jobject.metadata['icon-color'] = color
+ jobject.metadata['mime_type'] = 'image/png'
+ jobject.file_path = file_path
+ datastore.write(jobject, transfer_ownership=True)
+ finally:
+ jobject.destroy()
+ del jobject
+
+def _get_preview_data(screenshot):
+ preview = screenshot.scale_simple(style.zoom(300), style.zoom(225),
+ gtk.gdk.INTERP_BILINEAR)
+ preview_data = []
+ def save_func(buf, data):
+ data.append(buf)
+
+ preview.save_to_callback(save_func, 'png', user_data=preview_data)
+
+ return dbus.ByteArray(''.join(preview_data))
+
diff --git a/shell/extensions/globalkey/viewsource.py b/shell/extensions/globalkey/viewsource.py
new file mode 100644
index 0000000..df3cd9e
--- /dev/null
+++ b/shell/extensions/globalkey/viewsource.py
@@ -0,0 +1,27 @@
+# Copyright (C) 2008 One Laptop Per Child
+# Copyright (C) 2009 Tomeu Vizoso, Simon Schampijer
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from jarabe.view.viewsource import setup_view_source
+from jarabe.model import shell
+
+BOUND_KEYS = ['0xEC', '<alt><shift>v']
+
+def handle_key_press(key):
+ shell_model = shell.get_model()
+ activity = shell_model.get_active_activity()
+
+ setup_view_source(activity)
diff --git a/src/carquinyol/__init__.py b/shell/po/ChangeLog
index e69de29..e69de29 100644
--- a/src/carquinyol/__init__.py
+++ b/shell/po/ChangeLog
diff --git a/shell/po/POTFILES.in b/shell/po/POTFILES.in
new file mode 100644
index 0000000..45a0549
--- /dev/null
+++ b/shell/po/POTFILES.in
@@ -0,0 +1,67 @@
+extensions/cpsection/aboutme/__init__.py
+extensions/cpsection/aboutme/model.py
+extensions/cpsection/aboutme/view.py
+extensions/cpsection/aboutcomputer/__init__.py
+extensions/cpsection/aboutcomputer/model.py
+extensions/cpsection/aboutcomputer/view.py
+extensions/cpsection/datetime/__init__.py
+extensions/cpsection/datetime/model.py
+extensions/cpsection/datetime/view.py
+extensions/cpsection/frame/__init__.py
+extensions/cpsection/frame/model.py
+extensions/cpsection/frame/view.py
+extensions/cpsection/keyboard/__init__.py
+extensions/cpsection/keyboard/view.py
+extensions/cpsection/language/__init__.py
+extensions/cpsection/language/model.py
+extensions/cpsection/language/view.py
+extensions/cpsection/modemconfiguration/__init__.py
+extensions/cpsection/modemconfiguration/view.py
+extensions/cpsection/network/__init__.py
+extensions/cpsection/network/model.py
+extensions/cpsection/network/view.py
+extensions/cpsection/power/__init__.py
+extensions/cpsection/power/model.py
+extensions/cpsection/power/view.py
+extensions/cpsection/updater/__init__.py
+extensions/cpsection/updater/view.py
+extensions/deviceicon/battery.py
+extensions/deviceicon/network.py
+extensions/deviceicon/speaker.py
+extensions/deviceicon/touchpad.py
+extensions/deviceicon/volume.py
+extensions/globalkey/screenshot.py
+data/sugar.schemas.in
+src/jarabe/controlpanel/cmd.py
+src/jarabe/controlpanel/gui.py
+src/jarabe/controlpanel/sectionview.py
+src/jarabe/controlpanel/toolbar.py
+src/jarabe/desktop/activitieslist.py
+src/jarabe/desktop/favoriteslayout.py
+src/jarabe/desktop/favoritesview.py
+src/jarabe/desktop/homebox.py
+src/jarabe/desktop/keydialog.py
+src/jarabe/desktop/meshbox.py
+src/jarabe/desktop/schoolserver.py
+src/jarabe/frame/activitiestray.py
+src/jarabe/frame/clipboardmenu.py
+src/jarabe/frame/clipboardobject.py
+src/jarabe/frame/devicestray.py
+src/jarabe/frame/zoomtoolbar.py
+src/jarabe/intro/window.py
+src/jarabe/journal/detailview.py
+src/jarabe/journal/expandedentry.py
+src/jarabe/journal/journalactivity.py
+src/jarabe/journal/journaltoolbox.py
+src/jarabe/journal/listview.py
+src/jarabe/journal/misc.py
+src/jarabe/journal/modalalert.py
+src/jarabe/journal/objectchooser.py
+src/jarabe/journal/palettes.py
+src/jarabe/journal/volumestoolbar.py
+src/jarabe/view/buddymenu.py
+src/jarabe/view/keyhandler.py
+src/jarabe/view/launcher.py
+src/jarabe/view/palettes.py
+src/jarabe/view/viewsource.py
+
diff --git a/shell/po/POTFILES.skip b/shell/po/POTFILES.skip
new file mode 100644
index 0000000..f199f9c
--- /dev/null
+++ b/shell/po/POTFILES.skip
@@ -0,0 +1 @@
+data/sugar.xml.in
diff --git a/shell/po/af.po b/shell/po/af.po
new file mode 100644
index 0000000..8d6c9e1
--- /dev/null
+++ b/shell/po/af.po
@@ -0,0 +1,522 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-21 00:30-0400\n"
+"PO-Revision-Date: 2008-06-20 18:58-0400\n"
+"Last-Translator: Morgan Collett <morgan.collett@gmail.com>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Pootle 1.1.0rc2\n"
+
+#: ../src/intro/intro.py:65 ../src/controlpanel/view/aboutme.py:100
+msgid "Name:"
+msgstr "Naam:"
+
+#: ../src/intro/intro.py:94
+msgid "Click to change color:"
+msgstr "Kliek om die kleur the verander:"
+
+#: ../src/intro/intro.py:145
+msgid "Back"
+msgstr "Terug"
+
+#: ../src/intro/intro.py:159 ../src/controlpanel/toolbar.py:61
+msgid "Done"
+msgstr "Klaar"
+
+#: ../src/intro/intro.py:162
+msgid "Next"
+msgstr "Volgende"
+
+#: ../src/view/BuddyMenu.py:58
+msgid "Remove friend"
+msgstr "Verwyder vriend"
+
+#: ../src/view/BuddyMenu.py:61
+msgid "Make friend"
+msgstr "Maak vriend"
+
+#: ../src/view/BuddyMenu.py:91
+#, python-format
+msgid "Invite to %s"
+msgstr "Nooi na %s"
+
+#: ../src/view/clipboardmenu.py:48
+msgid "Remove"
+msgstr "Verwyder"
+
+#: ../src/view/clipboardmenu.py:53 ../src/view/clipboardmenu.py:79
+msgid "Open"
+msgstr "Maak oop"
+
+#. self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+#. TODO: Implement stopping downloads
+#. self._stop_item.connect('activate', self._stop_item_activate_cb)
+#. self.append_menu_item(self._stop_item)
+#: ../src/view/clipboardmenu.py:63
+msgid "Keep"
+msgstr "Hou"
+
+#: ../src/view/clipboardmenu.py:84
+msgid "Open with"
+msgstr "Maak oop met"
+
+#: ../src/view/clipboardmenu.py:212
+#, python-format
+msgid "Clipboard object: %s."
+msgstr "Knipbord objek: %s."
+
+#: ../src/hardware/keydialog.py:150
+msgid "Key Type:"
+msgstr "Sleutel Tipe:"
+
+#: ../src/hardware/keydialog.py:170
+msgid "Authentication Type:"
+msgstr "Geldigheidsverklaring Tipe:"
+
+#: ../src/hardware/keydialog.py:251
+msgid "Encryption Type:"
+msgstr "Enkripsie Tipe:"
+
+#: ../src/view/Shell.py:262
+msgid "Screenshot"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:147
+msgid "List view"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:148
+msgid "<Ctrl>L"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:204
+msgid "Favorites view"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:205
+msgid "<Ctrl>R"
+msgstr ""
+
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/view/home/HomeBox.py:211
+msgid "Freeform"
+msgstr ""
+
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/view/home/HomeBox.py:218
+msgid "Ring"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:97
+msgid "Connect"
+msgstr "Koppel"
+
+#: ../src/view/home/MeshBox.py:106
+msgid "Disconnect"
+msgstr "Ontkoppel"
+
+#: ../src/view/home/MeshBox.py:118
+msgid "Disconnecting..."
+msgstr "Ontkoppel..."
+
+#: ../src/view/home/MeshBox.py:152
+msgid "Connecting..."
+msgstr "Koppel..."
+
+#. TODO: show the channel number
+#: ../src/view/home/MeshBox.py:159
+msgid "Connected"
+msgstr "Gekoppel"
+
+#: ../src/view/home/MeshBox.py:211 ../src/view/devices/network/mesh.py:38
+#: ../src/view/devices/network/mesh.py:65
+#: ../src/view/devices/network/mesh.py:69
+msgid "Mesh Network"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:214 ../src/view/devices/network/wireless.py:116
+#: ../src/view/devices/network/mesh.py:86
+msgid "Disconnect..."
+msgstr "Ontkoppel..."
+
+#: ../src/view/home/MeshBox.py:302 ../src/view/palettes.py:60
+msgid "Resume"
+msgstr "Aangaan"
+
+#: ../src/view/home/MeshBox.py:307 ../src/view/frame/activitiestray.py:219
+msgid "Join"
+msgstr "Aansluit"
+
+#: ../src/view/devices/battery.py:42
+msgid "My Battery"
+msgstr ""
+
+#: ../src/view/devices/battery.py:111
+msgid "Charging"
+msgstr ""
+
+#: ../src/view/devices/battery.py:114
+msgid "Very little power remaining"
+msgstr "Baie min krag oor"
+
+#: ../src/view/devices/battery.py:120
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr "%(hour)d:%(min).2d oor"
+
+#: ../src/view/devices/battery.py:124
+msgid "Charged"
+msgstr ""
+
+#: ../src/view/devices/speaker.py:40
+msgid "My Speakers"
+msgstr ""
+
+#: ../src/view/devices/speaker.py:104
+msgid "Unmute"
+msgstr ""
+
+#: ../src/view/devices/speaker.py:107
+msgid "Mute"
+msgstr ""
+
+#: ../src/view/devices/network/wireless.py:64
+msgid "Disconnected"
+msgstr "Ontkoppel"
+
+#: ../src/view/devices/network/wireless.py:134
+msgid "Channel"
+msgstr "Kanaal"
+
+#: ../src/view/frame/zoomtoolbar.py:34
+msgid "Neighborhood"
+msgstr "Buurt"
+
+#: ../src/view/frame/zoomtoolbar.py:36
+msgid "Group"
+msgstr "Groep"
+
+#: ../src/view/frame/zoomtoolbar.py:38
+msgid "Home"
+msgstr "Tuis"
+
+#: ../src/view/frame/zoomtoolbar.py:40
+msgid "Activity"
+msgstr "Aktiwiteit"
+
+#: ../src/controlpanel/cmd.py:26
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:28
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:29
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:33
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" "
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:45
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr "Om jou veranderings te gebruik moet jy sugar herbegin.\n"
+
+#: ../src/controlpanel/toolbar.py:115
+msgid "Cancel"
+msgstr "Kanselleer"
+
+#: ../src/controlpanel/toolbar.py:121
+msgid "Ok"
+msgstr "Reg"
+
+#: ../src/controlpanel/sectionview.py:34 ../src/controlpanel/gui.py:250
+msgid "Changes require restart"
+msgstr "Veranderings het herbegin nodig"
+
+#: ../src/controlpanel/gui.py:249
+msgid "Warning"
+msgstr "Waarskuwing"
+
+#: ../src/controlpanel/gui.py:253
+msgid "Cancel changes"
+msgstr "Kanselleer veranderings"
+
+#: ../src/controlpanel/gui.py:257
+msgid "Later"
+msgstr "Later"
+
+#: ../src/controlpanel/gui.py:261
+msgid "Restart now"
+msgstr "Herbegin nou"
+
+#: ../src/controlpanel/model/aboutme.py:44
+msgid "You must enter a name."
+msgstr "Jy moet 'n naam inskryf."
+
+#: ../src/controlpanel/model/aboutme.py:67
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:70
+#, python-format
+msgid "stroke: %s"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:72
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:74
+#, python-format
+msgid "fill: %s"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:85
+msgid "Error in specified color modifiers."
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:88
+msgid "Error in specified colors."
+msgstr "Vout in gespesifiseerde kleure."
+
+#: ../src/controlpanel/model/aboutxo.py:24
+msgid "Not available"
+msgstr "Nie beskikbaar nie"
+
+#: ../src/controlpanel/model/datetime.py:85
+msgid "Error timezone does not exist."
+msgstr ""
+
+#: ../src/controlpanel/model/frame.py:38 ../src/controlpanel/model/frame.py:60
+msgid "Value must be an integer."
+msgstr ""
+
+#: ../src/controlpanel/model/language.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr ""
+
+#: ../src/controlpanel/model/language.py:104
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr ""
+
+#: ../src/controlpanel/model/language.py:121
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr "Jammer, ek praat nie '%s'."
+
+#: ../src/controlpanel/model/network.py:48
+msgid "You must enter a server."
+msgstr "Jy moet 'n bediener inskryf."
+
+#: ../src/controlpanel/model/network.py:63
+msgid "State is unknown."
+msgstr ""
+
+#: ../src/controlpanel/model/network.py:83
+msgid "Error in specified radio argument use on/off."
+msgstr "Vout in radio waarde: gebruik on/off."
+
+#: ../src/controlpanel/view/aboutme.py:32
+msgid "About Me"
+msgstr "Omtrent My"
+
+#: ../src/controlpanel/view/aboutme.py:134
+msgid "Click to change your color:"
+msgstr "Kliek om jou kleur te verander:"
+
+#: ../src/controlpanel/view/aboutxo.py:26
+msgid "About my XO"
+msgstr "Omtrent my XO"
+
+#: ../src/controlpanel/view/aboutxo.py:47
+msgid "Identity"
+msgstr "Identiteit"
+
+#: ../src/controlpanel/view/aboutxo.py:56
+msgid "Serial Number:"
+msgstr "Seriële Nommer:"
+
+#: ../src/controlpanel/view/aboutxo.py:79
+msgid "Software"
+msgstr "Sagteware"
+
+#: ../src/controlpanel/view/aboutxo.py:88
+msgid "Build:"
+msgstr "Bou:"
+
+#: ../src/controlpanel/view/aboutxo.py:103
+msgid "Firmware:"
+msgstr "Fermware:"
+
+#: ../src/controlpanel/view/datetime.py:29
+msgid "Date & Time"
+msgstr "Datem & Tyd"
+
+#: ../src/controlpanel/view/datetime.py:72
+msgid "Timezone"
+msgstr "Tydsone"
+
+#: ../src/controlpanel/view/frame.py:28
+msgid "Frame"
+msgstr "Raam"
+
+#: ../src/controlpanel/view/frame.py:30
+msgid "never"
+msgstr "nooit"
+
+#: ../src/controlpanel/view/frame.py:31
+msgid "instantaneous"
+msgstr "dadelik"
+
+#: ../src/controlpanel/view/frame.py:32
+#, python-format
+msgid "%s seconds"
+msgstr "%s sekondes"
+
+#: ../src/controlpanel/view/frame.py:56
+msgid "Activation Delay"
+msgstr "Aktiveerings Vertraging"
+
+#: ../src/controlpanel/view/frame.py:80
+msgid "Corner"
+msgstr "Hoekie"
+
+#: ../src/controlpanel/view/frame.py:115
+msgid "Edge"
+msgstr "Snykant"
+
+#: ../src/controlpanel/view/language.py:29
+#: ../src/controlpanel/view/language.py:74
+msgid "Language"
+msgstr "Taal"
+
+#: ../src/controlpanel/view/network.py:28
+msgid "Network"
+msgstr "Netwerk"
+
+#: ../src/controlpanel/view/network.py:53
+msgid "Wireless"
+msgstr "Draadloos"
+
+#: ../src/controlpanel/view/network.py:61
+msgid "Radio:"
+msgstr "Radio:"
+
+#: ../src/controlpanel/view/network.py:94
+msgid "Mesh"
+msgstr ""
+
+#: ../src/controlpanel/view/network.py:103
+msgid "Server:"
+msgstr "Bediener:"
+
+#: ../src/view/devices/network/mesh.py:108
+msgid "Connected to a School Mesh Portal"
+msgstr "Aan 'n Skool Mesh Portal gekoppel"
+
+#: ../src/view/devices/network/mesh.py:110
+msgid "Looking for a School Mesh Portal..."
+msgstr "Op soek na 'n Skool Mesh Portal..."
+
+#: ../src/view/devices/network/mesh.py:113
+msgid "Connected to an XO Mesh Portal"
+msgstr "Aan 'n XO Mesh Portal gekoppel"
+
+#: ../src/view/devices/network/mesh.py:115
+msgid "Looking for an XO Mesh Portal..."
+msgstr "Op soek na 'n XO Mesh Portal..."
+
+#: ../src/view/devices/network/mesh.py:118
+msgid "Connected to a Simple Mesh"
+msgstr "Aan Eenvoudig Mesh gekoppel"
+
+#: ../src/view/devices/network/mesh.py:120
+msgid "Starting a Simple Mesh"
+msgstr "Begin 'n Eenvoidige Mesh"
+
+#: ../src/view/devices/network/mesh.py:127
+msgid "Unknown Mesh"
+msgstr "Vreemde Mesh"
+
+#: ../src/view/frame/activitiestray.py:224
+msgid "Decline"
+msgstr "Weier"
+
+#: ../src/view/home/favoritesview.py:351
+msgid "Control Panel"
+msgstr "Kontrole Paneel"
+
+#: ../src/view/home/favoritesview.py:362
+msgid "Restart"
+msgstr "Herbegin"
+
+#: ../src/view/home/favoritesview.py:367
+msgid "Shutdown"
+msgstr "Uitskakel"
+
+#: ../src/view/home/favoritesview.py:373
+msgid "Register"
+msgstr "Registreer"
+
+#: ../src/view/palettes.py:41
+msgid "Starting..."
+msgstr "Begin..."
+
+#: ../src/view/palettes.py:71
+msgid "Stop"
+msgstr "Stop"
+
+#: ../src/view/palettes.py:96
+msgid "Start"
+msgstr "Begin"
+
+#: ../src/view/palettes.py:119
+msgid "Remove favorite"
+msgstr ""
+
+#: ../src/view/palettes.py:123
+msgid "Make favorite"
+msgstr ""
+
+#: ../src/view/palettes.py:169
+msgid "Show contents"
+msgstr "Wys inhoud"
+
+#: ../src/view/palettes.py:193
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr ""
+
+#~ msgid "Remove from ring"
+#~ msgstr "Verwyder van ring"
+
+#~ msgid "Add to ring"
+#~ msgstr "Bysit by ring"
diff --git a/shell/po/am.po b/shell/po/am.po
new file mode 100644
index 0000000..a79e711
--- /dev/null
+++ b/shell/po/am.po
@@ -0,0 +1,420 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-01-18 18:19+0000\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.0.1\n"
+
+#: ../shell/intro/intro.py:67
+msgid "Name:"
+msgstr ""
+
+#: ../shell/intro/intro.py:96
+msgid "Click to change color:"
+msgstr ""
+
+#: ../shell/intro/intro.py:146
+msgid "Back"
+msgstr ""
+
+#: ../shell/intro/intro.py:160
+msgid "Done"
+msgstr ""
+
+#: ../shell/intro/intro.py:163
+msgid "Next"
+msgstr ""
+
+#: ../shell/view/BuddyMenu.py:59
+msgid "Remove friend"
+msgstr ""
+
+#: ../shell/view/BuddyMenu.py:62
+msgid "Make friend"
+msgstr ""
+
+#: ../shell/view/BuddyMenu.py:84
+#, python-format
+msgid "Invite to %s"
+msgstr ""
+
+#: ../shell/view/clipboardmenu.py:58
+msgid "Remove"
+msgstr ""
+
+#: ../shell/view/clipboardmenu.py:63
+msgid "Open"
+msgstr ""
+
+#. self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+#. TODO: Implement stopping downloads
+#. self._stop_item.connect('activate', self._stop_item_activate_cb)
+#. self.append_menu_item(self._stop_item)
+#: ../shell/view/clipboardmenu.py:73
+msgid "Add to journal"
+msgstr ""
+
+#: ../shell/view/clipboardmenu.py:213
+#, python-format
+msgid "Clipboard object: %s."
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:149
+msgid "Key Type:"
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:169
+msgid "Authentication Type:"
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:250
+msgid "Encryption Type:"
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:90
+msgid "Starting..."
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:104 ../shell/view/home/MeshBox.py:295
+msgid "Resume"
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:111
+#: ../lib/sugar/activity/activity.py:132
+msgid "Stop"
+msgstr ""
+
+#: ../shell/view/Shell.py:285
+msgid "Screenshot"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:159
+msgid "Reboot"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:164
+msgid "Shutdown"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:170
+msgid "Register"
+msgstr ""
+
+#. Only show disconnect when there's a mesh device, because mesh takes
+#. priority over the normal wireless device. NM doesn't have a "disconnect"
+#. method for a device either (for various reasons) so this doesn't
+#. have a good mapping
+#: ../shell/view/home/MeshBox.py:90 ../shell/view/home/MeshBox.py:197
+#: ../shell/view/devices/network/wireless.py:113
+#: ../shell/view/devices/network/mesh.py:83
+msgid "Disconnect..."
+msgstr ""
+
+#: ../shell/view/home/MeshBox.py:195 ../shell/view/devices/network/mesh.py:37
+#: ../shell/view/devices/network/mesh.py:62
+#: ../shell/view/devices/network/mesh.py:66
+msgid "Mesh Network"
+msgstr ""
+
+#: ../shell/view/home/MeshBox.py:300
+msgid "Join"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:38
+msgid "My Battery life"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:94
+msgid "Battery charging"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:96
+msgid "Battery discharging"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:98
+msgid "Battery fully charged"
+msgstr ""
+
+#: ../shell/view/devices/network/wireless.py:61
+msgid "Disconnected"
+msgstr ""
+
+#: ../shell/view/devices/network/wireless.py:131
+msgid "Channel"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:42
+msgid "Neighborhood"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:54
+msgid "Group"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:66
+msgid "Home"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:78
+msgid "Activity"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:115
+msgid "Share with:"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:117
+msgid "Private"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:118
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:126
+msgid "Keep"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:245
+msgid "Undo"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:250
+msgid "Redo"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:260
+msgid "Copy"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:265
+msgid "Paste"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:454
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:824
+msgid "Keep error"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:825
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:828
+msgid "Don't stop"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:831
+msgid "Stop anyway"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:164 ../lib/sugar/graphics/alert.py:206
+msgid "Cancel"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:168
+msgid "Ok"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:216
+msgid "Continue"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:244
+msgid "OK"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d year"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d years"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d month"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d months"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d week"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d weeks"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d day"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d days"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hour"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hours"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minute"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minutes"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d second"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d seconds"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:191
+msgid " and "
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:193
+msgid ", "
+msgstr ""
+
+#: ../shell/controlpanel/control.py:213
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:267
+msgid "Error in specified color modifiers."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:270
+msgid "Error in specified colors."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:307
+msgid "off"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:309
+msgid "on"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:310
+msgid "State is unknown."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:332
+msgid "Error in specified radio argument use on/off."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:336
+msgid "Permission denied. You need to be root to run this method."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:366
+msgid "Error in reading timezone"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:397
+#, python-format
+msgid "Error copying timezone (from %s): %s"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:402
+#, python-format
+msgid "Changing permission of timezone: %s"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:412
+msgid "Error timezone does not exist."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:417 ../shell/controlpanel/control.py:436
+#, python-format
+msgid "Could not access %s. Create standard settings."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:463
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:473
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:105
+msgid "Connected to a School Mesh Portal"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:107
+msgid "Looking for a School Mesh Portal..."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:110
+msgid "Connected to an XO Mesh Portal"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:112
+msgid "Looking for an XO Mesh Portal..."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:115
+msgid "Connected to a Simple Mesh"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:117
+msgid "Starting a Simple Mesh"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:124
+msgid "Unknown Mesh"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:175 ../shell/view/home/HomeBox.py:216
+msgid "About this XO"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:222
+msgid "Not available"
+msgstr ""
diff --git a/shell/po/ar.po b/shell/po/ar.po
new file mode 100644
index 0000000..df5b323
--- /dev/null
+++ b/shell/po/ar.po
@@ -0,0 +1,1549 @@
+# translation of sugar.po to Arabic
+# Khaled Hosny <khaledhosny@eglug.org>, 2007, 2008, 2009.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+msgid ""
+msgstr ""
+"Project-Id-Version: sugar\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-01-30 00:31-0500\n"
+"PO-Revision-Date: 2010-02-07 17:12+0200\n"
+"Last-Translator: Khaled Hosny <khaledhosny@eglug.org>\n"
+"Language-Team: Arabic <doc@arabeyes.org>\n"
+"Language: ar\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 "
+"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n"
+"X-Generator: Pootle 2.0.1\n"
+"Nplurals=6; Plural=N==0 ? 0: n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 "
+": n%100>=11 && n%100<=99 ? 4 : 5;\n"
+
+#: ../extensions/cpsection/aboutme/__init__.py:24
+msgid "About Me"
+msgstr "عنّي"
+
+#: ../extensions/cpsection/aboutme/model.py:43
+msgid "You must enter a name."
+msgstr "يجب أن تُدخِل اسما."
+
+#: ../extensions/cpsection/aboutme/model.py:68
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr "الحواف: اللون=%s التشبع=%s"
+
+#: ../extensions/cpsection/aboutme/model.py:71
+#, python-format
+msgid "stroke: %s"
+msgstr "الحواف: %s"
+
+#: ../extensions/cpsection/aboutme/model.py:73
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr "الملء: اللون=%s التشبع=%s"
+
+#: ../extensions/cpsection/aboutme/model.py:75
+#, python-format
+msgid "fill: %s"
+msgstr "الملء: %s"
+
+#: ../extensions/cpsection/aboutme/model.py:86
+msgid "Error in specified color modifiers."
+msgstr "خطأ في مُغيّرات الألوان المحددة."
+
+#: ../extensions/cpsection/aboutme/model.py:89
+msgid "Error in specified colors."
+msgstr "خطأ في الألوان المحددة."
+
+#: ../extensions/cpsection/aboutme/view.py:94 ../src/jarabe/intro/window.py:93
+msgid "Name:"
+msgstr "الاسم:"
+
+#: ../extensions/cpsection/aboutme/view.py:128
+msgid "Click to change your color:"
+msgstr "انقر لتغيير اللون:"
+
+#: ../extensions/cpsection/aboutcomputer/__init__.py:21
+msgid "About my Computer"
+msgstr "عن حاسوبي"
+
+#: ../extensions/cpsection/aboutcomputer/model.py:28
+msgid "Not available"
+msgstr "غير مُتاح"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:60
+msgid "Identity"
+msgstr "المعرّف"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:69
+msgid "Serial Number:"
+msgstr "الرقم التسلسلي:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:91
+msgid "Software"
+msgstr "البرمجيات"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:100
+msgid "Build:"
+msgstr "البناء:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:115
+msgid "Sugar:"
+msgstr "سُكّر:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:131
+msgid "Firmware:"
+msgstr "البرمجيات المضمّنة:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:146
+msgid "Wireless Firmware:"
+msgstr "برمجية اللاسلكي المضمّنة:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:169
+msgid "Copyright and License"
+msgstr "حقوق النشر والترخيص"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:184
+msgid ""
+"Sugar is the graphical user interface that you are looking at. Sugar is free "
+"software, covered by the GNU General Public License, and you are welcome to "
+"change it and/or distribute copies of it under certain conditions described "
+"therein."
+msgstr ""
+"سُكّر هي واجهة المستخدم الرسومية التي تنظر لها الآن. سُكّر برمجية حرّة تخضع "
+"لرخصة جنو للتأميم عامّة الأغراض، ويمكنك تعديلها و/أو توزيع نسخ منها وفق شروط "
+"معينة مذكورة هنا."
+
+#: ../extensions/cpsection/aboutcomputer/view.py:196
+msgid "Full license:"
+msgstr "الترخيص كاملا:"
+
+#: ../extensions/cpsection/datetime/__init__.py:21
+msgid "Date & Time"
+msgstr "التاريخ والوقت"
+
+#: ../extensions/cpsection/datetime/model.py:87
+msgid "Error timezone does not exist."
+msgstr "عطل: المنطقة الزمنية لا وجود لها."
+
+#: ../extensions/cpsection/datetime/view.py:68 ../data/sugar.schemas.in.h:33
+msgid "Timezone"
+msgstr "المنطقة الزمنية"
+
+#: ../extensions/cpsection/frame/__init__.py:21
+msgid "Frame"
+msgstr "الإطار"
+
+#: ../extensions/cpsection/frame/model.py:38
+#: ../extensions/cpsection/frame/model.py:60
+msgid "Value must be an integer."
+msgstr "يجب أن تكون القيمة عددا صحيحا."
+
+#: ../extensions/cpsection/frame/view.py:26
+msgid "never"
+msgstr "أبدا"
+
+#: ../extensions/cpsection/frame/view.py:27
+msgid "instantaneous"
+msgstr "حالا"
+
+#: ../extensions/cpsection/frame/view.py:28
+#, python-format
+msgid "%s seconds"
+msgstr "%s ثوان"
+
+#: ../extensions/cpsection/frame/view.py:52
+msgid "Activation Delay"
+msgstr "تأخير التفعيل"
+
+#: ../extensions/cpsection/frame/view.py:76
+msgid "Corner"
+msgstr "الركن"
+
+#: ../extensions/cpsection/frame/view.py:111
+msgid "Edge"
+msgstr "الحافة"
+
+#: ../extensions/cpsection/keyboard/__init__.py:21
+#: ../extensions/cpsection/keyboard/view.py:31
+msgid "Keyboard"
+msgstr "لوحة المفاتيح"
+
+#: ../extensions/cpsection/keyboard/view.py:189
+msgid "Keyboard Model"
+msgstr "طراز لوحة المفاتيح"
+
+#: ../extensions/cpsection/keyboard/view.py:248
+msgid "Key(s) to change layout"
+msgstr "زر تغيير التخطيط"
+
+#: ../extensions/cpsection/keyboard/view.py:318
+msgid "Keyboard Layout(s)"
+msgstr "تخطيط لوحة المفاتيح"
+
+#: ../extensions/cpsection/language/__init__.py:21
+#: ../extensions/cpsection/language/view.py:33
+msgid "Language"
+msgstr "اللغة"
+
+#: ../extensions/cpsection/language/model.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr "تعذّر الوصول إلى ‭~/.i18n‬. ستُنشأ إعدادات قياسية."
+
+#: ../extensions/cpsection/language/model.py:124
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr "تعذّر تحديد اللغة ذات الرمز=%s."
+
+#: ../extensions/cpsection/language/model.py:144
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr "آسف، لا أتحدث '%s'."
+
+#: ../extensions/cpsection/language/view.py:56
+msgid ""
+"Add languages in the order you prefer. If a translation is not available, "
+"the next in the list will be used."
+msgstr ""
+"أضف اللغات حسب الترتيب الذي تفضله. إذا لم تتوفر ترجمة ستؤخذ من التالية في "
+"القائمة."
+
+#: ../extensions/cpsection/network/__init__.py:21
+#: ../extensions/cpsection/network/view.py:28
+msgid "Network"
+msgstr "الشبكة"
+
+#: ../extensions/cpsection/network/model.py:79
+msgid "State is unknown."
+msgstr "الحالة مجهولة."
+
+#: ../extensions/cpsection/network/model.py:105
+msgid "Error in specified radio argument use on/off."
+msgstr "خطأ في معامل الإذاعة المحدد، استخدم on/off."
+
+#: ../extensions/cpsection/network/model.py:137
+msgid "Error in specified argument use 0/1."
+msgstr "خطأ في المعامل المحدد، استخدم 0\\1."
+
+#: ../extensions/cpsection/network/view.py:59
+msgid "Wireless"
+msgstr "اللاسلكي"
+
+#: ../extensions/cpsection/network/view.py:67
+msgid "Turn off the wireless radio to save battery life"
+msgstr "عطل اللاسلكي لتوفير الطاقة"
+
+#: ../extensions/cpsection/network/view.py:80
+msgid "Radio"
+msgstr "الإذاعة"
+
+#: ../extensions/cpsection/network/view.py:96
+msgid "Discard network history if you have trouble connecting to the network"
+msgstr "تجاهل تأريخ الشبكة إن كنت تواجه مشاكل في الاتصال بالشبكة"
+
+#: ../extensions/cpsection/network/view.py:105
+msgid "Discard network history"
+msgstr "تجاهل تأريخ الشبكة"
+
+#: ../extensions/cpsection/network/view.py:118
+msgid "Collaboration"
+msgstr "التعاون"
+
+#: ../extensions/cpsection/network/view.py:126
+msgid ""
+"The server is the equivalent of what room you are in; people on the same "
+"server will be able to see each other, even when they aren't on the same "
+"network."
+msgstr ""
+"الخادوم مثله مثل الغرفة التي تجلس بها؛ الأشخاص على نفس الخادوم يرون بعضهم "
+"البعض، حتى لو لم يكونوا على نفس الشبكة."
+
+#: ../extensions/cpsection/network/view.py:136
+msgid "Server:"
+msgstr "الخادوم:"
+
+#: ../extensions/cpsection/power/__init__.py:21
+msgid "Power"
+msgstr "الطاقة"
+
+#: ../extensions/cpsection/power/model.py:54
+msgid "Error in automatic pm argument, use on/off."
+msgstr "خطأ في معامل pm التلقائي، استخدم on/off."
+
+#: ../extensions/cpsection/power/model.py:81
+msgid "Error in extreme pm argument, use on/off."
+msgstr "خطأ في معامل pm المتطرف، استخدم on/off."
+
+#: ../extensions/cpsection/power/view.py:47
+msgid "Power management"
+msgstr "إدارة الطاقة"
+
+#: ../extensions/cpsection/power/view.py:57
+msgid "Automatic power management (increases battery life)"
+msgstr "إدارة آلية للطاقة (تطيل من عمر البطارية)"
+
+#: ../extensions/cpsection/power/view.py:85
+msgid ""
+"Extreme power management (disableswireless radio, increases battery life)"
+msgstr "إدارة قصوى للطاقة (تعطّل اللاسلكي وتطيل عمر البطارية)"
+
+#: ../extensions/cpsection/updater/__init__.py:21
+msgid "Software update"
+msgstr "تحديث البرمجيات"
+
+#: ../extensions/cpsection/updater/view.py:63
+msgid ""
+"Software updates correct errors, eliminate security vulnerabilities, and "
+"provide new features."
+msgstr "تحديث البرمجيات يُصلح الأعطال ويغلق الثغرات الأمنية ويوفر خصائص جديدة."
+
+#: ../extensions/cpsection/updater/view.py:125
+#, python-format
+msgid "Checking %s..."
+msgstr "يلتمس %s..."
+
+#: ../extensions/cpsection/updater/view.py:127
+#, python-format
+msgid "Downloading %s..."
+msgstr "ينزّل %s..."
+
+#: ../extensions/cpsection/updater/view.py:129
+#, python-format
+msgid "Updating %s..."
+msgstr "يحدّث %s..."
+
+#: ../extensions/cpsection/updater/view.py:139
+msgid "Your software is up-to-date"
+msgstr "برمجيات محدّثة"
+
+#: ../extensions/cpsection/updater/view.py:141
+#, python-format
+msgid "You can install %s update"
+msgid_plural "You can install %s updates"
+msgstr[0] "يمكنك تنزيل %s تحديث"
+msgstr[1] "يمكنك تنزيل تحديث واحد"
+msgstr[2] "يمكنك تنزيل تحديثين"
+msgstr[3] "يمكنك تنزيل %s تحديثات"
+msgstr[4] "يمكنك تنزيل %s تحديثا"
+msgstr[5] "يمكنك تنزيل %s تحديث"
+
+#: ../extensions/cpsection/updater/view.py:159
+msgid "Checking for updates..."
+msgstr "يلتمس التحديثات..."
+
+#: ../extensions/cpsection/updater/view.py:164
+msgid "Installing updates..."
+msgstr "يُثبّت التحديثات..."
+
+#: ../extensions/cpsection/updater/view.py:172
+#, python-format
+msgid "%s update was installed"
+msgid_plural "%s updates were installed"
+msgstr[0] "لم تُثبّت أي تحديثات"
+msgstr[1] "ثُبّت تحديث واحد"
+msgstr[2] "ثُبّت تحديثين"
+msgstr[3] "ثُبّتت %s تحديثات"
+msgstr[4] "ثُبّت %s تحديثا"
+msgstr[5] "ثُبّت %s تحديث"
+
+#: ../extensions/cpsection/updater/view.py:253
+msgid "Install selected"
+msgstr "ثبّت المختار"
+
+#: ../extensions/cpsection/updater/view.py:274
+#, python-format
+msgid "Download size: %s"
+msgstr "حجم التنزيل: %s"
+
+#: ../extensions/cpsection/updater/view.py:362
+#, python-format
+msgid "From version %(current)d to %(new)s (Size: %(size)s)"
+msgstr "من الإصدارة %(current)d إلى %(new)s (الحجم %(size)s)"
+
+#. TRANS: download size is 0
+#: ../extensions/cpsection/updater/view.py:382
+msgid "None"
+msgstr "صفر"
+
+#. TRANS: download size of very small updates
+#: ../extensions/cpsection/updater/view.py:385
+msgid "1 KB"
+msgstr "1 ك.بايت"
+
+#. TRANS: download size of small updates, e.g. '250 KB'
+#: ../extensions/cpsection/updater/view.py:388
+#, python-format
+msgid "%.0f KB"
+msgstr "%.0f ك.بايت"
+
+#. TRANS: download size of updates, e.g. '2.3 MB'
+#: ../extensions/cpsection/updater/view.py:391
+#, python-format
+msgid "%.1f MB"
+msgstr "%.1f م.بايت"
+
+#: ../extensions/deviceicon/battery.py:58
+msgid "My Battery"
+msgstr "بطاريتي"
+
+#: ../extensions/deviceicon/battery.py:137
+msgid "Removed"
+msgstr "أُزيل"
+
+#: ../extensions/deviceicon/battery.py:140
+msgid "Charging"
+msgstr "يشحن"
+
+#: ../extensions/deviceicon/battery.py:143
+msgid "Very little power remaining"
+msgstr "بقي القليل جدا من الطاقة"
+
+#: ../extensions/deviceicon/battery.py:149
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr "باقي %(hour)d:%(min).2d"
+
+#: ../extensions/deviceicon/battery.py:152
+msgid "Charged"
+msgstr "مشحون"
+
+#: ../extensions/deviceicon/network.py:47
+#, python-format
+msgid "IP address: %s"
+msgstr "رقم (عنوان) الإنترنت: %s"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#: ../extensions/deviceicon/network.py:108
+msgid "Disconnect..."
+msgstr "افصِل..."
+
+#: ../extensions/deviceicon/network.py:113
+msgid "Create new wireless network"
+msgstr "أنشئ شبكة لاسلكية جديدة"
+
+#: ../extensions/deviceicon/network.py:119
+#: ../extensions/deviceicon/network.py:250
+#: ../src/jarabe/desktop/meshbox.py:248 ../src/jarabe/desktop/meshbox.py:537
+msgid "Connecting..."
+msgstr "يتصّل..."
+
+# TODO: show the channel number
+#: ../extensions/deviceicon/network.py:123
+#: ../extensions/deviceicon/network.py:195
+#: ../extensions/deviceicon/network.py:254
+#: ../src/jarabe/desktop/meshbox.py:254 ../src/jarabe/desktop/meshbox.py:543
+msgid "Connected"
+msgstr "مُتّصل"
+
+#: ../extensions/deviceicon/network.py:155
+msgid "Channel"
+msgstr "قناة"
+
+#: ../extensions/deviceicon/network.py:170
+msgid "Wired Network"
+msgstr "شبكة سلكية"
+
+#: ../extensions/deviceicon/network.py:198
+msgid "Speed"
+msgstr "السرعة"
+
+#: ../extensions/deviceicon/network.py:224
+msgid "Wireless modem"
+msgstr "مودم لاسلكي"
+
+#: ../extensions/deviceicon/network.py:242
+msgid "Please wait..."
+msgstr "انتظر من فضلك..."
+
+#: ../extensions/deviceicon/network.py:245
+#: ../src/jarabe/desktop/meshbox.py:164 ../src/jarabe/desktop/meshbox.py:494
+msgid "Connect"
+msgstr "اتصل"
+
+#: ../extensions/deviceicon/network.py:246
+msgid "Disconnected"
+msgstr "مفصول"
+
+#: ../extensions/deviceicon/network.py:249
+#: ../src/jarabe/controlpanel/toolbar.py:115
+#: ../src/jarabe/desktop/homebox.py:68
+#: ../src/jarabe/frame/activitiestray.py:700
+#: ../src/jarabe/frame/activitiestray.py:799
+#: ../src/jarabe/frame/activitiestray.py:827
+msgid "Cancel"
+msgstr "ألغِ"
+
+#: ../extensions/deviceicon/network.py:253
+#: ../src/jarabe/desktop/meshbox.py:168
+msgid "Disconnect"
+msgstr "اقطع الاتصال"
+
+#: ../extensions/deviceicon/network.py:496
+#, python-format
+msgid "%s's network"
+msgstr "شبكة %s"
+
+#: ../extensions/deviceicon/network.py:562
+#: ../extensions/deviceicon/network.py:621
+msgid "Mesh Network"
+msgstr "شبكة عُروِيّة"
+
+#: ../extensions/deviceicon/speaker.py:59
+msgid "My Speakers"
+msgstr "سماعاتي"
+
+#: ../extensions/deviceicon/speaker.py:133
+msgid "Unmute"
+msgstr "افتح"
+
+#: ../extensions/deviceicon/speaker.py:136
+msgid "Mute"
+msgstr "أصمِت"
+
+#: ../extensions/globalkey/screenshot.py:59
+msgid "Mesh"
+msgstr "الشبكة العروية"
+
+#: ../extensions/globalkey/screenshot.py:61
+#: ../src/jarabe/frame/zoomtoolbar.py:39
+msgid "Group"
+msgstr "المجموعة"
+
+#: ../extensions/globalkey/screenshot.py:63
+#: ../src/jarabe/frame/zoomtoolbar.py:41
+msgid "Home"
+msgstr "المنزل"
+
+#: ../extensions/globalkey/screenshot.py:69
+#: ../src/jarabe/frame/zoomtoolbar.py:43
+msgid "Activity"
+msgstr "النشاط"
+
+#: ../extensions/globalkey/screenshot.py:72
+msgid "Screenshot"
+msgstr "لقطة شاشة"
+
+#: ../extensions/globalkey/screenshot.py:74
+#, python-format
+msgid "Screenshot of \"%s\""
+msgstr "لقطة من \"%s\""
+
+#: ../data/sugar.schemas.in.h:1
+msgid ""
+"\"disabled\" to ask nick on initialization; \"system\" to reuse UNIX account "
+"long name."
+msgstr ""
+"استخدم \"disabled\" (معطل) للسؤال عن الكنية عند البدء، أو \"system\" (النظام) "
+"لاستخدام اسم الولوج إلى حساب النظام."
+
+#: ../data/sugar.schemas.in.h:2
+msgid "Backup URL"
+msgstr "مسار النسخ الاحتياطي"
+
+#: ../data/sugar.schemas.in.h:3
+msgid ""
+"Color for the XO icon that is used throughout the desktop. The string is "
+"composed of the stroke color and fill color, format is that of rbg colors. "
+"Example: #AC32FF,#9A5200"
+msgstr ""
+"لون الأيقونة المستخدم في أنحاء سطح المكتب. يركب من لون الحواف ولون الملء "
+"بنسق ألون rbg. مثال: #AC32FF,#9A5200"
+
+#: ../data/sugar.schemas.in.h:4
+msgid "Corner Delay"
+msgstr "تأخير الركن"
+
+#: ../data/sugar.schemas.in.h:5
+msgid "Default font face"
+msgstr "الخط المبدئي"
+
+#: ../data/sugar.schemas.in.h:6
+msgid "Default font size"
+msgstr "حجم الخط المبدئي"
+
+#: ../data/sugar.schemas.in.h:7
+msgid "Default nick"
+msgstr "الكنية المبدئية"
+
+#: ../data/sugar.schemas.in.h:8
+msgid "Delay for the activation of the frame using the corners."
+msgstr "تأخير تنشيط الإطار باستخدام الأركان."
+
+#: ../data/sugar.schemas.in.h:9
+msgid "Delay for the activation of the frame using the edges."
+msgstr "تأخير تنشيط الإطار باستخدام الحواف."
+
+#: ../data/sugar.schemas.in.h:10
+msgid "Edge Delay"
+msgstr "تأخير الحافة"
+
+#: ../data/sugar.schemas.in.h:11
+msgid "Favorites Layout"
+msgstr "المنظور المفضل"
+
+#: ../data/sugar.schemas.in.h:12
+msgid "Favorites resume mode"
+msgstr "طور الاستكمال المفضل"
+
+#: ../data/sugar.schemas.in.h:13
+msgid "Font face that is used throughout the desktop."
+msgstr "الخط المستَخدم في أرجاء سطح المكتب."
+
+#: ../data/sugar.schemas.in.h:14
+msgid "Font size that is used throughout the desktop."
+msgstr "حجم الخط المستَخدم في أرجاء سطح المكتب."
+
+#: ../data/sugar.schemas.in.h:15
+msgid ""
+"If TRUE, Sugar will make us searchable for the other users of the Jabber "
+"server."
+msgstr "إذا ضبطت \"صحيح\" فستتيح سكّر البحث عنا في خادوم جابِر."
+
+#: ../data/sugar.schemas.in.h:16
+msgid "If TRUE, Sugar will show a \"Log out\" option."
+msgstr "إذا ضبطت \"صحيح\" فستظهر سكّر خيار \"اخرج\"."
+
+#: ../data/sugar.schemas.in.h:17
+msgid "Jabber Server"
+msgstr "خادوم جابِر"
+
+#: ../data/sugar.schemas.in.h:18
+msgid "Keyboard layouts"
+msgstr "تخطيطات لوحة المفاتيح"
+
+#: ../data/sugar.schemas.in.h:19
+msgid "Keyboard model"
+msgstr "طراز لوحة المفاتيح"
+
+#: ../data/sugar.schemas.in.h:20
+msgid "Keyboard options"
+msgstr "خيارات لوحة المفاتيح"
+
+#: ../data/sugar.schemas.in.h:21
+msgid "Layout of the favorites view."
+msgstr "تنظيم المنظور المفضل."
+
+#: ../data/sugar.schemas.in.h:22
+msgid ""
+"List of keyboard layouts. Each entry should be in the form layout(variant)"
+msgstr "قائمة بتخطيطات لوحة المفاتيح، كل على الشكل ‪layout(variant)‬"
+
+#: ../data/sugar.schemas.in.h:23
+msgid "List of keyboard options."
+msgstr "قائمة بخيارات لوحة المفاتيح."
+
+#: ../data/sugar.schemas.in.h:24
+msgid "Power Automatic"
+msgstr "الطاقة تلقائية"
+
+#: ../data/sugar.schemas.in.h:25
+msgid "Power Automatic."
+msgstr "الطاقة تلقائية."
+
+#: ../data/sugar.schemas.in.h:26
+msgid "Power Extreme"
+msgstr "الطاقة متشددة"
+
+#: ../data/sugar.schemas.in.h:27
+msgid "Power Extreme."
+msgstr "الطاقة متشددة."
+
+#: ../data/sugar.schemas.in.h:28
+msgid "Publish to Gadget"
+msgstr "انشر في Gadget"
+
+#: ../data/sugar.schemas.in.h:29
+msgid "Setting for muting the sound device."
+msgstr "إعداد لكتم صوت الجهاز."
+
+#: ../data/sugar.schemas.in.h:30
+msgid "Show Log out"
+msgstr "أظهر الخروج"
+
+#: ../data/sugar.schemas.in.h:31
+msgid "Sound Muted"
+msgstr "الصوت مُصمَت"
+
+#: ../data/sugar.schemas.in.h:32
+msgid "The keyboard model to be used"
+msgstr "طراز لوحة المفاتيح الذي سيستخدم"
+
+#: ../data/sugar.schemas.in.h:34
+msgid "Timezone setting for the system."
+msgstr "المنطقة الزمنية للنظام."
+
+#: ../data/sugar.schemas.in.h:35
+msgid "Url of the jabber server to use."
+msgstr "مسار خادوم جابِر الذي سيستخدم."
+
+#: ../data/sugar.schemas.in.h:36
+msgid "Url where the backup is saved to."
+msgstr "مسار مكان الحفظ الاحتياطي."
+
+#: ../data/sugar.schemas.in.h:37
+msgid "User Color"
+msgstr "لون المستخدم"
+
+#: ../data/sugar.schemas.in.h:38
+msgid "User Name"
+msgstr "اسم المستخدم"
+
+#: ../data/sugar.schemas.in.h:39
+msgid "User name that is used throughout the desktop."
+msgstr "الاسم المستَخدم في أرجاء سطح المكتب."
+
+#: ../data/sugar.schemas.in.h:40
+msgid "Volume Level"
+msgstr "مستوى الصوت"
+
+#: ../data/sugar.schemas.in.h:41
+msgid "Volume level for the sound device."
+msgstr "مستوى شدة الصوت."
+
+#: ../data/sugar.schemas.in.h:42
+msgid ""
+"When in resume mode, clicking on a favorite icon will cause the last entry "
+"for that activity to be resumed."
+msgstr "في وضع الاستكمال، النقر على أيقونة مفضلة سيستكمل آخر مدخلة لهذا النشاط."
+
+#: ../src/jarabe/controlpanel/cmd.py:28
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr "لوحة تحكم سكر: تحذير، وُجد أكثر من خيار بنفس الاسم: %s الوحدة: %r"
+
+#: ../src/jarabe/controlpanel/cmd.py:30
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr "لوحة تحكم سكر: key=%s ليس متاحا كخيار"
+
+#: ../src/jarabe/controlpanel/cmd.py:31
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr "لوحة تحكم سكر: %s"
+
+#. TRANS: Translators, there's a empty line at the end of this string,
+#. which must appear in the translated string (msgstr) as well.
+#: ../src/jarabe/controlpanel/cmd.py:37
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" -c key clear the current value for the key \n"
+" "
+msgstr ""
+"الاستخدام: لوحة تحكم سكر [ خيار ] مفتاح [ معاملات ... ]\n"
+"لوحة تحكم لبيئة «سُكّر».\n"
+"الخيارات: \n"
+"-hاعرض رسالة المساعدة هذه \n"
+"-lاسرد كل الخيارات المتاحة \n"
+"-h keyاعرض معلومات عن هذا المفتاح \n"
+"-g key اعرض القيمة الحالية للمفتاح \n"
+"-s keyاضبط قيمة المفتاح \n"
+"-c keyامسح القيمة الحالية للمفتاح \n"
+" "
+
+#: ../src/jarabe/controlpanel/cmd.py:50
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr "تحتاج لإعادة تشغيل «سُكّر» لتُطبق التغييرات.\n"
+
+#: ../src/jarabe/controlpanel/gui.py:281
+msgid "Warning"
+msgstr "تحذير"
+
+#: ../src/jarabe/controlpanel/gui.py:282
+#: ../src/jarabe/controlpanel/sectionview.py:42
+msgid "Changes require restart"
+msgstr "تتطلب التغييرات إعادة التشغيل"
+
+#: ../src/jarabe/controlpanel/gui.py:285
+msgid "Cancel changes"
+msgstr "ألغِ التغييرات"
+
+#: ../src/jarabe/controlpanel/gui.py:290 ../src/jarabe/desktop/homebox.py:70
+msgid "Later"
+msgstr "لاحقا"
+
+#: ../src/jarabe/controlpanel/gui.py:294
+msgid "Restart now"
+msgstr "أعِد التشغيل الآن"
+
+#: ../src/jarabe/controlpanel/toolbar.py:61 ../src/jarabe/intro/window.py:206
+msgid "Done"
+msgstr "تمّ"
+
+#: ../src/jarabe/controlpanel/toolbar.py:121
+#: ../src/jarabe/desktop/favoritesview.py:333
+msgid "Ok"
+msgstr "حسنا"
+
+#: ../src/jarabe/desktop/activitieslist.py:236
+#, python-format
+msgid "Version %s"
+msgstr "الإصدارة %s"
+
+#: ../src/jarabe/desktop/activitieslist.py:357
+msgid "Confirm erase"
+msgstr "أكّد المسح"
+
+#: ../src/jarabe/desktop/activitieslist.py:359
+#, python-format
+msgid "Confirm erase: Do you want to permanently erase %s?"
+msgstr "أكّد المسح: أتريد مسح %s نهائيا؟"
+
+# self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+# TODO: Implement stopping downloads
+# self._stop_item.connect('activate', self._stop_item_activate_cb)
+# self.append_menu_item(self._stop_item)
+#: ../src/jarabe/desktop/activitieslist.py:363
+#: ../src/jarabe/frame/clipboardmenu.py:63
+#: ../src/jarabe/view/viewsource.py:218
+msgid "Keep"
+msgstr "احفظ"
+
+#: ../src/jarabe/desktop/activitieslist.py:366
+#: ../src/jarabe/desktop/activitieslist.py:409
+#: ../src/jarabe/journal/journaltoolbox.py:360
+#: ../src/jarabe/journal/palettes.py:105
+msgid "Erase"
+msgstr "امسح"
+
+#: ../src/jarabe/desktop/activitieslist.py:430
+msgid "Remove favorite"
+msgstr "احذف المفضلة"
+
+#: ../src/jarabe/desktop/activitieslist.py:434
+msgid "Make favorite"
+msgstr "اصنع مفضلة"
+
+# TRANS: label for the freeform layout in the favorites view
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:116
+msgid "Freeform"
+msgstr "حُر"
+
+# TRANS: label for the ring layout in the favorites view
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:198
+msgid "Ring"
+msgstr "حلقة"
+
+#. TRANS: label for the spiral layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:337
+msgid "Spiral"
+msgstr "حلزون"
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:404
+msgid "Box"
+msgstr "صندوق"
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:445
+msgid "Triangle"
+msgstr "مثلث"
+
+#: ../src/jarabe/desktop/favoritesview.py:324
+msgid "Registration Failed"
+msgstr "فشل التسجيل"
+
+#: ../src/jarabe/desktop/favoritesview.py:325
+#, python-format
+msgid "%s"
+msgstr "%s"
+
+#: ../src/jarabe/desktop/favoritesview.py:327
+msgid "Registration Successful"
+msgstr "نجح التسجيل"
+
+#: ../src/jarabe/desktop/favoritesview.py:328
+msgid "You are now registered with your school server."
+msgstr "الآن، أنت مسجّل في خادوم مدرستك."
+
+#: ../src/jarabe/desktop/favoritesview.py:630
+msgid "Register"
+msgstr "سجّل"
+
+#: ../src/jarabe/desktop/homebox.py:63
+msgid "Software Update"
+msgstr "تحديث البرمجيات"
+
+#: ../src/jarabe/desktop/homebox.py:64
+msgid "Update your activities to ensure compatibility with your new software"
+msgstr "حدّث نشاطاتك لتضمن التوافقية مع البرمجيات الجديدة"
+
+#: ../src/jarabe/desktop/homebox.py:73
+msgid "Check now"
+msgstr "التمس الآن"
+
+#: ../src/jarabe/desktop/homebox.py:192
+msgid "List view"
+msgstr "منظور القائمة"
+
+#: ../src/jarabe/desktop/homebox.py:193
+msgid "<Ctrl>2"
+msgstr "‪<Ctrl>‬2"
+
+#: ../src/jarabe/desktop/homebox.py:255
+msgid "Favorites view"
+msgstr "المنظور المفضل"
+
+#: ../src/jarabe/desktop/homebox.py:256
+msgid "<Ctrl>1"
+msgstr "‪<Ctrl>1"
+
+#: ../src/jarabe/desktop/keydialog.py:135
+msgid "Key Type:"
+msgstr "نوع المفتاح:"
+
+#: ../src/jarabe/desktop/keydialog.py:155
+msgid "Authentication Type:"
+msgstr "نوع الاستيثاق:"
+
+#: ../src/jarabe/desktop/keydialog.py:220
+msgid "WPA & WPA2 Personal"
+msgstr "‏WPA و WPA2 شخصي"
+
+#: ../src/jarabe/desktop/keydialog.py:229
+msgid "Wireless Security:"
+msgstr "أمن اللاسلكي:"
+
+#: ../src/jarabe/desktop/meshbox.py:492
+#, python-format
+msgid "Mesh Network %d"
+msgstr "شبكة عُروِيّة %d"
+
+# TRANS: Action label for resuming an activity.
+#. TRANS: Action label for resuming an activity.
+#: ../src/jarabe/desktop/meshbox.py:629
+#: ../src/jarabe/frame/activitiestray.py:735
+#: ../src/jarabe/journal/journaltoolbox.py:428
+#: ../src/jarabe/journal/palettes.py:65 ../src/jarabe/view/palettes.py:67
+msgid "Resume"
+msgstr "استكمل"
+
+#: ../src/jarabe/desktop/meshbox.py:634
+#: ../src/jarabe/frame/activitiestray.py:233
+msgid "Join"
+msgstr "التحق"
+
+#: ../src/jarabe/desktop/schoolserver.py:103
+msgid "Cannot connect to the server."
+msgstr "تعذّر الاتصال بالخادوم."
+
+#: ../src/jarabe/desktop/schoolserver.py:108
+msgid "The server could not complete the request."
+msgstr "لم يستطع الخادوم إكمال الطلب."
+
+#: ../src/jarabe/frame/activitiestray.py:238
+#: ../src/jarabe/frame/activitiestray.py:672
+msgid "Decline"
+msgstr "ارفض"
+
+#: ../src/jarabe/frame/activitiestray.py:624
+#, python-format
+msgid "%dB"
+msgstr "%d بايت"
+
+#: ../src/jarabe/frame/activitiestray.py:626
+#, python-format
+msgid "%dKB"
+msgstr "%d ك.بايت"
+
+#: ../src/jarabe/frame/activitiestray.py:628
+#, python-format
+msgid "%dMB"
+msgstr "%d م.بايت"
+
+#: ../src/jarabe/frame/activitiestray.py:645
+#, python-format
+msgid "%s of %s"
+msgstr "‏%s من %s"
+
+#: ../src/jarabe/frame/activitiestray.py:657
+#, python-format
+msgid "Transfer from %r"
+msgstr "نقل من %r"
+
+#: ../src/jarabe/frame/activitiestray.py:667
+msgid "Accept"
+msgstr "اقبل"
+
+#: ../src/jarabe/frame/activitiestray.py:690
+#: ../src/jarabe/frame/activitiestray.py:817
+#, python-format
+msgid "%s (%s)"
+msgstr "‏%s ‏(%s)"
+
+#: ../src/jarabe/frame/activitiestray.py:724
+#: ../src/jarabe/frame/activitiestray.py:852
+msgid "Dismiss"
+msgstr "ارفض"
+
+#: ../src/jarabe/frame/activitiestray.py:787
+#, python-format
+msgid "Transfer to %r"
+msgstr "نقل إلى %r"
+
+#: ../src/jarabe/frame/clipboardmenu.py:53 ../src/jarabe/view/palettes.py:221
+msgid "Remove"
+msgstr "أزل"
+
+#: ../src/jarabe/frame/clipboardmenu.py:58
+#: ../src/jarabe/frame/clipboardmenu.py:81
+msgid "Open"
+msgstr "افتح"
+
+#: ../src/jarabe/frame/clipboardmenu.py:86
+msgid "Open with"
+msgstr "افتح باستخدام"
+
+#: ../src/jarabe/frame/clipboardobject.py:49
+#, python-format
+msgid "%s clipping"
+msgstr "قصاصة %s"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:37
+msgid "Neighborhood"
+msgstr "الجِوَار"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:37
+msgid "F1"
+msgstr "F1"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:39
+msgid "F2"
+msgstr "F2"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:41
+msgid "F3"
+msgstr "F3"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:43
+msgid "F4"
+msgstr "F4"
+
+#: ../src/jarabe/intro/window.py:128
+msgid "Click to change color:"
+msgstr "انقر لتغيير اللون:"
+
+#: ../src/jarabe/intro/window.py:192 ../src/jarabe/journal/detailview.py:103
+msgid "Back"
+msgstr "السابق"
+
+#: ../src/jarabe/intro/window.py:209
+msgid "Next"
+msgstr "التالي"
+
+#: ../src/jarabe/journal/expandedentry.py:152
+#: ../src/jarabe/journal/palettes.py:59
+msgid "Untitled"
+msgstr "بدون عنوان"
+
+#: ../src/jarabe/journal/expandedentry.py:243
+msgid "No preview"
+msgstr "لا معاينة"
+
+#: ../src/jarabe/journal/expandedentry.py:262
+#, python-format
+msgid "Kind: %s"
+msgstr "النوع: %s"
+
+#: ../src/jarabe/journal/expandedentry.py:262
+msgid "Unknown"
+msgstr "مجهول"
+
+#: ../src/jarabe/journal/expandedentry.py:263
+#, python-format
+msgid "Date: %s"
+msgstr "التاريخ: %s"
+
+#: ../src/jarabe/journal/expandedentry.py:264
+#, python-format
+msgid "Size: %s"
+msgstr "الحجم: %s"
+
+#: ../src/jarabe/journal/expandedentry.py:286 ../src/jarabe/journal/misc.py:93
+msgid "No date"
+msgstr "لا تاريخ"
+
+#: ../src/jarabe/journal/expandedentry.py:293
+msgid "Participants:"
+msgstr "المشاركون:"
+
+#: ../src/jarabe/journal/expandedentry.py:316
+msgid "Description:"
+msgstr "الوصف:"
+
+#: ../src/jarabe/journal/expandedentry.py:341
+msgid "Tags:"
+msgstr "الوُسوم:"
+
+#: ../src/jarabe/journal/journalactivity.py:108
+#: ../src/jarabe/journal/volumestoolbar.py:47
+msgid "Journal"
+msgstr "الدفتر"
+
+#: ../src/jarabe/journal/journaltoolbox.py:67
+msgid "Search"
+msgstr "ابحث"
+
+#: ../src/jarabe/journal/journaltoolbox.py:126
+msgid "Anytime"
+msgstr "أي وقت"
+
+#: ../src/jarabe/journal/journaltoolbox.py:128
+msgid "Today"
+msgstr "اليوم"
+
+#: ../src/jarabe/journal/journaltoolbox.py:130
+msgid "Since yesterday"
+msgstr "منذ أمس"
+
+# TRANS: Filter entries modified during the last 7 days.
+#. TRANS: Filter entries modified during the last 7 days.
+#: ../src/jarabe/journal/journaltoolbox.py:132
+msgid "Past week"
+msgstr "الأسبوع الماضي"
+
+# TRANS: Filter entries modified during the last 30 days.
+#. TRANS: Filter entries modified during the last 30 days.
+#: ../src/jarabe/journal/journaltoolbox.py:134
+msgid "Past month"
+msgstr "الشهر الماضي"
+
+# TRANS: Filter entries modified during the last 356 days.
+#. TRANS: Filter entries modified during the last 356 days.
+#: ../src/jarabe/journal/journaltoolbox.py:136
+msgid "Past year"
+msgstr "السنة الماضية"
+
+#: ../src/jarabe/journal/journaltoolbox.py:143
+msgid "Anyone"
+msgstr "أي شخص"
+
+#: ../src/jarabe/journal/journaltoolbox.py:145
+msgid "My friends"
+msgstr "أصدقائي"
+
+#: ../src/jarabe/journal/journaltoolbox.py:146
+msgid "My class"
+msgstr "صفي"
+
+# TRANS: Item in a combo box that filters by entry type.
+#: ../src/jarabe/journal/journaltoolbox.py:274
+msgid "Anything"
+msgstr "أي شيء"
+
+# TODO: Add "Start with" menu item
+#: ../src/jarabe/journal/journaltoolbox.py:350
+#: ../src/jarabe/journal/palettes.py:83
+msgid "Copy"
+msgstr "انسخ"
+
+# TRANS: Action label for starting an entry.
+#. TRANS: Action label for starting an entry.
+#: ../src/jarabe/journal/journaltoolbox.py:431
+#: ../src/jarabe/journal/palettes.py:68
+msgid "Start"
+msgstr "ابدأ"
+
+#: ../src/jarabe/journal/listview.py:373
+msgid "Your Journal is empty"
+msgstr "دفترك خالِ"
+
+#: ../src/jarabe/journal/listview.py:375
+msgid "No matching entries"
+msgstr "لا مدخلات مطابقة"
+
+#: ../src/jarabe/journal/listview.py:386
+msgid "Clear search"
+msgstr "امسح البحث"
+
+#: ../src/jarabe/journal/modalalert.py:63
+msgid "Your Journal is full"
+msgstr "دفترك ممتلئ"
+
+#: ../src/jarabe/journal/modalalert.py:67
+msgid "Please delete some old Journal entries to make space for new ones."
+msgstr "من فضلك احذف بعض مُدخلات الدفتر القديمة لإخلاء مساحة للمُدخلات الجديدة."
+
+#: ../src/jarabe/journal/modalalert.py:79
+msgid "Show Journal"
+msgstr "أظهر الدفتر"
+
+#: ../src/jarabe/journal/objectchooser.py:146
+msgid "Choose an object"
+msgstr "اختر عنصرا"
+
+#: ../src/jarabe/journal/objectchooser.py:151
+#: ../src/jarabe/view/viewsource.py:308
+msgid "Close"
+msgstr "أغلق"
+
+#: ../src/jarabe/journal/palettes.py:66
+msgid "Resume with"
+msgstr "استعد مع"
+
+#: ../src/jarabe/journal/palettes.py:69
+msgid "Start with"
+msgstr "ابدأ مع"
+
+#: ../src/jarabe/journal/palettes.py:91
+msgid "Send to"
+msgstr "أرسل إلى"
+
+#: ../src/jarabe/journal/palettes.py:100
+msgid "View Details"
+msgstr "اعرض التفاصيل"
+
+#: ../src/jarabe/journal/palettes.py:178
+msgid "No friends present"
+msgstr "لا يوجد أصدقاء"
+
+#: ../src/jarabe/journal/palettes.py:183
+msgid "No valid connection found"
+msgstr "لم يُعثر على اتصال سليم"
+
+#: ../src/jarabe/journal/palettes.py:211
+msgid "No activity to resume entry"
+msgstr "لا نشاط لاستكمال المُدخلة"
+
+#: ../src/jarabe/journal/palettes.py:213
+msgid "No activity to start entry"
+msgstr "لا نشاط لبدء المُدخلة"
+
+#: ../src/jarabe/view/buddymenu.py:62
+msgid "Remove friend"
+msgstr "أزل الصديق"
+
+#: ../src/jarabe/view/buddymenu.py:65
+msgid "Make friend"
+msgstr "اصنع صديق"
+
+#: ../src/jarabe/view/buddymenu.py:82
+msgid "Shutdown"
+msgstr "أطفئ"
+
+#: ../src/jarabe/view/buddymenu.py:90
+msgid "Logout"
+msgstr "اخرج"
+
+#: ../src/jarabe/view/buddymenu.py:95
+msgid "My Settings"
+msgstr "إعداداتي"
+
+#: ../src/jarabe/view/buddymenu.py:130
+#, python-format
+msgid "Invite to %s"
+msgstr "ادعُ إلى %s"
+
+#: ../src/jarabe/view/palettes.py:45
+msgid "Starting..."
+msgstr "يبدأ..."
+
+#. TODO: share-with, keep
+#: ../src/jarabe/view/palettes.py:74
+msgid "View Source"
+msgstr "اعرض المصدر"
+
+#: ../src/jarabe/view/palettes.py:85
+msgid "Stop"
+msgstr "أوقف"
+
+#: ../src/jarabe/view/palettes.py:125
+msgid "Start new"
+msgstr "ابدأ الآن"
+
+#: ../src/jarabe/view/palettes.py:174
+msgid "Show contents"
+msgstr "أظهر المحتويات"
+
+#: ../src/jarabe/view/palettes.py:196 ../src/jarabe/view/palettes.py:246
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr "%(free_space)d م.بايت خالية"
+
+#: ../src/jarabe/view/viewsource.py:208
+msgid "Instance Source"
+msgstr "مصدر السيرورة"
+
+#: ../src/jarabe/view/viewsource.py:233
+msgid "Source"
+msgstr "المصدر"
+
+#: ../src/jarabe/view/viewsource.py:292
+msgid "Activity Bundle Source"
+msgstr "مصدر حزمة النشاط"
+
+#: ../src/jarabe/view/viewsource.py:299
+#, python-format
+msgid "View source: %r"
+msgstr "اعرض مصدر: %r"
+
+#~ msgid "Title"
+#~ msgstr "العنوان"
+
+#~ msgid "Version"
+#~ msgstr "الإصدارة"
+
+#~ msgid "Date"
+#~ msgstr "التاريخ"
+
+#~ msgid "Cannot obtain data needed for registration."
+#~ msgstr "تعذّر الحصول على البيانات المطلوبة للتسجيل."
+
+#~ msgid "Unmount"
+#~ msgstr "افصل"
+
+#~ msgid "Restart"
+#~ msgstr "أعد التشغيل"
+
+#~ msgid ""
+#~ "© 2008 One Laptop per Child Association Inc; Red Hat Inc; and Contributors."
+#~ msgstr "حقوق النشر محفوظة 2008 لجمعية حاسوب محمول لكل طفل؛ ردهات؛ والمساهمين."
+
+#~ msgid "Encryption Type:"
+#~ msgstr "نوع التعمية:"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#~ msgid "Disconnecting..."
+#~ msgstr "يجري قطع الاتصال..."
+
+#~ msgid "About my XO"
+#~ msgstr "عن حاسوبي"
+
+#~ msgid "Connected to a School Mesh Portal"
+#~ msgstr "اتصل ببوابة شبكة مدرسة"
+
+#~ msgid "Looking for a School Mesh Portal..."
+#~ msgstr "يبحث عن بوابة شبكة مدرسة..."
+
+#~ msgid "Connected to an XO Mesh Portal"
+#~ msgstr "اتصل ببوابة شبكة XO"
+
+#~ msgid "Looking for an XO Mesh Portal..."
+#~ msgstr "يبحث عن بوابة شبكة XO..."
+
+#~ msgid "Connected to a Simple Mesh"
+#~ msgstr "اتصل بشبكة بسيطة"
+
+#~ msgid "Starting a Simple Mesh"
+#~ msgstr "يبدأ شبكة بسيطة"
+
+#~ msgid "Unknown Mesh"
+#~ msgstr "شبكة مجهولة"
+
+#~ msgid "Settings"
+#~ msgstr "الإعدادات"
+
+#, python-format
+#~ msgid "Clipboard object: %s."
+#~ msgstr "عنصر الحافظة: %s."
+
+#~ msgid "You must enter a server."
+#~ msgstr "يجب أن تُدخِل خادوما."
+
+#~ msgid "Control Panel"
+#~ msgstr "لوحة التحكم"
+
+#~ msgid "<Ctrl>R"
+#~ msgstr "<Ctrl>ح"
+
+#~ msgid "Ring view"
+#~ msgstr "منظور الحلقة"
+
+#~ msgid "Remove from ring"
+#~ msgstr "أزِل من الحلقة"
+
+#~ msgid "Add to ring"
+#~ msgstr "أضِف للحلقة"
+
+#~ msgid "Changes require a sugar restart to take effect."
+#~ msgstr "يتطلب نفاذ التغييرات إعادة تشغيل «سُكّر»."
+
+#~ msgid "Changes require restart to take effect"
+#~ msgstr "يتطلب نفاذ التغييرات إعادة التشغيل."
+
+#~ msgid "Delay in milliseconds:"
+#~ msgstr "التأخير بالملي ثانية:"
+
+#~ msgid "Hot Corners"
+#~ msgstr "الزوايا النشطة"
+
+#~ msgid "Warm Edges"
+#~ msgstr "الحواف المتفاعلة"
+
+#~ msgid "off"
+#~ msgstr "معطّل"
+
+#~ msgid "on"
+#~ msgstr "مفعّل"
+
+#~ msgid "Permission denied. You need to be root to run this method."
+#~ msgstr "الصلاحية ممنوعة. تحتاج أن تكون الجذر لتشغل الوظيفة المطلوبة."
+
+#~ msgid "Error in reading timezone"
+#~ msgstr "خطأ في قراءة المنطقة الزمنية"
+
+#, python-format
+#~ msgid "Error copying timezone (from %s): %s"
+#~ msgstr "خطأ في نسخ المنطقة الزمنية (من %s): %s"
+
+#, python-format
+#~ msgid "Changing permission of timezone: %s"
+#~ msgstr "يجري تغيير صلاحيات المنطقة الزمنية: %s"
+
+#~ msgid "About this XO"
+#~ msgstr "عَنْ XO هذا"
+
+#~ msgid "Add to journal"
+#~ msgstr "أضِف إلى اليوميات"
+
+#~ msgid "Reboot"
+#~ msgstr "أعِد التشغيل"
+
+#~ msgid "My Battery life"
+#~ msgstr "عمر بطاريتي"
+
+#~ msgid "Battery charging"
+#~ msgstr "البطاريّة تشحن"
+
+#~ msgid "Battery discharging"
+#~ msgstr "البطاريّة تُفرّغ"
+
+#~ msgid "Battery fully charged"
+#~ msgstr "البطارية مشحونة تماما"
+
+#~ msgid "Invite"
+#~ msgstr "ادعُ"
+
+#~ msgid "Text"
+#~ msgstr "نص"
+
+#~ msgid "Image"
+#~ msgstr "صورة"
+
+#~ msgid "Private"
+#~ msgstr "خاص"
+
+#~ msgid "My Neighborhood"
+#~ msgstr "جِوارِي"
+
+#, python-format
+#~ msgid "%s Activity"
+#~ msgstr "نشاط %s"
+
+#~ msgid "Share with:"
+#~ msgstr "شارِك مع:"
+
+#~ msgid "Undo"
+#~ msgstr "تراجع"
+
+#~ msgid "Redo"
+#~ msgstr "أعِد"
+
+#~ msgid "Paste"
+#~ msgstr "الصق"
+
+#~ msgid "Keep error"
+#~ msgstr "خطأ في الحفظ"
+
+#~ msgid "Keep error: all changes will be lost"
+#~ msgstr "خطأ في الحفظ: ستُفقد كل التغييرات"
+
+#~ msgid "Don't stop"
+#~ msgstr "لا تتوقف"
+
+#~ msgid "Stop anyway"
+#~ msgstr "توقف على أي حال"
+
+#~ msgid "Continue"
+#~ msgstr "واصِل"
+
+#~ msgid "OK"
+#~ msgstr "حسنا"
+
+#, python-format
+#~ msgid "%d year"
+#~ msgstr "%d سنة"
+
+#, python-format
+#~ msgid "%d years"
+#~ msgstr "%d سنوات"
+
+#, python-format
+#~ msgid "%d month"
+#~ msgstr "%d شهر"
+
+#, python-format
+#~ msgid "%d months"
+#~ msgstr "%d شهور"
+
+#, python-format
+#~ msgid "%d week"
+#~ msgstr "%d أسبوع"
+
+#, python-format
+#~ msgid "%d weeks"
+#~ msgstr "%d أسابيع"
+
+#, python-format
+#~ msgid "%d day"
+#~ msgstr "%d يوم"
+
+#, python-format
+#~ msgid "%d days"
+#~ msgstr "%d أيام"
+
+#, python-format
+#~ msgid "%d hour"
+#~ msgstr "%d ساعة"
+
+#, python-format
+#~ msgid "%d hours"
+#~ msgstr "%d ساعات"
+
+#, python-format
+#~ msgid "%d minute"
+#~ msgstr "%d دقيقة"
+
+#, python-format
+#~ msgid "%d minutes"
+#~ msgstr "%d دقائق"
+
+#, python-format
+#~ msgid "%d second"
+#~ msgstr "%d ثانية"
+
+#~ msgid " and "
+#~ msgstr " و "
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#, python-format
+#~ msgid ", "
+#~ msgstr "، "
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#, python-format
+#~ msgid "No matching entries "
+#~ msgstr "لا نتائج مطابقة "
diff --git a/shell/po/ay.po b/shell/po/ay.po
new file mode 100644
index 0000000..a79e711
--- /dev/null
+++ b/shell/po/ay.po
@@ -0,0 +1,420 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-01-18 18:19+0000\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.0.1\n"
+
+#: ../shell/intro/intro.py:67
+msgid "Name:"
+msgstr ""
+
+#: ../shell/intro/intro.py:96
+msgid "Click to change color:"
+msgstr ""
+
+#: ../shell/intro/intro.py:146
+msgid "Back"
+msgstr ""
+
+#: ../shell/intro/intro.py:160
+msgid "Done"
+msgstr ""
+
+#: ../shell/intro/intro.py:163
+msgid "Next"
+msgstr ""
+
+#: ../shell/view/BuddyMenu.py:59
+msgid "Remove friend"
+msgstr ""
+
+#: ../shell/view/BuddyMenu.py:62
+msgid "Make friend"
+msgstr ""
+
+#: ../shell/view/BuddyMenu.py:84
+#, python-format
+msgid "Invite to %s"
+msgstr ""
+
+#: ../shell/view/clipboardmenu.py:58
+msgid "Remove"
+msgstr ""
+
+#: ../shell/view/clipboardmenu.py:63
+msgid "Open"
+msgstr ""
+
+#. self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+#. TODO: Implement stopping downloads
+#. self._stop_item.connect('activate', self._stop_item_activate_cb)
+#. self.append_menu_item(self._stop_item)
+#: ../shell/view/clipboardmenu.py:73
+msgid "Add to journal"
+msgstr ""
+
+#: ../shell/view/clipboardmenu.py:213
+#, python-format
+msgid "Clipboard object: %s."
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:149
+msgid "Key Type:"
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:169
+msgid "Authentication Type:"
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:250
+msgid "Encryption Type:"
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:90
+msgid "Starting..."
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:104 ../shell/view/home/MeshBox.py:295
+msgid "Resume"
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:111
+#: ../lib/sugar/activity/activity.py:132
+msgid "Stop"
+msgstr ""
+
+#: ../shell/view/Shell.py:285
+msgid "Screenshot"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:159
+msgid "Reboot"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:164
+msgid "Shutdown"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:170
+msgid "Register"
+msgstr ""
+
+#. Only show disconnect when there's a mesh device, because mesh takes
+#. priority over the normal wireless device. NM doesn't have a "disconnect"
+#. method for a device either (for various reasons) so this doesn't
+#. have a good mapping
+#: ../shell/view/home/MeshBox.py:90 ../shell/view/home/MeshBox.py:197
+#: ../shell/view/devices/network/wireless.py:113
+#: ../shell/view/devices/network/mesh.py:83
+msgid "Disconnect..."
+msgstr ""
+
+#: ../shell/view/home/MeshBox.py:195 ../shell/view/devices/network/mesh.py:37
+#: ../shell/view/devices/network/mesh.py:62
+#: ../shell/view/devices/network/mesh.py:66
+msgid "Mesh Network"
+msgstr ""
+
+#: ../shell/view/home/MeshBox.py:300
+msgid "Join"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:38
+msgid "My Battery life"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:94
+msgid "Battery charging"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:96
+msgid "Battery discharging"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:98
+msgid "Battery fully charged"
+msgstr ""
+
+#: ../shell/view/devices/network/wireless.py:61
+msgid "Disconnected"
+msgstr ""
+
+#: ../shell/view/devices/network/wireless.py:131
+msgid "Channel"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:42
+msgid "Neighborhood"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:54
+msgid "Group"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:66
+msgid "Home"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:78
+msgid "Activity"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:115
+msgid "Share with:"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:117
+msgid "Private"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:118
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:126
+msgid "Keep"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:245
+msgid "Undo"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:250
+msgid "Redo"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:260
+msgid "Copy"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:265
+msgid "Paste"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:454
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:824
+msgid "Keep error"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:825
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:828
+msgid "Don't stop"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:831
+msgid "Stop anyway"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:164 ../lib/sugar/graphics/alert.py:206
+msgid "Cancel"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:168
+msgid "Ok"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:216
+msgid "Continue"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:244
+msgid "OK"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d year"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d years"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d month"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d months"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d week"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d weeks"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d day"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d days"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hour"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hours"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minute"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minutes"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d second"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d seconds"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:191
+msgid " and "
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:193
+msgid ", "
+msgstr ""
+
+#: ../shell/controlpanel/control.py:213
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:267
+msgid "Error in specified color modifiers."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:270
+msgid "Error in specified colors."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:307
+msgid "off"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:309
+msgid "on"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:310
+msgid "State is unknown."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:332
+msgid "Error in specified radio argument use on/off."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:336
+msgid "Permission denied. You need to be root to run this method."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:366
+msgid "Error in reading timezone"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:397
+#, python-format
+msgid "Error copying timezone (from %s): %s"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:402
+#, python-format
+msgid "Changing permission of timezone: %s"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:412
+msgid "Error timezone does not exist."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:417 ../shell/controlpanel/control.py:436
+#, python-format
+msgid "Could not access %s. Create standard settings."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:463
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:473
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:105
+msgid "Connected to a School Mesh Portal"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:107
+msgid "Looking for a School Mesh Portal..."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:110
+msgid "Connected to an XO Mesh Portal"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:112
+msgid "Looking for an XO Mesh Portal..."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:115
+msgid "Connected to a Simple Mesh"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:117
+msgid "Starting a Simple Mesh"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:124
+msgid "Unknown Mesh"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:175 ../shell/view/home/HomeBox.py:216
+msgid "About this XO"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:222
+msgid "Not available"
+msgstr ""
diff --git a/shell/po/bg.po b/shell/po/bg.po
new file mode 100644
index 0000000..6f1700a
--- /dev/null
+++ b/shell/po/bg.po
@@ -0,0 +1,454 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-02-09 00:30-0500\n"
+"PO-Revision-Date: 2008-03-01 13:14-0500\n"
+"Last-Translator: Alexander Todorov <atodorov@redhat.com>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Pootle 1.0.2\n"
+
+#: ../src/intro/intro.py:67
+msgid "Name:"
+msgstr "Име:"
+
+#: ../src/intro/intro.py:96
+msgid "Click to change color:"
+msgstr "Натиснете, за да смените цвета:"
+
+#: ../src/intro/intro.py:146
+msgid "Back"
+msgstr "Назад"
+
+#: ../src/intro/intro.py:160
+msgid "Done"
+msgstr "Затваряне"
+
+#: ../src/intro/intro.py:163
+msgid "Next"
+msgstr "Следващ"
+
+#: ../src/view/BuddyMenu.py:59
+msgid "Remove friend"
+msgstr "Изтриване на приятел"
+
+#: ../src/view/BuddyMenu.py:62
+msgid "Make friend"
+msgstr "Създаване на приятел"
+
+#: ../src/view/BuddyMenu.py:84
+#, python-format
+msgid "Invite to %s"
+msgstr "Покани в %s"
+
+#: ../src/view/clipboardmenu.py:58
+msgid "Remove"
+msgstr "Премахване"
+
+#: ../src/view/clipboardmenu.py:63
+msgid "Open"
+msgstr "Отваряне"
+
+#. self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+#. TODO: Implement stopping downloads
+#. self._stop_item.connect('activate', self._stop_item_activate_cb)
+#. self.append_menu_item(self._stop_item)
+#: ../src/view/clipboardmenu.py:73
+msgid "Add to journal"
+msgstr "Добави към дневника"
+
+#: ../src/view/clipboardmenu.py:213
+#, python-format
+msgid "Clipboard object: %s."
+msgstr "Обект в паметта: %s."
+
+#: ../src/hardware/keydialog.py:149
+msgid "Key Type:"
+msgstr "Тип ключ:"
+
+#: ../src/hardware/keydialog.py:169
+msgid "Authentication Type:"
+msgstr "Тип удостоверяване:"
+
+#: ../src/hardware/keydialog.py:250
+msgid "Encryption Type:"
+msgstr "Вид криптиране:"
+
+#: ../src/view/home/activitiesdonut.py:90
+msgid "Starting..."
+msgstr "Стартиране..."
+
+#: ../src/view/home/activitiesdonut.py:104 ../src/view/home/MeshBox.py:295
+msgid "Resume"
+msgstr "Продължаване"
+
+#: ../src/view/home/activitiesdonut.py:111
+msgid "Stop"
+msgstr "Стоп"
+
+#: ../src/view/Shell.py:285
+msgid "Screenshot"
+msgstr "Снимка на екрана"
+
+#: ../src/view/home/HomeBox.py:159
+msgid "Reboot"
+msgstr "Рестартиране"
+
+#: ../src/view/home/HomeBox.py:164
+msgid "Shutdown"
+msgstr "Изключване"
+
+#: ../src/view/home/HomeBox.py:170
+msgid "Register"
+msgstr "Регистриране"
+
+#. Only show disconnect when there's a mesh device, because mesh takes
+#. priority over the normal wireless device. NM doesn't have a "disconnect"
+#. method for a device either (for various reasons) so this doesn't
+#. have a good mapping
+#: ../src/view/home/MeshBox.py:90 ../src/view/home/MeshBox.py:197
+#: ../src/view/devices/network/wireless.py:113
+#: ../src/view/devices/network/mesh.py:83
+msgid "Disconnect..."
+msgstr "Отделяне..."
+
+#: ../src/view/home/MeshBox.py:195 ../src/view/devices/network/mesh.py:37
+#: ../src/view/devices/network/mesh.py:62
+#: ../src/view/devices/network/mesh.py:66
+msgid "Mesh Network"
+msgstr "Mesh мрежа"
+
+#: ../src/view/home/MeshBox.py:300
+msgid "Join"
+msgstr "Присъединяване"
+
+#: ../src/view/devices/battery.py:38
+msgid "My Battery life"
+msgstr "Статус на батерията"
+
+#: ../src/view/devices/battery.py:94
+msgid "Battery charging"
+msgstr "Батерията се зарежда"
+
+#: ../src/view/devices/battery.py:96
+msgid "Battery discharging"
+msgstr "Батерията се разрежда"
+
+#: ../src/view/devices/battery.py:98
+msgid "Battery fully charged"
+msgstr "Батерията е напълно заредена"
+
+#: ../src/view/devices/network/wireless.py:61
+msgid "Disconnected"
+msgstr "Без свързаност"
+
+#: ../src/view/devices/network/wireless.py:131
+msgid "Channel"
+msgstr "Канал"
+
+# взето от руския превод
+#: ../src/view/frame/zoomtoolbar.py:42
+msgid "Neighborhood"
+msgstr "Съседи"
+
+#: ../src/view/frame/zoomtoolbar.py:54
+msgid "Group"
+msgstr "Група"
+
+#: ../src/view/frame/zoomtoolbar.py:66
+msgid "Home"
+msgstr "Начало"
+
+#: ../src/view/frame/zoomtoolbar.py:78
+msgid "Activity"
+msgstr "Дейност"
+
+#: ../lib/sugar/activity/activity.py:115
+msgid "Share with:"
+msgstr "Сподели с:"
+
+#: ../lib/sugar/activity/activity.py:117
+msgid "Private"
+msgstr "Личен"
+
+#: ../lib/sugar/activity/activity.py:118
+msgid "My Neighborhood"
+msgstr "Моите съседи"
+
+#: ../lib/sugar/activity/activity.py:126
+msgid "Keep"
+msgstr "Запазване"
+
+#: ../lib/sugar/activity/activity.py:245
+msgid "Undo"
+msgstr "Отмяна"
+
+#: ../lib/sugar/activity/activity.py:250
+msgid "Redo"
+msgstr "Възстановяване"
+
+#: ../lib/sugar/activity/activity.py:260
+msgid "Copy"
+msgstr "Копиране"
+
+#: ../lib/sugar/activity/activity.py:265
+msgid "Paste"
+msgstr "Поставяне"
+
+#: ../lib/sugar/activity/activity.py:454
+#, python-format
+msgid "%s Activity"
+msgstr "%s занятие"
+
+#: ../lib/sugar/activity/activity.py:824
+msgid "Keep error"
+msgstr "Грешка при запис"
+
+#: ../lib/sugar/activity/activity.py:825
+msgid "Keep error: all changes will be lost"
+msgstr "Грешка при запис: всички промени ще бъдат изгубени"
+
+#: ../lib/sugar/activity/activity.py:828
+msgid "Don't stop"
+msgstr "Без спиране"
+
+#: ../lib/sugar/activity/activity.py:831
+msgid "Stop anyway"
+msgstr "Спиране винаги"
+
+#: ../lib/sugar/graphics/alert.py:164 ../lib/sugar/graphics/alert.py:206
+msgid "Cancel"
+msgstr "Отказ"
+
+#: ../lib/sugar/graphics/alert.py:168
+msgid "Ok"
+msgstr "Ок"
+
+#: ../lib/sugar/graphics/alert.py:216
+msgid "Continue"
+msgstr "Продължение"
+
+#: ../lib/sugar/graphics/alert.py:244
+msgid "OK"
+msgstr "Добре"
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d year"
+msgstr "%d година"
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d years"
+msgstr "%d години"
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d month"
+msgstr "%d месец"
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d months"
+msgstr "%d месеца"
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d week"
+msgstr "%d седмица"
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d weeks"
+msgstr "%d седмици"
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d day"
+msgstr "%d ден"
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d days"
+msgstr "%d дни"
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hour"
+msgstr "%d час"
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hours"
+msgstr "%d часа"
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minute"
+msgstr "%d минута"
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minutes"
+msgstr "%d минути"
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d second"
+msgstr "%d секунда"
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d seconds"
+msgstr "%d секунди"
+
+#: ../lib/sugar/graphics/objectchooser.py:191
+msgid " and "
+msgstr " и "
+
+#: ../lib/sugar/graphics/objectchooser.py:193
+msgid ", "
+msgstr ", "
+
+#: ../src/controlpanel/control.py:219
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr "Моля, рестартирайте графичната среда, за да влязат в сила промените.\n"
+
+#: ../src/controlpanel/control.py:273
+msgid "Error in specified color modifiers."
+msgstr "Грешка в зададените цветови модификатори."
+
+#: ../src/controlpanel/control.py:276
+msgid "Error in specified colors."
+msgstr "Грешка в зададените цветове."
+
+#: ../src/controlpanel/control.py:312
+msgid "off"
+msgstr "изключен"
+
+#: ../src/controlpanel/control.py:314
+msgid "on"
+msgstr "включен"
+
+#: ../src/controlpanel/control.py:316
+msgid "State is unknown."
+msgstr "Непознато състояние."
+
+#: ../src/controlpanel/control.py:336
+msgid "Error in specified radio argument use on/off."
+msgstr "Грешка в зададения аргумент, използвайте вкл/изкл."
+
+#: ../src/controlpanel/control.py:340
+msgid "Permission denied. You need to be root to run this method."
+msgstr ""
+"Отказано е позволение. Трябва да сте администратор, за да изпълните този "
+"метод."
+
+#: ../src/controlpanel/control.py:370
+msgid "Error in reading timezone"
+msgstr "Грешка при прочитане на времева зона"
+
+#: ../src/controlpanel/control.py:401
+#, python-format
+msgid "Error copying timezone (from %s): %s"
+msgstr "Грешка при копиране на времева зона (от %s): %s"
+
+#: ../src/controlpanel/control.py:406
+#, python-format
+msgid "Changing permission of timezone: %s"
+msgstr "Замяна на позволенията за времева зона: %s"
+
+#: ../src/controlpanel/control.py:416
+msgid "Error timezone does not exist."
+msgstr "Грешка, времевата зона не съществува."
+
+#: ../src/controlpanel/control.py:421 ../src/controlpanel/control.py:440
+#, python-format
+msgid "Could not access %s. Create standard settings."
+msgstr "Отказан е достъп до %s. Моля, създайте стандартни настойки."
+
+#: ../src/controlpanel/control.py:467
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr "Не може да бъде определен езикът с код=%s."
+
+#: ../src/controlpanel/control.py:477
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr "Съжалявам, но не говоря '%s'."
+
+#: ../src/view/devices/network/mesh.py:105
+msgid "Connected to a School Mesh Portal"
+msgstr "Свързан към училищният Mash портал"
+
+#: ../src/view/devices/network/mesh.py:107
+msgid "Looking for a School Mesh Portal..."
+msgstr "Търсене на училищем Mesh портал..."
+
+#: ../src/view/devices/network/mesh.py:110
+msgid "Connected to an XO Mesh Portal"
+msgstr "Свързан към Mesh мрежа от лаптопи XO"
+
+#: ../src/view/devices/network/mesh.py:112
+msgid "Looking for an XO Mesh Portal..."
+msgstr "Търсене на Mesh мрежа от лаптопи XO..."
+
+#: ../src/view/devices/network/mesh.py:115
+msgid "Connected to a Simple Mesh"
+msgstr "Свързан към обикновенна Mesh мрежа"
+
+#: ../src/view/devices/network/mesh.py:117
+msgid "Starting a Simple Mesh"
+msgstr "Стартиране на обикновенна Mesh мрежа"
+
+#: ../src/view/devices/network/mesh.py:124
+msgid "Unknown Mesh"
+msgstr "Непозната Mesh мрежа"
+
+#: ../src/view/home/HomeBox.py:175 ../src/view/home/HomeBox.py:216
+msgid "About this XO"
+msgstr "Относно този лаптоп"
+
+#: ../src/view/home/HomeBox.py:222
+msgid "Not available"
+msgstr "Не е наличен"
+
+#: ../src/controlpanel/cmd.py:27
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" "
+msgstr ""
+"Използване: sugar-control-panel [ опции ] ключ [ аргументи ... ] \n"
+" Управление на работната среда. \n"
+" Опции: \n"
+" -h показване на помоща \n"
+" -l списък на всички опции \n"
+" -h ключ показване на информация за ключа \n"
+" -g ключ прочитане на текущата стойност на ключа \n"
+" -s ключ задаване на нова стойност на ключа \n"
+" "
+
+#: ../src/controlpanel/cmd.py:55 ../src/controlpanel/cmd.py:67
+#: ../src/controlpanel/cmd.py:74
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr "sugar-control-panel: ключ=%s - непозволена опция"
+
+#: ../src/controlpanel/cmd.py:80
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr "sugar-control-panel: %s"
diff --git a/shell/po/bi.po b/shell/po/bi.po
new file mode 100644
index 0000000..63d4e34
--- /dev/null
+++ b/shell/po/bi.po
@@ -0,0 +1,764 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-09-25 00:30-0400\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/intro/window.py:93 ../src/controlpanel/aboutme/view.py:100
+msgid "Name:"
+msgstr ""
+
+#: ../src/intro/window.py:125
+msgid "Click to change color:"
+msgstr ""
+
+#: ../src/intro/window.py:175 ../src/journal/detailview.py:119
+msgid "Back"
+msgstr ""
+
+#: ../src/intro/window.py:189 ../src/controlpanel/toolbar.py:61
+msgid "Done"
+msgstr ""
+
+#: ../src/intro/window.py:192
+msgid "Next"
+msgstr ""
+
+#: ../src/view/BuddyMenu.py:60
+msgid "Remove friend"
+msgstr ""
+
+#: ../src/view/BuddyMenu.py:63
+msgid "Make friend"
+msgstr ""
+
+#: ../src/view/BuddyMenu.py:92
+#, python-format
+msgid "Invite to %s"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:51
+msgid "Remove"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:56 ../src/view/clipboardmenu.py:78
+msgid "Open"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:61 ../src/view/home/HomeBox.py:84
+msgid "Keep"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:83
+msgid "Open with"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:228
+#, python-format
+msgid "%s clipping"
+msgstr ""
+
+#: ../src/hardware/keydialog.py:150
+msgid "Key Type:"
+msgstr ""
+
+#: ../src/hardware/keydialog.py:170
+msgid "Authentication Type:"
+msgstr ""
+
+#: ../src/hardware/keydialog.py:251
+msgid "Encryption Type:"
+msgstr ""
+
+#: ../src/hardware/schoolserver.py:17
+msgid "Cannot obtain data needed for registration."
+msgstr ""
+
+#: ../src/hardware/schoolserver.py:31
+msgid "Cannot connect to the server."
+msgstr ""
+
+#: ../src/hardware/schoolserver.py:36
+msgid "The server could not complete the request."
+msgstr ""
+
+#: ../src/view/Shell.py:251
+msgid "Screenshot"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:78
+msgid "Confirm erase"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:80
+#, python-format
+msgid "Confirm erase: Do you want to permanently erase %s?"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:87 ../src/view/palettes.py:120
+#: ../src/journal/journaltoolbox.py:335 ../src/journal/palettes.py:75
+msgid "Erase"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:117
+msgid "Software Update"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:118
+msgid "Update your activities to ensure compatibility with your new software"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:122 ../src/controlpanel/toolbar.py:115
+msgid "Cancel"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:124 ../src/controlpanel/gui.py:273
+msgid "Later"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:127
+msgid "Check now"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:261
+msgid "List view"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:262
+msgid "<Ctrl>2"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:320
+msgid "Favorites view"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:321
+msgid "<Ctrl>1"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:97
+msgid "Connect"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:106
+msgid "Disconnect"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:118
+msgid "Disconnecting..."
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:159
+msgid "Connecting..."
+msgstr ""
+
+#. TODO: show the channel number
+#: ../src/view/home/MeshBox.py:166
+msgid "Connected"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:218 ../src/view/devices/network/mesh.py:41
+#: ../src/view/devices/network/mesh.py:68
+#: ../src/view/devices/network/mesh.py:72
+msgid "Mesh Network"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:221 ../src/view/devices/network/wireless.py:125
+#: ../src/view/devices/network/mesh.py:89
+msgid "Disconnect..."
+msgstr ""
+
+#. TRANS: Action label for resuming an activity.
+#: ../src/view/home/MeshBox.py:309 ../src/view/palettes.py:61
+#: ../src/journal/journaltoolbox.py:399 ../src/journal/palettes.py:57
+msgid "Resume"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:314 ../src/view/frame/activitiestray.py:206
+msgid "Join"
+msgstr ""
+
+#: ../src/view/devices/battery.py:45
+msgid "My Battery"
+msgstr ""
+
+#: ../src/view/devices/battery.py:114
+msgid "Charging"
+msgstr ""
+
+#: ../src/view/devices/battery.py:117
+msgid "Very little power remaining"
+msgstr ""
+
+#: ../src/view/devices/battery.py:123
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr ""
+
+#: ../src/view/devices/battery.py:127
+msgid "Charged"
+msgstr ""
+
+#: ../src/view/devices/speaker.py:44
+msgid "My Speakers"
+msgstr ""
+
+#: ../src/view/devices/speaker.py:125
+msgid "Unmute"
+msgstr ""
+
+#: ../src/view/devices/speaker.py:128
+msgid "Mute"
+msgstr ""
+
+#: ../src/view/devices/network/wireless.py:67
+msgid "Disconnected"
+msgstr ""
+
+#: ../src/view/devices/network/wireless.py:143
+msgid "Channel"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:37
+msgid "Neighborhood"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:39
+msgid "Group"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:41
+msgid "Home"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:43
+msgid "Activity"
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:26
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:28
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:29
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr ""
+
+#. TRANS: Translators, there's a empty line at the end of this string,
+#. which must appear in the translated string (msgstr) as well.
+#: ../src/controlpanel/cmd.py:35
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" -c key clear the current value for the key \n"
+" "
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:48
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr ""
+
+#: ../src/controlpanel/toolbar.py:121 ../src/view/home/favoritesview.py:305
+msgid "Ok"
+msgstr ""
+
+#: ../src/controlpanel/sectionview.py:42 ../src/controlpanel/gui.py:265
+msgid "Changes require restart"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:264
+msgid "Warning"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:268
+msgid "Cancel changes"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:277
+msgid "Restart now"
+msgstr ""
+
+#: ../src/controlpanel/aboutme/model.py:44
+msgid "You must enter a name."
+msgstr ""
+
+#: ../src/controlpanel/aboutme/model.py:69
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr ""
+
+#: ../src/controlpanel/aboutme/model.py:72
+#, python-format
+msgid "stroke: %s"
+msgstr ""
+
+#: ../src/controlpanel/aboutme/model.py:74
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr ""
+
+#: ../src/controlpanel/aboutme/model.py:76
+#, python-format
+msgid "fill: %s"
+msgstr ""
+
+#: ../src/controlpanel/aboutme/model.py:87
+msgid "Error in specified color modifiers."
+msgstr ""
+
+#: ../src/controlpanel/aboutme/model.py:90
+msgid "Error in specified colors."
+msgstr ""
+
+#: ../src/controlpanel/aboutme/view.py:32
+#: ../src/controlpanel/aboutme/__init__.py:22
+msgid "About Me"
+msgstr ""
+
+#: ../src/controlpanel/aboutme/view.py:134
+msgid "Click to change your color:"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/model.py:24
+msgid "Not available"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:55
+msgid "Identity"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:64
+msgid "Serial Number:"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:87
+msgid "Software"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:96
+msgid "Build:"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:111
+msgid "Sugar:"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:126
+msgid "Firmware:"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:148
+msgid "Copyright and License"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:156
+msgid ""
+"© 2008 One Laptop per Child Association Inc; Red Hat Inc; and Contributors."
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:163
+msgid ""
+"Sugar is the graphical user interface that you are looking at. Sugar is free "
+"software, covered by the GNU General Public License, and you are welcome to "
+"change it and/or distribute copies of it under certain conditions described "
+"therein."
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:175
+msgid "Full license:"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/__init__.py:21
+msgid "About my XO"
+msgstr ""
+
+#: ../src/controlpanel/datetime/model.py:89
+msgid "Error timezone does not exist."
+msgstr ""
+
+#: ../src/controlpanel/datetime/view.py:68
+msgid "Timezone"
+msgstr ""
+
+#: ../src/controlpanel/datetime/__init__.py:21
+msgid "Date & Time"
+msgstr ""
+
+#: ../src/controlpanel/frame/model.py:38 ../src/controlpanel/frame/model.py:60
+msgid "Value must be an integer."
+msgstr ""
+
+#: ../src/controlpanel/frame/view.py:26
+msgid "never"
+msgstr ""
+
+#: ../src/controlpanel/frame/view.py:27
+msgid "instantaneous"
+msgstr ""
+
+#: ../src/controlpanel/frame/view.py:28
+#, python-format
+msgid "%s seconds"
+msgstr ""
+
+#: ../src/controlpanel/frame/view.py:52
+msgid "Activation Delay"
+msgstr ""
+
+#: ../src/controlpanel/frame/view.py:76
+msgid "Corner"
+msgstr ""
+
+#: ../src/controlpanel/frame/view.py:111
+msgid "Edge"
+msgstr ""
+
+#: ../src/controlpanel/frame/__init__.py:21
+msgid "Frame"
+msgstr ""
+
+#: ../src/controlpanel/language/model.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr ""
+
+#: ../src/controlpanel/language/model.py:114
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr ""
+
+#: ../src/controlpanel/language/model.py:131
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr ""
+
+#: ../src/controlpanel/language/view.py:70
+#: ../src/controlpanel/language/__init__.py:21
+msgid "Language"
+msgstr ""
+
+#: ../src/controlpanel/network/model.py:62
+msgid "State is unknown."
+msgstr ""
+
+#: ../src/controlpanel/network/model.py:82
+msgid "Error in specified radio argument use on/off."
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:28
+#: ../src/controlpanel/network/__init__.py:21
+msgid "Network"
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:54
+msgid "Wireless"
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:62
+msgid "Turn of the wireless radio to save battery life"
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:75
+msgid "Radio"
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:91
+msgid "Discard network history if you have trouble connecting to the network"
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:100
+msgid "Discard network history"
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:113
+msgid "Mesh"
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:122
+msgid "Server:"
+msgstr ""
+
+#: ../src/controlpanel/power/model.py:55
+msgid "Error in automatic pm argument, use on/off."
+msgstr ""
+
+#: ../src/controlpanel/power/model.py:84
+msgid "Error in extreme pm argument, use on/off."
+msgstr ""
+
+#: ../src/controlpanel/power/view.py:47
+msgid "Power management"
+msgstr ""
+
+#: ../src/controlpanel/power/view.py:57
+msgid "Automatic power management (increases battery life)"
+msgstr ""
+
+#: ../src/controlpanel/power/view.py:85
+msgid ""
+"Extreme power management (disableswireless radio, increases battery life)"
+msgstr ""
+
+#: ../src/controlpanel/power/__init__.py:21
+msgid "Power"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:111
+msgid "Connected to a School Mesh Portal"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:113
+msgid "Looking for a School Mesh Portal..."
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:116
+msgid "Connected to an XO Mesh Portal"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:118
+msgid "Looking for an XO Mesh Portal..."
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:121
+msgid "Connected to a Simple Mesh"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:123
+msgid "Starting a Simple Mesh"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:130
+msgid "Unknown Mesh"
+msgstr ""
+
+#: ../src/view/frame/activitiestray.py:211
+msgid "Decline"
+msgstr ""
+
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/view/home/favoriteslayout.py:107
+msgid "Freeform"
+msgstr ""
+
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/view/home/favoriteslayout.py:189
+msgid "Ring"
+msgstr ""
+
+#. TRANS: label for the spiral layout in the favorites view
+#: ../src/view/home/favoriteslayout.py:334
+msgid "Spiral"
+msgstr ""
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/view/home/favoriteslayout.py:401
+msgid "Box"
+msgstr ""
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/view/home/favoriteslayout.py:442
+msgid "Triangle"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:295
+msgid "Registration Failed"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:296
+#, python-format
+msgid "%s"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:298
+msgid "Registration Successful"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:299
+msgid "You are now registered with your school server."
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:420
+msgid "Settings"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:425
+msgid "Restart"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:430
+msgid "Shutdown"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:436
+msgid "Register"
+msgstr ""
+
+#: ../src/view/palettes.py:42
+msgid "Starting..."
+msgstr ""
+
+#: ../src/view/palettes.py:72
+msgid "Stop"
+msgstr ""
+
+#. TRANS: Action label for starting an entry.
+#: ../src/view/palettes.py:104 ../src/journal/journaltoolbox.py:402
+#: ../src/journal/palettes.py:59
+msgid "Start"
+msgstr ""
+
+#: ../src/view/palettes.py:138
+msgid "Remove favorite"
+msgstr ""
+
+#: ../src/view/palettes.py:142
+msgid "Make favorite"
+msgstr ""
+
+#: ../src/view/palettes.py:191
+msgid "Show contents"
+msgstr ""
+
+#: ../src/view/palettes.py:215
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr ""
+
+#: ../src/journal/journaltoolbox.py:62
+msgid "Search"
+msgstr ""
+
+#: ../src/journal/journaltoolbox.py:119
+msgid "Anytime"
+msgstr ""
+
+#: ../src/journal/journaltoolbox.py:121
+msgid "Today"
+msgstr ""
+
+#: ../src/journal/journaltoolbox.py:123
+msgid "Since yesterday"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 7 days.
+#: ../src/journal/journaltoolbox.py:125
+msgid "Past week"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 30 days.
+#: ../src/journal/journaltoolbox.py:127
+msgid "Past month"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 356 days.
+#: ../src/journal/journaltoolbox.py:129
+msgid "Past year"
+msgstr ""
+
+#: ../src/journal/journaltoolbox.py:136
+msgid "Anyone"
+msgstr ""
+
+#: ../src/journal/journaltoolbox.py:138
+msgid "My friends"
+msgstr ""
+
+#: ../src/journal/journaltoolbox.py:139
+msgid "My class"
+msgstr ""
+
+#. TRANS: Item in a combo box that filters by entry type.
+#: ../src/journal/journaltoolbox.py:255
+msgid "Anything"
+msgstr ""
+
+#. TODO: Add "Start with" menu item
+#: ../src/journal/journaltoolbox.py:325 ../src/journal/palettes.py:67
+msgid "Copy"
+msgstr ""
+
+#: ../src/journal/collapsedentry.py:248 ../src/journal/expandedentry.py:176
+#: ../src/journal/palettes.py:51
+msgid "Untitled"
+msgstr ""
+
+#: ../src/journal/journalactivity.py:119 ../src/journal/volumesmanager.py:57
+msgid "Journal"
+msgstr ""
+
+#: ../src/journal/expandedentry.py:222
+msgid "No preview"
+msgstr ""
+
+#: ../src/journal/expandedentry.py:241
+msgid "Participants:"
+msgstr ""
+
+#: ../src/journal/expandedentry.py:266
+msgid "Description:"
+msgstr ""
+
+#: ../src/journal/expandedentry.py:292
+msgid "Tags:"
+msgstr ""
+
+#: ../src/journal/objectchooser.py:134
+msgid "Choose an object"
+msgstr ""
+
+#: ../src/journal/objectchooser.py:139
+msgid "Close"
+msgstr ""
+
+#: ../src/journal/volumestoolbar.py:93
+msgid "Unmount"
+msgstr ""
+
+#: ../src/journal/misc.py:95
+msgid "No date"
+msgstr ""
+
+#: ../src/journal/listview.py:39
+msgid "Your Journal is empty"
+msgstr ""
+
+#: ../src/journal/listview.py:40
+msgid "No matching entries "
+msgstr ""
+
+#: ../src/journal/modalalert.py:59
+msgid "Your Journal is full"
+msgstr ""
+
+#: ../src/journal/modalalert.py:63
+msgid "Please delete some old Journal entries to make space for new ones."
+msgstr ""
+
+#: ../src/journal/modalalert.py:75
+msgid "Show Journal"
+msgstr ""
diff --git a/shell/po/bn.po b/shell/po/bn.po
new file mode 100644
index 0000000..45ede9f
--- /dev/null
+++ b/shell/po/bn.po
@@ -0,0 +1,419 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: Update 1\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-01-18 18:19+0000\n"
+"PO-Revision-Date: 2007-12-23 10:33+0000\n"
+"Last-Translator: Khandakar Mujahidul Islam <suzan229@gmail.com>\n"
+"Language-Team: Bengali <core@bengalinux.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Pootle 1.0.2\n"
+
+#: ../shell/intro/intro.py:67
+msgid "Name:"
+msgstr "নাম:"
+
+#: ../shell/intro/intro.py:96
+msgid "Click to change color:"
+msgstr "রং পরিবর্তন করতে ক্লিক:"
+
+#: ../shell/intro/intro.py:146
+msgid "Back"
+msgstr "পেছনে"
+
+#: ../shell/intro/intro.py:160
+msgid "Done"
+msgstr "করা হয়েছে"
+
+#: ../shell/intro/intro.py:163
+msgid "Next"
+msgstr "পরবর্তী"
+
+#: ../shell/view/BuddyMenu.py:59
+msgid "Remove friend"
+msgstr "বন্ধু মোছো"
+
+#: ../shell/view/BuddyMenu.py:62
+msgid "Make friend"
+msgstr "বন্ধু বানাও"
+
+#: ../shell/view/BuddyMenu.py:84
+#, python-format
+msgid "Invite to %s"
+msgstr "%s কে নিমণ্ত্রণ"
+
+#: ../shell/view/clipboardmenu.py:58
+msgid "Remove"
+msgstr "মোছো"
+
+#: ../shell/view/clipboardmenu.py:63
+msgid "Open"
+msgstr "খোলো"
+
+#. self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+#. TODO: Implement stopping downloads
+#. self._stop_item.connect('activate', self._stop_item_activate_cb)
+#. self.append_menu_item(self._stop_item)
+#: ../shell/view/clipboardmenu.py:73
+msgid "Add to journal"
+msgstr "জার্নালে লেখ"
+
+#: ../shell/view/clipboardmenu.py:213
+#, python-format
+msgid "Clipboard object: %s."
+msgstr "ক্লীপবোর্ড বস্তু: %s।"
+
+#: ../shell/hardware/keydialog.py:149
+msgid "Key Type:"
+msgstr "কী ধরণ:"
+
+#: ../shell/hardware/keydialog.py:169
+msgid "Authentication Type:"
+msgstr "পরিচয় প্রমানের ধরণ:"
+
+#: ../shell/hardware/keydialog.py:250
+msgid "Encryption Type:"
+msgstr "এনক্রিপশন ধরণ:"
+
+#: ../shell/view/home/activitiesdonut.py:90
+msgid "Starting..."
+msgstr "শুরু হচ্ছে..."
+
+#: ../shell/view/home/activitiesdonut.py:104 ../shell/view/home/MeshBox.py:295
+msgid "Resume"
+msgstr "পুনরায় শুরু করুন"
+
+#: ../shell/view/home/activitiesdonut.py:111
+#: ../lib/sugar/activity/activity.py:132
+msgid "Stop"
+msgstr "থামাও"
+
+#: ../shell/view/Shell.py:285
+msgid "Screenshot"
+msgstr "স্ক্রীণশট"
+
+#: ../shell/view/home/HomeBox.py:159
+msgid "Reboot"
+msgstr "পুনরায় চালু"
+
+#: ../shell/view/home/HomeBox.py:164
+msgid "Shutdown"
+msgstr "বন্ধ করো"
+
+#: ../shell/view/home/HomeBox.py:170
+msgid "Register"
+msgstr "রেজিস্টার"
+
+#. Only show disconnect when there's a mesh device, because mesh takes
+#. priority over the normal wireless device. NM doesn't have a "disconnect"
+#. method for a device either (for various reasons) so this doesn't
+#. have a good mapping
+#: ../shell/view/home/MeshBox.py:90 ../shell/view/home/MeshBox.py:197
+#: ../shell/view/devices/network/wireless.py:113
+#: ../shell/view/devices/network/mesh.py:83
+msgid "Disconnect..."
+msgstr "সংযোগ বিচ্ছিন্ন..."
+
+#: ../shell/view/home/MeshBox.py:195 ../shell/view/devices/network/mesh.py:37
+#: ../shell/view/devices/network/mesh.py:62
+#: ../shell/view/devices/network/mesh.py:66
+msgid "Mesh Network"
+msgstr "মেশ নেটওয়ার্ক"
+
+#: ../shell/view/home/MeshBox.py:300
+msgid "Join"
+msgstr "যোগ দাও"
+
+#: ../shell/view/devices/battery.py:38
+msgid "My Battery life"
+msgstr "আমার ব্যাটারীর জীবনসীমা"
+
+#: ../shell/view/devices/battery.py:94
+msgid "Battery charging"
+msgstr "ব্যাটারী চার্জ হচ্ছে"
+
+#: ../shell/view/devices/battery.py:96
+msgid "Battery discharging"
+msgstr "ব্যাটারী চার্জ কমে যাচ্ছে"
+
+#: ../shell/view/devices/battery.py:98
+msgid "Battery fully charged"
+msgstr "ব্যাটারী পুরোপুরি চার্জ হয়েছে"
+
+#: ../shell/view/devices/network/wireless.py:61
+msgid "Disconnected"
+msgstr "সংযোগ বিচ্ছিন্ন"
+
+#: ../shell/view/devices/network/wireless.py:131
+msgid "Channel"
+msgstr "চ্যানেল"
+
+#: ../shell/view/frame/zoomtoolbar.py:42
+msgid "Neighborhood"
+msgstr "ছোটবেলা"
+
+#: ../shell/view/frame/zoomtoolbar.py:54
+msgid "Group"
+msgstr "দল"
+
+#: ../shell/view/frame/zoomtoolbar.py:66
+msgid "Home"
+msgstr "বাড়ি"
+
+#: ../shell/view/frame/zoomtoolbar.py:78
+msgid "Activity"
+msgstr "সক্রিয়তা"
+
+#: ../lib/sugar/activity/activity.py:115
+msgid "Share with:"
+msgstr "ভাগাভাগি করো:"
+
+#: ../lib/sugar/activity/activity.py:117
+msgid "Private"
+msgstr "ব্যাক্তিগত"
+
+#: ../lib/sugar/activity/activity.py:118
+msgid "My Neighborhood"
+msgstr "আমার ছেলেবেলা"
+
+#: ../lib/sugar/activity/activity.py:126
+msgid "Keep"
+msgstr "রাখো"
+
+#: ../lib/sugar/activity/activity.py:245
+msgid "Undo"
+msgstr "বাতিল করো"
+
+#: ../lib/sugar/activity/activity.py:250
+msgid "Redo"
+msgstr "আবার করো"
+
+#: ../lib/sugar/activity/activity.py:260
+msgid "Copy"
+msgstr "কপি"
+
+#: ../lib/sugar/activity/activity.py:265
+msgid "Paste"
+msgstr "সাঁটো"
+
+#: ../lib/sugar/activity/activity.py:454
+#, python-format
+msgid "%s Activity"
+msgstr "%s সক্রিয়তা"
+
+#: ../lib/sugar/activity/activity.py:824
+msgid "Keep error"
+msgstr "ত্রুটি রেখে দাও"
+
+#: ../lib/sugar/activity/activity.py:825
+msgid "Keep error: all changes will be lost"
+msgstr "ত্রুটি রেখে দাও: সব পরিবর্তন হারিয়ে যাবে"
+
+#: ../lib/sugar/activity/activity.py:828
+msgid "Don't stop"
+msgstr "থামিও না"
+
+#: ../lib/sugar/activity/activity.py:831
+msgid "Stop anyway"
+msgstr "যে কোন ভাবে থামো"
+
+#: ../lib/sugar/graphics/alert.py:164 ../lib/sugar/graphics/alert.py:206
+msgid "Cancel"
+msgstr "বাতিল"
+
+#: ../lib/sugar/graphics/alert.py:168
+msgid "Ok"
+msgstr "ঠিক আছে"
+
+#: ../lib/sugar/graphics/alert.py:216
+msgid "Continue"
+msgstr "এগিয়ে যাও"
+
+#: ../lib/sugar/graphics/alert.py:244
+msgid "OK"
+msgstr "ঠিক আছে"
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d year"
+msgstr "%d বছর"
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d years"
+msgstr "%d বছর"
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d month"
+msgstr "%d মাস"
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d months"
+msgstr "%d মাস"
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d week"
+msgstr "%d সপ্তাহ"
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d weeks"
+msgstr "%d সপ্তাহ"
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d day"
+msgstr "%d দিন"
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d days"
+msgstr "%d দিন"
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hour"
+msgstr "%d ঘন্টা"
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hours"
+msgstr "%d ঘন্টা"
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minute"
+msgstr "%d মিনিট"
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minutes"
+msgstr "%d মিনিট"
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d second"
+msgstr "%d সেকেন্ড"
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d seconds"
+msgstr "%d সেকেন্ড"
+
+#: ../lib/sugar/graphics/objectchooser.py:191
+msgid " and "
+msgstr " এবং "
+
+#: ../lib/sugar/graphics/objectchooser.py:193
+msgid ", "
+msgstr ", "
+
+#: ../shell/controlpanel/control.py:213
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr "তোমার পরিবর্তন কার্যকর করার জন্য সুগার পুনরায় চালু করতে হবে।\n"
+
+#: ../shell/controlpanel/control.py:267
+msgid "Error in specified color modifiers."
+msgstr "উল্লেখিত রং পরিবর্তকে ত্রুটি।"
+
+#: ../shell/controlpanel/control.py:270
+msgid "Error in specified colors."
+msgstr "উল্লেখিত রং এ ত্রুটি।"
+
+#: ../shell/controlpanel/control.py:307
+msgid "off"
+msgstr "off"
+
+#: ../shell/controlpanel/control.py:309
+msgid "on"
+msgstr "on"
+
+#: ../shell/controlpanel/control.py:310
+msgid "State is unknown."
+msgstr "অবস্থানটি অজানা।"
+
+#: ../shell/controlpanel/control.py:332
+msgid "Error in specified radio argument use on/off."
+msgstr "উল্লেখিত রেডিও প্রেরিত মান ব্যবহার on/off তে ত্রুটি।"
+
+#: ../shell/controlpanel/control.py:336
+msgid "Permission denied. You need to be root to run this method."
+msgstr "অনুমতি প্রত্যাখ্যাত। এই পন্থাটি চালাতে তোমাকে root হিসেবে কাজ করতে হবে।"
+
+#: ../shell/controlpanel/control.py:366
+msgid "Error in reading timezone"
+msgstr "সময় জোন পড়তে ত্রুটি"
+
+#: ../shell/controlpanel/control.py:397
+#, python-format
+msgid "Error copying timezone (from %s): %s"
+msgstr "সময় জোন (%s হতে) কপি করায় ত্রুটি: %s"
+
+#: ../shell/controlpanel/control.py:402
+#, python-format
+msgid "Changing permission of timezone: %s"
+msgstr "সময় জোনের অনুমতি পরিবর্তন: %s"
+
+#: ../shell/controlpanel/control.py:412
+msgid "Error timezone does not exist."
+msgstr "ত্রুটিযুক্ত সময় জোনের অস্তিত্ব নেই।"
+
+#: ../shell/controlpanel/control.py:417 ../shell/controlpanel/control.py:436
+#, python-format
+msgid "Could not access %s. Create standard settings."
+msgstr "%s এ প্রবেশ করতে পারে নি। প্রমাণ মানসমূহ তৈরি করো।"
+
+#: ../shell/controlpanel/control.py:463
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr "কোড=%s এর জন্য কোন ভাষা নির্ধারণ করা যায় নি।"
+
+#: ../shell/controlpanel/control.py:473
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr "দুঃখিত আমি '%s' বলতে পারি না।"
+
+#: ../shell/view/devices/network/mesh.py:105
+msgid "Connected to a School Mesh Portal"
+msgstr "একটি স্কুলের মেশ পোর্টালে সংযুক্ত"
+
+#: ../shell/view/devices/network/mesh.py:107
+msgid "Looking for a School Mesh Portal..."
+msgstr "একটি স্কুল মেশ পোর্টাল খুজছি..."
+
+#: ../shell/view/devices/network/mesh.py:110
+msgid "Connected to an XO Mesh Portal"
+msgstr "একটি জো(XO) মেশ পোর্টালে সংযুক্ত"
+
+#: ../shell/view/devices/network/mesh.py:112
+msgid "Looking for an XO Mesh Portal..."
+msgstr "একটি জো(XO) মেশ পোর্টাল খুজছি..."
+
+#: ../shell/view/devices/network/mesh.py:115
+msgid "Connected to a Simple Mesh"
+msgstr "একটি সাধারণ মেশ এ সংযুক্ত"
+
+#: ../shell/view/devices/network/mesh.py:117
+msgid "Starting a Simple Mesh"
+msgstr "একটি সাধারণ মেশ চালু করছি"
+
+#: ../shell/view/devices/network/mesh.py:124
+msgid "Unknown Mesh"
+msgstr "অজানা মেশ"
+
+#: ../shell/view/home/HomeBox.py:175 ../shell/view/home/HomeBox.py:216
+msgid "About this XO"
+msgstr "XO সম্বন্ধে"
+
+#: ../shell/view/home/HomeBox.py:222
+msgid "Not available"
+msgstr "পাওয়া যায় নি"
diff --git a/shell/po/bn_IN.po b/shell/po/bn_IN.po
new file mode 100644
index 0000000..45ede9f
--- /dev/null
+++ b/shell/po/bn_IN.po
@@ -0,0 +1,419 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: Update 1\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-01-18 18:19+0000\n"
+"PO-Revision-Date: 2007-12-23 10:33+0000\n"
+"Last-Translator: Khandakar Mujahidul Islam <suzan229@gmail.com>\n"
+"Language-Team: Bengali <core@bengalinux.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Pootle 1.0.2\n"
+
+#: ../shell/intro/intro.py:67
+msgid "Name:"
+msgstr "নাম:"
+
+#: ../shell/intro/intro.py:96
+msgid "Click to change color:"
+msgstr "রং পরিবর্তন করতে ক্লিক:"
+
+#: ../shell/intro/intro.py:146
+msgid "Back"
+msgstr "পেছনে"
+
+#: ../shell/intro/intro.py:160
+msgid "Done"
+msgstr "করা হয়েছে"
+
+#: ../shell/intro/intro.py:163
+msgid "Next"
+msgstr "পরবর্তী"
+
+#: ../shell/view/BuddyMenu.py:59
+msgid "Remove friend"
+msgstr "বন্ধু মোছো"
+
+#: ../shell/view/BuddyMenu.py:62
+msgid "Make friend"
+msgstr "বন্ধু বানাও"
+
+#: ../shell/view/BuddyMenu.py:84
+#, python-format
+msgid "Invite to %s"
+msgstr "%s কে নিমণ্ত্রণ"
+
+#: ../shell/view/clipboardmenu.py:58
+msgid "Remove"
+msgstr "মোছো"
+
+#: ../shell/view/clipboardmenu.py:63
+msgid "Open"
+msgstr "খোলো"
+
+#. self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+#. TODO: Implement stopping downloads
+#. self._stop_item.connect('activate', self._stop_item_activate_cb)
+#. self.append_menu_item(self._stop_item)
+#: ../shell/view/clipboardmenu.py:73
+msgid "Add to journal"
+msgstr "জার্নালে লেখ"
+
+#: ../shell/view/clipboardmenu.py:213
+#, python-format
+msgid "Clipboard object: %s."
+msgstr "ক্লীপবোর্ড বস্তু: %s।"
+
+#: ../shell/hardware/keydialog.py:149
+msgid "Key Type:"
+msgstr "কী ধরণ:"
+
+#: ../shell/hardware/keydialog.py:169
+msgid "Authentication Type:"
+msgstr "পরিচয় প্রমানের ধরণ:"
+
+#: ../shell/hardware/keydialog.py:250
+msgid "Encryption Type:"
+msgstr "এনক্রিপশন ধরণ:"
+
+#: ../shell/view/home/activitiesdonut.py:90
+msgid "Starting..."
+msgstr "শুরু হচ্ছে..."
+
+#: ../shell/view/home/activitiesdonut.py:104 ../shell/view/home/MeshBox.py:295
+msgid "Resume"
+msgstr "পুনরায় শুরু করুন"
+
+#: ../shell/view/home/activitiesdonut.py:111
+#: ../lib/sugar/activity/activity.py:132
+msgid "Stop"
+msgstr "থামাও"
+
+#: ../shell/view/Shell.py:285
+msgid "Screenshot"
+msgstr "স্ক্রীণশট"
+
+#: ../shell/view/home/HomeBox.py:159
+msgid "Reboot"
+msgstr "পুনরায় চালু"
+
+#: ../shell/view/home/HomeBox.py:164
+msgid "Shutdown"
+msgstr "বন্ধ করো"
+
+#: ../shell/view/home/HomeBox.py:170
+msgid "Register"
+msgstr "রেজিস্টার"
+
+#. Only show disconnect when there's a mesh device, because mesh takes
+#. priority over the normal wireless device. NM doesn't have a "disconnect"
+#. method for a device either (for various reasons) so this doesn't
+#. have a good mapping
+#: ../shell/view/home/MeshBox.py:90 ../shell/view/home/MeshBox.py:197
+#: ../shell/view/devices/network/wireless.py:113
+#: ../shell/view/devices/network/mesh.py:83
+msgid "Disconnect..."
+msgstr "সংযোগ বিচ্ছিন্ন..."
+
+#: ../shell/view/home/MeshBox.py:195 ../shell/view/devices/network/mesh.py:37
+#: ../shell/view/devices/network/mesh.py:62
+#: ../shell/view/devices/network/mesh.py:66
+msgid "Mesh Network"
+msgstr "মেশ নেটওয়ার্ক"
+
+#: ../shell/view/home/MeshBox.py:300
+msgid "Join"
+msgstr "যোগ দাও"
+
+#: ../shell/view/devices/battery.py:38
+msgid "My Battery life"
+msgstr "আমার ব্যাটারীর জীবনসীমা"
+
+#: ../shell/view/devices/battery.py:94
+msgid "Battery charging"
+msgstr "ব্যাটারী চার্জ হচ্ছে"
+
+#: ../shell/view/devices/battery.py:96
+msgid "Battery discharging"
+msgstr "ব্যাটারী চার্জ কমে যাচ্ছে"
+
+#: ../shell/view/devices/battery.py:98
+msgid "Battery fully charged"
+msgstr "ব্যাটারী পুরোপুরি চার্জ হয়েছে"
+
+#: ../shell/view/devices/network/wireless.py:61
+msgid "Disconnected"
+msgstr "সংযোগ বিচ্ছিন্ন"
+
+#: ../shell/view/devices/network/wireless.py:131
+msgid "Channel"
+msgstr "চ্যানেল"
+
+#: ../shell/view/frame/zoomtoolbar.py:42
+msgid "Neighborhood"
+msgstr "ছোটবেলা"
+
+#: ../shell/view/frame/zoomtoolbar.py:54
+msgid "Group"
+msgstr "দল"
+
+#: ../shell/view/frame/zoomtoolbar.py:66
+msgid "Home"
+msgstr "বাড়ি"
+
+#: ../shell/view/frame/zoomtoolbar.py:78
+msgid "Activity"
+msgstr "সক্রিয়তা"
+
+#: ../lib/sugar/activity/activity.py:115
+msgid "Share with:"
+msgstr "ভাগাভাগি করো:"
+
+#: ../lib/sugar/activity/activity.py:117
+msgid "Private"
+msgstr "ব্যাক্তিগত"
+
+#: ../lib/sugar/activity/activity.py:118
+msgid "My Neighborhood"
+msgstr "আমার ছেলেবেলা"
+
+#: ../lib/sugar/activity/activity.py:126
+msgid "Keep"
+msgstr "রাখো"
+
+#: ../lib/sugar/activity/activity.py:245
+msgid "Undo"
+msgstr "বাতিল করো"
+
+#: ../lib/sugar/activity/activity.py:250
+msgid "Redo"
+msgstr "আবার করো"
+
+#: ../lib/sugar/activity/activity.py:260
+msgid "Copy"
+msgstr "কপি"
+
+#: ../lib/sugar/activity/activity.py:265
+msgid "Paste"
+msgstr "সাঁটো"
+
+#: ../lib/sugar/activity/activity.py:454
+#, python-format
+msgid "%s Activity"
+msgstr "%s সক্রিয়তা"
+
+#: ../lib/sugar/activity/activity.py:824
+msgid "Keep error"
+msgstr "ত্রুটি রেখে দাও"
+
+#: ../lib/sugar/activity/activity.py:825
+msgid "Keep error: all changes will be lost"
+msgstr "ত্রুটি রেখে দাও: সব পরিবর্তন হারিয়ে যাবে"
+
+#: ../lib/sugar/activity/activity.py:828
+msgid "Don't stop"
+msgstr "থামিও না"
+
+#: ../lib/sugar/activity/activity.py:831
+msgid "Stop anyway"
+msgstr "যে কোন ভাবে থামো"
+
+#: ../lib/sugar/graphics/alert.py:164 ../lib/sugar/graphics/alert.py:206
+msgid "Cancel"
+msgstr "বাতিল"
+
+#: ../lib/sugar/graphics/alert.py:168
+msgid "Ok"
+msgstr "ঠিক আছে"
+
+#: ../lib/sugar/graphics/alert.py:216
+msgid "Continue"
+msgstr "এগিয়ে যাও"
+
+#: ../lib/sugar/graphics/alert.py:244
+msgid "OK"
+msgstr "ঠিক আছে"
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d year"
+msgstr "%d বছর"
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d years"
+msgstr "%d বছর"
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d month"
+msgstr "%d মাস"
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d months"
+msgstr "%d মাস"
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d week"
+msgstr "%d সপ্তাহ"
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d weeks"
+msgstr "%d সপ্তাহ"
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d day"
+msgstr "%d দিন"
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d days"
+msgstr "%d দিন"
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hour"
+msgstr "%d ঘন্টা"
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hours"
+msgstr "%d ঘন্টা"
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minute"
+msgstr "%d মিনিট"
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minutes"
+msgstr "%d মিনিট"
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d second"
+msgstr "%d সেকেন্ড"
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d seconds"
+msgstr "%d সেকেন্ড"
+
+#: ../lib/sugar/graphics/objectchooser.py:191
+msgid " and "
+msgstr " এবং "
+
+#: ../lib/sugar/graphics/objectchooser.py:193
+msgid ", "
+msgstr ", "
+
+#: ../shell/controlpanel/control.py:213
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr "তোমার পরিবর্তন কার্যকর করার জন্য সুগার পুনরায় চালু করতে হবে।\n"
+
+#: ../shell/controlpanel/control.py:267
+msgid "Error in specified color modifiers."
+msgstr "উল্লেখিত রং পরিবর্তকে ত্রুটি।"
+
+#: ../shell/controlpanel/control.py:270
+msgid "Error in specified colors."
+msgstr "উল্লেখিত রং এ ত্রুটি।"
+
+#: ../shell/controlpanel/control.py:307
+msgid "off"
+msgstr "off"
+
+#: ../shell/controlpanel/control.py:309
+msgid "on"
+msgstr "on"
+
+#: ../shell/controlpanel/control.py:310
+msgid "State is unknown."
+msgstr "অবস্থানটি অজানা।"
+
+#: ../shell/controlpanel/control.py:332
+msgid "Error in specified radio argument use on/off."
+msgstr "উল্লেখিত রেডিও প্রেরিত মান ব্যবহার on/off তে ত্রুটি।"
+
+#: ../shell/controlpanel/control.py:336
+msgid "Permission denied. You need to be root to run this method."
+msgstr "অনুমতি প্রত্যাখ্যাত। এই পন্থাটি চালাতে তোমাকে root হিসেবে কাজ করতে হবে।"
+
+#: ../shell/controlpanel/control.py:366
+msgid "Error in reading timezone"
+msgstr "সময় জোন পড়তে ত্রুটি"
+
+#: ../shell/controlpanel/control.py:397
+#, python-format
+msgid "Error copying timezone (from %s): %s"
+msgstr "সময় জোন (%s হতে) কপি করায় ত্রুটি: %s"
+
+#: ../shell/controlpanel/control.py:402
+#, python-format
+msgid "Changing permission of timezone: %s"
+msgstr "সময় জোনের অনুমতি পরিবর্তন: %s"
+
+#: ../shell/controlpanel/control.py:412
+msgid "Error timezone does not exist."
+msgstr "ত্রুটিযুক্ত সময় জোনের অস্তিত্ব নেই।"
+
+#: ../shell/controlpanel/control.py:417 ../shell/controlpanel/control.py:436
+#, python-format
+msgid "Could not access %s. Create standard settings."
+msgstr "%s এ প্রবেশ করতে পারে নি। প্রমাণ মানসমূহ তৈরি করো।"
+
+#: ../shell/controlpanel/control.py:463
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr "কোড=%s এর জন্য কোন ভাষা নির্ধারণ করা যায় নি।"
+
+#: ../shell/controlpanel/control.py:473
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr "দুঃখিত আমি '%s' বলতে পারি না।"
+
+#: ../shell/view/devices/network/mesh.py:105
+msgid "Connected to a School Mesh Portal"
+msgstr "একটি স্কুলের মেশ পোর্টালে সংযুক্ত"
+
+#: ../shell/view/devices/network/mesh.py:107
+msgid "Looking for a School Mesh Portal..."
+msgstr "একটি স্কুল মেশ পোর্টাল খুজছি..."
+
+#: ../shell/view/devices/network/mesh.py:110
+msgid "Connected to an XO Mesh Portal"
+msgstr "একটি জো(XO) মেশ পোর্টালে সংযুক্ত"
+
+#: ../shell/view/devices/network/mesh.py:112
+msgid "Looking for an XO Mesh Portal..."
+msgstr "একটি জো(XO) মেশ পোর্টাল খুজছি..."
+
+#: ../shell/view/devices/network/mesh.py:115
+msgid "Connected to a Simple Mesh"
+msgstr "একটি সাধারণ মেশ এ সংযুক্ত"
+
+#: ../shell/view/devices/network/mesh.py:117
+msgid "Starting a Simple Mesh"
+msgstr "একটি সাধারণ মেশ চালু করছি"
+
+#: ../shell/view/devices/network/mesh.py:124
+msgid "Unknown Mesh"
+msgstr "অজানা মেশ"
+
+#: ../shell/view/home/HomeBox.py:175 ../shell/view/home/HomeBox.py:216
+msgid "About this XO"
+msgstr "XO সম্বন্ধে"
+
+#: ../shell/view/home/HomeBox.py:222
+msgid "Not available"
+msgstr "পাওয়া যায় নি"
diff --git a/shell/po/ca.po b/shell/po/ca.po
new file mode 100644
index 0000000..a9ab315
--- /dev/null
+++ b/shell/po/ca.po
@@ -0,0 +1,438 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+# revisar
+#, python-format
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-01-18 18:19+0000\n"
+"PO-Revision-Date: 2008-01-14 17:19+0000\n"
+"Last-Translator: Jaume <jaume@nualart.com>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Pootle 1.0.2\n"
+
+#: ../shell/intro/intro.py:67
+msgid "Name:"
+msgstr "Nom:"
+
+#: ../shell/intro/intro.py:96
+msgid "Click to change color:"
+msgstr "Clic per a canviar de color:"
+
+#: ../shell/intro/intro.py:146
+msgid "Back"
+msgstr "Enrere"
+
+#: ../shell/intro/intro.py:160
+msgid "Done"
+msgstr "Fet"
+
+#: ../shell/intro/intro.py:163
+msgid "Next"
+msgstr "Següent"
+
+#: ../shell/view/BuddyMenu.py:59
+msgid "Remove friend"
+msgstr "Esborrar amic/ga"
+
+#: ../shell/view/BuddyMenu.py:62
+msgid "Make friend"
+msgstr "Fer amic/ga"
+
+#: ../shell/view/BuddyMenu.py:84
+#, python-format
+msgid "Invite to %s"
+msgstr "Convidar a %s"
+
+#: ../shell/view/clipboardmenu.py:58
+msgid "Remove"
+msgstr "Eliminar"
+
+#: ../shell/view/clipboardmenu.py:63
+msgid "Open"
+msgstr "Obrir"
+
+#. self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+#. TODO: Implement stopping downloads
+#. self._stop_item.connect('activate', self._stop_item_activate_cb)
+#. self.append_menu_item(self._stop_item)
+#: ../shell/view/clipboardmenu.py:73
+msgid "Add to journal"
+msgstr "Afegir al diari"
+
+#: ../shell/view/clipboardmenu.py:213
+#, python-format
+msgid "Clipboard object: %s."
+msgstr "Objecte del porta-retalls: %s"
+
+#: ../shell/hardware/keydialog.py:149
+msgid "Key Type:"
+msgstr "Tipus de clau:"
+
+#: ../shell/hardware/keydialog.py:169
+msgid "Authentication Type:"
+msgstr "Tipus d'autentificació:"
+
+#: ../shell/hardware/keydialog.py:250
+msgid "Encryption Type:"
+msgstr "Tipus d'encriptació:"
+
+#: ../shell/view/home/activitiesdonut.py:90
+msgid "Starting..."
+msgstr "Arrencant ..."
+
+#: ../shell/view/home/activitiesdonut.py:104 ../shell/view/home/MeshBox.py:295
+msgid "Resume"
+msgstr "Reprendre"
+
+#: ../shell/view/home/activitiesdonut.py:111
+#: ../lib/sugar/activity/activity.py:132
+msgid "Stop"
+msgstr "Aturar"
+
+#: ../shell/view/Shell.py:285
+msgid "Screenshot"
+msgstr "Captura de pantalla"
+
+#: ../shell/view/home/HomeBox.py:159
+msgid "Reboot"
+msgstr "Reiniciar"
+
+#: ../shell/view/home/HomeBox.py:164
+msgid "Shutdown"
+msgstr "Aturar"
+
+#: ../shell/view/home/HomeBox.py:170
+msgid "Register"
+msgstr "Registre"
+
+#. Only show disconnect when there's a mesh device, because mesh takes
+#. priority over the normal wireless device. NM doesn't have a "disconnect"
+#. method for a device either (for various reasons) so this doesn't
+#. have a good mapping
+#: ../shell/view/home/MeshBox.py:90 ../shell/view/home/MeshBox.py:197
+#: ../shell/view/devices/network/wireless.py:113
+#: ../shell/view/devices/network/mesh.py:83
+msgid "Disconnect..."
+msgstr "Desconnecta ..."
+
+# Aquí he agafat l'original "mesh", en comptes de fer-ne una traducció
+#: ../shell/view/home/MeshBox.py:195 ../shell/view/devices/network/mesh.py:37
+#: ../shell/view/devices/network/mesh.py:62
+#: ../shell/view/devices/network/mesh.py:66
+#: ../src/view/home/MeshBox.py:195
+#: ../src/view/devices/network/mesh.py:37
+#: ../src/view/devices/network/mesh.py:62
+#: ../src/view/devices/network/mesh.py:66
+#, fuzzy
+msgid "Mesh Network"
+msgstr "Xarxa Mesh"
+
+# mmm, potser més simple?
+#: ../shell/view/home/MeshBox.py:300
+msgid "Join"
+msgstr "Uneix-t'hi"
+
+#: ../shell/view/devices/battery.py:38
+msgid "My Battery life"
+msgstr "Vida de la bateria"
+
+#: ../shell/view/devices/battery.py:94
+msgid "Battery charging"
+msgstr "Carregant bateria"
+
+#: ../shell/view/devices/battery.py:96
+msgid "Battery discharging"
+msgstr "Descarregant bateria"
+
+#: ../shell/view/devices/battery.py:98
+msgid "Battery fully charged"
+msgstr "Bateria plena"
+
+#: ../shell/view/devices/network/wireless.py:61
+msgid "Disconnected"
+msgstr "Desconnectat"
+
+#: ../shell/view/devices/network/wireless.py:131
+msgid "Channel"
+msgstr "Canal"
+
+# aquí cal pensar si es millorÑ barri, veinatge, maquines veines,....
+#: ../shell/view/frame/zoomtoolbar.py:42
+msgid "Neighborhood"
+msgstr "Veïns"
+
+#: ../shell/view/frame/zoomtoolbar.py:54
+msgid "Group"
+msgstr "Grup"
+
+#: ../shell/view/frame/zoomtoolbar.py:66
+msgid "Home"
+msgstr "Inici"
+
+#: ../shell/view/frame/zoomtoolbar.py:78
+msgid "Activity"
+msgstr "Activitat"
+
+#: ../lib/sugar/activity/activity.py:115
+msgid "Share with:"
+msgstr "Compartir amb:"
+
+#: ../lib/sugar/activity/activity.py:117
+msgid "Private"
+msgstr "Privat"
+
+#: ../lib/sugar/activity/activity.py:118
+msgid "My Neighborhood"
+msgstr "Els meus veïns"
+
+#: ../lib/sugar/activity/activity.py:126
+msgid "Keep"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:245
+msgid "Undo"
+msgstr "Desfer"
+
+#: ../lib/sugar/activity/activity.py:250
+msgid "Redo"
+msgstr "Refer"
+
+#: ../lib/sugar/activity/activity.py:260
+msgid "Copy"
+msgstr "Copia"
+
+#: ../lib/sugar/activity/activity.py:265
+msgid "Paste"
+msgstr "Enganxa"
+
+#: ../lib/sugar/activity/activity.py:454
+#, python-format
+msgid "%s Activity"
+msgstr "%s Activitat"
+
+# revisar
+#: ../lib/sugar/activity/activity.py:824
+msgid "Keep error"
+msgstr "Manté l'error"
+
+#: ../lib/sugar/activity/activity.py:825
+msgid "Keep error: all changes will be lost"
+msgstr "Manté l'error: tots els canvis es perdran"
+
+#: ../lib/sugar/activity/activity.py:828
+msgid "Don't stop"
+msgstr "No t'aturis"
+
+#: ../lib/sugar/activity/activity.py:831
+msgid "Stop anyway"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:164 ../lib/sugar/graphics/alert.py:206
+msgid "Cancel"
+msgstr "Cancel·lar"
+
+#: ../lib/sugar/graphics/alert.py:168
+msgid "Ok"
+msgstr "D'acord"
+
+#: ../lib/sugar/graphics/alert.py:216
+msgid "Continue"
+msgstr "Continuar"
+
+#: ../lib/sugar/graphics/alert.py:244
+msgid "OK"
+msgstr "D'acord"
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d year"
+msgstr "%d any"
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d years"
+msgstr "%d anys"
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d month"
+msgstr "%d mes"
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d months"
+msgstr "%d mesos"
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d week"
+msgstr "%d setmana"
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d weeks"
+msgstr "%d setmanes"
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d day"
+msgstr "%d dia"
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d days"
+msgstr "%d dies"
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hour"
+msgstr "%d hora"
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hours"
+msgstr "%d hores"
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minute"
+msgstr "%d minut"
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minutes"
+msgstr "%d minuts"
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d second"
+msgstr "%d segon"
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d seconds"
+msgstr "%d segons"
+
+#: ../lib/sugar/graphics/objectchooser.py:191
+msgid " and "
+msgstr "i"
+
+#: ../lib/sugar/graphics/objectchooser.py:193
+msgid ", "
+msgstr ","
+
+#: ../shell/controlpanel/control.py:213
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr "Per aplicar els canvis, cal que reiniciar el sugar.\n"
+
+#: ../shell/controlpanel/control.py:267
+msgid "Error in specified color modifiers."
+msgstr "Error en els modificadors de color especificats"
+
+#: ../shell/controlpanel/control.py:270
+msgid "Error in specified colors."
+msgstr "Error en els colors especificats"
+
+#: ../shell/controlpanel/control.py:307
+msgid "off"
+msgstr "apagar"
+
+#: ../shell/controlpanel/control.py:309
+msgid "on"
+msgstr "arrancar"
+
+#: ../shell/controlpanel/control.py:310
+msgid "State is unknown."
+msgstr "Status deconegut"
+
+# dubtes:
+# 1- manca una coma al text original
+# 2- He posat on/off
+#: ../shell/controlpanel/control.py:332
+#: ../src/controlpanel/control.py:336
+#, fuzzy
+msgid "Error in specified radio argument use on/off."
+msgstr "Error en l'argument de ràdio especificat, useu on/off"
+
+#: ../shell/controlpanel/control.py:336
+msgid "Permission denied. You need to be root to run this method."
+msgstr "Permís denegat: cal que ser root per aquesta operació"
+
+#: ../shell/controlpanel/control.py:366
+msgid "Error in reading timezone"
+msgstr "Error en llegir la zona horària"
+
+#: ../shell/controlpanel/control.py:397
+#, python-format
+msgid "Error copying timezone (from %s): %s"
+msgstr "Error en copiar la zona horària (des de %s): %s"
+
+#: ../shell/controlpanel/control.py:402
+#, python-format
+msgid "Changing permission of timezone: %s"
+msgstr "Canviant permisos de zona horària: %s"
+
+#: ../shell/controlpanel/control.py:412
+msgid "Error timezone does not exist."
+msgstr "Error: aquesta zona horària no existeix"
+
+#: ../shell/controlpanel/control.py:417 ../shell/controlpanel/control.py:436
+#, python-format
+msgid "Could not access %s. Create standard settings."
+msgstr "No es pot accedir a %s. Crea una configuració estàndar."
+
+#: ../shell/controlpanel/control.py:463
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr "La llengua que té com a codi code=%s no es pot determinar."
+
+#: ../shell/controlpanel/control.py:473
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr "Ho sento, no parlo '%s'."
+
+#: ../shell/view/devices/network/mesh.py:105
+msgid "Connected to a School Mesh Portal"
+msgstr "Connectat al Portal de la Xarxa de l'Escola"
+
+#: ../shell/view/devices/network/mesh.py:107
+msgid "Looking for a School Mesh Portal..."
+msgstr "Cercant el Portal de la Xarxa de l'Escola ..."
+
+#: ../shell/view/devices/network/mesh.py:110
+msgid "Connected to an XO Mesh Portal"
+msgstr "Connectat a un Portal de Xarxa XO"
+
+#: ../shell/view/devices/network/mesh.py:112
+msgid "Looking for an XO Mesh Portal..."
+msgstr "Cercant un Portal de Xarxa XO ..."
+
+#: ../shell/view/devices/network/mesh.py:115
+msgid "Connected to a Simple Mesh"
+msgstr "Connectat a una xarxa simple"
+
+#: ../shell/view/devices/network/mesh.py:117
+msgid "Starting a Simple Mesh"
+msgstr "Arrancant una xarxa simple"
+
+#: ../shell/view/devices/network/mesh.py:124
+msgid "Unknown Mesh"
+msgstr "Xarxa desconeguda"
+
+#: ../shell/view/home/HomeBox.py:175 ../shell/view/home/HomeBox.py:216
+#: ../src/view/home/HomeBox.py:175
+#: ../src/view/home/HomeBox.py:216
+msgid "About this XO"
+msgstr "Sobre aquest XO"
+
+#: ../shell/view/home/HomeBox.py:222
+#: ../src/view/home/HomeBox.py:222
+msgid "Not available"
+msgstr "No disponible"
diff --git a/shell/po/cpp.po b/shell/po/cpp.po
new file mode 100644
index 0000000..93c6b6e
--- /dev/null
+++ b/shell/po/cpp.po
@@ -0,0 +1,958 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-01-31 00:30-0500\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../extensions/cpsection/aboutme/__init__.py:24
+msgid "About Me"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:43
+msgid "You must enter a name."
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:68
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:71
+#, python-format
+msgid "stroke: %s"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:73
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:75
+#, python-format
+msgid "fill: %s"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:86
+msgid "Error in specified color modifiers."
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:89
+msgid "Error in specified colors."
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/view.py:94 ../src/jarabe/intro/window.py:92
+msgid "Name:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/view.py:128
+msgid "Click to change your color:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/__init__.py:21
+msgid "About my Computer"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/model.py:26
+msgid "Not available"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:59
+msgid "Identity"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:68
+msgid "Serial Number:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:90
+msgid "Software"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:99
+msgid "Build:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:114
+msgid "Sugar:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:130
+msgid "Firmware:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:145
+msgid "Wireless Firmware:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:168
+msgid "Copyright and License"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:176
+msgid ""
+"© 2008 One Laptop per Child Association Inc; Red Hat Inc; and Contributors."
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:183
+msgid ""
+"Sugar is the graphical user interface that you are looking at. Sugar is free "
+"software, covered by the GNU General Public License, and you are welcome to "
+"change it and/or distribute copies of it under certain conditions described "
+"therein."
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:195
+msgid "Full license:"
+msgstr ""
+
+#: ../extensions/cpsection/datetime/__init__.py:21
+msgid "Date & Time"
+msgstr ""
+
+#: ../extensions/cpsection/datetime/model.py:87
+msgid "Error timezone does not exist."
+msgstr ""
+
+#: ../extensions/cpsection/datetime/view.py:68 ../data/sugar.schemas.in.h:19
+msgid "Timezone"
+msgstr ""
+
+#: ../extensions/cpsection/frame/__init__.py:21
+msgid "Frame"
+msgstr ""
+
+#: ../extensions/cpsection/frame/model.py:38
+#: ../extensions/cpsection/frame/model.py:60
+msgid "Value must be an integer."
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:26
+msgid "never"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:27
+msgid "instantaneous"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:28
+#, python-format
+msgid "%s seconds"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:52
+msgid "Activation Delay"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:76
+msgid "Corner"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:111
+msgid "Edge"
+msgstr ""
+
+#: ../extensions/cpsection/language/__init__.py:21
+#: ../extensions/cpsection/language/view.py:32
+msgid "Language"
+msgstr ""
+
+#: ../extensions/cpsection/language/model.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr ""
+
+#: ../extensions/cpsection/language/model.py:124
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr ""
+
+#: ../extensions/cpsection/language/model.py:144
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr ""
+
+#: ../extensions/cpsection/network/__init__.py:21
+#: ../extensions/cpsection/network/view.py:28
+msgid "Network"
+msgstr ""
+
+#: ../extensions/cpsection/network/model.py:79
+msgid "State is unknown."
+msgstr ""
+
+#: ../extensions/cpsection/network/model.py:105
+msgid "Error in specified radio argument use on/off."
+msgstr ""
+
+#: ../extensions/cpsection/network/model.py:137
+msgid "Error in specified argument use 0/1."
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:56
+msgid "Wireless"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:64
+msgid "Turn off the wireless radio to save battery life"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:77
+msgid "Radio"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:93
+msgid "Discard network history if you have trouble connecting to the network"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:102
+msgid "Discard network history"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:115
+msgid "Collaboration"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:123
+msgid ""
+"The server is the equivalent of what room you are in; people on the same "
+"server will be able to see each other, even when they aren't on the same "
+"network."
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:133
+msgid "Server:"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:56
+msgid "My Battery"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:137
+msgid "Removed"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:140
+msgid "Charging"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:143
+msgid "Very little power remaining"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:149
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:153
+msgid "Charged"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:40
+#, python-format
+msgid "IP address: %s"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:104
+msgid "Disconnect..."
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:109
+#: ../src/jarabe/desktop/meshbox.py:246
+msgid "Connecting..."
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:113
+#: ../extensions/deviceicon/network.py:166
+#: ../src/jarabe/desktop/meshbox.py:252
+msgid "Connected"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:126
+msgid "Channel"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:141
+msgid "Wired Network"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:169
+msgid "Speed"
+msgstr ""
+
+#: ../extensions/deviceicon/speaker.py:46
+msgid "My Speakers"
+msgstr ""
+
+#: ../extensions/deviceicon/speaker.py:128
+msgid "Unmute"
+msgstr ""
+
+#: ../extensions/deviceicon/speaker.py:131
+msgid "Mute"
+msgstr ""
+
+#: ../extensions/globalkey/screenshot.py:50
+msgid "Screenshot"
+msgstr ""
+
+#: ../extensions/globalkey/viewsource.py:196
+#, python-format
+msgid "View source: %r"
+msgstr ""
+
+#: ../extensions/globalkey/viewsource.py:205
+#: ../src/jarabe/frame/zoomtoolbar.py:42
+msgid "Activity"
+msgstr ""
+
+#: ../extensions/globalkey/viewsource.py:212
+msgid "Document"
+msgstr ""
+
+#: ../extensions/globalkey/viewsource.py:226
+#: ../src/jarabe/journal/objectchooser.py:141
+msgid "Close"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:1
+msgid "Backup URL"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:2
+msgid ""
+"Color for the XO icon that is used throughout the desktop. The string is "
+"composed of the stroke color and fill color, format is that of rbg colors. "
+"Example: #AC32FF,#9A5200"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:3
+msgid "Corner Delay"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:4
+msgid "Delay for the activation of the frame using the corners."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:5
+msgid "Delay for the activation of the frame using the edges."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:6
+msgid "Edge Delay"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:7
+msgid "Favorites Layout"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:8
+msgid "Favorites resume mode"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:9
+msgid ""
+"If TRUE, Sugar will make us searchable for the other users of the Jabber "
+"server."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:10
+msgid "Jabber Server"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:11
+msgid "Layout of the favorites view."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:12
+msgid "Power Automatic"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:13
+msgid "Power Automatic."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:14
+msgid "Power Extreme"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:15
+msgid "Power Extreme."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:16
+msgid "Publish to Gadget"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:17
+msgid "Setting for muting the sound device."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:18
+msgid "Sound Muted"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:20
+msgid "Timezone setting for the system."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:21
+msgid "Url of the jabber server to use."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:22
+msgid "Url where the backup is saved to."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:23
+msgid "User Color"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:24
+msgid "User Name"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:25
+msgid "User name that is used throughout the desktop."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:26
+msgid "Volume Level"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:27
+msgid "Volume level for the sound device."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:28
+msgid ""
+"When in resume mode, clicking on a favorite icon will cause the last entry "
+"for that activity to be resumed."
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:28
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:30
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:31
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr ""
+
+#. TRANS: Translators, there's a empty line at the end of this string,
+#. which must appear in the translated string (msgstr) as well.
+#: ../src/jarabe/controlpanel/cmd.py:37
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" -c key clear the current value for the key \n"
+" "
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:50
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:272
+msgid "Warning"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:273
+#: ../src/jarabe/controlpanel/sectionview.py:42
+msgid "Changes require restart"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:276
+msgid "Cancel changes"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:281 ../src/jarabe/desktop/homebox.py:113
+msgid "Later"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:285
+msgid "Restart now"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/toolbar.py:61 ../src/jarabe/intro/window.py:188
+msgid "Done"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/toolbar.py:115
+#: ../src/jarabe/desktop/homebox.py:111
+#: ../src/jarabe/frame/activitiestray.py:683
+#: ../src/jarabe/frame/activitiestray.py:762
+#: ../src/jarabe/frame/activitiestray.py:790
+msgid "Cancel"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/toolbar.py:121
+#: ../src/jarabe/desktop/favoritesview.py:334
+msgid "Ok"
+msgstr ""
+
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:114
+msgid "Freeform"
+msgstr ""
+
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:196
+msgid "Ring"
+msgstr ""
+
+#. TRANS: label for the spiral layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:341
+msgid "Spiral"
+msgstr ""
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:408
+msgid "Box"
+msgstr ""
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:449
+msgid "Triangle"
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:325
+msgid "Registration Failed"
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:326
+#, python-format
+msgid "%s"
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:328
+msgid "Registration Successful"
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:329
+msgid "You are now registered with your school server."
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:666
+msgid "Register"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:67
+msgid "Confirm erase"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:69
+#, python-format
+msgid "Confirm erase: Do you want to permanently erase %s?"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:73 ../src/jarabe/frame/clipboardmenu.py:62
+msgid "Keep"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:76
+#: ../src/jarabe/journal/journaltoolbox.py:357
+#: ../src/jarabe/journal/palettes.py:112 ../src/jarabe/view/palettes.py:127
+msgid "Erase"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:106
+msgid "Software Update"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:107
+msgid "Update your activities to ensure compatibility with your new software"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:116
+msgid "Check now"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:233
+msgid "List view"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:234
+msgid "<Ctrl>2"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:296
+msgid "Favorites view"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:297
+msgid "<Ctrl>1"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:304
+msgid "Resume by default"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:131
+msgid "Key Type:"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:151
+msgid "Authentication Type:"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:215
+msgid "WPA & WPA2 Personal"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:224
+msgid "Wireless Security:"
+msgstr ""
+
+#: ../src/jarabe/desktop/meshbox.py:130
+msgid "Connect"
+msgstr ""
+
+#: ../src/jarabe/desktop/meshbox.py:134
+msgid "Disconnect"
+msgstr ""
+
+#. TRANS: Action label for resuming an activity.
+#: ../src/jarabe/desktop/meshbox.py:440
+#: ../src/jarabe/frame/activitiestray.py:707
+#: ../src/jarabe/journal/journaltoolbox.py:425
+#: ../src/jarabe/journal/palettes.py:72 ../src/jarabe/view/palettes.py:62
+msgid "Resume"
+msgstr ""
+
+#: ../src/jarabe/desktop/meshbox.py:445
+#: ../src/jarabe/frame/activitiestray.py:221
+msgid "Join"
+msgstr ""
+
+#: ../src/jarabe/desktop/schoolserver.py:18
+msgid "Cannot obtain data needed for registration."
+msgstr ""
+
+#: ../src/jarabe/desktop/schoolserver.py:35
+msgid "Cannot connect to the server."
+msgstr ""
+
+#: ../src/jarabe/desktop/schoolserver.py:40
+msgid "The server could not complete the request."
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:226
+#: ../src/jarabe/frame/activitiestray.py:655
+msgid "Decline"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:608
+#, python-format
+msgid "%dB"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:610
+#, python-format
+msgid "%dKB"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:612
+#, python-format
+msgid "%dMB"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:629
+#, python-format
+msgid "%s of %s"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:640
+#, python-format
+msgid "Transfer from %r"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:650
+msgid "Accept"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:673
+#: ../src/jarabe/frame/activitiestray.py:780
+#, python-format
+msgid "%s (%s)"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:751
+#, python-format
+msgid "Transfer to %r"
+msgstr ""
+
+#: ../src/jarabe/frame/clipboardmenu.py:52
+msgid "Remove"
+msgstr ""
+
+#: ../src/jarabe/frame/clipboardmenu.py:57
+#: ../src/jarabe/frame/clipboardmenu.py:80
+msgid "Open"
+msgstr ""
+
+#: ../src/jarabe/frame/clipboardmenu.py:85
+msgid "Open with"
+msgstr ""
+
+#: ../src/jarabe/frame/clipboardobject.py:47
+#, python-format
+msgid "%s clipping"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:36
+msgid "Neighborhood"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:38
+msgid "Group"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:40
+msgid "Home"
+msgstr ""
+
+#: ../src/jarabe/intro/window.py:124
+msgid "Click to change color:"
+msgstr ""
+
+#: ../src/jarabe/intro/window.py:174 ../src/jarabe/journal/detailview.py:103
+msgid "Back"
+msgstr ""
+
+#: ../src/jarabe/intro/window.py:191
+msgid "Next"
+msgstr ""
+
+#: ../src/jarabe/journal/collapsedentry.py:258
+#: ../src/jarabe/journal/expandedentry.py:159
+#: ../src/jarabe/journal/palettes.py:66
+msgid "Untitled"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:205
+msgid "No preview"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:224
+msgid "Participants:"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:247
+msgid "Description:"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:273
+msgid "Tags:"
+msgstr ""
+
+#: ../src/jarabe/journal/journalactivity.py:108
+#: ../src/jarabe/journal/volumestoolbar.py:47
+msgid "Journal"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:65
+msgid "Search"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:124
+msgid "Anytime"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:126
+msgid "Today"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:128
+msgid "Since yesterday"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 7 days.
+#: ../src/jarabe/journal/journaltoolbox.py:130
+msgid "Past week"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 30 days.
+#: ../src/jarabe/journal/journaltoolbox.py:132
+msgid "Past month"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 356 days.
+#: ../src/jarabe/journal/journaltoolbox.py:134
+msgid "Past year"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:141
+msgid "Anyone"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:143
+msgid "My friends"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:144
+msgid "My class"
+msgstr ""
+
+#. TRANS: Item in a combo box that filters by entry type.
+#: ../src/jarabe/journal/journaltoolbox.py:271
+msgid "Anything"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:347
+#: ../src/jarabe/journal/palettes.py:90
+msgid "Copy"
+msgstr ""
+
+#. TRANS: Action label for starting an entry.
+#: ../src/jarabe/journal/journaltoolbox.py:428
+#: ../src/jarabe/journal/palettes.py:75 ../src/jarabe/view/palettes.py:111
+msgid "Start"
+msgstr ""
+
+#: ../src/jarabe/journal/listview.py:40
+msgid "Your Journal is empty"
+msgstr ""
+
+#: ../src/jarabe/journal/listview.py:41
+msgid "No matching entries "
+msgstr ""
+
+#: ../src/jarabe/journal/listview.py:369
+msgid "Clear search"
+msgstr ""
+
+#: ../src/jarabe/journal/misc.py:92
+msgid "No date"
+msgstr ""
+
+#: ../src/jarabe/journal/modalalert.py:63
+msgid "Your Journal is full"
+msgstr ""
+
+#: ../src/jarabe/journal/modalalert.py:67
+msgid "Please delete some old Journal entries to make space for new ones."
+msgstr ""
+
+#: ../src/jarabe/journal/modalalert.py:79
+msgid "Show Journal"
+msgstr ""
+
+#: ../src/jarabe/journal/objectchooser.py:136
+msgid "Choose an object"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:73
+msgid "Resume with"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:76
+msgid "Start with"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:98
+msgid "Send to"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:107
+msgid "View Details"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:185
+msgid "No friends present"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:190
+msgid "No valid connection found"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:218
+msgid "No activity to resume entry"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:220
+msgid "No activity to start entry"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:61
+msgid "Remove friend"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:64
+msgid "Make friend"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:81
+msgid "My Settings"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:86
+msgid "Logout"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:91
+msgid "Restart"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:96
+msgid "Shutdown"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:131
+#, python-format
+msgid "Invite to %s"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:43
+msgid "Starting..."
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:73
+msgid "Stop"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:145
+msgid "Remove favorite"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:149
+msgid "Make favorite"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:201
+msgid "Show contents"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:223 ../src/jarabe/view/palettes.py:272
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:248
+msgid "Unmount"
+msgstr ""
diff --git a/shell/po/cs.po b/shell/po/cs.po
new file mode 100644
index 0000000..63d4e34
--- /dev/null
+++ b/shell/po/cs.po
@@ -0,0 +1,764 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-09-25 00:30-0400\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/intro/window.py:93 ../src/controlpanel/aboutme/view.py:100
+msgid "Name:"
+msgstr ""
+
+#: ../src/intro/window.py:125
+msgid "Click to change color:"
+msgstr ""
+
+#: ../src/intro/window.py:175 ../src/journal/detailview.py:119
+msgid "Back"
+msgstr ""
+
+#: ../src/intro/window.py:189 ../src/controlpanel/toolbar.py:61
+msgid "Done"
+msgstr ""
+
+#: ../src/intro/window.py:192
+msgid "Next"
+msgstr ""
+
+#: ../src/view/BuddyMenu.py:60
+msgid "Remove friend"
+msgstr ""
+
+#: ../src/view/BuddyMenu.py:63
+msgid "Make friend"
+msgstr ""
+
+#: ../src/view/BuddyMenu.py:92
+#, python-format
+msgid "Invite to %s"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:51
+msgid "Remove"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:56 ../src/view/clipboardmenu.py:78
+msgid "Open"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:61 ../src/view/home/HomeBox.py:84
+msgid "Keep"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:83
+msgid "Open with"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:228
+#, python-format
+msgid "%s clipping"
+msgstr ""
+
+#: ../src/hardware/keydialog.py:150
+msgid "Key Type:"
+msgstr ""
+
+#: ../src/hardware/keydialog.py:170
+msgid "Authentication Type:"
+msgstr ""
+
+#: ../src/hardware/keydialog.py:251
+msgid "Encryption Type:"
+msgstr ""
+
+#: ../src/hardware/schoolserver.py:17
+msgid "Cannot obtain data needed for registration."
+msgstr ""
+
+#: ../src/hardware/schoolserver.py:31
+msgid "Cannot connect to the server."
+msgstr ""
+
+#: ../src/hardware/schoolserver.py:36
+msgid "The server could not complete the request."
+msgstr ""
+
+#: ../src/view/Shell.py:251
+msgid "Screenshot"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:78
+msgid "Confirm erase"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:80
+#, python-format
+msgid "Confirm erase: Do you want to permanently erase %s?"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:87 ../src/view/palettes.py:120
+#: ../src/journal/journaltoolbox.py:335 ../src/journal/palettes.py:75
+msgid "Erase"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:117
+msgid "Software Update"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:118
+msgid "Update your activities to ensure compatibility with your new software"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:122 ../src/controlpanel/toolbar.py:115
+msgid "Cancel"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:124 ../src/controlpanel/gui.py:273
+msgid "Later"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:127
+msgid "Check now"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:261
+msgid "List view"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:262
+msgid "<Ctrl>2"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:320
+msgid "Favorites view"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:321
+msgid "<Ctrl>1"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:97
+msgid "Connect"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:106
+msgid "Disconnect"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:118
+msgid "Disconnecting..."
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:159
+msgid "Connecting..."
+msgstr ""
+
+#. TODO: show the channel number
+#: ../src/view/home/MeshBox.py:166
+msgid "Connected"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:218 ../src/view/devices/network/mesh.py:41
+#: ../src/view/devices/network/mesh.py:68
+#: ../src/view/devices/network/mesh.py:72
+msgid "Mesh Network"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:221 ../src/view/devices/network/wireless.py:125
+#: ../src/view/devices/network/mesh.py:89
+msgid "Disconnect..."
+msgstr ""
+
+#. TRANS: Action label for resuming an activity.
+#: ../src/view/home/MeshBox.py:309 ../src/view/palettes.py:61
+#: ../src/journal/journaltoolbox.py:399 ../src/journal/palettes.py:57
+msgid "Resume"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:314 ../src/view/frame/activitiestray.py:206
+msgid "Join"
+msgstr ""
+
+#: ../src/view/devices/battery.py:45
+msgid "My Battery"
+msgstr ""
+
+#: ../src/view/devices/battery.py:114
+msgid "Charging"
+msgstr ""
+
+#: ../src/view/devices/battery.py:117
+msgid "Very little power remaining"
+msgstr ""
+
+#: ../src/view/devices/battery.py:123
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr ""
+
+#: ../src/view/devices/battery.py:127
+msgid "Charged"
+msgstr ""
+
+#: ../src/view/devices/speaker.py:44
+msgid "My Speakers"
+msgstr ""
+
+#: ../src/view/devices/speaker.py:125
+msgid "Unmute"
+msgstr ""
+
+#: ../src/view/devices/speaker.py:128
+msgid "Mute"
+msgstr ""
+
+#: ../src/view/devices/network/wireless.py:67
+msgid "Disconnected"
+msgstr ""
+
+#: ../src/view/devices/network/wireless.py:143
+msgid "Channel"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:37
+msgid "Neighborhood"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:39
+msgid "Group"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:41
+msgid "Home"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:43
+msgid "Activity"
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:26
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:28
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:29
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr ""
+
+#. TRANS: Translators, there's a empty line at the end of this string,
+#. which must appear in the translated string (msgstr) as well.
+#: ../src/controlpanel/cmd.py:35
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" -c key clear the current value for the key \n"
+" "
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:48
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr ""
+
+#: ../src/controlpanel/toolbar.py:121 ../src/view/home/favoritesview.py:305
+msgid "Ok"
+msgstr ""
+
+#: ../src/controlpanel/sectionview.py:42 ../src/controlpanel/gui.py:265
+msgid "Changes require restart"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:264
+msgid "Warning"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:268
+msgid "Cancel changes"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:277
+msgid "Restart now"
+msgstr ""
+
+#: ../src/controlpanel/aboutme/model.py:44
+msgid "You must enter a name."
+msgstr ""
+
+#: ../src/controlpanel/aboutme/model.py:69
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr ""
+
+#: ../src/controlpanel/aboutme/model.py:72
+#, python-format
+msgid "stroke: %s"
+msgstr ""
+
+#: ../src/controlpanel/aboutme/model.py:74
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr ""
+
+#: ../src/controlpanel/aboutme/model.py:76
+#, python-format
+msgid "fill: %s"
+msgstr ""
+
+#: ../src/controlpanel/aboutme/model.py:87
+msgid "Error in specified color modifiers."
+msgstr ""
+
+#: ../src/controlpanel/aboutme/model.py:90
+msgid "Error in specified colors."
+msgstr ""
+
+#: ../src/controlpanel/aboutme/view.py:32
+#: ../src/controlpanel/aboutme/__init__.py:22
+msgid "About Me"
+msgstr ""
+
+#: ../src/controlpanel/aboutme/view.py:134
+msgid "Click to change your color:"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/model.py:24
+msgid "Not available"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:55
+msgid "Identity"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:64
+msgid "Serial Number:"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:87
+msgid "Software"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:96
+msgid "Build:"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:111
+msgid "Sugar:"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:126
+msgid "Firmware:"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:148
+msgid "Copyright and License"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:156
+msgid ""
+"© 2008 One Laptop per Child Association Inc; Red Hat Inc; and Contributors."
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:163
+msgid ""
+"Sugar is the graphical user interface that you are looking at. Sugar is free "
+"software, covered by the GNU General Public License, and you are welcome to "
+"change it and/or distribute copies of it under certain conditions described "
+"therein."
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:175
+msgid "Full license:"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/__init__.py:21
+msgid "About my XO"
+msgstr ""
+
+#: ../src/controlpanel/datetime/model.py:89
+msgid "Error timezone does not exist."
+msgstr ""
+
+#: ../src/controlpanel/datetime/view.py:68
+msgid "Timezone"
+msgstr ""
+
+#: ../src/controlpanel/datetime/__init__.py:21
+msgid "Date & Time"
+msgstr ""
+
+#: ../src/controlpanel/frame/model.py:38 ../src/controlpanel/frame/model.py:60
+msgid "Value must be an integer."
+msgstr ""
+
+#: ../src/controlpanel/frame/view.py:26
+msgid "never"
+msgstr ""
+
+#: ../src/controlpanel/frame/view.py:27
+msgid "instantaneous"
+msgstr ""
+
+#: ../src/controlpanel/frame/view.py:28
+#, python-format
+msgid "%s seconds"
+msgstr ""
+
+#: ../src/controlpanel/frame/view.py:52
+msgid "Activation Delay"
+msgstr ""
+
+#: ../src/controlpanel/frame/view.py:76
+msgid "Corner"
+msgstr ""
+
+#: ../src/controlpanel/frame/view.py:111
+msgid "Edge"
+msgstr ""
+
+#: ../src/controlpanel/frame/__init__.py:21
+msgid "Frame"
+msgstr ""
+
+#: ../src/controlpanel/language/model.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr ""
+
+#: ../src/controlpanel/language/model.py:114
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr ""
+
+#: ../src/controlpanel/language/model.py:131
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr ""
+
+#: ../src/controlpanel/language/view.py:70
+#: ../src/controlpanel/language/__init__.py:21
+msgid "Language"
+msgstr ""
+
+#: ../src/controlpanel/network/model.py:62
+msgid "State is unknown."
+msgstr ""
+
+#: ../src/controlpanel/network/model.py:82
+msgid "Error in specified radio argument use on/off."
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:28
+#: ../src/controlpanel/network/__init__.py:21
+msgid "Network"
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:54
+msgid "Wireless"
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:62
+msgid "Turn of the wireless radio to save battery life"
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:75
+msgid "Radio"
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:91
+msgid "Discard network history if you have trouble connecting to the network"
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:100
+msgid "Discard network history"
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:113
+msgid "Mesh"
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:122
+msgid "Server:"
+msgstr ""
+
+#: ../src/controlpanel/power/model.py:55
+msgid "Error in automatic pm argument, use on/off."
+msgstr ""
+
+#: ../src/controlpanel/power/model.py:84
+msgid "Error in extreme pm argument, use on/off."
+msgstr ""
+
+#: ../src/controlpanel/power/view.py:47
+msgid "Power management"
+msgstr ""
+
+#: ../src/controlpanel/power/view.py:57
+msgid "Automatic power management (increases battery life)"
+msgstr ""
+
+#: ../src/controlpanel/power/view.py:85
+msgid ""
+"Extreme power management (disableswireless radio, increases battery life)"
+msgstr ""
+
+#: ../src/controlpanel/power/__init__.py:21
+msgid "Power"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:111
+msgid "Connected to a School Mesh Portal"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:113
+msgid "Looking for a School Mesh Portal..."
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:116
+msgid "Connected to an XO Mesh Portal"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:118
+msgid "Looking for an XO Mesh Portal..."
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:121
+msgid "Connected to a Simple Mesh"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:123
+msgid "Starting a Simple Mesh"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:130
+msgid "Unknown Mesh"
+msgstr ""
+
+#: ../src/view/frame/activitiestray.py:211
+msgid "Decline"
+msgstr ""
+
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/view/home/favoriteslayout.py:107
+msgid "Freeform"
+msgstr ""
+
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/view/home/favoriteslayout.py:189
+msgid "Ring"
+msgstr ""
+
+#. TRANS: label for the spiral layout in the favorites view
+#: ../src/view/home/favoriteslayout.py:334
+msgid "Spiral"
+msgstr ""
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/view/home/favoriteslayout.py:401
+msgid "Box"
+msgstr ""
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/view/home/favoriteslayout.py:442
+msgid "Triangle"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:295
+msgid "Registration Failed"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:296
+#, python-format
+msgid "%s"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:298
+msgid "Registration Successful"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:299
+msgid "You are now registered with your school server."
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:420
+msgid "Settings"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:425
+msgid "Restart"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:430
+msgid "Shutdown"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:436
+msgid "Register"
+msgstr ""
+
+#: ../src/view/palettes.py:42
+msgid "Starting..."
+msgstr ""
+
+#: ../src/view/palettes.py:72
+msgid "Stop"
+msgstr ""
+
+#. TRANS: Action label for starting an entry.
+#: ../src/view/palettes.py:104 ../src/journal/journaltoolbox.py:402
+#: ../src/journal/palettes.py:59
+msgid "Start"
+msgstr ""
+
+#: ../src/view/palettes.py:138
+msgid "Remove favorite"
+msgstr ""
+
+#: ../src/view/palettes.py:142
+msgid "Make favorite"
+msgstr ""
+
+#: ../src/view/palettes.py:191
+msgid "Show contents"
+msgstr ""
+
+#: ../src/view/palettes.py:215
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr ""
+
+#: ../src/journal/journaltoolbox.py:62
+msgid "Search"
+msgstr ""
+
+#: ../src/journal/journaltoolbox.py:119
+msgid "Anytime"
+msgstr ""
+
+#: ../src/journal/journaltoolbox.py:121
+msgid "Today"
+msgstr ""
+
+#: ../src/journal/journaltoolbox.py:123
+msgid "Since yesterday"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 7 days.
+#: ../src/journal/journaltoolbox.py:125
+msgid "Past week"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 30 days.
+#: ../src/journal/journaltoolbox.py:127
+msgid "Past month"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 356 days.
+#: ../src/journal/journaltoolbox.py:129
+msgid "Past year"
+msgstr ""
+
+#: ../src/journal/journaltoolbox.py:136
+msgid "Anyone"
+msgstr ""
+
+#: ../src/journal/journaltoolbox.py:138
+msgid "My friends"
+msgstr ""
+
+#: ../src/journal/journaltoolbox.py:139
+msgid "My class"
+msgstr ""
+
+#. TRANS: Item in a combo box that filters by entry type.
+#: ../src/journal/journaltoolbox.py:255
+msgid "Anything"
+msgstr ""
+
+#. TODO: Add "Start with" menu item
+#: ../src/journal/journaltoolbox.py:325 ../src/journal/palettes.py:67
+msgid "Copy"
+msgstr ""
+
+#: ../src/journal/collapsedentry.py:248 ../src/journal/expandedentry.py:176
+#: ../src/journal/palettes.py:51
+msgid "Untitled"
+msgstr ""
+
+#: ../src/journal/journalactivity.py:119 ../src/journal/volumesmanager.py:57
+msgid "Journal"
+msgstr ""
+
+#: ../src/journal/expandedentry.py:222
+msgid "No preview"
+msgstr ""
+
+#: ../src/journal/expandedentry.py:241
+msgid "Participants:"
+msgstr ""
+
+#: ../src/journal/expandedentry.py:266
+msgid "Description:"
+msgstr ""
+
+#: ../src/journal/expandedentry.py:292
+msgid "Tags:"
+msgstr ""
+
+#: ../src/journal/objectchooser.py:134
+msgid "Choose an object"
+msgstr ""
+
+#: ../src/journal/objectchooser.py:139
+msgid "Close"
+msgstr ""
+
+#: ../src/journal/volumestoolbar.py:93
+msgid "Unmount"
+msgstr ""
+
+#: ../src/journal/misc.py:95
+msgid "No date"
+msgstr ""
+
+#: ../src/journal/listview.py:39
+msgid "Your Journal is empty"
+msgstr ""
+
+#: ../src/journal/listview.py:40
+msgid "No matching entries "
+msgstr ""
+
+#: ../src/journal/modalalert.py:59
+msgid "Your Journal is full"
+msgstr ""
+
+#: ../src/journal/modalalert.py:63
+msgid "Please delete some old Journal entries to make space for new ones."
+msgstr ""
+
+#: ../src/journal/modalalert.py:75
+msgid "Show Journal"
+msgstr ""
diff --git a/shell/po/da.po b/shell/po/da.po
new file mode 100644
index 0000000..f6abf2d
--- /dev/null
+++ b/shell/po/da.po
@@ -0,0 +1,1227 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-09-05 00:31-0400\n"
+"PO-Revision-Date: 2010-01-25 03:48+0200\n"
+"Last-Translator: Chris <cjl@laptop.org>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: da\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Pootle 2.0.1\n"
+
+#: ../extensions/cpsection/aboutme/__init__.py:24
+msgid "About Me"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:43
+msgid "You must enter a name."
+msgstr "Du skal skrive et navn."
+
+#: ../extensions/cpsection/aboutme/model.py:68
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr "streg: farve=%s nuance=%s"
+
+#: ../extensions/cpsection/aboutme/model.py:71
+#, python-format
+msgid "stroke: %s"
+msgstr "streg: %s"
+
+#: ../extensions/cpsection/aboutme/model.py:73
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr "fyld: farve=%s nuance=%s"
+
+#: ../extensions/cpsection/aboutme/model.py:75
+#, python-format
+msgid "fill: %s"
+msgstr "fyld: %s"
+
+#: ../extensions/cpsection/aboutme/model.py:86
+msgid "Error in specified color modifiers."
+msgstr "Fejl i angivne farvetilrettere."
+
+#: ../extensions/cpsection/aboutme/model.py:89
+msgid "Error in specified colors."
+msgstr "Fejl i angivne farver."
+
+#: ../extensions/cpsection/aboutme/view.py:94 ../src/jarabe/intro/window.py:92
+msgid "Name:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/view.py:128
+#, fuzzy
+msgid "Click to change your color:"
+msgstr "Klik for at ændre farve:"
+
+#: ../extensions/cpsection/aboutcomputer/__init__.py:21
+msgid "About my Computer"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/model.py:28
+msgid "Not available"
+msgstr "Ikke tilgængelig"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:60
+msgid "Identity"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:69
+msgid "Serial Number:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:91
+msgid "Software"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:100
+msgid "Build:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:115
+msgid "Sugar:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:131
+msgid "Firmware:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:146
+msgid "Wireless Firmware:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:169
+msgid "Copyright and License"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:184
+msgid ""
+"Sugar is the graphical user interface that you are looking at. Sugar is free "
+"software, covered by the GNU General Public License, and you are welcome to "
+"change it and/or distribute copies of it under certain conditions described "
+"therein."
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:196
+msgid "Full license:"
+msgstr ""
+
+#: ../extensions/cpsection/datetime/__init__.py:21
+msgid "Date & Time"
+msgstr ""
+
+#: ../extensions/cpsection/datetime/model.py:87
+msgid "Error timezone does not exist."
+msgstr "Fejl tidszone eksisterer ikke."
+
+#: ../extensions/cpsection/datetime/view.py:68 ../data/sugar.schemas.in.h:27
+msgid "Timezone"
+msgstr "Tidszone"
+
+#: ../extensions/cpsection/frame/__init__.py:21
+msgid "Frame"
+msgstr ""
+
+#: ../extensions/cpsection/frame/model.py:38
+#: ../extensions/cpsection/frame/model.py:60
+msgid "Value must be an integer."
+msgstr "Værdi skal være et heltal."
+
+#: ../extensions/cpsection/frame/view.py:26
+msgid "never"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:27
+msgid "instantaneous"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:28
+#, python-format
+msgid "%s seconds"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:52
+msgid "Activation Delay"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:76
+msgid "Corner"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:111
+msgid "Edge"
+msgstr ""
+
+#: ../extensions/cpsection/keyboard/__init__.py:21
+#: ../extensions/cpsection/keyboard/view.py:31
+msgid "Keyboard"
+msgstr ""
+
+#: ../extensions/cpsection/keyboard/view.py:187
+msgid "Keyboard Model"
+msgstr ""
+
+#: ../extensions/cpsection/keyboard/view.py:243
+msgid "Key(s) to change layout"
+msgstr ""
+
+#: ../extensions/cpsection/keyboard/view.py:311
+msgid "Keyboard Layout(s)"
+msgstr ""
+
+#: ../extensions/cpsection/language/__init__.py:21
+#: ../extensions/cpsection/language/view.py:33
+msgid "Language"
+msgstr ""
+
+#: ../extensions/cpsection/language/model.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr ""
+
+#: ../extensions/cpsection/language/model.py:124
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr ""
+
+#: ../extensions/cpsection/language/model.py:144
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr "Desværre jeg taler ikke '%s'."
+
+#: ../extensions/cpsection/language/view.py:56
+msgid ""
+"Add languages in the order you prefer. If a translation is not available, "
+"the next in the list will be used."
+msgstr ""
+
+#: ../extensions/cpsection/network/__init__.py:21
+#: ../extensions/cpsection/network/view.py:28
+msgid "Network"
+msgstr ""
+
+#: ../extensions/cpsection/network/model.py:79
+msgid "State is unknown."
+msgstr "Tilstand er ukendt."
+
+#: ../extensions/cpsection/network/model.py:105
+msgid "Error in specified radio argument use on/off."
+msgstr "Fejl i angivne radioargument brug on/off."
+
+#: ../extensions/cpsection/network/model.py:137
+msgid "Error in specified argument use 0/1."
+msgstr "Fejl i angivne argument brug 0/1."
+
+#: ../extensions/cpsection/network/view.py:59
+msgid "Wireless"
+msgstr "Trådløs"
+
+#: ../extensions/cpsection/network/view.py:67
+msgid "Turn off the wireless radio to save battery life"
+msgstr "Sluk for den trådløse radio for at spare på batteriet"
+
+#: ../extensions/cpsection/network/view.py:80
+msgid "Radio"
+msgstr "Radio"
+
+#: ../extensions/cpsection/network/view.py:96
+msgid "Discard network history if you have trouble connecting to the network"
+msgstr ""
+"Drop netværkshistorik hvis du har problemer med at forbinde til netværket"
+
+#: ../extensions/cpsection/network/view.py:105
+msgid "Discard network history"
+msgstr "Drop netværkshistorik"
+
+#: ../extensions/cpsection/network/view.py:118
+msgid "Collaboration"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:126
+msgid ""
+"The server is the equivalent of what room you are in; people on the same "
+"server will be able to see each other, even when they aren't on the same "
+"network."
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:136
+msgid "Server:"
+msgstr "Server:"
+
+#: ../extensions/cpsection/power/__init__.py:21
+msgid "Power"
+msgstr "Strøm"
+
+#: ../extensions/cpsection/power/model.py:54
+msgid "Error in automatic pm argument, use on/off."
+msgstr ""
+
+#: ../extensions/cpsection/power/model.py:81
+msgid "Error in extreme pm argument, use on/off."
+msgstr ""
+
+#: ../extensions/cpsection/power/view.py:47
+msgid "Power management"
+msgstr "Strømstyring"
+
+#: ../extensions/cpsection/power/view.py:57
+msgid "Automatic power management (increases battery life)"
+msgstr "Automatisk strømstyring (øger batterilevetiden)"
+
+#: ../extensions/cpsection/power/view.py:85
+msgid ""
+"Extreme power management (disableswireless radio, increases battery life)"
+msgstr "Ekstrem strømstyring (slukker trådløs radio, øger batterilevetid)"
+
+#: ../extensions/cpsection/updater/__init__.py:21
+#, fuzzy
+msgid "Software update"
+msgstr "Software-opdatering"
+
+#: ../extensions/cpsection/updater/view.py:62
+msgid ""
+"Software updates correct errors, eliminate security vulnerabilities, and "
+"provide new features."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:122
+#, python-format
+msgid "Checking %s..."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:124
+#, python-format
+msgid "Downloading %s..."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:126
+#, python-format
+msgid "Updating %s..."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:135
+msgid "Your software is up-to-date"
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:137
+#, python-format
+msgid "You can install %s update"
+msgid_plural "You can install %s updates"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../extensions/cpsection/updater/view.py:155
+msgid "Checking for updates..."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:160
+msgid "Installing updates..."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:165
+#, python-format
+msgid "%s update was installed"
+msgid_plural "%s updates were installed"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../extensions/cpsection/updater/view.py:244
+msgid "Install selected"
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:265
+#, python-format
+msgid "Download size: %s"
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:353
+#, python-format
+msgid "From version %(current)d to %(new)s (Size: %(size)s)"
+msgstr ""
+
+#. TRANS: download size is 0
+#: ../extensions/cpsection/updater/view.py:373
+#, fuzzy
+msgid "None"
+msgstr "Færdig"
+
+#. TRANS: download size of very small updates
+#: ../extensions/cpsection/updater/view.py:376
+msgid "1 KB"
+msgstr ""
+
+#. TRANS: download size of small updates, e.g. '250 KB'
+#: ../extensions/cpsection/updater/view.py:379
+#, python-format
+msgid "%.0f KB"
+msgstr ""
+
+#. TRANS: download size of updates, e.g. '2.3 MB'
+#: ../extensions/cpsection/updater/view.py:382
+#, python-format
+msgid "%.1f MB"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:58
+msgid "My Battery"
+msgstr "Mit batteri"
+
+#: ../extensions/deviceicon/battery.py:137
+#, fuzzy
+msgid "Removed"
+msgstr "Fjern"
+
+#: ../extensions/deviceicon/battery.py:140
+msgid "Charging"
+msgstr "Oplader"
+
+#: ../extensions/deviceicon/battery.py:143
+msgid "Very little power remaining"
+msgstr "Meget lidt strøm tilbage"
+
+#: ../extensions/deviceicon/battery.py:149
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr "%(hour)d:%(min).2d endnu"
+
+#: ../extensions/deviceicon/battery.py:152
+msgid "Charged"
+msgstr "Opladet"
+
+#: ../extensions/deviceicon/network.py:44
+#, python-format
+msgid "IP address: %s"
+msgstr "IP-adresse: %s"
+
+#: ../extensions/deviceicon/network.py:110
+msgid "Disconnect..."
+msgstr "Afbryd..."
+
+#: ../extensions/deviceicon/network.py:114
+msgid "Create new wireless network"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:120
+#: ../src/jarabe/desktop/meshbox.py:264
+msgid "Connecting..."
+msgstr "Forbinder..."
+
+#: ../extensions/deviceicon/network.py:124
+#: ../extensions/deviceicon/network.py:186
+#: ../src/jarabe/desktop/meshbox.py:270
+msgid "Connected"
+msgstr "Tilsluttet"
+
+#: ../extensions/deviceicon/network.py:146
+msgid "Channel"
+msgstr "Kanal"
+
+#: ../extensions/deviceicon/network.py:161
+msgid "Wired Network"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:189
+msgid "Speed"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:415
+#, python-format
+msgid "%s's network %s"
+msgstr ""
+
+#: ../extensions/deviceicon/speaker.py:59
+msgid "My Speakers"
+msgstr "Mine højttalere"
+
+#: ../extensions/deviceicon/speaker.py:133
+msgid "Unmute"
+msgstr "Tænd for lyden"
+
+#: ../extensions/deviceicon/speaker.py:136
+msgid "Mute"
+msgstr "Sluk for lyden"
+
+#: ../extensions/globalkey/screenshot.py:56
+msgid "Mesh"
+msgstr ""
+
+#: ../extensions/globalkey/screenshot.py:58
+#: ../src/jarabe/frame/zoomtoolbar.py:39
+msgid "Group"
+msgstr "Gruppe"
+
+#: ../extensions/globalkey/screenshot.py:60
+#: ../src/jarabe/frame/zoomtoolbar.py:41
+msgid "Home"
+msgstr "Hjem"
+
+#: ../extensions/globalkey/screenshot.py:66
+#: ../src/jarabe/frame/zoomtoolbar.py:43
+msgid "Activity"
+msgstr "Aktivitet"
+
+#: ../extensions/globalkey/screenshot.py:69
+msgid "Screenshot"
+msgstr "Skærmaftryk"
+
+#: ../extensions/globalkey/screenshot.py:71
+#, python-format
+msgid "Screenshot of \"%s\""
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:1
+msgid "Backup URL"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:2
+msgid ""
+"Color for the XO icon that is used throughout the desktop. The string is "
+"composed of the stroke color and fill color, format is that of rbg colors. "
+"Example: #AC32FF,#9A5200"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:3
+msgid "Corner Delay"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:4
+msgid "Delay for the activation of the frame using the corners."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:5
+msgid "Delay for the activation of the frame using the edges."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:6
+msgid "Edge Delay"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:7
+msgid "Favorites Layout"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:8
+msgid "Favorites resume mode"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:9
+msgid ""
+"If TRUE, Sugar will make us searchable for the other users of the Jabber "
+"server."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:10
+msgid "If TRUE, Sugar will show a \"Log out\" option."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:11
+msgid "Jabber Server"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:12
+msgid "Keyboard layouts"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:13
+msgid "Keyboard model"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:14
+msgid "Keyboard options"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:15
+msgid "Layout of the favorites view."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:16
+msgid ""
+"List of keyboard layouts. Each entry should be in the form layout(variant)"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:17
+msgid "List of keyboard options."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:18
+msgid "Power Automatic"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:19
+msgid "Power Automatic."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:20
+msgid "Power Extreme"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:21
+msgid "Power Extreme."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:22
+msgid "Publish to Gadget"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:23
+msgid "Setting for muting the sound device."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:24
+msgid "Show Log out"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:25
+msgid "Sound Muted"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:26
+msgid "The keyboard model to be used"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:28
+msgid "Timezone setting for the system."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:29
+msgid "Url of the jabber server to use."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:30
+msgid "Url where the backup is saved to."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:31
+msgid "User Color"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:32
+msgid "User Name"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:33
+msgid "User name that is used throughout the desktop."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:34
+msgid "Volume Level"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:35
+msgid "Volume level for the sound device."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:36
+msgid ""
+"When in resume mode, clicking on a favorite icon will cause the last entry "
+"for that activity to be resumed."
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:28
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+"sugar-control-panel: ADVARSEL: fandt flere end ét valg med samme navn: %s "
+"modul: %r"
+
+#: ../src/jarabe/controlpanel/cmd.py:30
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr "sugar-control-panel: key=%s ikke et tilgængeligt valg"
+
+#: ../src/jarabe/controlpanel/cmd.py:31
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr "sugar-control-panel: %s"
+
+#. TRANS: Translators, there's a empty line at the end of this string,
+#. which must appear in the translated string (msgstr) as well.
+#: ../src/jarabe/controlpanel/cmd.py:37
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" -c key clear the current value for the key \n"
+" "
+msgstr ""
+"Anvendelse: sugar-control-panel [ valg ] nøgle [ argumenter ... ] \n"
+" Styring af sukker-miljøet. \n"
+" Valg: \n"
+" -h vis denne hjælpebesked og afslut \n"
+" -l vis liste over alle tilgængelige valg \n"
+" -h key vis information om denne nøgle \n"
+" -g key hent den nuværende værdi for nøglen \n"
+" -s key angiv den nuværende værdi for nøglen \n"
+" -c key nulstil den nuværende værdi for nøglen \n"
+" "
+
+#: ../src/jarabe/controlpanel/cmd.py:50
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr "For at dine ændringer træder i kraft skal du genstarte sukker.\n"
+
+#: ../src/jarabe/controlpanel/gui.py:280
+msgid "Warning"
+msgstr "Advarsel"
+
+#: ../src/jarabe/controlpanel/gui.py:281
+#: ../src/jarabe/controlpanel/sectionview.py:42
+msgid "Changes require restart"
+msgstr "Ændringer kræver genstart"
+
+#: ../src/jarabe/controlpanel/gui.py:284
+msgid "Cancel changes"
+msgstr "Fortryd ændringer"
+
+#: ../src/jarabe/controlpanel/gui.py:289 ../src/jarabe/desktop/homebox.py:70
+msgid "Later"
+msgstr "Senere"
+
+#: ../src/jarabe/controlpanel/gui.py:293
+msgid "Restart now"
+msgstr "Genstart nu"
+
+#: ../src/jarabe/controlpanel/toolbar.py:61 ../src/jarabe/intro/window.py:188
+msgid "Done"
+msgstr "Færdig"
+
+#: ../src/jarabe/controlpanel/toolbar.py:115
+#: ../src/jarabe/desktop/homebox.py:68
+#: ../src/jarabe/frame/activitiestray.py:726
+#: ../src/jarabe/frame/activitiestray.py:822
+#: ../src/jarabe/frame/activitiestray.py:850
+msgid "Cancel"
+msgstr "Fortryd"
+
+#: ../src/jarabe/controlpanel/toolbar.py:121
+#: ../src/jarabe/desktop/favoritesview.py:332
+msgid "Ok"
+msgstr "Ok"
+
+#: ../src/jarabe/desktop/activitieslist.py:80
+#: ../src/jarabe/journal/listview.py:147
+msgid "Title"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:91
+msgid "Version"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:105
+#: ../src/jarabe/journal/listview.py:178
+msgid "Date"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:234
+#, python-format
+msgid "Version %s"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:355
+msgid "Confirm erase"
+msgstr "Bekræft sletning"
+
+#: ../src/jarabe/desktop/activitieslist.py:357
+#, python-format
+msgid "Confirm erase: Do you want to permanently erase %s?"
+msgstr "Bekræft sletning: Vil du virkeligt permanent slette %s?"
+
+#: ../src/jarabe/desktop/activitieslist.py:361
+#: ../src/jarabe/frame/clipboardmenu.py:62
+#: ../src/jarabe/view/viewsource.py:218
+msgid "Keep"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:364
+#: ../src/jarabe/desktop/activitieslist.py:407
+#: ../src/jarabe/journal/journaltoolbox.py:360
+#: ../src/jarabe/journal/palettes.py:112
+msgid "Erase"
+msgstr "Slet"
+
+#: ../src/jarabe/desktop/activitieslist.py:428
+msgid "Remove favorite"
+msgstr "Fjern favorit"
+
+#: ../src/jarabe/desktop/activitieslist.py:432
+msgid "Make favorite"
+msgstr "Opret favorit"
+
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:116
+msgid "Freeform"
+msgstr ""
+
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:198
+msgid "Ring"
+msgstr ""
+
+#. TRANS: label for the spiral layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:337
+msgid "Spiral"
+msgstr ""
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:404
+msgid "Box"
+msgstr ""
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:445
+msgid "Triangle"
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:323
+msgid "Registration Failed"
+msgstr "Registrering mislykkedes"
+
+#: ../src/jarabe/desktop/favoritesview.py:324
+#, python-format
+msgid "%s"
+msgstr "%s"
+
+#: ../src/jarabe/desktop/favoritesview.py:326
+msgid "Registration Successful"
+msgstr "Registrering lykkedes"
+
+#: ../src/jarabe/desktop/favoritesview.py:327
+msgid "You are now registered with your school server."
+msgstr "Du er nu registreret ved din skoleserver."
+
+#: ../src/jarabe/desktop/favoritesview.py:671
+msgid "Register"
+msgstr "Registrér"
+
+#: ../src/jarabe/desktop/homebox.py:63
+msgid "Software Update"
+msgstr "Software-opdatering"
+
+#: ../src/jarabe/desktop/homebox.py:64
+msgid "Update your activities to ensure compatibility with your new software"
+msgstr ""
+"Opdatér dine aktiviteter for at sikre kompatibilitet med din nye software"
+
+#: ../src/jarabe/desktop/homebox.py:73
+msgid "Check now"
+msgstr "Undersøg nu"
+
+#: ../src/jarabe/desktop/homebox.py:192
+msgid "List view"
+msgstr "Listevisning"
+
+#: ../src/jarabe/desktop/homebox.py:193
+msgid "<Ctrl>2"
+msgstr "<Ctrl>2"
+
+#: ../src/jarabe/desktop/homebox.py:255
+msgid "Favorites view"
+msgstr "Favoritvisning"
+
+#: ../src/jarabe/desktop/homebox.py:256
+msgid "<Ctrl>1"
+msgstr "<Ctrl>1"
+
+#: ../src/jarabe/desktop/keydialog.py:131
+msgid "Key Type:"
+msgstr "Nøgletype:"
+
+#: ../src/jarabe/desktop/keydialog.py:151
+msgid "Authentication Type:"
+msgstr "Ægthedstype:"
+
+#: ../src/jarabe/desktop/keydialog.py:215
+msgid "WPA & WPA2 Personal"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:224
+msgid "Wireless Security:"
+msgstr ""
+
+#: ../src/jarabe/desktop/meshbox.py:136
+msgid "Connect"
+msgstr "Forbind"
+
+#: ../src/jarabe/desktop/meshbox.py:140
+msgid "Disconnect"
+msgstr "Afbryd"
+
+#. TRANS: Action label for resuming an activity.
+#: ../src/jarabe/desktop/meshbox.py:466
+#: ../src/jarabe/frame/activitiestray.py:761
+#: ../src/jarabe/journal/journaltoolbox.py:428
+#: ../src/jarabe/journal/palettes.py:72 ../src/jarabe/view/palettes.py:64
+msgid "Resume"
+msgstr "Genoptag"
+
+#: ../src/jarabe/desktop/meshbox.py:471
+#: ../src/jarabe/frame/activitiestray.py:235
+msgid "Join"
+msgstr "Tilslut"
+
+#: ../src/jarabe/desktop/schoolserver.py:103
+msgid "Cannot connect to the server."
+msgstr "Kan ikke forbinde til serveren."
+
+#: ../src/jarabe/desktop/schoolserver.py:108
+msgid "The server could not complete the request."
+msgstr "Serveren kunne ikke gennemføre forespørgslen."
+
+#: ../src/jarabe/frame/activitiestray.py:240
+#: ../src/jarabe/frame/activitiestray.py:698
+msgid "Decline"
+msgstr "Afslå"
+
+#: ../src/jarabe/frame/activitiestray.py:650
+#, python-format
+msgid "%dB"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:652
+#, python-format
+msgid "%dKB"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:654
+#, python-format
+msgid "%dMB"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:671
+#, python-format
+msgid "%s of %s"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:683
+#, python-format
+msgid "Transfer from %r"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:693
+msgid "Accept"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:716
+#: ../src/jarabe/frame/activitiestray.py:840
+#, python-format
+msgid "%s (%s)"
+msgstr "%s (%s)"
+
+#: ../src/jarabe/frame/activitiestray.py:750
+#: ../src/jarabe/frame/activitiestray.py:875
+msgid "Dismiss"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:810
+#, python-format
+msgid "Transfer to %r"
+msgstr ""
+
+#: ../src/jarabe/frame/clipboardmenu.py:52 ../src/jarabe/view/palettes.py:218
+msgid "Remove"
+msgstr "Fjern"
+
+#: ../src/jarabe/frame/clipboardmenu.py:57
+#: ../src/jarabe/frame/clipboardmenu.py:80
+msgid "Open"
+msgstr "Åbn"
+
+#: ../src/jarabe/frame/clipboardmenu.py:85
+msgid "Open with"
+msgstr "Åbn med"
+
+#: ../src/jarabe/frame/clipboardobject.py:49
+#, python-format
+msgid "%s clipping"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:37
+msgid "Neighborhood"
+msgstr "Nabolag"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:37
+msgid "F1"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:39
+msgid "F2"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:41
+msgid "F3"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:43
+msgid "F4"
+msgstr ""
+
+#: ../src/jarabe/intro/window.py:124
+msgid "Click to change color:"
+msgstr "Klik for at ændre farve:"
+
+#: ../src/jarabe/intro/window.py:174 ../src/jarabe/journal/detailview.py:103
+msgid "Back"
+msgstr "Tilbage"
+
+#: ../src/jarabe/intro/window.py:191
+msgid "Next"
+msgstr "Næste"
+
+#: ../src/jarabe/journal/expandedentry.py:164
+#: ../src/jarabe/journal/palettes.py:66
+msgid "Untitled"
+msgstr "Uden navn"
+
+#: ../src/jarabe/journal/expandedentry.py:210
+msgid "No preview"
+msgstr "Ingen smugkig"
+
+#: ../src/jarabe/journal/expandedentry.py:229
+#, python-format
+msgid "Kind: %s"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:229
+msgid "Unknown"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:230
+#, python-format
+msgid "Date: %s"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:231
+#, python-format
+msgid "Size: %s"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:253 ../src/jarabe/journal/misc.py:92
+msgid "No date"
+msgstr "Ingen dato"
+
+#: ../src/jarabe/journal/expandedentry.py:260
+msgid "Participants:"
+msgstr "Deltagere:"
+
+#: ../src/jarabe/journal/expandedentry.py:283
+msgid "Description:"
+msgstr "Beskrivelse:"
+
+#: ../src/jarabe/journal/expandedentry.py:309
+msgid "Tags:"
+msgstr "Mærkater:"
+
+#: ../src/jarabe/journal/journalactivity.py:108
+#: ../src/jarabe/journal/volumestoolbar.py:47
+msgid "Journal"
+msgstr "Logbog"
+
+#: ../src/jarabe/journal/journaltoolbox.py:67
+msgid "Search"
+msgstr "Søg"
+
+#: ../src/jarabe/journal/journaltoolbox.py:126
+msgid "Anytime"
+msgstr "Nårsomhelst"
+
+#: ../src/jarabe/journal/journaltoolbox.py:128
+msgid "Today"
+msgstr "Idag"
+
+#: ../src/jarabe/journal/journaltoolbox.py:130
+msgid "Since yesterday"
+msgstr "Siden igår"
+
+#. TRANS: Filter entries modified during the last 7 days.
+#: ../src/jarabe/journal/journaltoolbox.py:132
+msgid "Past week"
+msgstr "Seneste uge"
+
+#. TRANS: Filter entries modified during the last 30 days.
+#: ../src/jarabe/journal/journaltoolbox.py:134
+msgid "Past month"
+msgstr "Seneste måned"
+
+#. TRANS: Filter entries modified during the last 356 days.
+#: ../src/jarabe/journal/journaltoolbox.py:136
+msgid "Past year"
+msgstr "Seneste år"
+
+#: ../src/jarabe/journal/journaltoolbox.py:143
+msgid "Anyone"
+msgstr "Enhver"
+
+#: ../src/jarabe/journal/journaltoolbox.py:145
+msgid "My friends"
+msgstr "Mine venner"
+
+#: ../src/jarabe/journal/journaltoolbox.py:146
+msgid "My class"
+msgstr "Min klasse"
+
+#: ../src/jarabe/journal/journaltoolbox.py:274
+msgid "Anything"
+msgstr "Hvadsomhelst"
+
+#: ../src/jarabe/journal/journaltoolbox.py:350
+#: ../src/jarabe/journal/palettes.py:90
+msgid "Copy"
+msgstr "Kopiér"
+
+#. TRANS: Action label for starting an entry.
+#: ../src/jarabe/journal/journaltoolbox.py:431
+#: ../src/jarabe/journal/palettes.py:75
+msgid "Start"
+msgstr "Start"
+
+#: ../src/jarabe/journal/listview.py:361
+msgid "Your Journal is empty"
+msgstr "Din logbog er tom"
+
+#: ../src/jarabe/journal/listview.py:363
+msgid "No matching entries"
+msgstr "Ingen modsvarende emner"
+
+#: ../src/jarabe/journal/listview.py:374
+msgid "Clear search"
+msgstr ""
+
+#: ../src/jarabe/journal/modalalert.py:63
+msgid "Your Journal is full"
+msgstr "Din logbog er fuld"
+
+#: ../src/jarabe/journal/modalalert.py:67
+msgid "Please delete some old Journal entries to make space for new ones."
+msgstr "Slet nogle logbogsindlæg for at frigøre plads for nye indlæg."
+
+#: ../src/jarabe/journal/modalalert.py:79
+msgid "Show Journal"
+msgstr "Vis logbog"
+
+#: ../src/jarabe/journal/objectchooser.py:146
+msgid "Choose an object"
+msgstr "Vælg et objekt"
+
+#: ../src/jarabe/journal/objectchooser.py:151
+#: ../src/jarabe/view/viewsource.py:308
+msgid "Close"
+msgstr "Luk"
+
+#: ../src/jarabe/journal/palettes.py:73
+msgid "Resume with"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:76
+msgid "Start with"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:98
+msgid "Send to"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:107
+msgid "View Details"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:185
+msgid "No friends present"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:190
+msgid "No valid connection found"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:218
+msgid "No activity to resume entry"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:220
+msgid "No activity to start entry"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:62
+msgid "Remove friend"
+msgstr "Fjern ven"
+
+#: ../src/jarabe/view/buddymenu.py:65
+msgid "Make friend"
+msgstr "Opret ven"
+
+#: ../src/jarabe/view/buddymenu.py:82
+msgid "Shutdown"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:90
+msgid "Logout"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:95
+msgid "My Settings"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:130
+#, python-format
+msgid "Invite to %s"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:45
+msgid "Starting..."
+msgstr "Starter..."
+
+#. TODO: share-with, keep
+#: ../src/jarabe/view/palettes.py:71
+msgid "View Source"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:82
+msgid "Stop"
+msgstr "Stop"
+
+#: ../src/jarabe/view/palettes.py:122
+msgid "Start new"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:171
+msgid "Show contents"
+msgstr "Vis indhold"
+
+#: ../src/jarabe/view/palettes.py:193 ../src/jarabe/view/palettes.py:243
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr "%(free_space)d MB ledig"
+
+#: ../src/jarabe/view/viewsource.py:208
+msgid "Instance Source"
+msgstr ""
+
+#: ../src/jarabe/view/viewsource.py:233
+msgid "Source"
+msgstr ""
+
+#: ../src/jarabe/view/viewsource.py:292
+msgid "Activity Bundle Source"
+msgstr ""
+
+#: ../src/jarabe/view/viewsource.py:299
+#, python-format
+msgid "View source: %r"
+msgstr ""
+
+#~ msgid "Cannot obtain data needed for registration."
+#~ msgstr "Kan ikke skaffe data nødvendig for registrering."
+
+#~ msgid "Restart"
+#~ msgstr "Genstart"
diff --git a/shell/po/de.po b/shell/po/de.po
new file mode 100644
index 0000000..5ca6d88
--- /dev/null
+++ b/shell/po/de.po
@@ -0,0 +1,1711 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+# This file is distributed under the same license as the PACKAGE package.
+# Fabian Affolter <fab@fedoraproject.org>, 2007.
+msgid ""
+msgstr ""
+"Project-Id-Version: sugar\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-03-11 00:31-0500\n"
+"PO-Revision-Date: 2010-03-29 01:49+0200\n"
+"Last-Translator: Markus <m.slg@gmx.de>\n"
+"Language-Team: German <fedora-trans-de@redhat.com>\n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Pootle 2.0.3\n"
+"X-Poedit-Language: German\n"
+
+#: ../extensions/cpsection/aboutme/__init__.py:24
+msgid "About Me"
+msgstr "Über mich"
+
+#: ../extensions/cpsection/aboutme/model.py:43
+msgid "You must enter a name."
+msgstr "Bitte einen Namen eingeben."
+
+#: ../extensions/cpsection/aboutme/model.py:68
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr "Linie: Farbe=%s Farbton=%s"
+
+#: ../extensions/cpsection/aboutme/model.py:71
+#, python-format
+msgid "stroke: %s"
+msgstr "Linie: %s"
+
+#: ../extensions/cpsection/aboutme/model.py:73
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr "Füllung: Farbe=%s Farbton=%s"
+
+#: ../extensions/cpsection/aboutme/model.py:75
+#, python-format
+msgid "fill: %s"
+msgstr "Füllung: %s"
+
+#: ../extensions/cpsection/aboutme/model.py:86
+msgid "Error in specified color modifiers."
+msgstr "Fehler in den angegebenen Farbänderungen."
+
+#: ../extensions/cpsection/aboutme/model.py:89
+msgid "Error in specified colors."
+msgstr "Fehler in den angegebenen Farben."
+
+#: ../extensions/cpsection/aboutme/view.py:94 ../src/jarabe/intro/window.py:93
+msgid "Name:"
+msgstr "Name:"
+
+#: ../extensions/cpsection/aboutme/view.py:128
+msgid "Click to change your color:"
+msgstr "Klicken, um deine Farbe zu wechseln:"
+
+#: ../extensions/cpsection/aboutcomputer/__init__.py:21
+msgid "About my Computer"
+msgstr "Über meinen Computer"
+
+#: ../extensions/cpsection/aboutcomputer/model.py:28
+msgid "Not available"
+msgstr "Nicht verfügbar"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:60
+msgid "Identity"
+msgstr "Identität"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:69
+msgid "Serial Number:"
+msgstr "Seriennummer:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:91
+msgid "Software"
+msgstr "Software"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:100
+msgid "Build:"
+msgstr "Version:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:115
+msgid "Sugar:"
+msgstr "Sugar:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:131
+msgid "Firmware:"
+msgstr "Firmware:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:146
+msgid "Wireless Firmware:"
+msgstr "WLAN-Firmware:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:169
+msgid "Copyright and License"
+msgstr "Copyright und Lizenz"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:184
+msgid ""
+"Sugar is the graphical user interface that you are looking at. Sugar is free "
+"software, covered by the GNU General Public License, and you are welcome to "
+"change it and/or distribute copies of it under certain conditions described "
+"therein."
+msgstr ""
+"Sugar ist die grafische Benutzeroberfläche, die du vor die siehst. Sugar ist "
+"freie Software und unterliegt der GNU General Public License. Im Rahmen der "
+"darin festgelegten Bedingungen ist es erlaubt, die Software zu verändern "
+"und/oder Kopien davon zu erstellen und zu verteilen."
+
+#: ../extensions/cpsection/aboutcomputer/view.py:196
+msgid "Full license:"
+msgstr "Vollständige Lizenz:"
+
+#: ../extensions/cpsection/datetime/__init__.py:21
+msgid "Date & Time"
+msgstr "Datum & Uhrzeit"
+
+#: ../extensions/cpsection/datetime/model.py:87
+msgid "Error timezone does not exist."
+msgstr "Fehler: unbekannte Zeitzone."
+
+#: ../extensions/cpsection/datetime/view.py:68 ../data/sugar.schemas.in.h:47
+msgid "Timezone"
+msgstr "Zeitzone"
+
+# (Markus S.) 'Rahmen'? (Vorschlag von hmeyer)
+#: ../extensions/cpsection/frame/__init__.py:21
+msgid "Frame"
+msgstr "Rahmen"
+
+#: ../extensions/cpsection/frame/model.py:38
+#: ../extensions/cpsection/frame/model.py:60
+msgid "Value must be an integer."
+msgstr "Der Wert muss ganzzahlig sein."
+
+#: ../extensions/cpsection/frame/view.py:26
+msgid "never"
+msgstr "nie"
+
+# (Markus S.) ' unmittelbar'?
+#: ../extensions/cpsection/frame/view.py:27
+msgid "instantaneous"
+msgstr "sofort"
+
+#: ../extensions/cpsection/frame/view.py:28
+#, python-format
+msgid "%s seconds"
+msgstr "%s Sekunden"
+
+#: ../extensions/cpsection/frame/view.py:52
+msgid "Activation Delay"
+msgstr "Aktivierungsverzögerung"
+
+#: ../extensions/cpsection/frame/view.py:76
+msgid "Corner"
+msgstr "Ecke"
+
+#: ../extensions/cpsection/frame/view.py:111
+msgid "Edge"
+msgstr "Kante"
+
+#: ../extensions/cpsection/keyboard/__init__.py:21
+#: ../extensions/cpsection/keyboard/view.py:31
+msgid "Keyboard"
+msgstr "Tastatur"
+
+#: ../extensions/cpsection/keyboard/view.py:189
+msgid "Keyboard Model"
+msgstr "Tastaturmodell"
+
+#: ../extensions/cpsection/keyboard/view.py:248
+msgid "Key(s) to change layout"
+msgstr "Taste(n) zum Layoutwechsel"
+
+#: ../extensions/cpsection/keyboard/view.py:318
+msgid "Keyboard Layout(s)"
+msgstr "Tastaturlayout(s)"
+
+#: ../extensions/cpsection/language/__init__.py:21
+#: ../extensions/cpsection/language/view.py:33
+msgid "Language"
+msgstr "Sprache"
+
+#: ../extensions/cpsection/language/model.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr "Zugriff auf ~/.i18n nicht möglich. Erzeuge daher Standardeinstellungen."
+
+#: ../extensions/cpsection/language/model.py:124
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr "Sprache für Code=%s konnte nicht ermittelt werden."
+
+#: ../extensions/cpsection/language/model.py:144
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr "Entschuldigung, ich spreche nicht '%s'."
+
+#: ../extensions/cpsection/language/view.py:56
+msgid ""
+"Add languages in the order you prefer. If a translation is not available, "
+"the next in the list will be used."
+msgstr ""
+"Sprachen in der gewünschten Reihenfolge hinzufügen. Wenn eine Übersetzung "
+"nicht verfügbar ist, wird die nächste aus der Liste benutzt."
+
+#: ../extensions/cpsection/modemconfiguration/__init__.py:21
+msgid "Modem Configuration"
+msgstr "Modem-Konfiguration"
+
+#: ../extensions/cpsection/modemconfiguration/view.py:91
+msgid "Username:"
+msgstr "Benutzername:"
+
+#: ../extensions/cpsection/modemconfiguration/view.py:102
+msgid "Password:"
+msgstr "Passwort:"
+
+#: ../extensions/cpsection/modemconfiguration/view.py:113
+msgid "Number:"
+msgstr "Nummer:"
+
+#: ../extensions/cpsection/modemconfiguration/view.py:124
+msgid "Access Point Name (APN):"
+msgstr "Access-Point-Name (APN):"
+
+#: ../extensions/cpsection/modemconfiguration/view.py:135
+msgid "Personal Identity Number (PIN):"
+msgstr "Persönliche Kennnumer (PIN):"
+
+#: ../extensions/cpsection/modemconfiguration/view.py:146
+msgid "Personal Unblocking Key (PUK):"
+msgstr "Persönlicher Freischaltschlüssel (PUK):"
+
+#: ../extensions/cpsection/modemconfiguration/view.py:167
+msgid ""
+"You will need to provide the following information to set up a mobile "
+"broadband connection to a cellular (3G) network."
+msgstr ""
+"Du musst die folgenden Daten eingeben, um eine Verbindung zu einem "
+"Mobilfunknetz (3G) aufzubauen."
+
+#: ../extensions/cpsection/network/__init__.py:21
+#: ../extensions/cpsection/network/view.py:28
+msgid "Network"
+msgstr "Netzwerk"
+
+#: ../extensions/cpsection/network/model.py:79
+msgid "State is unknown."
+msgstr "Status ist nicht bekannt."
+
+# (Markus S.) vgl. http://lists.laptop.org/pipermail/localization/2008-July/001232.html
+#: ../extensions/cpsection/network/model.py:105
+msgid "Error in specified radio argument use on/off."
+msgstr "Fehler im angegebenen Funknetzparameter -- on/off verwenden."
+
+# (Markus S.) vgl. http://lists.laptop.org/pipermail/localization/2008-July/001232.html
+#: ../extensions/cpsection/network/model.py:137
+msgid "Error in specified argument use 0/1."
+msgstr "Fehler im angegebenen Parameter -- 0/1 verwenden."
+
+#: ../extensions/cpsection/network/view.py:59
+msgid "Wireless"
+msgstr "Funknetzwerk"
+
+#: ../extensions/cpsection/network/view.py:67
+msgid "Turn off the wireless radio to save battery life"
+msgstr "Schalte das Funknetz aus, um die Lebensdauer der Batterie zu erhöhen."
+
+# (Markus S,) war 'Radio:'
+#: ../extensions/cpsection/network/view.py:80
+msgid "Radio"
+msgstr "Funknetz"
+
+#: ../extensions/cpsection/network/view.py:96
+msgid "Discard network history if you have trouble connecting to the network"
+msgstr ""
+"Verwirf die Netzwerk-Chronik, wenn du Schwierigkeiten hast, dich mit dem "
+"Netzwerk zu verbinden."
+
+#: ../extensions/cpsection/network/view.py:105
+msgid "Discard network history"
+msgstr "Netzwerk-Chronik verwerfen"
+
+#: ../extensions/cpsection/network/view.py:118
+msgid "Collaboration"
+msgstr "Zusammenarbeit"
+
+#: ../extensions/cpsection/network/view.py:126
+msgid ""
+"The server is the equivalent of what room you are in; people on the same "
+"server will be able to see each other, even when they aren't on the same "
+"network."
+msgstr ""
+"Der Server entspricht dem Raum, in dem du dich befindest; Personen am selben "
+"Server können einander sehen, selbst wenn sie sich nicht im selben Netzwerk "
+"befinden."
+
+#: ../extensions/cpsection/network/view.py:136
+msgid "Server:"
+msgstr "Server:"
+
+#: ../extensions/cpsection/power/__init__.py:21
+msgid "Power"
+msgstr "Energieversorgung"
+
+# (Markus S.) vgl. http://lists.laptop.org/pipermail/localization/2008-July/001232.html
+#: ../extensions/cpsection/power/model.py:85
+msgid "Error in automatic pm argument, use on/off."
+msgstr ""
+"Fehler im automatischen Energieverwaltungsparameter -- on/off verwenden."
+
+# (Markus S.) vgl. http://lists.laptop.org/pipermail/localization/2008-July/001232.html
+#: ../extensions/cpsection/power/model.py:112
+msgid "Error in extreme pm argument, use on/off."
+msgstr "Fehler im extremen Energieverwaltungsparameter -- on/off verwenden."
+
+#: ../extensions/cpsection/power/view.py:47
+msgid "Power management"
+msgstr "Energieverwaltung"
+
+#: ../extensions/cpsection/power/view.py:57
+msgid "Automatic power management (increases battery life)"
+msgstr "Automatische Energieverwaltung (erhöht die Lebensdauer der Batterie)"
+
+#: ../extensions/cpsection/power/view.py:85
+msgid ""
+"Extreme power management (disableswireless radio, increases battery life)"
+msgstr ""
+"Extreme Energieverwaltung (deaktiviert das Funknetz, erhöht die Lebensdauer "
+"der Batterie)"
+
+#: ../extensions/cpsection/updater/__init__.py:21
+msgid "Software update"
+msgstr "Software-Aktualisierung"
+
+#: ../extensions/cpsection/updater/view.py:63
+msgid ""
+"Software updates correct errors, eliminate security vulnerabilities, and "
+"provide new features."
+msgstr ""
+"Software-Aktualisierungen beheben Fehler, schließen Sicherheitslücken und "
+"bieten neue Funktionen."
+
+#: ../extensions/cpsection/updater/view.py:125
+#, python-format
+msgid "Checking %s..."
+msgstr "Prüfe %s..."
+
+#: ../extensions/cpsection/updater/view.py:127
+#, python-format
+msgid "Downloading %s..."
+msgstr "Lade %s herunter..."
+
+#: ../extensions/cpsection/updater/view.py:129
+#, python-format
+msgid "Updating %s..."
+msgstr "Aktualisiere %s..."
+
+#: ../extensions/cpsection/updater/view.py:139
+msgid "Your software is up-to-date"
+msgstr "Deine Software ist auf dem neuesten Stand."
+
+#: ../extensions/cpsection/updater/view.py:141
+#, python-format
+msgid "You can install %s update"
+msgid_plural "You can install %s updates"
+msgstr[0] "Du kannst %s Update installieren."
+msgstr[1] "Du kannst %s Updates installieren."
+
+#: ../extensions/cpsection/updater/view.py:159
+msgid "Checking for updates..."
+msgstr "Suche nach Updates..."
+
+#: ../extensions/cpsection/updater/view.py:164
+msgid "Installing updates..."
+msgstr "Installiere Updates..."
+
+#: ../extensions/cpsection/updater/view.py:172
+#, python-format
+msgid "%s update was installed"
+msgid_plural "%s updates were installed"
+msgstr[0] "%s Update wurde installiert."
+msgstr[1] "%s Updates wurden installiert."
+
+#: ../extensions/cpsection/updater/view.py:253
+msgid "Install selected"
+msgstr "Auswahl installieren"
+
+#: ../extensions/cpsection/updater/view.py:274
+#, python-format
+msgid "Download size: %s"
+msgstr "Downloadgröße: %s"
+
+#: ../extensions/cpsection/updater/view.py:362
+#, python-format
+msgid "From version %(current)d to %(new)s (Size: %(size)s)"
+msgstr "Von Version %(current)d auf %(new)s (Größe: %(size)s)"
+
+#. TRANS: download size is 0
+#: ../extensions/cpsection/updater/view.py:382
+msgid "None"
+msgstr "Nichts"
+
+#. TRANS: download size of very small updates
+#: ../extensions/cpsection/updater/view.py:385
+msgid "1 KB"
+msgstr "1 KB"
+
+#. TRANS: download size of small updates, e.g. '250 KB'
+#: ../extensions/cpsection/updater/view.py:388
+#, python-format
+msgid "%.0f KB"
+msgstr "%.0f KB"
+
+#. TRANS: download size of updates, e.g. '2.3 MB'
+#: ../extensions/cpsection/updater/view.py:391
+#, python-format
+msgid "%.1f MB"
+msgstr "%.1f MB"
+
+#: ../extensions/deviceicon/battery.py:58
+msgid "My Battery"
+msgstr "Meine Batterie"
+
+#: ../extensions/deviceicon/battery.py:137
+msgid "Removed"
+msgstr "Entfernt"
+
+#: ../extensions/deviceicon/battery.py:140
+msgid "Charging"
+msgstr "Aufladen"
+
+#: ../extensions/deviceicon/battery.py:143
+msgid "Very little power remaining"
+msgstr "Sehr wenig Ladung verbleibend"
+
+#: ../extensions/deviceicon/battery.py:149
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr "%(hour)d:%(min).2d verbleibend"
+
+#: ../extensions/deviceicon/battery.py:152
+msgid "Charged"
+msgstr "Aufgeladen"
+
+#: ../extensions/deviceicon/network.py:49
+#, python-format
+msgid "IP address: %s"
+msgstr "IP-Addresse: %s"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#: ../extensions/deviceicon/network.py:112
+msgid "Disconnect..."
+msgstr "Verbindung trennen..."
+
+#: ../extensions/deviceicon/network.py:117
+msgid "Create new wireless network"
+msgstr "Neues Funknetzwerk erstellen"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#: ../extensions/deviceicon/network.py:123
+#: ../extensions/deviceicon/network.py:285
+#: ../src/jarabe/desktop/meshbox.py:247 ../src/jarabe/desktop/meshbox.py:536
+msgid "Connecting..."
+msgstr "Verbinde..."
+
+# TODO: show the channel number
+#: ../extensions/deviceicon/network.py:127
+#: ../extensions/deviceicon/network.py:199
+#: ../extensions/deviceicon/network.py:289
+#: ../src/jarabe/desktop/meshbox.py:253 ../src/jarabe/desktop/meshbox.py:542
+msgid "Connected"
+msgstr "Verbunden"
+
+#: ../extensions/deviceicon/network.py:159
+msgid "Channel"
+msgstr "Kanal"
+
+#: ../extensions/deviceicon/network.py:174
+msgid "Wired Network"
+msgstr "Kabelnetzwerk"
+
+# (Markus S.) War 'Geschwindigkeit'
+#: ../extensions/deviceicon/network.py:202
+msgid "Speed"
+msgstr "Übertragungsrate"
+
+#: ../extensions/deviceicon/network.py:229
+msgid "Wireless modem"
+msgstr "Funkmodem"
+
+#: ../extensions/deviceicon/network.py:277
+msgid "Please wait..."
+msgstr "Bitte warten..."
+
+#: ../extensions/deviceicon/network.py:280
+#: ../src/jarabe/desktop/meshbox.py:163 ../src/jarabe/desktop/meshbox.py:493
+msgid "Connect"
+msgstr "Verbinden"
+
+#: ../extensions/deviceicon/network.py:281
+msgid "Disconnected"
+msgstr "Nicht verbunden"
+
+#: ../extensions/deviceicon/network.py:284
+#: ../src/jarabe/controlpanel/toolbar.py:115
+#: ../src/jarabe/desktop/homebox.py:68
+#: ../src/jarabe/frame/activitiestray.py:700
+#: ../src/jarabe/frame/activitiestray.py:799
+#: ../src/jarabe/frame/activitiestray.py:827
+msgid "Cancel"
+msgstr "Abbrechen"
+
+# (mschlager) war 'Nicht verbunden', ich würde aber eher erwarten, dass das die Beschriftung eines Menüeintrags ist, mit dem man eine Verbindung trennt, was dann in der Folge die Meldung 'Disconnecting...' liefert.
+#: ../extensions/deviceicon/network.py:288
+#: ../src/jarabe/desktop/meshbox.py:167
+msgid "Disconnect"
+msgstr "Verbindung trennen"
+
+#: ../extensions/deviceicon/network.py:292
+msgid "Sim requires Pin/Puk"
+msgstr "Sim benötigt PIN/PUK"
+
+#: ../extensions/deviceicon/network.py:293
+msgid "Authentication Error"
+msgstr "Authentifizierungsfehler"
+
+#: ../extensions/deviceicon/network.py:538
+#, python-format
+msgid "%s's network"
+msgstr "Netzwerk von %s"
+
+#: ../extensions/deviceicon/network.py:605
+#: ../extensions/deviceicon/network.py:664
+msgid "Mesh Network"
+msgstr "Maschennetzwerk"
+
+#: ../extensions/deviceicon/network.py:869
+#, python-format
+msgid "Data sent %d KB / received %d KB"
+msgstr "gesendet %d KB / empfangen %d KB"
+
+#: ../extensions/deviceicon/network.py:880
+msgid "Connection time "
+msgstr "Verbindungsdauer "
+
+#: ../extensions/deviceicon/speaker.py:59
+msgid "My Speakers"
+msgstr "Meine Lautsprecher"
+
+#: ../extensions/deviceicon/speaker.py:133
+msgid "Unmute"
+msgstr "Laut schalten"
+
+#: ../extensions/deviceicon/speaker.py:136
+msgid "Mute"
+msgstr "Stumm schalten"
+
+# (Markus S.) 'Zelle'?
+#: ../extensions/globalkey/screenshot.py:59
+msgid "Mesh"
+msgstr "Masche"
+
+#: ../extensions/globalkey/screenshot.py:61
+#: ../src/jarabe/frame/zoomtoolbar.py:39
+msgid "Group"
+msgstr "Gruppe"
+
+# (Markus S.) war 'Zuhause', vgl. stuffer-sheet
+#: ../extensions/globalkey/screenshot.py:63
+#: ../src/jarabe/frame/zoomtoolbar.py:41
+msgid "Home"
+msgstr "Startbildschirm"
+
+#: ../extensions/globalkey/screenshot.py:69
+#: ../src/jarabe/frame/zoomtoolbar.py:43
+msgid "Activity"
+msgstr "Aktivität"
+
+#: ../extensions/globalkey/screenshot.py:72
+msgid "Screenshot"
+msgstr "Bildschirmfoto"
+
+#: ../extensions/globalkey/screenshot.py:74
+#, python-format
+msgid "Screenshot of \"%s\""
+msgstr "Bildschirmfoto von \"%s\""
+
+#: ../data/sugar.schemas.in.h:1
+msgid ""
+"\"disabled\" to ask nick on initialization; \"system\" to reuse UNIX account "
+"long name."
+msgstr ""
+"\"Deaktiviert\" fragt bei der Initalisierung nach einem Nicknamen; \"System\" "
+"nutzt den vollständigen UNIX-Benutzernamen."
+
+#: ../data/sugar.schemas.in.h:2
+msgid "Additional directories which can contain updated translations."
+msgstr ""
+"Weitere Verzeichnisse, die aktualisierte Übersetzungen enthalten können."
+
+#: ../data/sugar.schemas.in.h:3
+msgid "Backup URL"
+msgstr "Backup-URL"
+
+#: ../data/sugar.schemas.in.h:4
+msgid ""
+"Color for the XO icon that is used throughout the desktop. The string is "
+"composed of the stroke color and fill color, format is that of rbg colors. "
+"Example: #AC32FF,#9A5200"
+msgstr ""
+"Farbe des XO-Symbols, das überall auf dem Desktop benutzt wird. Die "
+"Zeichenkette setzt sich aus der Linien- und der Füllfarbe zusammen, die "
+"jeweils als RGB-Farben angeben werden. Beispiel: #AC32FF,#9A5200"
+
+#: ../data/sugar.schemas.in.h:5
+msgid "Corner Delay"
+msgstr "Eckenverzögerung"
+
+#: ../data/sugar.schemas.in.h:6
+msgid "Default font face"
+msgstr "Standard-Schriftart"
+
+#: ../data/sugar.schemas.in.h:7
+msgid "Default font size"
+msgstr "Standard-Schriftgröße"
+
+#: ../data/sugar.schemas.in.h:8
+msgid "Default nick"
+msgstr "Standard-Benutzername"
+
+#: ../data/sugar.schemas.in.h:9
+msgid "Delay for the activation of the frame using the corners."
+msgstr "Verzögerung bei der Aktivierung eines Rahmens über die Ecken."
+
+#: ../data/sugar.schemas.in.h:10
+msgid "Delay for the activation of the frame using the edges."
+msgstr "Verzögerung bei der Aktivierung eines Rahmens über die Ränder."
+
+#: ../data/sugar.schemas.in.h:11
+msgid "Directory to search for translations"
+msgstr "Verzeichnis zur Suche nach Übersetzungen"
+
+#: ../data/sugar.schemas.in.h:12
+msgid "Edge Delay"
+msgstr "Randverzögerung"
+
+#: ../data/sugar.schemas.in.h:13
+msgid "Favorites Layout"
+msgstr "Favoriten-Layout"
+
+#: ../data/sugar.schemas.in.h:14
+msgid "Favorites resume mode"
+msgstr "Favoriten-Wiederaufnahmemodus"
+
+#: ../data/sugar.schemas.in.h:15
+msgid "Font face that is used throughout the desktop."
+msgstr "Schriftart, die auf dem Desktop benutzt wird."
+
+#: ../data/sugar.schemas.in.h:16
+msgid "Font size that is used throughout the desktop."
+msgstr "Schriftgröße, die auf dem Desktop benutzt wird."
+
+#: ../data/sugar.schemas.in.h:17
+msgid "GSM network APN"
+msgstr "GSM-Netzwerk APN"
+
+#: ../data/sugar.schemas.in.h:18
+msgid "GSM network PIN"
+msgstr "GSM-Netzwerk PIN"
+
+#: ../data/sugar.schemas.in.h:19
+msgid "GSM network PUK"
+msgstr "GSM-Netzwerk PUK"
+
+#: ../data/sugar.schemas.in.h:20
+msgid "GSM network access point name configuration"
+msgstr "GSM-Netzwerk Namenseinstellung Zugriffspunkt"
+
+#: ../data/sugar.schemas.in.h:21
+msgid "GSM network number"
+msgstr "GSM-Netzwerk Nummer"
+
+#: ../data/sugar.schemas.in.h:22
+msgid "GSM network password"
+msgstr "GSM-Netzwerk Passwort"
+
+#: ../data/sugar.schemas.in.h:23
+msgid "GSM network password configuration"
+msgstr "GSM-Netzwerk Passworteinstellung"
+
+#: ../data/sugar.schemas.in.h:24
+msgid "GSM network personal identification number configuration"
+msgstr "GSM-Netzwerk PIN-Einstellung"
+
+#: ../data/sugar.schemas.in.h:25
+msgid "GSM network personal unlock key configuration"
+msgstr "GSM-Netzwerk PUK-Einstellung"
+
+#: ../data/sugar.schemas.in.h:26
+msgid "GSM network telephone number configuration"
+msgstr "GSM-Netzwerk Telefonnummereinstellung"
+
+#: ../data/sugar.schemas.in.h:27
+msgid "GSM network username"
+msgstr "GSM-Netzwerk Benutzername"
+
+#: ../data/sugar.schemas.in.h:28
+msgid "GSM network username configuration"
+msgstr "GSM-Netzwerk Benutzernameneinstellung"
+
+#: ../data/sugar.schemas.in.h:29
+msgid ""
+"If TRUE, Sugar will make us searchable for the other users of the Jabber "
+"server."
+msgstr ""
+"Falls WAHR, wird Sugar es für andere Nutzer des Jabber-Servers zulassen, uns "
+"zu suchen."
+
+#: ../data/sugar.schemas.in.h:30
+msgid "If TRUE, Sugar will show a \"Log out\" option."
+msgstr "Falls WAHR, wird Sugar die Option \"Abmelden\" anzeigen."
+
+#: ../data/sugar.schemas.in.h:31
+msgid "Jabber Server"
+msgstr "Jabber-Server"
+
+#: ../data/sugar.schemas.in.h:32
+msgid "Keyboard layouts"
+msgstr "Tastaturlayouts"
+
+#: ../data/sugar.schemas.in.h:33
+msgid "Keyboard model"
+msgstr "Tastaturmodell"
+
+#: ../data/sugar.schemas.in.h:34
+msgid "Keyboard options"
+msgstr "Tastatureinstellungen"
+
+#: ../data/sugar.schemas.in.h:35
+msgid "Layout of the favorites view."
+msgstr "Layout der Favoriten-Ansicht."
+
+#: ../data/sugar.schemas.in.h:36
+msgid ""
+"List of keyboard layouts. Each entry should be in the form layout(variant)"
+msgstr ""
+"Liste der Tastaturlayouts. Jeder Eintrag sollte von der Form "
+"Layout(Variante) sein."
+
+#: ../data/sugar.schemas.in.h:37
+msgid "List of keyboard options."
+msgstr "Liste der Tastatureinstellungen."
+
+#: ../data/sugar.schemas.in.h:38
+msgid "Power Automatic"
+msgstr "Automatische Energieverwaltung"
+
+#: ../data/sugar.schemas.in.h:39
+msgid "Power Automatic."
+msgstr "Automatische Energieverwaltung."
+
+# (Markus S.) war 'Extreme Energieverwaltung'
+#: ../data/sugar.schemas.in.h:40
+msgid "Power Extreme"
+msgstr "Extremes Energiesparen"
+
+# (Markus S.) war 'Extreme Energieverwaltung'
+#: ../data/sugar.schemas.in.h:41
+msgid "Power Extreme."
+msgstr "Extremes Energiesparen."
+
+#: ../data/sugar.schemas.in.h:42
+msgid "Publish to Gadget"
+msgstr "Veröffentlichen auf Gerät"
+
+#: ../data/sugar.schemas.in.h:43
+msgid "Setting for muting the sound device."
+msgstr "Einstellung zum Stummschalten der Audio-Ausgabe."
+
+#: ../data/sugar.schemas.in.h:44
+msgid "Show Log out"
+msgstr "Abmelden anzeigen"
+
+#: ../data/sugar.schemas.in.h:45
+msgid "Sound Muted"
+msgstr "Stummgeschaltet"
+
+#: ../data/sugar.schemas.in.h:46
+msgid "The keyboard model to be used"
+msgstr "Das zu verwendende Tastaturmodell"
+
+#: ../data/sugar.schemas.in.h:48
+msgid "Timezone setting for the system."
+msgstr "Zeitzoneneinstellung des Systems."
+
+#: ../data/sugar.schemas.in.h:49
+msgid "Url of the jabber server to use."
+msgstr "URL des zu nutzenden Jabber-Servers."
+
+#: ../data/sugar.schemas.in.h:50
+msgid "Url where the backup is saved to."
+msgstr "URL, unter der das Backup gespeichert wird."
+
+#: ../data/sugar.schemas.in.h:51
+msgid "User Color"
+msgstr "Benutzerfarbe"
+
+#: ../data/sugar.schemas.in.h:52
+msgid "User Name"
+msgstr "Benutzername"
+
+#: ../data/sugar.schemas.in.h:53
+msgid "User name that is used throughout the desktop."
+msgstr "Benutzername, der überall auf dem Desktop benutzt wird."
+
+#: ../data/sugar.schemas.in.h:54
+msgid "Volume Level"
+msgstr "Lautstärke"
+
+#: ../data/sugar.schemas.in.h:55
+msgid "Volume level for the sound device."
+msgstr "Lautstärkepegel für die Audio-Ausgabe."
+
+#: ../data/sugar.schemas.in.h:56
+msgid ""
+"When in resume mode, clicking on a favorite icon will cause the last entry "
+"for that activity to be resumed."
+msgstr ""
+"Im Wiederaufnahmemodus führt ein Anklicken des Favoriten-Symbols dazu, dass "
+"der letzte Eintrag für diese Aktivität fortgesetzt wird."
+
+#: ../src/jarabe/controlpanel/cmd.py:28
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+"sugar-control-panel: WARNUNG, mehr als eine Auswahl mit dem gleichen Namen "
+"gefunden: %s module: %r"
+
+#: ../src/jarabe/controlpanel/cmd.py:30
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr "sugar-control-panel: Parameter=%s ist keine verfügbare Option"
+
+#: ../src/jarabe/controlpanel/cmd.py:31
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr "sugar-control-panel: %s"
+
+# (Markus S.) war 'Benutzung: sugar-control-panel [ option ] key [ args ... ] \n'; 'Parameter' war 'Key'.
+# TRANS: Translators, there's a empty line at the end of this string,
+# which must appear in the translated string (msgstr) as well.
+#. TRANS: Translators, there's a empty line at the end of this string,
+#. which must appear in the translated string (msgstr) as well.
+#: ../src/jarabe/controlpanel/cmd.py:37
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" -c key clear the current value for the key \n"
+" "
+msgstr ""
+"Benutzung: sugar-control-panel [ Option ] Parameter [ Argumente ... ] \n"
+" Einstellungen für Sugar. \n"
+" Optionen: \n"
+" -h Diese Hilfe anzeigen und beenden\n"
+" -l Alle verfügbaren Optionen auflisten\n"
+" -h Parameter Informationen zu diesem Parameter zeigen\n"
+" -g Parameter Den aktuellen Wert für diesen Parameter auslesen\n"
+" -s Parameter Den aktuellen Wert für diesen Parameter festlegen\n"
+" -c Parameter Den aktuellen Wert für diesen Parameter zurücksetzen\n"
+" "
+
+#: ../src/jarabe/controlpanel/cmd.py:50
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr "Um die Änderungen zu übernehmen, muss Sugar neu gestartet werden.\n"
+
+#: ../src/jarabe/controlpanel/gui.py:281
+msgid "Warning"
+msgstr "Warnung"
+
+#: ../src/jarabe/controlpanel/gui.py:282
+#: ../src/jarabe/controlpanel/sectionview.py:42
+msgid "Changes require restart"
+msgstr "Neustart zur Übernahme der Änderungen notwendig"
+
+#: ../src/jarabe/controlpanel/gui.py:285
+msgid "Cancel changes"
+msgstr "Änderungen verwerfen"
+
+#: ../src/jarabe/controlpanel/gui.py:290 ../src/jarabe/desktop/homebox.py:70
+msgid "Later"
+msgstr "Später"
+
+#: ../src/jarabe/controlpanel/gui.py:294
+msgid "Restart now"
+msgstr "Jetzt neu starten"
+
+#: ../src/jarabe/controlpanel/toolbar.py:61 ../src/jarabe/intro/window.py:206
+msgid "Done"
+msgstr "Fertig"
+
+#: ../src/jarabe/controlpanel/toolbar.py:121
+#: ../src/jarabe/desktop/favoritesview.py:333
+msgid "Ok"
+msgstr "Ok"
+
+#: ../src/jarabe/desktop/activitieslist.py:236
+#, python-format
+msgid "Version %s"
+msgstr "Version %s"
+
+#: ../src/jarabe/desktop/activitieslist.py:357
+msgid "Confirm erase"
+msgstr "Löschen bestätigen"
+
+#: ../src/jarabe/desktop/activitieslist.py:359
+#, python-format
+msgid "Confirm erase: Do you want to permanently erase %s?"
+msgstr "Löschen bestätigen: Willst du %s wirklich dauerhaft löschen?"
+
+# self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+# TODO: Implement stopping downloads
+# self._stop_item.connect('activate', self._stop_item_activate_cb)
+# self.append_menu_item(self._stop_item)
+#: ../src/jarabe/desktop/activitieslist.py:363
+#: ../src/jarabe/frame/clipboardmenu.py:63
+#: ../src/jarabe/view/viewsource.py:218
+msgid "Keep"
+msgstr "Behalten"
+
+#: ../src/jarabe/desktop/activitieslist.py:366
+#: ../src/jarabe/desktop/activitieslist.py:409
+#: ../src/jarabe/journal/journaltoolbox.py:360
+#: ../src/jarabe/journal/palettes.py:106
+msgid "Erase"
+msgstr "Löschen"
+
+#: ../src/jarabe/desktop/activitieslist.py:430
+msgid "Remove favorite"
+msgstr "Favorit entfernen"
+
+#: ../src/jarabe/desktop/activitieslist.py:434
+msgid "Make favorite"
+msgstr "Zum Favoriten machen"
+
+# TRANS: label for the freeform layout in the favorites view
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:116
+msgid "Freeform"
+msgstr "Freie Form"
+
+# TRANS: label for the ring layout in the favorites view
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:198
+msgid "Ring"
+msgstr "Ring"
+
+# TRANS: label for the spiral layout in the favorites view
+#. TRANS: label for the spiral layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:337
+msgid "Spiral"
+msgstr "Spirale"
+
+# TRANS: label for the box layout in the favorites view
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:404
+msgid "Box"
+msgstr "Rechteck"
+
+# TRANS: label for the box layout in the favorites view
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:445
+msgid "Triangle"
+msgstr "Dreieck"
+
+#: ../src/jarabe/desktop/favoritesview.py:324
+msgid "Registration Failed"
+msgstr "Anmeldung fehlgeschlagen"
+
+#: ../src/jarabe/desktop/favoritesview.py:325
+#, python-format
+msgid "%s"
+msgstr "%s"
+
+#: ../src/jarabe/desktop/favoritesview.py:327
+msgid "Registration Successful"
+msgstr "Anmeldung erfolgreich"
+
+#: ../src/jarabe/desktop/favoritesview.py:328
+msgid "You are now registered with your school server."
+msgstr "Du bist nun an deinem Schulserver angemeldet."
+
+#: ../src/jarabe/desktop/favoritesview.py:631
+msgid "Register"
+msgstr "Am Schulserver anmelden"
+
+#: ../src/jarabe/desktop/homebox.py:63
+msgid "Software Update"
+msgstr "Software-Aktualisierung"
+
+#: ../src/jarabe/desktop/homebox.py:64
+msgid "Update your activities to ensure compatibility with your new software"
+msgstr ""
+"Aktualisiere deine Aktivitäten, um die Kompatibilität mit deiner neuen "
+"Software sicherzustellen."
+
+#: ../src/jarabe/desktop/homebox.py:73
+msgid "Check now"
+msgstr "Jetzt prüfen"
+
+#: ../src/jarabe/desktop/homebox.py:192
+msgid "List view"
+msgstr "Listenansicht"
+
+#: ../src/jarabe/desktop/homebox.py:193
+msgid "<Ctrl>2"
+msgstr "<Ctrl>2"
+
+#: ../src/jarabe/desktop/homebox.py:255
+msgid "Favorites view"
+msgstr "Favoritenansicht"
+
+#: ../src/jarabe/desktop/homebox.py:256
+msgid "<Ctrl>1"
+msgstr "<Ctrl>1"
+
+#: ../src/jarabe/desktop/keydialog.py:135
+msgid "Key Type:"
+msgstr "Schlüsseltyp:"
+
+#: ../src/jarabe/desktop/keydialog.py:155
+msgid "Authentication Type:"
+msgstr "Authentifizierungstyp:"
+
+#: ../src/jarabe/desktop/keydialog.py:220
+msgid "WPA & WPA2 Personal"
+msgstr "WPA & WPA2 Personal"
+
+#: ../src/jarabe/desktop/keydialog.py:229
+msgid "Wireless Security:"
+msgstr "WLAN-Sicherheit:"
+
+#: ../src/jarabe/desktop/meshbox.py:491
+#, python-format
+msgid "Mesh Network %d"
+msgstr "Maschennetzwerk %d"
+
+# TRANS: Action label for resuming an activity.
+#. TRANS: Action label for resuming an activity.
+#: ../src/jarabe/desktop/meshbox.py:628
+#: ../src/jarabe/frame/activitiestray.py:735
+#: ../src/jarabe/journal/journaltoolbox.py:428
+#: ../src/jarabe/journal/palettes.py:66 ../src/jarabe/view/palettes.py:67
+msgid "Resume"
+msgstr "Fortsetzen"
+
+#: ../src/jarabe/desktop/meshbox.py:633
+#: ../src/jarabe/frame/activitiestray.py:233
+msgid "Join"
+msgstr "Mitmachen"
+
+#: ../src/jarabe/desktop/schoolserver.py:104
+msgid "Cannot connect to the server."
+msgstr "Kann nicht mit dem Server verbinden."
+
+#: ../src/jarabe/desktop/schoolserver.py:109
+msgid "The server could not complete the request."
+msgstr "Der Server konnte die Anforderung nicht erfüllen."
+
+#: ../src/jarabe/frame/activitiestray.py:238
+#: ../src/jarabe/frame/activitiestray.py:672
+msgid "Decline"
+msgstr "Ablehnen"
+
+#: ../src/jarabe/frame/activitiestray.py:624
+#, python-format
+msgid "%dB"
+msgstr "%dB"
+
+#: ../src/jarabe/frame/activitiestray.py:626
+#, python-format
+msgid "%dKB"
+msgstr "%dKB"
+
+#: ../src/jarabe/frame/activitiestray.py:628
+#, python-format
+msgid "%dMB"
+msgstr "%dMB"
+
+#: ../src/jarabe/frame/activitiestray.py:645
+#, python-format
+msgid "%s of %s"
+msgstr "%s von %s"
+
+#: ../src/jarabe/frame/activitiestray.py:657
+#, python-format
+msgid "Transfer from %r"
+msgstr "Übertragung von %r"
+
+#: ../src/jarabe/frame/activitiestray.py:667
+msgid "Accept"
+msgstr "Akzeptieren"
+
+#: ../src/jarabe/frame/activitiestray.py:690
+#: ../src/jarabe/frame/activitiestray.py:817
+#, python-format
+msgid "%s (%s)"
+msgstr "%s (%s)"
+
+#: ../src/jarabe/frame/activitiestray.py:724
+#: ../src/jarabe/frame/activitiestray.py:852
+msgid "Dismiss"
+msgstr "Verwerfen"
+
+#: ../src/jarabe/frame/activitiestray.py:787
+#, python-format
+msgid "Transfer to %r"
+msgstr "Übertragung zu %r"
+
+#: ../src/jarabe/frame/clipboardmenu.py:53 ../src/jarabe/view/palettes.py:221
+msgid "Remove"
+msgstr "Entfernen"
+
+#: ../src/jarabe/frame/clipboardmenu.py:58
+#: ../src/jarabe/frame/clipboardmenu.py:81
+msgid "Open"
+msgstr "Öffnen"
+
+#: ../src/jarabe/frame/clipboardmenu.py:86
+msgid "Open with"
+msgstr "Öffnen mit"
+
+# (Markus S.) 'clipping', nicht 'clipped'
+#: ../src/jarabe/frame/clipboardobject.py:49
+#, python-format
+msgid "%s clipping"
+msgstr "%s ausgeschnitten"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:37
+msgid "Neighborhood"
+msgstr "Umgebung"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:37
+msgid "F1"
+msgstr "F1"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:39
+msgid "F2"
+msgstr "F2"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:41
+msgid "F3"
+msgstr "F3"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:43
+msgid "F4"
+msgstr "F4"
+
+#: ../src/jarabe/intro/window.py:128
+msgid "Click to change color:"
+msgstr "Klicken zum Wechseln der Farbe:"
+
+#: ../src/jarabe/intro/window.py:192 ../src/jarabe/journal/detailview.py:103
+msgid "Back"
+msgstr "Zurück"
+
+# (Markus S.) war 'Nächste'
+#: ../src/jarabe/intro/window.py:209
+msgid "Next"
+msgstr "Vor"
+
+#: ../src/jarabe/journal/expandedentry.py:151
+#: ../src/jarabe/journal/palettes.py:60
+msgid "Untitled"
+msgstr "Ohne Titel"
+
+#: ../src/jarabe/journal/expandedentry.py:242
+msgid "No preview"
+msgstr "Keine Vorschau"
+
+#: ../src/jarabe/journal/expandedentry.py:261
+#, python-format
+msgid "Kind: %s"
+msgstr "Art: %s"
+
+#: ../src/jarabe/journal/expandedentry.py:261
+msgid "Unknown"
+msgstr "Unbekannt"
+
+#: ../src/jarabe/journal/expandedentry.py:262
+#, python-format
+msgid "Date: %s"
+msgstr "Datum: %s"
+
+#: ../src/jarabe/journal/expandedentry.py:263
+#, python-format
+msgid "Size: %s"
+msgstr "Größe: %s"
+
+#: ../src/jarabe/journal/expandedentry.py:285 ../src/jarabe/journal/misc.py:93
+msgid "No date"
+msgstr "Kein Datum"
+
+#: ../src/jarabe/journal/expandedentry.py:292
+msgid "Participants:"
+msgstr "Teilnehmer:"
+
+#: ../src/jarabe/journal/expandedentry.py:315
+msgid "Description:"
+msgstr "Beschreibung:"
+
+#: ../src/jarabe/journal/expandedentry.py:340
+msgid "Tags:"
+msgstr "Stichwörter:"
+
+#: ../src/jarabe/journal/journalactivity.py:108
+#: ../src/jarabe/journal/volumestoolbar.py:47
+msgid "Journal"
+msgstr "Tagebuch"
+
+#: ../src/jarabe/journal/journaltoolbox.py:67
+msgid "Search"
+msgstr "Suchen"
+
+#: ../src/jarabe/journal/journaltoolbox.py:126
+msgid "Anytime"
+msgstr "Beliebiges Datum"
+
+#: ../src/jarabe/journal/journaltoolbox.py:128
+msgid "Today"
+msgstr "Heute"
+
+#: ../src/jarabe/journal/journaltoolbox.py:130
+msgid "Since yesterday"
+msgstr "Seit gestern"
+
+# TRANS: Filter entries modified during the last 7 days.
+#. TRANS: Filter entries modified during the last 7 days.
+#: ../src/jarabe/journal/journaltoolbox.py:132
+msgid "Past week"
+msgstr "Vergangene Woche"
+
+# TRANS: Filter entries modified during the last 30 days.
+#. TRANS: Filter entries modified during the last 30 days.
+#: ../src/jarabe/journal/journaltoolbox.py:134
+msgid "Past month"
+msgstr "Vergangener Monat"
+
+# TRANS: Filter entries modified during the last 356 days.
+#. TRANS: Filter entries modified during the last 356 days.
+#: ../src/jarabe/journal/journaltoolbox.py:136
+msgid "Past year"
+msgstr "Vergangenes Jahr"
+
+#: ../src/jarabe/journal/journaltoolbox.py:143
+msgid "Anyone"
+msgstr "Alle"
+
+#: ../src/jarabe/journal/journaltoolbox.py:145
+msgid "My friends"
+msgstr "Meine Freunde"
+
+#: ../src/jarabe/journal/journaltoolbox.py:146
+msgid "My class"
+msgstr "Meine Klasse"
+
+# TRANS: Item in a combo box that filters by entry type.
+#: ../src/jarabe/journal/journaltoolbox.py:274
+msgid "Anything"
+msgstr "Alles"
+
+# TODO: Add "Start with" menu item
+#: ../src/jarabe/journal/journaltoolbox.py:350
+#: ../src/jarabe/journal/palettes.py:84
+msgid "Copy"
+msgstr "Kopieren"
+
+# TRANS: Action label for starting an entry.
+#. TRANS: Action label for starting an entry.
+#: ../src/jarabe/journal/journaltoolbox.py:431
+#: ../src/jarabe/journal/palettes.py:69
+msgid "Start"
+msgstr "Start"
+
+#: ../src/jarabe/journal/listview.py:373
+msgid "Your Journal is empty"
+msgstr "Dein Tagebuch ist leer."
+
+#: ../src/jarabe/journal/listview.py:375
+msgid "No matching entries"
+msgstr "Keine passenden Einträge"
+
+#: ../src/jarabe/journal/listview.py:386
+msgid "Clear search"
+msgstr "Suchfeld leeren"
+
+#: ../src/jarabe/journal/modalalert.py:63
+msgid "Your Journal is full"
+msgstr "Dein Tagebuch ist voll."
+
+#: ../src/jarabe/journal/modalalert.py:67
+msgid "Please delete some old Journal entries to make space for new ones."
+msgstr ""
+"Lösche bitte einige alte Tagebucheinträge, um Platz für neue zu schaffen."
+
+#: ../src/jarabe/journal/modalalert.py:79
+msgid "Show Journal"
+msgstr "Tagebuch anzeigen"
+
+#: ../src/jarabe/journal/objectchooser.py:146
+msgid "Choose an object"
+msgstr "Ein Objekt auswählen"
+
+#: ../src/jarabe/journal/objectchooser.py:151
+#: ../src/jarabe/view/viewsource.py:310
+msgid "Close"
+msgstr "Schließen"
+
+#: ../src/jarabe/journal/palettes.py:67
+msgid "Resume with"
+msgstr "Fortsetzen mit"
+
+#: ../src/jarabe/journal/palettes.py:70
+msgid "Start with"
+msgstr "Beginnen mit"
+
+#: ../src/jarabe/journal/palettes.py:92
+msgid "Send to"
+msgstr "Senden an"
+
+#: ../src/jarabe/journal/palettes.py:101
+msgid "View Details"
+msgstr "Details betrachten"
+
+#: ../src/jarabe/journal/palettes.py:179
+msgid "No friends present"
+msgstr "Keine Freunde anwesend"
+
+#: ../src/jarabe/journal/palettes.py:184
+msgid "No valid connection found"
+msgstr "Keine gültige Verbindung gefunden"
+
+#: ../src/jarabe/journal/palettes.py:212
+msgid "No activity to resume entry"
+msgstr "Keine Aktivität, um den Eintrag fortzusetzen"
+
+#: ../src/jarabe/journal/palettes.py:214
+msgid "No activity to start entry"
+msgstr "Keine Aktivität, um den Eintrag zu beginnen"
+
+#: ../src/jarabe/view/buddymenu.py:62
+msgid "Remove friend"
+msgstr "Freund entfernen"
+
+#: ../src/jarabe/view/buddymenu.py:65
+msgid "Make friend"
+msgstr "Freunde werden"
+
+#: ../src/jarabe/view/buddymenu.py:82
+msgid "Shutdown"
+msgstr "Rechner Ausschalten"
+
+#: ../src/jarabe/view/buddymenu.py:90
+msgid "Logout"
+msgstr "Benutzer Abmelden"
+
+#: ../src/jarabe/view/buddymenu.py:95
+msgid "My Settings"
+msgstr "Meine Einstellungen"
+
+#: ../src/jarabe/view/buddymenu.py:130
+#, python-format
+msgid "Invite to %s"
+msgstr "Einladen zu %s"
+
+#: ../src/jarabe/view/launcher.py:192
+#, python-format
+msgid "<b>%s</b> failed to start."
+msgstr "<b>%s</b> konnte nicht gestartet werden."
+
+#: ../src/jarabe/view/palettes.py:45
+msgid "Starting..."
+msgstr "Starte..."
+
+#. TODO: share-with, keep
+#: ../src/jarabe/view/palettes.py:74
+msgid "View Source"
+msgstr "Quelltext betrachten"
+
+#: ../src/jarabe/view/palettes.py:85
+msgid "Stop"
+msgstr "Beenden"
+
+#: ../src/jarabe/view/palettes.py:125
+msgid "Start new"
+msgstr "Neu beginnen"
+
+#: ../src/jarabe/view/palettes.py:174
+msgid "Show contents"
+msgstr "Inhalte anzeigen"
+
+#: ../src/jarabe/view/palettes.py:196 ../src/jarabe/view/palettes.py:246
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr "%(free_space)d MB frei"
+
+#: ../src/jarabe/view/viewsource.py:208
+msgid "Instance Source"
+msgstr "Quelltext der Instanz"
+
+#: ../src/jarabe/view/viewsource.py:233
+msgid "Source"
+msgstr "Quelltext"
+
+#: ../src/jarabe/view/viewsource.py:294
+msgid "Activity Bundle Source"
+msgstr "Quelltext des Aktivitätenbündels"
+
+#: ../src/jarabe/view/viewsource.py:301
+#, python-format
+msgid "View source: %r"
+msgstr "Quelltext betrachten: %r"
+
+#~ msgid "APN:"
+#~ msgstr "APN (Zugangspunkt):"
+
+#~ msgid "Title"
+#~ msgstr "Titel"
+
+#~ msgid "Version"
+#~ msgstr "Version"
+
+#~ msgid "Date"
+#~ msgstr "Datum"
+
+#~ msgid "Cannot obtain data needed for registration."
+#~ msgstr "Notwendige Daten für die Registrierung sind nicht verfügbar."
+
+#~ msgid "Unmount"
+#~ msgstr "Einbindung lösen"
+
+#~ msgid "Restart"
+#~ msgstr "Neustart"
+
+#~ msgid ""
+#~ "© 2008 One Laptop per Child Association Inc; Red Hat Inc; and Contributors."
+#~ msgstr ""
+#~ "© 2008 One Laptop per Child Association Inc; Red Hat Inc; and Contributors."
+
+#~ msgid "Encryption Type:"
+#~ msgstr "Verschlüsselungstyp:"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#~ msgid "Disconnecting..."
+#~ msgstr "Trenne Verbindung..."
+
+#~ msgid "About my XO"
+#~ msgstr "Über meinen XO"
+
+#~ msgid "Connected to a School Mesh Portal"
+#~ msgstr "Verbunden mit einem Schul-Maschennetzwerk-Portal"
+
+#~ msgid "Looking for a School Mesh Portal..."
+#~ msgstr "Suche Schul-Maschennetzwerk-Portal..."
+
+#~ msgid "Connected to an XO Mesh Portal"
+#~ msgstr "Verbunden mit einem XO-Maschennetzwerk-Portal"
+
+#~ msgid "Looking for an XO Mesh Portal..."
+#~ msgstr "Suche XO-Maschennetzwerk-Portal..."
+
+#~ msgid "Connected to a Simple Mesh"
+#~ msgstr "Verbunden mit einem einfachen Maschennetzwerk"
+
+#~ msgid "Starting a Simple Mesh"
+#~ msgstr "Starte ein einfaches Maschennetzwerk"
+
+#~ msgid "Unknown Mesh"
+#~ msgstr "Unbekanntes Maschennetzwerk"
+
+#~ msgid "Settings"
+#~ msgstr "Einstellungen"
+
+#, python-format
+#~ msgid "Clipboard object: %s."
+#~ msgstr "Zwischenablage-Objekt: %s."
+
+#~ msgid "You must enter a server."
+#~ msgstr "Bitte einen Server angeben"
+
+# (Markus S.) war 'Kontrollfeld'
+#~ msgid "Control Panel"
+#~ msgstr "Kontrollleiste"
+
+#~ msgid "<Ctrl>R"
+#~ msgstr "<Ctrl>R"
+
+# (Markus S.) Es geht offenbar um den Ring der gerade genutzten Aktivitäten unter Home
+#~ msgid "Ring view"
+#~ msgstr "Ringansicht"
+
+# (Markus S.) es scheint um den Ring der gerade genutzten Aktivitäten in Home zu gehen
+#~ msgid "Remove from ring"
+#~ msgstr "Aus dem Ring entfernen"
+
+#~ msgid "Add to ring"
+#~ msgstr "Zum Ring hinzufügen"
+
+#~ msgid "Changes require a sugar restart to take effect."
+#~ msgstr "Neustart von Sugar erforderlich, um die Änderungen zu übernehmen"
+
+#~ msgid "Changes require restart to take effect"
+#~ msgstr "Neustart erforderlich, um die Änderungen zu übernehmen"
+
+#~ msgid "Delay in milliseconds:"
+#~ msgstr "Verzögerung in Millisekunden:"
+
+#, fuzzy
+#~ msgid "Hot Corners"
+#~ msgstr "Heiße Ecken"
+
+#, fuzzy
+#~ msgid "Warm Edges"
+#~ msgstr "Warme Kanten"
+
+#~ msgid "off"
+#~ msgstr "Aus"
+
+#~ msgid "on"
+#~ msgstr "An"
+
+#~ msgid "Permission denied. You need to be root to run this method."
+#~ msgstr "Zugriffsrechte notwendig. Du musst root sein für diese Aktion."
+
+#~ msgid "Error in reading timezone"
+#~ msgstr "Fehler beim Lesen der Zeitzone"
+
+#, python-format
+#~ msgid "Error copying timezone (from %s): %s"
+#~ msgstr "Fehler beim Kopieren der Zeitzone (von %s): %s"
+
+#, python-format
+#~ msgid "Changing permission of timezone: %s"
+#~ msgstr "Änderung der Berechtigung für Zeitzone: %s"
+
+#~ msgid "About this XO"
+#~ msgstr "Infos zu diesem XO"
+
+#~ msgid "Add to journal"
+#~ msgstr "Zum Journal hinzufügen"
+
+#~ msgid "Reboot"
+#~ msgstr "Neu starten"
+
+#~ msgid "My Battery life"
+#~ msgstr "Meine Akku-Laufzeit"
+
+#~ msgid "Battery charging"
+#~ msgstr "Akku wird geladen"
+
+#~ msgid "Battery discharging"
+#~ msgstr "Akku in Benutzung"
+
+#~ msgid "Battery fully charged"
+#~ msgstr "Akku vollständig geladen"
+
+#~ msgid "Invite"
+#~ msgstr "Einladen"
+
+#~ msgid "Text"
+#~ msgstr "Text"
+
+#~ msgid "Image"
+#~ msgstr "Bild"
+
+#~ msgid "Private"
+#~ msgstr "Privat"
+
+#~ msgid "My Neighborhood"
+#~ msgstr "Meine Umgebung"
+
+#, python-format
+#~ msgid "%s Activity"
+#~ msgstr "%s Aktivität"
+
+# Alternative: Gemeinsam mit:
+#~ msgid "Share with:"
+#~ msgstr "Gemeinsam mit:"
+
+#~ msgid "Undo"
+#~ msgstr "Rückgängig"
+
+#~ msgid "Redo"
+#~ msgstr "Wiederholen"
+
+#~ msgid "Paste"
+#~ msgstr "Einfügen"
+
+#~ msgid "Keep error"
+#~ msgstr "Fehler beim Speichern"
+
+#~ msgid "Keep error: all changes will be lost"
+#~ msgstr "Fehler beim Speichern: Alle Änderungen gehen verloren"
+
+#~ msgid "Don't stop"
+#~ msgstr "Nicht stoppen"
+
+#~ msgid "Stop anyway"
+#~ msgstr "Trotzdem stoppen"
+
+#~ msgid "Continue"
+#~ msgstr "Weitermachen"
+
+#~ msgid "OK"
+#~ msgstr "OK"
+
+#, python-format
+#~ msgid "%d year"
+#~ msgstr "%d Jahr"
+
+#, python-format
+#~ msgid "%d years"
+#~ msgstr "%d Jahre"
+
+#, python-format
+#~ msgid "%d month"
+#~ msgstr "%d Monat"
+
+#, python-format
+#~ msgid "%d months"
+#~ msgstr "%d Monate"
+
+#, python-format
+#~ msgid "%d week"
+#~ msgstr "%d Woche"
+
+#, python-format
+#~ msgid "%d weeks"
+#~ msgstr "%d Wochen"
+
+#, python-format
+#~ msgid "%d day"
+#~ msgstr "%d Tag"
+
+#, python-format
+#~ msgid "%d days"
+#~ msgstr "%d Tage"
+
+#, python-format
+#~ msgid "%d hour"
+#~ msgstr "%d Stunde"
+
+#, python-format
+#~ msgid "%d hours"
+#~ msgstr "%d Stunden"
+
+#, python-format
+#~ msgid "%d minute"
+#~ msgstr "%d Minute"
+
+#, python-format
+#~ msgid "%d minutes"
+#~ msgstr "%d Minuten"
+
+#, python-format
+#~ msgid "%d second"
+#~ msgstr "%d Sekunde"
+
+#~ msgid " and "
+#~ msgstr " und "
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#~ msgid ", "
+#~ msgstr ", "
diff --git a/shell/po/dz.po b/shell/po/dz.po
new file mode 100644
index 0000000..a79e711
--- /dev/null
+++ b/shell/po/dz.po
@@ -0,0 +1,420 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-01-18 18:19+0000\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.0.1\n"
+
+#: ../shell/intro/intro.py:67
+msgid "Name:"
+msgstr ""
+
+#: ../shell/intro/intro.py:96
+msgid "Click to change color:"
+msgstr ""
+
+#: ../shell/intro/intro.py:146
+msgid "Back"
+msgstr ""
+
+#: ../shell/intro/intro.py:160
+msgid "Done"
+msgstr ""
+
+#: ../shell/intro/intro.py:163
+msgid "Next"
+msgstr ""
+
+#: ../shell/view/BuddyMenu.py:59
+msgid "Remove friend"
+msgstr ""
+
+#: ../shell/view/BuddyMenu.py:62
+msgid "Make friend"
+msgstr ""
+
+#: ../shell/view/BuddyMenu.py:84
+#, python-format
+msgid "Invite to %s"
+msgstr ""
+
+#: ../shell/view/clipboardmenu.py:58
+msgid "Remove"
+msgstr ""
+
+#: ../shell/view/clipboardmenu.py:63
+msgid "Open"
+msgstr ""
+
+#. self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+#. TODO: Implement stopping downloads
+#. self._stop_item.connect('activate', self._stop_item_activate_cb)
+#. self.append_menu_item(self._stop_item)
+#: ../shell/view/clipboardmenu.py:73
+msgid "Add to journal"
+msgstr ""
+
+#: ../shell/view/clipboardmenu.py:213
+#, python-format
+msgid "Clipboard object: %s."
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:149
+msgid "Key Type:"
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:169
+msgid "Authentication Type:"
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:250
+msgid "Encryption Type:"
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:90
+msgid "Starting..."
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:104 ../shell/view/home/MeshBox.py:295
+msgid "Resume"
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:111
+#: ../lib/sugar/activity/activity.py:132
+msgid "Stop"
+msgstr ""
+
+#: ../shell/view/Shell.py:285
+msgid "Screenshot"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:159
+msgid "Reboot"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:164
+msgid "Shutdown"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:170
+msgid "Register"
+msgstr ""
+
+#. Only show disconnect when there's a mesh device, because mesh takes
+#. priority over the normal wireless device. NM doesn't have a "disconnect"
+#. method for a device either (for various reasons) so this doesn't
+#. have a good mapping
+#: ../shell/view/home/MeshBox.py:90 ../shell/view/home/MeshBox.py:197
+#: ../shell/view/devices/network/wireless.py:113
+#: ../shell/view/devices/network/mesh.py:83
+msgid "Disconnect..."
+msgstr ""
+
+#: ../shell/view/home/MeshBox.py:195 ../shell/view/devices/network/mesh.py:37
+#: ../shell/view/devices/network/mesh.py:62
+#: ../shell/view/devices/network/mesh.py:66
+msgid "Mesh Network"
+msgstr ""
+
+#: ../shell/view/home/MeshBox.py:300
+msgid "Join"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:38
+msgid "My Battery life"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:94
+msgid "Battery charging"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:96
+msgid "Battery discharging"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:98
+msgid "Battery fully charged"
+msgstr ""
+
+#: ../shell/view/devices/network/wireless.py:61
+msgid "Disconnected"
+msgstr ""
+
+#: ../shell/view/devices/network/wireless.py:131
+msgid "Channel"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:42
+msgid "Neighborhood"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:54
+msgid "Group"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:66
+msgid "Home"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:78
+msgid "Activity"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:115
+msgid "Share with:"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:117
+msgid "Private"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:118
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:126
+msgid "Keep"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:245
+msgid "Undo"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:250
+msgid "Redo"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:260
+msgid "Copy"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:265
+msgid "Paste"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:454
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:824
+msgid "Keep error"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:825
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:828
+msgid "Don't stop"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:831
+msgid "Stop anyway"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:164 ../lib/sugar/graphics/alert.py:206
+msgid "Cancel"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:168
+msgid "Ok"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:216
+msgid "Continue"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:244
+msgid "OK"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d year"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d years"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d month"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d months"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d week"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d weeks"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d day"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d days"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hour"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hours"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minute"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minutes"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d second"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d seconds"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:191
+msgid " and "
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:193
+msgid ", "
+msgstr ""
+
+#: ../shell/controlpanel/control.py:213
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:267
+msgid "Error in specified color modifiers."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:270
+msgid "Error in specified colors."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:307
+msgid "off"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:309
+msgid "on"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:310
+msgid "State is unknown."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:332
+msgid "Error in specified radio argument use on/off."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:336
+msgid "Permission denied. You need to be root to run this method."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:366
+msgid "Error in reading timezone"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:397
+#, python-format
+msgid "Error copying timezone (from %s): %s"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:402
+#, python-format
+msgid "Changing permission of timezone: %s"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:412
+msgid "Error timezone does not exist."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:417 ../shell/controlpanel/control.py:436
+#, python-format
+msgid "Could not access %s. Create standard settings."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:463
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:473
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:105
+msgid "Connected to a School Mesh Portal"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:107
+msgid "Looking for a School Mesh Portal..."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:110
+msgid "Connected to an XO Mesh Portal"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:112
+msgid "Looking for an XO Mesh Portal..."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:115
+msgid "Connected to a Simple Mesh"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:117
+msgid "Starting a Simple Mesh"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:124
+msgid "Unknown Mesh"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:175 ../shell/view/home/HomeBox.py:216
+msgid "About this XO"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:222
+msgid "Not available"
+msgstr ""
diff --git a/shell/po/el.po b/shell/po/el.po
new file mode 100644
index 0000000..7980d12
--- /dev/null
+++ b/shell/po/el.po
@@ -0,0 +1,777 @@
+# Greek translation of Sugar.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# Simos Xenitellis <simos.lists@googlemail.com>, 2007.
+msgid ""
+msgstr ""
+"Project-Id-Version: Sugar\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-07-23 07:44-0400\n"
+"PO-Revision-Date: 2008-08-05 08:05-0400\n"
+"Last-Translator: Γιάννης Κασκαμανίδης <ttnfy17@yahoo.gr>\n"
+"Language-Team: Greek <olpc@grnet.gr>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Pootle 1.1.0rc2\n"
+
+#: ../src/intro/intro.py:65 ../src/controlpanel/view/aboutme.py:100
+msgid "Name:"
+msgstr "Όνομα:"
+
+#: ../src/intro/intro.py:94
+msgid "Click to change color:"
+msgstr "Πατήστε για να αλλάξτε χρώμα:"
+
+#: ../src/intro/intro.py:145
+msgid "Back"
+msgstr "Πίσω"
+
+#: ../src/intro/intro.py:159 ../src/controlpanel/toolbar.py:61
+msgid "Done"
+msgstr "Ολοκληρώθηκε"
+
+#: ../src/intro/intro.py:162
+msgid "Next"
+msgstr "Επόμενο"
+
+#: ../src/view/BuddyMenu.py:58
+msgid "Remove friend"
+msgstr "Διαγραφή φίλου/ης"
+
+#: ../src/view/BuddyMenu.py:61
+msgid "Make friend"
+msgstr "Να γίνει φίλος/η"
+
+#: ../src/view/BuddyMenu.py:91
+#, python-format
+msgid "Invite to %s"
+msgstr "Προσκάλεσε στο %s"
+
+#: ../src/view/clipboardmenu.py:48
+msgid "Remove"
+msgstr "Διαγραφή"
+
+#: ../src/view/clipboardmenu.py:53 ../src/view/clipboardmenu.py:79
+msgid "Open"
+msgstr "Άνοιγμα"
+
+# self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+# TODO: Implement stopping downloads
+# self._stop_item.connect('activate', self._stop_item_activate_cb)
+# self.append_menu_item(self._stop_item)
+#. self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+#. TODO: Implement stopping downloads
+#. self._stop_item.connect('activate', self._stop_item_activate_cb)
+#. self.append_menu_item(self._stop_item)
+#: ../src/view/clipboardmenu.py:63 ../src/view/home/HomeBox.py:86
+msgid "Keep"
+msgstr "Διατήρησε"
+
+#: ../src/view/clipboardmenu.py:84
+msgid "Open with"
+msgstr "Άνοιγμα με"
+
+#: ../src/view/clipboardmenu.py:216
+#, python-format
+msgid "Clipboard object: %s."
+msgstr "Αντικείμενο προχείρου: %s."
+
+#: ../src/hardware/keydialog.py:150
+msgid "Key Type:"
+msgstr "Τύπος Κλειδιού"
+
+#: ../src/hardware/keydialog.py:170
+msgid "Authentication Type:"
+msgstr "Τύπος Πιστοποίησης"
+
+#: ../src/hardware/keydialog.py:251
+msgid "Encryption Type:"
+msgstr "Τύπος Κρυπτογράφησης"
+
+#: ../src/view/Shell.py:240
+msgid "Screenshot"
+msgstr "Στιγμιότυπο οθόνης"
+
+#: ../src/view/home/HomeBox.py:80
+msgid "Confirm erase"
+msgstr "Επιβεβαίωση διαγραφής"
+
+#: ../src/view/home/HomeBox.py:82
+#, python-format
+msgid "Confirm erase: Do you want to permanently erase %s?"
+msgstr "Επιβεβαίωση διαγραφής: Θέλετε να διαγράψετε μόνιμα %s?"
+
+#: ../src/view/home/HomeBox.py:89 ../src/view/palettes.py:120
+msgid "Erase"
+msgstr "Διαγραφή"
+
+#: ../src/view/home/HomeBox.py:215
+msgid "List view"
+msgstr "Προβολή λίστας"
+
+#: ../src/view/home/HomeBox.py:216
+msgid "<Ctrl>2"
+msgstr "<Ctrl>2"
+
+#: ../src/view/home/HomeBox.py:273
+msgid "Favorites view"
+msgstr "Προβολή αγαπημένων"
+
+#: ../src/view/home/HomeBox.py:274
+msgid "<Ctrl>1"
+msgstr "<Ctrl>1"
+
+# TRANS: label for the freeform layout in the favorites view
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/view/home/HomeBox.py:282
+msgid "Freeform"
+msgstr "Ελεύθερο σχήμα"
+
+# TRANS: label for the ring layout in the favorites view
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/view/home/HomeBox.py:289
+msgid "Ring"
+msgstr "Δακτύλιος"
+
+#: ../src/view/home/MeshBox.py:97
+msgid "Connect"
+msgstr "Σύνδεση"
+
+#: ../src/view/home/MeshBox.py:106
+msgid "Disconnect"
+msgstr "Αποσύνδεση"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#: ../src/view/home/MeshBox.py:118
+msgid "Disconnecting..."
+msgstr "Αποσύνδεση..."
+
+#: ../src/view/home/MeshBox.py:152
+msgid "Connecting..."
+msgstr "Σύνδεση..."
+
+# TODO: show the channel number
+#. TODO: show the channel number
+#: ../src/view/home/MeshBox.py:159
+msgid "Connected"
+msgstr "Συνδέθηκε"
+
+#: ../src/view/home/MeshBox.py:211 ../src/view/devices/network/mesh.py:41
+#: ../src/view/devices/network/mesh.py:68
+#: ../src/view/devices/network/mesh.py:72
+msgid "Mesh Network"
+msgstr "Δίκτυο πλέγματος"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#: ../src/view/home/MeshBox.py:214 ../src/view/devices/network/wireless.py:119
+#: ../src/view/devices/network/mesh.py:89
+msgid "Disconnect..."
+msgstr "Αποσύνδεση..."
+
+#: ../src/view/home/MeshBox.py:302 ../src/view/palettes.py:61
+msgid "Resume"
+msgstr "Συνέχιση"
+
+#: ../src/view/home/MeshBox.py:307 ../src/view/frame/activitiestray.py:205
+msgid "Join"
+msgstr "Σύνδεση"
+
+#: ../src/view/devices/battery.py:45
+msgid "My Battery"
+msgstr "Η μπαταρία μου"
+
+#: ../src/view/devices/battery.py:114
+msgid "Charging"
+msgstr "Φόρτιση"
+
+#: ../src/view/devices/battery.py:117
+msgid "Very little power remaining"
+msgstr "Απομένει ελάχιστη ενέργεια"
+
+#: ../src/view/devices/battery.py:123
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr "%(hour)d:%(min).2d απομένουν"
+
+#: ../src/view/devices/battery.py:127
+msgid "Charged"
+msgstr "Φορτισμένη"
+
+#: ../src/view/devices/speaker.py:44
+msgid "My Speakers"
+msgstr "Τα ηχεία μου"
+
+#: ../src/view/devices/speaker.py:119
+msgid "Unmute"
+msgstr "Άρση σίγασης"
+
+#: ../src/view/devices/speaker.py:122
+msgid "Mute"
+msgstr "Σίγαση"
+
+#: ../src/view/devices/network/wireless.py:67
+msgid "Disconnected"
+msgstr "Αποσυνδέθηκε"
+
+#: ../src/view/devices/network/wireless.py:137
+msgid "Channel"
+msgstr "Κανάλι"
+
+#: ../src/view/frame/zoomtoolbar.py:34
+msgid "Neighborhood"
+msgstr "Γειτονιά"
+
+#: ../src/view/frame/zoomtoolbar.py:36
+msgid "Group"
+msgstr "Ομάδα"
+
+#: ../src/view/frame/zoomtoolbar.py:38
+msgid "Home"
+msgstr "Αρχή"
+
+#: ../src/view/frame/zoomtoolbar.py:40
+msgid "Activity"
+msgstr "Δραστηριότητα"
+
+#: ../src/controlpanel/cmd.py:26
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+"sugar-πίνακας ελέγχου: Προειδοποίηση, βρέθηκαν περισσότερες από μία επιλογές "
+"με το ίδιο όνομα: %s άρθρωμα: %r"
+
+#: ../src/controlpanel/cmd.py:28
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr "sugar-πίνακας ελέγχου: κλειδί=%s μη διαθέσιμη επιλογή"
+
+#: ../src/controlpanel/cmd.py:29
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr "sugar-πίνακας ελέγχου: %s"
+
+#: ../src/controlpanel/cmd.py:33
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" "
+msgstr ""
+"Χρήση: sugar-πίνακας ελέγχου [ επιλογή ] κλειδί [ ορίσματα ... ] \n"
+"Έλεγχος περιβάλλοντος εργασίας sugar. \n"
+"Επιλογές: \n"
+"-h εμφάνιση αυτού του μηνύματος βοήθειας και έξοδος \n"
+"-l καταλογογράφηση όλων των διαθέσιμων επιλογών \n"
+"-h κλειδί εμφάνισης πληροφοριών γι' αυτό το κλειδί \n"
+"-g κλειδί που παίρνει την τρέχουσα τιμή του κλειδιού \n"
+"-s κλειδί που καθορίζει την τρέχουσα τιμή του κλειδιού \n"
+" "
+#: ../src/controlpanel/cmd.py:45
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr "Για να εφαρμοστούν οι αλλαγές σου θα πρέπει να επανεκκινήσεις το sugar\n"
+
+#: ../src/controlpanel/toolbar.py:115
+msgid "Cancel"
+msgstr "Ακύρωση"
+
+#: ../src/controlpanel/toolbar.py:121 ../src/view/home/favoritesview.py:294
+msgid "Ok"
+msgstr "Οκ"
+
+#: ../src/controlpanel/sectionview.py:34 ../src/controlpanel/gui.py:260
+msgid "Changes require restart"
+msgstr "Οι αλλαγές απαιτούν επανεκκίνηση"
+
+#: ../src/controlpanel/gui.py:259
+msgid "Warning"
+msgstr "Προειδοποίηση"
+
+#: ../src/controlpanel/gui.py:263
+msgid "Cancel changes"
+msgstr "Ακύρωση αλλαγών"
+
+#: ../src/controlpanel/gui.py:267
+msgid "Later"
+msgstr "Αργότερα"
+
+#: ../src/controlpanel/gui.py:271
+msgid "Restart now"
+msgstr "Επανεκκίνηση τώρα"
+
+#: ../src/controlpanel/model/aboutme.py:44
+msgid "You must enter a name."
+msgstr "Πρέπει να εισαχθεί ένα όνομα."
+
+#: ../src/controlpanel/model/aboutme.py:69
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr "πινελιά: χρώμα=%s απόχρωση=%s"
+
+#: ../src/controlpanel/model/aboutme.py:72
+#, python-format
+msgid "stroke: %s"
+msgstr "πινελιά: %s"
+
+#: ../src/controlpanel/model/aboutme.py:74
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr "γέμισμα: χρώμα=%s απόχρωση=%s"
+
+#: ../src/controlpanel/model/aboutme.py:76
+#, python-format
+msgid "fill: %s"
+msgstr "γέμισμα: %s"
+
+#: ../src/controlpanel/model/aboutme.py:87
+msgid "Error in specified color modifiers."
+msgstr "Λάθος στους διαμορφωτές καθορισμένου χρώματος"
+
+#: ../src/controlpanel/model/aboutme.py:90
+msgid "Error in specified colors."
+msgstr "Λάθος στα καθορισμένα χρώματα"
+
+#: ../src/controlpanel/model/aboutxo.py:24
+msgid "Not available"
+msgstr "Δεν είναι διαθέσιμο"
+
+#: ../src/controlpanel/model/datetime.py:85
+msgid "Error timezone does not exist."
+msgstr "Λάθος η ζώνη ώρας δεν υπάρχει"
+
+#: ../src/controlpanel/model/frame.py:38 ../src/controlpanel/model/frame.py:60
+msgid "Value must be an integer."
+msgstr "Η τιμή πρέπει να είναι ακέραιος αριθμός."
+
+#: ../src/controlpanel/model/language.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr ""
+"Δεν είναι δυνατή η πρόσβαση ~/.i18n. Δημιουργία προκαθορισμένων ρυθμίσεων."
+
+#: ../src/controlpanel/model/language.py:104
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr "Γλώσσα για τον κωδικό=%s δεν μπορεί να καθοριστεί."
+
+#: ../src/controlpanel/model/language.py:121
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr "Συγνώμη αλλά δεν μιλάω '%s'."
+
+#: ../src/controlpanel/model/network.py:48
+msgid "You must enter a server."
+msgstr "Πρέπει να εισαχθεί ένα όνομα."
+
+#: ../src/controlpanel/model/network.py:63
+msgid "State is unknown."
+msgstr "Η κατάσταση είναι άγνωστη."
+
+#: ../src/controlpanel/model/network.py:83
+msgid "Error in specified radio argument use on/off."
+msgstr ""
+"Λάθος σε καθορισμένη παράμετρο κουμπιού επιλογής χρήση κλειστού/ανοικτού."
+
+#: ../src/controlpanel/model/power.py:57
+msgid "Error in automatic pm argument, use on/off."
+msgstr "Σφάλμα στην αυτόματη παράμετρο pm, χρησιμοποιήστε το on/off."
+
+#: ../src/controlpanel/model/power.py:86
+msgid "Error in extreme pm argument, use on/off."
+msgstr "Σφάλμα στην ακραία παράμετρο pm, χρησιμοποιήστε το on/off."
+
+#: ../src/controlpanel/view/aboutme.py:32
+msgid "About Me"
+msgstr "Σχετικά με μένα"
+
+#: ../src/controlpanel/view/aboutme.py:134
+msgid "Click to change your color:"
+msgstr "Πατήστε για να αλλάξετε χρώμα:"
+
+#: ../src/controlpanel/view/aboutxo.py:26
+msgid "About my XO"
+msgstr "Σχετικά με τον XO μου"
+
+#: ../src/controlpanel/view/aboutxo.py:47
+msgid "Identity"
+msgstr "Ταυτότητα"
+
+#: ../src/controlpanel/view/aboutxo.py:56
+msgid "Serial Number:"
+msgstr "Σειριακός αριθμός:"
+
+#: ../src/controlpanel/view/aboutxo.py:79
+msgid "Software"
+msgstr "Λογισμικό"
+
+#: ../src/controlpanel/view/aboutxo.py:88
+msgid "Build:"
+msgstr "Αναγνωριστικό έκδοσης:"
+
+#: ../src/controlpanel/view/aboutxo.py:103
+msgid "Firmware:"
+msgstr "Firmware:"
+
+#: ../src/controlpanel/view/datetime.py:29
+msgid "Date & Time"
+msgstr "Ημερομηνία & ώρα"
+
+#: ../src/controlpanel/view/datetime.py:72
+msgid "Timezone"
+msgstr "Ζώνη ώρας"
+
+#: ../src/controlpanel/view/frame.py:28
+msgid "Frame"
+msgstr "Πλαίσιο"
+
+#: ../src/controlpanel/view/frame.py:30
+msgid "never"
+msgstr "ποτέ"
+
+#: ../src/controlpanel/view/frame.py:31
+msgid "instantaneous"
+msgstr "στιγμιαίος"
+
+#: ../src/controlpanel/view/frame.py:32
+#, python-format
+msgid "%s seconds"
+msgstr "%s δεπτερόλεπτα"
+
+#: ../src/controlpanel/view/frame.py:56
+msgid "Activation Delay"
+msgstr "Καθυστέρηση ενεργοποίησης"
+
+#: ../src/controlpanel/view/frame.py:80
+msgid "Corner"
+msgstr "Γωνία"
+
+#: ../src/controlpanel/view/frame.py:115
+msgid "Edge"
+msgstr "Άκρη"
+
+#: ../src/controlpanel/view/language.py:29
+#: ../src/controlpanel/view/language.py:74
+msgid "Language"
+msgstr "Γλώσσα"
+
+#: ../src/controlpanel/view/network.py:28
+msgid "Network"
+msgstr "Δίκτυο"
+
+#: ../src/controlpanel/view/network.py:53
+msgid "Wireless"
+msgstr "Ασύρματο"
+
+#: ../src/controlpanel/view/network.py:61
+#, fuzzy
+msgid "Radio:"
+msgstr "Ραδιοκύματα:"
+
+#: ../src/controlpanel/view/network.py:94
+msgid "Mesh"
+msgstr "Πλεγματοειδές δίκτυο"
+
+#: ../src/controlpanel/view/network.py:103
+msgid "Server:"
+msgstr "Διακομιστής:"
+
+#: ../src/controlpanel/view/power.py:27
+msgid "Power"
+msgstr "Ενέργεια"
+
+#: ../src/controlpanel/view/power.py:51
+msgid "Power management"
+msgstr "Διαχείριση ενέργειας"
+
+#: ../src/controlpanel/view/power.py:61
+msgid "Automatic power management (increases battery life)"
+msgstr "Αυτόματα διαχείριση ενέργειας (αυξάνει τη διάρκεια της μπαταρίας)"
+
+#: ../src/controlpanel/view/power.py:89
+msgid ""
+"Extreme power management (disables wireless radio, increases battery life)"
+msgstr ""
+"Ακραία διαχείριση ενέργειας (απενεργοποιεί το ασύρματο ραδιόφωνο, αυξάνει τη "
+"διάρκεια της μπαταρίας)"
+
+#: ../src/view/devices/network/mesh.py:111
+msgid "Connected to a School Mesh Portal"
+msgstr "Συνδέθηκα σε μια Σχολική Πύλη Δικτυακού Πλέγματος"
+
+#: ../src/view/devices/network/mesh.py:113
+msgid "Looking for a School Mesh Portal..."
+msgstr "Αναζητώ μια Σχολική Πύλη Δικτυακού Πλέγματος..."
+
+#: ../src/view/devices/network/mesh.py:116
+msgid "Connected to an XO Mesh Portal"
+msgstr "Συνδέθηκα σε μια Πύλη Δικτυακού Πλέγματος XO"
+
+#: ../src/view/devices/network/mesh.py:118
+msgid "Looking for an XO Mesh Portal..."
+msgstr "Αναζητώ μια Πύλη Δικτυακού Πλέγματος XO..."
+
+#: ../src/view/devices/network/mesh.py:121
+msgid "Connected to a Simple Mesh"
+msgstr "Συνδέθηκα σε ένα Απλό Δικτυακό Πλέγμα"
+
+#: ../src/view/devices/network/mesh.py:123
+msgid "Starting a Simple Mesh"
+msgstr "Ξεκινώ ένα Απλό Δικτυακό Πλέγμα"
+
+#: ../src/view/devices/network/mesh.py:130
+msgid "Unknown Mesh"
+msgstr "Άγνωστο Δικτυακό Πλέγμα"
+
+#: ../src/view/frame/activitiestray.py:210
+#, fuzzy
+msgid "Decline"
+msgstr "Πτώση"
+
+#: ../src/view/home/favoritesview.py:285
+msgid "Registration Failed"
+msgstr "Η καταχώρηση απέτυχε"
+
+#: ../src/view/home/favoritesview.py:286
+#, python-format
+msgid "%s"
+msgstr "%s"
+
+#: ../src/view/home/favoritesview.py:288
+msgid "Registration Successful"
+msgstr "Η καταχώρηση πέτυχε"
+
+#: ../src/view/home/favoritesview.py:289
+msgid "You are now registered with your school server."
+msgstr "Είστε καταχωρημένος στον διακομιστή του σχολείου."
+
+#: ../src/view/home/favoritesview.py:405
+msgid "Control Panel"
+msgstr "Πίνακας ελέγχου"
+
+#: ../src/view/home/favoritesview.py:416
+msgid "Restart"
+msgstr "Επανεκκίνηση"
+
+#: ../src/view/home/favoritesview.py:421
+msgid "Shutdown"
+msgstr "Τερματισμός"
+
+#: ../src/view/home/favoritesview.py:427
+msgid "Register"
+msgstr "Καταχώρηση"
+
+#: ../src/view/palettes.py:42
+msgid "Starting..."
+msgstr "Εκκίνηση..."
+
+#: ../src/view/palettes.py:72
+msgid "Stop"
+msgstr "Διακοπή"
+
+#: ../src/view/palettes.py:104
+msgid "Start"
+msgstr "Έναρξη"
+
+#: ../src/view/palettes.py:132
+msgid "Remove favorite"
+msgstr "Αφαίρεση αγαπημένου"
+
+#: ../src/view/palettes.py:136
+msgid "Make favorite"
+msgstr "Δημιουργία αγαπημένου"
+
+#: ../src/view/palettes.py:185
+msgid "Show contents"
+msgstr "Προβολή περιεχομένων"
+
+#: ../src/view/palettes.py:209
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr "%(free_space)d MB ελεύθερα"
+
+#~ msgid "<Ctrl>R"
+#~ msgstr "<Ctrl>R"
+
+#~ msgid "Ring view"
+#~ msgstr "Προβολή δακτυλίου"
+
+#~ msgid "Remove from ring"
+#~ msgstr "Αφαίρεση από τον δακτύλιο"
+
+#~ msgid "Add to ring"
+#~ msgstr "Προσθήκη στον δακτύλιο"
+
+#~ msgid "Changes require a sugar restart to take effect."
+#~ msgstr ""
+#~ "Οι αλλαγές απαιτούν επανεκκίνηση του sugar προκειμένου να τεθούν σε ισχύ."
+
+#~ msgid "Changes require restart to take effect"
+#~ msgstr "Οι αλλαγές απαιτούν επανεκκίνηση προκειμένου να τεθούν σε ισχύ."
+
+#~ msgid "off"
+#~ msgstr "κλειστό"
+
+#~ msgid "on"
+#~ msgstr "ανοικτό"
+
+#~ msgid "Permission denied. You need to be root to run this method."
+#~ msgstr ""
+#~ "Δεν επιτρέπεται η πρόσβαση. Πρέπει να είσαι διαχειριστής για να τρέξεις αυτή "
+#~ "τη μέθοδο."
+
+#~ msgid "Error in reading timezone"
+#~ msgstr "Λάθος στην ανάγνωση της ζώνης ώρας"
+
+#, python-format
+#~ msgid "Error copying timezone (from %s): %s"
+#~ msgstr "Λάθος αντιγραφής ζώνης ώρας (από %s): %s"
+
+#, python-format
+#~ msgid "Changing permission of timezone: %s"
+#~ msgstr "Άλλαξε την πρόσβαση της ζώνης ώρας: %s"
+
+#~ msgid "About this XO"
+#~ msgstr "Σχετικά με αυτόν τον XO"
+
+#~ msgid "Add to journal"
+#~ msgstr "Προσθήκη στο χρονικό"
+
+#~ msgid "Reboot"
+#~ msgstr "Επανεκκίνηση"
+
+#~ msgid "My Battery life"
+#~ msgstr "Διάρκεια μπαταρίας"
+
+#~ msgid "Battery charging"
+#~ msgstr "Μπαταρία φορτίζει"
+
+#~ msgid "Battery discharging"
+#~ msgstr "Μπαταρία ξεφορτίζει"
+
+#~ msgid "Battery fully charged"
+#~ msgstr "Μπαταρία πλήρως φορτισμένη"
+
+#~ msgid "Invite"
+#~ msgstr "Πρόσκληση"
+
+#~ msgid "Text"
+#~ msgstr "Κείμενο"
+
+#~ msgid "Image"
+#~ msgstr "Εικόνα"
+
+#~ msgid "Share"
+#~ msgstr "Μοίρασε"
+
+#, python-format
+#~ msgid "%s Activity"
+#~ msgstr "Δραστηριότητα %s"
+
+#~ msgid "Share with:"
+#~ msgstr "Μοιράσου με:"
+
+#~ msgid "Private"
+#~ msgstr "Ιδιωτικό"
+
+#~ msgid "My Neighborhood"
+#~ msgstr "Η Γειτονιά μου"
+
+#~ msgid "Undo"
+#~ msgstr "Αναίρεση"
+
+#~ msgid "Redo"
+#~ msgstr "Επανάληψη"
+
+#~ msgid "Copy"
+#~ msgstr "Αντιγραφή"
+
+#~ msgid "Paste"
+#~ msgstr "Επικόλληση"
+
+#~ msgid "Keep error"
+#~ msgstr "Λάθος διατήρησης"
+
+#~ msgid "Keep error: all changes will be lost"
+#~ msgstr "Λάθος διατήρησης: όλες οι αλλαγές θα χαθούν"
+
+#~ msgid "Don't stop"
+#~ msgstr "Μη σταματάς"
+
+#~ msgid "Stop anyway"
+#~ msgstr "Σταμάτα οπωσδήποτε"
+
+#~ msgid "Continue"
+#~ msgstr "Συνέχισε"
+
+#~ msgid "OK"
+#~ msgstr "ΟΚ"
+
+#, python-format
+#~ msgid "%d year"
+#~ msgstr "%d χρόνος"
+
+#, python-format
+#~ msgid "%d years"
+#~ msgstr "%d χρόνια"
+
+#, python-format
+#~ msgid "%d month"
+#~ msgstr "%d μήνας"
+
+#, python-format
+#~ msgid "%d months"
+#~ msgstr "%d μήνες"
+
+#, python-format
+#~ msgid "%d week"
+#~ msgstr "%d βδομάδα"
+
+#, python-format
+#~ msgid "%d weeks"
+#~ msgstr "%d βδομάδες"
+
+#, python-format
+#~ msgid "%d day"
+#~ msgstr "%d ημέρα"
+
+#, python-format
+#~ msgid "%d days"
+#~ msgstr "%d ημέρες"
+
+#, python-format
+#~ msgid "%d hour"
+#~ msgstr "%d ώρα"
+
+#, python-format
+#~ msgid "%d hours"
+#~ msgstr "%d ώρες"
+
+#, python-format
+#~ msgid "%d minute"
+#~ msgstr "%d λεπτό"
+
+#, python-format
+#~ msgid "%d minutes"
+#~ msgstr "%d λεπτά"
+
+#, python-format
+#~ msgid "%d second"
+#~ msgstr "%d δεπτερόλεπτο"
+
+#~ msgid " and "
+#~ msgstr " και "
+
+#~ msgid ", "
+#~ msgstr ", "
diff --git a/shell/po/en.po b/shell/po/en.po
new file mode 100644
index 0000000..a79e711
--- /dev/null
+++ b/shell/po/en.po
@@ -0,0 +1,420 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-01-18 18:19+0000\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.0.1\n"
+
+#: ../shell/intro/intro.py:67
+msgid "Name:"
+msgstr ""
+
+#: ../shell/intro/intro.py:96
+msgid "Click to change color:"
+msgstr ""
+
+#: ../shell/intro/intro.py:146
+msgid "Back"
+msgstr ""
+
+#: ../shell/intro/intro.py:160
+msgid "Done"
+msgstr ""
+
+#: ../shell/intro/intro.py:163
+msgid "Next"
+msgstr ""
+
+#: ../shell/view/BuddyMenu.py:59
+msgid "Remove friend"
+msgstr ""
+
+#: ../shell/view/BuddyMenu.py:62
+msgid "Make friend"
+msgstr ""
+
+#: ../shell/view/BuddyMenu.py:84
+#, python-format
+msgid "Invite to %s"
+msgstr ""
+
+#: ../shell/view/clipboardmenu.py:58
+msgid "Remove"
+msgstr ""
+
+#: ../shell/view/clipboardmenu.py:63
+msgid "Open"
+msgstr ""
+
+#. self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+#. TODO: Implement stopping downloads
+#. self._stop_item.connect('activate', self._stop_item_activate_cb)
+#. self.append_menu_item(self._stop_item)
+#: ../shell/view/clipboardmenu.py:73
+msgid "Add to journal"
+msgstr ""
+
+#: ../shell/view/clipboardmenu.py:213
+#, python-format
+msgid "Clipboard object: %s."
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:149
+msgid "Key Type:"
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:169
+msgid "Authentication Type:"
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:250
+msgid "Encryption Type:"
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:90
+msgid "Starting..."
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:104 ../shell/view/home/MeshBox.py:295
+msgid "Resume"
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:111
+#: ../lib/sugar/activity/activity.py:132
+msgid "Stop"
+msgstr ""
+
+#: ../shell/view/Shell.py:285
+msgid "Screenshot"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:159
+msgid "Reboot"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:164
+msgid "Shutdown"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:170
+msgid "Register"
+msgstr ""
+
+#. Only show disconnect when there's a mesh device, because mesh takes
+#. priority over the normal wireless device. NM doesn't have a "disconnect"
+#. method for a device either (for various reasons) so this doesn't
+#. have a good mapping
+#: ../shell/view/home/MeshBox.py:90 ../shell/view/home/MeshBox.py:197
+#: ../shell/view/devices/network/wireless.py:113
+#: ../shell/view/devices/network/mesh.py:83
+msgid "Disconnect..."
+msgstr ""
+
+#: ../shell/view/home/MeshBox.py:195 ../shell/view/devices/network/mesh.py:37
+#: ../shell/view/devices/network/mesh.py:62
+#: ../shell/view/devices/network/mesh.py:66
+msgid "Mesh Network"
+msgstr ""
+
+#: ../shell/view/home/MeshBox.py:300
+msgid "Join"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:38
+msgid "My Battery life"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:94
+msgid "Battery charging"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:96
+msgid "Battery discharging"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:98
+msgid "Battery fully charged"
+msgstr ""
+
+#: ../shell/view/devices/network/wireless.py:61
+msgid "Disconnected"
+msgstr ""
+
+#: ../shell/view/devices/network/wireless.py:131
+msgid "Channel"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:42
+msgid "Neighborhood"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:54
+msgid "Group"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:66
+msgid "Home"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:78
+msgid "Activity"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:115
+msgid "Share with:"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:117
+msgid "Private"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:118
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:126
+msgid "Keep"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:245
+msgid "Undo"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:250
+msgid "Redo"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:260
+msgid "Copy"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:265
+msgid "Paste"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:454
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:824
+msgid "Keep error"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:825
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:828
+msgid "Don't stop"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:831
+msgid "Stop anyway"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:164 ../lib/sugar/graphics/alert.py:206
+msgid "Cancel"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:168
+msgid "Ok"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:216
+msgid "Continue"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:244
+msgid "OK"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d year"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d years"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d month"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d months"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d week"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d weeks"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d day"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d days"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hour"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hours"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minute"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minutes"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d second"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d seconds"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:191
+msgid " and "
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:193
+msgid ", "
+msgstr ""
+
+#: ../shell/controlpanel/control.py:213
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:267
+msgid "Error in specified color modifiers."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:270
+msgid "Error in specified colors."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:307
+msgid "off"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:309
+msgid "on"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:310
+msgid "State is unknown."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:332
+msgid "Error in specified radio argument use on/off."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:336
+msgid "Permission denied. You need to be root to run this method."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:366
+msgid "Error in reading timezone"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:397
+#, python-format
+msgid "Error copying timezone (from %s): %s"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:402
+#, python-format
+msgid "Changing permission of timezone: %s"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:412
+msgid "Error timezone does not exist."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:417 ../shell/controlpanel/control.py:436
+#, python-format
+msgid "Could not access %s. Create standard settings."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:463
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:473
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:105
+msgid "Connected to a School Mesh Portal"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:107
+msgid "Looking for a School Mesh Portal..."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:110
+msgid "Connected to an XO Mesh Portal"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:112
+msgid "Looking for an XO Mesh Portal..."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:115
+msgid "Connected to a Simple Mesh"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:117
+msgid "Starting a Simple Mesh"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:124
+msgid "Unknown Mesh"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:175 ../shell/view/home/HomeBox.py:216
+msgid "About this XO"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:222
+msgid "Not available"
+msgstr ""
diff --git a/shell/po/es.po b/shell/po/es.po
new file mode 100644
index 0000000..587608f
--- /dev/null
+++ b/shell/po/es.po
@@ -0,0 +1,1573 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: olpc-sugar\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-09-05 00:31-0400\n"
+"PO-Revision-Date: 2010-01-18 19:18+0200\n"
+"Last-Translator: Chris <cjl@laptop.org>\n"
+"Language-Team: Fedora Spanish <fedora-trans-es@redhat.com>\n"
+"Language: es\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Pootle 2.0.1\n"
+"X-Poedit-Language: Spanish\n"
+"X-Poedit-SourceCharset: utf-8\n"
+"X-Poedit-Basepath: .\n"
+
+#: ../extensions/cpsection/aboutme/__init__.py:24
+msgid "About Me"
+msgstr "Acerca de mí"
+
+#: ../extensions/cpsection/aboutme/model.py:43
+msgid "You must enter a name."
+msgstr "Debe ingresar un nombre."
+
+#: ../extensions/cpsection/aboutme/model.py:68
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr "borde: color=%s tonalidad=%s"
+
+#: ../extensions/cpsection/aboutme/model.py:71
+#, python-format
+msgid "stroke: %s"
+msgstr "borde: %s"
+
+#: ../extensions/cpsection/aboutme/model.py:73
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr "relleno: color=%s tonalidad=%s"
+
+#: ../extensions/cpsection/aboutme/model.py:75
+#, python-format
+msgid "fill: %s"
+msgstr "relleno: %s"
+
+#: ../extensions/cpsection/aboutme/model.py:86
+msgid "Error in specified color modifiers."
+msgstr "Error en modificadores de color especificados."
+
+#: ../extensions/cpsection/aboutme/model.py:89
+msgid "Error in specified colors."
+msgstr "Error en colores especificados."
+
+#: ../extensions/cpsection/aboutme/view.py:94 ../src/jarabe/intro/window.py:92
+msgid "Name:"
+msgstr "Nombre:"
+
+#: ../extensions/cpsection/aboutme/view.py:128
+msgid "Click to change your color:"
+msgstr "Clic para cambiar su color:"
+
+#: ../extensions/cpsection/aboutcomputer/__init__.py:21
+msgid "About my Computer"
+msgstr "Acerca de mi computadora"
+
+#: ../extensions/cpsection/aboutcomputer/model.py:28
+msgid "Not available"
+msgstr "No disponible"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:60
+msgid "Identity"
+msgstr "Identidad"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:69
+msgid "Serial Number:"
+msgstr "Número de serie:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:91
+msgid "Software"
+msgstr "Software"
+
+# Por ahora..
+#: ../extensions/cpsection/aboutcomputer/view.py:100
+msgid "Build:"
+msgstr "Ensamble:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:115
+msgid "Sugar:"
+msgstr "Azúcar:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:131
+msgid "Firmware:"
+msgstr "Firmware:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:146
+msgid "Wireless Firmware:"
+msgstr "Firmware Wireless:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:169
+msgid "Copyright and License"
+msgstr "Licencia y Copyright"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:184
+msgid ""
+"Sugar is the graphical user interface that you are looking at. Sugar is free "
+"software, covered by the GNU General Public License, and you are welcome to "
+"change it and/or distribute copies of it under certain conditions described "
+"therein."
+msgstr ""
+"Azucar es la interfaz gráfica de usuario que usted esta mirando. Azucar es "
+"software libre, cubierto bajo la licencia GNU Licencia Publica General, y "
+"esta invitado a cambiarla y/o distribuir copias bajo ciertas condiciones que "
+"se describen en ella."
+
+#: ../extensions/cpsection/aboutcomputer/view.py:196
+msgid "Full license:"
+msgstr "Licencia completa:"
+
+#: ../extensions/cpsection/datetime/__init__.py:21
+msgid "Date & Time"
+msgstr "Fecha y hora"
+
+#: ../extensions/cpsection/datetime/model.py:87
+msgid "Error timezone does not exist."
+msgstr "Error, zona horaria no existe."
+
+#: ../extensions/cpsection/datetime/view.py:68 ../data/sugar.schemas.in.h:27
+msgid "Timezone"
+msgstr "Zona horaria"
+
+#: ../extensions/cpsection/frame/__init__.py:21
+msgid "Frame"
+msgstr "Cuadro"
+
+#: ../extensions/cpsection/frame/model.py:38
+#: ../extensions/cpsection/frame/model.py:60
+msgid "Value must be an integer."
+msgstr "El valor debe ser un número entero."
+
+#: ../extensions/cpsection/frame/view.py:26
+msgid "never"
+msgstr "nunca"
+
+#: ../extensions/cpsection/frame/view.py:27
+msgid "instantaneous"
+msgstr "instantáneo"
+
+#: ../extensions/cpsection/frame/view.py:28
+#, python-format
+msgid "%s seconds"
+msgstr "%s segundos"
+
+#: ../extensions/cpsection/frame/view.py:52
+msgid "Activation Delay"
+msgstr "Retraso de activación"
+
+#: ../extensions/cpsection/frame/view.py:76
+msgid "Corner"
+msgstr "Esquina"
+
+#: ../extensions/cpsection/frame/view.py:111
+msgid "Edge"
+msgstr "Borde"
+
+#: ../extensions/cpsection/keyboard/__init__.py:21
+#: ../extensions/cpsection/keyboard/view.py:31
+msgid "Keyboard"
+msgstr "Teclado"
+
+#: ../extensions/cpsection/keyboard/view.py:187
+msgid "Keyboard Model"
+msgstr "Modelo de teclado"
+
+#: ../extensions/cpsection/keyboard/view.py:243
+msgid "Key(s) to change layout"
+msgstr "Tecla(s) para cambiar el diseño"
+
+#: ../extensions/cpsection/keyboard/view.py:311
+msgid "Keyboard Layout(s)"
+msgstr "Diseño(s) de teclado"
+
+#: ../extensions/cpsection/language/__init__.py:21
+#: ../extensions/cpsection/language/view.py:33
+msgid "Language"
+msgstr "Idioma"
+
+#: ../extensions/cpsection/language/model.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr ""
+"No se puede acceder a ~/.i18n. Crear configuración internacional estándar."
+
+#: ../extensions/cpsection/language/model.py:124
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr "El lenguaje del código=%s no pudo ser determinado."
+
+#: ../extensions/cpsection/language/model.py:144
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr "Lo siento, yo no hablo '%s'."
+
+#: ../extensions/cpsection/language/view.py:56
+msgid ""
+"Add languages in the order you prefer. If a translation is not available, "
+"the next in the list will be used."
+msgstr ""
+"Añade idiomas en el orden que prefieres. Si una traducción no se encuentra "
+"disponible, se usará la siguiente en la lista."
+
+#: ../extensions/cpsection/network/__init__.py:21
+#: ../extensions/cpsection/network/view.py:28
+msgid "Network"
+msgstr "Red"
+
+#: ../extensions/cpsection/network/model.py:79
+msgid "State is unknown."
+msgstr "Estado desconocido."
+
+#: ../extensions/cpsection/network/model.py:105
+msgid "Error in specified radio argument use on/off."
+msgstr "Error en argumento especificado de radio use on/off."
+
+#: ../extensions/cpsection/network/model.py:137
+msgid "Error in specified argument use 0/1."
+msgstr "Error en argumento especificado use 0/1."
+
+#: ../extensions/cpsection/network/view.py:59
+msgid "Wireless"
+msgstr "Inalámbrica"
+
+#: ../extensions/cpsection/network/view.py:67
+msgid "Turn off the wireless radio to save battery life"
+msgstr "Apague la radio inalámbrica y ahorre vida de batería"
+
+#: ../extensions/cpsection/network/view.py:80
+msgid "Radio"
+msgstr "Radio"
+
+#: ../extensions/cpsection/network/view.py:96
+msgid "Discard network history if you have trouble connecting to the network"
+msgstr "Descarte el historial de la red si tiene problemas de conexión"
+
+#: ../extensions/cpsection/network/view.py:105
+msgid "Discard network history"
+msgstr "Descarte historial de la red"
+
+#: ../extensions/cpsection/network/view.py:118
+msgid "Collaboration"
+msgstr "Colaboración"
+
+#: ../extensions/cpsection/network/view.py:126
+msgid ""
+"The server is the equivalent of what room you are in; people on the same "
+"server will be able to see each other, even when they aren't on the same "
+"network."
+msgstr ""
+"El servidor es equivalente al cuarto en el cual se esta; la gente en el "
+"mismo servidor podrá verse entre ellos, aun cuando no esten en la misma red."
+
+#: ../extensions/cpsection/network/view.py:136
+msgid "Server:"
+msgstr "Servidor:"
+
+#: ../extensions/cpsection/power/__init__.py:21
+msgid "Power"
+msgstr "Energía"
+
+#: ../extensions/cpsection/power/model.py:54
+msgid "Error in automatic pm argument, use on/off."
+msgstr "Error en argumento automático de pm, use on/off."
+
+#: ../extensions/cpsection/power/model.py:81
+msgid "Error in extreme pm argument, use on/off."
+msgstr "Error en argumento extremo de pm, use on/off."
+
+#: ../extensions/cpsection/power/view.py:47
+msgid "Power management"
+msgstr "Manejo de energía"
+
+#: ../extensions/cpsection/power/view.py:57
+msgid "Automatic power management (increases battery life)"
+msgstr "Manejo automático de energía (incrementa la vida de la batería)"
+
+# best translationfor now
+#: ../extensions/cpsection/power/view.py:85
+msgid ""
+"Extreme power management (disableswireless radio, increases battery life)"
+msgstr ""
+"Manejo extremo de energía (deshabilita el radio wireless, incrementa la vida "
+"de la batería)"
+
+#: ../extensions/cpsection/updater/__init__.py:21
+msgid "Software update"
+msgstr "Actualización de Software"
+
+#: ../extensions/cpsection/updater/view.py:62
+msgid ""
+"Software updates correct errors, eliminate security vulnerabilities, and "
+"provide new features."
+msgstr ""
+"Las actualizaciones de software corrigen errores, eliminan vulnerabilidades "
+"de seguridad y proveen nuevas características."
+
+#: ../extensions/cpsection/updater/view.py:122
+#, python-format
+msgid "Checking %s..."
+msgstr "Probando %s..."
+
+#: ../extensions/cpsection/updater/view.py:124
+#, python-format
+msgid "Downloading %s..."
+msgstr "Descargando %s..."
+
+#: ../extensions/cpsection/updater/view.py:126
+#, python-format
+msgid "Updating %s..."
+msgstr "Actualizando %s..."
+
+#: ../extensions/cpsection/updater/view.py:135
+msgid "Your software is up-to-date"
+msgstr "Tu software esta actualizado"
+
+#: ../extensions/cpsection/updater/view.py:137
+#, python-format
+msgid "You can install %s update"
+msgid_plural "You can install %s updates"
+msgstr[0] "Puedes instalar %s actualización"
+msgstr[1] "Puedes instalar %s actualizaciones"
+
+#: ../extensions/cpsection/updater/view.py:155
+msgid "Checking for updates..."
+msgstr "Buscando actualizaciones..."
+
+#: ../extensions/cpsection/updater/view.py:160
+msgid "Installing updates..."
+msgstr "Instalando actualizaciones..."
+
+#: ../extensions/cpsection/updater/view.py:165
+#, python-format
+msgid "%s update was installed"
+msgid_plural "%s updates were installed"
+msgstr[0] "%s actualización fue instalada"
+msgstr[1] "%s actualizaciones fueron instaladas"
+
+#: ../extensions/cpsection/updater/view.py:244
+msgid "Install selected"
+msgstr "Instalación seleccionada"
+
+#: ../extensions/cpsection/updater/view.py:265
+#, python-format
+msgid "Download size: %s"
+msgstr "Tamaño de descarga: %s"
+
+#: ../extensions/cpsection/updater/view.py:353
+#, python-format
+msgid "From version %(current)d to %(new)s (Size: %(size)s)"
+msgstr "Desde la version %(current)d hacia %(new)s (Size: %(size)s)"
+
+#. TRANS: download size is 0
+#: ../extensions/cpsection/updater/view.py:373
+msgid "None"
+msgstr "Ninguno"
+
+#. TRANS: download size of very small updates
+#: ../extensions/cpsection/updater/view.py:376
+msgid "1 KB"
+msgstr "1 KB"
+
+#. TRANS: download size of small updates, e.g. '250 KB'
+#: ../extensions/cpsection/updater/view.py:379
+#, python-format
+msgid "%.0f KB"
+msgstr "%.0f KB"
+
+#. TRANS: download size of updates, e.g. '2.3 MB'
+#: ../extensions/cpsection/updater/view.py:382
+#, python-format
+msgid "%.1f MB"
+msgstr "%.1f MB"
+
+#: ../extensions/deviceicon/battery.py:58
+msgid "My Battery"
+msgstr "Mi batería"
+
+#: ../extensions/deviceicon/battery.py:137
+msgid "Removed"
+msgstr "Eliminado"
+
+#: ../extensions/deviceicon/battery.py:140
+msgid "Charging"
+msgstr "Cargando"
+
+#: ../extensions/deviceicon/battery.py:143
+msgid "Very little power remaining"
+msgstr "Queda muy poca batería"
+
+#: ../extensions/deviceicon/battery.py:149
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr "Quedan %(hour)d:%(min).2d"
+
+#: ../extensions/deviceicon/battery.py:152
+msgid "Charged"
+msgstr "Cargada"
+
+#: ../extensions/deviceicon/network.py:44
+#, python-format
+msgid "IP address: %s"
+msgstr "Direccion IP: %s"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#: ../extensions/deviceicon/network.py:110
+msgid "Disconnect..."
+msgstr "Desconectando..."
+
+#: ../extensions/deviceicon/network.py:114
+msgid "Create new wireless network"
+msgstr "Crear nueva red inalámbrica"
+
+#: ../extensions/deviceicon/network.py:120
+#: ../src/jarabe/desktop/meshbox.py:264
+msgid "Connecting..."
+msgstr "Conectando..."
+
+# TODO: show the channel number
+#: ../extensions/deviceicon/network.py:124
+#: ../extensions/deviceicon/network.py:186
+#: ../src/jarabe/desktop/meshbox.py:270
+msgid "Connected"
+msgstr "Conectado"
+
+#: ../extensions/deviceicon/network.py:146
+msgid "Channel"
+msgstr "Canal"
+
+#: ../extensions/deviceicon/network.py:161
+msgid "Wired Network"
+msgstr "Red Cableada"
+
+#: ../extensions/deviceicon/network.py:189
+msgid "Speed"
+msgstr "Velocidad"
+
+#: ../extensions/deviceicon/network.py:415
+#, python-format
+msgid "%s's network %s"
+msgstr "%s's red %s"
+
+#: ../extensions/deviceicon/speaker.py:59
+msgid "My Speakers"
+msgstr "Mis parlantes"
+
+# la traducción la tome del AlsaMixer de Gnome.
+#: ../extensions/deviceicon/speaker.py:133
+msgid "Unmute"
+msgstr "Dar voz"
+
+#: ../extensions/deviceicon/speaker.py:136
+msgid "Mute"
+msgstr "Silenciar"
+
+#: ../extensions/globalkey/screenshot.py:56
+msgid "Mesh"
+msgstr "Malla"
+
+#: ../extensions/globalkey/screenshot.py:58
+#: ../src/jarabe/frame/zoomtoolbar.py:39
+msgid "Group"
+msgstr "Grupo"
+
+#: ../extensions/globalkey/screenshot.py:60
+#: ../src/jarabe/frame/zoomtoolbar.py:41
+msgid "Home"
+msgstr "Hogar"
+
+#: ../extensions/globalkey/screenshot.py:66
+#: ../src/jarabe/frame/zoomtoolbar.py:43
+msgid "Activity"
+msgstr "Actividad"
+
+#: ../extensions/globalkey/screenshot.py:69
+msgid "Screenshot"
+msgstr "Captura de pantalla"
+
+#: ../extensions/globalkey/screenshot.py:71
+#, python-format
+msgid "Screenshot of \"%s\""
+msgstr "Captura pantalla de \"%s\""
+
+#: ../data/sugar.schemas.in.h:1
+msgid "Backup URL"
+msgstr "URL de Respaldo"
+
+#: ../data/sugar.schemas.in.h:2
+msgid ""
+"Color for the XO icon that is used throughout the desktop. The string is "
+"composed of the stroke color and fill color, format is that of rbg colors. "
+"Example: #AC32FF,#9A5200"
+msgstr ""
+"El color para el ícono del XO se utiliza en todo el escritorio. La cadena "
+"está compuesta por el trazo y color de relleno de color, el formato es el de "
+"colores RBG. Ejemplo: #AC32FF, #9A5200"
+
+# es la mejor traduccion ?
+#: ../data/sugar.schemas.in.h:3
+msgid "Corner Delay"
+msgstr "Retraso de las Esquinas"
+
+#: ../data/sugar.schemas.in.h:4
+msgid "Delay for the activation of the frame using the corners."
+msgstr "Retraso para la activación del cuadro utilizando las esquinas."
+
+#: ../data/sugar.schemas.in.h:5
+msgid "Delay for the activation of the frame using the edges."
+msgstr "Retraso para la activación del cuadro utilizando los bordes."
+
+# es la mejor traduccion ?
+#: ../data/sugar.schemas.in.h:6
+msgid "Edge Delay"
+msgstr "Retraso del Borde"
+
+#: ../data/sugar.schemas.in.h:7
+msgid "Favorites Layout"
+msgstr "Diseño de favoritos"
+
+#: ../data/sugar.schemas.in.h:8
+msgid "Favorites resume mode"
+msgstr "Modo de reanudar favoritos"
+
+#: ../data/sugar.schemas.in.h:9
+msgid ""
+"If TRUE, Sugar will make us searchable for the other users of the Jabber "
+"server."
+msgstr ""
+"Si es TRUE, Azúcar habilitará que otros usuarios nos busquen en el servidor "
+"Jabber."
+
+#: ../data/sugar.schemas.in.h:10
+msgid "If TRUE, Sugar will show a \"Log out\" option."
+msgstr "Si es TRUE, Azúcar mostrará una opción \"Terminar Sesión\"."
+
+#: ../data/sugar.schemas.in.h:11
+msgid "Jabber Server"
+msgstr "Servidor Jabber"
+
+#: ../data/sugar.schemas.in.h:12
+msgid "Keyboard layouts"
+msgstr "Distribuciones del teclado"
+
+#: ../data/sugar.schemas.in.h:13
+msgid "Keyboard model"
+msgstr "Modelo del teclado"
+
+#: ../data/sugar.schemas.in.h:14
+msgid "Keyboard options"
+msgstr "Opciones del teclado"
+
+#: ../data/sugar.schemas.in.h:15
+msgid "Layout of the favorites view."
+msgstr "Distribución de las actividades favoritas."
+
+#: ../data/sugar.schemas.in.h:16
+msgid ""
+"List of keyboard layouts. Each entry should be in the form layout(variant)"
+msgstr ""
+"Lista de las distribuciones de teclado. Cada entrada debe ser en la forma "
+"distribución(variante)"
+
+#: ../data/sugar.schemas.in.h:17
+msgid "List of keyboard options."
+msgstr "Lista de las opciones del teclado."
+
+#: ../data/sugar.schemas.in.h:18
+msgid "Power Automatic"
+msgstr "Manejo automática de energía"
+
+#: ../data/sugar.schemas.in.h:19
+msgid "Power Automatic."
+msgstr "Manejo automática de energía."
+
+#: ../data/sugar.schemas.in.h:20
+msgid "Power Extreme"
+msgstr "Manejo extremo de energía"
+
+#: ../data/sugar.schemas.in.h:21
+msgid "Power Extreme."
+msgstr "Manejo extremo de energía."
+
+#: ../data/sugar.schemas.in.h:22
+msgid "Publish to Gadget"
+msgstr "Publicar en Gadget"
+
+#: ../data/sugar.schemas.in.h:23
+msgid "Setting for muting the sound device."
+msgstr "Configuración para silenciar el dispositivo de sonido."
+
+#: ../data/sugar.schemas.in.h:24
+msgid "Show Log out"
+msgstr "Mostrar Terminar Sesión"
+
+#: ../data/sugar.schemas.in.h:25
+msgid "Sound Muted"
+msgstr "Sonido silenciado"
+
+#: ../data/sugar.schemas.in.h:26
+msgid "The keyboard model to be used"
+msgstr "El modelo del teclado que se utilizará"
+
+#: ../data/sugar.schemas.in.h:28
+msgid "Timezone setting for the system."
+msgstr "Configuración de zona horaria para el sistema."
+
+#: ../data/sugar.schemas.in.h:29
+msgid "Url of the jabber server to use."
+msgstr "URL del servidor de Jabber para usar."
+
+#: ../data/sugar.schemas.in.h:30
+msgid "Url where the backup is saved to."
+msgstr "URL donde se guarda el backup."
+
+#: ../data/sugar.schemas.in.h:31
+msgid "User Color"
+msgstr "Color del usuario"
+
+#: ../data/sugar.schemas.in.h:32
+msgid "User Name"
+msgstr "Nombre de usuario"
+
+#: ../data/sugar.schemas.in.h:33
+msgid "User name that is used throughout the desktop."
+msgstr "Nombre de usuario que se utiliza en todo el escritorio."
+
+#: ../data/sugar.schemas.in.h:34
+msgid "Volume Level"
+msgstr "Nivel de volumen"
+
+#: ../data/sugar.schemas.in.h:35
+msgid "Volume level for the sound device."
+msgstr "Nivel de volumen para el dispositivo de sonido."
+
+#: ../data/sugar.schemas.in.h:36
+msgid ""
+"When in resume mode, clicking on a favorite icon will cause the last entry "
+"for that activity to be resumed."
+msgstr ""
+"Cuando en el modo de retomar, al hacer clic en un icono de favoritos causa "
+"que se retome la última entrada de esa actividad."
+
+#: ../src/jarabe/controlpanel/cmd.py:28
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+"sugar-control-panel: ADVERTENCIA, hay más de una opción con el mismo nombre: "
+"módulo %s: %r"
+
+#: ../src/jarabe/controlpanel/cmd.py:30
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr "sugar-control-panel: clave=%s no es una opción disponible"
+
+#: ../src/jarabe/controlpanel/cmd.py:31
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr "sugar-control-panel: %s"
+
+# TRANS: Translators, there's a empty line at the end of this string,<br /><br />
+# which must appear in the translated string (msgstr) as well.
+#. TRANS: Translators, there's a empty line at the end of this string,
+#. which must appear in the translated string (msgstr) as well.
+#: ../src/jarabe/controlpanel/cmd.py:37
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" -c key clear the current value for the key \n"
+" "
+msgstr ""
+"Uso: sugar-control-panel [ opción ] clave [ args ... ] \n"
+" Control para el ambiente de sugar. \n"
+" Opciones: \n"
+" -h muestra este mensaje de ayuda y sale \n"
+" -l enumera todas las opciones disponibles \n"
+" -h clave muestra la información sobre esta clave \n"
+" -g clave obtiene el valor actual de la clave \n"
+" -s clave establece el valor actual de la clave \n"
+" -c clave vaciar el valor actual de la clave \n"
+" "
+
+#: ../src/jarabe/controlpanel/cmd.py:50
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr "Para aplicar sus cambios tiene que reiniciar Azúcar.\n"
+
+#: ../src/jarabe/controlpanel/gui.py:280
+msgid "Warning"
+msgstr "Advertencia"
+
+#: ../src/jarabe/controlpanel/gui.py:281
+#: ../src/jarabe/controlpanel/sectionview.py:42
+msgid "Changes require restart"
+msgstr "Los cambios requieren reiniciar"
+
+#: ../src/jarabe/controlpanel/gui.py:284
+msgid "Cancel changes"
+msgstr "Cancelar cambios"
+
+#: ../src/jarabe/controlpanel/gui.py:289 ../src/jarabe/desktop/homebox.py:70
+msgid "Later"
+msgstr "Después"
+
+#: ../src/jarabe/controlpanel/gui.py:293
+msgid "Restart now"
+msgstr "Reiniciar ahora"
+
+#: ../src/jarabe/controlpanel/toolbar.py:61 ../src/jarabe/intro/window.py:188
+msgid "Done"
+msgstr "Hecho"
+
+#: ../src/jarabe/controlpanel/toolbar.py:115
+#: ../src/jarabe/desktop/homebox.py:68
+#: ../src/jarabe/frame/activitiestray.py:726
+#: ../src/jarabe/frame/activitiestray.py:822
+#: ../src/jarabe/frame/activitiestray.py:850
+msgid "Cancel"
+msgstr "Cancelar"
+
+#: ../src/jarabe/controlpanel/toolbar.py:121
+#: ../src/jarabe/desktop/favoritesview.py:332
+msgid "Ok"
+msgstr "Ok"
+
+#: ../src/jarabe/desktop/activitieslist.py:80
+#: ../src/jarabe/journal/listview.py:147
+msgid "Title"
+msgstr "Título"
+
+#: ../src/jarabe/desktop/activitieslist.py:91
+msgid "Version"
+msgstr "Versión"
+
+#: ../src/jarabe/desktop/activitieslist.py:105
+#: ../src/jarabe/journal/listview.py:178
+msgid "Date"
+msgstr "Fecha"
+
+#: ../src/jarabe/desktop/activitieslist.py:234
+#, python-format
+msgid "Version %s"
+msgstr "Versión %s"
+
+#: ../src/jarabe/desktop/activitieslist.py:355
+msgid "Confirm erase"
+msgstr "Confirmar borrado"
+
+#: ../src/jarabe/desktop/activitieslist.py:357
+#, python-format
+msgid "Confirm erase: Do you want to permanently erase %s?"
+msgstr "Confirmar el borrado: ¿Quiere borrar %s de forma permanente?"
+
+# self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+# TODO: Implement stopping downloads
+# self._stop_item.connect('activate', self._stop_item_activate_cb)
+# self.append_menu_item(self._stop_item)
+#: ../src/jarabe/desktop/activitieslist.py:361
+#: ../src/jarabe/frame/clipboardmenu.py:62
+#: ../src/jarabe/view/viewsource.py:218
+msgid "Keep"
+msgstr "Guardar"
+
+#: ../src/jarabe/desktop/activitieslist.py:364
+#: ../src/jarabe/desktop/activitieslist.py:407
+#: ../src/jarabe/journal/journaltoolbox.py:360
+#: ../src/jarabe/journal/palettes.py:112
+msgid "Erase"
+msgstr "Borrar"
+
+#: ../src/jarabe/desktop/activitieslist.py:428
+msgid "Remove favorite"
+msgstr "Remover favorito"
+
+#: ../src/jarabe/desktop/activitieslist.py:432
+msgid "Make favorite"
+msgstr "Hacer favorito"
+
+# TRANS: label for the freeform layout in the favorites view
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:116
+msgid "Freeform"
+msgstr "Forma libre"
+
+# TRANS: label for the ring layout in the favorites view
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:198
+msgid "Ring"
+msgstr "Anillo"
+
+# TRANS: label for the spiral layout in the favorites view
+#. TRANS: label for the spiral layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:337
+msgid "Spiral"
+msgstr "Espiral"
+
+# TRANS: label for the box layout in the favorites view
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:404
+msgid "Box"
+msgstr "Caja"
+
+# TRANS: label for the box layout in the favorites view
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:445
+msgid "Triangle"
+msgstr "Triángulo"
+
+#: ../src/jarabe/desktop/favoritesview.py:323
+msgid "Registration Failed"
+msgstr "Registro fallido"
+
+#: ../src/jarabe/desktop/favoritesview.py:324
+#, python-format
+msgid "%s"
+msgstr "%s"
+
+#: ../src/jarabe/desktop/favoritesview.py:326
+msgid "Registration Successful"
+msgstr "Registro exitoso"
+
+#: ../src/jarabe/desktop/favoritesview.py:327
+msgid "You are now registered with your school server."
+msgstr "Ahora estás registrado en el servidor de colegio."
+
+#: ../src/jarabe/desktop/favoritesview.py:671
+msgid "Register"
+msgstr "Registro"
+
+#: ../src/jarabe/desktop/homebox.py:63
+msgid "Software Update"
+msgstr "Actualización de Software"
+
+#: ../src/jarabe/desktop/homebox.py:64
+msgid "Update your activities to ensure compatibility with your new software"
+msgstr ""
+"Actualice sus actividades para asegurar compatibilidad con su nuevo software"
+
+#: ../src/jarabe/desktop/homebox.py:73
+msgid "Check now"
+msgstr "Pruebe ahora"
+
+#: ../src/jarabe/desktop/homebox.py:192
+msgid "List view"
+msgstr "Vista en lista"
+
+#: ../src/jarabe/desktop/homebox.py:193
+msgid "<Ctrl>2"
+msgstr "<Ctrl>2"
+
+#: ../src/jarabe/desktop/homebox.py:255
+msgid "Favorites view"
+msgstr "Vista de favoritos"
+
+#: ../src/jarabe/desktop/homebox.py:256
+msgid "<Ctrl>1"
+msgstr "<Ctrl>1"
+
+# This is an encryption key type, not a keyboard key
+#: ../src/jarabe/desktop/keydialog.py:131
+msgid "Key Type:"
+msgstr "Tipo de clave:"
+
+#: ../src/jarabe/desktop/keydialog.py:151
+msgid "Authentication Type:"
+msgstr "Tipo de autenticación:"
+
+#: ../src/jarabe/desktop/keydialog.py:215
+msgid "WPA & WPA2 Personal"
+msgstr "WPA y WPA2 Personal"
+
+#: ../src/jarabe/desktop/keydialog.py:224
+msgid "Wireless Security:"
+msgstr "Seguridad inalámbrica:"
+
+#: ../src/jarabe/desktop/meshbox.py:136
+msgid "Connect"
+msgstr "Conectar"
+
+#: ../src/jarabe/desktop/meshbox.py:140
+msgid "Disconnect"
+msgstr "Desconectar"
+
+# TRANS: Action label for resuming an activity.
+#. TRANS: Action label for resuming an activity.
+#: ../src/jarabe/desktop/meshbox.py:466
+#: ../src/jarabe/frame/activitiestray.py:761
+#: ../src/jarabe/journal/journaltoolbox.py:428
+#: ../src/jarabe/journal/palettes.py:72 ../src/jarabe/view/palettes.py:64
+msgid "Resume"
+msgstr "Retomar"
+
+#: ../src/jarabe/desktop/meshbox.py:471
+#: ../src/jarabe/frame/activitiestray.py:235
+msgid "Join"
+msgstr "Unirse"
+
+#: ../src/jarabe/desktop/schoolserver.py:103
+msgid "Cannot connect to the server."
+msgstr "No se puede conectar al servidor."
+
+#: ../src/jarabe/desktop/schoolserver.py:108
+msgid "The server could not complete the request."
+msgstr "El servidor no pudo completar el pedido."
+
+#: ../src/jarabe/frame/activitiestray.py:240
+#: ../src/jarabe/frame/activitiestray.py:698
+msgid "Decline"
+msgstr "Rechazar"
+
+#: ../src/jarabe/frame/activitiestray.py:650
+#, python-format
+msgid "%dB"
+msgstr "%dB"
+
+#: ../src/jarabe/frame/activitiestray.py:652
+#, python-format
+msgid "%dKB"
+msgstr "%dKB"
+
+#: ../src/jarabe/frame/activitiestray.py:654
+#, python-format
+msgid "%dMB"
+msgstr "%dMB"
+
+#: ../src/jarabe/frame/activitiestray.py:671
+#, python-format
+msgid "%s of %s"
+msgstr "%s de %s"
+
+#: ../src/jarabe/frame/activitiestray.py:683
+#, python-format
+msgid "Transfer from %r"
+msgstr "Transferencia desde %r"
+
+#: ../src/jarabe/frame/activitiestray.py:693
+msgid "Accept"
+msgstr "Aceptar"
+
+#: ../src/jarabe/frame/activitiestray.py:716
+#: ../src/jarabe/frame/activitiestray.py:840
+#, python-format
+msgid "%s (%s)"
+msgstr "%s (%s)"
+
+#: ../src/jarabe/frame/activitiestray.py:750
+#: ../src/jarabe/frame/activitiestray.py:875
+msgid "Dismiss"
+msgstr "Descartar"
+
+#: ../src/jarabe/frame/activitiestray.py:810
+#, python-format
+msgid "Transfer to %r"
+msgstr "Transferencia a %r"
+
+#: ../src/jarabe/frame/clipboardmenu.py:52 ../src/jarabe/view/palettes.py:218
+msgid "Remove"
+msgstr "Eliminar"
+
+#: ../src/jarabe/frame/clipboardmenu.py:57
+#: ../src/jarabe/frame/clipboardmenu.py:80
+msgid "Open"
+msgstr "Abrir"
+
+#: ../src/jarabe/frame/clipboardmenu.py:85
+msgid "Open with"
+msgstr "Abrir con"
+
+#: ../src/jarabe/frame/clipboardobject.py:49
+#, python-format
+msgid "%s clipping"
+msgstr "recorte de %s"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:37
+msgid "Neighborhood"
+msgstr "Vecindario"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:37
+msgid "F1"
+msgstr "F1"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:39
+msgid "F2"
+msgstr "F2"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:41
+msgid "F3"
+msgstr "F3"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:43
+msgid "F4"
+msgstr "F4"
+
+#: ../src/jarabe/intro/window.py:124
+msgid "Click to change color:"
+msgstr "Clic para cambiar de color:"
+
+#: ../src/jarabe/intro/window.py:174 ../src/jarabe/journal/detailview.py:103
+msgid "Back"
+msgstr "Atrás"
+
+#: ../src/jarabe/intro/window.py:191
+msgid "Next"
+msgstr "Siguiente"
+
+#: ../src/jarabe/journal/expandedentry.py:164
+#: ../src/jarabe/journal/palettes.py:66
+msgid "Untitled"
+msgstr "Sin título"
+
+#: ../src/jarabe/journal/expandedentry.py:210
+msgid "No preview"
+msgstr "Sin vista previa"
+
+#: ../src/jarabe/journal/expandedentry.py:229
+#, python-format
+msgid "Kind: %s"
+msgstr "Tipo: %s"
+
+#: ../src/jarabe/journal/expandedentry.py:229
+msgid "Unknown"
+msgstr "Desconocido"
+
+#: ../src/jarabe/journal/expandedentry.py:230
+#, python-format
+msgid "Date: %s"
+msgstr "Fecha: %s"
+
+#: ../src/jarabe/journal/expandedentry.py:231
+#, python-format
+msgid "Size: %s"
+msgstr "Tamaño: %s"
+
+#: ../src/jarabe/journal/expandedentry.py:253 ../src/jarabe/journal/misc.py:92
+msgid "No date"
+msgstr "Sin fecha"
+
+#: ../src/jarabe/journal/expandedentry.py:260
+msgid "Participants:"
+msgstr "Participantes:"
+
+#: ../src/jarabe/journal/expandedentry.py:283
+msgid "Description:"
+msgstr "Descripción:"
+
+#: ../src/jarabe/journal/expandedentry.py:309
+msgid "Tags:"
+msgstr "Etiquetas:"
+
+#: ../src/jarabe/journal/journalactivity.py:108
+#: ../src/jarabe/journal/volumestoolbar.py:47
+msgid "Journal"
+msgstr "Diario"
+
+#: ../src/jarabe/journal/journaltoolbox.py:67
+msgid "Search"
+msgstr "Buscar"
+
+#: ../src/jarabe/journal/journaltoolbox.py:126
+msgid "Anytime"
+msgstr "Cualquier momento"
+
+#: ../src/jarabe/journal/journaltoolbox.py:128
+msgid "Today"
+msgstr "Hoy"
+
+#: ../src/jarabe/journal/journaltoolbox.py:130
+msgid "Since yesterday"
+msgstr "Desde ayer"
+
+# TRANS: Filter entries modified during the last 7 days.
+#. TRANS: Filter entries modified during the last 7 days.
+#: ../src/jarabe/journal/journaltoolbox.py:132
+msgid "Past week"
+msgstr "Última semana"
+
+# TRANS: Filter entries modified during the last 30 days.
+#. TRANS: Filter entries modified during the last 30 days.
+#: ../src/jarabe/journal/journaltoolbox.py:134
+msgid "Past month"
+msgstr "Último mes"
+
+# TRANS: Filter entries modified during the last 356 days.
+#. TRANS: Filter entries modified during the last 356 days.
+#: ../src/jarabe/journal/journaltoolbox.py:136
+msgid "Past year"
+msgstr "Último año"
+
+#: ../src/jarabe/journal/journaltoolbox.py:143
+msgid "Anyone"
+msgstr "Cualquiera"
+
+#: ../src/jarabe/journal/journaltoolbox.py:145
+msgid "My friends"
+msgstr "Mis amigos"
+
+#: ../src/jarabe/journal/journaltoolbox.py:146
+msgid "My class"
+msgstr "Mi clase"
+
+# TRANS: Item in a combo box that filters by entry type.
+#: ../src/jarabe/journal/journaltoolbox.py:274
+msgid "Anything"
+msgstr "Cualquiera"
+
+# TODO: Add "Start with" menu item
+#: ../src/jarabe/journal/journaltoolbox.py:350
+#: ../src/jarabe/journal/palettes.py:90
+msgid "Copy"
+msgstr "Copiar"
+
+# TRANS: Action label for starting an entry.
+#. TRANS: Action label for starting an entry.
+#: ../src/jarabe/journal/journaltoolbox.py:431
+#: ../src/jarabe/journal/palettes.py:75
+msgid "Start"
+msgstr "Iniciar"
+
+#: ../src/jarabe/journal/listview.py:361
+msgid "Your Journal is empty"
+msgstr "Su diario está vacío"
+
+#: ../src/jarabe/journal/listview.py:363
+msgid "No matching entries"
+msgstr "No hay entradas coincidentes"
+
+#: ../src/jarabe/journal/listview.py:374
+msgid "Clear search"
+msgstr "Limpiar búsqueda"
+
+#: ../src/jarabe/journal/modalalert.py:63
+msgid "Your Journal is full"
+msgstr "Su diario está vacío"
+
+#: ../src/jarabe/journal/modalalert.py:67
+msgid "Please delete some old Journal entries to make space for new ones."
+msgstr ""
+"Por favor borre las entradas viejas del diario para hacer espacio a las "
+"nuevas entradas."
+
+#: ../src/jarabe/journal/modalalert.py:79
+msgid "Show Journal"
+msgstr "Mostrar diario"
+
+#: ../src/jarabe/journal/objectchooser.py:146
+msgid "Choose an object"
+msgstr "Escoja un objeto"
+
+#: ../src/jarabe/journal/objectchooser.py:151
+#: ../src/jarabe/view/viewsource.py:308
+msgid "Close"
+msgstr "Cerrar"
+
+#: ../src/jarabe/journal/palettes.py:73
+msgid "Resume with"
+msgstr "Reiniciar con"
+
+#: ../src/jarabe/journal/palettes.py:76
+msgid "Start with"
+msgstr "Empezar con"
+
+#: ../src/jarabe/journal/palettes.py:98
+msgid "Send to"
+msgstr "Enviar a"
+
+#: ../src/jarabe/journal/palettes.py:107
+msgid "View Details"
+msgstr "Ver detalles"
+
+#: ../src/jarabe/journal/palettes.py:185
+msgid "No friends present"
+msgstr "No hay amigos presentes"
+
+# tildes
+#: ../src/jarabe/journal/palettes.py:190
+msgid "No valid connection found"
+msgstr "No se encontró una conexión válida"
+
+# tildes...
+#: ../src/jarabe/journal/palettes.py:218
+msgid "No activity to resume entry"
+msgstr "No se encontró una actividad para retomar la entrada"
+
+#: ../src/jarabe/journal/palettes.py:220
+msgid "No activity to start entry"
+msgstr "No se encontró una actividad para iniciar la entrada"
+
+# "Eliminate friend"??? That's a bit harsh. Wouldn't "quitar amigo" be a better choice?--
+# agree but i preffer remover :). that verbe has the exact meaning we are looking on here.
+#: ../src/jarabe/view/buddymenu.py:62
+msgid "Remove friend"
+msgstr "Remover amigo"
+
+#: ../src/jarabe/view/buddymenu.py:65
+msgid "Make friend"
+msgstr "Agregar amigo"
+
+#: ../src/jarabe/view/buddymenu.py:82
+msgid "Shutdown"
+msgstr "Apagar"
+
+#: ../src/jarabe/view/buddymenu.py:90
+msgid "Logout"
+msgstr "Salir"
+
+#: ../src/jarabe/view/buddymenu.py:95
+msgid "My Settings"
+msgstr "Mis ajustes"
+
+#: ../src/jarabe/view/buddymenu.py:130
+#, python-format
+msgid "Invite to %s"
+msgstr "Invitar a %s"
+
+#: ../src/jarabe/view/palettes.py:45
+msgid "Starting..."
+msgstr "Iniciando..."
+
+#. TODO: share-with, keep
+#: ../src/jarabe/view/palettes.py:71
+msgid "View Source"
+msgstr "Ver fuente"
+
+#: ../src/jarabe/view/palettes.py:82
+msgid "Stop"
+msgstr "Parar"
+
+#: ../src/jarabe/view/palettes.py:122
+msgid "Start new"
+msgstr "Empezar nuevo"
+
+#: ../src/jarabe/view/palettes.py:171
+msgid "Show contents"
+msgstr "Mostrar contenidos"
+
+#: ../src/jarabe/view/palettes.py:193 ../src/jarabe/view/palettes.py:243
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr "%(free_space)d MB libres"
+
+#: ../src/jarabe/view/viewsource.py:208
+msgid "Instance Source"
+msgstr "Fuente de la instancia"
+
+#: ../src/jarabe/view/viewsource.py:233
+msgid "Source"
+msgstr "Fuente"
+
+#: ../src/jarabe/view/viewsource.py:292
+msgid "Activity Bundle Source"
+msgstr "Fuente del paquete de la actividad"
+
+#: ../src/jarabe/view/viewsource.py:299
+#, python-format
+msgid "View source: %r"
+msgstr "Ver código fuente: %r"
+
+#~ msgid "Cannot obtain data needed for registration."
+#~ msgstr "No se puede obtener datos necesarios para el registro"
+
+#~ msgid "Unmount"
+#~ msgstr "Desmontar"
+
+#~ msgid "Restart"
+#~ msgstr "Reiniciar"
+
+#~ msgid ""
+#~ "© 2008 One Laptop per Child Association Inc; Red Hat Inc; and Contributors."
+#~ msgstr ""
+#~ "© 2008 One Laptop per Child Association Inc; Red Hat Inc; y Contribuyentes."
+
+#~ msgid "Document"
+#~ msgstr "Documento"
+
+#~ msgid "Resume by default"
+#~ msgstr "Continuar de forma predeterminada"
+
+#~ msgid "Encryption Type:"
+#~ msgstr "Tipo de Encriptación:"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+# DjToXiC: It's a good point, however, I think you should communicate that bug to the developers, because we only have to translate. ;-) In my opinion, the translation is perfect (I'm Uruguaian).
+#~ msgid "Disconnecting..."
+#~ msgstr "Desconectando..."
+
+#~ msgid "Mesh Network"
+#~ msgstr "Red Malla"
+
+#~ msgid "Disconnected"
+#~ msgstr "Desconectado"
+
+#~ msgid "About my XO"
+#~ msgstr "Acerca de mi XO"
+
+#~ msgid "Connected to a School Mesh Portal"
+#~ msgstr "Conectado a un enlace escolar de red malla"
+
+# "portal malla de colegio", en Castellano de España suena fatal... ¿Realmente se quiere decir malla?
+#~ msgid "Looking for a School Mesh Portal..."
+#~ msgstr "Buscando un enlace escolar de red malla..."
+
+#~ msgid "Connected to an XO Mesh Portal"
+#~ msgstr "Conectado a un Portal Malla XO"
+
+#~ msgid "Looking for an XO Mesh Portal..."
+#~ msgstr "Buscando un Portal Malla XO..."
+
+#~ msgid "Connected to a Simple Mesh"
+#~ msgstr "Conectado a una Red Malla Simple"
+
+#~ msgid "Starting a Simple Mesh"
+#~ msgstr "Empezando una Red Malla Simple"
+
+#~ msgid "Unknown Mesh"
+#~ msgstr "Red Malla Desconocida"
+
+#~ msgid "Settings"
+#~ msgstr "Configuración "
+
+#, python-format
+#~ msgid "Clipboard object: %s."
+#~ msgstr "Objeto de portapapel: %s."
+
+#~ msgid "You must enter a server."
+#~ msgstr "Debe ingresar un servidor"
+
+#~ msgid "Control Panel"
+#~ msgstr "Panel de Control"
+
+#~ msgid "<Ctrl>R"
+#~ msgstr "<Ctrl>R"
+
+#, fuzzy
+#~ msgid "Ring view"
+#~ msgstr "Vista de llamada"
+
+#~ msgid "Remove from ring"
+#~ msgstr "Eliminar del anillo"
+
+#~ msgid "Add to ring"
+#~ msgstr "Agregar al anillo"
+
+#~ msgid "Changes require a sugar restart to take effect."
+#~ msgstr "Los cambios requieren reiniciar sugar para ser efectivos."
+
+#~ msgid "Changes require restart to take effect"
+#~ msgstr "Los cambios requieren reiniciar para ser efectivos"
+
+#~ msgid "Delay in milliseconds:"
+#~ msgstr "Retraso en milisegundos:"
+
+#~ msgid "Hot Corners"
+#~ msgstr "Esquinas Activas"
+
+#~ msgid "Warm Edges"
+#~ msgstr "Bordes Activos"
+
+#~ msgid "off"
+#~ msgstr "apagado"
+
+#~ msgid "on"
+#~ msgstr "encendido"
+
+#~ msgid "Permission denied. You need to be root to run this method."
+#~ msgstr "permiso denegado. Usted necesita ser root para ejecutar este método."
+
+#~ msgid "Error in reading timezone"
+#~ msgstr "Error en la lectura de la zona horaria"
+
+#, python-format
+#~ msgid "Error copying timezone (from %s): %s"
+#~ msgstr "Error copiando zona horaria (desde %s): %s"
+
+#, python-format
+#~ msgid "Changing permission of timezone: %s"
+#~ msgstr "Cambiando permisos de zona horaria: %s"
+
+#~ msgid "About this XO"
+#~ msgstr "Acerca de este XO"
+
+#~ msgid "Add to journal"
+#~ msgstr "Agregar al diario"
+
+#~ msgid "Reboot"
+#~ msgstr "Reiniciar"
+
+#~ msgid "My Battery life"
+#~ msgstr "Carga de mi batería"
+
+#~ msgid "Battery charging"
+#~ msgstr "Batería cargándose"
+
+#~ msgid "Battery discharging"
+#~ msgstr "Batería descargandose"
+
+#~ msgid "Battery fully charged"
+#~ msgstr "Batería totalmente cargada"
+
+#~ msgid "Invite"
+#~ msgstr "Invitar"
+
+#~ msgid "Text"
+#~ msgstr "Texto"
+
+#~ msgid "Image"
+#~ msgstr "Imagen"
+
+#~ msgid "Audio"
+#~ msgstr "Audio"
+
+#~ msgid "Video"
+#~ msgstr "Video"
+
+#~ msgid "Etoys project"
+#~ msgstr "Proyecto Etoys"
+
+#~ msgid "Link"
+#~ msgstr "Enlace"
+
+#~ msgid "Share with:"
+#~ msgstr "Compartir con:"
+
+#~ msgid "Private"
+#~ msgstr "Privado"
+
+#~ msgid "My Neighborhood"
+#~ msgstr "Mi Vecindario"
+
+#~ msgid "Undo"
+#~ msgstr "Deshacer"
+
+#~ msgid "Redo"
+#~ msgstr "Rehacer"
+
+#~ msgid "Paste"
+#~ msgstr "Pegar"
+
+#, python-format
+#~ msgid "%s Activity"
+#~ msgstr "Actividad %s"
+
+#, python-format
+#~ msgid "Text snippet"
+#~ "Web Page"
+#~ "PDF file"
+#~ "MS Word file"
+#~ "RTF file"
+#~ "Abiword file"
+#~ "Squeak project"
+#~ "OpenOffice text file"
+#~ "Object"
+#~ "Pick a buddy picture"
+#~ "My Picture:"
+#~ "My Color:"
+#~ "Stop download"
+#~ "Close"
+#~ "No options"
+#~ "Send"
+#~ msgstr "Recorte de texto"
+#~ "Página web"
+#~ "Archivo PDF"
+#~ "Archivo MS-Word"
+#~ "Archivo RTF"
+#~ "Archivo Abiword"
+#~ "Proyecto de Squeak"
+#~ "Archivo de texto de OpenOffice"
+#~ "Objeto"
+#~ "Elegir la imagen de amigo"
+#~ "Mi imagen:"
+#~ "Mi color:"
+#~ "Interrumpir la bajada"
+#~ "Cerrar"
+#~ "Ninguna opción"
+#~ "Enviar"
+
+#~ msgid "Keep error"
+#~ msgstr "Error de guardado"
+
+#~ msgid "Keep error: all changes will be lost"
+#~ msgstr "Error de guardado: todos los cambios se perderán"
+
+#~ msgid "Don't stop"
+#~ msgstr "No detener"
+
+#~ msgid "Stop anyway"
+#~ msgstr "Detener de todas formas"
+
+#~ msgid "Continue"
+#~ msgstr "Continuar"
+
+#~ msgid "OK"
+#~ msgstr "OK"
+
+#, python-format
+#~ msgid "%d year"
+#~ msgstr "%d año"
+
+#, python-format
+#~ msgid "%d years"
+#~ msgstr "%d años"
+
+#, python-format
+#~ msgid "%d month"
+#~ msgstr "%d mes"
+
+#, python-format
+#~ msgid "%d months"
+#~ msgstr "%d meses"
+
+#, python-format
+#~ msgid "%d week"
+#~ msgstr "%d semana"
+
+#, python-format
+#~ msgid "%d weeks"
+#~ msgstr "%d semanas"
+
+#, python-format
+#~ msgid "%d day"
+#~ msgstr "%d día"
+
+#, python-format
+#~ msgid "%d days"
+#~ msgstr "%d días"
+
+#, python-format
+#~ msgid "%d hour"
+#~ msgstr "%d hora"
+
+#, python-format
+#~ msgid "%d hours"
+#~ msgstr "%d horas"
+
+#, python-format
+#~ msgid "%d minute"
+#~ msgstr "%d minuto"
+
+#, python-format
+#~ msgid "%d minutes"
+#~ msgstr "%d minutos"
+
+#, python-format
+#~ msgid "%d second"
+#~ msgstr "%d segundo"
+
+#~ msgid " and "
+#~ msgstr " y "
+
+#~ msgid ", "
+#~ msgstr ", "
diff --git a/shell/po/fa.po b/shell/po/fa.po
new file mode 100644
index 0000000..08db87b
--- /dev/null
+++ b/shell/po/fa.po
@@ -0,0 +1,419 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-01-18 18:19+0000\n"
+"PO-Revision-Date: 2008-01-09 10:16+0000\n"
+"Last-Translator: Sohaib Obaidi <ebtihaj_obaidi@yahoo.com>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Pootle 1.0.2\n"
+
+#: ../shell/intro/intro.py:67
+msgid "Name:"
+msgstr "نام:"
+
+#: ../shell/intro/intro.py:96
+msgid "Click to change color:"
+msgstr "برای تبدیلی رنگ تیک کن:"
+
+#: ../shell/intro/intro.py:146
+msgid "Back"
+msgstr "عقب"
+
+#: ../shell/intro/intro.py:160
+msgid "Done"
+msgstr "شد"
+
+#: ../shell/intro/intro.py:163
+msgid "Next"
+msgstr "بعدی"
+
+#: ../shell/view/BuddyMenu.py:59
+msgid "Remove friend"
+msgstr "دوست را پاک کن"
+
+#: ../shell/view/BuddyMenu.py:62
+msgid "Make friend"
+msgstr "دوست بسازید"
+
+#: ../shell/view/BuddyMenu.py:84
+#, python-format
+msgid "Invite to %s"
+msgstr "دعوت به ( ) %s"
+
+#: ../shell/view/clipboardmenu.py:58
+msgid "Remove"
+msgstr "پاک کردن"
+
+#: ../shell/view/clipboardmenu.py:63
+msgid "Open"
+msgstr "باز کردن"
+
+#. self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+#. TODO: Implement stopping downloads
+#. self._stop_item.connect('activate', self._stop_item_activate_cb)
+#. self.append_menu_item(self._stop_item)
+#: ../shell/view/clipboardmenu.py:73
+msgid "Add to journal"
+msgstr "به یادداشت اضافه کردن"
+
+#: ../shell/view/clipboardmenu.py:213
+#, python-format
+msgid "Clipboard object: %s."
+msgstr "شی در حافظه : %s"
+
+#: ../shell/hardware/keydialog.py:149
+msgid "Key Type:"
+msgstr "نوعیت کلید:"
+
+#: ../shell/hardware/keydialog.py:169
+msgid "Authentication Type:"
+msgstr "نوعیت تصدیق:"
+
+#: ../shell/hardware/keydialog.py:250
+msgid "Encryption Type:"
+msgstr "نوعیت پنهانی کردن:"
+
+#: ../shell/view/home/activitiesdonut.py:90
+msgid "Starting..."
+msgstr "در حال شروع شدن...."
+
+#: ../shell/view/home/activitiesdonut.py:104 ../shell/view/home/MeshBox.py:295
+msgid "Resume"
+msgstr "از سر گرفتن"
+
+#: ../shell/view/home/activitiesdonut.py:111
+#: ../lib/sugar/activity/activity.py:132
+msgid "Stop"
+msgstr "توقف"
+
+#: ../shell/view/Shell.py:285
+msgid "Screenshot"
+msgstr "عکس صفحه"
+
+#: ../shell/view/home/HomeBox.py:159
+msgid "Reboot"
+msgstr "دوباره چالان کردن"
+
+#: ../shell/view/home/HomeBox.py:164
+msgid "Shutdown"
+msgstr "بند کردن"
+
+#: ../shell/view/home/HomeBox.py:170
+msgid "Register"
+msgstr "راجستر و ثبت کردن"
+
+#. Only show disconnect when there's a mesh device, because mesh takes
+#. priority over the normal wireless device. NM doesn't have a "disconnect"
+#. method for a device either (for various reasons) so this doesn't
+#. have a good mapping
+#: ../shell/view/home/MeshBox.py:90 ../shell/view/home/MeshBox.py:197
+#: ../shell/view/devices/network/wireless.py:113
+#: ../shell/view/devices/network/mesh.py:83
+msgid "Disconnect..."
+msgstr "منقطع شدن"
+
+#: ../shell/view/home/MeshBox.py:195 ../shell/view/devices/network/mesh.py:37
+#: ../shell/view/devices/network/mesh.py:62
+#: ../shell/view/devices/network/mesh.py:66
+msgid "Mesh Network"
+msgstr "شبکه بافته شده"
+
+#: ../shell/view/home/MeshBox.py:300
+msgid "Join"
+msgstr "پیوستن"
+
+#: ../shell/view/devices/battery.py:38
+msgid "My Battery life"
+msgstr "دوام باطری من"
+
+#: ../shell/view/devices/battery.py:94
+msgid "Battery charging"
+msgstr "باطری پر میشود"
+
+#: ../shell/view/devices/battery.py:96
+msgid "Battery discharging"
+msgstr "باطری خالی میشود"
+
+#: ../shell/view/devices/battery.py:98
+msgid "Battery fully charged"
+msgstr "باطری کاملا پر ش"
+
+#: ../shell/view/devices/network/wireless.py:61
+msgid "Disconnected"
+msgstr "منقطع شد"
+
+#: ../shell/view/devices/network/wireless.py:131
+msgid "Channel"
+msgstr "کانال"
+
+#: ../shell/view/frame/zoomtoolbar.py:42
+msgid "Neighborhood"
+msgstr "همسایگی"
+
+#: ../shell/view/frame/zoomtoolbar.py:54
+msgid "Group"
+msgstr "گروه"
+
+#: ../shell/view/frame/zoomtoolbar.py:66
+msgid "Home"
+msgstr "خانه"
+
+#: ../shell/view/frame/zoomtoolbar.py:78
+msgid "Activity"
+msgstr " فعالیت"
+
+#: ../lib/sugar/activity/activity.py:115
+msgid "Share with:"
+msgstr "تقسیم با:"
+
+#: ../lib/sugar/activity/activity.py:117
+msgid "Private"
+msgstr "شخصی"
+
+#: ../lib/sugar/activity/activity.py:118
+msgid "My Neighborhood"
+msgstr "همسایگی من"
+
+#: ../lib/sugar/activity/activity.py:126
+msgid "Keep"
+msgstr "نگه داشتن"
+
+#: ../lib/sugar/activity/activity.py:245
+msgid "Undo"
+msgstr "نکن"
+
+#: ../lib/sugar/activity/activity.py:250
+msgid "Redo"
+msgstr "تکرار کن"
+
+#: ../lib/sugar/activity/activity.py:260
+msgid "Copy"
+msgstr "نقل بگیر"
+
+#: ../lib/sugar/activity/activity.py:265
+msgid "Paste"
+msgstr "بچسپان"
+
+#: ../lib/sugar/activity/activity.py:454
+#, python-format
+msgid "%s Activity"
+msgstr "%s فعالیت"
+
+#: ../lib/sugar/activity/activity.py:824
+msgid "Keep error"
+msgstr "نگه داشتن اشتباه"
+
+#: ../lib/sugar/activity/activity.py:825
+msgid "Keep error: all changes will be lost"
+msgstr "نگه داشتن اشتباه: همه تغیرات از بین میروند"
+
+#: ../lib/sugar/activity/activity.py:828
+msgid "Don't stop"
+msgstr "توقف نکن"
+
+#: ../lib/sugar/activity/activity.py:831
+msgid "Stop anyway"
+msgstr "به هر طوری توقف کن"
+
+#: ../lib/sugar/graphics/alert.py:164 ../lib/sugar/graphics/alert.py:206
+msgid "Cancel"
+msgstr "لغو کن"
+
+#: ../lib/sugar/graphics/alert.py:168
+msgid "Ok"
+msgstr "درست است."
+
+#: ../lib/sugar/graphics/alert.py:216
+msgid "Continue"
+msgstr "جاری"
+
+#: ../lib/sugar/graphics/alert.py:244
+msgid "OK"
+msgstr "درست است."
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d year"
+msgstr "%d سال"
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d years"
+msgstr "%d سال ها"
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d month"
+msgstr "%d ماه"
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d months"
+msgstr "%d ماه ها"
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d week"
+msgstr "%d هفته"
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d weeks"
+msgstr "%d هفته ها"
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d day"
+msgstr "%d روز"
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d days"
+msgstr "%d روزها"
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hour"
+msgstr "%d ساعت"
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hours"
+msgstr "%d ساعت ها"
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minute"
+msgstr "%d دقیقه"
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minutes"
+msgstr "%d دقایق"
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d second"
+msgstr "%d ثانیه"
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d seconds"
+msgstr "%d ثانیه ها"
+
+#: ../lib/sugar/graphics/objectchooser.py:191
+msgid " and "
+msgstr "_ و _"
+
+#: ../lib/sugar/graphics/objectchooser.py:193
+msgid ", "
+msgstr "_ یا _"
+
+#: ../shell/controlpanel/control.py:213
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr "برای اجرا نمودن تغیرات تان شما باید شوگر را دوباره چالان کنید\n"
+
+#: ../shell/controlpanel/control.py:267
+msgid "Error in specified color modifiers."
+msgstr "اشتباه در تعدیل کننده رنگ معین"
+
+#: ../shell/controlpanel/control.py:270
+msgid "Error in specified colors."
+msgstr "اشتباه د رنگ معین"
+
+#: ../shell/controlpanel/control.py:307
+msgid "off"
+msgstr "خاموش کردن"
+
+#: ../shell/controlpanel/control.py:309
+msgid "on"
+msgstr "روشن کردن"
+
+#: ../shell/controlpanel/control.py:310
+msgid "State is unknown."
+msgstr "حالان نامشخص هست"
+
+#: ../shell/controlpanel/control.py:332
+msgid "Error in specified radio argument use on/off."
+msgstr "اشتباه دراستدلال معین از خاموش/روشن نمودن رادیو "
+
+#: ../shell/controlpanel/control.py:336
+msgid "Permission denied. You need to be root to run this method."
+msgstr "تردید اجازه. شما برای راندن این روش باید ریشه کار باشید"
+
+#: ../shell/controlpanel/control.py:366
+msgid "Error in reading timezone"
+msgstr "اشتباه در خواندن منطقه جغرافیایی ساعات"
+
+#: ../shell/controlpanel/control.py:397
+#, python-format
+msgid "Error copying timezone (from %s): %s"
+msgstr "اشتباه در نقل برداری منطقه جغرافیایی ساعات (از %s): %s"
+
+#: ../shell/controlpanel/control.py:402
+#, python-format
+msgid "Changing permission of timezone: %s"
+msgstr "تبدیلی اجازت منطقه جغرافیایی ساعات: %s"
+
+#: ../shell/controlpanel/control.py:412
+msgid "Error timezone does not exist."
+msgstr "اشتباه منطقه جغرافیایی ساعات وجود ندارد"
+
+#: ../shell/controlpanel/control.py:417 ../shell/controlpanel/control.py:436
+#, python-format
+msgid "Could not access %s. Create standard settings."
+msgstr "دسترسی نمیتواند %s. زمینه معیاری بسازید"
+
+#: ../shell/controlpanel/control.py:463
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr "زبان برای رمز=%s تعین نمی گردد"
+
+#: ../shell/controlpanel/control.py:473
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr "ببخشید من سخن گفته نمیتوانم \"%s\""
+
+#: ../shell/view/devices/network/mesh.py:105
+msgid "Connected to a School Mesh Portal"
+msgstr "با مدخل تنیده مکتب وصل گردید"
+
+#: ../shell/view/devices/network/mesh.py:107
+msgid "Looking for a School Mesh Portal..."
+msgstr "در حال تلاش برای یک مدخل تنیده مکتب"
+
+#: ../shell/view/devices/network/mesh.py:110
+msgid "Connected to an XO Mesh Portal"
+msgstr "با مدخل تنیده XO وصل گردید"
+
+#: ../shell/view/devices/network/mesh.py:112
+msgid "Looking for an XO Mesh Portal..."
+msgstr "در حال تلاش برای یک مدخل تنیده XO"
+
+#: ../shell/view/devices/network/mesh.py:115
+msgid "Connected to a Simple Mesh"
+msgstr "با یک بافته ساده وصل گردید"
+
+#: ../shell/view/devices/network/mesh.py:117
+msgid "Starting a Simple Mesh"
+msgstr "در حال شروع یک بافته ساده"
+
+#: ../shell/view/devices/network/mesh.py:124
+msgid "Unknown Mesh"
+msgstr "بافته نا مشخص"
+
+#: ../shell/view/home/HomeBox.py:175 ../shell/view/home/HomeBox.py:216
+msgid "About this XO"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:222
+msgid "Not available"
+msgstr ""
diff --git a/shell/po/fa_AF.po b/shell/po/fa_AF.po
new file mode 100644
index 0000000..f285c64
--- /dev/null
+++ b/shell/po/fa_AF.po
@@ -0,0 +1,419 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-01-18 18:19+0000\n"
+"PO-Revision-Date: 2008-02-06 06:09-0500\n"
+"Last-Translator: Sohaib Obaidi <ebtihaj_obaidi@yahoo.com>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Pootle 1.0.2\n"
+
+#: ../shell/intro/intro.py:67
+msgid "Name:"
+msgstr "نام:"
+
+#: ../shell/intro/intro.py:96
+msgid "Click to change color:"
+msgstr "برای تبدیلی رنگ تیک کن:"
+
+#: ../shell/intro/intro.py:146
+msgid "Back"
+msgstr "عقب"
+
+#: ../shell/intro/intro.py:160
+msgid "Done"
+msgstr "شد"
+
+#: ../shell/intro/intro.py:163
+msgid "Next"
+msgstr "بعدی"
+
+#: ../shell/view/BuddyMenu.py:59
+msgid "Remove friend"
+msgstr "دوست را حذف کن"
+
+#: ../shell/view/BuddyMenu.py:62
+msgid "Make friend"
+msgstr "دوست بسازید"
+
+#: ../shell/view/BuddyMenu.py:84
+#, python-format
+msgid "Invite to %s"
+msgstr "دعوت به %s"
+
+#: ../shell/view/clipboardmenu.py:58
+msgid "Remove"
+msgstr "حذف کن"
+
+#: ../shell/view/clipboardmenu.py:63
+msgid "Open"
+msgstr "باز کن"
+
+#. self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+#. TODO: Implement stopping downloads
+#. self._stop_item.connect('activate', self._stop_item_activate_cb)
+#. self.append_menu_item(self._stop_item)
+#: ../shell/view/clipboardmenu.py:73
+msgid "Add to journal"
+msgstr "به یادداشت اضافه کن"
+
+#: ../shell/view/clipboardmenu.py:213
+#, python-format
+msgid "Clipboard object: %s."
+msgstr "شیی تخته رسم : %s."
+
+#: ../shell/hardware/keydialog.py:149
+msgid "Key Type:"
+msgstr "نوعیت کلید:"
+
+#: ../shell/hardware/keydialog.py:169
+msgid "Authentication Type:"
+msgstr "نوعیت تصدیق:"
+
+#: ../shell/hardware/keydialog.py:250
+msgid "Encryption Type:"
+msgstr "نوعیت پنهانی کردن:"
+
+#: ../shell/view/home/activitiesdonut.py:90
+msgid "Starting..."
+msgstr "در حال شروع شدن..."
+
+#: ../shell/view/home/activitiesdonut.py:104 ../shell/view/home/MeshBox.py:295
+msgid "Resume"
+msgstr "ادامه"
+
+#: ../shell/view/home/activitiesdonut.py:111
+#: ../lib/sugar/activity/activity.py:132
+msgid "Stop"
+msgstr "توقف"
+
+#: ../shell/view/Shell.py:285
+msgid "Screenshot"
+msgstr "عکس صفحه"
+
+#: ../shell/view/home/HomeBox.py:159
+msgid "Reboot"
+msgstr "راه اندازي مجدد"
+
+#: ../shell/view/home/HomeBox.py:164
+msgid "Shutdown"
+msgstr "خاموش کردن"
+
+#: ../shell/view/home/HomeBox.py:170
+msgid "Register"
+msgstr "ثبت کردن"
+
+#. Only show disconnect when there's a mesh device, because mesh takes
+#. priority over the normal wireless device. NM doesn't have a "disconnect"
+#. method for a device either (for various reasons) so this doesn't
+#. have a good mapping
+#: ../shell/view/home/MeshBox.py:90 ../shell/view/home/MeshBox.py:197
+#: ../shell/view/devices/network/wireless.py:113
+#: ../shell/view/devices/network/mesh.py:83
+msgid "Disconnect..."
+msgstr "منقطع شدن..."
+
+#: ../shell/view/home/MeshBox.py:195 ../shell/view/devices/network/mesh.py:37
+#: ../shell/view/devices/network/mesh.py:62
+#: ../shell/view/devices/network/mesh.py:66
+msgid "Mesh Network"
+msgstr "شبکه تنیده"
+
+#: ../shell/view/home/MeshBox.py:300
+msgid "Join"
+msgstr "پیوستن"
+
+#: ../shell/view/devices/battery.py:38
+msgid "My Battery life"
+msgstr "حیات باطری من"
+
+#: ../shell/view/devices/battery.py:94
+msgid "Battery charging"
+msgstr "باطری در حال پرشدن"
+
+#: ../shell/view/devices/battery.py:96
+msgid "Battery discharging"
+msgstr "باطری در حال تخلیه"
+
+#: ../shell/view/devices/battery.py:98
+msgid "Battery fully charged"
+msgstr "باطری کاملا پر شد"
+
+#: ../shell/view/devices/network/wireless.py:61
+msgid "Disconnected"
+msgstr "منقطع شد"
+
+#: ../shell/view/devices/network/wireless.py:131
+msgid "Channel"
+msgstr "کانال"
+
+#: ../shell/view/frame/zoomtoolbar.py:42
+msgid "Neighborhood"
+msgstr "همسایگی"
+
+#: ../shell/view/frame/zoomtoolbar.py:54
+msgid "Group"
+msgstr "گروه"
+
+#: ../shell/view/frame/zoomtoolbar.py:66
+msgid "Home"
+msgstr "خانه"
+
+#: ../shell/view/frame/zoomtoolbar.py:78
+msgid "Activity"
+msgstr "فعالیت"
+
+#: ../lib/sugar/activity/activity.py:115
+msgid "Share with:"
+msgstr "تقسیم با:"
+
+#: ../lib/sugar/activity/activity.py:117
+msgid "Private"
+msgstr "شخصی"
+
+#: ../lib/sugar/activity/activity.py:118
+msgid "My Neighborhood"
+msgstr "همسایگی من"
+
+#: ../lib/sugar/activity/activity.py:126
+msgid "Keep"
+msgstr "نگه داشتن"
+
+#: ../lib/sugar/activity/activity.py:245
+msgid "Undo"
+msgstr "نکن"
+
+#: ../lib/sugar/activity/activity.py:250
+msgid "Redo"
+msgstr "تکرار کن"
+
+#: ../lib/sugar/activity/activity.py:260
+msgid "Copy"
+msgstr "نقل بگیر"
+
+#: ../lib/sugar/activity/activity.py:265
+msgid "Paste"
+msgstr "بچسپان"
+
+#: ../lib/sugar/activity/activity.py:454
+#, python-format
+msgid "%s Activity"
+msgstr "فعالیت %s"
+
+#: ../lib/sugar/activity/activity.py:824
+msgid "Keep error"
+msgstr "خطا را نگه دار"
+
+#: ../lib/sugar/activity/activity.py:825
+msgid "Keep error: all changes will be lost"
+msgstr "خطا را نگه دار: همه تغیرات از بین خواهند رفت"
+
+#: ../lib/sugar/activity/activity.py:828
+msgid "Don't stop"
+msgstr "توقف نکن"
+
+#: ../lib/sugar/activity/activity.py:831
+msgid "Stop anyway"
+msgstr "به هر صورت توقف کن"
+
+#: ../lib/sugar/graphics/alert.py:164 ../lib/sugar/graphics/alert.py:206
+msgid "Cancel"
+msgstr "لغو کن"
+
+#: ../lib/sugar/graphics/alert.py:168
+msgid "Ok"
+msgstr "درست است"
+
+#: ../lib/sugar/graphics/alert.py:216
+msgid "Continue"
+msgstr "جاری"
+
+#: ../lib/sugar/graphics/alert.py:244
+msgid "OK"
+msgstr "OK"
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d year"
+msgstr "%d سال"
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d years"
+msgstr "%d سال"
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d month"
+msgstr "%d ماه"
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d months"
+msgstr "%d ماه"
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d week"
+msgstr "%d هفته"
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d weeks"
+msgstr "%d هفته ها"
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d day"
+msgstr "%d روز"
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d days"
+msgstr "%d روزها"
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hour"
+msgstr "%d ساعت"
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hours"
+msgstr "%d ساعت ها"
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minute"
+msgstr "%d دقیقه"
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minutes"
+msgstr "%d دقیقه ها"
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d second"
+msgstr "%d ثانیه"
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d seconds"
+msgstr "%d ثانیه ها"
+
+#: ../lib/sugar/graphics/objectchooser.py:191
+msgid " and "
+msgstr " و "
+
+#: ../lib/sugar/graphics/objectchooser.py:193
+msgid ", "
+msgstr "، "
+
+#: ../shell/controlpanel/control.py:213
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr "برای اجرا نمودن تغیرات تان شما باید شکر را دوباره آغاز کنید.\n"
+
+#: ../shell/controlpanel/control.py:267
+msgid "Error in specified color modifiers."
+msgstr "خطا در تعدیل کننده رنگ معین."
+
+#: ../shell/controlpanel/control.py:270
+msgid "Error in specified colors."
+msgstr "خطا در رنگ معین."
+
+#: ../shell/controlpanel/control.py:307
+msgid "off"
+msgstr "خاموش"
+
+#: ../shell/controlpanel/control.py:309
+msgid "on"
+msgstr "روشن"
+
+#: ../shell/controlpanel/control.py:310
+msgid "State is unknown."
+msgstr "وضعیت نامشخص است."
+
+#: ../shell/controlpanel/control.py:332
+msgid "Error in specified radio argument use on/off."
+msgstr "روشن یا خاموش بودن خطا در استدلال مشخص استعمال رادیویی."
+
+#: ../shell/controlpanel/control.py:336
+msgid "Permission denied. You need to be root to run this method."
+msgstr "اجازه غير قابل دسترس هست. شما برای اجرای این طریقه باید ریشه باشید."
+
+#: ../shell/controlpanel/control.py:366
+msgid "Error in reading timezone"
+msgstr "خطا در خوانش منطقه زمانی"
+
+#: ../shell/controlpanel/control.py:397
+#, python-format
+msgid "Error copying timezone (from %s): %s"
+msgstr "خطا در خوانش منطقه زمانی (از %s): %s"
+
+#: ../shell/controlpanel/control.py:402
+#, python-format
+msgid "Changing permission of timezone: %s"
+msgstr "در حال تغییر دادن اجازت منطقه زمانی: %s"
+
+#: ../shell/controlpanel/control.py:412
+msgid "Error timezone does not exist."
+msgstr "خطای منطقه زمانی وجود ندارد."
+
+#: ../shell/controlpanel/control.py:417 ../shell/controlpanel/control.py:436
+#, python-format
+msgid "Could not access %s. Create standard settings."
+msgstr "به %s دسترسی نمی تواند. زمینه معیاریی بسازید."
+
+#: ../shell/controlpanel/control.py:463
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr "زبان برای رمز=%s تعیین کرده نمیشود."
+
+#: ../shell/controlpanel/control.py:473
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr "متأسفم من '%s' گفته نمیتوانم."
+
+#: ../shell/view/devices/network/mesh.py:105
+msgid "Connected to a School Mesh Portal"
+msgstr "با مدخل تنیده مکتب وصل گردید"
+
+#: ../shell/view/devices/network/mesh.py:107
+msgid "Looking for a School Mesh Portal..."
+msgstr "در حال تلاش برای یک مدخل تنیده مکتب..."
+
+#: ../shell/view/devices/network/mesh.py:110
+msgid "Connected to an XO Mesh Portal"
+msgstr "با مدخل تنیده XO وصل گردید"
+
+#: ../shell/view/devices/network/mesh.py:112
+msgid "Looking for an XO Mesh Portal..."
+msgstr "در حال تلاش برای یک مدخل تنیده XO..."
+
+#: ../shell/view/devices/network/mesh.py:115
+msgid "Connected to a Simple Mesh"
+msgstr "با یک شبکه تنیده ساده وصل گردید"
+
+#: ../shell/view/devices/network/mesh.py:117
+msgid "Starting a Simple Mesh"
+msgstr "در حال شروع کردن یک شبکه تنیده ساده"
+
+#: ../shell/view/devices/network/mesh.py:124
+msgid "Unknown Mesh"
+msgstr "شبکه تنیده نامشخص"
+
+#: ../shell/view/home/HomeBox.py:175 ../shell/view/home/HomeBox.py:216
+msgid "About this XO"
+msgstr "در مورد این XO"
+
+#: ../shell/view/home/HomeBox.py:222
+msgid "Not available"
+msgstr "در دسترس نیست"
diff --git a/shell/po/ff.po b/shell/po/ff.po
new file mode 100644
index 0000000..a79e711
--- /dev/null
+++ b/shell/po/ff.po
@@ -0,0 +1,420 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-01-18 18:19+0000\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.0.1\n"
+
+#: ../shell/intro/intro.py:67
+msgid "Name:"
+msgstr ""
+
+#: ../shell/intro/intro.py:96
+msgid "Click to change color:"
+msgstr ""
+
+#: ../shell/intro/intro.py:146
+msgid "Back"
+msgstr ""
+
+#: ../shell/intro/intro.py:160
+msgid "Done"
+msgstr ""
+
+#: ../shell/intro/intro.py:163
+msgid "Next"
+msgstr ""
+
+#: ../shell/view/BuddyMenu.py:59
+msgid "Remove friend"
+msgstr ""
+
+#: ../shell/view/BuddyMenu.py:62
+msgid "Make friend"
+msgstr ""
+
+#: ../shell/view/BuddyMenu.py:84
+#, python-format
+msgid "Invite to %s"
+msgstr ""
+
+#: ../shell/view/clipboardmenu.py:58
+msgid "Remove"
+msgstr ""
+
+#: ../shell/view/clipboardmenu.py:63
+msgid "Open"
+msgstr ""
+
+#. self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+#. TODO: Implement stopping downloads
+#. self._stop_item.connect('activate', self._stop_item_activate_cb)
+#. self.append_menu_item(self._stop_item)
+#: ../shell/view/clipboardmenu.py:73
+msgid "Add to journal"
+msgstr ""
+
+#: ../shell/view/clipboardmenu.py:213
+#, python-format
+msgid "Clipboard object: %s."
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:149
+msgid "Key Type:"
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:169
+msgid "Authentication Type:"
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:250
+msgid "Encryption Type:"
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:90
+msgid "Starting..."
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:104 ../shell/view/home/MeshBox.py:295
+msgid "Resume"
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:111
+#: ../lib/sugar/activity/activity.py:132
+msgid "Stop"
+msgstr ""
+
+#: ../shell/view/Shell.py:285
+msgid "Screenshot"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:159
+msgid "Reboot"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:164
+msgid "Shutdown"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:170
+msgid "Register"
+msgstr ""
+
+#. Only show disconnect when there's a mesh device, because mesh takes
+#. priority over the normal wireless device. NM doesn't have a "disconnect"
+#. method for a device either (for various reasons) so this doesn't
+#. have a good mapping
+#: ../shell/view/home/MeshBox.py:90 ../shell/view/home/MeshBox.py:197
+#: ../shell/view/devices/network/wireless.py:113
+#: ../shell/view/devices/network/mesh.py:83
+msgid "Disconnect..."
+msgstr ""
+
+#: ../shell/view/home/MeshBox.py:195 ../shell/view/devices/network/mesh.py:37
+#: ../shell/view/devices/network/mesh.py:62
+#: ../shell/view/devices/network/mesh.py:66
+msgid "Mesh Network"
+msgstr ""
+
+#: ../shell/view/home/MeshBox.py:300
+msgid "Join"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:38
+msgid "My Battery life"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:94
+msgid "Battery charging"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:96
+msgid "Battery discharging"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:98
+msgid "Battery fully charged"
+msgstr ""
+
+#: ../shell/view/devices/network/wireless.py:61
+msgid "Disconnected"
+msgstr ""
+
+#: ../shell/view/devices/network/wireless.py:131
+msgid "Channel"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:42
+msgid "Neighborhood"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:54
+msgid "Group"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:66
+msgid "Home"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:78
+msgid "Activity"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:115
+msgid "Share with:"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:117
+msgid "Private"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:118
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:126
+msgid "Keep"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:245
+msgid "Undo"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:250
+msgid "Redo"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:260
+msgid "Copy"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:265
+msgid "Paste"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:454
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:824
+msgid "Keep error"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:825
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:828
+msgid "Don't stop"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:831
+msgid "Stop anyway"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:164 ../lib/sugar/graphics/alert.py:206
+msgid "Cancel"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:168
+msgid "Ok"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:216
+msgid "Continue"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:244
+msgid "OK"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d year"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d years"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d month"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d months"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d week"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d weeks"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d day"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d days"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hour"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hours"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minute"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minutes"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d second"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d seconds"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:191
+msgid " and "
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:193
+msgid ", "
+msgstr ""
+
+#: ../shell/controlpanel/control.py:213
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:267
+msgid "Error in specified color modifiers."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:270
+msgid "Error in specified colors."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:307
+msgid "off"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:309
+msgid "on"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:310
+msgid "State is unknown."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:332
+msgid "Error in specified radio argument use on/off."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:336
+msgid "Permission denied. You need to be root to run this method."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:366
+msgid "Error in reading timezone"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:397
+#, python-format
+msgid "Error copying timezone (from %s): %s"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:402
+#, python-format
+msgid "Changing permission of timezone: %s"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:412
+msgid "Error timezone does not exist."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:417 ../shell/controlpanel/control.py:436
+#, python-format
+msgid "Could not access %s. Create standard settings."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:463
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:473
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:105
+msgid "Connected to a School Mesh Portal"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:107
+msgid "Looking for a School Mesh Portal..."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:110
+msgid "Connected to an XO Mesh Portal"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:112
+msgid "Looking for an XO Mesh Portal..."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:115
+msgid "Connected to a Simple Mesh"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:117
+msgid "Starting a Simple Mesh"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:124
+msgid "Unknown Mesh"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:175 ../shell/view/home/HomeBox.py:216
+msgid "About this XO"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:222
+msgid "Not available"
+msgstr ""
diff --git a/shell/po/fil.po b/shell/po/fil.po
new file mode 100644
index 0000000..93c6b6e
--- /dev/null
+++ b/shell/po/fil.po
@@ -0,0 +1,958 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-01-31 00:30-0500\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../extensions/cpsection/aboutme/__init__.py:24
+msgid "About Me"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:43
+msgid "You must enter a name."
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:68
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:71
+#, python-format
+msgid "stroke: %s"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:73
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:75
+#, python-format
+msgid "fill: %s"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:86
+msgid "Error in specified color modifiers."
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:89
+msgid "Error in specified colors."
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/view.py:94 ../src/jarabe/intro/window.py:92
+msgid "Name:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/view.py:128
+msgid "Click to change your color:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/__init__.py:21
+msgid "About my Computer"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/model.py:26
+msgid "Not available"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:59
+msgid "Identity"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:68
+msgid "Serial Number:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:90
+msgid "Software"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:99
+msgid "Build:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:114
+msgid "Sugar:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:130
+msgid "Firmware:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:145
+msgid "Wireless Firmware:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:168
+msgid "Copyright and License"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:176
+msgid ""
+"© 2008 One Laptop per Child Association Inc; Red Hat Inc; and Contributors."
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:183
+msgid ""
+"Sugar is the graphical user interface that you are looking at. Sugar is free "
+"software, covered by the GNU General Public License, and you are welcome to "
+"change it and/or distribute copies of it under certain conditions described "
+"therein."
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:195
+msgid "Full license:"
+msgstr ""
+
+#: ../extensions/cpsection/datetime/__init__.py:21
+msgid "Date & Time"
+msgstr ""
+
+#: ../extensions/cpsection/datetime/model.py:87
+msgid "Error timezone does not exist."
+msgstr ""
+
+#: ../extensions/cpsection/datetime/view.py:68 ../data/sugar.schemas.in.h:19
+msgid "Timezone"
+msgstr ""
+
+#: ../extensions/cpsection/frame/__init__.py:21
+msgid "Frame"
+msgstr ""
+
+#: ../extensions/cpsection/frame/model.py:38
+#: ../extensions/cpsection/frame/model.py:60
+msgid "Value must be an integer."
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:26
+msgid "never"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:27
+msgid "instantaneous"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:28
+#, python-format
+msgid "%s seconds"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:52
+msgid "Activation Delay"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:76
+msgid "Corner"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:111
+msgid "Edge"
+msgstr ""
+
+#: ../extensions/cpsection/language/__init__.py:21
+#: ../extensions/cpsection/language/view.py:32
+msgid "Language"
+msgstr ""
+
+#: ../extensions/cpsection/language/model.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr ""
+
+#: ../extensions/cpsection/language/model.py:124
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr ""
+
+#: ../extensions/cpsection/language/model.py:144
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr ""
+
+#: ../extensions/cpsection/network/__init__.py:21
+#: ../extensions/cpsection/network/view.py:28
+msgid "Network"
+msgstr ""
+
+#: ../extensions/cpsection/network/model.py:79
+msgid "State is unknown."
+msgstr ""
+
+#: ../extensions/cpsection/network/model.py:105
+msgid "Error in specified radio argument use on/off."
+msgstr ""
+
+#: ../extensions/cpsection/network/model.py:137
+msgid "Error in specified argument use 0/1."
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:56
+msgid "Wireless"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:64
+msgid "Turn off the wireless radio to save battery life"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:77
+msgid "Radio"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:93
+msgid "Discard network history if you have trouble connecting to the network"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:102
+msgid "Discard network history"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:115
+msgid "Collaboration"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:123
+msgid ""
+"The server is the equivalent of what room you are in; people on the same "
+"server will be able to see each other, even when they aren't on the same "
+"network."
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:133
+msgid "Server:"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:56
+msgid "My Battery"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:137
+msgid "Removed"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:140
+msgid "Charging"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:143
+msgid "Very little power remaining"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:149
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:153
+msgid "Charged"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:40
+#, python-format
+msgid "IP address: %s"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:104
+msgid "Disconnect..."
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:109
+#: ../src/jarabe/desktop/meshbox.py:246
+msgid "Connecting..."
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:113
+#: ../extensions/deviceicon/network.py:166
+#: ../src/jarabe/desktop/meshbox.py:252
+msgid "Connected"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:126
+msgid "Channel"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:141
+msgid "Wired Network"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:169
+msgid "Speed"
+msgstr ""
+
+#: ../extensions/deviceicon/speaker.py:46
+msgid "My Speakers"
+msgstr ""
+
+#: ../extensions/deviceicon/speaker.py:128
+msgid "Unmute"
+msgstr ""
+
+#: ../extensions/deviceicon/speaker.py:131
+msgid "Mute"
+msgstr ""
+
+#: ../extensions/globalkey/screenshot.py:50
+msgid "Screenshot"
+msgstr ""
+
+#: ../extensions/globalkey/viewsource.py:196
+#, python-format
+msgid "View source: %r"
+msgstr ""
+
+#: ../extensions/globalkey/viewsource.py:205
+#: ../src/jarabe/frame/zoomtoolbar.py:42
+msgid "Activity"
+msgstr ""
+
+#: ../extensions/globalkey/viewsource.py:212
+msgid "Document"
+msgstr ""
+
+#: ../extensions/globalkey/viewsource.py:226
+#: ../src/jarabe/journal/objectchooser.py:141
+msgid "Close"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:1
+msgid "Backup URL"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:2
+msgid ""
+"Color for the XO icon that is used throughout the desktop. The string is "
+"composed of the stroke color and fill color, format is that of rbg colors. "
+"Example: #AC32FF,#9A5200"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:3
+msgid "Corner Delay"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:4
+msgid "Delay for the activation of the frame using the corners."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:5
+msgid "Delay for the activation of the frame using the edges."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:6
+msgid "Edge Delay"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:7
+msgid "Favorites Layout"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:8
+msgid "Favorites resume mode"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:9
+msgid ""
+"If TRUE, Sugar will make us searchable for the other users of the Jabber "
+"server."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:10
+msgid "Jabber Server"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:11
+msgid "Layout of the favorites view."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:12
+msgid "Power Automatic"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:13
+msgid "Power Automatic."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:14
+msgid "Power Extreme"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:15
+msgid "Power Extreme."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:16
+msgid "Publish to Gadget"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:17
+msgid "Setting for muting the sound device."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:18
+msgid "Sound Muted"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:20
+msgid "Timezone setting for the system."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:21
+msgid "Url of the jabber server to use."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:22
+msgid "Url where the backup is saved to."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:23
+msgid "User Color"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:24
+msgid "User Name"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:25
+msgid "User name that is used throughout the desktop."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:26
+msgid "Volume Level"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:27
+msgid "Volume level for the sound device."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:28
+msgid ""
+"When in resume mode, clicking on a favorite icon will cause the last entry "
+"for that activity to be resumed."
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:28
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:30
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:31
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr ""
+
+#. TRANS: Translators, there's a empty line at the end of this string,
+#. which must appear in the translated string (msgstr) as well.
+#: ../src/jarabe/controlpanel/cmd.py:37
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" -c key clear the current value for the key \n"
+" "
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:50
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:272
+msgid "Warning"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:273
+#: ../src/jarabe/controlpanel/sectionview.py:42
+msgid "Changes require restart"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:276
+msgid "Cancel changes"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:281 ../src/jarabe/desktop/homebox.py:113
+msgid "Later"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:285
+msgid "Restart now"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/toolbar.py:61 ../src/jarabe/intro/window.py:188
+msgid "Done"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/toolbar.py:115
+#: ../src/jarabe/desktop/homebox.py:111
+#: ../src/jarabe/frame/activitiestray.py:683
+#: ../src/jarabe/frame/activitiestray.py:762
+#: ../src/jarabe/frame/activitiestray.py:790
+msgid "Cancel"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/toolbar.py:121
+#: ../src/jarabe/desktop/favoritesview.py:334
+msgid "Ok"
+msgstr ""
+
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:114
+msgid "Freeform"
+msgstr ""
+
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:196
+msgid "Ring"
+msgstr ""
+
+#. TRANS: label for the spiral layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:341
+msgid "Spiral"
+msgstr ""
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:408
+msgid "Box"
+msgstr ""
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:449
+msgid "Triangle"
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:325
+msgid "Registration Failed"
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:326
+#, python-format
+msgid "%s"
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:328
+msgid "Registration Successful"
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:329
+msgid "You are now registered with your school server."
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:666
+msgid "Register"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:67
+msgid "Confirm erase"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:69
+#, python-format
+msgid "Confirm erase: Do you want to permanently erase %s?"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:73 ../src/jarabe/frame/clipboardmenu.py:62
+msgid "Keep"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:76
+#: ../src/jarabe/journal/journaltoolbox.py:357
+#: ../src/jarabe/journal/palettes.py:112 ../src/jarabe/view/palettes.py:127
+msgid "Erase"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:106
+msgid "Software Update"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:107
+msgid "Update your activities to ensure compatibility with your new software"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:116
+msgid "Check now"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:233
+msgid "List view"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:234
+msgid "<Ctrl>2"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:296
+msgid "Favorites view"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:297
+msgid "<Ctrl>1"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:304
+msgid "Resume by default"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:131
+msgid "Key Type:"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:151
+msgid "Authentication Type:"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:215
+msgid "WPA & WPA2 Personal"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:224
+msgid "Wireless Security:"
+msgstr ""
+
+#: ../src/jarabe/desktop/meshbox.py:130
+msgid "Connect"
+msgstr ""
+
+#: ../src/jarabe/desktop/meshbox.py:134
+msgid "Disconnect"
+msgstr ""
+
+#. TRANS: Action label for resuming an activity.
+#: ../src/jarabe/desktop/meshbox.py:440
+#: ../src/jarabe/frame/activitiestray.py:707
+#: ../src/jarabe/journal/journaltoolbox.py:425
+#: ../src/jarabe/journal/palettes.py:72 ../src/jarabe/view/palettes.py:62
+msgid "Resume"
+msgstr ""
+
+#: ../src/jarabe/desktop/meshbox.py:445
+#: ../src/jarabe/frame/activitiestray.py:221
+msgid "Join"
+msgstr ""
+
+#: ../src/jarabe/desktop/schoolserver.py:18
+msgid "Cannot obtain data needed for registration."
+msgstr ""
+
+#: ../src/jarabe/desktop/schoolserver.py:35
+msgid "Cannot connect to the server."
+msgstr ""
+
+#: ../src/jarabe/desktop/schoolserver.py:40
+msgid "The server could not complete the request."
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:226
+#: ../src/jarabe/frame/activitiestray.py:655
+msgid "Decline"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:608
+#, python-format
+msgid "%dB"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:610
+#, python-format
+msgid "%dKB"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:612
+#, python-format
+msgid "%dMB"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:629
+#, python-format
+msgid "%s of %s"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:640
+#, python-format
+msgid "Transfer from %r"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:650
+msgid "Accept"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:673
+#: ../src/jarabe/frame/activitiestray.py:780
+#, python-format
+msgid "%s (%s)"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:751
+#, python-format
+msgid "Transfer to %r"
+msgstr ""
+
+#: ../src/jarabe/frame/clipboardmenu.py:52
+msgid "Remove"
+msgstr ""
+
+#: ../src/jarabe/frame/clipboardmenu.py:57
+#: ../src/jarabe/frame/clipboardmenu.py:80
+msgid "Open"
+msgstr ""
+
+#: ../src/jarabe/frame/clipboardmenu.py:85
+msgid "Open with"
+msgstr ""
+
+#: ../src/jarabe/frame/clipboardobject.py:47
+#, python-format
+msgid "%s clipping"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:36
+msgid "Neighborhood"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:38
+msgid "Group"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:40
+msgid "Home"
+msgstr ""
+
+#: ../src/jarabe/intro/window.py:124
+msgid "Click to change color:"
+msgstr ""
+
+#: ../src/jarabe/intro/window.py:174 ../src/jarabe/journal/detailview.py:103
+msgid "Back"
+msgstr ""
+
+#: ../src/jarabe/intro/window.py:191
+msgid "Next"
+msgstr ""
+
+#: ../src/jarabe/journal/collapsedentry.py:258
+#: ../src/jarabe/journal/expandedentry.py:159
+#: ../src/jarabe/journal/palettes.py:66
+msgid "Untitled"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:205
+msgid "No preview"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:224
+msgid "Participants:"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:247
+msgid "Description:"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:273
+msgid "Tags:"
+msgstr ""
+
+#: ../src/jarabe/journal/journalactivity.py:108
+#: ../src/jarabe/journal/volumestoolbar.py:47
+msgid "Journal"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:65
+msgid "Search"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:124
+msgid "Anytime"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:126
+msgid "Today"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:128
+msgid "Since yesterday"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 7 days.
+#: ../src/jarabe/journal/journaltoolbox.py:130
+msgid "Past week"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 30 days.
+#: ../src/jarabe/journal/journaltoolbox.py:132
+msgid "Past month"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 356 days.
+#: ../src/jarabe/journal/journaltoolbox.py:134
+msgid "Past year"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:141
+msgid "Anyone"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:143
+msgid "My friends"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:144
+msgid "My class"
+msgstr ""
+
+#. TRANS: Item in a combo box that filters by entry type.
+#: ../src/jarabe/journal/journaltoolbox.py:271
+msgid "Anything"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:347
+#: ../src/jarabe/journal/palettes.py:90
+msgid "Copy"
+msgstr ""
+
+#. TRANS: Action label for starting an entry.
+#: ../src/jarabe/journal/journaltoolbox.py:428
+#: ../src/jarabe/journal/palettes.py:75 ../src/jarabe/view/palettes.py:111
+msgid "Start"
+msgstr ""
+
+#: ../src/jarabe/journal/listview.py:40
+msgid "Your Journal is empty"
+msgstr ""
+
+#: ../src/jarabe/journal/listview.py:41
+msgid "No matching entries "
+msgstr ""
+
+#: ../src/jarabe/journal/listview.py:369
+msgid "Clear search"
+msgstr ""
+
+#: ../src/jarabe/journal/misc.py:92
+msgid "No date"
+msgstr ""
+
+#: ../src/jarabe/journal/modalalert.py:63
+msgid "Your Journal is full"
+msgstr ""
+
+#: ../src/jarabe/journal/modalalert.py:67
+msgid "Please delete some old Journal entries to make space for new ones."
+msgstr ""
+
+#: ../src/jarabe/journal/modalalert.py:79
+msgid "Show Journal"
+msgstr ""
+
+#: ../src/jarabe/journal/objectchooser.py:136
+msgid "Choose an object"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:73
+msgid "Resume with"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:76
+msgid "Start with"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:98
+msgid "Send to"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:107
+msgid "View Details"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:185
+msgid "No friends present"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:190
+msgid "No valid connection found"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:218
+msgid "No activity to resume entry"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:220
+msgid "No activity to start entry"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:61
+msgid "Remove friend"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:64
+msgid "Make friend"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:81
+msgid "My Settings"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:86
+msgid "Logout"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:91
+msgid "Restart"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:96
+msgid "Shutdown"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:131
+#, python-format
+msgid "Invite to %s"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:43
+msgid "Starting..."
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:73
+msgid "Stop"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:145
+msgid "Remove favorite"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:149
+msgid "Make favorite"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:201
+msgid "Show contents"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:223 ../src/jarabe/view/palettes.py:272
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:248
+msgid "Unmount"
+msgstr ""
diff --git a/shell/po/fr.po b/shell/po/fr.po
new file mode 100644
index 0000000..caf42af
--- /dev/null
+++ b/shell/po/fr.po
@@ -0,0 +1,1619 @@
+# translation of sugar.po to french
+# Copyright (C) 2007 the Package Owner
+# This file is distributed under the same license as the sugar graphical shell package.
+# Samuel Bizien <samuel@bizien.info>, 2007.
+msgid ""
+msgstr ""
+"Project-Id-Version: sugar\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-02-11 00:32-0500\n"
+"PO-Revision-Date: 2010-08-08 11:26+0200\n"
+"Last-Translator: samy boutayeb <s.boutayeb@free.fr>\n"
+"Language-Team: French <traduc@traduc.org>\n"
+"Language: fr\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+"X-Generator: Pootle 2.0.3\n"
+
+#: ../extensions/cpsection/aboutme/__init__.py:24
+msgid "About Me"
+msgstr "Moi"
+
+#: ../extensions/cpsection/aboutme/model.py:43
+msgid "You must enter a name."
+msgstr "Vous devez indiquer un nom."
+
+#: ../extensions/cpsection/aboutme/model.py:68
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr "stroke: color=%s hue=%s"
+
+#: ../extensions/cpsection/aboutme/model.py:71
+#, python-format
+msgid "stroke: %s"
+msgstr "stroke: %s"
+
+#: ../extensions/cpsection/aboutme/model.py:73
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr "fill: color=%s hue=%s"
+
+#: ../extensions/cpsection/aboutme/model.py:75
+#, python-format
+msgid "fill: %s"
+msgstr "fill: %s"
+
+#: ../extensions/cpsection/aboutme/model.py:86
+msgid "Error in specified color modifiers."
+msgstr "Erreur dans les modificateurs de couleur spécifiés."
+
+#: ../extensions/cpsection/aboutme/model.py:89
+msgid "Error in specified colors."
+msgstr "Erreur dans les couleurs spécifiées."
+
+#: ../extensions/cpsection/aboutme/view.py:94 ../src/jarabe/intro/window.py:93
+msgid "Name:"
+msgstr "Nom :"
+
+#: ../extensions/cpsection/aboutme/view.py:128
+msgid "Click to change your color:"
+msgstr "Cliquer pour changer de couleur :"
+
+#: ../extensions/cpsection/aboutcomputer/__init__.py:21
+msgid "About my Computer"
+msgstr "Mon ordinateur"
+
+#: ../extensions/cpsection/aboutcomputer/model.py:28
+msgid "Not available"
+msgstr "Non disponible"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:60
+msgid "Identity"
+msgstr "Identité"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:69
+msgid "Serial Number:"
+msgstr "Numéro de série :"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:91
+msgid "Software"
+msgstr "Logiciel"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:100
+msgid "Build:"
+msgstr "Version :"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:115
+msgid "Sugar:"
+msgstr "Sugar :"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:131
+msgid "Firmware:"
+msgstr "Micrologiciel :"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:146
+msgid "Wireless Firmware:"
+msgstr "Micrologiciel sans fil :"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:169
+msgid "Copyright and License"
+msgstr "Copyright et licence"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:184
+msgid ""
+"Sugar is the graphical user interface that you are looking at. Sugar is free "
+"software, covered by the GNU General Public License, and you are welcome to "
+"change it and/or distribute copies of it under certain conditions described "
+"therein."
+msgstr ""
+"Sugar est l'interface graphique utilisateur que vous utilisez actuellement. "
+"Sugar est un logiciel libre couvert par la licence GNU/GPL (General Public "
+"License). Vous êtes autorisé à le modifier et/ou à en distribuer des copies "
+"aux conditions spécifiées."
+
+#: ../extensions/cpsection/aboutcomputer/view.py:196
+msgid "Full license:"
+msgstr "Licence complète :"
+
+#: ../extensions/cpsection/datetime/__init__.py:21
+msgid "Date & Time"
+msgstr "Date & heure"
+
+#: ../extensions/cpsection/datetime/model.py:87
+msgid "Error timezone does not exist."
+msgstr "Erreur : le fuseau horaire n'existe pas."
+
+#: ../extensions/cpsection/datetime/view.py:68 ../data/sugar.schemas.in.h:33
+msgid "Timezone"
+msgstr "Fuseau horaire"
+
+#: ../extensions/cpsection/frame/__init__.py:21
+msgid "Frame"
+msgstr "Cadre"
+
+#: ../extensions/cpsection/frame/model.py:38
+#: ../extensions/cpsection/frame/model.py:60
+msgid "Value must be an integer."
+msgstr "La valeur doit être un entier."
+
+#: ../extensions/cpsection/frame/view.py:26
+msgid "never"
+msgstr "jamais"
+
+#: ../extensions/cpsection/frame/view.py:27
+msgid "instantaneous"
+msgstr "immédiat"
+
+#: ../extensions/cpsection/frame/view.py:28
+#, python-format
+msgid "%s seconds"
+msgstr "%s secondes"
+
+#: ../extensions/cpsection/frame/view.py:52
+msgid "Activation Delay"
+msgstr "Délai d'activation"
+
+#: ../extensions/cpsection/frame/view.py:76
+msgid "Corner"
+msgstr "Coin"
+
+#: ../extensions/cpsection/frame/view.py:111
+msgid "Edge"
+msgstr "Bord"
+
+#: ../extensions/cpsection/keyboard/__init__.py:21
+#: ../extensions/cpsection/keyboard/view.py:31
+msgid "Keyboard"
+msgstr "Clavier"
+
+#: ../extensions/cpsection/keyboard/view.py:189
+msgid "Keyboard Model"
+msgstr "Modèle de clavier"
+
+#: ../extensions/cpsection/keyboard/view.py:248
+msgid "Key(s) to change layout"
+msgstr "Touche(s) de modification de la disposition"
+
+#: ../extensions/cpsection/keyboard/view.py:318
+msgid "Keyboard Layout(s)"
+msgstr "Disposition(s) du clavier"
+
+#: ../extensions/cpsection/language/__init__.py:21
+#: ../extensions/cpsection/language/view.py:33
+msgid "Language"
+msgstr "Langue"
+
+#: ../extensions/cpsection/language/model.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr "Accès impossible à ~/.i18n. Création de paramètres par défaut."
+
+#: ../extensions/cpsection/language/model.py:124
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr "La langue associée au code = %s n'a pas pu être déterminée."
+
+#: ../extensions/cpsection/language/model.py:144
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr "Désolé je ne parle pas '%s'."
+
+#: ../extensions/cpsection/language/view.py:56
+msgid ""
+"Add languages in the order you prefer. If a translation is not available, "
+"the next in the list will be used."
+msgstr ""
+"Ajoutez des langues dans l'ordre souhaité. Si la traduction n'est pas "
+"disponible, la suivante dans la liste sera utilisée."
+
+#: ../extensions/cpsection/modemconfiguration/__init__.py:21
+msgid "Modem Configuration"
+msgstr "Configuration du modem"
+
+#: ../extensions/cpsection/modemconfiguration/view.py:90
+msgid "Username:"
+msgstr "Identifiant :"
+
+#: ../extensions/cpsection/modemconfiguration/view.py:101
+msgid "Password:"
+msgstr "Mot de passe :"
+
+#: ../extensions/cpsection/modemconfiguration/view.py:112
+msgid "Number:"
+msgstr "Numéro :"
+
+#: ../extensions/cpsection/modemconfiguration/view.py:123
+msgid "APN:"
+msgstr "APN :"
+
+#: ../extensions/cpsection/network/__init__.py:21
+#: ../extensions/cpsection/network/view.py:28
+msgid "Network"
+msgstr "Réseau"
+
+#: ../extensions/cpsection/network/model.py:79
+msgid "State is unknown."
+msgstr "État inconnu."
+
+#: ../extensions/cpsection/network/model.py:105
+msgid "Error in specified radio argument use on/off."
+msgstr "Argument 'radio' spécifié incorrect. Utiliser marche/arrêt."
+
+#: ../extensions/cpsection/network/model.py:137
+msgid "Error in specified argument use 0/1."
+msgstr "Argument spécifié incorrect. Utiliser 0/1."
+
+#: ../extensions/cpsection/network/view.py:59
+msgid "Wireless"
+msgstr "Réseau sans fil"
+
+#: ../extensions/cpsection/network/view.py:67
+msgid "Turn off the wireless radio to save battery life"
+msgstr "Désactiver la radio sans fil pour prolonger la batterie"
+
+#: ../extensions/cpsection/network/view.py:80
+msgid "Radio"
+msgstr "Radio"
+
+#: ../extensions/cpsection/network/view.py:96
+msgid "Discard network history if you have trouble connecting to the network"
+msgstr ""
+"Ignorer l'historique du réseau si vous avez du mal à vous connecter au "
+"réseau"
+
+#: ../extensions/cpsection/network/view.py:105
+msgid "Discard network history"
+msgstr "Ignorer l'historique du réseau"
+
+#: ../extensions/cpsection/network/view.py:118
+msgid "Collaboration"
+msgstr "Collaboration"
+
+#: ../extensions/cpsection/network/view.py:126
+msgid ""
+"The server is the equivalent of what room you are in; people on the same "
+"server will be able to see each other, even when they aren't on the same "
+"network."
+msgstr ""
+"Le serveur est comparable à la pièce dans laquelle vous vous trouvez : les "
+"personnes présentes sur le même serveur pourront se voir même si elles ne se "
+"trouvent pas sur le même réseau."
+
+#: ../extensions/cpsection/network/view.py:136
+msgid "Server:"
+msgstr "Serveur :"
+
+#: ../extensions/cpsection/power/__init__.py:21
+msgid "Power"
+msgstr "Alimentation"
+
+#: ../extensions/cpsection/power/model.py:54
+msgid "Error in automatic pm argument, use on/off."
+msgstr "Erreur dans l'argument gestion de l'alimentation automatique."
+
+#: ../extensions/cpsection/power/model.py:81
+msgid "Error in extreme pm argument, use on/off."
+msgstr "Erreur dans l'argument gestion de l'alimentation extrême."
+
+#: ../extensions/cpsection/power/view.py:47
+msgid "Power management"
+msgstr "Gestion de l'alimentation"
+
+#: ../extensions/cpsection/power/view.py:57
+msgid "Automatic power management (increases battery life)"
+msgstr "Gestion automatique de l'alimentation (prolonge la batterie)"
+
+#: ../extensions/cpsection/power/view.py:85
+msgid ""
+"Extreme power management (disableswireless radio, increases battery life)"
+msgstr ""
+"Gestion extrême de l'alimentation (désactive la radio sans fil, prolonge la "
+"durée de vie de la batterie)"
+
+#: ../extensions/cpsection/updater/__init__.py:21
+msgid "Software update"
+msgstr "Mise à jour logicielle"
+
+#: ../extensions/cpsection/updater/view.py:63
+msgid ""
+"Software updates correct errors, eliminate security vulnerabilities, and "
+"provide new features."
+msgstr ""
+"Les mises à jour logicielles corrigent les erreurs, éliminent les failles de "
+"sécurité et apportent de nouvelles fonctionnalités."
+
+#: ../extensions/cpsection/updater/view.py:125
+#, python-format
+msgid "Checking %s..."
+msgstr "Vérification de %s..."
+
+#: ../extensions/cpsection/updater/view.py:127
+#, python-format
+msgid "Downloading %s..."
+msgstr "Téléchargement de %s..."
+
+#: ../extensions/cpsection/updater/view.py:129
+#, python-format
+msgid "Updating %s..."
+msgstr "Mise à jour de %s..."
+
+#: ../extensions/cpsection/updater/view.py:139
+msgid "Your software is up-to-date"
+msgstr "Vos logiciels sont à jour"
+
+#: ../extensions/cpsection/updater/view.py:141
+#, python-format
+msgid "You can install %s update"
+msgid_plural "You can install %s updates"
+msgstr[0] "Vous pouvez installer %s mise à jour"
+msgstr[1] "Vous pouvez installer %s mises à jour"
+
+#: ../extensions/cpsection/updater/view.py:159
+msgid "Checking for updates..."
+msgstr "Vérification des mises à jour..."
+
+#: ../extensions/cpsection/updater/view.py:164
+msgid "Installing updates..."
+msgstr "Installation des mises à jour..."
+
+#: ../extensions/cpsection/updater/view.py:172
+#, python-format
+msgid "%s update was installed"
+msgid_plural "%s updates were installed"
+msgstr[0] "%s mise à jour a été installée"
+msgstr[1] "%s mises à jour ont été installées"
+
+#: ../extensions/cpsection/updater/view.py:253
+msgid "Install selected"
+msgstr "Installer les activités sélectionnées"
+
+#: ../extensions/cpsection/updater/view.py:274
+#, python-format
+msgid "Download size: %s"
+msgstr "Taille du téléchargement : %s"
+
+#: ../extensions/cpsection/updater/view.py:362
+#, python-format
+msgid "From version %(current)d to %(new)s (Size: %(size)s)"
+msgstr "De la version %(current)d à %(new)s (taille : %(size)s)"
+
+#. TRANS: download size is 0
+#: ../extensions/cpsection/updater/view.py:382
+msgid "None"
+msgstr "Zéro"
+
+#. TRANS: download size of very small updates
+#: ../extensions/cpsection/updater/view.py:385
+msgid "1 KB"
+msgstr "1 Ko"
+
+#. TRANS: download size of small updates, e.g. '250 KB'
+#: ../extensions/cpsection/updater/view.py:388
+#, python-format
+msgid "%.0f KB"
+msgstr "%.0f Ko"
+
+#. TRANS: download size of updates, e.g. '2.3 MB'
+#: ../extensions/cpsection/updater/view.py:391
+#, python-format
+msgid "%.1f MB"
+msgstr "%.1f Mo"
+
+#: ../extensions/deviceicon/battery.py:58
+msgid "My Battery"
+msgstr "Ma batterie"
+
+#: ../extensions/deviceicon/battery.py:137
+msgid "Removed"
+msgstr "Retiré"
+
+#: ../extensions/deviceicon/battery.py:140
+msgid "Charging"
+msgstr "En charge"
+
+#: ../extensions/deviceicon/battery.py:143
+msgid "Very little power remaining"
+msgstr "La batterie est pratiquement déchargée"
+
+#: ../extensions/deviceicon/battery.py:149
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr "%(hour)d:%(min).2d restantes"
+
+#: ../extensions/deviceicon/battery.py:152
+msgid "Charged"
+msgstr "Charge complète"
+
+#: ../extensions/deviceicon/network.py:49
+#, python-format
+msgid "IP address: %s"
+msgstr "Adresse IP : %s"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#: ../extensions/deviceicon/network.py:111
+msgid "Disconnect..."
+msgstr "Déconnexion..."
+
+#: ../extensions/deviceicon/network.py:116
+msgid "Create new wireless network"
+msgstr "Créer un nouveau réseau sans fil"
+
+#: ../extensions/deviceicon/network.py:122
+#: ../extensions/deviceicon/network.py:284
+#: ../src/jarabe/desktop/meshbox.py:248 ../src/jarabe/desktop/meshbox.py:537
+msgid "Connecting..."
+msgstr "Connexion..."
+
+# TODO: show the channel number
+#: ../extensions/deviceicon/network.py:126
+#: ../extensions/deviceicon/network.py:198
+#: ../extensions/deviceicon/network.py:288
+#: ../src/jarabe/desktop/meshbox.py:254 ../src/jarabe/desktop/meshbox.py:543
+msgid "Connected"
+msgstr "Connecté"
+
+#: ../extensions/deviceicon/network.py:158
+msgid "Channel"
+msgstr "Canal"
+
+#: ../extensions/deviceicon/network.py:173
+msgid "Wired Network"
+msgstr "Réseau filaire"
+
+#: ../extensions/deviceicon/network.py:201
+msgid "Speed"
+msgstr "Vitesse"
+
+#: ../extensions/deviceicon/network.py:228
+msgid "Wireless modem"
+msgstr "Modem sans fil"
+
+#: ../extensions/deviceicon/network.py:276
+msgid "Please wait..."
+msgstr "Patienter..."
+
+#: ../extensions/deviceicon/network.py:279
+#: ../src/jarabe/desktop/meshbox.py:164 ../src/jarabe/desktop/meshbox.py:494
+msgid "Connect"
+msgstr "Connecter"
+
+#: ../extensions/deviceicon/network.py:280
+msgid "Disconnected"
+msgstr "Déconnecté"
+
+#: ../extensions/deviceicon/network.py:283
+#: ../src/jarabe/controlpanel/toolbar.py:115
+#: ../src/jarabe/desktop/homebox.py:68
+#: ../src/jarabe/frame/activitiestray.py:700
+#: ../src/jarabe/frame/activitiestray.py:799
+#: ../src/jarabe/frame/activitiestray.py:827
+msgid "Cancel"
+msgstr "Annuler"
+
+#: ../extensions/deviceicon/network.py:287
+#: ../src/jarabe/desktop/meshbox.py:168
+msgid "Disconnect"
+msgstr "Déconnecter"
+
+#: ../extensions/deviceicon/network.py:530
+#, python-format
+msgid "%s's network"
+msgstr "Réseau %s"
+
+#: ../extensions/deviceicon/network.py:597
+#: ../extensions/deviceicon/network.py:656
+msgid "Mesh Network"
+msgstr "Réseau maillé"
+
+#: ../extensions/deviceicon/network.py:857
+#, python-format
+msgid "Data sent %d kb / received %d kb"
+msgstr "Données envoyées %d ko / reçues %d ko"
+
+#: ../extensions/deviceicon/network.py:868
+msgid "Connection time "
+msgstr "Durée de connexion"
+
+#: ../extensions/deviceicon/speaker.py:59
+msgid "My Speakers"
+msgstr "Haut-parleurs"
+
+#: ../extensions/deviceicon/speaker.py:133
+msgid "Unmute"
+msgstr "Activer le son"
+
+#: ../extensions/deviceicon/speaker.py:136
+msgid "Mute"
+msgstr "Mettre en sourdine"
+
+#: ../extensions/globalkey/screenshot.py:59
+msgid "Mesh"
+msgstr "Réseau maillé"
+
+#: ../extensions/globalkey/screenshot.py:61
+#: ../src/jarabe/frame/zoomtoolbar.py:39
+msgid "Group"
+msgstr "Groupe"
+
+#: ../extensions/globalkey/screenshot.py:63
+#: ../src/jarabe/frame/zoomtoolbar.py:41
+msgid "Home"
+msgstr "Accueil"
+
+#: ../extensions/globalkey/screenshot.py:69
+#: ../src/jarabe/frame/zoomtoolbar.py:43
+msgid "Activity"
+msgstr "Activité"
+
+#: ../extensions/globalkey/screenshot.py:72
+msgid "Screenshot"
+msgstr "Capture d'écran"
+
+#: ../extensions/globalkey/screenshot.py:74
+#, python-format
+msgid "Screenshot of \"%s\""
+msgstr "Capture d'écran de \"%s\""
+
+#: ../data/sugar.schemas.in.h:1
+msgid ""
+"\"disabled\" to ask nick on initialization; \"system\" to reuse UNIX account "
+"long name."
+msgstr ""
+"\"désactivé\" pour demander un pseudo lors de l'initialisation ; \"système\" "
+"pour réutiliser l'identifiant long du compte UNIX."
+
+#: ../data/sugar.schemas.in.h:2
+msgid "Backup URL"
+msgstr "Sauvegarde de l'URL"
+
+#: ../data/sugar.schemas.in.h:3
+msgid ""
+"Color for the XO icon that is used throughout the desktop. The string is "
+"composed of the stroke color and fill color, format is that of rbg colors. "
+"Example: #AC32FF,#9A5200"
+msgstr ""
+"Couleur du XO utilisée sur le Bureau. La chaîne indique la couleur du trait "
+"et du remplissage. Le format correspond aux couleurs RVB. Exemple : "
+"#AC32FF,#9A5200"
+
+#: ../data/sugar.schemas.in.h:4
+msgid "Corner Delay"
+msgstr "Délai des coins"
+
+#: ../data/sugar.schemas.in.h:5
+msgid "Default font face"
+msgstr "Police par défaut"
+
+#: ../data/sugar.schemas.in.h:6
+msgid "Default font size"
+msgstr "Corps de la police par défaut"
+
+#: ../data/sugar.schemas.in.h:7
+msgid "Default nick"
+msgstr "Pseudo par défaut"
+
+#: ../data/sugar.schemas.in.h:8
+msgid "Delay for the activation of the frame using the corners."
+msgstr "Délai d'activation du cadre à l'aide des coins."
+
+#: ../data/sugar.schemas.in.h:9
+msgid "Delay for the activation of the frame using the edges."
+msgstr "Délai d'activation du cadre à l'aide des bords."
+
+#: ../data/sugar.schemas.in.h:10
+msgid "Edge Delay"
+msgstr "Délai des bords"
+
+#: ../data/sugar.schemas.in.h:11
+msgid "Favorites Layout"
+msgstr "Disposition favorite"
+
+#: ../data/sugar.schemas.in.h:12
+msgid "Favorites resume mode"
+msgstr "Mode de reprise favori"
+
+#: ../data/sugar.schemas.in.h:13
+msgid "Font face that is used throughout the desktop."
+msgstr "Police utilisée sur le bureau."
+
+#: ../data/sugar.schemas.in.h:14
+msgid "Font size that is used throughout the desktop."
+msgstr "Corps de la police utilisée sur le bureau."
+
+#: ../data/sugar.schemas.in.h:15
+msgid ""
+"If TRUE, Sugar will make us searchable for the other users of the Jabber "
+"server."
+msgstr ""
+"Si VRAI, Sugar permettra aux autres utilisateurs du serveur Jabber de nous "
+"retrouver."
+
+#: ../data/sugar.schemas.in.h:16
+msgid "If TRUE, Sugar will show a \"Log out\" option."
+msgstr "Si VRAI, Sugar affichera une option \"Déconnexion\"."
+
+#: ../data/sugar.schemas.in.h:17
+msgid "Jabber Server"
+msgstr "Serveur Jabber"
+
+#: ../data/sugar.schemas.in.h:18
+msgid "Keyboard layouts"
+msgstr "Dispositions du clavier"
+
+#: ../data/sugar.schemas.in.h:19
+msgid "Keyboard model"
+msgstr "Modèle de clavier"
+
+#: ../data/sugar.schemas.in.h:20
+msgid "Keyboard options"
+msgstr "Options du clavier"
+
+#: ../data/sugar.schemas.in.h:21
+msgid "Layout of the favorites view."
+msgstr "Disposition de la vue favorite."
+
+#: ../data/sugar.schemas.in.h:22
+msgid ""
+"List of keyboard layouts. Each entry should be in the form layout(variant)"
+msgstr ""
+"Liste des dispositions de claviers. Chaque ligne doit avoir la forme "
+"disposition(variante)"
+
+#: ../data/sugar.schemas.in.h:23
+msgid "List of keyboard options."
+msgstr "Liste des options de clavier."
+
+#: ../data/sugar.schemas.in.h:24
+msgid "Power Automatic"
+msgstr "Alimentation automatique"
+
+#: ../data/sugar.schemas.in.h:25
+msgid "Power Automatic."
+msgstr "Alimentation automatique."
+
+#: ../data/sugar.schemas.in.h:26
+msgid "Power Extreme"
+msgstr "Alimentation extrême"
+
+#: ../data/sugar.schemas.in.h:27
+msgid "Power Extreme."
+msgstr "Alimentation extrême."
+
+#: ../data/sugar.schemas.in.h:28
+msgid "Publish to Gadget"
+msgstr "Publication vers Gadget"
+
+#: ../data/sugar.schemas.in.h:29
+msgid "Setting for muting the sound device."
+msgstr "Configuration de la mise en sourdine du périphérique audio."
+
+#: ../data/sugar.schemas.in.h:30
+msgid "Show Log out"
+msgstr "Afficher Déconnexion"
+
+#: ../data/sugar.schemas.in.h:31
+msgid "Sound Muted"
+msgstr "Audio désactivé"
+
+#: ../data/sugar.schemas.in.h:32
+msgid "The keyboard model to be used"
+msgstr "Modèle de clavier à utiliser"
+
+#: ../data/sugar.schemas.in.h:34
+msgid "Timezone setting for the system."
+msgstr "Configuration du fuseau horaire du système."
+
+#: ../data/sugar.schemas.in.h:35
+msgid "Url of the jabber server to use."
+msgstr "URL du serveur Jabber à utiliser."
+
+#: ../data/sugar.schemas.in.h:36
+msgid "Url where the backup is saved to."
+msgstr "URL d'enregistrement de la sauvegarde."
+
+#: ../data/sugar.schemas.in.h:37
+msgid "User Color"
+msgstr "Couleurs de l'utilisateur"
+
+#: ../data/sugar.schemas.in.h:38
+msgid "User Name"
+msgstr "Nom de l'utilisateur"
+
+#: ../data/sugar.schemas.in.h:39
+msgid "User name that is used throughout the desktop."
+msgstr "Nom identifiant l'utilisateur sur le bureau."
+
+#: ../data/sugar.schemas.in.h:40
+msgid "Volume Level"
+msgstr "Niveau de volume"
+
+#: ../data/sugar.schemas.in.h:41
+msgid "Volume level for the sound device."
+msgstr "Niveau de volume du périphérique audio."
+
+#: ../data/sugar.schemas.in.h:42
+msgid ""
+"When in resume mode, clicking on a favorite icon will cause the last entry "
+"for that activity to be resumed."
+msgstr ""
+"Lorsque le mode reprise est activé, cliquez sur l'icône Favoris pour "
+"reprendre le dernier élément de cette activité."
+
+#: ../src/jarabe/controlpanel/cmd.py:28
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+"sugar-control-panel: ATTENTION, plusieurs options avec un nom identique ont "
+"été trouvées : %s module: %r"
+
+#: ../src/jarabe/controlpanel/cmd.py:30
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr "sugar-control-panel: key=%s n'est pas une option disponible"
+
+#: ../src/jarabe/controlpanel/cmd.py:31
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr "sugar-control-panel: %s"
+
+# TRANS: Translators, there's a empty line at the end of this string,
+# which must appear in the translated string (msgstr) as well.
+#. TRANS: Translators, there's a empty line at the end of this string,
+#. which must appear in the translated string (msgstr) as well.
+#: ../src/jarabe/controlpanel/cmd.py:37
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" -c key clear the current value for the key \n"
+" "
+msgstr ""
+"Usage: sugar-control-panel [ option ] clé [ args ... ] \n"
+" Contrôle de l'environnement sugar. \n"
+" Options: \n"
+" -h afficher ce message d'aide et quitter \n"
+" -l afficher la liste des options disponibles \n"
+" -h clé afficher les informations sur cette clé \n"
+" -g clé obtenir la valeur actuelle associée à cette clé \n"
+" -s clé définir la valeur actuelle de cette clé \n"
+" "
+
+#: ../src/jarabe/controlpanel/cmd.py:50
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr "Redémarrer sugar pour que les changements prennent effet.\n"
+
+#: ../src/jarabe/controlpanel/gui.py:281
+msgid "Warning"
+msgstr "Attention"
+
+#: ../src/jarabe/controlpanel/gui.py:282
+#: ../src/jarabe/controlpanel/sectionview.py:42
+msgid "Changes require restart"
+msgstr "Relancer pour valider"
+
+#: ../src/jarabe/controlpanel/gui.py:285
+msgid "Cancel changes"
+msgstr "Abandonner"
+
+#: ../src/jarabe/controlpanel/gui.py:290 ../src/jarabe/desktop/homebox.py:70
+msgid "Later"
+msgstr "Plus tard"
+
+#: ../src/jarabe/controlpanel/gui.py:294
+msgid "Restart now"
+msgstr "Maintenant"
+
+#: ../src/jarabe/controlpanel/toolbar.py:61 ../src/jarabe/intro/window.py:206
+msgid "Done"
+msgstr "Accepter"
+
+#: ../src/jarabe/controlpanel/toolbar.py:121
+#: ../src/jarabe/desktop/favoritesview.py:333
+msgid "Ok"
+msgstr "Ok"
+
+#: ../src/jarabe/desktop/activitieslist.py:236
+#, python-format
+msgid "Version %s"
+msgstr "Version %s"
+
+#: ../src/jarabe/desktop/activitieslist.py:357
+msgid "Confirm erase"
+msgstr "Confirmer la suppression"
+
+# Conformer la suppression : faut-il supprimer %s définitivement ?
+#: ../src/jarabe/desktop/activitieslist.py:359
+#, python-format
+msgid "Confirm erase: Do you want to permanently erase %s?"
+msgstr "Confirmer la suppression : faut-il supprimer %s définitivement ?"
+
+# self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+# TODO: Implement stopping downloads
+# self._stop_item.connect('activate', self._stop_item_activate_cb)
+# self.append_menu_item(self._stop_item)
+#: ../src/jarabe/desktop/activitieslist.py:363
+#: ../src/jarabe/frame/clipboardmenu.py:63
+#: ../src/jarabe/view/viewsource.py:218
+msgid "Keep"
+msgstr "Conserver"
+
+#: ../src/jarabe/desktop/activitieslist.py:366
+#: ../src/jarabe/desktop/activitieslist.py:409
+#: ../src/jarabe/journal/journaltoolbox.py:360
+#: ../src/jarabe/journal/palettes.py:105
+msgid "Erase"
+msgstr "Supprimer"
+
+#: ../src/jarabe/desktop/activitieslist.py:430
+msgid "Remove favorite"
+msgstr "Retirer le favori"
+
+#: ../src/jarabe/desktop/activitieslist.py:434
+msgid "Make favorite"
+msgstr "Ajouter aux favoris"
+
+# TRANS: label for the freeform layout in the favorites view
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:116
+msgid "Freeform"
+msgstr "Libre"
+
+# TRANS: label for the ring layout in the favorites view
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:198
+msgid "Ring"
+msgstr "Concentrique"
+
+# TRANS: label for the spiral layout in the favorites view
+#. TRANS: label for the spiral layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:337
+msgid "Spiral"
+msgstr "Spirale"
+
+# TRANS: label for the box layout in the favorites view
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:404
+msgid "Box"
+msgstr "Boîte"
+
+# TRANS: label for the box layout in the favorites view
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:445
+msgid "Triangle"
+msgstr "Triangle"
+
+#: ../src/jarabe/desktop/favoritesview.py:324
+msgid "Registration Failed"
+msgstr "Echec de l'enregistrement"
+
+#: ../src/jarabe/desktop/favoritesview.py:325
+#, python-format
+msgid "%s"
+msgstr "%s"
+
+#: ../src/jarabe/desktop/favoritesview.py:327
+msgid "Registration Successful"
+msgstr "Enregistrement réussi"
+
+#: ../src/jarabe/desktop/favoritesview.py:328
+msgid "You are now registered with your school server."
+msgstr "Vous êtes maintenant enregistré sur le serveur de l'école."
+
+#: ../src/jarabe/desktop/favoritesview.py:630
+msgid "Register"
+msgstr "S'enregistrer"
+
+#: ../src/jarabe/desktop/homebox.py:63
+msgid "Software Update"
+msgstr "Mise à jour logicielle"
+
+#: ../src/jarabe/desktop/homebox.py:64
+msgid "Update your activities to ensure compatibility with your new software"
+msgstr "Actualiser les activités pour assurer la compatibilité logicielle"
+
+#: ../src/jarabe/desktop/homebox.py:73
+msgid "Check now"
+msgstr "Vérifier maintenant"
+
+#: ../src/jarabe/desktop/homebox.py:192
+msgid "List view"
+msgstr "Écran liste"
+
+#: ../src/jarabe/desktop/homebox.py:193
+msgid "<Ctrl>2"
+msgstr "<Ctrl>2"
+
+#: ../src/jarabe/desktop/homebox.py:255
+msgid "Favorites view"
+msgstr "Écran favoris"
+
+#: ../src/jarabe/desktop/homebox.py:256
+msgid "<Ctrl>1"
+msgstr "<Ctrl>1"
+
+#: ../src/jarabe/desktop/keydialog.py:135
+msgid "Key Type:"
+msgstr "Type de clé :"
+
+#: ../src/jarabe/desktop/keydialog.py:155
+msgid "Authentication Type:"
+msgstr "Type d'authentification :"
+
+#: ../src/jarabe/desktop/keydialog.py:220
+msgid "WPA & WPA2 Personal"
+msgstr "WPA & WPA2 Personal"
+
+#: ../src/jarabe/desktop/keydialog.py:229
+msgid "Wireless Security:"
+msgstr "Sécurité sans fil :"
+
+#: ../src/jarabe/desktop/meshbox.py:492
+#, python-format
+msgid "Mesh Network %d"
+msgstr "Réseau maillé %d"
+
+# TRANS: Action label for resuming an activity.
+#. TRANS: Action label for resuming an activity.
+#: ../src/jarabe/desktop/meshbox.py:629
+#: ../src/jarabe/frame/activitiestray.py:735
+#: ../src/jarabe/journal/journaltoolbox.py:428
+#: ../src/jarabe/journal/palettes.py:65 ../src/jarabe/view/palettes.py:67
+msgid "Resume"
+msgstr "Reprendre"
+
+#: ../src/jarabe/desktop/meshbox.py:634
+#: ../src/jarabe/frame/activitiestray.py:233
+msgid "Join"
+msgstr "Rejoindre"
+
+#: ../src/jarabe/desktop/schoolserver.py:103
+msgid "Cannot connect to the server."
+msgstr "Impossible de se connecter au serveur."
+
+#: ../src/jarabe/desktop/schoolserver.py:108
+msgid "The server could not complete the request."
+msgstr "Le serveur n'a pas pu achever la requête."
+
+#: ../src/jarabe/frame/activitiestray.py:238
+#: ../src/jarabe/frame/activitiestray.py:672
+msgid "Decline"
+msgstr "Refuser"
+
+#: ../src/jarabe/frame/activitiestray.py:624
+#, python-format
+msgid "%dB"
+msgstr "%do"
+
+#: ../src/jarabe/frame/activitiestray.py:626
+#, python-format
+msgid "%dKB"
+msgstr "%dKo"
+
+#: ../src/jarabe/frame/activitiestray.py:628
+#, python-format
+msgid "%dMB"
+msgstr "%dMo"
+
+#: ../src/jarabe/frame/activitiestray.py:645
+#, python-format
+msgid "%s of %s"
+msgstr "%s sur %s"
+
+#: ../src/jarabe/frame/activitiestray.py:657
+#, python-format
+msgid "Transfer from %r"
+msgstr "Transfert depuis %r"
+
+#: ../src/jarabe/frame/activitiestray.py:667
+msgid "Accept"
+msgstr "Accepter"
+
+#: ../src/jarabe/frame/activitiestray.py:690
+#: ../src/jarabe/frame/activitiestray.py:817
+#, python-format
+msgid "%s (%s)"
+msgstr "%s (%s)"
+
+#: ../src/jarabe/frame/activitiestray.py:724
+#: ../src/jarabe/frame/activitiestray.py:852
+msgid "Dismiss"
+msgstr "Refuser"
+
+#: ../src/jarabe/frame/activitiestray.py:787
+#, python-format
+msgid "Transfer to %r"
+msgstr "Transfert vers %r"
+
+#: ../src/jarabe/frame/clipboardmenu.py:53 ../src/jarabe/view/palettes.py:221
+msgid "Remove"
+msgstr "Retirer"
+
+#: ../src/jarabe/frame/clipboardmenu.py:58
+#: ../src/jarabe/frame/clipboardmenu.py:81
+msgid "Open"
+msgstr "Ouvrir"
+
+#: ../src/jarabe/frame/clipboardmenu.py:86
+msgid "Open with"
+msgstr "Ouvrir avec"
+
+#: ../src/jarabe/frame/clipboardobject.py:49
+#, python-format
+msgid "%s clipping"
+msgstr "%s coupure"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:37
+msgid "Neighborhood"
+msgstr "Voisinage"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:37
+msgid "F1"
+msgstr "F1"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:39
+msgid "F2"
+msgstr "F2"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:41
+msgid "F3"
+msgstr "F3"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:43
+msgid "F4"
+msgstr "F4"
+
+#: ../src/jarabe/intro/window.py:128
+msgid "Click to change color:"
+msgstr "Cliquer pour changer de couleur :"
+
+#: ../src/jarabe/intro/window.py:192 ../src/jarabe/journal/detailview.py:103
+msgid "Back"
+msgstr "Précédent"
+
+#: ../src/jarabe/intro/window.py:209
+msgid "Next"
+msgstr "Suivant"
+
+#: ../src/jarabe/journal/expandedentry.py:152
+#: ../src/jarabe/journal/palettes.py:59
+msgid "Untitled"
+msgstr "Sans titre"
+
+#: ../src/jarabe/journal/expandedentry.py:243
+msgid "No preview"
+msgstr "Pas de prévisualisation"
+
+#: ../src/jarabe/journal/expandedentry.py:262
+#, python-format
+msgid "Kind: %s"
+msgstr "Variante : %s"
+
+#: ../src/jarabe/journal/expandedentry.py:262
+msgid "Unknown"
+msgstr "Inconnu"
+
+#: ../src/jarabe/journal/expandedentry.py:263
+#, python-format
+msgid "Date: %s"
+msgstr "Date : %s"
+
+#: ../src/jarabe/journal/expandedentry.py:264
+#, python-format
+msgid "Size: %s"
+msgstr "Taille : %s"
+
+#: ../src/jarabe/journal/expandedentry.py:286 ../src/jarabe/journal/misc.py:93
+msgid "No date"
+msgstr "Sans date"
+
+#: ../src/jarabe/journal/expandedentry.py:293
+msgid "Participants:"
+msgstr "Participants :"
+
+#: ../src/jarabe/journal/expandedentry.py:316
+msgid "Description:"
+msgstr "Description :"
+
+#: ../src/jarabe/journal/expandedentry.py:341
+msgid "Tags:"
+msgstr "Étiquettes :"
+
+#: ../src/jarabe/journal/journalactivity.py:108
+#: ../src/jarabe/journal/volumestoolbar.py:47
+msgid "Journal"
+msgstr "Journal"
+
+#: ../src/jarabe/journal/journaltoolbox.py:67
+msgid "Search"
+msgstr "Rechercher"
+
+#: ../src/jarabe/journal/journaltoolbox.py:126
+msgid "Anytime"
+msgstr "N'importe quand"
+
+#: ../src/jarabe/journal/journaltoolbox.py:128
+msgid "Today"
+msgstr "Aujourd'hui"
+
+#: ../src/jarabe/journal/journaltoolbox.py:130
+msgid "Since yesterday"
+msgstr "Depuis hier"
+
+# TRANS: Filter entries modified during the last 7 days.
+#. TRANS: Filter entries modified during the last 7 days.
+#: ../src/jarabe/journal/journaltoolbox.py:132
+msgid "Past week"
+msgstr "Depuis une semaine"
+
+# TRANS: Filter entries modified during the last 30 days.
+#. TRANS: Filter entries modified during the last 30 days.
+#: ../src/jarabe/journal/journaltoolbox.py:134
+msgid "Past month"
+msgstr "Depuis un mois"
+
+# TRANS: Filter entries modified during the last 356 days.
+#. TRANS: Filter entries modified during the last 356 days.
+#: ../src/jarabe/journal/journaltoolbox.py:136
+msgid "Past year"
+msgstr "Depuis une année"
+
+#: ../src/jarabe/journal/journaltoolbox.py:143
+msgid "Anyone"
+msgstr "Tout le monde"
+
+#: ../src/jarabe/journal/journaltoolbox.py:145
+msgid "My friends"
+msgstr "Mes amis"
+
+#: ../src/jarabe/journal/journaltoolbox.py:146
+msgid "My class"
+msgstr "Ma classe"
+
+# TRANS: Item in a combo box that filters by entry type.
+#: ../src/jarabe/journal/journaltoolbox.py:274
+msgid "Anything"
+msgstr "Tout"
+
+# TODO: Add "Start with" menu item
+#: ../src/jarabe/journal/journaltoolbox.py:350
+#: ../src/jarabe/journal/palettes.py:83
+msgid "Copy"
+msgstr "Copier"
+
+# TRANS: Action label for starting an entry.
+#. TRANS: Action label for starting an entry.
+#: ../src/jarabe/journal/journaltoolbox.py:431
+#: ../src/jarabe/journal/palettes.py:68
+msgid "Start"
+msgstr "Lancer"
+
+#: ../src/jarabe/journal/listview.py:373
+msgid "Your Journal is empty"
+msgstr "Le journal est vide"
+
+#: ../src/jarabe/journal/listview.py:375
+msgid "No matching entries"
+msgstr "Aucune entrée correspondante"
+
+#: ../src/jarabe/journal/listview.py:386
+msgid "Clear search"
+msgstr "Effacer la recherche"
+
+#: ../src/jarabe/journal/modalalert.py:63
+msgid "Your Journal is full"
+msgstr "Votre journal est plein"
+
+#: ../src/jarabe/journal/modalalert.py:67
+msgid "Please delete some old Journal entries to make space for new ones."
+msgstr ""
+"Effacer des entrées anciennes du Journal pour libérer de la place pour les "
+"nouvelles entrées."
+
+#: ../src/jarabe/journal/modalalert.py:79
+msgid "Show Journal"
+msgstr "Montre le Journal"
+
+#: ../src/jarabe/journal/objectchooser.py:146
+msgid "Choose an object"
+msgstr "Choisir un objet"
+
+#: ../src/jarabe/journal/objectchooser.py:151
+#: ../src/jarabe/view/viewsource.py:308
+msgid "Close"
+msgstr "Fermer"
+
+#: ../src/jarabe/journal/palettes.py:66
+msgid "Resume with"
+msgstr "Reprendre avec"
+
+#: ../src/jarabe/journal/palettes.py:69
+msgid "Start with"
+msgstr "Commencer avec"
+
+#: ../src/jarabe/journal/palettes.py:91
+msgid "Send to"
+msgstr "Envoyer à"
+
+#: ../src/jarabe/journal/palettes.py:100
+msgid "View Details"
+msgstr "Afficher les détails"
+
+#: ../src/jarabe/journal/palettes.py:178
+msgid "No friends present"
+msgstr "Aucun ami présent"
+
+#: ../src/jarabe/journal/palettes.py:183
+msgid "No valid connection found"
+msgstr "Aucune connexion valide trouvée"
+
+#: ../src/jarabe/journal/palettes.py:211
+msgid "No activity to resume entry"
+msgstr "Aucune activité pour reprendre l'entrée"
+
+#: ../src/jarabe/journal/palettes.py:213
+msgid "No activity to start entry"
+msgstr "Acune activité pour démarrer l'entrée"
+
+#: ../src/jarabe/view/buddymenu.py:62
+msgid "Remove friend"
+msgstr "Retirer de mes amis"
+
+#: ../src/jarabe/view/buddymenu.py:65
+msgid "Make friend"
+msgstr "Ajouter à mes amis"
+
+#: ../src/jarabe/view/buddymenu.py:82
+msgid "Shutdown"
+msgstr "Éteindre"
+
+#: ../src/jarabe/view/buddymenu.py:90
+msgid "Logout"
+msgstr "Se déconnecter"
+
+#: ../src/jarabe/view/buddymenu.py:95
+msgid "My Settings"
+msgstr "Mes paramètres"
+
+#: ../src/jarabe/view/buddymenu.py:130
+#, python-format
+msgid "Invite to %s"
+msgstr "Inviter à %s"
+
+#: ../src/jarabe/view/palettes.py:45
+msgid "Starting..."
+msgstr "Démarrage..."
+
+#. TODO: share-with, keep
+#: ../src/jarabe/view/palettes.py:74
+msgid "View Source"
+msgstr "Afficher la source"
+
+#: ../src/jarabe/view/palettes.py:85
+msgid "Stop"
+msgstr "Arrêter"
+
+#: ../src/jarabe/view/palettes.py:125
+msgid "Start new"
+msgstr "Commencer un nouveau"
+
+#: ../src/jarabe/view/palettes.py:174
+msgid "Show contents"
+msgstr "Afficher les contenus"
+
+#: ../src/jarabe/view/palettes.py:196 ../src/jarabe/view/palettes.py:246
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr "%(free_space)d Mo de libre"
+
+#: ../src/jarabe/view/viewsource.py:208
+msgid "Instance Source"
+msgstr "Instancie source"
+
+#: ../src/jarabe/view/viewsource.py:233
+msgid "Source"
+msgstr "Source"
+
+#: ../src/jarabe/view/viewsource.py:292
+msgid "Activity Bundle Source"
+msgstr "Source du paquet activité"
+
+#: ../src/jarabe/view/viewsource.py:299
+#, python-format
+msgid "View source: %r"
+msgstr "Afficher le code source : %r"
+
+#~ msgid "Title"
+#~ msgstr "Titre"
+
+#~ msgid "Version"
+#~ msgstr "Version"
+
+#~ msgid "Date"
+#~ msgstr "Date"
+
+#~ msgid "Cannot obtain data needed for registration."
+#~ msgstr "Impossible d'obtenir les données nécessaires à l'enregistrement."
+
+#~ msgid "Unmount"
+#~ msgstr "Démonter"
+
+#~ msgid "Restart"
+#~ msgstr "Redémarrer"
+
+#~ msgid ""
+#~ "© 2008 One Laptop per Child Association Inc; Red Hat Inc; and Contributors."
+#~ msgstr ""
+#~ "© 2008 One Laptop per Child Association Inc ; Red Hat Inc ; et "
+#~ "contributeurs."
+
+#~ msgid "Document"
+#~ msgstr "Document"
+
+#~ msgid "Resume by default"
+#~ msgstr "Reprise par défaut"
+
+#~ msgid "Encryption Type:"
+#~ msgstr "Type d'encryptage :"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#~ msgid "Disconnecting..."
+#~ msgstr "Déconnexion..."
+
+#~ msgid "About my XO"
+#~ msgstr "Mon XO"
+
+#~ msgid "Connected to a School Mesh Portal"
+#~ msgstr "Connecté au portail du réseau maillé d'école"
+
+#~ msgid "Looking for a School Mesh Portal..."
+#~ msgstr "Recherche un portail de réseau maillé d'école..."
+
+#~ msgid "Connected to an XO Mesh Portal"
+#~ msgstr "Connecté au portail de réseau maillé de XO"
+
+#~ msgid "Looking for an XO Mesh Portal..."
+#~ msgstr "Recherche un portail de réseau maillé de XO..."
+
+#~ msgid "Connected to a Simple Mesh"
+#~ msgstr "Connecté à un réseau maillé simple"
+
+#~ msgid "Starting a Simple Mesh"
+#~ msgstr "Démarre un réseau maillé simple"
+
+#~ msgid "Unknown Mesh"
+#~ msgstr "Réseau maillé inconnu"
+
+#~ msgid "Settings"
+#~ msgstr "Configuration"
+
+#, python-format
+#~ msgid "Clipboard object: %s."
+#~ msgstr "Objet dans le presse-papier : %s."
+
+#~ msgid "You must enter a server."
+#~ msgstr "Vous devez indiquer un serveur."
+
+#~ msgid "Control Panel"
+#~ msgstr "Panneau de contrôle"
+
+#~ msgid "© 2008 One Laptop per Child Assocation "
+#~ msgstr "© 2008 One Laptop per Child Assocation "
+
+#, fuzzy
+#~ msgid "Sugar is the graphical user interface that "
+#~ msgstr "Sugar est l'interface utilisateur graphique que"
+
+#~ msgid "<Ctrl>R"
+#~ msgstr "<Ctrl>R"
+
+#~ msgid "Ring view"
+#~ msgstr "Vue concentrique"
+
+#, fuzzy
+#~ msgid "Remove from ring"
+#~ msgstr "Retirer de l'anneau"
+
+#~ msgid "Add to ring"
+#~ msgstr "Ajouter à l'anneau"
+
+#~ msgid "Changes require a sugar restart to take effect."
+#~ msgstr "Pour appliquer les changements, sugar doit être redémarré."
+
+#~ msgid "Changes require restart to take effect"
+#~ msgstr "Redémarrer pour appliquer les changements"
+
+#~ msgid "Delay in milliseconds:"
+#~ msgstr "Délai en millisecondes :"
+
+#, fuzzy
+#~ msgid "Hot Corners"
+#~ msgstr "Coins activables"
+
+#, fuzzy
+#~ msgid "Warm Edges"
+#~ msgstr "Bords dynamiques"
+
+#~ msgid "off"
+#~ msgstr "arrêt"
+
+#~ msgid "on"
+#~ msgstr "marche"
+
+#~ msgid "Permission denied. You need to be root to run this method."
+#~ msgstr ""
+#~ "Permission refusée. Vous devez être administrateur afin de lancer cette "
+#~ "méthode."
+
+#~ msgid "Error in reading timezone"
+#~ msgstr "Erreur de lecture de la zone temporelle"
+
+#, python-format
+#~ msgid "Error copying timezone (from %s): %s"
+#~ msgstr "Erreur en copiant la zone temporelle (de %s): %s"
+
+#, python-format
+#~ msgid "Changing permission of timezone: %s"
+#~ msgstr "Changement de la permission de zone temporelle : %s"
+
+#~ msgid "About this XO"
+#~ msgstr "Information sur ce XO"
+
+#~ msgid "Add to journal"
+#~ msgstr "Ajouter au journal"
+
+#~ msgid "Reboot"
+#~ msgstr "Redémarrer"
+
+#~ msgid "My Battery life"
+#~ msgstr "Charge de la batterie"
+
+#~ msgid "Battery charging"
+#~ msgstr "Batterie en charge"
+
+#~ msgid "Battery discharging"
+#~ msgstr "Batterie en décharge"
+
+#~ msgid "Battery fully charged"
+#~ msgstr "Batterie chargée"
+
+#~ msgid "Pick a buddy picture"
+#~ msgstr "Choisir un avatar XO"
+
+#~ msgid "My Picture:"
+#~ msgstr "Ma photo :"
+
+#~ msgid "My Name:"
+#~ msgstr "Mon surnom :"
+
+#~ msgid "My Color:"
+#~ msgstr "Ma couleur :"
+
+#~ msgid "Invite"
+#~ msgstr "Inviter"
+
+#~ msgid "Stop download"
+#~ msgstr "Arrêter de télécharger"
+
+#~ msgid "Text"
+#~ msgstr "Texte"
+
+#~ msgid "Image"
+#~ msgstr "Image"
+
+#~ msgid "Save"
+#~ msgstr "Enregistrer"
+
+#~ msgid "Share"
+#~ msgstr "Partager"
+
+#, python-format
+#~ msgid "%s Activity"
+#~ msgstr "Activité %s"
+
+#~ msgid "Share with:"
+#~ msgstr "Partager avec :"
+
+#~ msgid "Private"
+#~ msgstr "Privé"
+
+#~ msgid "My Neighborhood"
+#~ msgstr "Mon voisinage"
+
+#~ msgid "Undo"
+#~ msgstr "Annuler"
+
+#~ msgid "Redo"
+#~ msgstr "Répéter"
+
+#~ msgid "Paste"
+#~ msgstr "Coller"
+
+#~ msgid "Keep error"
+#~ msgstr "Conserver l'erreur"
+
+#~ msgid "Keep error: all changes will be lost"
+#~ msgstr "Conserver l'erreur : tous les changements seront perdus"
+
+#~ msgid "Don't stop"
+#~ msgstr "Ne pas arrêter"
+
+#~ msgid "Stop anyway"
+#~ msgstr "Arrêter malgré tout"
+
+#~ msgid "Continue"
+#~ msgstr "Continuer"
+
+#~ msgid "OK"
+#~ msgstr "OK"
+
+#, python-format
+#~ msgid "%d year"
+#~ msgstr "%d an"
+
+#, python-format
+#~ msgid "%d years"
+#~ msgstr "%d ans"
+
+#, python-format
+#~ msgid "%d month"
+#~ msgstr "%d mois"
+
+#, python-format
+#~ msgid "%d months"
+#~ msgstr "%d mois"
+
+#, python-format
+#~ msgid "%d week"
+#~ msgstr "%d semaine"
+
+#, python-format
+#~ msgid "%d weeks"
+#~ msgstr "%d semaines"
+
+#, python-format
+#~ msgid "%d day"
+#~ msgstr "%d jour"
+
+#, python-format
+#~ msgid "%d days"
+#~ msgstr "%d jours"
+
+#, python-format
+#~ msgid "%d hour"
+#~ msgstr "%d heure"
+
+#, python-format
+#~ msgid "%d hours"
+#~ msgstr "%d heures"
+
+#, python-format
+#~ msgid "%d minute"
+#~ msgstr "%d minute"
+
+#, python-format
+#~ msgid "%d minutes"
+#~ msgstr "%d minutes"
+
+#, python-format
+#~ msgid "%d second"
+#~ msgstr "%d seconde"
+
+#~ msgid " and "
+#~ msgstr "_et_"
+
+#~ msgid ", "
+#~ msgstr ", "
diff --git a/shell/po/gu.po b/shell/po/gu.po
new file mode 100644
index 0000000..baf1787
--- /dev/null
+++ b/shell/po/gu.po
@@ -0,0 +1,365 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-02-09 00:30-0500\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.0.1\n"
+
+#: ../src/intro/intro.py:67
+msgid "Name:"
+msgstr ""
+
+#: ../src/intro/intro.py:96
+msgid "Click to change color:"
+msgstr ""
+
+#: ../src/intro/intro.py:146
+msgid "Back"
+msgstr ""
+
+#: ../src/intro/intro.py:160
+msgid "Done"
+msgstr ""
+
+#: ../src/intro/intro.py:163
+msgid "Next"
+msgstr ""
+
+#: ../src/view/BuddyMenu.py:59
+msgid "Remove friend"
+msgstr ""
+
+#: ../src/view/BuddyMenu.py:62
+msgid "Make friend"
+msgstr ""
+
+#: ../src/view/BuddyMenu.py:84
+#, python-format
+msgid "Invite to %s"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:58
+msgid "Remove"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:63
+msgid "Open"
+msgstr ""
+
+#. self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+#. TODO: Implement stopping downloads
+#. self._stop_item.connect('activate', self._stop_item_activate_cb)
+#. self.append_menu_item(self._stop_item)
+#: ../src/view/clipboardmenu.py:73
+msgid "Add to journal"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:213
+#, python-format
+msgid "Clipboard object: %s."
+msgstr ""
+
+#: ../src/hardware/keydialog.py:149
+msgid "Key Type:"
+msgstr ""
+
+#: ../src/hardware/keydialog.py:169
+msgid "Authentication Type:"
+msgstr ""
+
+#: ../src/hardware/keydialog.py:250
+msgid "Encryption Type:"
+msgstr ""
+
+#: ../src/view/home/activitiesdonut.py:90
+msgid "Starting..."
+msgstr ""
+
+#: ../src/view/home/activitiesdonut.py:104 ../src/view/home/MeshBox.py:295
+msgid "Resume"
+msgstr ""
+
+#: ../src/view/home/activitiesdonut.py:111
+msgid "Stop"
+msgstr ""
+
+#: ../src/view/Shell.py:285
+msgid "Screenshot"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:159
+msgid "Reboot"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:164
+msgid "Shutdown"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:170
+msgid "Register"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:175 ../src/view/home/HomeBox.py:216
+msgid "About this XO"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:222
+msgid "Not available"
+msgstr ""
+
+#. Only show disconnect when there's a mesh device, because mesh takes
+#. priority over the normal wireless device. NM doesn't have a "disconnect"
+#. method for a device either (for various reasons) so this doesn't
+#. have a good mapping
+#: ../src/view/home/MeshBox.py:90 ../src/view/home/MeshBox.py:197
+#: ../src/view/devices/network/wireless.py:113
+#: ../src/view/devices/network/mesh.py:83
+msgid "Disconnect..."
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:195 ../src/view/devices/network/mesh.py:37
+#: ../src/view/devices/network/mesh.py:62
+#: ../src/view/devices/network/mesh.py:66
+msgid "Mesh Network"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:300
+msgid "Join"
+msgstr ""
+
+#: ../src/view/devices/battery.py:38
+msgid "My Battery life"
+msgstr ""
+
+#: ../src/view/devices/battery.py:94
+msgid "Battery charging"
+msgstr ""
+
+#: ../src/view/devices/battery.py:96
+msgid "Battery discharging"
+msgstr ""
+
+#: ../src/view/devices/battery.py:98
+msgid "Battery fully charged"
+msgstr ""
+
+#: ../src/view/devices/network/wireless.py:61
+msgid "Disconnected"
+msgstr ""
+
+#: ../src/view/devices/network/wireless.py:131
+msgid "Channel"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:42
+msgid "Neighborhood"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:54
+msgid "Group"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:66
+msgid "Home"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:78
+msgid "Activity"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:115
+msgid "Share with:"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:117
+msgid "Private"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:118
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:126
+msgid "Keep"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:245
+msgid "Undo"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:250
+msgid "Redo"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:260
+msgid "Copy"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:265
+msgid "Paste"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:454
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:824
+msgid "Keep error"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:825
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:828
+msgid "Don't stop"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:831
+msgid "Stop anyway"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:164 ../lib/sugar/graphics/alert.py:206
+msgid "Cancel"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:168
+msgid "Ok"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:216
+msgid "Continue"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:244
+msgid "OK"
+msgstr ""
+
+#: ../src/controlpanel/control.py:219
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr ""
+
+#: ../src/controlpanel/control.py:273
+msgid "Error in specified color modifiers."
+msgstr ""
+
+#: ../src/controlpanel/control.py:276
+msgid "Error in specified colors."
+msgstr ""
+
+#: ../src/controlpanel/control.py:312
+msgid "off"
+msgstr ""
+
+#: ../src/controlpanel/control.py:314
+msgid "on"
+msgstr ""
+
+#: ../src/controlpanel/control.py:316
+msgid "State is unknown."
+msgstr ""
+
+#: ../src/controlpanel/control.py:336
+msgid "Error in specified radio argument use on/off."
+msgstr ""
+
+#: ../src/controlpanel/control.py:340
+msgid "Permission denied. You need to be root to run this method."
+msgstr ""
+
+#: ../src/controlpanel/control.py:370
+msgid "Error in reading timezone"
+msgstr ""
+
+#: ../src/controlpanel/control.py:401
+#, python-format
+msgid "Error copying timezone (from %s): %s"
+msgstr ""
+
+#: ../src/controlpanel/control.py:406
+#, python-format
+msgid "Changing permission of timezone: %s"
+msgstr ""
+
+#: ../src/controlpanel/control.py:416
+msgid "Error timezone does not exist."
+msgstr ""
+
+#: ../src/controlpanel/control.py:421 ../src/controlpanel/control.py:440
+#, python-format
+msgid "Could not access %s. Create standard settings."
+msgstr ""
+
+#: ../src/controlpanel/control.py:467
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr ""
+
+#: ../src/controlpanel/control.py:477
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:105
+msgid "Connected to a School Mesh Portal"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:107
+msgid "Looking for a School Mesh Portal..."
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:110
+msgid "Connected to an XO Mesh Portal"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:112
+msgid "Looking for an XO Mesh Portal..."
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:115
+msgid "Connected to a Simple Mesh"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:117
+msgid "Starting a Simple Mesh"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:124
+msgid "Unknown Mesh"
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:27
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" "
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:55 ../src/controlpanel/cmd.py:67
+#: ../src/controlpanel/cmd.py:74
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:80
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr ""
diff --git a/shell/po/ha.po b/shell/po/ha.po
new file mode 100644
index 0000000..53ad5cf
--- /dev/null
+++ b/shell/po/ha.po
@@ -0,0 +1,447 @@
+# translation of sugar.po to hausa
+# This file is distributed under the same license as the PACKAGE package.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER.
+# saudat mohammed <saudat@wazobialinux>, 2006.
+msgid ""
+msgstr ""
+"Project-Id-Version: sugar\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-01-18 18:19+0000\n"
+"PO-Revision-Date: 2006-07-10 16:37+0100\n"
+"Last-Translator: saudat mohammed <saudat@wazobialinux>\n"
+"Language-Team: hausa\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: KBabel 1.10\n"
+
+#: ../shell/PresenceWindow.py:62
+msgid "Who's around:"
+msgstr "Wa ke nan:"
+
+#: ../shell/PresenceWindow.py:104
+msgid "Share"
+msgstr "Raba"
+
+#: ../shell/StartPage.py:189
+msgid "Search"
+msgstr "Yi Bincike"
+
+#: ../shell/intro/intro.py:146
+msgid "Back"
+msgstr "Baya"
+
+#: ../activities/browser/NavigationToolbar.py:23
+msgid "Forward"
+msgstr "Tura"
+
+#: ../activities/browser/NavigationToolbar.py:29
+msgid "Reload"
+msgstr "Sake Lodi"
+
+#: ../shell/shell.py:333
+msgid "Everyone"
+msgstr "Kowa"
+
+#: ../sugar/chat/ChatEditor.py:43
+msgid "Send"
+msgstr "Aika"
+
+#: ../shell/intro/intro.py:67
+msgid "Name:"
+msgstr ""
+
+#: ../shell/intro/intro.py:96
+msgid "Click to change color:"
+msgstr ""
+
+#: ../shell/intro/intro.py:160
+msgid "Done"
+msgstr ""
+
+#: ../shell/intro/intro.py:163
+msgid "Next"
+msgstr ""
+
+#: ../shell/view/BuddyMenu.py:59
+msgid "Remove friend"
+msgstr ""
+
+#: ../shell/view/BuddyMenu.py:62
+msgid "Make friend"
+msgstr ""
+
+#: ../shell/view/BuddyMenu.py:84
+#, python-format
+msgid "Invite to %s"
+msgstr ""
+
+#: ../shell/view/clipboardmenu.py:58
+msgid "Remove"
+msgstr ""
+
+#: ../shell/view/clipboardmenu.py:63
+msgid "Open"
+msgstr ""
+
+#. self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+#. TODO: Implement stopping downloads
+#. self._stop_item.connect('activate', self._stop_item_activate_cb)
+#. self.append_menu_item(self._stop_item)
+#: ../shell/view/clipboardmenu.py:73
+msgid "Add to journal"
+msgstr ""
+
+#: ../shell/view/clipboardmenu.py:213
+#, python-format
+msgid "Clipboard object: %s."
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:149
+msgid "Key Type:"
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:169
+msgid "Authentication Type:"
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:250
+msgid "Encryption Type:"
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:90
+msgid "Starting..."
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:104 ../shell/view/home/MeshBox.py:295
+msgid "Resume"
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:111
+#: ../lib/sugar/activity/activity.py:132
+msgid "Stop"
+msgstr ""
+
+#: ../shell/view/Shell.py:285
+msgid "Screenshot"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:159
+msgid "Reboot"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:164
+msgid "Shutdown"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:170
+msgid "Register"
+msgstr ""
+
+#. Only show disconnect when there's a mesh device, because mesh takes
+#. priority over the normal wireless device. NM doesn't have a "disconnect"
+#. method for a device either (for various reasons) so this doesn't
+#. have a good mapping
+#: ../shell/view/home/MeshBox.py:90 ../shell/view/home/MeshBox.py:197
+#: ../shell/view/devices/network/wireless.py:113
+#: ../shell/view/devices/network/mesh.py:83
+msgid "Disconnect..."
+msgstr ""
+
+#: ../shell/view/home/MeshBox.py:195 ../shell/view/devices/network/mesh.py:37
+#: ../shell/view/devices/network/mesh.py:62
+#: ../shell/view/devices/network/mesh.py:66
+msgid "Mesh Network"
+msgstr ""
+
+#: ../shell/view/home/MeshBox.py:300
+msgid "Join"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:38
+msgid "My Battery life"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:94
+msgid "Battery charging"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:96
+msgid "Battery discharging"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:98
+msgid "Battery fully charged"
+msgstr ""
+
+#: ../shell/view/devices/network/wireless.py:61
+msgid "Disconnected"
+msgstr ""
+
+#: ../shell/view/devices/network/wireless.py:131
+msgid "Channel"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:42
+msgid "Neighborhood"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:54
+msgid "Group"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:66
+msgid "Home"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:78
+msgid "Activity"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:115
+msgid "Share with:"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:117
+msgid "Private"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:118
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:126
+msgid "Keep"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:245
+msgid "Undo"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:250
+msgid "Redo"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:260
+msgid "Copy"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:265
+msgid "Paste"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:454
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:824
+msgid "Keep error"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:825
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:828
+msgid "Don't stop"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:831
+msgid "Stop anyway"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:164 ../lib/sugar/graphics/alert.py:206
+msgid "Cancel"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:168
+msgid "Ok"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:216
+msgid "Continue"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:244
+msgid "OK"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d year"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d years"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d month"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d months"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d week"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d weeks"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d day"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d days"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hour"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hours"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minute"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minutes"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d second"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d seconds"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:191
+msgid " and "
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:193
+msgid ", "
+msgstr ""
+
+#: ../shell/controlpanel/control.py:213
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:267
+msgid "Error in specified color modifiers."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:270
+msgid "Error in specified colors."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:307
+msgid "off"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:309
+msgid "on"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:310
+msgid "State is unknown."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:332
+msgid "Error in specified radio argument use on/off."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:336
+msgid "Permission denied. You need to be root to run this method."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:366
+msgid "Error in reading timezone"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:397
+#, python-format
+msgid "Error copying timezone (from %s): %s"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:402
+#, python-format
+msgid "Changing permission of timezone: %s"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:412
+msgid "Error timezone does not exist."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:417 ../shell/controlpanel/control.py:436
+#, python-format
+msgid "Could not access %s. Create standard settings."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:463
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:473
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:105
+msgid "Connected to a School Mesh Portal"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:107
+msgid "Looking for a School Mesh Portal..."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:110
+msgid "Connected to an XO Mesh Portal"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:112
+msgid "Looking for an XO Mesh Portal..."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:115
+msgid "Connected to a Simple Mesh"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:117
+msgid "Starting a Simple Mesh"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:124
+msgid "Unknown Mesh"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:175 ../shell/view/home/HomeBox.py:216
+msgid "About this XO"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:222
+msgid "Not available"
+msgstr ""
diff --git a/shell/po/he.po b/shell/po/he.po
new file mode 100644
index 0000000..63d4e34
--- /dev/null
+++ b/shell/po/he.po
@@ -0,0 +1,764 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-09-25 00:30-0400\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/intro/window.py:93 ../src/controlpanel/aboutme/view.py:100
+msgid "Name:"
+msgstr ""
+
+#: ../src/intro/window.py:125
+msgid "Click to change color:"
+msgstr ""
+
+#: ../src/intro/window.py:175 ../src/journal/detailview.py:119
+msgid "Back"
+msgstr ""
+
+#: ../src/intro/window.py:189 ../src/controlpanel/toolbar.py:61
+msgid "Done"
+msgstr ""
+
+#: ../src/intro/window.py:192
+msgid "Next"
+msgstr ""
+
+#: ../src/view/BuddyMenu.py:60
+msgid "Remove friend"
+msgstr ""
+
+#: ../src/view/BuddyMenu.py:63
+msgid "Make friend"
+msgstr ""
+
+#: ../src/view/BuddyMenu.py:92
+#, python-format
+msgid "Invite to %s"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:51
+msgid "Remove"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:56 ../src/view/clipboardmenu.py:78
+msgid "Open"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:61 ../src/view/home/HomeBox.py:84
+msgid "Keep"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:83
+msgid "Open with"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:228
+#, python-format
+msgid "%s clipping"
+msgstr ""
+
+#: ../src/hardware/keydialog.py:150
+msgid "Key Type:"
+msgstr ""
+
+#: ../src/hardware/keydialog.py:170
+msgid "Authentication Type:"
+msgstr ""
+
+#: ../src/hardware/keydialog.py:251
+msgid "Encryption Type:"
+msgstr ""
+
+#: ../src/hardware/schoolserver.py:17
+msgid "Cannot obtain data needed for registration."
+msgstr ""
+
+#: ../src/hardware/schoolserver.py:31
+msgid "Cannot connect to the server."
+msgstr ""
+
+#: ../src/hardware/schoolserver.py:36
+msgid "The server could not complete the request."
+msgstr ""
+
+#: ../src/view/Shell.py:251
+msgid "Screenshot"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:78
+msgid "Confirm erase"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:80
+#, python-format
+msgid "Confirm erase: Do you want to permanently erase %s?"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:87 ../src/view/palettes.py:120
+#: ../src/journal/journaltoolbox.py:335 ../src/journal/palettes.py:75
+msgid "Erase"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:117
+msgid "Software Update"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:118
+msgid "Update your activities to ensure compatibility with your new software"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:122 ../src/controlpanel/toolbar.py:115
+msgid "Cancel"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:124 ../src/controlpanel/gui.py:273
+msgid "Later"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:127
+msgid "Check now"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:261
+msgid "List view"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:262
+msgid "<Ctrl>2"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:320
+msgid "Favorites view"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:321
+msgid "<Ctrl>1"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:97
+msgid "Connect"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:106
+msgid "Disconnect"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:118
+msgid "Disconnecting..."
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:159
+msgid "Connecting..."
+msgstr ""
+
+#. TODO: show the channel number
+#: ../src/view/home/MeshBox.py:166
+msgid "Connected"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:218 ../src/view/devices/network/mesh.py:41
+#: ../src/view/devices/network/mesh.py:68
+#: ../src/view/devices/network/mesh.py:72
+msgid "Mesh Network"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:221 ../src/view/devices/network/wireless.py:125
+#: ../src/view/devices/network/mesh.py:89
+msgid "Disconnect..."
+msgstr ""
+
+#. TRANS: Action label for resuming an activity.
+#: ../src/view/home/MeshBox.py:309 ../src/view/palettes.py:61
+#: ../src/journal/journaltoolbox.py:399 ../src/journal/palettes.py:57
+msgid "Resume"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:314 ../src/view/frame/activitiestray.py:206
+msgid "Join"
+msgstr ""
+
+#: ../src/view/devices/battery.py:45
+msgid "My Battery"
+msgstr ""
+
+#: ../src/view/devices/battery.py:114
+msgid "Charging"
+msgstr ""
+
+#: ../src/view/devices/battery.py:117
+msgid "Very little power remaining"
+msgstr ""
+
+#: ../src/view/devices/battery.py:123
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr ""
+
+#: ../src/view/devices/battery.py:127
+msgid "Charged"
+msgstr ""
+
+#: ../src/view/devices/speaker.py:44
+msgid "My Speakers"
+msgstr ""
+
+#: ../src/view/devices/speaker.py:125
+msgid "Unmute"
+msgstr ""
+
+#: ../src/view/devices/speaker.py:128
+msgid "Mute"
+msgstr ""
+
+#: ../src/view/devices/network/wireless.py:67
+msgid "Disconnected"
+msgstr ""
+
+#: ../src/view/devices/network/wireless.py:143
+msgid "Channel"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:37
+msgid "Neighborhood"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:39
+msgid "Group"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:41
+msgid "Home"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:43
+msgid "Activity"
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:26
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:28
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:29
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr ""
+
+#. TRANS: Translators, there's a empty line at the end of this string,
+#. which must appear in the translated string (msgstr) as well.
+#: ../src/controlpanel/cmd.py:35
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" -c key clear the current value for the key \n"
+" "
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:48
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr ""
+
+#: ../src/controlpanel/toolbar.py:121 ../src/view/home/favoritesview.py:305
+msgid "Ok"
+msgstr ""
+
+#: ../src/controlpanel/sectionview.py:42 ../src/controlpanel/gui.py:265
+msgid "Changes require restart"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:264
+msgid "Warning"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:268
+msgid "Cancel changes"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:277
+msgid "Restart now"
+msgstr ""
+
+#: ../src/controlpanel/aboutme/model.py:44
+msgid "You must enter a name."
+msgstr ""
+
+#: ../src/controlpanel/aboutme/model.py:69
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr ""
+
+#: ../src/controlpanel/aboutme/model.py:72
+#, python-format
+msgid "stroke: %s"
+msgstr ""
+
+#: ../src/controlpanel/aboutme/model.py:74
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr ""
+
+#: ../src/controlpanel/aboutme/model.py:76
+#, python-format
+msgid "fill: %s"
+msgstr ""
+
+#: ../src/controlpanel/aboutme/model.py:87
+msgid "Error in specified color modifiers."
+msgstr ""
+
+#: ../src/controlpanel/aboutme/model.py:90
+msgid "Error in specified colors."
+msgstr ""
+
+#: ../src/controlpanel/aboutme/view.py:32
+#: ../src/controlpanel/aboutme/__init__.py:22
+msgid "About Me"
+msgstr ""
+
+#: ../src/controlpanel/aboutme/view.py:134
+msgid "Click to change your color:"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/model.py:24
+msgid "Not available"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:55
+msgid "Identity"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:64
+msgid "Serial Number:"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:87
+msgid "Software"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:96
+msgid "Build:"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:111
+msgid "Sugar:"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:126
+msgid "Firmware:"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:148
+msgid "Copyright and License"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:156
+msgid ""
+"© 2008 One Laptop per Child Association Inc; Red Hat Inc; and Contributors."
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:163
+msgid ""
+"Sugar is the graphical user interface that you are looking at. Sugar is free "
+"software, covered by the GNU General Public License, and you are welcome to "
+"change it and/or distribute copies of it under certain conditions described "
+"therein."
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:175
+msgid "Full license:"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/__init__.py:21
+msgid "About my XO"
+msgstr ""
+
+#: ../src/controlpanel/datetime/model.py:89
+msgid "Error timezone does not exist."
+msgstr ""
+
+#: ../src/controlpanel/datetime/view.py:68
+msgid "Timezone"
+msgstr ""
+
+#: ../src/controlpanel/datetime/__init__.py:21
+msgid "Date & Time"
+msgstr ""
+
+#: ../src/controlpanel/frame/model.py:38 ../src/controlpanel/frame/model.py:60
+msgid "Value must be an integer."
+msgstr ""
+
+#: ../src/controlpanel/frame/view.py:26
+msgid "never"
+msgstr ""
+
+#: ../src/controlpanel/frame/view.py:27
+msgid "instantaneous"
+msgstr ""
+
+#: ../src/controlpanel/frame/view.py:28
+#, python-format
+msgid "%s seconds"
+msgstr ""
+
+#: ../src/controlpanel/frame/view.py:52
+msgid "Activation Delay"
+msgstr ""
+
+#: ../src/controlpanel/frame/view.py:76
+msgid "Corner"
+msgstr ""
+
+#: ../src/controlpanel/frame/view.py:111
+msgid "Edge"
+msgstr ""
+
+#: ../src/controlpanel/frame/__init__.py:21
+msgid "Frame"
+msgstr ""
+
+#: ../src/controlpanel/language/model.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr ""
+
+#: ../src/controlpanel/language/model.py:114
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr ""
+
+#: ../src/controlpanel/language/model.py:131
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr ""
+
+#: ../src/controlpanel/language/view.py:70
+#: ../src/controlpanel/language/__init__.py:21
+msgid "Language"
+msgstr ""
+
+#: ../src/controlpanel/network/model.py:62
+msgid "State is unknown."
+msgstr ""
+
+#: ../src/controlpanel/network/model.py:82
+msgid "Error in specified radio argument use on/off."
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:28
+#: ../src/controlpanel/network/__init__.py:21
+msgid "Network"
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:54
+msgid "Wireless"
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:62
+msgid "Turn of the wireless radio to save battery life"
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:75
+msgid "Radio"
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:91
+msgid "Discard network history if you have trouble connecting to the network"
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:100
+msgid "Discard network history"
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:113
+msgid "Mesh"
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:122
+msgid "Server:"
+msgstr ""
+
+#: ../src/controlpanel/power/model.py:55
+msgid "Error in automatic pm argument, use on/off."
+msgstr ""
+
+#: ../src/controlpanel/power/model.py:84
+msgid "Error in extreme pm argument, use on/off."
+msgstr ""
+
+#: ../src/controlpanel/power/view.py:47
+msgid "Power management"
+msgstr ""
+
+#: ../src/controlpanel/power/view.py:57
+msgid "Automatic power management (increases battery life)"
+msgstr ""
+
+#: ../src/controlpanel/power/view.py:85
+msgid ""
+"Extreme power management (disableswireless radio, increases battery life)"
+msgstr ""
+
+#: ../src/controlpanel/power/__init__.py:21
+msgid "Power"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:111
+msgid "Connected to a School Mesh Portal"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:113
+msgid "Looking for a School Mesh Portal..."
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:116
+msgid "Connected to an XO Mesh Portal"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:118
+msgid "Looking for an XO Mesh Portal..."
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:121
+msgid "Connected to a Simple Mesh"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:123
+msgid "Starting a Simple Mesh"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:130
+msgid "Unknown Mesh"
+msgstr ""
+
+#: ../src/view/frame/activitiestray.py:211
+msgid "Decline"
+msgstr ""
+
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/view/home/favoriteslayout.py:107
+msgid "Freeform"
+msgstr ""
+
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/view/home/favoriteslayout.py:189
+msgid "Ring"
+msgstr ""
+
+#. TRANS: label for the spiral layout in the favorites view
+#: ../src/view/home/favoriteslayout.py:334
+msgid "Spiral"
+msgstr ""
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/view/home/favoriteslayout.py:401
+msgid "Box"
+msgstr ""
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/view/home/favoriteslayout.py:442
+msgid "Triangle"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:295
+msgid "Registration Failed"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:296
+#, python-format
+msgid "%s"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:298
+msgid "Registration Successful"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:299
+msgid "You are now registered with your school server."
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:420
+msgid "Settings"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:425
+msgid "Restart"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:430
+msgid "Shutdown"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:436
+msgid "Register"
+msgstr ""
+
+#: ../src/view/palettes.py:42
+msgid "Starting..."
+msgstr ""
+
+#: ../src/view/palettes.py:72
+msgid "Stop"
+msgstr ""
+
+#. TRANS: Action label for starting an entry.
+#: ../src/view/palettes.py:104 ../src/journal/journaltoolbox.py:402
+#: ../src/journal/palettes.py:59
+msgid "Start"
+msgstr ""
+
+#: ../src/view/palettes.py:138
+msgid "Remove favorite"
+msgstr ""
+
+#: ../src/view/palettes.py:142
+msgid "Make favorite"
+msgstr ""
+
+#: ../src/view/palettes.py:191
+msgid "Show contents"
+msgstr ""
+
+#: ../src/view/palettes.py:215
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr ""
+
+#: ../src/journal/journaltoolbox.py:62
+msgid "Search"
+msgstr ""
+
+#: ../src/journal/journaltoolbox.py:119
+msgid "Anytime"
+msgstr ""
+
+#: ../src/journal/journaltoolbox.py:121
+msgid "Today"
+msgstr ""
+
+#: ../src/journal/journaltoolbox.py:123
+msgid "Since yesterday"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 7 days.
+#: ../src/journal/journaltoolbox.py:125
+msgid "Past week"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 30 days.
+#: ../src/journal/journaltoolbox.py:127
+msgid "Past month"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 356 days.
+#: ../src/journal/journaltoolbox.py:129
+msgid "Past year"
+msgstr ""
+
+#: ../src/journal/journaltoolbox.py:136
+msgid "Anyone"
+msgstr ""
+
+#: ../src/journal/journaltoolbox.py:138
+msgid "My friends"
+msgstr ""
+
+#: ../src/journal/journaltoolbox.py:139
+msgid "My class"
+msgstr ""
+
+#. TRANS: Item in a combo box that filters by entry type.
+#: ../src/journal/journaltoolbox.py:255
+msgid "Anything"
+msgstr ""
+
+#. TODO: Add "Start with" menu item
+#: ../src/journal/journaltoolbox.py:325 ../src/journal/palettes.py:67
+msgid "Copy"
+msgstr ""
+
+#: ../src/journal/collapsedentry.py:248 ../src/journal/expandedentry.py:176
+#: ../src/journal/palettes.py:51
+msgid "Untitled"
+msgstr ""
+
+#: ../src/journal/journalactivity.py:119 ../src/journal/volumesmanager.py:57
+msgid "Journal"
+msgstr ""
+
+#: ../src/journal/expandedentry.py:222
+msgid "No preview"
+msgstr ""
+
+#: ../src/journal/expandedentry.py:241
+msgid "Participants:"
+msgstr ""
+
+#: ../src/journal/expandedentry.py:266
+msgid "Description:"
+msgstr ""
+
+#: ../src/journal/expandedentry.py:292
+msgid "Tags:"
+msgstr ""
+
+#: ../src/journal/objectchooser.py:134
+msgid "Choose an object"
+msgstr ""
+
+#: ../src/journal/objectchooser.py:139
+msgid "Close"
+msgstr ""
+
+#: ../src/journal/volumestoolbar.py:93
+msgid "Unmount"
+msgstr ""
+
+#: ../src/journal/misc.py:95
+msgid "No date"
+msgstr ""
+
+#: ../src/journal/listview.py:39
+msgid "Your Journal is empty"
+msgstr ""
+
+#: ../src/journal/listview.py:40
+msgid "No matching entries "
+msgstr ""
+
+#: ../src/journal/modalalert.py:59
+msgid "Your Journal is full"
+msgstr ""
+
+#: ../src/journal/modalalert.py:63
+msgid "Please delete some old Journal entries to make space for new ones."
+msgstr ""
+
+#: ../src/journal/modalalert.py:75
+msgid "Show Journal"
+msgstr ""
diff --git a/shell/po/hi.po b/shell/po/hi.po
new file mode 100644
index 0000000..522ddc9
--- /dev/null
+++ b/shell/po/hi.po
@@ -0,0 +1,1416 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-02-11 00:32-0500\n"
+"PO-Revision-Date: 2008-04-11 03:32-0400\n"
+"Last-Translator: Prashant Thakkar <prashantbthakkar@gmail.com>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Pootle 1.1.0rc2\n"
+
+#: ../extensions/cpsection/aboutme/__init__.py:24
+msgid "About Me"
+msgstr "मेरे बारे में"
+
+#: ../extensions/cpsection/aboutme/model.py:43
+msgid "You must enter a name."
+msgstr "आपको एक नाम प्रविष्ट करना होगा."
+
+#: ../extensions/cpsection/aboutme/model.py:68
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr "स्ट्रोक: रंग=%s वर्ण=%s"
+
+#: ../extensions/cpsection/aboutme/model.py:71
+#, python-format
+msgid "stroke: %s"
+msgstr "स्ट्रोक: %s"
+
+#: ../extensions/cpsection/aboutme/model.py:73
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr "भरें: रंग=%s वर्ण=%s"
+
+#: ../extensions/cpsection/aboutme/model.py:75
+#, python-format
+msgid "fill: %s"
+msgstr "भरें: %s"
+
+#: ../extensions/cpsection/aboutme/model.py:86
+msgid "Error in specified color modifiers."
+msgstr " दिए गये रंगरूपांतर के वर्णन मे ग़लती है"
+
+#: ../extensions/cpsection/aboutme/model.py:89
+msgid "Error in specified colors."
+msgstr " दिए गये रंगो के वर्णन मे ग़लती है"
+
+#: ../extensions/cpsection/aboutme/view.py:94 ../src/jarabe/intro/window.py:93
+msgid "Name:"
+msgstr "नाम"
+
+#: ../extensions/cpsection/aboutme/view.py:128
+msgid "Click to change your color:"
+msgstr "रंग बदलने हेतु क्लिक करें:"
+
+#: ../extensions/cpsection/aboutcomputer/__init__.py:21
+msgid "About my Computer"
+msgstr "मेरे कम्प्यूटर के बारे में"
+
+#: ../extensions/cpsection/aboutcomputer/model.py:28
+msgid "Not available"
+msgstr "उपलब्ध नही है"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:60
+msgid "Identity"
+msgstr "पहचान"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:69
+msgid "Serial Number:"
+msgstr "सरल क्रमांक:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:91
+msgid "Software"
+msgstr "सॉफ्टवेयर"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:100
+msgid "Build:"
+msgstr "बिल्ड:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:115
+msgid "Sugar:"
+msgstr "सुगर:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:131
+msgid "Firmware:"
+msgstr "फर्मवेयर:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:146
+msgid "Wireless Firmware:"
+msgstr "वायरलेस फर्मवेयर:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:169
+msgid "Copyright and License"
+msgstr "सर्वाधिकार तथा लाइसेंस"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:184
+msgid ""
+"Sugar is the graphical user interface that you are looking at. Sugar is free "
+"software, covered by the GNU General Public License, and you are welcome to "
+"change it and/or distribute copies of it under certain conditions described "
+"therein."
+msgstr ""
+"आप जिसे देख रहे हैं वो ग्राफ़िकल यूजर इंटरफेस - सुगर है. सुगर मुफ़्त "
+"सॉफ़्टवेयर है, और इसे ग्नू - जनरल पब्लिक लाइसेंस के तहत जारी किया गया है, और "
+"लाइसेंस में दिए गए कुछ विशेष शर्तों के अधीन आप इसमें परिवर्तन करने या इसकी "
+"प्रति बनाकर वितरण करने के लिए आपका स्वागत है."
+
+#: ../extensions/cpsection/aboutcomputer/view.py:196
+msgid "Full license:"
+msgstr "पूरा लाइसेंस:"
+
+#: ../extensions/cpsection/datetime/__init__.py:21
+msgid "Date & Time"
+msgstr "तारीख़ व समय"
+
+#: ../extensions/cpsection/datetime/model.py:87
+msgid "Error timezone does not exist."
+msgstr "गलती: समयक्षेत्र मौजूद नही है"
+
+#: ../extensions/cpsection/datetime/view.py:68 ../data/sugar.schemas.in.h:33
+msgid "Timezone"
+msgstr "समयक्षेत्र"
+
+#: ../extensions/cpsection/frame/__init__.py:21
+msgid "Frame"
+msgstr "ढांचा"
+
+#: ../extensions/cpsection/frame/model.py:38
+#: ../extensions/cpsection/frame/model.py:60
+msgid "Value must be an integer."
+msgstr "मूल्य पूर्णांक में होना चाहिए."
+
+#: ../extensions/cpsection/frame/view.py:26
+msgid "never"
+msgstr "कभी नहीं"
+
+#: ../extensions/cpsection/frame/view.py:27
+msgid "instantaneous"
+msgstr "तत्काल"
+
+#: ../extensions/cpsection/frame/view.py:28
+#, python-format
+msgid "%s seconds"
+msgstr "%s सेकंड"
+
+#: ../extensions/cpsection/frame/view.py:52
+msgid "Activation Delay"
+msgstr "सक्रियण देरी"
+
+#: ../extensions/cpsection/frame/view.py:76
+msgid "Corner"
+msgstr "किनारा"
+
+#: ../extensions/cpsection/frame/view.py:111
+msgid "Edge"
+msgstr "किनारा"
+
+#: ../extensions/cpsection/keyboard/__init__.py:21
+#: ../extensions/cpsection/keyboard/view.py:31
+msgid "Keyboard"
+msgstr "कुंजीपट"
+
+#: ../extensions/cpsection/keyboard/view.py:189
+msgid "Keyboard Model"
+msgstr "कुंजीपट मॉडल"
+
+#: ../extensions/cpsection/keyboard/view.py:248
+msgid "Key(s) to change layout"
+msgstr "खाका बदलने के लिए कुंजियाँ"
+
+#: ../extensions/cpsection/keyboard/view.py:318
+msgid "Keyboard Layout(s)"
+msgstr "कुंजीपट खाका"
+
+#: ../extensions/cpsection/language/__init__.py:21
+#: ../extensions/cpsection/language/view.py:33
+msgid "Language"
+msgstr "भाषा"
+
+#: ../extensions/cpsection/language/model.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr " ~/.i18n पर पहुँच नहीं सका. मानक सेटिंग बनाएँ"
+
+#: ../extensions/cpsection/language/model.py:124
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr "कूट की भाषा = %s अग्यात है"
+
+#: ../extensions/cpsection/language/model.py:144
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr "माफ़ कीजिए , मैं %s नही बोलता"
+
+#: ../extensions/cpsection/language/view.py:56
+msgid ""
+"Add languages in the order you prefer. If a translation is not available, "
+"the next in the list will be used."
+msgstr ""
+"जिस अनुक्रम में आप चाहते हैं, उसमें भाषाएँ जोड़ें. यदि अनुवाद उपलब्ध नहीं "
+"होगा, तो सूची में दिए गए अगले वाले को प्रयोग में लिया जाएगा."
+
+#: ../extensions/cpsection/modemconfiguration/__init__.py:21
+msgid "Modem Configuration"
+msgstr ""
+
+#: ../extensions/cpsection/modemconfiguration/view.py:90
+msgid "Username:"
+msgstr ""
+
+#: ../extensions/cpsection/modemconfiguration/view.py:101
+msgid "Password:"
+msgstr ""
+
+#: ../extensions/cpsection/modemconfiguration/view.py:112
+msgid "Number:"
+msgstr ""
+
+#: ../extensions/cpsection/modemconfiguration/view.py:123
+msgid "APN:"
+msgstr ""
+
+#: ../extensions/cpsection/network/__init__.py:21
+#: ../extensions/cpsection/network/view.py:28
+msgid "Network"
+msgstr "नेटवर्क"
+
+#: ../extensions/cpsection/network/model.py:79
+msgid "State is unknown."
+msgstr "स्थिति अज्ञात है"
+
+#: ../extensions/cpsection/network/model.py:105
+msgid "Error in specified radio argument use on/off."
+msgstr "दिए गये रेडियो तर्क मे ग़लती है ,बंद/चालू का वापर करे"
+
+#: ../extensions/cpsection/network/model.py:137
+msgid "Error in specified argument use 0/1."
+msgstr "निर्दिष्ट तर्क प्रयोग 0/1 में त्रुटि है."
+
+#: ../extensions/cpsection/network/view.py:59
+msgid "Wireless"
+msgstr "वायरलेस"
+
+#: ../extensions/cpsection/network/view.py:67
+msgid "Turn off the wireless radio to save battery life"
+msgstr "बैटरी का जीवन बढ़ाने के लिए वायरलेस रेडियो को बन्द कर दें"
+
+#: ../extensions/cpsection/network/view.py:80
+msgid "Radio"
+msgstr "रेडियो"
+
+#: ../extensions/cpsection/network/view.py:96
+msgid "Discard network history if you have trouble connecting to the network"
+msgstr ""
+"यदि आपको नेटवर्क में कनेक्ट करने में समस्या हो रही है तो नेटवर्क इतिहास को "
+"मिटा दें."
+
+#: ../extensions/cpsection/network/view.py:105
+msgid "Discard network history"
+msgstr "नेटवर्क इतिहास मिटाएँ"
+
+#: ../extensions/cpsection/network/view.py:118
+msgid "Collaboration"
+msgstr "साझेदारी"
+
+#: ../extensions/cpsection/network/view.py:126
+msgid ""
+"The server is the equivalent of what room you are in; people on the same "
+"server will be able to see each other, even when they aren't on the same "
+"network."
+msgstr ""
+"सर्वर को आपके कमरे के रुप में, जहाँ आप हैं, माना जा सकता है. एक ही सर्वर के "
+"लोग आपस में एक दूसरे को देख सकेंगे, भले ही वो एक ही नेटवर्क में न हों."
+
+#: ../extensions/cpsection/network/view.py:136
+msgid "Server:"
+msgstr "सर्वर:"
+
+#: ../extensions/cpsection/power/__init__.py:21
+msgid "Power"
+msgstr "पावर"
+
+#: ../extensions/cpsection/power/model.py:54
+msgid "Error in automatic pm argument, use on/off."
+msgstr "स्वचालित पीएम तर्क में त्रुटि है, चालू/बन्द प्रयोग करें."
+
+#: ../extensions/cpsection/power/model.py:81
+msgid "Error in extreme pm argument, use on/off."
+msgstr "एक्सट्रीम पीएम तर्क में त्रुटि है, चालू/बन्द प्रयोग करें."
+
+#: ../extensions/cpsection/power/view.py:47
+msgid "Power management"
+msgstr "बिज़ली प्रबंधन"
+
+#: ../extensions/cpsection/power/view.py:57
+msgid "Automatic power management (increases battery life)"
+msgstr "स्वचालित बिजली प्रबंधन (बैटरी का जीवन बढ़ाता है)"
+
+#: ../extensions/cpsection/power/view.py:85
+msgid ""
+"Extreme power management (disableswireless radio, increases battery life)"
+msgstr ""
+"एक्सट्रीम ऊर्जा प्रबंधन (वायरलेस रेडियो को बन्द करता है, बैटरी का जीवन "
+"बढ़ाने के लिए)"
+
+#: ../extensions/cpsection/updater/__init__.py:21
+msgid "Software update"
+msgstr "सॉफ़्टवेयर अद्यतन"
+
+#: ../extensions/cpsection/updater/view.py:63
+msgid ""
+"Software updates correct errors, eliminate security vulnerabilities, and "
+"provide new features."
+msgstr ""
+"सॉफ़्टवेयर अपडेट में त्रुटियाँ दूर होती हैं, सुरक्षा समस्याओं से छुटकारा "
+"मिलता है तथा नई विशेषताएँ मिलती हैं."
+
+#: ../extensions/cpsection/updater/view.py:125
+#, python-format
+msgid "Checking %s..."
+msgstr "%s जाँचा जा रहा है..."
+
+#: ../extensions/cpsection/updater/view.py:127
+#, python-format
+msgid "Downloading %s..."
+msgstr "%s डाउनलोड किया जा रहा है..."
+
+#: ../extensions/cpsection/updater/view.py:129
+#, python-format
+msgid "Updating %s..."
+msgstr "%s अद्यतन किया जा रहा है..."
+
+#: ../extensions/cpsection/updater/view.py:139
+msgid "Your software is up-to-date"
+msgstr "आपका सॉफ़्टवेयर अद्यतन है"
+
+#: ../extensions/cpsection/updater/view.py:141
+#, python-format
+msgid "You can install %s update"
+msgid_plural "You can install %s updates"
+msgstr[0] "आप %s अद्यतन संस्थापित कर सकते हैं"
+msgstr[1] "आप %s अद्यतन संस्थापित कर सकते हैं"
+
+#: ../extensions/cpsection/updater/view.py:159
+msgid "Checking for updates..."
+msgstr "अद्यतन के लिए जाँचा जा रहा है..."
+
+#: ../extensions/cpsection/updater/view.py:164
+msgid "Installing updates..."
+msgstr "अद्यतनों को संस्थापित किया जा रहा है..."
+
+#: ../extensions/cpsection/updater/view.py:172
+#, python-format
+msgid "%s update was installed"
+msgid_plural "%s updates were installed"
+msgstr[0] "%s अद्यतन संस्थापित किया गया"
+msgstr[1] "%s अद्यतनों को संस्थापित किया गया"
+
+#: ../extensions/cpsection/updater/view.py:253
+msgid "Install selected"
+msgstr "चयनित को संस्थापित करें"
+
+#: ../extensions/cpsection/updater/view.py:274
+#, python-format
+msgid "Download size: %s"
+msgstr "डाउनलोड आकार: %s"
+
+#: ../extensions/cpsection/updater/view.py:362
+#, python-format
+msgid "From version %(current)d to %(new)s (Size: %(size)s)"
+msgstr "संस्करण %(current)d से %(new)s (आकार: %(size)s)"
+
+#. TRANS: download size is 0
+#: ../extensions/cpsection/updater/view.py:382
+msgid "None"
+msgstr "कुछ नहीं"
+
+#. TRANS: download size of very small updates
+#: ../extensions/cpsection/updater/view.py:385
+msgid "1 KB"
+msgstr "1कि.बा."
+
+#. TRANS: download size of small updates, e.g. '250 KB'
+#: ../extensions/cpsection/updater/view.py:388
+#, python-format
+msgid "%.0f KB"
+msgstr "%.0f कि.बा."
+
+#. TRANS: download size of updates, e.g. '2.3 MB'
+#: ../extensions/cpsection/updater/view.py:391
+#, python-format
+msgid "%.1f MB"
+msgstr "%.1f मे.बा."
+
+#: ../extensions/deviceicon/battery.py:58
+msgid "My Battery"
+msgstr "मेरी बैटरी"
+
+#: ../extensions/deviceicon/battery.py:137
+msgid "Removed"
+msgstr "हटा दिया गया"
+
+#: ../extensions/deviceicon/battery.py:140
+msgid "Charging"
+msgstr "चार्जिंग में"
+
+#: ../extensions/deviceicon/battery.py:143
+msgid "Very little power remaining"
+msgstr "बहुत कम पावर बचा है"
+
+#: ../extensions/deviceicon/battery.py:149
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr "%(hour)d:%(min).2d बाकी"
+
+#: ../extensions/deviceicon/battery.py:152
+msgid "Charged"
+msgstr "चार्ज है"
+
+#: ../extensions/deviceicon/network.py:49
+#, python-format
+msgid "IP address: %s"
+msgstr "आईपी पता: %s"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#: ../extensions/deviceicon/network.py:111
+msgid "Disconnect..."
+msgstr " सम्पर्क तोडे"
+
+#: ../extensions/deviceicon/network.py:116
+msgid "Create new wireless network"
+msgstr "नया वायरलेस नेटवर्क बनाएँ"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#: ../extensions/deviceicon/network.py:122
+#: ../extensions/deviceicon/network.py:284
+#: ../src/jarabe/desktop/meshbox.py:248 ../src/jarabe/desktop/meshbox.py:537
+msgid "Connecting..."
+msgstr "कनेक्ट हो रहा है..."
+
+#: ../extensions/deviceicon/network.py:126
+#: ../extensions/deviceicon/network.py:198
+#: ../extensions/deviceicon/network.py:288
+#: ../src/jarabe/desktop/meshbox.py:254 ../src/jarabe/desktop/meshbox.py:543
+msgid "Connected"
+msgstr "कनेक्टेड"
+
+#: ../extensions/deviceicon/network.py:158
+msgid "Channel"
+msgstr "माध्यम"
+
+#: ../extensions/deviceicon/network.py:173
+msgid "Wired Network"
+msgstr "वायर्ड नेटवर्क"
+
+#: ../extensions/deviceicon/network.py:201
+msgid "Speed"
+msgstr "गति"
+
+#: ../extensions/deviceicon/network.py:228
+msgid "Wireless modem"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:276
+msgid "Please wait..."
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:279
+#: ../src/jarabe/desktop/meshbox.py:164 ../src/jarabe/desktop/meshbox.py:494
+msgid "Connect"
+msgstr "कनेक्ट"
+
+#: ../extensions/deviceicon/network.py:280
+msgid "Disconnected"
+msgstr "सम्पर्क टूट गया"
+
+#: ../extensions/deviceicon/network.py:283
+#: ../src/jarabe/controlpanel/toolbar.py:115
+#: ../src/jarabe/desktop/homebox.py:68
+#: ../src/jarabe/frame/activitiestray.py:700
+#: ../src/jarabe/frame/activitiestray.py:799
+#: ../src/jarabe/frame/activitiestray.py:827
+msgid "Cancel"
+msgstr "रद्द करें"
+
+#: ../extensions/deviceicon/network.py:287
+#: ../src/jarabe/desktop/meshbox.py:168
+msgid "Disconnect"
+msgstr "डिस्कनेक्ट"
+
+#: ../extensions/deviceicon/network.py:530
+#, python-format
+#, fuzzy
+msgid "%s's network"
+msgstr "%s का नेटवर्क %s"
+
+#: ../extensions/deviceicon/network.py:597
+#: ../extensions/deviceicon/network.py:656
+msgid "Mesh Network"
+msgstr "मेश नेटवर्क "
+
+#: ../extensions/deviceicon/network.py:857
+#, python-format
+msgid "Data sent %d kb / received %d kb"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:868
+msgid "Connection time "
+msgstr ""
+
+#: ../extensions/deviceicon/speaker.py:59
+msgid "My Speakers"
+msgstr "मेरे स्पीकर"
+
+#: ../extensions/deviceicon/speaker.py:133
+msgid "Unmute"
+msgstr "मौन हटाएँ"
+
+#: ../extensions/deviceicon/speaker.py:136
+msgid "Mute"
+msgstr "मौन"
+
+#: ../extensions/globalkey/screenshot.py:59
+msgid "Mesh"
+msgstr "जाल"
+
+#: ../extensions/globalkey/screenshot.py:61
+#: ../src/jarabe/frame/zoomtoolbar.py:39
+msgid "Group"
+msgstr "समूह"
+
+#: ../extensions/globalkey/screenshot.py:63
+#: ../src/jarabe/frame/zoomtoolbar.py:41
+msgid "Home"
+msgstr "घर"
+
+#: ../extensions/globalkey/screenshot.py:69
+#: ../src/jarabe/frame/zoomtoolbar.py:43
+msgid "Activity"
+msgstr "क्रिया"
+
+#: ../extensions/globalkey/screenshot.py:72
+msgid "Screenshot"
+msgstr "पटचित्र"
+
+#: ../extensions/globalkey/screenshot.py:74
+#, python-format
+msgid "Screenshot of \"%s\""
+msgstr "\"%s\" का स्क्रीनशॉट"
+
+#: ../data/sugar.schemas.in.h:1
+msgid ""
+"\"disabled\" to ask nick on initialization; \"system\" to reuse UNIX account "
+"long name."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:2
+msgid "Backup URL"
+msgstr "बैकअप यूआरएल"
+
+#: ../data/sugar.schemas.in.h:3
+msgid ""
+"Color for the XO icon that is used throughout the desktop. The string is "
+"composed of the stroke color and fill color, format is that of rbg colors. "
+"Example: #AC32FF,#9A5200"
+msgstr ""
+"XO प्रतीक के लिए रंग जिसे पूरे डेस्कटॉप में प्रयोग में लिया जाएगा. स्ट्रिंग "
+"को स्ट्रोक रंग तथा भरने के रंग से बनाया गया है तथा आरजीबी रंग फ़ॉर्मेट में "
+"है. उदाहरण: #AC32FF,#9A5200"
+
+#: ../data/sugar.schemas.in.h:4
+msgid "Corner Delay"
+msgstr "किनारा देरी"
+
+#: ../data/sugar.schemas.in.h:5
+msgid "Default font face"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:6
+msgid "Default font size"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:7
+msgid "Default nick"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:8
+msgid "Delay for the activation of the frame using the corners."
+msgstr "कोना का प्रयोग करते हुए फ्रेम एक्टिवेशन में देरी"
+
+#: ../data/sugar.schemas.in.h:9
+msgid "Delay for the activation of the frame using the edges."
+msgstr "किनारा का प्रयोग करते हुए फ्रेम एक्टिवेशन में देरी"
+
+#: ../data/sugar.schemas.in.h:10
+msgid "Edge Delay"
+msgstr "किनारा देरी"
+
+#: ../data/sugar.schemas.in.h:11
+msgid "Favorites Layout"
+msgstr "पसंदीदा ख़ाका"
+
+#: ../data/sugar.schemas.in.h:12
+msgid "Favorites resume mode"
+msgstr "पसंदीदा रेज्यूम मोड"
+
+#: ../data/sugar.schemas.in.h:13
+#, fuzzy
+msgid "Font face that is used throughout the desktop."
+msgstr "प्रयोक्ता नाम जिसका प्रयोग पूरे डेस्कटॉप में किया जाना है."
+
+#: ../data/sugar.schemas.in.h:14
+#, fuzzy
+msgid "Font size that is used throughout the desktop."
+msgstr "प्रयोक्ता नाम जिसका प्रयोग पूरे डेस्कटॉप में किया जाना है."
+
+#: ../data/sugar.schemas.in.h:15
+msgid ""
+"If TRUE, Sugar will make us searchable for the other users of the Jabber "
+"server."
+msgstr ""
+"यदि सही है, सुगर हमें जैबर सर्वर के अन्य प्रयोक्ताओं के लिए खोजने लायक बना "
+"देगा."
+
+#: ../data/sugar.schemas.in.h:16
+msgid "If TRUE, Sugar will show a \"Log out\" option."
+msgstr "यदि सही है, सुगर \"लॉग आउट\" विकल्प दिखाएगा."
+
+#: ../data/sugar.schemas.in.h:17
+msgid "Jabber Server"
+msgstr "जैबर सर्वर"
+
+#: ../data/sugar.schemas.in.h:18
+msgid "Keyboard layouts"
+msgstr "कुंजीपट ख़ाका"
+
+#: ../data/sugar.schemas.in.h:19
+msgid "Keyboard model"
+msgstr "कुंजीपट मॉडल"
+
+#: ../data/sugar.schemas.in.h:20
+msgid "Keyboard options"
+msgstr "कुंजीपट विकल्प"
+
+#: ../data/sugar.schemas.in.h:21
+msgid "Layout of the favorites view."
+msgstr "पसंदीदा दृश्य का ख़ाका."
+
+#: ../data/sugar.schemas.in.h:22
+msgid ""
+"List of keyboard layouts. Each entry should be in the form layout(variant)"
+msgstr ""
+"कुंजीपट खाका की सूची. प्रत्येक सूची खाका फ़ॉर्म (वेरिएंट) में होनी चाहिए"
+
+#: ../data/sugar.schemas.in.h:23
+msgid "List of keyboard options."
+msgstr "कुंजीपट विकल्पों की सूची."
+
+#: ../data/sugar.schemas.in.h:24
+msgid "Power Automatic"
+msgstr "स्वचलित पावर"
+
+#: ../data/sugar.schemas.in.h:25
+msgid "Power Automatic."
+msgstr "स्वचलित पावर."
+
+#: ../data/sugar.schemas.in.h:26
+msgid "Power Extreme"
+msgstr "एक्सट्रीम पावर"
+
+#: ../data/sugar.schemas.in.h:27
+msgid "Power Extreme."
+msgstr "एक्सट्रीम पावर."
+
+#: ../data/sugar.schemas.in.h:28
+msgid "Publish to Gadget"
+msgstr "गॅजेट में प्रकाशित करें"
+
+#: ../data/sugar.schemas.in.h:29
+msgid "Setting for muting the sound device."
+msgstr "आवाज उपकरण को मौन करने के लिए सेटिंग."
+
+#: ../data/sugar.schemas.in.h:30
+msgid "Show Log out"
+msgstr "लाग आउट दिखाएँ"
+
+#: ../data/sugar.schemas.in.h:31
+msgid "Sound Muted"
+msgstr "आवाज मौन है"
+
+#: ../data/sugar.schemas.in.h:32
+msgid "The keyboard model to be used"
+msgstr "कुंजीपट मॉडल जिसे प्रयोग करना है"
+
+#: ../data/sugar.schemas.in.h:34
+msgid "Timezone setting for the system."
+msgstr "सिस्टम के लिए समयक्षेत्र सेटिंग."
+
+#: ../data/sugar.schemas.in.h:35
+msgid "Url of the jabber server to use."
+msgstr "प्रयोग किए जाने वाले जैबर सर्वर का यूआरएल."
+
+#: ../data/sugar.schemas.in.h:36
+msgid "Url where the backup is saved to."
+msgstr "यूआरएल जहाँ बैकअप सहेजा जाना है."
+
+#: ../data/sugar.schemas.in.h:37
+msgid "User Color"
+msgstr "प्रयोक्ता रंग"
+
+#: ../data/sugar.schemas.in.h:38
+msgid "User Name"
+msgstr "प्रयोक्ता नाम"
+
+#: ../data/sugar.schemas.in.h:39
+msgid "User name that is used throughout the desktop."
+msgstr "प्रयोक्ता नाम जिसका प्रयोग पूरे डेस्कटॉप में किया जाना है."
+
+#: ../data/sugar.schemas.in.h:40
+msgid "Volume Level"
+msgstr "आवाज़ स्तर"
+
+#: ../data/sugar.schemas.in.h:41
+msgid "Volume level for the sound device."
+msgstr "ध्वनि उपकरण के लिए आवाज स्तर."
+
+#: ../data/sugar.schemas.in.h:42
+msgid ""
+"When in resume mode, clicking on a favorite icon will cause the last entry "
+"for that activity to be resumed."
+msgstr ""
+"जब यह रेज्यूम मोड पर होगा, पसंदीदा प्रतीक पर क्लिक करने पर उक्त कार्य की "
+"अंतिम प्रविष्टि बहाल हो जाएगी."
+
+#: ../src/jarabe/controlpanel/cmd.py:28
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+"सुगर-कंट्रोल-पैनल: चेतावनी, एक ही नाम के एक से अधिक विकल्प मिले: %s मॉड्यूल: "
+"%r"
+
+#: ../src/jarabe/controlpanel/cmd.py:30
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr "शुगर -कंट्रोल-पेनल: कुंजी =%s विकल्प उपलब्ध नही है"
+
+#: ../src/jarabe/controlpanel/cmd.py:31
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr "शुगर -कंट्रोल-पेनल: %s"
+
+# TRANS: Translators, there's a empty line at the end of this string,
+# which must appear in the translated string (msgstr) as well.
+#. TRANS: Translators, there's a empty line at the end of this string,
+#. which must appear in the translated string (msgstr) as well.
+#: ../src/jarabe/controlpanel/cmd.py:37
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" -c key clear the current value for the key \n"
+" "
+msgstr ""
+"उपयोग: sugar-control-panel [ विकल्प ] key [ आर्गुमेंट ... ] \n"
+" सुगर वातावरण के लिए नियंत्रण. \n"
+" विकल्प: \n"
+" -h यह मदद दिखाकर बाहर हो जाता है \n"
+" -l सभी उपलब्ध विकल्पों की सूची देता है \n"
+" -h key इस कुंजी के बारे में जानकारी देता है \n"
+" -g key कुंजी का वर्तमान मूल्य प्राप्त करें \n"
+" -s key कुंजी का वर्तमान मूल्य सेट करता है \n"
+" -c key कुंजी का वर्तमान मूल्य साफ करता है \n"
+" "
+
+#: ../src/jarabe/controlpanel/cmd.py:50
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr "अपने बदलावो को उपयोग मे लाने के लिए शुगर को पुनरारंभ करे .\n"
+
+#: ../src/jarabe/controlpanel/gui.py:281
+msgid "Warning"
+msgstr "चेतावनी"
+
+#: ../src/jarabe/controlpanel/gui.py:282
+#: ../src/jarabe/controlpanel/sectionview.py:42
+msgid "Changes require restart"
+msgstr "परिवर्तनों के लिए कम्प्यूटर फिर से प्रारंभ करना आवश्यक है"
+
+#: ../src/jarabe/controlpanel/gui.py:285
+msgid "Cancel changes"
+msgstr "परिवर्तनों को रद्द करें"
+
+#: ../src/jarabe/controlpanel/gui.py:290 ../src/jarabe/desktop/homebox.py:70
+msgid "Later"
+msgstr "बाद में"
+
+#: ../src/jarabe/controlpanel/gui.py:294
+msgid "Restart now"
+msgstr "अभी फिर से प्रारंभ करें"
+
+#: ../src/jarabe/controlpanel/toolbar.py:61 ../src/jarabe/intro/window.py:206
+msgid "Done"
+msgstr "सम्पन्न"
+
+#: ../src/jarabe/controlpanel/toolbar.py:121
+#: ../src/jarabe/desktop/favoritesview.py:333
+msgid "Ok"
+msgstr "ठीक"
+
+#: ../src/jarabe/desktop/activitieslist.py:236
+#, python-format
+msgid "Version %s"
+msgstr "संस्करण %s"
+
+#: ../src/jarabe/desktop/activitieslist.py:357
+msgid "Confirm erase"
+msgstr "मिटाने हेतु पुष्टि"
+
+#: ../src/jarabe/desktop/activitieslist.py:359
+#, python-format
+msgid "Confirm erase: Do you want to permanently erase %s?"
+msgstr "मिटाने की पुष्टि: क्या आप %s को हमेशा के लिए मिटा देना चाहते हैं?"
+
+#: ../src/jarabe/desktop/activitieslist.py:363
+#: ../src/jarabe/frame/clipboardmenu.py:63
+#: ../src/jarabe/view/viewsource.py:218
+msgid "Keep"
+msgstr "रखें"
+
+#: ../src/jarabe/desktop/activitieslist.py:366
+#: ../src/jarabe/desktop/activitieslist.py:409
+#: ../src/jarabe/journal/journaltoolbox.py:360
+#: ../src/jarabe/journal/palettes.py:105
+msgid "Erase"
+msgstr "मिटाएँ"
+
+#: ../src/jarabe/desktop/activitieslist.py:430
+msgid "Remove favorite"
+msgstr "पसंदीदा मिटाएँ"
+
+#: ../src/jarabe/desktop/activitieslist.py:434
+msgid "Make favorite"
+msgstr "पसंदीदा बनाएँ"
+
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:116
+msgid "Freeform"
+msgstr "फ्रीफ़ॉर्म"
+
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:198
+msgid "Ring"
+msgstr "रिंग"
+
+#. TRANS: label for the spiral layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:337
+msgid "Spiral"
+msgstr "सर्पिल"
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:404
+msgid "Box"
+msgstr "बक्सा"
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:445
+msgid "Triangle"
+msgstr "त्रिभुज"
+
+#: ../src/jarabe/desktop/favoritesview.py:324
+msgid "Registration Failed"
+msgstr "पंजीकरण असफल"
+
+#: ../src/jarabe/desktop/favoritesview.py:325
+#, python-format
+msgid "%s"
+msgstr "%s"
+
+#: ../src/jarabe/desktop/favoritesview.py:327
+msgid "Registration Successful"
+msgstr "पंजीकरण सफल"
+
+#: ../src/jarabe/desktop/favoritesview.py:328
+msgid "You are now registered with your school server."
+msgstr "आप अब अपने स्कूल के सर्वर से पंजीकृत हो चुके हैं."
+
+#: ../src/jarabe/desktop/favoritesview.py:630
+msgid "Register"
+msgstr "पंजीकृत करे"
+
+#: ../src/jarabe/desktop/homebox.py:63
+msgid "Software Update"
+msgstr "सॉफ़्टवेयर अद्यतन"
+
+#: ../src/jarabe/desktop/homebox.py:64
+msgid "Update your activities to ensure compatibility with your new software"
+msgstr ""
+"अपने नये सॉफ़्टवेयर के साथ संगतता सुनिश्चित करने के लिए अपनी क्रियाओं को "
+"अद्यतन करें"
+
+#: ../src/jarabe/desktop/homebox.py:73
+msgid "Check now"
+msgstr "अभी जाँचें"
+
+#: ../src/jarabe/desktop/homebox.py:192
+msgid "List view"
+msgstr "सूची दृश्य"
+
+#: ../src/jarabe/desktop/homebox.py:193
+msgid "<Ctrl>2"
+msgstr "<Ctrl>2"
+
+#: ../src/jarabe/desktop/homebox.py:255
+msgid "Favorites view"
+msgstr "पसंदीदा दृश्य"
+
+#: ../src/jarabe/desktop/homebox.py:256
+msgid "<Ctrl>1"
+msgstr "<Ctrl>1"
+
+#: ../src/jarabe/desktop/keydialog.py:135
+msgid "Key Type:"
+msgstr "कुंजी का प्रकार"
+
+#: ../src/jarabe/desktop/keydialog.py:155
+msgid "Authentication Type:"
+msgstr "विश्वसनियता का प्रकार"
+
+#: ../src/jarabe/desktop/keydialog.py:220
+msgid "WPA & WPA2 Personal"
+msgstr "WPA & WPA2 व्यक्तिगत"
+
+#: ../src/jarabe/desktop/keydialog.py:229
+msgid "Wireless Security:"
+msgstr "बेतार सुरक्षा:"
+
+#: ../src/jarabe/desktop/meshbox.py:492
+#, python-format
+#, fuzzy
+msgid "Mesh Network %d"
+msgstr "मेश नेटवर्क "
+
+# TRANS: Action label for resuming an activity.
+#. TRANS: Action label for resuming an activity.
+#: ../src/jarabe/desktop/meshbox.py:629
+#: ../src/jarabe/frame/activitiestray.py:735
+#: ../src/jarabe/journal/journaltoolbox.py:428
+#: ../src/jarabe/journal/palettes.py:65 ../src/jarabe/view/palettes.py:67
+msgid "Resume"
+msgstr "पुनरारंभ"
+
+#: ../src/jarabe/desktop/meshbox.py:634
+#: ../src/jarabe/frame/activitiestray.py:233
+msgid "Join"
+msgstr "जोड़े"
+
+#: ../src/jarabe/desktop/schoolserver.py:103
+msgid "Cannot connect to the server."
+msgstr "सर्वर से कनेक्ट करें नहीं हो सका."
+
+#: ../src/jarabe/desktop/schoolserver.py:108
+msgid "The server could not complete the request."
+msgstr "सर्वर ने निवेदन पूरा नहीं किया."
+
+#: ../src/jarabe/frame/activitiestray.py:238
+#: ../src/jarabe/frame/activitiestray.py:672
+msgid "Decline"
+msgstr "अस्वीकार"
+
+#: ../src/jarabe/frame/activitiestray.py:624
+#, python-format
+msgid "%dB"
+msgstr "%dबा."
+
+#: ../src/jarabe/frame/activitiestray.py:626
+#, python-format
+msgid "%dKB"
+msgstr "%dकि.बा."
+
+#: ../src/jarabe/frame/activitiestray.py:628
+#, python-format
+msgid "%dMB"
+msgstr "%dमे.बा."
+
+#: ../src/jarabe/frame/activitiestray.py:645
+#, python-format
+msgid "%s of %s"
+msgstr "%s इसमें का- %s"
+
+#: ../src/jarabe/frame/activitiestray.py:657
+#, python-format
+msgid "Transfer from %r"
+msgstr "%r से हस्तांतरण"
+
+#: ../src/jarabe/frame/activitiestray.py:667
+msgid "Accept"
+msgstr "स्वीकृत"
+
+#: ../src/jarabe/frame/activitiestray.py:690
+#: ../src/jarabe/frame/activitiestray.py:817
+#, python-format
+msgid "%s (%s)"
+msgstr "%s (%s)"
+
+#: ../src/jarabe/frame/activitiestray.py:724
+#: ../src/jarabe/frame/activitiestray.py:852
+msgid "Dismiss"
+msgstr "खारिज"
+
+#: ../src/jarabe/frame/activitiestray.py:787
+#, python-format
+msgid "Transfer to %r"
+msgstr "%r को हस्तांतरित करें"
+
+#: ../src/jarabe/frame/clipboardmenu.py:53 ../src/jarabe/view/palettes.py:221
+msgid "Remove"
+msgstr "हटाएँ"
+
+#: ../src/jarabe/frame/clipboardmenu.py:58
+#: ../src/jarabe/frame/clipboardmenu.py:81
+msgid "Open"
+msgstr "खोलें"
+
+#: ../src/jarabe/frame/clipboardmenu.py:86
+msgid "Open with"
+msgstr "के साथ खोलें"
+
+#: ../src/jarabe/frame/clipboardobject.py:49
+#, python-format
+msgid "%s clipping"
+msgstr "%s क्लिपिंग"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:37
+msgid "Neighborhood"
+msgstr "आसपडोस"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:37
+msgid "F1"
+msgstr "F1"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:39
+msgid "F2"
+msgstr "F2"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:41
+msgid "F3"
+msgstr "F3"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:43
+msgid "F4"
+msgstr "F4"
+
+#: ../src/jarabe/intro/window.py:128
+msgid "Click to change color:"
+msgstr "रंग बदलने के लिए दबाए"
+
+#: ../src/jarabe/intro/window.py:192 ../src/jarabe/journal/detailview.py:103
+msgid "Back"
+msgstr "पीछे जाएँ"
+
+#: ../src/jarabe/intro/window.py:209
+msgid "Next"
+msgstr "अगला"
+
+#: ../src/jarabe/journal/expandedentry.py:152
+#: ../src/jarabe/journal/palettes.py:59
+msgid "Untitled"
+msgstr "बिना शीर्षक"
+
+#: ../src/jarabe/journal/expandedentry.py:243
+msgid "No preview"
+msgstr "कोई पूर्वावलोकन नहीं"
+
+#: ../src/jarabe/journal/expandedentry.py:262
+#, python-format
+msgid "Kind: %s"
+msgstr "काइंड: %s"
+
+#: ../src/jarabe/journal/expandedentry.py:262
+msgid "Unknown"
+msgstr "अज्ञात"
+
+#: ../src/jarabe/journal/expandedentry.py:263
+#, python-format
+msgid "Date: %s"
+msgstr "तारीख़: %s"
+
+#: ../src/jarabe/journal/expandedentry.py:264
+#, python-format
+msgid "Size: %s"
+msgstr "आकार: %s"
+
+#: ../src/jarabe/journal/expandedentry.py:286 ../src/jarabe/journal/misc.py:93
+msgid "No date"
+msgstr "बिना तारीख़"
+
+#: ../src/jarabe/journal/expandedentry.py:293
+msgid "Participants:"
+msgstr "प्रतिभागी:"
+
+#: ../src/jarabe/journal/expandedentry.py:316
+msgid "Description:"
+msgstr "वर्णनः"
+
+#: ../src/jarabe/journal/expandedentry.py:341
+msgid "Tags:"
+msgstr "चिप्पियाँ:"
+
+#: ../src/jarabe/journal/journalactivity.py:108
+#: ../src/jarabe/journal/volumestoolbar.py:47
+msgid "Journal"
+msgstr "चिठ्ठा"
+
+#: ../src/jarabe/journal/journaltoolbox.py:67
+msgid "Search"
+msgstr "ढूंढें"
+
+#: ../src/jarabe/journal/journaltoolbox.py:126
+msgid "Anytime"
+msgstr "कभी भी"
+
+#: ../src/jarabe/journal/journaltoolbox.py:128
+msgid "Today"
+msgstr "आज"
+
+#: ../src/jarabe/journal/journaltoolbox.py:130
+msgid "Since yesterday"
+msgstr "कल से"
+
+# TRANS: Filter entries modified during the last 7 days.
+#. TRANS: Filter entries modified during the last 7 days.
+#: ../src/jarabe/journal/journaltoolbox.py:132
+msgid "Past week"
+msgstr "पिछला हफ्ता"
+
+# TRANS: Filter entries modified during the last 30 days.
+#. TRANS: Filter entries modified during the last 30 days.
+#: ../src/jarabe/journal/journaltoolbox.py:134
+msgid "Past month"
+msgstr "पिछला महीना"
+
+# TRANS: Filter entries modified during the last 356 days.
+#. TRANS: Filter entries modified during the last 356 days.
+#: ../src/jarabe/journal/journaltoolbox.py:136
+msgid "Past year"
+msgstr "पिछला साल"
+
+#: ../src/jarabe/journal/journaltoolbox.py:143
+msgid "Anyone"
+msgstr "कोई भी"
+
+#: ../src/jarabe/journal/journaltoolbox.py:145
+msgid "My friends"
+msgstr "मेरे दोस्त"
+
+#: ../src/jarabe/journal/journaltoolbox.py:146
+msgid "My class"
+msgstr "मेरी कक्षा"
+
+# TRANS: Item in a combo box that filters by entry type.
+#: ../src/jarabe/journal/journaltoolbox.py:274
+msgid "Anything"
+msgstr "कुछ भी"
+
+#: ../src/jarabe/journal/journaltoolbox.py:350
+#: ../src/jarabe/journal/palettes.py:83
+msgid "Copy"
+msgstr "नक़ल"
+
+# TRANS: Action label for starting an entry.
+#. TRANS: Action label for starting an entry.
+#: ../src/jarabe/journal/journaltoolbox.py:431
+#: ../src/jarabe/journal/palettes.py:68
+msgid "Start"
+msgstr "प्रारंभ"
+
+#: ../src/jarabe/journal/listview.py:373
+msgid "Your Journal is empty"
+msgstr "आपकी दैनिकी खाली है"
+
+#: ../src/jarabe/journal/listview.py:375
+msgid "No matching entries"
+msgstr "कोई भी मेल खाती हुई प्रविष्टि नहीं है"
+
+#: ../src/jarabe/journal/listview.py:386
+msgid "Clear search"
+msgstr "खोज साफ करें"
+
+#: ../src/jarabe/journal/modalalert.py:63
+msgid "Your Journal is full"
+msgstr "आपकी दैनिकी पूरी भरी है"
+
+#: ../src/jarabe/journal/modalalert.py:67
+msgid "Please delete some old Journal entries to make space for new ones."
+msgstr ""
+"नई प्रविष्टियों के लिए जगह बनाने के लिए कृपया कुछ पुरानी दैनिकी प्रविष्टियों "
+"को मिटाएँ."
+
+#: ../src/jarabe/journal/modalalert.py:79
+msgid "Show Journal"
+msgstr "दैनिकी दिखाएँ"
+
+#: ../src/jarabe/journal/objectchooser.py:146
+msgid "Choose an object"
+msgstr "एक वस्तु चुनें"
+
+#: ../src/jarabe/journal/objectchooser.py:151
+#: ../src/jarabe/view/viewsource.py:308
+msgid "Close"
+msgstr "बंद करें"
+
+#: ../src/jarabe/journal/palettes.py:66
+msgid "Resume with"
+msgstr "के साथ बहाल करें"
+
+#: ../src/jarabe/journal/palettes.py:69
+msgid "Start with"
+msgstr "के साथ प्रारंभ करें"
+
+#: ../src/jarabe/journal/palettes.py:91
+msgid "Send to"
+msgstr "को भेजें"
+
+#: ../src/jarabe/journal/palettes.py:100
+msgid "View Details"
+msgstr "विवरणों को देखें"
+
+#: ../src/jarabe/journal/palettes.py:178
+msgid "No friends present"
+msgstr "कोई मित्र उपस्थित नहीं है"
+
+#: ../src/jarabe/journal/palettes.py:183
+msgid "No valid connection found"
+msgstr "कोई भी वैध कनेक्शन नहीं मिला"
+
+#: ../src/jarabe/journal/palettes.py:211
+msgid "No activity to resume entry"
+msgstr "प्रविष्टि बहाल करने के लिए कोई क्रिया नहीं"
+
+#: ../src/jarabe/journal/palettes.py:213
+msgid "No activity to start entry"
+msgstr "प्रविष्टि प्रारंभ करने के लिए कोई क्रिया नहीं"
+
+#: ../src/jarabe/view/buddymenu.py:62
+msgid "Remove friend"
+msgstr "दोस्त को हटाएँ "
+
+#: ../src/jarabe/view/buddymenu.py:65
+msgid "Make friend"
+msgstr "दोस्त बनाओ"
+
+#: ../src/jarabe/view/buddymenu.py:82
+msgid "Shutdown"
+msgstr "बन्दकरें"
+
+#: ../src/jarabe/view/buddymenu.py:90
+msgid "Logout"
+msgstr "लॉगआउट"
+
+#: ../src/jarabe/view/buddymenu.py:95
+msgid "My Settings"
+msgstr "मेरे सेटिंग"
+
+#: ../src/jarabe/view/buddymenu.py:130
+#, python-format
+msgid "Invite to %s"
+msgstr " % s निमंत्रित करें "
+
+#: ../src/jarabe/view/palettes.py:45
+msgid "Starting..."
+msgstr "शुरू हो रहा है"
+
+#. TODO: share-with, keep
+#: ../src/jarabe/view/palettes.py:74
+msgid "View Source"
+msgstr "स्रोत देखें"
+
+#: ../src/jarabe/view/palettes.py:85
+msgid "Stop"
+msgstr "रूकें"
+
+#: ../src/jarabe/view/palettes.py:125
+msgid "Start new"
+msgstr "नया प्रारंभ करें"
+
+#: ../src/jarabe/view/palettes.py:174
+msgid "Show contents"
+msgstr "सामग्री दिखाएँ"
+
+#: ../src/jarabe/view/palettes.py:196 ../src/jarabe/view/palettes.py:246
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr "%(free_space)d मे.बा. रिक्त"
+
+#: ../src/jarabe/view/viewsource.py:208
+msgid "Instance Source"
+msgstr "इंस्टैंस स्रोत"
+
+#: ../src/jarabe/view/viewsource.py:233
+msgid "Source"
+msgstr "स्रोत"
+
+#: ../src/jarabe/view/viewsource.py:292
+msgid "Activity Bundle Source"
+msgstr "क्रिया बंडल स्रोत"
+
+#: ../src/jarabe/view/viewsource.py:299
+#, python-format
+msgid "View source: %r"
+msgstr "स्रोत देखें: %r"
+
+#~ msgid "Title"
+#~ msgstr "शीर्षक"
+
+#~ msgid "Version"
+#~ msgstr "संस्करण"
+
+#~ msgid "Date"
+#~ msgstr "तारीख़"
+
+#~ msgid "Unmount"
+#~ msgstr "अनमाउन्ट"
+
+#~ msgid "Encryption Type:"
+#~ msgstr "गोपनियता का प्रकार"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#, fuzzy
+#~ msgid "Disconnecting..."
+#~ msgstr " सम्पर्क तोडे"
+
+#~ msgid "Connected to a School Mesh Portal"
+#~ msgstr "स्कूल मेश पोर्टल से संपर्क हो गया है"
+
+#~ msgid "Looking for a School Mesh Portal..."
+#~ msgstr "स्कूल मेश पोर्टल की खोज मे..."
+
+#~ msgid "Connected to an XO Mesh Portal"
+#~ msgstr "एक्सो मेश पोर्टल से संपर्क हो गया है"
+
+#~ msgid "Looking for an XO Mesh Portal..."
+#~ msgstr "एक्सो मेश पोर्टल की खोज मे..."
+
+#~ msgid "Connected to a Simple Mesh"
+#~ msgstr "सिंपल मेश से संपर्क हो गया है"
+
+#~ msgid "Starting a Simple Mesh"
+#~ msgstr "सिंपल मेश शुरू हो रहा है"
+
+#~ msgid "Unknown Mesh"
+#~ msgstr "मेश अग्यात है"
+
+#, python-format
+#~ msgid "Clipboard object: %s."
+#~ msgstr "काटपट की वस्तु: %s"
+
+#~ msgid "off"
+#~ msgstr "बंद"
+
+#~ msgid "on"
+#~ msgstr "चालू"
+
+#~ msgid "Permission denied. You need to be root to run this method."
+#~ msgstr "अनुमति नही है . इस विधि को चलाने के लिए आपका रूट मे होना आवश्यक है "
+
+#~ msgid "Error in reading timezone"
+#~ msgstr "समयक्षेत्र की जानकारी मे गलती"
+
+#, python-format
+#~ msgid "Error copying timezone (from %s): %s"
+#~ msgstr "समयक्षेत्र की नक़ल करने मे गलती (%s से) : %s"
+
+#, python-format
+#~ msgid "Changing permission of timezone: %s"
+#~ msgstr "समयक्षेत्र की अनुमति बदली जा रही है: %s"
+
+#~ msgid "About this XO"
+#~ msgstr "एक्सो के बारे मे "
+
+#~ msgid "Add to journal"
+#~ msgstr "चिठ्ठे मे जोड़ें"
+
+#~ msgid "Reboot"
+#~ msgstr " पुनरारंभ करें"
+
+#~ msgid "My Battery life"
+#~ msgstr "मेरी बैटरी की आयू"
+
+#~ msgid "Battery charging"
+#~ msgstr "बैटरी चार्ज हो रही है"
+
+#~ msgid "Battery discharging"
+#~ msgstr "बैटरी समाप्त हो रही है"
+
+#~ msgid "Battery fully charged"
+#~ msgstr "बैटरी पूरी चार्ज है "
diff --git a/shell/po/ht.po b/shell/po/ht.po
new file mode 100644
index 0000000..86a54e2
--- /dev/null
+++ b/shell/po/ht.po
@@ -0,0 +1,583 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-21 00:30-0400\n"
+"PO-Revision-Date: 2008-03-12 09:14-0400\n"
+"Last-Translator: Jude Augusma <jayme2901@yahoo.com>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Pootle 1.1.0rc2\n"
+
+#: ../src/intro/intro.py:65 ../src/controlpanel/view/aboutme.py:100
+msgid "Name:"
+msgstr "Non"
+
+#: ../src/intro/intro.py:94
+msgid "Click to change color:"
+msgstr "Klike pou chanje koulè"
+
+#: ../src/intro/intro.py:145
+msgid "Back"
+msgstr "Retounen"
+
+#: ../src/intro/intro.py:159 ../src/controlpanel/toolbar.py:61
+msgid "Done"
+msgstr "Fini"
+
+#: ../src/intro/intro.py:162
+msgid "Next"
+msgstr "Prochen"
+
+#: ../src/view/BuddyMenu.py:58
+msgid "Remove friend"
+msgstr "Retire zanmi"
+
+#: ../src/view/BuddyMenu.py:61
+msgid "Make friend"
+msgstr "Fè zanmi"
+
+#: ../src/view/BuddyMenu.py:91
+#, python-format
+msgid "Invite to %s"
+msgstr "Envite sou %s"
+
+#: ../src/view/clipboardmenu.py:48
+msgid "Remove"
+msgstr "Retire"
+
+#: ../src/view/clipboardmenu.py:53 ../src/view/clipboardmenu.py:79
+msgid "Open"
+msgstr "Louvri"
+
+#. self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+#. TODO: Implement stopping downloads
+#. self._stop_item.connect('activate', self._stop_item_activate_cb)
+#. self.append_menu_item(self._stop_item)
+#: ../src/view/clipboardmenu.py:63
+msgid "Keep"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:84
+msgid "Open with"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:212
+#, python-format
+msgid "Clipboard object: %s."
+msgstr "Objè ekritwa: %s."
+
+#: ../src/hardware/keydialog.py:150
+msgid "Key Type:"
+msgstr "Tip kle:"
+
+#: ../src/hardware/keydialog.py:170
+msgid "Authentication Type:"
+msgstr "Tip otantifikasyon:"
+
+#: ../src/hardware/keydialog.py:251
+msgid "Encryption Type:"
+msgstr "Tip kod sekrè"
+
+#: ../src/view/Shell.py:262
+msgid "Screenshot"
+msgstr "Ekran projektwa"
+
+#: ../src/view/home/HomeBox.py:147
+msgid "List view"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:148
+msgid "<Ctrl>L"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:204
+msgid "Favorites view"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:205
+msgid "<Ctrl>R"
+msgstr ""
+
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/view/home/HomeBox.py:211
+msgid "Freeform"
+msgstr ""
+
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/view/home/HomeBox.py:218
+msgid "Ring"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:97
+msgid "Connect"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:106
+#, fuzzy
+msgid "Disconnect"
+msgstr "Dekonekte"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#: ../src/view/home/MeshBox.py:118
+#, fuzzy
+msgid "Disconnecting..."
+msgstr "Dekonekte..."
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#: ../src/view/home/MeshBox.py:152
+#, fuzzy
+msgid "Connecting..."
+msgstr "Dekonekte..."
+
+#. TODO: show the channel number
+#: ../src/view/home/MeshBox.py:159
+msgid "Connected"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:211 ../src/view/devices/network/mesh.py:38
+#: ../src/view/devices/network/mesh.py:65
+#: ../src/view/devices/network/mesh.py:69
+msgid "Mesh Network"
+msgstr "Rezo "
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#: ../src/view/home/MeshBox.py:214 ../src/view/devices/network/wireless.py:116
+#: ../src/view/devices/network/mesh.py:86
+msgid "Disconnect..."
+msgstr "Dekonekte..."
+
+#: ../src/view/home/MeshBox.py:302 ../src/view/palettes.py:60
+msgid "Resume"
+msgstr "Repwann"
+
+#: ../src/view/home/MeshBox.py:307 ../src/view/frame/activitiestray.py:219
+msgid "Join"
+msgstr "Rankontre"
+
+#: ../src/view/devices/battery.py:42
+msgid "My Battery"
+msgstr ""
+
+#: ../src/view/devices/battery.py:111
+msgid "Charging"
+msgstr ""
+
+#: ../src/view/devices/battery.py:114
+msgid "Very little power remaining"
+msgstr ""
+
+#: ../src/view/devices/battery.py:120
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr ""
+
+#: ../src/view/devices/battery.py:124
+msgid "Charged"
+msgstr ""
+
+#: ../src/view/devices/speaker.py:40
+msgid "My Speakers"
+msgstr ""
+
+#: ../src/view/devices/speaker.py:104
+msgid "Unmute"
+msgstr ""
+
+#: ../src/view/devices/speaker.py:107
+msgid "Mute"
+msgstr ""
+
+#: ../src/view/devices/network/wireless.py:64
+msgid "Disconnected"
+msgstr "Dekonekte"
+
+#: ../src/view/devices/network/wireless.py:134
+msgid "Channel"
+msgstr "Chanèl"
+
+#: ../src/view/frame/zoomtoolbar.py:34
+msgid "Neighborhood"
+msgstr "Vwazinaj"
+
+#: ../src/view/frame/zoomtoolbar.py:36
+msgid "Group"
+msgstr "Gwoup"
+
+#: ../src/view/frame/zoomtoolbar.py:38
+msgid "Home"
+msgstr "Lakay"
+
+#: ../src/view/frame/zoomtoolbar.py:40
+msgid "Activity"
+msgstr "Aktivite"
+
+#: ../src/controlpanel/cmd.py:26
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:28
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr "Sugar-kontwòl-panèl: kle=%s pa yon opsyon disponib"
+
+#: ../src/controlpanel/cmd.py:29
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr "Sugar-kontwòl-panèl: %s"
+
+#: ../src/controlpanel/cmd.py:33
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" "
+msgstr ""
+"Sèvi: Sugar-Kontwòl-panèl [opsyon] kle [args ... ]\n"
+" Kontwòl pou anvironman sugar. \n"
+" opsyon: \n"
+" -h montre mesaj èd sa epi soti \n"
+" -l fè lis tout opsyon disponib yo \n"
+" -h kle montre enfòmasyon sou kle sa \n"
+" -g kle pwan valè kouran kle-a \n"
+" -s kle fikse valè Kouran pou kle-a \n"
+" "
+
+#: ../src/controlpanel/cmd.py:45
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr "Pou aplike chanjman ou yo ou bezwen reyinisyalize program nan.\n"
+
+#: ../src/controlpanel/toolbar.py:115
+msgid "Cancel"
+msgstr ""
+
+#: ../src/controlpanel/toolbar.py:121
+msgid "Ok"
+msgstr ""
+
+#: ../src/controlpanel/sectionview.py:34 ../src/controlpanel/gui.py:250
+msgid "Changes require restart"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:249
+msgid "Warning"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:253
+msgid "Cancel changes"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:257
+msgid "Later"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:261
+msgid "Restart now"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:44
+msgid "You must enter a name."
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:67
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:70
+#, python-format
+msgid "stroke: %s"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:72
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:74
+#, python-format
+msgid "fill: %s"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:85
+msgid "Error in specified color modifiers."
+msgstr "Erè nan modifikatè endike koulè yo"
+
+#: ../src/controlpanel/model/aboutme.py:88
+msgid "Error in specified colors."
+msgstr "Erè nan koulè endike yo"
+
+#: ../src/controlpanel/model/aboutxo.py:24
+msgid "Not available"
+msgstr "Pa disponib"
+
+#: ../src/controlpanel/model/datetime.py:85
+msgid "Error timezone does not exist."
+msgstr "Erè lè lokal pa egziste."
+
+#: ../src/controlpanel/model/frame.py:38 ../src/controlpanel/model/frame.py:60
+msgid "Value must be an integer."
+msgstr ""
+
+#: ../src/controlpanel/model/language.py:28
+#, fuzzy
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr "Pa ka rantre %s. Kreye reglaj estanda"
+
+#: ../src/controlpanel/model/language.py:104
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr "Lang pou kod=%s pa ka tèmine."
+
+#: ../src/controlpanel/model/language.py:121
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr "Mwen regrèt mwen pa pale '%s'."
+
+#: ../src/controlpanel/model/network.py:48
+msgid "You must enter a server."
+msgstr ""
+
+#: ../src/controlpanel/model/network.py:63
+msgid "State is unknown."
+msgstr "Yo pa konnen eta l'"
+
+#: ../src/controlpanel/model/network.py:83
+msgid "Error in specified radio argument use on/off."
+msgstr "Erè nan itilizasyon limen/etenn agiman radio endike-a. "
+
+#: ../src/controlpanel/view/aboutme.py:32
+msgid "About Me"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutme.py:134
+#, fuzzy
+msgid "Click to change your color:"
+msgstr "Klike pou chanje koulè"
+
+#: ../src/controlpanel/view/aboutxo.py:26
+msgid "About my XO"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutxo.py:47
+msgid "Identity"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutxo.py:56
+msgid "Serial Number:"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutxo.py:79
+msgid "Software"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutxo.py:88
+msgid "Build:"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutxo.py:103
+msgid "Firmware:"
+msgstr ""
+
+#: ../src/controlpanel/view/datetime.py:29
+msgid "Date & Time"
+msgstr ""
+
+#: ../src/controlpanel/view/datetime.py:72
+msgid "Timezone"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:28
+msgid "Frame"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:30
+msgid "never"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:31
+msgid "instantaneous"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:32
+#, python-format
+msgid "%s seconds"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:56
+msgid "Activation Delay"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:80
+msgid "Corner"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:115
+msgid "Edge"
+msgstr ""
+
+#: ../src/controlpanel/view/language.py:29
+#: ../src/controlpanel/view/language.py:74
+msgid "Language"
+msgstr ""
+
+#: ../src/controlpanel/view/network.py:28
+msgid "Network"
+msgstr ""
+
+#: ../src/controlpanel/view/network.py:53
+msgid "Wireless"
+msgstr ""
+
+#: ../src/controlpanel/view/network.py:61
+msgid "Radio:"
+msgstr ""
+
+#: ../src/controlpanel/view/network.py:94
+msgid "Mesh"
+msgstr ""
+
+#: ../src/controlpanel/view/network.py:103
+msgid "Server:"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:108
+msgid "Connected to a School Mesh Portal"
+msgstr "Konekte sou rezo lekòl la"
+
+#: ../src/view/devices/network/mesh.py:110
+msgid "Looking for a School Mesh Portal..."
+msgstr "Ap chache rezo lekòl la"
+
+#: ../src/view/devices/network/mesh.py:113
+msgid "Connected to an XO Mesh Portal"
+msgstr "Konekte sou yon rezo XO"
+
+#: ../src/view/devices/network/mesh.py:115
+msgid "Looking for an XO Mesh Portal..."
+msgstr "Ap chache yon rezo XO..."
+
+#: ../src/view/devices/network/mesh.py:118
+msgid "Connected to a Simple Mesh"
+msgstr "Konekte sou yon senp rezo"
+
+#: ../src/view/devices/network/mesh.py:120
+msgid "Starting a Simple Mesh"
+msgstr "Kòmanse yon senp rezo"
+
+#: ../src/view/devices/network/mesh.py:127
+msgid "Unknown Mesh"
+msgstr "Rezo non idantifye"
+
+#: ../src/view/frame/activitiestray.py:224
+msgid "Decline"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:351
+msgid "Control Panel"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:362
+msgid "Restart"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:367
+msgid "Shutdown"
+msgstr "Fèmen"
+
+#: ../src/view/home/favoritesview.py:373
+msgid "Register"
+msgstr "Enskri"
+
+#: ../src/view/palettes.py:41
+msgid "Starting..."
+msgstr "ap louvri"
+
+#: ../src/view/palettes.py:71
+msgid "Stop"
+msgstr "Stope, rete"
+
+#: ../src/view/palettes.py:96
+msgid "Start"
+msgstr ""
+
+#: ../src/view/palettes.py:119
+msgid "Remove favorite"
+msgstr ""
+
+#: ../src/view/palettes.py:123
+msgid "Make favorite"
+msgstr ""
+
+#: ../src/view/palettes.py:169
+msgid "Show contents"
+msgstr ""
+
+#: ../src/view/palettes.py:193
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr ""
+
+#~ msgid "off"
+#~ msgstr "Etenn"
+
+#~ msgid "on"
+#~ msgstr "Limen"
+
+#~ msgid "Permission denied. You need to be root to run this method."
+#~ msgstr "Pèmisyon renye. Ou bezwen nan rasin nan pou metòd sa mache."
+
+#~ msgid "Error in reading timezone"
+#~ msgstr "Erè nan lèkti lè lokal"
+
+#, python-format
+#~ msgid "Error copying timezone (from %s): %s"
+#~ msgstr "Erè nan kopye lè lokal la (de %s): %s"
+
+#, python-format
+#~ msgid "Changing permission of timezone: %s"
+#~ msgstr "Pèmisyon pou chanje lè lokal: %s"
+
+#~ msgid "About this XO"
+#~ msgstr "Apwopo XO sa"
+
+#~ msgid "Add to journal"
+#~ msgstr "Ajoute jounal"
+
+#~ msgid "Reboot"
+#~ msgstr "Reyinisyalize"
+
+#~ msgid "My Battery life"
+#~ msgstr "Dire de tan batri m' nan"
+
+#~ msgid "Battery charging"
+#~ msgstr "Batri ap chaje"
+
+#~ msgid "Battery discharging"
+#~ msgstr "Batri ap dechaje"
+
+#~ msgid "Battery fully charged"
+#~ msgstr "Batri foul chaj"
diff --git a/shell/po/hu.po b/shell/po/hu.po
new file mode 100644
index 0000000..63d4e34
--- /dev/null
+++ b/shell/po/hu.po
@@ -0,0 +1,764 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-09-25 00:30-0400\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/intro/window.py:93 ../src/controlpanel/aboutme/view.py:100
+msgid "Name:"
+msgstr ""
+
+#: ../src/intro/window.py:125
+msgid "Click to change color:"
+msgstr ""
+
+#: ../src/intro/window.py:175 ../src/journal/detailview.py:119
+msgid "Back"
+msgstr ""
+
+#: ../src/intro/window.py:189 ../src/controlpanel/toolbar.py:61
+msgid "Done"
+msgstr ""
+
+#: ../src/intro/window.py:192
+msgid "Next"
+msgstr ""
+
+#: ../src/view/BuddyMenu.py:60
+msgid "Remove friend"
+msgstr ""
+
+#: ../src/view/BuddyMenu.py:63
+msgid "Make friend"
+msgstr ""
+
+#: ../src/view/BuddyMenu.py:92
+#, python-format
+msgid "Invite to %s"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:51
+msgid "Remove"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:56 ../src/view/clipboardmenu.py:78
+msgid "Open"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:61 ../src/view/home/HomeBox.py:84
+msgid "Keep"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:83
+msgid "Open with"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:228
+#, python-format
+msgid "%s clipping"
+msgstr ""
+
+#: ../src/hardware/keydialog.py:150
+msgid "Key Type:"
+msgstr ""
+
+#: ../src/hardware/keydialog.py:170
+msgid "Authentication Type:"
+msgstr ""
+
+#: ../src/hardware/keydialog.py:251
+msgid "Encryption Type:"
+msgstr ""
+
+#: ../src/hardware/schoolserver.py:17
+msgid "Cannot obtain data needed for registration."
+msgstr ""
+
+#: ../src/hardware/schoolserver.py:31
+msgid "Cannot connect to the server."
+msgstr ""
+
+#: ../src/hardware/schoolserver.py:36
+msgid "The server could not complete the request."
+msgstr ""
+
+#: ../src/view/Shell.py:251
+msgid "Screenshot"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:78
+msgid "Confirm erase"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:80
+#, python-format
+msgid "Confirm erase: Do you want to permanently erase %s?"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:87 ../src/view/palettes.py:120
+#: ../src/journal/journaltoolbox.py:335 ../src/journal/palettes.py:75
+msgid "Erase"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:117
+msgid "Software Update"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:118
+msgid "Update your activities to ensure compatibility with your new software"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:122 ../src/controlpanel/toolbar.py:115
+msgid "Cancel"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:124 ../src/controlpanel/gui.py:273
+msgid "Later"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:127
+msgid "Check now"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:261
+msgid "List view"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:262
+msgid "<Ctrl>2"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:320
+msgid "Favorites view"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:321
+msgid "<Ctrl>1"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:97
+msgid "Connect"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:106
+msgid "Disconnect"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:118
+msgid "Disconnecting..."
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:159
+msgid "Connecting..."
+msgstr ""
+
+#. TODO: show the channel number
+#: ../src/view/home/MeshBox.py:166
+msgid "Connected"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:218 ../src/view/devices/network/mesh.py:41
+#: ../src/view/devices/network/mesh.py:68
+#: ../src/view/devices/network/mesh.py:72
+msgid "Mesh Network"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:221 ../src/view/devices/network/wireless.py:125
+#: ../src/view/devices/network/mesh.py:89
+msgid "Disconnect..."
+msgstr ""
+
+#. TRANS: Action label for resuming an activity.
+#: ../src/view/home/MeshBox.py:309 ../src/view/palettes.py:61
+#: ../src/journal/journaltoolbox.py:399 ../src/journal/palettes.py:57
+msgid "Resume"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:314 ../src/view/frame/activitiestray.py:206
+msgid "Join"
+msgstr ""
+
+#: ../src/view/devices/battery.py:45
+msgid "My Battery"
+msgstr ""
+
+#: ../src/view/devices/battery.py:114
+msgid "Charging"
+msgstr ""
+
+#: ../src/view/devices/battery.py:117
+msgid "Very little power remaining"
+msgstr ""
+
+#: ../src/view/devices/battery.py:123
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr ""
+
+#: ../src/view/devices/battery.py:127
+msgid "Charged"
+msgstr ""
+
+#: ../src/view/devices/speaker.py:44
+msgid "My Speakers"
+msgstr ""
+
+#: ../src/view/devices/speaker.py:125
+msgid "Unmute"
+msgstr ""
+
+#: ../src/view/devices/speaker.py:128
+msgid "Mute"
+msgstr ""
+
+#: ../src/view/devices/network/wireless.py:67
+msgid "Disconnected"
+msgstr ""
+
+#: ../src/view/devices/network/wireless.py:143
+msgid "Channel"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:37
+msgid "Neighborhood"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:39
+msgid "Group"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:41
+msgid "Home"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:43
+msgid "Activity"
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:26
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:28
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:29
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr ""
+
+#. TRANS: Translators, there's a empty line at the end of this string,
+#. which must appear in the translated string (msgstr) as well.
+#: ../src/controlpanel/cmd.py:35
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" -c key clear the current value for the key \n"
+" "
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:48
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr ""
+
+#: ../src/controlpanel/toolbar.py:121 ../src/view/home/favoritesview.py:305
+msgid "Ok"
+msgstr ""
+
+#: ../src/controlpanel/sectionview.py:42 ../src/controlpanel/gui.py:265
+msgid "Changes require restart"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:264
+msgid "Warning"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:268
+msgid "Cancel changes"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:277
+msgid "Restart now"
+msgstr ""
+
+#: ../src/controlpanel/aboutme/model.py:44
+msgid "You must enter a name."
+msgstr ""
+
+#: ../src/controlpanel/aboutme/model.py:69
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr ""
+
+#: ../src/controlpanel/aboutme/model.py:72
+#, python-format
+msgid "stroke: %s"
+msgstr ""
+
+#: ../src/controlpanel/aboutme/model.py:74
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr ""
+
+#: ../src/controlpanel/aboutme/model.py:76
+#, python-format
+msgid "fill: %s"
+msgstr ""
+
+#: ../src/controlpanel/aboutme/model.py:87
+msgid "Error in specified color modifiers."
+msgstr ""
+
+#: ../src/controlpanel/aboutme/model.py:90
+msgid "Error in specified colors."
+msgstr ""
+
+#: ../src/controlpanel/aboutme/view.py:32
+#: ../src/controlpanel/aboutme/__init__.py:22
+msgid "About Me"
+msgstr ""
+
+#: ../src/controlpanel/aboutme/view.py:134
+msgid "Click to change your color:"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/model.py:24
+msgid "Not available"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:55
+msgid "Identity"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:64
+msgid "Serial Number:"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:87
+msgid "Software"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:96
+msgid "Build:"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:111
+msgid "Sugar:"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:126
+msgid "Firmware:"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:148
+msgid "Copyright and License"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:156
+msgid ""
+"© 2008 One Laptop per Child Association Inc; Red Hat Inc; and Contributors."
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:163
+msgid ""
+"Sugar is the graphical user interface that you are looking at. Sugar is free "
+"software, covered by the GNU General Public License, and you are welcome to "
+"change it and/or distribute copies of it under certain conditions described "
+"therein."
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:175
+msgid "Full license:"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/__init__.py:21
+msgid "About my XO"
+msgstr ""
+
+#: ../src/controlpanel/datetime/model.py:89
+msgid "Error timezone does not exist."
+msgstr ""
+
+#: ../src/controlpanel/datetime/view.py:68
+msgid "Timezone"
+msgstr ""
+
+#: ../src/controlpanel/datetime/__init__.py:21
+msgid "Date & Time"
+msgstr ""
+
+#: ../src/controlpanel/frame/model.py:38 ../src/controlpanel/frame/model.py:60
+msgid "Value must be an integer."
+msgstr ""
+
+#: ../src/controlpanel/frame/view.py:26
+msgid "never"
+msgstr ""
+
+#: ../src/controlpanel/frame/view.py:27
+msgid "instantaneous"
+msgstr ""
+
+#: ../src/controlpanel/frame/view.py:28
+#, python-format
+msgid "%s seconds"
+msgstr ""
+
+#: ../src/controlpanel/frame/view.py:52
+msgid "Activation Delay"
+msgstr ""
+
+#: ../src/controlpanel/frame/view.py:76
+msgid "Corner"
+msgstr ""
+
+#: ../src/controlpanel/frame/view.py:111
+msgid "Edge"
+msgstr ""
+
+#: ../src/controlpanel/frame/__init__.py:21
+msgid "Frame"
+msgstr ""
+
+#: ../src/controlpanel/language/model.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr ""
+
+#: ../src/controlpanel/language/model.py:114
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr ""
+
+#: ../src/controlpanel/language/model.py:131
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr ""
+
+#: ../src/controlpanel/language/view.py:70
+#: ../src/controlpanel/language/__init__.py:21
+msgid "Language"
+msgstr ""
+
+#: ../src/controlpanel/network/model.py:62
+msgid "State is unknown."
+msgstr ""
+
+#: ../src/controlpanel/network/model.py:82
+msgid "Error in specified radio argument use on/off."
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:28
+#: ../src/controlpanel/network/__init__.py:21
+msgid "Network"
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:54
+msgid "Wireless"
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:62
+msgid "Turn of the wireless radio to save battery life"
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:75
+msgid "Radio"
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:91
+msgid "Discard network history if you have trouble connecting to the network"
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:100
+msgid "Discard network history"
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:113
+msgid "Mesh"
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:122
+msgid "Server:"
+msgstr ""
+
+#: ../src/controlpanel/power/model.py:55
+msgid "Error in automatic pm argument, use on/off."
+msgstr ""
+
+#: ../src/controlpanel/power/model.py:84
+msgid "Error in extreme pm argument, use on/off."
+msgstr ""
+
+#: ../src/controlpanel/power/view.py:47
+msgid "Power management"
+msgstr ""
+
+#: ../src/controlpanel/power/view.py:57
+msgid "Automatic power management (increases battery life)"
+msgstr ""
+
+#: ../src/controlpanel/power/view.py:85
+msgid ""
+"Extreme power management (disableswireless radio, increases battery life)"
+msgstr ""
+
+#: ../src/controlpanel/power/__init__.py:21
+msgid "Power"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:111
+msgid "Connected to a School Mesh Portal"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:113
+msgid "Looking for a School Mesh Portal..."
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:116
+msgid "Connected to an XO Mesh Portal"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:118
+msgid "Looking for an XO Mesh Portal..."
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:121
+msgid "Connected to a Simple Mesh"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:123
+msgid "Starting a Simple Mesh"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:130
+msgid "Unknown Mesh"
+msgstr ""
+
+#: ../src/view/frame/activitiestray.py:211
+msgid "Decline"
+msgstr ""
+
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/view/home/favoriteslayout.py:107
+msgid "Freeform"
+msgstr ""
+
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/view/home/favoriteslayout.py:189
+msgid "Ring"
+msgstr ""
+
+#. TRANS: label for the spiral layout in the favorites view
+#: ../src/view/home/favoriteslayout.py:334
+msgid "Spiral"
+msgstr ""
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/view/home/favoriteslayout.py:401
+msgid "Box"
+msgstr ""
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/view/home/favoriteslayout.py:442
+msgid "Triangle"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:295
+msgid "Registration Failed"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:296
+#, python-format
+msgid "%s"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:298
+msgid "Registration Successful"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:299
+msgid "You are now registered with your school server."
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:420
+msgid "Settings"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:425
+msgid "Restart"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:430
+msgid "Shutdown"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:436
+msgid "Register"
+msgstr ""
+
+#: ../src/view/palettes.py:42
+msgid "Starting..."
+msgstr ""
+
+#: ../src/view/palettes.py:72
+msgid "Stop"
+msgstr ""
+
+#. TRANS: Action label for starting an entry.
+#: ../src/view/palettes.py:104 ../src/journal/journaltoolbox.py:402
+#: ../src/journal/palettes.py:59
+msgid "Start"
+msgstr ""
+
+#: ../src/view/palettes.py:138
+msgid "Remove favorite"
+msgstr ""
+
+#: ../src/view/palettes.py:142
+msgid "Make favorite"
+msgstr ""
+
+#: ../src/view/palettes.py:191
+msgid "Show contents"
+msgstr ""
+
+#: ../src/view/palettes.py:215
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr ""
+
+#: ../src/journal/journaltoolbox.py:62
+msgid "Search"
+msgstr ""
+
+#: ../src/journal/journaltoolbox.py:119
+msgid "Anytime"
+msgstr ""
+
+#: ../src/journal/journaltoolbox.py:121
+msgid "Today"
+msgstr ""
+
+#: ../src/journal/journaltoolbox.py:123
+msgid "Since yesterday"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 7 days.
+#: ../src/journal/journaltoolbox.py:125
+msgid "Past week"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 30 days.
+#: ../src/journal/journaltoolbox.py:127
+msgid "Past month"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 356 days.
+#: ../src/journal/journaltoolbox.py:129
+msgid "Past year"
+msgstr ""
+
+#: ../src/journal/journaltoolbox.py:136
+msgid "Anyone"
+msgstr ""
+
+#: ../src/journal/journaltoolbox.py:138
+msgid "My friends"
+msgstr ""
+
+#: ../src/journal/journaltoolbox.py:139
+msgid "My class"
+msgstr ""
+
+#. TRANS: Item in a combo box that filters by entry type.
+#: ../src/journal/journaltoolbox.py:255
+msgid "Anything"
+msgstr ""
+
+#. TODO: Add "Start with" menu item
+#: ../src/journal/journaltoolbox.py:325 ../src/journal/palettes.py:67
+msgid "Copy"
+msgstr ""
+
+#: ../src/journal/collapsedentry.py:248 ../src/journal/expandedentry.py:176
+#: ../src/journal/palettes.py:51
+msgid "Untitled"
+msgstr ""
+
+#: ../src/journal/journalactivity.py:119 ../src/journal/volumesmanager.py:57
+msgid "Journal"
+msgstr ""
+
+#: ../src/journal/expandedentry.py:222
+msgid "No preview"
+msgstr ""
+
+#: ../src/journal/expandedentry.py:241
+msgid "Participants:"
+msgstr ""
+
+#: ../src/journal/expandedentry.py:266
+msgid "Description:"
+msgstr ""
+
+#: ../src/journal/expandedentry.py:292
+msgid "Tags:"
+msgstr ""
+
+#: ../src/journal/objectchooser.py:134
+msgid "Choose an object"
+msgstr ""
+
+#: ../src/journal/objectchooser.py:139
+msgid "Close"
+msgstr ""
+
+#: ../src/journal/volumestoolbar.py:93
+msgid "Unmount"
+msgstr ""
+
+#: ../src/journal/misc.py:95
+msgid "No date"
+msgstr ""
+
+#: ../src/journal/listview.py:39
+msgid "Your Journal is empty"
+msgstr ""
+
+#: ../src/journal/listview.py:40
+msgid "No matching entries "
+msgstr ""
+
+#: ../src/journal/modalalert.py:59
+msgid "Your Journal is full"
+msgstr ""
+
+#: ../src/journal/modalalert.py:63
+msgid "Please delete some old Journal entries to make space for new ones."
+msgstr ""
+
+#: ../src/journal/modalalert.py:75
+msgid "Show Journal"
+msgstr ""
diff --git a/shell/po/id.po b/shell/po/id.po
new file mode 100644
index 0000000..93c6b6e
--- /dev/null
+++ b/shell/po/id.po
@@ -0,0 +1,958 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-01-31 00:30-0500\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../extensions/cpsection/aboutme/__init__.py:24
+msgid "About Me"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:43
+msgid "You must enter a name."
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:68
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:71
+#, python-format
+msgid "stroke: %s"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:73
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:75
+#, python-format
+msgid "fill: %s"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:86
+msgid "Error in specified color modifiers."
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:89
+msgid "Error in specified colors."
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/view.py:94 ../src/jarabe/intro/window.py:92
+msgid "Name:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/view.py:128
+msgid "Click to change your color:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/__init__.py:21
+msgid "About my Computer"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/model.py:26
+msgid "Not available"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:59
+msgid "Identity"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:68
+msgid "Serial Number:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:90
+msgid "Software"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:99
+msgid "Build:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:114
+msgid "Sugar:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:130
+msgid "Firmware:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:145
+msgid "Wireless Firmware:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:168
+msgid "Copyright and License"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:176
+msgid ""
+"© 2008 One Laptop per Child Association Inc; Red Hat Inc; and Contributors."
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:183
+msgid ""
+"Sugar is the graphical user interface that you are looking at. Sugar is free "
+"software, covered by the GNU General Public License, and you are welcome to "
+"change it and/or distribute copies of it under certain conditions described "
+"therein."
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:195
+msgid "Full license:"
+msgstr ""
+
+#: ../extensions/cpsection/datetime/__init__.py:21
+msgid "Date & Time"
+msgstr ""
+
+#: ../extensions/cpsection/datetime/model.py:87
+msgid "Error timezone does not exist."
+msgstr ""
+
+#: ../extensions/cpsection/datetime/view.py:68 ../data/sugar.schemas.in.h:19
+msgid "Timezone"
+msgstr ""
+
+#: ../extensions/cpsection/frame/__init__.py:21
+msgid "Frame"
+msgstr ""
+
+#: ../extensions/cpsection/frame/model.py:38
+#: ../extensions/cpsection/frame/model.py:60
+msgid "Value must be an integer."
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:26
+msgid "never"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:27
+msgid "instantaneous"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:28
+#, python-format
+msgid "%s seconds"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:52
+msgid "Activation Delay"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:76
+msgid "Corner"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:111
+msgid "Edge"
+msgstr ""
+
+#: ../extensions/cpsection/language/__init__.py:21
+#: ../extensions/cpsection/language/view.py:32
+msgid "Language"
+msgstr ""
+
+#: ../extensions/cpsection/language/model.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr ""
+
+#: ../extensions/cpsection/language/model.py:124
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr ""
+
+#: ../extensions/cpsection/language/model.py:144
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr ""
+
+#: ../extensions/cpsection/network/__init__.py:21
+#: ../extensions/cpsection/network/view.py:28
+msgid "Network"
+msgstr ""
+
+#: ../extensions/cpsection/network/model.py:79
+msgid "State is unknown."
+msgstr ""
+
+#: ../extensions/cpsection/network/model.py:105
+msgid "Error in specified radio argument use on/off."
+msgstr ""
+
+#: ../extensions/cpsection/network/model.py:137
+msgid "Error in specified argument use 0/1."
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:56
+msgid "Wireless"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:64
+msgid "Turn off the wireless radio to save battery life"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:77
+msgid "Radio"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:93
+msgid "Discard network history if you have trouble connecting to the network"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:102
+msgid "Discard network history"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:115
+msgid "Collaboration"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:123
+msgid ""
+"The server is the equivalent of what room you are in; people on the same "
+"server will be able to see each other, even when they aren't on the same "
+"network."
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:133
+msgid "Server:"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:56
+msgid "My Battery"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:137
+msgid "Removed"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:140
+msgid "Charging"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:143
+msgid "Very little power remaining"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:149
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:153
+msgid "Charged"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:40
+#, python-format
+msgid "IP address: %s"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:104
+msgid "Disconnect..."
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:109
+#: ../src/jarabe/desktop/meshbox.py:246
+msgid "Connecting..."
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:113
+#: ../extensions/deviceicon/network.py:166
+#: ../src/jarabe/desktop/meshbox.py:252
+msgid "Connected"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:126
+msgid "Channel"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:141
+msgid "Wired Network"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:169
+msgid "Speed"
+msgstr ""
+
+#: ../extensions/deviceicon/speaker.py:46
+msgid "My Speakers"
+msgstr ""
+
+#: ../extensions/deviceicon/speaker.py:128
+msgid "Unmute"
+msgstr ""
+
+#: ../extensions/deviceicon/speaker.py:131
+msgid "Mute"
+msgstr ""
+
+#: ../extensions/globalkey/screenshot.py:50
+msgid "Screenshot"
+msgstr ""
+
+#: ../extensions/globalkey/viewsource.py:196
+#, python-format
+msgid "View source: %r"
+msgstr ""
+
+#: ../extensions/globalkey/viewsource.py:205
+#: ../src/jarabe/frame/zoomtoolbar.py:42
+msgid "Activity"
+msgstr ""
+
+#: ../extensions/globalkey/viewsource.py:212
+msgid "Document"
+msgstr ""
+
+#: ../extensions/globalkey/viewsource.py:226
+#: ../src/jarabe/journal/objectchooser.py:141
+msgid "Close"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:1
+msgid "Backup URL"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:2
+msgid ""
+"Color for the XO icon that is used throughout the desktop. The string is "
+"composed of the stroke color and fill color, format is that of rbg colors. "
+"Example: #AC32FF,#9A5200"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:3
+msgid "Corner Delay"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:4
+msgid "Delay for the activation of the frame using the corners."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:5
+msgid "Delay for the activation of the frame using the edges."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:6
+msgid "Edge Delay"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:7
+msgid "Favorites Layout"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:8
+msgid "Favorites resume mode"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:9
+msgid ""
+"If TRUE, Sugar will make us searchable for the other users of the Jabber "
+"server."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:10
+msgid "Jabber Server"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:11
+msgid "Layout of the favorites view."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:12
+msgid "Power Automatic"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:13
+msgid "Power Automatic."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:14
+msgid "Power Extreme"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:15
+msgid "Power Extreme."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:16
+msgid "Publish to Gadget"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:17
+msgid "Setting for muting the sound device."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:18
+msgid "Sound Muted"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:20
+msgid "Timezone setting for the system."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:21
+msgid "Url of the jabber server to use."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:22
+msgid "Url where the backup is saved to."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:23
+msgid "User Color"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:24
+msgid "User Name"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:25
+msgid "User name that is used throughout the desktop."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:26
+msgid "Volume Level"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:27
+msgid "Volume level for the sound device."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:28
+msgid ""
+"When in resume mode, clicking on a favorite icon will cause the last entry "
+"for that activity to be resumed."
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:28
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:30
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:31
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr ""
+
+#. TRANS: Translators, there's a empty line at the end of this string,
+#. which must appear in the translated string (msgstr) as well.
+#: ../src/jarabe/controlpanel/cmd.py:37
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" -c key clear the current value for the key \n"
+" "
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:50
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:272
+msgid "Warning"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:273
+#: ../src/jarabe/controlpanel/sectionview.py:42
+msgid "Changes require restart"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:276
+msgid "Cancel changes"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:281 ../src/jarabe/desktop/homebox.py:113
+msgid "Later"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:285
+msgid "Restart now"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/toolbar.py:61 ../src/jarabe/intro/window.py:188
+msgid "Done"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/toolbar.py:115
+#: ../src/jarabe/desktop/homebox.py:111
+#: ../src/jarabe/frame/activitiestray.py:683
+#: ../src/jarabe/frame/activitiestray.py:762
+#: ../src/jarabe/frame/activitiestray.py:790
+msgid "Cancel"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/toolbar.py:121
+#: ../src/jarabe/desktop/favoritesview.py:334
+msgid "Ok"
+msgstr ""
+
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:114
+msgid "Freeform"
+msgstr ""
+
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:196
+msgid "Ring"
+msgstr ""
+
+#. TRANS: label for the spiral layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:341
+msgid "Spiral"
+msgstr ""
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:408
+msgid "Box"
+msgstr ""
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:449
+msgid "Triangle"
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:325
+msgid "Registration Failed"
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:326
+#, python-format
+msgid "%s"
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:328
+msgid "Registration Successful"
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:329
+msgid "You are now registered with your school server."
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:666
+msgid "Register"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:67
+msgid "Confirm erase"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:69
+#, python-format
+msgid "Confirm erase: Do you want to permanently erase %s?"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:73 ../src/jarabe/frame/clipboardmenu.py:62
+msgid "Keep"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:76
+#: ../src/jarabe/journal/journaltoolbox.py:357
+#: ../src/jarabe/journal/palettes.py:112 ../src/jarabe/view/palettes.py:127
+msgid "Erase"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:106
+msgid "Software Update"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:107
+msgid "Update your activities to ensure compatibility with your new software"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:116
+msgid "Check now"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:233
+msgid "List view"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:234
+msgid "<Ctrl>2"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:296
+msgid "Favorites view"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:297
+msgid "<Ctrl>1"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:304
+msgid "Resume by default"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:131
+msgid "Key Type:"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:151
+msgid "Authentication Type:"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:215
+msgid "WPA & WPA2 Personal"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:224
+msgid "Wireless Security:"
+msgstr ""
+
+#: ../src/jarabe/desktop/meshbox.py:130
+msgid "Connect"
+msgstr ""
+
+#: ../src/jarabe/desktop/meshbox.py:134
+msgid "Disconnect"
+msgstr ""
+
+#. TRANS: Action label for resuming an activity.
+#: ../src/jarabe/desktop/meshbox.py:440
+#: ../src/jarabe/frame/activitiestray.py:707
+#: ../src/jarabe/journal/journaltoolbox.py:425
+#: ../src/jarabe/journal/palettes.py:72 ../src/jarabe/view/palettes.py:62
+msgid "Resume"
+msgstr ""
+
+#: ../src/jarabe/desktop/meshbox.py:445
+#: ../src/jarabe/frame/activitiestray.py:221
+msgid "Join"
+msgstr ""
+
+#: ../src/jarabe/desktop/schoolserver.py:18
+msgid "Cannot obtain data needed for registration."
+msgstr ""
+
+#: ../src/jarabe/desktop/schoolserver.py:35
+msgid "Cannot connect to the server."
+msgstr ""
+
+#: ../src/jarabe/desktop/schoolserver.py:40
+msgid "The server could not complete the request."
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:226
+#: ../src/jarabe/frame/activitiestray.py:655
+msgid "Decline"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:608
+#, python-format
+msgid "%dB"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:610
+#, python-format
+msgid "%dKB"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:612
+#, python-format
+msgid "%dMB"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:629
+#, python-format
+msgid "%s of %s"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:640
+#, python-format
+msgid "Transfer from %r"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:650
+msgid "Accept"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:673
+#: ../src/jarabe/frame/activitiestray.py:780
+#, python-format
+msgid "%s (%s)"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:751
+#, python-format
+msgid "Transfer to %r"
+msgstr ""
+
+#: ../src/jarabe/frame/clipboardmenu.py:52
+msgid "Remove"
+msgstr ""
+
+#: ../src/jarabe/frame/clipboardmenu.py:57
+#: ../src/jarabe/frame/clipboardmenu.py:80
+msgid "Open"
+msgstr ""
+
+#: ../src/jarabe/frame/clipboardmenu.py:85
+msgid "Open with"
+msgstr ""
+
+#: ../src/jarabe/frame/clipboardobject.py:47
+#, python-format
+msgid "%s clipping"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:36
+msgid "Neighborhood"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:38
+msgid "Group"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:40
+msgid "Home"
+msgstr ""
+
+#: ../src/jarabe/intro/window.py:124
+msgid "Click to change color:"
+msgstr ""
+
+#: ../src/jarabe/intro/window.py:174 ../src/jarabe/journal/detailview.py:103
+msgid "Back"
+msgstr ""
+
+#: ../src/jarabe/intro/window.py:191
+msgid "Next"
+msgstr ""
+
+#: ../src/jarabe/journal/collapsedentry.py:258
+#: ../src/jarabe/journal/expandedentry.py:159
+#: ../src/jarabe/journal/palettes.py:66
+msgid "Untitled"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:205
+msgid "No preview"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:224
+msgid "Participants:"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:247
+msgid "Description:"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:273
+msgid "Tags:"
+msgstr ""
+
+#: ../src/jarabe/journal/journalactivity.py:108
+#: ../src/jarabe/journal/volumestoolbar.py:47
+msgid "Journal"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:65
+msgid "Search"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:124
+msgid "Anytime"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:126
+msgid "Today"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:128
+msgid "Since yesterday"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 7 days.
+#: ../src/jarabe/journal/journaltoolbox.py:130
+msgid "Past week"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 30 days.
+#: ../src/jarabe/journal/journaltoolbox.py:132
+msgid "Past month"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 356 days.
+#: ../src/jarabe/journal/journaltoolbox.py:134
+msgid "Past year"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:141
+msgid "Anyone"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:143
+msgid "My friends"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:144
+msgid "My class"
+msgstr ""
+
+#. TRANS: Item in a combo box that filters by entry type.
+#: ../src/jarabe/journal/journaltoolbox.py:271
+msgid "Anything"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:347
+#: ../src/jarabe/journal/palettes.py:90
+msgid "Copy"
+msgstr ""
+
+#. TRANS: Action label for starting an entry.
+#: ../src/jarabe/journal/journaltoolbox.py:428
+#: ../src/jarabe/journal/palettes.py:75 ../src/jarabe/view/palettes.py:111
+msgid "Start"
+msgstr ""
+
+#: ../src/jarabe/journal/listview.py:40
+msgid "Your Journal is empty"
+msgstr ""
+
+#: ../src/jarabe/journal/listview.py:41
+msgid "No matching entries "
+msgstr ""
+
+#: ../src/jarabe/journal/listview.py:369
+msgid "Clear search"
+msgstr ""
+
+#: ../src/jarabe/journal/misc.py:92
+msgid "No date"
+msgstr ""
+
+#: ../src/jarabe/journal/modalalert.py:63
+msgid "Your Journal is full"
+msgstr ""
+
+#: ../src/jarabe/journal/modalalert.py:67
+msgid "Please delete some old Journal entries to make space for new ones."
+msgstr ""
+
+#: ../src/jarabe/journal/modalalert.py:79
+msgid "Show Journal"
+msgstr ""
+
+#: ../src/jarabe/journal/objectchooser.py:136
+msgid "Choose an object"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:73
+msgid "Resume with"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:76
+msgid "Start with"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:98
+msgid "Send to"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:107
+msgid "View Details"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:185
+msgid "No friends present"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:190
+msgid "No valid connection found"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:218
+msgid "No activity to resume entry"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:220
+msgid "No activity to start entry"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:61
+msgid "Remove friend"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:64
+msgid "Make friend"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:81
+msgid "My Settings"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:86
+msgid "Logout"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:91
+msgid "Restart"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:96
+msgid "Shutdown"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:131
+#, python-format
+msgid "Invite to %s"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:43
+msgid "Starting..."
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:73
+msgid "Stop"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:145
+msgid "Remove favorite"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:149
+msgid "Make favorite"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:201
+msgid "Show contents"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:223 ../src/jarabe/view/palettes.py:272
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:248
+msgid "Unmount"
+msgstr ""
diff --git a/shell/po/ig.po b/shell/po/ig.po
new file mode 100644
index 0000000..7287d5e
--- /dev/null
+++ b/shell/po/ig.po
@@ -0,0 +1,448 @@
+# translation of sugar.po to Igbo
+# translation of sugar.po to
+# This file is distributed under the same license as the PACKAGE package.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER, 2006.
+# Onye, Sylvester <sylvester@wazobialinux.com>, 2006.
+msgid ""
+msgstr ""
+"Project-Id-Version: sugar\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-01-18 18:19+0000\n"
+"PO-Revision-Date: 2006-07-07 10:23+0100\n"
+"Last-Translator: Onye, Sylvester <sylvester@wazobialinux.com>\n"
+"Language-Team: Igbo\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: KBabel 1.10.2\n"
+
+#: ../shell/PresenceWindow.py:62
+msgid "Who's around:"
+msgstr "Onye nọ ya:"
+
+#: ../shell/PresenceWindow.py:104
+msgid "Share"
+msgstr "Òkè"
+
+#: ../shell/StartPage.py:189
+msgid "Search"
+msgstr "Chọ̀ọ́"
+
+#: ../shell/intro/intro.py:146
+msgid "Back"
+msgstr "Àzụ́"
+
+#: ../activities/browser/NavigationToolbar.py:23
+msgid "Forward"
+msgstr "Íhú"
+
+#: ../activities/browser/NavigationToolbar.py:29
+msgid "Reload"
+msgstr "Bubatagharịa"
+
+#: ../shell/shell.py:333
+msgid "Everyone"
+msgstr "Onyeọbụla"
+
+#: ../sugar/chat/ChatEditor.py:43
+msgid "Send"
+msgstr "Ziga"
+
+#: ../shell/intro/intro.py:67
+msgid "Name:"
+msgstr ""
+
+#: ../shell/intro/intro.py:96
+msgid "Click to change color:"
+msgstr ""
+
+#: ../shell/intro/intro.py:160
+msgid "Done"
+msgstr ""
+
+#: ../shell/intro/intro.py:163
+msgid "Next"
+msgstr ""
+
+#: ../shell/view/BuddyMenu.py:59
+msgid "Remove friend"
+msgstr ""
+
+#: ../shell/view/BuddyMenu.py:62
+msgid "Make friend"
+msgstr ""
+
+#: ../shell/view/BuddyMenu.py:84
+#, python-format
+msgid "Invite to %s"
+msgstr ""
+
+#: ../shell/view/clipboardmenu.py:58
+msgid "Remove"
+msgstr ""
+
+#: ../shell/view/clipboardmenu.py:63
+msgid "Open"
+msgstr ""
+
+#. self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+#. TODO: Implement stopping downloads
+#. self._stop_item.connect('activate', self._stop_item_activate_cb)
+#. self.append_menu_item(self._stop_item)
+#: ../shell/view/clipboardmenu.py:73
+msgid "Add to journal"
+msgstr ""
+
+#: ../shell/view/clipboardmenu.py:213
+#, python-format
+msgid "Clipboard object: %s."
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:149
+msgid "Key Type:"
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:169
+msgid "Authentication Type:"
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:250
+msgid "Encryption Type:"
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:90
+msgid "Starting..."
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:104 ../shell/view/home/MeshBox.py:295
+msgid "Resume"
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:111
+#: ../lib/sugar/activity/activity.py:132
+msgid "Stop"
+msgstr ""
+
+#: ../shell/view/Shell.py:285
+msgid "Screenshot"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:159
+msgid "Reboot"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:164
+msgid "Shutdown"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:170
+msgid "Register"
+msgstr ""
+
+#. Only show disconnect when there's a mesh device, because mesh takes
+#. priority over the normal wireless device. NM doesn't have a "disconnect"
+#. method for a device either (for various reasons) so this doesn't
+#. have a good mapping
+#: ../shell/view/home/MeshBox.py:90 ../shell/view/home/MeshBox.py:197
+#: ../shell/view/devices/network/wireless.py:113
+#: ../shell/view/devices/network/mesh.py:83
+msgid "Disconnect..."
+msgstr ""
+
+#: ../shell/view/home/MeshBox.py:195 ../shell/view/devices/network/mesh.py:37
+#: ../shell/view/devices/network/mesh.py:62
+#: ../shell/view/devices/network/mesh.py:66
+msgid "Mesh Network"
+msgstr ""
+
+#: ../shell/view/home/MeshBox.py:300
+msgid "Join"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:38
+msgid "My Battery life"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:94
+msgid "Battery charging"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:96
+msgid "Battery discharging"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:98
+msgid "Battery fully charged"
+msgstr ""
+
+#: ../shell/view/devices/network/wireless.py:61
+msgid "Disconnected"
+msgstr ""
+
+#: ../shell/view/devices/network/wireless.py:131
+msgid "Channel"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:42
+msgid "Neighborhood"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:54
+msgid "Group"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:66
+msgid "Home"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:78
+msgid "Activity"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:115
+msgid "Share with:"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:117
+msgid "Private"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:118
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:126
+msgid "Keep"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:245
+msgid "Undo"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:250
+msgid "Redo"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:260
+msgid "Copy"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:265
+msgid "Paste"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:454
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:824
+msgid "Keep error"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:825
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:828
+msgid "Don't stop"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:831
+msgid "Stop anyway"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:164 ../lib/sugar/graphics/alert.py:206
+msgid "Cancel"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:168
+msgid "Ok"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:216
+msgid "Continue"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:244
+msgid "OK"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d year"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d years"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d month"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d months"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d week"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d weeks"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d day"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d days"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hour"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hours"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minute"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minutes"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d second"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d seconds"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:191
+msgid " and "
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:193
+msgid ", "
+msgstr ""
+
+#: ../shell/controlpanel/control.py:213
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:267
+msgid "Error in specified color modifiers."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:270
+msgid "Error in specified colors."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:307
+msgid "off"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:309
+msgid "on"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:310
+msgid "State is unknown."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:332
+msgid "Error in specified radio argument use on/off."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:336
+msgid "Permission denied. You need to be root to run this method."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:366
+msgid "Error in reading timezone"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:397
+#, python-format
+msgid "Error copying timezone (from %s): %s"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:402
+#, python-format
+msgid "Changing permission of timezone: %s"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:412
+msgid "Error timezone does not exist."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:417 ../shell/controlpanel/control.py:436
+#, python-format
+msgid "Could not access %s. Create standard settings."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:463
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:473
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:105
+msgid "Connected to a School Mesh Portal"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:107
+msgid "Looking for a School Mesh Portal..."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:110
+msgid "Connected to an XO Mesh Portal"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:112
+msgid "Looking for an XO Mesh Portal..."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:115
+msgid "Connected to a Simple Mesh"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:117
+msgid "Starting a Simple Mesh"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:124
+msgid "Unknown Mesh"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:175 ../shell/view/home/HomeBox.py:216
+msgid "About this XO"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:222
+msgid "Not available"
+msgstr ""
diff --git a/shell/po/is.po b/shell/po/is.po
new file mode 100644
index 0000000..a79e711
--- /dev/null
+++ b/shell/po/is.po
@@ -0,0 +1,420 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-01-18 18:19+0000\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.0.1\n"
+
+#: ../shell/intro/intro.py:67
+msgid "Name:"
+msgstr ""
+
+#: ../shell/intro/intro.py:96
+msgid "Click to change color:"
+msgstr ""
+
+#: ../shell/intro/intro.py:146
+msgid "Back"
+msgstr ""
+
+#: ../shell/intro/intro.py:160
+msgid "Done"
+msgstr ""
+
+#: ../shell/intro/intro.py:163
+msgid "Next"
+msgstr ""
+
+#: ../shell/view/BuddyMenu.py:59
+msgid "Remove friend"
+msgstr ""
+
+#: ../shell/view/BuddyMenu.py:62
+msgid "Make friend"
+msgstr ""
+
+#: ../shell/view/BuddyMenu.py:84
+#, python-format
+msgid "Invite to %s"
+msgstr ""
+
+#: ../shell/view/clipboardmenu.py:58
+msgid "Remove"
+msgstr ""
+
+#: ../shell/view/clipboardmenu.py:63
+msgid "Open"
+msgstr ""
+
+#. self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+#. TODO: Implement stopping downloads
+#. self._stop_item.connect('activate', self._stop_item_activate_cb)
+#. self.append_menu_item(self._stop_item)
+#: ../shell/view/clipboardmenu.py:73
+msgid "Add to journal"
+msgstr ""
+
+#: ../shell/view/clipboardmenu.py:213
+#, python-format
+msgid "Clipboard object: %s."
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:149
+msgid "Key Type:"
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:169
+msgid "Authentication Type:"
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:250
+msgid "Encryption Type:"
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:90
+msgid "Starting..."
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:104 ../shell/view/home/MeshBox.py:295
+msgid "Resume"
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:111
+#: ../lib/sugar/activity/activity.py:132
+msgid "Stop"
+msgstr ""
+
+#: ../shell/view/Shell.py:285
+msgid "Screenshot"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:159
+msgid "Reboot"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:164
+msgid "Shutdown"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:170
+msgid "Register"
+msgstr ""
+
+#. Only show disconnect when there's a mesh device, because mesh takes
+#. priority over the normal wireless device. NM doesn't have a "disconnect"
+#. method for a device either (for various reasons) so this doesn't
+#. have a good mapping
+#: ../shell/view/home/MeshBox.py:90 ../shell/view/home/MeshBox.py:197
+#: ../shell/view/devices/network/wireless.py:113
+#: ../shell/view/devices/network/mesh.py:83
+msgid "Disconnect..."
+msgstr ""
+
+#: ../shell/view/home/MeshBox.py:195 ../shell/view/devices/network/mesh.py:37
+#: ../shell/view/devices/network/mesh.py:62
+#: ../shell/view/devices/network/mesh.py:66
+msgid "Mesh Network"
+msgstr ""
+
+#: ../shell/view/home/MeshBox.py:300
+msgid "Join"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:38
+msgid "My Battery life"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:94
+msgid "Battery charging"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:96
+msgid "Battery discharging"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:98
+msgid "Battery fully charged"
+msgstr ""
+
+#: ../shell/view/devices/network/wireless.py:61
+msgid "Disconnected"
+msgstr ""
+
+#: ../shell/view/devices/network/wireless.py:131
+msgid "Channel"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:42
+msgid "Neighborhood"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:54
+msgid "Group"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:66
+msgid "Home"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:78
+msgid "Activity"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:115
+msgid "Share with:"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:117
+msgid "Private"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:118
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:126
+msgid "Keep"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:245
+msgid "Undo"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:250
+msgid "Redo"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:260
+msgid "Copy"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:265
+msgid "Paste"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:454
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:824
+msgid "Keep error"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:825
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:828
+msgid "Don't stop"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:831
+msgid "Stop anyway"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:164 ../lib/sugar/graphics/alert.py:206
+msgid "Cancel"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:168
+msgid "Ok"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:216
+msgid "Continue"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:244
+msgid "OK"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d year"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d years"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d month"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d months"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d week"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d weeks"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d day"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d days"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hour"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hours"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minute"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minutes"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d second"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d seconds"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:191
+msgid " and "
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:193
+msgid ", "
+msgstr ""
+
+#: ../shell/controlpanel/control.py:213
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:267
+msgid "Error in specified color modifiers."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:270
+msgid "Error in specified colors."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:307
+msgid "off"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:309
+msgid "on"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:310
+msgid "State is unknown."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:332
+msgid "Error in specified radio argument use on/off."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:336
+msgid "Permission denied. You need to be root to run this method."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:366
+msgid "Error in reading timezone"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:397
+#, python-format
+msgid "Error copying timezone (from %s): %s"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:402
+#, python-format
+msgid "Changing permission of timezone: %s"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:412
+msgid "Error timezone does not exist."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:417 ../shell/controlpanel/control.py:436
+#, python-format
+msgid "Could not access %s. Create standard settings."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:463
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:473
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:105
+msgid "Connected to a School Mesh Portal"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:107
+msgid "Looking for a School Mesh Portal..."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:110
+msgid "Connected to an XO Mesh Portal"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:112
+msgid "Looking for an XO Mesh Portal..."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:115
+msgid "Connected to a Simple Mesh"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:117
+msgid "Starting a Simple Mesh"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:124
+msgid "Unknown Mesh"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:175 ../shell/view/home/HomeBox.py:216
+msgid "About this XO"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:222
+msgid "Not available"
+msgstr ""
diff --git a/shell/po/it.po b/shell/po/it.po
new file mode 100644
index 0000000..d9a8548
--- /dev/null
+++ b/shell/po/it.po
@@ -0,0 +1,1668 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-02-11 00:32-0500\n"
+"PO-Revision-Date: 2010-03-17 17:21+0200\n"
+"Last-Translator: Carlo Falciola <cfalciola@yahoo.it>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: it\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Pootle 2.0.3\n"
+
+#: ../extensions/cpsection/aboutme/__init__.py:24
+msgid "About Me"
+msgstr "Informazioni su"
+
+#: ../extensions/cpsection/aboutme/model.py:43
+msgid "You must enter a name."
+msgstr "Devi inserire un nome."
+
+#: ../extensions/cpsection/aboutme/model.py:68
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr "linea: colore=%s tinta=%s"
+
+#: ../extensions/cpsection/aboutme/model.py:71
+#, python-format
+msgid "stroke: %s"
+msgstr "linea: %s"
+
+#: ../extensions/cpsection/aboutme/model.py:73
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr "riempimento: colore=%s tinta=%s"
+
+#: ../extensions/cpsection/aboutme/model.py:75
+#, python-format
+msgid "fill: %s"
+msgstr "riempimento: %s"
+
+#: ../extensions/cpsection/aboutme/model.py:86
+msgid "Error in specified color modifiers."
+msgstr "Errore nella variazione dei colori richiesta"
+
+#: ../extensions/cpsection/aboutme/model.py:89
+msgid "Error in specified colors."
+msgstr "Errore nella definizione dei colori."
+
+#: ../extensions/cpsection/aboutme/view.py:94 ../src/jarabe/intro/window.py:93
+msgid "Name:"
+msgstr "Nome:"
+
+#: ../extensions/cpsection/aboutme/view.py:128
+msgid "Click to change your color:"
+msgstr "Seleziona per cambiare il tuo colore:"
+
+#: ../extensions/cpsection/aboutcomputer/__init__.py:21
+msgid "About my Computer"
+msgstr "Il mio Computer"
+
+#: ../extensions/cpsection/aboutcomputer/model.py:28
+msgid "Not available"
+msgstr "Non disponibile"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:60
+msgid "Identity"
+msgstr "Identità"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:69
+msgid "Serial Number:"
+msgstr "Numero di Serie:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:91
+msgid "Software"
+msgstr "Software"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:100
+msgid "Build:"
+msgstr "Build:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:115
+msgid "Sugar:"
+msgstr "Sugar:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:131
+msgid "Firmware:"
+msgstr "Firmware:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:146
+msgid "Wireless Firmware:"
+msgstr "Wireless Firmware:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:169
+msgid "Copyright and License"
+msgstr "Copyright e Licenza"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:184
+msgid ""
+"Sugar is the graphical user interface that you are looking at. Sugar is free "
+"software, covered by the GNU General Public License, and you are welcome to "
+"change it and/or distribute copies of it under certain conditions described "
+"therein."
+msgstr ""
+"Sugar è l'interfaccia utente che stai utilizzando in questo istante. Sugar è "
+"Software Libero, protetto dalla licenza General Public License di GNU, e "
+"chiunque è il benvenuto per apportare modifiche e migliorie e/o distribuirne "
+"copie, alle condizioni descritte nella licenza medesima."
+
+#: ../extensions/cpsection/aboutcomputer/view.py:196
+msgid "Full license:"
+msgstr "Testo della Licenza:"
+
+#: ../extensions/cpsection/datetime/__init__.py:21
+msgid "Date & Time"
+msgstr "Data e Ora"
+
+#: ../extensions/cpsection/datetime/model.py:87
+msgid "Error timezone does not exist."
+msgstr "Errore, timezone non esistente."
+
+#: ../extensions/cpsection/datetime/view.py:68 ../data/sugar.schemas.in.h:33
+msgid "Timezone"
+msgstr "Timezone"
+
+#: ../extensions/cpsection/frame/__init__.py:21
+msgid "Frame"
+msgstr "Cornice"
+
+#: ../extensions/cpsection/frame/model.py:38
+#: ../extensions/cpsection/frame/model.py:60
+msgid "Value must be an integer."
+msgstr "Valore deve essere un intero."
+
+#: ../extensions/cpsection/frame/view.py:26
+msgid "never"
+msgstr "mai"
+
+#: ../extensions/cpsection/frame/view.py:27
+msgid "instantaneous"
+msgstr "istantaneamente"
+
+#: ../extensions/cpsection/frame/view.py:28
+#, python-format
+msgid "%s seconds"
+msgstr "%s secondi"
+
+#: ../extensions/cpsection/frame/view.py:52
+msgid "Activation Delay"
+msgstr "Ritardo attivazione"
+
+#: ../extensions/cpsection/frame/view.py:76
+msgid "Corner"
+msgstr "Angolo"
+
+#: ../extensions/cpsection/frame/view.py:111
+msgid "Edge"
+msgstr "Margine"
+
+#: ../extensions/cpsection/keyboard/__init__.py:21
+#: ../extensions/cpsection/keyboard/view.py:31
+msgid "Keyboard"
+msgstr "Tastiera"
+
+#: ../extensions/cpsection/keyboard/view.py:189
+msgid "Keyboard Model"
+msgstr "Modello Tastiera"
+
+#: ../extensions/cpsection/keyboard/view.py:248
+msgid "Key(s) to change layout"
+msgstr "Tasto/i per cambiare la disposizione"
+
+#: ../extensions/cpsection/keyboard/view.py:318
+msgid "Keyboard Layout(s)"
+msgstr "Disposizione Tastiera"
+
+#: ../extensions/cpsection/language/__init__.py:21
+#: ../extensions/cpsection/language/view.py:33
+msgid "Language"
+msgstr "Lingua"
+
+#: ../extensions/cpsection/language/model.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr "Impossibile accedere a ~/.i18n. Creazione configurazione standard."
+
+#: ../extensions/cpsection/language/model.py:124
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr "Linguaggio con codice=%s sconosciuto."
+
+#: ../extensions/cpsection/language/model.py:144
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr "Spiacente, ma non parlo '%s'."
+
+#: ../extensions/cpsection/language/view.py:56
+msgid ""
+"Add languages in the order you prefer. If a translation is not available, "
+"the next in the list will be used."
+msgstr ""
+"Aggiungi i linguaggi nell'ordine che preferisci. Se una traduzione non fosse "
+"disponibile, verrà utilizzata quella successiva della lista."
+
+#: ../extensions/cpsection/modemconfiguration/__init__.py:21
+msgid "Modem Configuration"
+msgstr "Configurazione del modem"
+
+#: ../extensions/cpsection/modemconfiguration/view.py:90
+msgid "Username:"
+msgstr "Utente:"
+
+#: ../extensions/cpsection/modemconfiguration/view.py:101
+msgid "Password:"
+msgstr "Password:"
+
+#: ../extensions/cpsection/modemconfiguration/view.py:112
+msgid "Number:"
+msgstr "Numero:"
+
+#: ../extensions/cpsection/modemconfiguration/view.py:123
+msgid "APN:"
+msgstr "APN:"
+
+#: ../extensions/cpsection/network/__init__.py:21
+#: ../extensions/cpsection/network/view.py:28
+msgid "Network"
+msgstr "Network"
+
+#: ../extensions/cpsection/network/model.py:79
+msgid "State is unknown."
+msgstr "Stato sconosciuto."
+
+#: ../extensions/cpsection/network/model.py:105
+msgid "Error in specified radio argument use on/off."
+msgstr "Errore nel campo specificato, utilizzare on/off."
+
+#: ../extensions/cpsection/network/model.py:137
+msgid "Error in specified argument use 0/1."
+msgstr "Errore nel campo specificato, utilizzare 0/1."
+
+#: ../extensions/cpsection/network/view.py:59
+msgid "Wireless"
+msgstr "Wireless"
+
+#: ../extensions/cpsection/network/view.py:67
+msgid "Turn off the wireless radio to save battery life"
+msgstr "Spegni il trasmettitore wireless per risparmiare carica della batteria"
+
+#: ../extensions/cpsection/network/view.py:80
+msgid "Radio"
+msgstr "Radio"
+
+#: ../extensions/cpsection/network/view.py:96
+msgid "Discard network history if you have trouble connecting to the network"
+msgstr ""
+"Elimina la storia delle connessioni di rete effettuate nel caso di problemi "
+"di connessione"
+
+#: ../extensions/cpsection/network/view.py:105
+msgid "Discard network history"
+msgstr "Elimina la storia delle connessioni di rete"
+
+#: ../extensions/cpsection/network/view.py:118
+msgid "Collaboration"
+msgstr "Collaborazione"
+
+#: ../extensions/cpsection/network/view.py:126
+msgid ""
+"The server is the equivalent of what room you are in; people on the same "
+"server will be able to see each other, even when they aren't on the same "
+"network."
+msgstr ""
+"Il Server equivale alla stanza in cui si sta; le persone collegate allo "
+"stesso server sono in grado di vedersi fra loro, anche se collegate a reti "
+"differenti."
+
+#: ../extensions/cpsection/network/view.py:136
+msgid "Server:"
+msgstr "Server:"
+
+#: ../extensions/cpsection/power/__init__.py:21
+msgid "Power"
+msgstr "Energia"
+
+#: ../extensions/cpsection/power/model.py:54
+msgid "Error in automatic pm argument, use on/off."
+msgstr ""
+"Errore nel campo Gestione Automatica Risparmio Energetico, utilizza on/off"
+
+#: ../extensions/cpsection/power/model.py:81
+msgid "Error in extreme pm argument, use on/off."
+msgstr "Errore nel campo Gestione Estrema Risparmio Energetico, utilizza on/off"
+
+#: ../extensions/cpsection/power/view.py:47
+msgid "Power management"
+msgstr "Gestione Risparmio Energetico (power management)"
+
+#: ../extensions/cpsection/power/view.py:57
+msgid "Automatic power management (increases battery life)"
+msgstr ""
+"Gestione Automatica Risparmio Energetico (incrementa la durata delle "
+"batterie)"
+
+#: ../extensions/cpsection/power/view.py:85
+msgid ""
+"Extreme power management (disableswireless radio, increases battery life)"
+msgstr ""
+"Gestione Risparmio Energetico Estrema (spegne la connessione radio wireless, "
+"incrementa la durata delle batterie)"
+
+#: ../extensions/cpsection/updater/__init__.py:21
+msgid "Software update"
+msgstr "Aggiornamento software"
+
+#: ../extensions/cpsection/updater/view.py:63
+msgid ""
+"Software updates correct errors, eliminate security vulnerabilities, and "
+"provide new features."
+msgstr ""
+"Gli aggiornamenti software correggono errori, eliminano vulnerabilità del "
+"codice ed offrono nuove funzioni. "
+
+#: ../extensions/cpsection/updater/view.py:125
+#, python-format
+msgid "Checking %s..."
+msgstr "Verifica %s..."
+
+#: ../extensions/cpsection/updater/view.py:127
+#, python-format
+msgid "Downloading %s..."
+msgstr "Scaricando %s..."
+
+#: ../extensions/cpsection/updater/view.py:129
+#, python-format
+msgid "Updating %s..."
+msgstr "Aggirnamento %s..."
+
+#: ../extensions/cpsection/updater/view.py:139
+msgid "Your software is up-to-date"
+msgstr "Il tuo software è aggiornato"
+
+#: ../extensions/cpsection/updater/view.py:141
+#, python-format
+msgid "You can install %s update"
+msgid_plural "You can install %s updates"
+msgstr[0] "Puoi installare %s aggiornamento"
+msgstr[1] "Puoi installare %s aggiornamenti"
+
+#: ../extensions/cpsection/updater/view.py:159
+msgid "Checking for updates..."
+msgstr "Ricerca aggiornamenti..."
+
+#: ../extensions/cpsection/updater/view.py:164
+msgid "Installing updates..."
+msgstr "Installazione aggiornamenti..."
+
+#: ../extensions/cpsection/updater/view.py:172
+#, python-format
+msgid "%s update was installed"
+msgid_plural "%s updates were installed"
+msgstr[0] "%s aggiornamento è stato installato"
+msgstr[1] "%s aggiornamenti sono stati installati"
+
+#: ../extensions/cpsection/updater/view.py:253
+msgid "Install selected"
+msgstr "Installa selezionati"
+
+#: ../extensions/cpsection/updater/view.py:274
+#, python-format
+msgid "Download size: %s"
+msgstr "Dimensione dati da scaricare: %s"
+
+#: ../extensions/cpsection/updater/view.py:362
+#, python-format
+msgid "From version %(current)d to %(new)s (Size: %(size)s)"
+msgstr "Dalla versione %(current)d alla %(new)s (Dimensione: %(size)s)"
+
+#. TRANS: download size is 0
+#: ../extensions/cpsection/updater/view.py:382
+msgid "None"
+msgstr "Nessuno"
+
+#. TRANS: download size of very small updates
+#: ../extensions/cpsection/updater/view.py:385
+msgid "1 KB"
+msgstr "1 KB"
+
+#. TRANS: download size of small updates, e.g. '250 KB'
+#: ../extensions/cpsection/updater/view.py:388
+#, python-format
+msgid "%.0f KB"
+msgstr "%.0f KB"
+
+#. TRANS: download size of updates, e.g. '2.3 MB'
+#: ../extensions/cpsection/updater/view.py:391
+#, python-format
+msgid "%.1f MB"
+msgstr "%.1f MB"
+
+#: ../extensions/deviceicon/battery.py:58
+msgid "My Battery"
+msgstr "La mia Batteria"
+
+#: ../extensions/deviceicon/battery.py:137
+msgid "Removed"
+msgstr "Rimosso"
+
+#: ../extensions/deviceicon/battery.py:140
+msgid "Charging"
+msgstr "Caricando"
+
+#: ../extensions/deviceicon/battery.py:143
+msgid "Very little power remaining"
+msgstr "Pochissima carica rimanente"
+
+#: ../extensions/deviceicon/battery.py:149
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr "%(hour)d:%(min).2d rimanenti"
+
+#: ../extensions/deviceicon/battery.py:152
+msgid "Charged"
+msgstr "Carica"
+
+#: ../extensions/deviceicon/network.py:49
+#, python-format
+msgid "IP address: %s"
+msgstr "Indirizzo IP: %s"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#: ../extensions/deviceicon/network.py:111
+msgid "Disconnect..."
+msgstr "Disconnessione..."
+
+#: ../extensions/deviceicon/network.py:116
+msgid "Create new wireless network"
+msgstr "Crea una nuova rete wireless"
+
+#: ../extensions/deviceicon/network.py:122
+#: ../extensions/deviceicon/network.py:284
+#: ../src/jarabe/desktop/meshbox.py:248 ../src/jarabe/desktop/meshbox.py:537
+msgid "Connecting..."
+msgstr "Connessione..."
+
+# TODO: show the channel number
+#: ../extensions/deviceicon/network.py:126
+#: ../extensions/deviceicon/network.py:198
+#: ../extensions/deviceicon/network.py:288
+#: ../src/jarabe/desktop/meshbox.py:254 ../src/jarabe/desktop/meshbox.py:543
+msgid "Connected"
+msgstr "Connesso"
+
+#: ../extensions/deviceicon/network.py:158
+msgid "Channel"
+msgstr "Canale"
+
+#: ../extensions/deviceicon/network.py:173
+msgid "Wired Network"
+msgstr "Rete su cavo"
+
+#: ../extensions/deviceicon/network.py:201
+msgid "Speed"
+msgstr "Velocità"
+
+#: ../extensions/deviceicon/network.py:228
+msgid "Wireless modem"
+msgstr "Modem Wireless"
+
+#: ../extensions/deviceicon/network.py:276
+msgid "Please wait..."
+msgstr "Attendi..."
+
+#: ../extensions/deviceicon/network.py:279
+#: ../src/jarabe/desktop/meshbox.py:164 ../src/jarabe/desktop/meshbox.py:494
+msgid "Connect"
+msgstr "Connetti"
+
+#: ../extensions/deviceicon/network.py:280
+msgid "Disconnected"
+msgstr "Disconnesso"
+
+#: ../extensions/deviceicon/network.py:283
+#: ../src/jarabe/controlpanel/toolbar.py:115
+#: ../src/jarabe/desktop/homebox.py:68
+#: ../src/jarabe/frame/activitiestray.py:700
+#: ../src/jarabe/frame/activitiestray.py:799
+#: ../src/jarabe/frame/activitiestray.py:827
+msgid "Cancel"
+msgstr "Cancella"
+
+#: ../extensions/deviceicon/network.py:287
+#: ../src/jarabe/desktop/meshbox.py:168
+msgid "Disconnect"
+msgstr "Disconnetti"
+
+#: ../extensions/deviceicon/network.py:530
+#, python-format
+msgid "%s's network"
+msgstr "rete di %s"
+
+# A complete translation in italian: "rete a maglie" becames a tautology
+#: ../extensions/deviceicon/network.py:597
+#: ../extensions/deviceicon/network.py:656
+msgid "Mesh Network"
+msgstr "Rete Mesh"
+
+#: ../extensions/deviceicon/network.py:857
+#, python-format
+msgid "Data sent %d kb / received %d kb"
+msgstr "Dati %d kb inviati / %d kb ricevuti"
+
+#: ../extensions/deviceicon/network.py:868
+msgid "Connection time "
+msgstr "Tempo di connessione"
+
+#: ../extensions/deviceicon/speaker.py:59
+msgid "My Speakers"
+msgstr "I miei Altoparlanti"
+
+#: ../extensions/deviceicon/speaker.py:133
+msgid "Unmute"
+msgstr "Attiva"
+
+#: ../extensions/deviceicon/speaker.py:136
+msgid "Mute"
+msgstr "Silenzia"
+
+#: ../extensions/globalkey/screenshot.py:59
+msgid "Mesh"
+msgstr "Mesh"
+
+#: ../extensions/globalkey/screenshot.py:61
+#: ../src/jarabe/frame/zoomtoolbar.py:39
+msgid "Group"
+msgstr "Gruppo"
+
+#: ../extensions/globalkey/screenshot.py:63
+#: ../src/jarabe/frame/zoomtoolbar.py:41
+msgid "Home"
+msgstr "Casa"
+
+#: ../extensions/globalkey/screenshot.py:69
+#: ../src/jarabe/frame/zoomtoolbar.py:43
+msgid "Activity"
+msgstr "Attività"
+
+#: ../extensions/globalkey/screenshot.py:72
+msgid "Screenshot"
+msgstr "Schermata"
+
+#: ../extensions/globalkey/screenshot.py:74
+#, python-format
+msgid "Screenshot of \"%s\""
+msgstr "Schermata di \"%s\""
+
+#: ../data/sugar.schemas.in.h:1
+msgid ""
+"\"disabled\" to ask nick on initialization; \"system\" to reuse UNIX account "
+"long name."
+msgstr ""
+"se \"disabled\" viene richiesto il nome alla inizializzazione; se \"system\" "
+"verrà utilizzato il nome presente nell'account UNIX."
+
+#: ../data/sugar.schemas.in.h:2
+msgid "Backup URL"
+msgstr "URL di salvataggio"
+
+#: ../data/sugar.schemas.in.h:3
+msgid ""
+"Color for the XO icon that is used throughout the desktop. The string is "
+"composed of the stroke color and fill color, format is that of rbg colors. "
+"Example: #AC32FF,#9A5200"
+msgstr ""
+"Colore per l'icona XO che viene utilizzata nel sistema. La stringa è "
+"composta dal colore della linea e dal colore del riempimento, il formato è "
+"quello dei colori rbg. Esempio: #AC32FF,#9A5200"
+
+#: ../data/sugar.schemas.in.h:4
+msgid "Corner Delay"
+msgstr "Ritardo dell'angolo"
+
+#: ../data/sugar.schemas.in.h:5
+msgid "Default font face"
+msgstr "Tipo di carattere di default"
+
+#: ../data/sugar.schemas.in.h:6
+msgid "Default font size"
+msgstr "Dimensione del carattere di default"
+
+#: ../data/sugar.schemas.in.h:7
+msgid "Default nick"
+msgstr "Nome di default"
+
+#: ../data/sugar.schemas.in.h:8
+msgid "Delay for the activation of the frame using the corners."
+msgstr ""
+"Ritardo nella attivazione della cornice portando il puntatore in un angolo."
+
+#: ../data/sugar.schemas.in.h:9
+msgid "Delay for the activation of the frame using the edges."
+msgstr "Ritardo nella attivazione della cornice portando il puntatore sui bordi"
+
+#: ../data/sugar.schemas.in.h:10
+msgid "Edge Delay"
+msgstr "Ritardo del bordo"
+
+#: ../data/sugar.schemas.in.h:11
+msgid "Favorites Layout"
+msgstr "Disposizione delle attività preferite"
+
+#: ../data/sugar.schemas.in.h:12
+msgid "Favorites resume mode"
+msgstr "Modalità di richiamo delle attività preferite"
+
+#: ../data/sugar.schemas.in.h:13
+msgid "Font face that is used throughout the desktop."
+msgstr "Tipo di carattere di default utilizzato in tutto il sistema."
+
+#: ../data/sugar.schemas.in.h:14
+msgid "Font size that is used throughout the desktop."
+msgstr "Dimensione del carattere utilizzato in tutto il sistema."
+
+#: ../data/sugar.schemas.in.h:15
+msgid ""
+"If TRUE, Sugar will make us searchable for the other users of the Jabber "
+"server."
+msgstr ""
+"Se TRUE, Sugar renderà il computer rintracciabile dagli altri utenti del "
+"server Jabber."
+
+#: ../data/sugar.schemas.in.h:16
+msgid "If TRUE, Sugar will show a \"Log out\" option."
+msgstr "Se TRUE, Sugar mostrerà una opzione di \"Disconnessione\"."
+
+#: ../data/sugar.schemas.in.h:17
+msgid "Jabber Server"
+msgstr "Jabber Server"
+
+#: ../data/sugar.schemas.in.h:18
+msgid "Keyboard layouts"
+msgstr "Disposizioni tastiera"
+
+#: ../data/sugar.schemas.in.h:19
+msgid "Keyboard model"
+msgstr "Modello tastiera"
+
+#: ../data/sugar.schemas.in.h:20
+msgid "Keyboard options"
+msgstr "Opzioni tastiera"
+
+#: ../data/sugar.schemas.in.h:21
+msgid "Layout of the favorites view."
+msgstr "Disposizione della vista dei favoriti."
+
+#: ../data/sugar.schemas.in.h:22
+msgid ""
+"List of keyboard layouts. Each entry should be in the form layout(variant)"
+msgstr ""
+"Elenco di disposizioni della tastiera. Ogni voce deve essere nel formato "
+"layout(variant) "
+
+#: ../data/sugar.schemas.in.h:23
+msgid "List of keyboard options."
+msgstr "Lista di opzioni per la tastiera."
+
+#: ../data/sugar.schemas.in.h:24
+msgid "Power Automatic"
+msgstr "Economizzazione Automatica"
+
+#: ../data/sugar.schemas.in.h:25
+msgid "Power Automatic."
+msgstr "Economizzazione Automatica."
+
+#: ../data/sugar.schemas.in.h:26
+msgid "Power Extreme"
+msgstr "Economizzazione Massima"
+
+#: ../data/sugar.schemas.in.h:27
+msgid "Power Extreme."
+msgstr "Economizzazione Massima."
+
+#: ../data/sugar.schemas.in.h:28
+msgid "Publish to Gadget"
+msgstr "Pubblica su Gadget"
+
+#: ../data/sugar.schemas.in.h:29
+msgid "Setting for muting the sound device."
+msgstr "Selezione per silenziare il riproduttore sonoro."
+
+#: ../data/sugar.schemas.in.h:30
+msgid "Show Log out"
+msgstr "Mostra Disconnessione"
+
+#: ../data/sugar.schemas.in.h:31
+msgid "Sound Muted"
+msgstr "Suono Silenziato"
+
+#: ../data/sugar.schemas.in.h:32
+msgid "The keyboard model to be used"
+msgstr "Modello di tastiera da utilizzare"
+
+#: ../data/sugar.schemas.in.h:34
+msgid "Timezone setting for the system."
+msgstr "Selezione per il Timezone del sistema"
+
+#: ../data/sugar.schemas.in.h:35
+msgid "Url of the jabber server to use."
+msgstr "Url del server jabber da utilizzare."
+
+#: ../data/sugar.schemas.in.h:36
+msgid "Url where the backup is saved to."
+msgstr "Url su cui effettuare i salvataggi."
+
+#: ../data/sugar.schemas.in.h:37
+msgid "User Color"
+msgstr "Colore dell'Utente "
+
+#: ../data/sugar.schemas.in.h:38
+msgid "User Name"
+msgstr "Nome Utente"
+
+#: ../data/sugar.schemas.in.h:39
+msgid "User name that is used throughout the desktop."
+msgstr "Nome Utente utilizzato in tutto il sistema."
+
+#: ../data/sugar.schemas.in.h:40
+msgid "Volume Level"
+msgstr "Livello Volume"
+
+#: ../data/sugar.schemas.in.h:41
+msgid "Volume level for the sound device."
+msgstr "Livello del volume del riproduttore di suoni."
+
+#: ../data/sugar.schemas.in.h:42
+msgid ""
+"When in resume mode, clicking on a favorite icon will cause the last entry "
+"for that activity to be resumed."
+msgstr ""
+"Quando è selezionata la modalità di ripresa, selezionando una delle icone "
+"delle attività favorite provocherà il riavvio della ultima versione "
+"utilizzata."
+
+#: ../src/jarabe/controlpanel/cmd.py:28
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+"sugar-control-panel: ATTENZIONE, trovata più di una opzione avente lo stesso "
+"nome: %s modulo: %r"
+
+#: ../src/jarabe/controlpanel/cmd.py:30
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr "sugar-control-panel: key=%s non è una opzione disponibile"
+
+# non credo sia da tradurre....
+#: ../src/jarabe/controlpanel/cmd.py:31
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr "sugar-control-panel: %s"
+
+# TRANS: Translators, there's a empty line at the end of this string,
+# which must appear in the translated string (msgstr) as well.
+#. TRANS: Translators, there's a empty line at the end of this string,
+#. which must appear in the translated string (msgstr) as well.
+#: ../src/jarabe/controlpanel/cmd.py:37
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" -c key clear the current value for the key \n"
+" "
+msgstr ""
+"Utilizzo: sugar-control-panel [ opzioni ] key [ args ... ] \n"
+" Controllo dell'ambiente sugar. \n"
+" Opzioni: \n"
+" -h Mostra questo messaggio di aiuto ed esci\n"
+" -l lista tutte le opzioni disponibili \n"
+" -h key mostra informazioni sulla \"key\" \n"
+" -g key stampa il valore corrente della \"key\" \n"
+" -s key assegna il valore corrente alla \"key\" \n"
+" "
+
+#: ../src/jarabe/controlpanel/cmd.py:50
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr "Per applicare le modifiche è necessario riavviare sugar.\n"
+
+#: ../src/jarabe/controlpanel/gui.py:281
+msgid "Warning"
+msgstr "Attenzione"
+
+#: ../src/jarabe/controlpanel/gui.py:282
+#: ../src/jarabe/controlpanel/sectionview.py:42
+msgid "Changes require restart"
+msgstr "Le modifiche rendono necessario un riavvio"
+
+#: ../src/jarabe/controlpanel/gui.py:285
+msgid "Cancel changes"
+msgstr "Annulla modifiche"
+
+#: ../src/jarabe/controlpanel/gui.py:290 ../src/jarabe/desktop/homebox.py:70
+msgid "Later"
+msgstr "Dopo"
+
+#: ../src/jarabe/controlpanel/gui.py:294
+msgid "Restart now"
+msgstr "Riavvia adesso"
+
+#: ../src/jarabe/controlpanel/toolbar.py:61 ../src/jarabe/intro/window.py:206
+msgid "Done"
+msgstr "Fatto"
+
+#: ../src/jarabe/controlpanel/toolbar.py:121
+#: ../src/jarabe/desktop/favoritesview.py:333
+msgid "Ok"
+msgstr "Ok"
+
+#: ../src/jarabe/desktop/activitieslist.py:236
+#, python-format
+msgid "Version %s"
+msgstr "Versione %s"
+
+#: ../src/jarabe/desktop/activitieslist.py:357
+msgid "Confirm erase"
+msgstr "Conferma cancellazione"
+
+#: ../src/jarabe/desktop/activitieslist.py:359
+#, python-format
+msgid "Confirm erase: Do you want to permanently erase %s?"
+msgstr ""
+"Conferma cancellazione: Sei sicuro di voler eliminare definitivamente %s?"
+
+# self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+# TODO: Implement stopping downloads
+# self._stop_item.connect('activate', self._stop_item_activate_cb)
+# self.append_menu_item(self._stop_item)
+#: ../src/jarabe/desktop/activitieslist.py:363
+#: ../src/jarabe/frame/clipboardmenu.py:63
+#: ../src/jarabe/view/viewsource.py:218
+msgid "Keep"
+msgstr "Memorizza"
+
+#: ../src/jarabe/desktop/activitieslist.py:366
+#: ../src/jarabe/desktop/activitieslist.py:409
+#: ../src/jarabe/journal/journaltoolbox.py:360
+#: ../src/jarabe/journal/palettes.py:105
+msgid "Erase"
+msgstr "Elimina"
+
+#: ../src/jarabe/desktop/activitieslist.py:430
+msgid "Remove favorite"
+msgstr "Rimuovi preferito"
+
+#: ../src/jarabe/desktop/activitieslist.py:434
+msgid "Make favorite"
+msgstr "Definisci preferito"
+
+# TRANS: label for the freeform layout in the favorites view
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:116
+msgid "Freeform"
+msgstr "Formato libero"
+
+# TRANS: label for the ring layout in the favorites view
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:198
+msgid "Ring"
+msgstr "Anello"
+
+# TRANS: label for the spiral layout in the favorites view
+#. TRANS: label for the spiral layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:337
+msgid "Spiral"
+msgstr "Spirale"
+
+# TRANS: label for the box layout in the favorites view
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:404
+msgid "Box"
+msgstr "Scatola"
+
+# TRANS: label for the box layout in the favorites view
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:445
+msgid "Triangle"
+msgstr "Triangolo"
+
+#: ../src/jarabe/desktop/favoritesview.py:324
+msgid "Registration Failed"
+msgstr "Registrazione Fallita"
+
+#: ../src/jarabe/desktop/favoritesview.py:325
+#, python-format
+msgid "%s"
+msgstr "%s"
+
+#: ../src/jarabe/desktop/favoritesview.py:327
+msgid "Registration Successful"
+msgstr "Registrazione Effettuata"
+
+#: ../src/jarabe/desktop/favoritesview.py:328
+msgid "You are now registered with your school server."
+msgstr "Ora sei registrato sul tuo server di scuola"
+
+#: ../src/jarabe/desktop/favoritesview.py:630
+msgid "Register"
+msgstr "Registra"
+
+#: ../src/jarabe/desktop/homebox.py:63
+msgid "Software Update"
+msgstr "Aggiornamento software"
+
+#: ../src/jarabe/desktop/homebox.py:64
+msgid "Update your activities to ensure compatibility with your new software"
+msgstr ""
+"Aggiorna le tue attività perchè siano compatibili con il tuo sistema "
+"aggiornato."
+
+#: ../src/jarabe/desktop/homebox.py:73
+msgid "Check now"
+msgstr "Verifica adesso"
+
+#: ../src/jarabe/desktop/homebox.py:192
+msgid "List view"
+msgstr "Vista Elenco"
+
+#: ../src/jarabe/desktop/homebox.py:193
+msgid "<Ctrl>2"
+msgstr "<Ctrl>2"
+
+#: ../src/jarabe/desktop/homebox.py:255
+msgid "Favorites view"
+msgstr "Visualizza i Preferiti"
+
+#: ../src/jarabe/desktop/homebox.py:256
+msgid "<Ctrl>1"
+msgstr "<Ctrl>1"
+
+#: ../src/jarabe/desktop/keydialog.py:135
+msgid "Key Type:"
+msgstr "Tipo Chiave:"
+
+#: ../src/jarabe/desktop/keydialog.py:155
+msgid "Authentication Type:"
+msgstr "Tipo di Autenticazione:"
+
+#: ../src/jarabe/desktop/keydialog.py:220
+msgid "WPA & WPA2 Personal"
+msgstr "WPA & WPA2 Personal"
+
+#: ../src/jarabe/desktop/keydialog.py:229
+msgid "Wireless Security:"
+msgstr "Wireless Security:"
+
+# A complete translation in italian: "rete a maglie" becames a tautology
+#: ../src/jarabe/desktop/meshbox.py:492
+#, python-format
+msgid "Mesh Network %d"
+msgstr "Rete Mesh %d"
+
+# TRANS: Action label for resuming an activity.
+#. TRANS: Action label for resuming an activity.
+#: ../src/jarabe/desktop/meshbox.py:629
+#: ../src/jarabe/frame/activitiestray.py:735
+#: ../src/jarabe/journal/journaltoolbox.py:428
+#: ../src/jarabe/journal/palettes.py:65 ../src/jarabe/view/palettes.py:67
+msgid "Resume"
+msgstr "Riprendi"
+
+#: ../src/jarabe/desktop/meshbox.py:634
+#: ../src/jarabe/frame/activitiestray.py:233
+msgid "Join"
+msgstr "Associa"
+
+#: ../src/jarabe/desktop/schoolserver.py:103
+msgid "Cannot connect to the server."
+msgstr "Impossibile connettersi al server."
+
+#: ../src/jarabe/desktop/schoolserver.py:108
+msgid "The server could not complete the request."
+msgstr "Il server non può completare la richiesta."
+
+#: ../src/jarabe/frame/activitiestray.py:238
+#: ../src/jarabe/frame/activitiestray.py:672
+msgid "Decline"
+msgstr "Rinuncia"
+
+#: ../src/jarabe/frame/activitiestray.py:624
+#, python-format
+msgid "%dB"
+msgstr "%dB"
+
+#: ../src/jarabe/frame/activitiestray.py:626
+#, python-format
+msgid "%dKB"
+msgstr "%dKB"
+
+#: ../src/jarabe/frame/activitiestray.py:628
+#, python-format
+msgid "%dMB"
+msgstr "%dMB"
+
+#: ../src/jarabe/frame/activitiestray.py:645
+#, python-format
+msgid "%s of %s"
+msgstr "%s di %s"
+
+#: ../src/jarabe/frame/activitiestray.py:657
+#, python-format
+msgid "Transfer from %r"
+msgstr "Trasferisci da %r"
+
+#: ../src/jarabe/frame/activitiestray.py:667
+msgid "Accept"
+msgstr "Accetta"
+
+#: ../src/jarabe/frame/activitiestray.py:690
+#: ../src/jarabe/frame/activitiestray.py:817
+#, python-format
+msgid "%s (%s)"
+msgstr "%s (%s)"
+
+#: ../src/jarabe/frame/activitiestray.py:724
+#: ../src/jarabe/frame/activitiestray.py:852
+msgid "Dismiss"
+msgstr "Abbandona"
+
+#: ../src/jarabe/frame/activitiestray.py:787
+#, python-format
+msgid "Transfer to %r"
+msgstr "Trasferisci verso %r"
+
+#: ../src/jarabe/frame/clipboardmenu.py:53 ../src/jarabe/view/palettes.py:221
+msgid "Remove"
+msgstr "Rimuovi"
+
+#: ../src/jarabe/frame/clipboardmenu.py:58
+#: ../src/jarabe/frame/clipboardmenu.py:81
+msgid "Open"
+msgstr "Apri"
+
+#: ../src/jarabe/frame/clipboardmenu.py:86
+msgid "Open with"
+msgstr "Apri con"
+
+#: ../src/jarabe/frame/clipboardobject.py:49
+#, python-format
+msgid "%s clipping"
+msgstr "ritaglio %s"
+
+# Letterale "Vicinato", sperimentale: I miei vicini
+#: ../src/jarabe/frame/zoomtoolbar.py:37
+msgid "Neighborhood"
+msgstr "I miei vicini"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:37
+msgid "F1"
+msgstr "F1"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:39
+msgid "F2"
+msgstr "F2"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:41
+msgid "F3"
+msgstr "F3"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:43
+msgid "F4"
+msgstr "F4"
+
+#: ../src/jarabe/intro/window.py:128
+msgid "Click to change color:"
+msgstr "Seleziona per cambiare colore:"
+
+#: ../src/jarabe/intro/window.py:192 ../src/jarabe/journal/detailview.py:103
+msgid "Back"
+msgstr "Indietro"
+
+#: ../src/jarabe/intro/window.py:209
+msgid "Next"
+msgstr "Prossimo"
+
+#: ../src/jarabe/journal/expandedentry.py:152
+#: ../src/jarabe/journal/palettes.py:59
+msgid "Untitled"
+msgstr "Senza titolo"
+
+#: ../src/jarabe/journal/expandedentry.py:243
+msgid "No preview"
+msgstr "Nessuna anteprima"
+
+#: ../src/jarabe/journal/expandedentry.py:262
+#, python-format
+msgid "Kind: %s"
+msgstr "Tipo: %s"
+
+#: ../src/jarabe/journal/expandedentry.py:262
+msgid "Unknown"
+msgstr "Sconosciuto"
+
+#: ../src/jarabe/journal/expandedentry.py:263
+#, python-format
+msgid "Date: %s"
+msgstr "Data: %s"
+
+#: ../src/jarabe/journal/expandedentry.py:264
+#, python-format
+msgid "Size: %s"
+msgstr "Dimensione: %s"
+
+#: ../src/jarabe/journal/expandedentry.py:286 ../src/jarabe/journal/misc.py:93
+msgid "No date"
+msgstr "Nessuna data"
+
+#: ../src/jarabe/journal/expandedentry.py:293
+msgid "Participants:"
+msgstr "Partecipanti:"
+
+#: ../src/jarabe/journal/expandedentry.py:316
+msgid "Description:"
+msgstr "Descrizione:"
+
+#: ../src/jarabe/journal/expandedentry.py:341
+msgid "Tags:"
+msgstr "Etichette:"
+
+#: ../src/jarabe/journal/journalactivity.py:108
+#: ../src/jarabe/journal/volumestoolbar.py:47
+msgid "Journal"
+msgstr "Diario"
+
+#: ../src/jarabe/journal/journaltoolbox.py:67
+msgid "Search"
+msgstr "Cerca"
+
+#: ../src/jarabe/journal/journaltoolbox.py:126
+msgid "Anytime"
+msgstr "Sempre"
+
+#: ../src/jarabe/journal/journaltoolbox.py:128
+msgid "Today"
+msgstr "Oggi"
+
+#: ../src/jarabe/journal/journaltoolbox.py:130
+msgid "Since yesterday"
+msgstr "Da ieri"
+
+# TRANS: Filter entries modified during the last 7 days.
+#. TRANS: Filter entries modified during the last 7 days.
+#: ../src/jarabe/journal/journaltoolbox.py:132
+msgid "Past week"
+msgstr "Settimana scorsa"
+
+# TRANS: Filter entries modified during the last 30 days.
+#. TRANS: Filter entries modified during the last 30 days.
+#: ../src/jarabe/journal/journaltoolbox.py:134
+msgid "Past month"
+msgstr "Mese scorso"
+
+# TRANS: Filter entries modified during the last 356 days.
+#. TRANS: Filter entries modified during the last 356 days.
+#: ../src/jarabe/journal/journaltoolbox.py:136
+msgid "Past year"
+msgstr "Anno scorso"
+
+#: ../src/jarabe/journal/journaltoolbox.py:143
+msgid "Anyone"
+msgstr "Tutti"
+
+#: ../src/jarabe/journal/journaltoolbox.py:145
+msgid "My friends"
+msgstr "I miei amici"
+
+#: ../src/jarabe/journal/journaltoolbox.py:146
+msgid "My class"
+msgstr "La mia classe"
+
+# TRANS: Item in a combo box that filters by entry type.
+#: ../src/jarabe/journal/journaltoolbox.py:274
+msgid "Anything"
+msgstr "Qualsiasi"
+
+# TODO: Add "Start with" menu item
+#: ../src/jarabe/journal/journaltoolbox.py:350
+#: ../src/jarabe/journal/palettes.py:83
+msgid "Copy"
+msgstr "Copia"
+
+# TRANS: Action label for starting an entry.
+#. TRANS: Action label for starting an entry.
+#: ../src/jarabe/journal/journaltoolbox.py:431
+#: ../src/jarabe/journal/palettes.py:68
+msgid "Start"
+msgstr "Avvia"
+
+#: ../src/jarabe/journal/listview.py:373
+msgid "Your Journal is empty"
+msgstr "Il tuo Diario è vuoto"
+
+#: ../src/jarabe/journal/listview.py:375
+msgid "No matching entries"
+msgstr "Non ci sono registrazioni corrispondenti"
+
+#: ../src/jarabe/journal/listview.py:386
+msgid "Clear search"
+msgstr "Annulla ricerca"
+
+#: ../src/jarabe/journal/modalalert.py:63
+msgid "Your Journal is full"
+msgstr "Il tuo Diario è pieno"
+
+#: ../src/jarabe/journal/modalalert.py:67
+msgid "Please delete some old Journal entries to make space for new ones."
+msgstr ""
+"Per favore cancella alcune registrazioni vecchie dal Diario per far spazio "
+"alle nuove."
+
+#: ../src/jarabe/journal/modalalert.py:79
+msgid "Show Journal"
+msgstr "Apri il Diario"
+
+#: ../src/jarabe/journal/objectchooser.py:146
+msgid "Choose an object"
+msgstr "Scegli un oggetto"
+
+#: ../src/jarabe/journal/objectchooser.py:151
+#: ../src/jarabe/view/viewsource.py:308
+msgid "Close"
+msgstr "Chiudi"
+
+#: ../src/jarabe/journal/palettes.py:66
+msgid "Resume with"
+msgstr "Riprendi con"
+
+#: ../src/jarabe/journal/palettes.py:69
+msgid "Start with"
+msgstr "Inizia con"
+
+#: ../src/jarabe/journal/palettes.py:91
+msgid "Send to"
+msgstr "Invia a"
+
+#: ../src/jarabe/journal/palettes.py:100
+msgid "View Details"
+msgstr "Visualizza Dettagli"
+
+#: ../src/jarabe/journal/palettes.py:178
+msgid "No friends present"
+msgstr "Non ci sono amici presenti"
+
+#: ../src/jarabe/journal/palettes.py:183
+msgid "No valid connection found"
+msgstr "Connessione non trovata"
+
+#: ../src/jarabe/journal/palettes.py:211
+msgid "No activity to resume entry"
+msgstr "Sessione dell'Attività da riprendere non presente"
+
+#: ../src/jarabe/journal/palettes.py:213
+msgid "No activity to start entry"
+msgstr "Attività per riprendere la sessione non presente"
+
+#: ../src/jarabe/view/buddymenu.py:62
+msgid "Remove friend"
+msgstr "Rimuovi l'amico"
+
+#: ../src/jarabe/view/buddymenu.py:65
+msgid "Make friend"
+msgstr "Aggiungi agli amici"
+
+#: ../src/jarabe/view/buddymenu.py:82
+msgid "Shutdown"
+msgstr "Spegni"
+
+#: ../src/jarabe/view/buddymenu.py:90
+msgid "Logout"
+msgstr "Disconnessione"
+
+#: ../src/jarabe/view/buddymenu.py:95
+msgid "My Settings"
+msgstr "Le mie Preferenze"
+
+#: ../src/jarabe/view/buddymenu.py:130
+#, python-format
+msgid "Invite to %s"
+msgstr "Invito per %s"
+
+#: ../src/jarabe/view/palettes.py:45
+msgid "Starting..."
+msgstr "Inizio..."
+
+#. TODO: share-with, keep
+#: ../src/jarabe/view/palettes.py:74
+msgid "View Source"
+msgstr "Visualizza Sorgente"
+
+#: ../src/jarabe/view/palettes.py:85
+msgid "Stop"
+msgstr "Chiudi"
+
+#: ../src/jarabe/view/palettes.py:125
+msgid "Start new"
+msgstr "Inizia nuovo"
+
+#: ../src/jarabe/view/palettes.py:174
+msgid "Show contents"
+msgstr "Mostra i contenuti"
+
+#: ../src/jarabe/view/palettes.py:196 ../src/jarabe/view/palettes.py:246
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr "%(free_space)d MB Liberi"
+
+#: ../src/jarabe/view/viewsource.py:208
+msgid "Instance Source"
+msgstr "Sorgente Istanza"
+
+#: ../src/jarabe/view/viewsource.py:233
+msgid "Source"
+msgstr "Sorgente"
+
+#: ../src/jarabe/view/viewsource.py:292
+msgid "Activity Bundle Source"
+msgstr "Sorgente della Attività"
+
+#: ../src/jarabe/view/viewsource.py:299
+#, python-format
+msgid "View source: %r"
+msgstr "Vedi codice sorgente: %r"
+
+#~ msgid "Title"
+#~ msgstr "Titolo"
+
+#~ msgid "Version"
+#~ msgstr "Versione"
+
+#~ msgid "Date"
+#~ msgstr "Data"
+
+#~ msgid "Cannot obtain data needed for registration."
+#~ msgstr "Non riesco ad ottenere i dati necessari alla registrazione."
+
+#~ msgid "Unmount"
+#~ msgstr "Rimuovi"
+
+#~ msgid "Restart"
+#~ msgstr "Riavvia"
+
+#~ msgid ""
+#~ "© 2008 One Laptop per Child Association Inc; Red Hat Inc; and Contributors."
+#~ msgstr ""
+#~ "© 2008 One Laptop per Child Association Inc; Red Hat Inc; e Contributori."
+
+#~ msgid "Document"
+#~ msgstr "Documento"
+
+#~ msgid "Resume by default"
+#~ msgstr "Riprendi per default"
+
+#~ msgid "Encryption Type:"
+#~ msgstr "Tipo di Crittografia:"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#~ msgid "Disconnecting..."
+#~ msgstr "Disconnessione..."
+
+#~ msgid "About my XO"
+#~ msgstr "Informazioni sul mio XO"
+
+#~ msgid "Connected to a School Mesh Portal"
+#~ msgstr "Connesso ad un Portale Mesh di scuola"
+
+#~ msgid "Looking for a School Mesh Portal..."
+#~ msgstr "Ricerca di un Portale Mesh di scuola..."
+
+#~ msgid "Connected to an XO Mesh Portal"
+#~ msgstr "Connesso ad un Portale Mesh XO"
+
+#~ msgid "Looking for an XO Mesh Portal..."
+#~ msgstr "Sto cercando un Portale Mesh XO..."
+
+# Diretto?
+#~ msgid "Connected to a Simple Mesh"
+#~ msgstr "Connesso ad un Mesh Semplice"
+
+# Diretto?
+#~ msgid "Starting a Simple Mesh"
+#~ msgstr "Attivazione Mesh Semplice"
+
+#~ msgid "Unknown Mesh"
+#~ msgstr "Mesh sconosciuto"
+
+#~ msgid "Settings"
+#~ msgstr "Configurazioni"
+
+#, python-format
+#~ msgid "Clipboard object: %s."
+#~ msgstr "Oggetto Clipboard: %s."
+
+#~ msgid "You must enter a server."
+#~ msgstr "Devi inserire un server."
+
+#~ msgid "Control Panel"
+#~ msgstr "Pannello di Controllo"
+
+#~ msgid "© 2008 One Laptop per Child Assocation "
+#~ msgstr "© 2008 One Laptop per Child Association "
+
+#~ msgid "Sugar is the graphical user interface that "
+#~ msgstr "Sugar è l'interfaccia grafica utente che "
+
+#~ msgid "<Ctrl>R"
+#~ msgstr "<Ctrl>R"
+
+# Ruota, piuttosto che anello per mantenere consistenza con lo shortcut da tastiera... cf 27_05_08
+#~ msgid "Ring view"
+#~ msgstr "Vista Ruota"
+
+#~ msgid "Remove from ring"
+#~ msgstr "Rimuovi dalla ruota"
+
+#~ msgid "Add to ring"
+#~ msgstr "Aggiungi alla ruota"
+
+#~ msgid "Changes require a sugar restart to take effect."
+#~ msgstr ""
+#~ "Le modifiche effettuate necessitano di un riavvio di Sugar per avere "
+#~ "effetto."
+
+#~ msgid "Changes require restart to take effect"
+#~ msgstr "Le modifiche effettuate necessitano di un riavvio per avere effetto"
+
+#~ msgid "Delay in milliseconds:"
+#~ msgstr "Ritardo in millisecondi:"
+
+#~ msgid "Hot Corners"
+#~ msgstr "Angoli sensibili"
+
+#, fuzzy
+#~ msgid "Warm Edges"
+#~ msgstr "Bordi Attivi"
+
+#~ msgid "off"
+#~ msgstr "spento"
+
+#~ msgid "on"
+#~ msgstr "acceso"
+
+#~ msgid "Permission denied. You need to be root to run this method."
+#~ msgstr ""
+#~ "Autorizzazione negata. E' necessario essere \"root\" per eseguire questo "
+#~ "metodo."
+
+#, fuzzy
+#~ msgid "Error in reading timezone"
+#~ msgstr "Errore nel timezone"
+
+#, python-format
+#, fuzzy
+#~ msgid "Error copying timezone (from %s): %s"
+#~ msgstr "Errore nella copia del timezone (da %s): %s"
+
+#, python-format
+#, fuzzy
+#~ msgid "Changing permission of timezone: %s"
+#~ msgstr "Modifica i permessi del timezone: %s"
+
+#~ msgid "About this XO"
+#~ msgstr "Informazioni su questo XO"
+
+#~ msgid "Add to journal"
+#~ msgstr "Aggiungi al diario"
+
+#~ msgid "Reboot"
+#~ msgstr "Riavvia"
+
+#~ msgid "My Battery life"
+#~ msgstr "Durata Batteria"
+
+#~ msgid "Battery charging"
+#~ msgstr "Batteria in carica"
+
+#~ msgid "Battery discharging"
+#~ msgstr "Batteria in uso"
+
+#~ msgid "Battery fully charged"
+#~ msgstr "Batteria caricata completamente"
+
+#~ msgid "Text snippet"
+#~ msgstr "Parte di testo"
+
+#~ msgid "Image"
+#~ msgstr "Immagine"
+
+#~ msgid "Web Page"
+#~ msgstr "Pagina web"
+
+#~ msgid "PDF file"
+#~ msgstr "File PDF"
+
+#~ msgid "MS Word file"
+#~ msgstr "File MS Word"
+
+#~ msgid "RTF file"
+#~ msgstr "File RTF"
+
+#~ msgid "Abiword file"
+#~ msgstr "File Abiword"
+
+#~ msgid "Squeak project"
+#~ msgstr "Progetto Squeak"
+
+#~ msgid "OpenOffice text file"
+#~ msgstr "File di testo OpenOffice"
+
+#~ msgid "Object"
+#~ msgstr "Oggetto"
+
+#~ msgid "Pick a buddy picture"
+#~ msgstr "Scegli un'immagine"
+
+#~ msgid "My Picture:"
+#~ msgstr "La mia immagine:"
+
+#~ msgid "My Name:"
+#~ msgstr "Il mio nome:"
+
+#~ msgid "My Color:"
+#~ msgstr "Il mio color:"
+
+#~ msgid "Invite"
+#~ msgstr "Invita"
+
+#~ msgid "Stop download"
+#~ msgstr "Sospendi il trasferimento"
+
+#~ msgid "No options"
+#~ msgstr "Nessuna opzione"
+
+#~ msgid "Send"
+#~ msgstr "Invia"
+
+#~ msgid "Share with:"
+#~ msgstr "Condividi con:"
+
+#~ msgid "Private"
+#~ msgstr "Privato"
+
+#~ msgid "My Neighborhood"
+#~ msgstr "Il mio Vicinato"
+
+#~ msgid "Undo"
+#~ msgstr "Annulla"
+
+#~ msgid "Redo"
+#~ msgstr "Ripeti"
+
+#~ msgid "Paste"
+#~ msgstr "Incolla"
+
+#, python-format
+#~ msgid "%s Activity"
+#~ msgstr "Attività %s"
+
+#, fuzzy
+#~ msgid "Keep error"
+#~ msgstr "Errore di scrittura"
+
+#, fuzzy
+#~ msgid "Keep error: all changes will be lost"
+#~ msgstr "Errore di scrittura: tutte le modifiche verranno perse"
+
+#~ msgid "Don't stop"
+#~ msgstr "Non interrompere"
+
+#~ msgid "Stop anyway"
+#~ msgstr "Interrompi comunque"
+
+#~ msgid "Continue"
+#~ msgstr "Continua"
+
+#~ msgid "OK"
+#~ msgstr "OK"
+
+#, python-format
+#~ msgid "%d year"
+#~ msgstr "%d anno"
+
+#, python-format
+#~ msgid "%d years"
+#~ msgstr "%d anni"
+
+#, python-format
+#~ msgid "%d month"
+#~ msgstr "%d mese"
+
+#, python-format
+#~ msgid "%d months"
+#~ msgstr "%d mesi"
+
+#, python-format
+#~ msgid "%d week"
+#~ msgstr "%d settimana"
+
+#, python-format
+#~ msgid "%d weeks"
+#~ msgstr "%d settimane"
+
+#, python-format
+#~ msgid "%d day"
+#~ msgstr "%d giorno"
+
+#, python-format
+#~ msgid "%d days"
+#~ msgstr "%d giorni"
+
+#, python-format
+#~ msgid "%d hour"
+#~ msgstr "%d ora"
+
+#, python-format
+#~ msgid "%d hours"
+#~ msgstr "%d ore"
+
+#, python-format
+#~ msgid "%d minute"
+#~ msgstr "%d minuto"
+
+#, python-format
+#~ msgid "%d minutes"
+#~ msgstr "%d minuti"
+
+#, python-format
+#~ msgid "%d second"
+#~ msgstr "%d secondo"
+
+#~ msgid " and "
+#~ msgstr " e "
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+# A complete translation in italian: "rete a maglie" becames a tautology
+# Diretto?
+# Ruota, piuttosto che anello per mantenere consistenza con lo shortcut da tastiera... cf 27_05_08
+#, python-format
+#~ msgid ", "
+#~ msgstr ", "
diff --git a/shell/po/ja.po b/shell/po/ja.po
new file mode 100644
index 0000000..bb96a0c
--- /dev/null
+++ b/shell/po/ja.po
@@ -0,0 +1,1558 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-02-11 00:32-0500\n"
+"PO-Revision-Date: 2010-03-23 06:29+0200\n"
+"Last-Translator: korakurider <korakurider@gmail.com>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: ja\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Pootle 2.0.3\n"
+
+#: ../extensions/cpsection/aboutme/__init__.py:24
+msgid "About Me"
+msgstr "私について"
+
+#: ../extensions/cpsection/aboutme/model.py:43
+msgid "You must enter a name."
+msgstr "名前の入力が必要です"
+
+#: ../extensions/cpsection/aboutme/model.py:68
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr "線: 色=%s 色相=%s"
+
+#: ../extensions/cpsection/aboutme/model.py:71
+#, python-format
+msgid "stroke: %s"
+msgstr "線: %s"
+
+#: ../extensions/cpsection/aboutme/model.py:73
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr "塗りつぶし: 色=%s 色相=%s"
+
+#: ../extensions/cpsection/aboutme/model.py:75
+#, python-format
+msgid "fill: %s"
+msgstr "塗りつぶし: %s"
+
+#: ../extensions/cpsection/aboutme/model.py:86
+msgid "Error in specified color modifiers."
+msgstr "カラーモディファイアの指定でエラー"
+
+#: ../extensions/cpsection/aboutme/model.py:89
+msgid "Error in specified colors."
+msgstr "色の指定でエラー"
+
+#: ../extensions/cpsection/aboutme/view.py:94 ../src/jarabe/intro/window.py:93
+msgid "Name:"
+msgstr "名前:"
+
+#: ../extensions/cpsection/aboutme/view.py:128
+msgid "Click to change your color:"
+msgstr "クリックして色を変更:"
+
+#: ../extensions/cpsection/aboutcomputer/__init__.py:21
+msgid "About my Computer"
+msgstr "私のコンピュータについて"
+
+#: ../extensions/cpsection/aboutcomputer/model.py:28
+msgid "Not available"
+msgstr "不明です"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:60
+msgid "Identity"
+msgstr "個体の識別"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:69
+msgid "Serial Number:"
+msgstr "シリアル番号:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:91
+msgid "Software"
+msgstr "ソフトウェア"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:100
+msgid "Build:"
+msgstr "ビルド:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:115
+msgid "Sugar:"
+msgstr "Sugar:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:131
+msgid "Firmware:"
+msgstr "ファームウェア:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:146
+msgid "Wireless Firmware:"
+msgstr "ワイヤレスファームウェア:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:169
+msgid "Copyright and License"
+msgstr "著作権とライセンス"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:184
+msgid ""
+"Sugar is the graphical user interface that you are looking at. Sugar is free "
+"software, covered by the GNU General Public License, and you are welcome to "
+"change it and/or distribute copies of it under certain conditions described "
+"therein."
+msgstr ""
+"Sugarは、あなたが見ているグラフィカルユーザインターフェースです。Sugarはフリーソフトウェアで、GNU General Public Licen"
+"se(一般公衆利用許諾契約書)による保護対象です。ここに記載されている条件の範囲で、Sugarを変更したりコピーを配布することが自由に行えます。"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:196
+msgid "Full license:"
+msgstr "ライセンス全文:"
+
+#: ../extensions/cpsection/datetime/__init__.py:21
+msgid "Date & Time"
+msgstr "日付・時刻"
+
+#: ../extensions/cpsection/datetime/model.py:87
+msgid "Error timezone does not exist."
+msgstr "エラー:そのタイムゾーンは存在しません。"
+
+#: ../extensions/cpsection/datetime/view.py:68 ../data/sugar.schemas.in.h:33
+msgid "Timezone"
+msgstr "タイムゾーン"
+
+#: ../extensions/cpsection/frame/__init__.py:21
+msgid "Frame"
+msgstr "フレーム"
+
+#: ../extensions/cpsection/frame/model.py:38
+#: ../extensions/cpsection/frame/model.py:60
+msgid "Value must be an integer."
+msgstr "値は整数でなくてはなりません"
+
+#: ../extensions/cpsection/frame/view.py:26
+msgid "never"
+msgstr "表示しない"
+
+#: ../extensions/cpsection/frame/view.py:27
+msgid "instantaneous"
+msgstr "すぐに表示"
+
+#: ../extensions/cpsection/frame/view.py:28
+#, python-format
+msgid "%s seconds"
+msgstr "%s 秒"
+
+#: ../extensions/cpsection/frame/view.py:52
+msgid "Activation Delay"
+msgstr "表示するまでの時間"
+
+#: ../extensions/cpsection/frame/view.py:76
+msgid "Corner"
+msgstr "画面の4すみ"
+
+#: ../extensions/cpsection/frame/view.py:111
+msgid "Edge"
+msgstr "画面のふち"
+
+#: ../extensions/cpsection/keyboard/__init__.py:21
+#: ../extensions/cpsection/keyboard/view.py:31
+msgid "Keyboard"
+msgstr "キーボード"
+
+#: ../extensions/cpsection/keyboard/view.py:189
+msgid "Keyboard Model"
+msgstr "キーボードのモデル"
+
+#: ../extensions/cpsection/keyboard/view.py:248
+msgid "Key(s) to change layout"
+msgstr "レイアウト変更に使うキー"
+
+#: ../extensions/cpsection/keyboard/view.py:318
+msgid "Keyboard Layout(s)"
+msgstr "キーボードの配列"
+
+#: ../extensions/cpsection/language/__init__.py:21
+#: ../extensions/cpsection/language/view.py:33
+msgid "Language"
+msgstr "言語"
+
+#: ../extensions/cpsection/language/model.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr "~/.i18nにアクセスできませんでした。標準設定を作ってください。"
+
+#: ../extensions/cpsection/language/model.py:124
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr "コード%sの言語が不明です。"
+
+#: ../extensions/cpsection/language/model.py:144
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr "ごめんなさい。'%s'は話せません。"
+
+#: ../extensions/cpsection/language/view.py:56
+msgid ""
+"Add languages in the order you prefer. If a translation is not available, "
+"the next in the list will be used."
+msgstr "使いたい言語を順番に追加してください。翻訳されていないと、リストの次のものが使われます。"
+
+#: ../extensions/cpsection/modemconfiguration/__init__.py:21
+msgid "Modem Configuration"
+msgstr "モデムの設定"
+
+#: ../extensions/cpsection/modemconfiguration/view.py:90
+msgid "Username:"
+msgstr "ユーザ名:"
+
+#: ../extensions/cpsection/modemconfiguration/view.py:101
+msgid "Password:"
+msgstr "パスワード:"
+
+#: ../extensions/cpsection/modemconfiguration/view.py:112
+msgid "Number:"
+msgstr "番号:"
+
+#: ../extensions/cpsection/modemconfiguration/view.py:123
+msgid "APN:"
+msgstr "APN:"
+
+#: ../extensions/cpsection/network/__init__.py:21
+#: ../extensions/cpsection/network/view.py:28
+msgid "Network"
+msgstr "ネットワーク"
+
+#: ../extensions/cpsection/network/model.py:79
+msgid "State is unknown."
+msgstr "状態が不明です"
+
+#: ../extensions/cpsection/network/model.py:105
+msgid "Error in specified radio argument use on/off."
+msgstr "radio引数指定でエラー、on/offを使ってください。"
+
+#: ../extensions/cpsection/network/model.py:137
+msgid "Error in specified argument use 0/1."
+msgstr "引数指定でエラー、0/1を使ってください。"
+
+#: ../extensions/cpsection/network/view.py:59
+msgid "Wireless"
+msgstr "無線"
+
+#: ../extensions/cpsection/network/view.py:67
+msgid "Turn off the wireless radio to save battery life"
+msgstr "電池を節約するため、無線を止める"
+
+#: ../extensions/cpsection/network/view.py:80
+msgid "Radio"
+msgstr "電波"
+
+#: ../extensions/cpsection/network/view.py:96
+msgid "Discard network history if you have trouble connecting to the network"
+msgstr "ネットワーク接続で問題がある場合、接続履歴を破棄します"
+
+#: ../extensions/cpsection/network/view.py:105
+msgid "Discard network history"
+msgstr "ネットワーク接続履歴を破棄"
+
+#: ../extensions/cpsection/network/view.py:118
+msgid "Collaboration"
+msgstr "コラボレーション"
+
+#: ../extensions/cpsection/network/view.py:126
+msgid ""
+"The server is the equivalent of what room you are in; people on the same "
+"server will be able to see each other, even when they aren't on the same "
+"network."
+msgstr "サーバーは、あなたがいる部屋と同じようなものです。同じサーバー上にいる人は、違うネットワークにいてもお互いに相手が見えます。"
+
+#: ../extensions/cpsection/network/view.py:136
+msgid "Server:"
+msgstr "サーバー:"
+
+#: ../extensions/cpsection/power/__init__.py:21
+msgid "Power"
+msgstr "電源"
+
+#: ../extensions/cpsection/power/model.py:54
+msgid "Error in automatic pm argument, use on/off."
+msgstr "automatic_pm 引数指定でエラー、 on/offを指定してください"
+
+#: ../extensions/cpsection/power/model.py:81
+msgid "Error in extreme pm argument, use on/off."
+msgstr "extreme_pm 引数指定のエラー、 on/offを指定してください。"
+
+#: ../extensions/cpsection/power/view.py:47
+msgid "Power management"
+msgstr "電源の管理"
+
+#: ../extensions/cpsection/power/view.py:57
+msgid "Automatic power management (increases battery life)"
+msgstr "自動的な電源管理(電池が長持ちします)"
+
+#: ../extensions/cpsection/power/view.py:85
+msgid ""
+"Extreme power management (disableswireless radio, increases battery life)"
+msgstr "最大の電源管理(無線の電波を停めて、電池を長持ちさせます)"
+
+#: ../extensions/cpsection/updater/__init__.py:21
+msgid "Software update"
+msgstr "ソフトウェアの更新"
+
+#: ../extensions/cpsection/updater/view.py:63
+msgid ""
+"Software updates correct errors, eliminate security vulnerabilities, and "
+"provide new features."
+msgstr "ソフトウェアの更新により、エラーを修正し、セキュリティ脆弱性を減らし、新機能を追加します。"
+
+#: ../extensions/cpsection/updater/view.py:125
+#, python-format
+msgid "Checking %s..."
+msgstr "%s を調べています..."
+
+#: ../extensions/cpsection/updater/view.py:127
+#, python-format
+msgid "Downloading %s..."
+msgstr "% をダウンロードしています..."
+
+#: ../extensions/cpsection/updater/view.py:129
+#, python-format
+msgid "Updating %s..."
+msgstr "%s を更新しています..."
+
+#: ../extensions/cpsection/updater/view.py:139
+msgid "Your software is up-to-date"
+msgstr "ソフトウェアは最新になっています"
+
+#: ../extensions/cpsection/updater/view.py:141
+#, python-format
+msgid "You can install %s update"
+msgid_plural "You can install %s updates"
+msgstr[0] "%s の更新をインストールできます"
+
+#: ../extensions/cpsection/updater/view.py:159
+msgid "Checking for updates..."
+msgstr "更新をチェックしています..."
+
+#: ../extensions/cpsection/updater/view.py:164
+msgid "Installing updates..."
+msgstr "更新をインストールしています..."
+
+#: ../extensions/cpsection/updater/view.py:172
+#, python-format
+msgid "%s update was installed"
+msgid_plural "%s updates were installed"
+msgstr[0] "%s の更新がインストールされました"
+
+#: ../extensions/cpsection/updater/view.py:253
+msgid "Install selected"
+msgstr "選ばれているものをインストールする"
+
+#: ../extensions/cpsection/updater/view.py:274
+#, python-format
+msgid "Download size: %s"
+msgstr "ダウンロードのサイズ: %s"
+
+#: ../extensions/cpsection/updater/view.py:362
+#, python-format
+msgid "From version %(current)d to %(new)s (Size: %(size)s)"
+msgstr "現在のバージョン %(current)d から %(new)s (サイズ: %(size)s) へ"
+
+#. TRANS: download size is 0
+#: ../extensions/cpsection/updater/view.py:382
+msgid "None"
+msgstr "なし"
+
+#. TRANS: download size of very small updates
+#: ../extensions/cpsection/updater/view.py:385
+msgid "1 KB"
+msgstr "1KB"
+
+#. TRANS: download size of small updates, e.g. '250 KB'
+#: ../extensions/cpsection/updater/view.py:388
+#, python-format
+msgid "%.0f KB"
+msgstr "%.0f KB"
+
+#. TRANS: download size of updates, e.g. '2.3 MB'
+#: ../extensions/cpsection/updater/view.py:391
+#, python-format
+msgid "%.1f MB"
+msgstr "%.1f MB"
+
+#: ../extensions/deviceicon/battery.py:58
+msgid "My Battery"
+msgstr "私の電池"
+
+#: ../extensions/deviceicon/battery.py:137
+msgid "Removed"
+msgstr "取り外されています"
+
+#: ../extensions/deviceicon/battery.py:140
+msgid "Charging"
+msgstr "充電中"
+
+#: ../extensions/deviceicon/battery.py:143
+msgid "Very little power remaining"
+msgstr "電池が残りわずかです"
+
+#: ../extensions/deviceicon/battery.py:149
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr "残り %(hour)d:%(min).2d"
+
+#: ../extensions/deviceicon/battery.py:152
+msgid "Charged"
+msgstr "充電済み"
+
+#: ../extensions/deviceicon/network.py:49
+#, python-format
+msgid "IP address: %s"
+msgstr "IPアドレス: %s"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#: ../extensions/deviceicon/network.py:111
+msgid "Disconnect..."
+msgstr "切断..."
+
+#: ../extensions/deviceicon/network.py:116
+msgid "Create new wireless network"
+msgstr "無線ネットワークを作成"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#: ../extensions/deviceicon/network.py:122
+#: ../extensions/deviceicon/network.py:284
+#: ../src/jarabe/desktop/meshbox.py:248 ../src/jarabe/desktop/meshbox.py:537
+msgid "Connecting..."
+msgstr "接続しています..."
+
+# TODO: show the channel number
+#: ../extensions/deviceicon/network.py:126
+#: ../extensions/deviceicon/network.py:198
+#: ../extensions/deviceicon/network.py:288
+#: ../src/jarabe/desktop/meshbox.py:254 ../src/jarabe/desktop/meshbox.py:543
+msgid "Connected"
+msgstr "接続しました"
+
+#: ../extensions/deviceicon/network.py:158
+msgid "Channel"
+msgstr "チャンネル"
+
+#: ../extensions/deviceicon/network.py:173
+msgid "Wired Network"
+msgstr "有線ネットワーク"
+
+#: ../extensions/deviceicon/network.py:201
+msgid "Speed"
+msgstr "速度"
+
+#: ../extensions/deviceicon/network.py:228
+msgid "Wireless modem"
+msgstr "無線モデム"
+
+#: ../extensions/deviceicon/network.py:276
+msgid "Please wait..."
+msgstr "お待ちください..."
+
+#: ../extensions/deviceicon/network.py:279
+#: ../src/jarabe/desktop/meshbox.py:164 ../src/jarabe/desktop/meshbox.py:494
+msgid "Connect"
+msgstr "接続"
+
+#: ../extensions/deviceicon/network.py:280
+msgid "Disconnected"
+msgstr "切断されました"
+
+#: ../extensions/deviceicon/network.py:283
+#: ../src/jarabe/controlpanel/toolbar.py:115
+#: ../src/jarabe/desktop/homebox.py:68
+#: ../src/jarabe/frame/activitiestray.py:700
+#: ../src/jarabe/frame/activitiestray.py:799
+#: ../src/jarabe/frame/activitiestray.py:827
+msgid "Cancel"
+msgstr "中止"
+
+#: ../extensions/deviceicon/network.py:287
+#: ../src/jarabe/desktop/meshbox.py:168
+msgid "Disconnect"
+msgstr "切断"
+
+#: ../extensions/deviceicon/network.py:530
+#, python-format
+msgid "%s's network"
+msgstr "%s のネットワーク"
+
+#: ../extensions/deviceicon/network.py:597
+#: ../extensions/deviceicon/network.py:656
+msgid "Mesh Network"
+msgstr "メッシュネットワーク"
+
+#: ../extensions/deviceicon/network.py:857
+#, python-format
+msgid "Data sent %d kb / received %d kb"
+msgstr "送信 %d kb/受信 %d kb"
+
+#: ../extensions/deviceicon/network.py:868
+msgid "Connection time "
+msgstr "接続時間 "
+
+#: ../extensions/deviceicon/speaker.py:59
+msgid "My Speakers"
+msgstr "私のスピーカー"
+
+#: ../extensions/deviceicon/speaker.py:133
+msgid "Unmute"
+msgstr "音を出す"
+
+#: ../extensions/deviceicon/speaker.py:136
+msgid "Mute"
+msgstr "音を消す"
+
+#: ../extensions/globalkey/screenshot.py:59
+msgid "Mesh"
+msgstr "メッシュ"
+
+#: ../extensions/globalkey/screenshot.py:61
+#: ../src/jarabe/frame/zoomtoolbar.py:39
+msgid "Group"
+msgstr "グループ"
+
+#: ../extensions/globalkey/screenshot.py:63
+#: ../src/jarabe/frame/zoomtoolbar.py:41
+msgid "Home"
+msgstr "ホーム"
+
+#: ../extensions/globalkey/screenshot.py:69
+#: ../src/jarabe/frame/zoomtoolbar.py:43
+msgid "Activity"
+msgstr "アクティビティ"
+
+#: ../extensions/globalkey/screenshot.py:72
+msgid "Screenshot"
+msgstr "スクリーンショット"
+
+#: ../extensions/globalkey/screenshot.py:74
+#, python-format
+msgid "Screenshot of \"%s\""
+msgstr "\"%s\"のスクリーンショット"
+
+#: ../data/sugar.schemas.in.h:1
+msgid ""
+"\"disabled\" to ask nick on initialization; \"system\" to reuse UNIX account "
+"long name."
+msgstr "初期化の際ニックネームを確認する場合は \"disabled\"を、UNIXアカウントの長い名前を再利用するには\"system\"を指定してください"
+
+#: ../data/sugar.schemas.in.h:2
+msgid "Backup URL"
+msgstr "バックアップURL"
+
+#: ../data/sugar.schemas.in.h:3
+msgid ""
+"Color for the XO icon that is used throughout the desktop. The string is "
+"composed of the stroke color and fill color, format is that of rbg colors. "
+"Example: #AC32FF,#9A5200"
+msgstr ""
+"デスクトップで使われるXOアイコンの色。文字列は線と塗りつぶしの色で構成されます。フォーマットは赤青緑の形式です。例: #AC32FF,#9A5200"
+
+#: ../data/sugar.schemas.in.h:4
+msgid "Corner Delay"
+msgstr "四隅の遅延"
+
+#: ../data/sugar.schemas.in.h:5
+msgid "Default font face"
+msgstr "デフォルトのフォント種類"
+
+#: ../data/sugar.schemas.in.h:6
+msgid "Default font size"
+msgstr "デフォルトのフォントサイズ"
+
+#: ../data/sugar.schemas.in.h:7
+msgid "Default nick"
+msgstr "デフォルトのニックネーム"
+
+#: ../data/sugar.schemas.in.h:8
+msgid "Delay for the activation of the frame using the corners."
+msgstr "画面四隅でフレームを起動する際の遅延"
+
+#: ../data/sugar.schemas.in.h:9
+msgid "Delay for the activation of the frame using the edges."
+msgstr "画面四辺でフレームを起動する際の遅延"
+
+#: ../data/sugar.schemas.in.h:10
+msgid "Edge Delay"
+msgstr "四辺の遅延"
+
+#: ../data/sugar.schemas.in.h:11
+msgid "Favorites Layout"
+msgstr "お気に入りのレイアウト"
+
+#: ../data/sugar.schemas.in.h:12
+msgid "Favorites resume mode"
+msgstr "お気に入り復元モード"
+
+#: ../data/sugar.schemas.in.h:13
+msgid "Font face that is used throughout the desktop."
+msgstr "デスクトップで使われるフォント種類"
+
+#: ../data/sugar.schemas.in.h:14
+msgid "Font size that is used throughout the desktop."
+msgstr "デスクトップで使われるフォントサイズ"
+
+#: ../data/sugar.schemas.in.h:15
+msgid ""
+"If TRUE, Sugar will make us searchable for the other users of the Jabber "
+"server."
+msgstr "ONの場合、SugarはJabberサーバーの他のユーザが私たちを検索できるようにします"
+
+#: ../data/sugar.schemas.in.h:16
+msgid "If TRUE, Sugar will show a \"Log out\" option."
+msgstr "もしONなら、Sugarはログアウト オプションを表示します。"
+
+#: ../data/sugar.schemas.in.h:17
+msgid "Jabber Server"
+msgstr "Jabberサーバー"
+
+#: ../data/sugar.schemas.in.h:18
+msgid "Keyboard layouts"
+msgstr "キーボードの配列"
+
+#: ../data/sugar.schemas.in.h:19
+msgid "Keyboard model"
+msgstr "キーボードのモデル"
+
+#: ../data/sugar.schemas.in.h:20
+msgid "Keyboard options"
+msgstr "キーボードのオプション"
+
+#: ../data/sugar.schemas.in.h:21
+msgid "Layout of the favorites view."
+msgstr "お気に入り表示のレイアウト"
+
+#: ../data/sugar.schemas.in.h:22
+msgid ""
+"List of keyboard layouts. Each entry should be in the form layout(variant)"
+msgstr "キーボード配列のリスト。"
+
+#: ../data/sugar.schemas.in.h:23
+msgid "List of keyboard options."
+msgstr "キーボードオプションのリスト"
+
+#: ../data/sugar.schemas.in.h:24
+msgid "Power Automatic"
+msgstr "電源設定-自動"
+
+#: ../data/sugar.schemas.in.h:25
+msgid "Power Automatic."
+msgstr "電源設定-自動"
+
+#: ../data/sugar.schemas.in.h:26
+msgid "Power Extreme"
+msgstr "電源設定-最大"
+
+#: ../data/sugar.schemas.in.h:27
+msgid "Power Extreme."
+msgstr "電源設定-最大"
+
+#: ../data/sugar.schemas.in.h:28
+msgid "Publish to Gadget"
+msgstr "ガジェットに送信"
+
+#: ../data/sugar.schemas.in.h:29
+msgid "Setting for muting the sound device."
+msgstr "サウンド装置の音を消す"
+
+#: ../data/sugar.schemas.in.h:30
+msgid "Show Log out"
+msgstr "ログアウト を表示"
+
+#: ../data/sugar.schemas.in.h:31
+msgid "Sound Muted"
+msgstr "消音されました"
+
+#: ../data/sugar.schemas.in.h:32
+msgid "The keyboard model to be used"
+msgstr "使用するキーボードのモデル"
+
+#: ../data/sugar.schemas.in.h:34
+msgid "Timezone setting for the system."
+msgstr "システムのタイムゾーン設定"
+
+#: ../data/sugar.schemas.in.h:35
+msgid "Url of the jabber server to use."
+msgstr "使用するJabberサーバーのURL"
+
+#: ../data/sugar.schemas.in.h:36
+msgid "Url where the backup is saved to."
+msgstr "バックアップが保存されるURL"
+
+#: ../data/sugar.schemas.in.h:37
+msgid "User Color"
+msgstr "ユーザの色"
+
+#: ../data/sugar.schemas.in.h:38
+msgid "User Name"
+msgstr "ユーザの名前"
+
+#: ../data/sugar.schemas.in.h:39
+msgid "User name that is used throughout the desktop."
+msgstr "デスクトップで使われるユーザ名"
+
+#: ../data/sugar.schemas.in.h:40
+msgid "Volume Level"
+msgstr "音量レベル"
+
+#: ../data/sugar.schemas.in.h:41
+msgid "Volume level for the sound device."
+msgstr "サウンド装置の音量レベル"
+
+#: ../data/sugar.schemas.in.h:42
+msgid ""
+"When in resume mode, clicking on a favorite icon will cause the last entry "
+"for that activity to be resumed."
+msgstr "復元モードの時、お気に入りアイコンをクリックすると、そのアクティビティで使用した最後のエントリが再開します"
+
+#: ../src/jarabe/controlpanel/cmd.py:28
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr "sugar-control-panel: 注意, 同じ名前のオプションが複数あります: %s モジュール: %r"
+
+#: ../src/jarabe/controlpanel/cmd.py:30
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr "sugar-control-panel: key=%s は有効なオプションではありません"
+
+#: ../src/jarabe/controlpanel/cmd.py:31
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr "sugar-control-panel: %s"
+
+# TRANS: Translators, there's a empty line at the end of this string,
+# which must appear in the translated string (msgstr) as well.
+#. TRANS: Translators, there's a empty line at the end of this string,
+#. which must appear in the translated string (msgstr) as well.
+#: ../src/jarabe/controlpanel/cmd.py:37
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" -c key clear the current value for the key \n"
+" "
+msgstr ""
+"使い方: sugar-control-panel [ option ] key [ args ... ] \n"
+"Sugarの環境を設定する。 \n"
+"Options: \n"
+"-h このヘルプメッセージを表示して終了する\n"
+"-l 全ての有効なオプションを表示\n"
+"-h key このキーについての情報を表示\n"
+"-g key このキーの現在の設定値を取得\n"
+"-s key このキーの値を設定 \n"
+"-c key このキーの設定を消去\n"
+" "
+
+#: ../src/jarabe/controlpanel/cmd.py:50
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr "変更を適用するにはsugarを再起動しなければいけません。\n"
+
+#: ../src/jarabe/controlpanel/gui.py:281
+msgid "Warning"
+msgstr "注意"
+
+#: ../src/jarabe/controlpanel/gui.py:282
+#: ../src/jarabe/controlpanel/sectionview.py:42
+msgid "Changes require restart"
+msgstr "変更には再起動が必要です"
+
+#: ../src/jarabe/controlpanel/gui.py:285
+msgid "Cancel changes"
+msgstr "変更を取りやめる"
+
+#: ../src/jarabe/controlpanel/gui.py:290 ../src/jarabe/desktop/homebox.py:70
+msgid "Later"
+msgstr "あとで"
+
+#: ../src/jarabe/controlpanel/gui.py:294
+msgid "Restart now"
+msgstr "今すぐ再起動"
+
+#: ../src/jarabe/controlpanel/toolbar.py:61 ../src/jarabe/intro/window.py:206
+msgid "Done"
+msgstr "完了"
+
+#: ../src/jarabe/controlpanel/toolbar.py:121
+#: ../src/jarabe/desktop/favoritesview.py:333
+msgid "Ok"
+msgstr "了解"
+
+#: ../src/jarabe/desktop/activitieslist.py:236
+#, python-format
+msgid "Version %s"
+msgstr "バージョン %s"
+
+#: ../src/jarabe/desktop/activitieslist.py:357
+msgid "Confirm erase"
+msgstr "消去の確認"
+
+#: ../src/jarabe/desktop/activitieslist.py:359
+#, python-format
+msgid "Confirm erase: Do you want to permanently erase %s?"
+msgstr "消去の確認: %s を完全に消去しますか?"
+
+# self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+# TODO: Implement stopping downloads
+# self._stop_item.connect('activate', self._stop_item_activate_cb)
+# self.append_menu_item(self._stop_item)
+#: ../src/jarabe/desktop/activitieslist.py:363
+#: ../src/jarabe/frame/clipboardmenu.py:63
+#: ../src/jarabe/view/viewsource.py:218
+msgid "Keep"
+msgstr "ジャーナルに保存"
+
+#: ../src/jarabe/desktop/activitieslist.py:366
+#: ../src/jarabe/desktop/activitieslist.py:409
+#: ../src/jarabe/journal/journaltoolbox.py:360
+#: ../src/jarabe/journal/palettes.py:105
+msgid "Erase"
+msgstr "消去する"
+
+#: ../src/jarabe/desktop/activitieslist.py:430
+msgid "Remove favorite"
+msgstr "お気に入りから削除"
+
+#: ../src/jarabe/desktop/activitieslist.py:434
+msgid "Make favorite"
+msgstr "お気に入りに登録"
+
+# TRANS: label for the freeform layout in the favorites view
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:116
+msgid "Freeform"
+msgstr "自由に並べる"
+
+# TRANS: label for the ring layout in the favorites view
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:198
+msgid "Ring"
+msgstr "円に並べる"
+
+# TRANS: label for the spiral layout in the favorites view
+#. TRANS: label for the spiral layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:337
+msgid "Spiral"
+msgstr "渦巻きに並べる"
+
+# TRANS: label for the box layout in the favorites view
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:404
+msgid "Box"
+msgstr "箱型に並べる"
+
+# TRANS: label for the box layout in the favorites view
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:445
+msgid "Triangle"
+msgstr "三角形に並べる"
+
+#: ../src/jarabe/desktop/favoritesview.py:324
+msgid "Registration Failed"
+msgstr "登録失敗"
+
+#: ../src/jarabe/desktop/favoritesview.py:325
+#, python-format
+msgid "%s"
+msgstr "%s"
+
+#: ../src/jarabe/desktop/favoritesview.py:327
+msgid "Registration Successful"
+msgstr "登録成功"
+
+#: ../src/jarabe/desktop/favoritesview.py:328
+msgid "You are now registered with your school server."
+msgstr "あなたはスクールサーバーに登録されました"
+
+#: ../src/jarabe/desktop/favoritesview.py:630
+msgid "Register"
+msgstr "登録"
+
+#: ../src/jarabe/desktop/homebox.py:63
+msgid "Software Update"
+msgstr "ソフトウェアの更新"
+
+#: ../src/jarabe/desktop/homebox.py:64
+msgid "Update your activities to ensure compatibility with your new software"
+msgstr "新しいソフトウェアとの互換性を保つため、アクティビティを更新してください"
+
+#: ../src/jarabe/desktop/homebox.py:73
+msgid "Check now"
+msgstr "今すぐ調べる"
+
+#: ../src/jarabe/desktop/homebox.py:192
+msgid "List view"
+msgstr "リスト表示"
+
+#: ../src/jarabe/desktop/homebox.py:193
+msgid "<Ctrl>2"
+msgstr "<Ctrl>2"
+
+#: ../src/jarabe/desktop/homebox.py:255
+msgid "Favorites view"
+msgstr "お気に入り"
+
+#: ../src/jarabe/desktop/homebox.py:256
+msgid "<Ctrl>1"
+msgstr "<Ctrl>1"
+
+#: ../src/jarabe/desktop/keydialog.py:135
+msgid "Key Type:"
+msgstr "キーのタイプ:"
+
+#: ../src/jarabe/desktop/keydialog.py:155
+msgid "Authentication Type:"
+msgstr "認証タイプ:"
+
+#: ../src/jarabe/desktop/keydialog.py:220
+msgid "WPA & WPA2 Personal"
+msgstr "WPA と WPA2パーソナル"
+
+#: ../src/jarabe/desktop/keydialog.py:229
+msgid "Wireless Security:"
+msgstr "無線のセキュリティ:"
+
+#: ../src/jarabe/desktop/meshbox.py:492
+#, python-format
+msgid "Mesh Network %d"
+msgstr "メッシュネットワーク %d"
+
+# TRANS: Action label for resuming an activity.
+#. TRANS: Action label for resuming an activity.
+#: ../src/jarabe/desktop/meshbox.py:629
+#: ../src/jarabe/frame/activitiestray.py:735
+#: ../src/jarabe/journal/journaltoolbox.py:428
+#: ../src/jarabe/journal/palettes.py:65 ../src/jarabe/view/palettes.py:67
+msgid "Resume"
+msgstr "再開"
+
+#: ../src/jarabe/desktop/meshbox.py:634
+#: ../src/jarabe/frame/activitiestray.py:233
+msgid "Join"
+msgstr "参加"
+
+#: ../src/jarabe/desktop/schoolserver.py:103
+msgid "Cannot connect to the server."
+msgstr "サーバーに接続できません"
+
+#: ../src/jarabe/desktop/schoolserver.py:108
+msgid "The server could not complete the request."
+msgstr "サーバーは要求の処理を完了できませんでした"
+
+#: ../src/jarabe/frame/activitiestray.py:238
+#: ../src/jarabe/frame/activitiestray.py:672
+msgid "Decline"
+msgstr "断る"
+
+#: ../src/jarabe/frame/activitiestray.py:624
+#, python-format
+msgid "%dB"
+msgstr "%dB"
+
+#: ../src/jarabe/frame/activitiestray.py:626
+#, python-format
+msgid "%dKB"
+msgstr "%dKB"
+
+#: ../src/jarabe/frame/activitiestray.py:628
+#, python-format
+msgid "%dMB"
+msgstr "%dMB"
+
+#: ../src/jarabe/frame/activitiestray.py:645
+#, python-format
+msgid "%s of %s"
+msgstr "%s の %s"
+
+#: ../src/jarabe/frame/activitiestray.py:657
+#, python-format
+msgid "Transfer from %r"
+msgstr "%rから転送"
+
+#: ../src/jarabe/frame/activitiestray.py:667
+msgid "Accept"
+msgstr "受け入れる"
+
+#: ../src/jarabe/frame/activitiestray.py:690
+#: ../src/jarabe/frame/activitiestray.py:817
+#, python-format
+msgid "%s (%s)"
+msgstr "%s (%s)"
+
+#: ../src/jarabe/frame/activitiestray.py:724
+#: ../src/jarabe/frame/activitiestray.py:852
+msgid "Dismiss"
+msgstr "閉じる"
+
+#: ../src/jarabe/frame/activitiestray.py:787
+#, python-format
+msgid "Transfer to %r"
+msgstr "%rへ転送"
+
+#: ../src/jarabe/frame/clipboardmenu.py:53 ../src/jarabe/view/palettes.py:221
+msgid "Remove"
+msgstr "削除"
+
+#: ../src/jarabe/frame/clipboardmenu.py:58
+#: ../src/jarabe/frame/clipboardmenu.py:81
+msgid "Open"
+msgstr "開く"
+
+#: ../src/jarabe/frame/clipboardmenu.py:86
+msgid "Open with"
+msgstr "次のもので開く:"
+
+#: ../src/jarabe/frame/clipboardobject.py:49
+#, python-format
+msgid "%s clipping"
+msgstr "%sのクリッピング"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:37
+msgid "Neighborhood"
+msgstr "お隣さん"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:37
+msgid "F1"
+msgstr "F1"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:39
+msgid "F2"
+msgstr "F2"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:41
+msgid "F3"
+msgstr "F3"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:43
+msgid "F4"
+msgstr "F4"
+
+#: ../src/jarabe/intro/window.py:128
+msgid "Click to change color:"
+msgstr "クリックして色を変更:"
+
+#: ../src/jarabe/intro/window.py:192 ../src/jarabe/journal/detailview.py:103
+msgid "Back"
+msgstr "戻る"
+
+#: ../src/jarabe/intro/window.py:209
+msgid "Next"
+msgstr "進む"
+
+#: ../src/jarabe/journal/expandedentry.py:152
+#: ../src/jarabe/journal/palettes.py:59
+msgid "Untitled"
+msgstr "タイトル無し"
+
+#: ../src/jarabe/journal/expandedentry.py:243
+msgid "No preview"
+msgstr "プレビュー無し"
+
+#: ../src/jarabe/journal/expandedentry.py:262
+#, python-format
+msgid "Kind: %s"
+msgstr "種類: %s"
+
+#: ../src/jarabe/journal/expandedentry.py:262
+msgid "Unknown"
+msgstr "不明"
+
+#: ../src/jarabe/journal/expandedentry.py:263
+#, python-format
+msgid "Date: %s"
+msgstr "日付: %s"
+
+#: ../src/jarabe/journal/expandedentry.py:264
+#, python-format
+msgid "Size: %s"
+msgstr "サイズ: %s"
+
+#: ../src/jarabe/journal/expandedentry.py:286 ../src/jarabe/journal/misc.py:93
+msgid "No date"
+msgstr "日付無し"
+
+#: ../src/jarabe/journal/expandedentry.py:293
+msgid "Participants:"
+msgstr "参加者:"
+
+#: ../src/jarabe/journal/expandedentry.py:316
+msgid "Description:"
+msgstr "説明:"
+
+#: ../src/jarabe/journal/expandedentry.py:341
+msgid "Tags:"
+msgstr "タグ:"
+
+#: ../src/jarabe/journal/journalactivity.py:108
+#: ../src/jarabe/journal/volumestoolbar.py:47
+msgid "Journal"
+msgstr "ジャーナル"
+
+#: ../src/jarabe/journal/journaltoolbox.py:67
+msgid "Search"
+msgstr "検索"
+
+#: ../src/jarabe/journal/journaltoolbox.py:126
+msgid "Anytime"
+msgstr "すべての期間"
+
+#: ../src/jarabe/journal/journaltoolbox.py:128
+msgid "Today"
+msgstr "今日"
+
+#: ../src/jarabe/journal/journaltoolbox.py:130
+msgid "Since yesterday"
+msgstr "昨日から後"
+
+# TRANS: Filter entries modified during the last 7 days.
+#. TRANS: Filter entries modified during the last 7 days.
+#: ../src/jarabe/journal/journaltoolbox.py:132
+msgid "Past week"
+msgstr "過去1週間"
+
+# TRANS: Filter entries modified during the last 30 days.
+#. TRANS: Filter entries modified during the last 30 days.
+#: ../src/jarabe/journal/journaltoolbox.py:134
+msgid "Past month"
+msgstr "過去1ヶ月"
+
+# TRANS: Filter entries modified during the last 356 days.
+#. TRANS: Filter entries modified during the last 356 days.
+#: ../src/jarabe/journal/journaltoolbox.py:136
+msgid "Past year"
+msgstr "過去1年"
+
+#: ../src/jarabe/journal/journaltoolbox.py:143
+msgid "Anyone"
+msgstr "誰でも"
+
+#: ../src/jarabe/journal/journaltoolbox.py:145
+msgid "My friends"
+msgstr "私の友だち"
+
+#: ../src/jarabe/journal/journaltoolbox.py:146
+msgid "My class"
+msgstr "私のクラス"
+
+# TRANS: Item in a combo box that filters by entry type.
+#: ../src/jarabe/journal/journaltoolbox.py:274
+msgid "Anything"
+msgstr "すべての種類"
+
+# TODO: Add "Start with" menu item
+#: ../src/jarabe/journal/journaltoolbox.py:350
+#: ../src/jarabe/journal/palettes.py:83
+msgid "Copy"
+msgstr "コピー"
+
+# TRANS: Action label for starting an entry.
+#. TRANS: Action label for starting an entry.
+#: ../src/jarabe/journal/journaltoolbox.py:431
+#: ../src/jarabe/journal/palettes.py:68
+msgid "Start"
+msgstr "開始"
+
+#: ../src/jarabe/journal/listview.py:373
+msgid "Your Journal is empty"
+msgstr "ジャーナルが空です"
+
+#: ../src/jarabe/journal/listview.py:375
+msgid "No matching entries"
+msgstr "あてはまるものが無いです"
+
+#: ../src/jarabe/journal/listview.py:386
+msgid "Clear search"
+msgstr "検索をクリア"
+
+#: ../src/jarabe/journal/modalalert.py:63
+msgid "Your Journal is full"
+msgstr "ジャーナルが満杯です"
+
+#: ../src/jarabe/journal/modalalert.py:67
+msgid "Please delete some old Journal entries to make space for new ones."
+msgstr "新しいものが入るよう、ジャーナルの中身の古いものをどれか削除してください"
+
+#: ../src/jarabe/journal/modalalert.py:79
+msgid "Show Journal"
+msgstr "ジャーナルを表示"
+
+#: ../src/jarabe/journal/objectchooser.py:146
+msgid "Choose an object"
+msgstr "オブジェクトを選ぶ"
+
+#: ../src/jarabe/journal/objectchooser.py:151
+#: ../src/jarabe/view/viewsource.py:308
+msgid "Close"
+msgstr "閉じる"
+
+#: ../src/jarabe/journal/palettes.py:66
+msgid "Resume with"
+msgstr "次のアクティビティで再開"
+
+#: ../src/jarabe/journal/palettes.py:69
+msgid "Start with"
+msgstr "次のアクティビティで開始"
+
+#: ../src/jarabe/journal/palettes.py:91
+msgid "Send to"
+msgstr "次に送る"
+
+#: ../src/jarabe/journal/palettes.py:100
+msgid "View Details"
+msgstr "詳細を表示"
+
+#: ../src/jarabe/journal/palettes.py:178
+msgid "No friends present"
+msgstr "お友達がいません"
+
+#: ../src/jarabe/journal/palettes.py:183
+msgid "No valid connection found"
+msgstr "正しい接続がありません"
+
+#: ../src/jarabe/journal/palettes.py:211
+msgid "No activity to resume entry"
+msgstr "エントリを再開するアクティビティがありません"
+
+#: ../src/jarabe/journal/palettes.py:213
+msgid "No activity to start entry"
+msgstr "エントリを開始するアクティビティがありません"
+
+#: ../src/jarabe/view/buddymenu.py:62
+msgid "Remove friend"
+msgstr "お友だちの登録削除"
+
+#: ../src/jarabe/view/buddymenu.py:65
+msgid "Make friend"
+msgstr "お友だちを登録"
+
+#: ../src/jarabe/view/buddymenu.py:82
+msgid "Shutdown"
+msgstr "シャットダウン"
+
+#: ../src/jarabe/view/buddymenu.py:90
+msgid "Logout"
+msgstr "ログアウト"
+
+#: ../src/jarabe/view/buddymenu.py:95
+msgid "My Settings"
+msgstr "私の設定"
+
+#: ../src/jarabe/view/buddymenu.py:130
+#, python-format
+msgid "Invite to %s"
+msgstr "%s に招待する"
+
+#: ../src/jarabe/view/palettes.py:45
+msgid "Starting..."
+msgstr "起動中..."
+
+#. TODO: share-with, keep
+#: ../src/jarabe/view/palettes.py:74
+msgid "View Source"
+msgstr "ソースコードを表示"
+
+#: ../src/jarabe/view/palettes.py:85
+msgid "Stop"
+msgstr "停止"
+
+#: ../src/jarabe/view/palettes.py:125
+msgid "Start new"
+msgstr "新しく開始する"
+
+#: ../src/jarabe/view/palettes.py:174
+msgid "Show contents"
+msgstr "内容を表示"
+
+#: ../src/jarabe/view/palettes.py:196 ../src/jarabe/view/palettes.py:246
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr "%(free_space)d MB 空き"
+
+#: ../src/jarabe/view/viewsource.py:208
+msgid "Instance Source"
+msgstr "インスタンスのソースコード"
+
+#: ../src/jarabe/view/viewsource.py:233
+msgid "Source"
+msgstr "ソースコード"
+
+#: ../src/jarabe/view/viewsource.py:292
+msgid "Activity Bundle Source"
+msgstr "アクティビティバンドルのソースコード"
+
+#: ../src/jarabe/view/viewsource.py:299
+#, python-format
+msgid "View source: %r"
+msgstr "ソースを表示: %r"
+
+#~ msgid "Title"
+#~ msgstr "表題"
+
+#~ msgid "Version"
+#~ msgstr "バージョン"
+
+#~ msgid "Date"
+#~ msgstr "日付"
+
+#~ msgid "Cannot obtain data needed for registration."
+#~ msgstr "登録に必要なデータを取得できません"
+
+#~ msgid "Unmount"
+#~ msgstr "取りはずす"
+
+#~ msgid "Restart"
+#~ msgstr "再起動"
+
+#~ msgid ""
+#~ "© 2008 One Laptop per Child Association Inc; Red Hat Inc; and Contributors."
+#~ msgstr ""
+#~ "(C) 2008 One Laptop per Child Association Inc; Red Hat Inc; and "
+#~ "Contributors."
+
+#~ msgid "Document"
+#~ msgstr "ドキュメント"
+
+#~ msgid "Resume by default"
+#~ msgstr "デフォルトのアクティビティで再開"
+
+#~ msgid "Encryption Type:"
+#~ msgstr "暗号タイプ:"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#~ msgid "Disconnecting..."
+#~ msgstr "切断しています..."
+
+#~ msgid "About my XO"
+#~ msgstr "私のXOについて"
+
+#~ msgid "Connected to a School Mesh Portal"
+#~ msgstr "スクールメッシュポータルに接続しました。"
+
+#~ msgid "Looking for a School Mesh Portal..."
+#~ msgstr "スクールメッシュポータルを探しています..."
+
+#~ msgid "Connected to an XO Mesh Portal"
+#~ msgstr "XOメッシュポータルに接続しました"
+
+#~ msgid "Looking for an XO Mesh Portal..."
+#~ msgstr "XOメッシュポータルを探しています..."
+
+#~ msgid "Connected to a Simple Mesh"
+#~ msgstr "シンプルメッシュに接続しました"
+
+#~ msgid "Starting a Simple Mesh"
+#~ msgstr "シンプルメッシュを開始しています"
+
+#~ msgid "Unknown Mesh"
+#~ msgstr "不明なメッシュ"
+
+#~ msgid "Settings"
+#~ msgstr "設定"
+
+#, python-format
+#~ msgid "Clipboard object: %s."
+#~ msgstr "クリップボードのオブジェクト: %s."
+
+#~ msgid "You must enter a server."
+#~ msgstr "サーバーの指定が必要です"
+
+#~ msgid "Control Panel"
+#~ msgstr "コントロールパネル"
+
+#, fuzzy
+#~ msgid "off"
+#~ msgstr "オフ"
+
+#, fuzzy
+#~ msgid "on"
+#~ msgstr "オン"
+
+#, fuzzy
+#~ msgid "Permission denied. You need to be root to run this method."
+#~ msgstr "実行が拒否されました。このメソッドを実行するにはrootでなければいけません。"
+
+#, fuzzy
+#~ msgid "Error in reading timezone"
+#~ msgstr "タイムゾーンの読み取りでエラー"
+
+#, python-format
+#, fuzzy
+#~ msgid "Error copying timezone (from %s): %s"
+#~ msgstr "(%sから)タイムゾーンをコピー中にエラー:%s"
+
+#, python-format
+#, fuzzy
+#~ msgid "Changing permission of timezone: %s"
+#~ msgstr "タイムゾーンの権限変更でエラー:%s"
+
+#~ msgid "Add to journal"
+#~ msgstr "ジャーナルに追加"
+
+#~ msgid "Reboot"
+#~ msgstr "再起動"
+
+#, fuzzy
+#~ msgid "My Battery life"
+#~ msgstr "バッテリーの残り"
+
+#~ msgid "Battery charging"
+#~ msgstr "充電中"
+
+#, fuzzy
+#~ msgid "Battery discharging"
+#~ msgstr "放電中"
+
+#, fuzzy
+#~ msgid "Battery fully charged"
+#~ msgstr "充電完了"
+
+#, fuzzy
+#~ msgid "Share with:"
+#~ msgstr "共有するおともだち:"
+
+#, fuzzy
+#~ msgid "Private"
+#~ msgstr "プライベート"
+
+#, fuzzy
+#~ msgid "My Neighborhood"
+#~ msgstr "自分の周り"
+
+#, fuzzy
+#~ msgid "Undo"
+#~ msgstr "元に戻す"
+
+#, fuzzy
+#~ msgid "Redo"
+#~ msgstr "やり直す"
+
+#, fuzzy
+#~ msgid "Paste"
+#~ msgstr "ペースト"
+
+#, python-format
+#, fuzzy
+#~ msgid "%s Activity"
+#~ msgstr "%sアクティビティ"
+
+#, fuzzy
+#~ msgid "Keep error"
+#~ msgstr "エラーを記録"
+
+#, fuzzy
+#~ msgid "Keep error: all changes will be lost"
+#~ msgstr "記録エラー発生:全ての変更は失われます"
+
+#, fuzzy
+#~ msgid "Don't stop"
+#~ msgstr "停止しない"
+
+#, fuzzy
+#~ msgid "Stop anyway"
+#~ msgstr "強制停止"
+
+#, fuzzy
+#~ msgid "Continue"
+#~ msgstr "続ける"
+
+#, fuzzy
+#~ msgid "OK"
+#~ msgstr "了解"
+
+#, python-format
+#, fuzzy
+#~ msgid "%d year"
+#~ msgstr "%d年"
+
+#, python-format
+#, fuzzy
+#~ msgid "%d years"
+#~ msgstr "%d年"
+
+#, python-format
+#, fuzzy
+#~ msgid "%d month"
+#~ msgstr "%d月"
+
+#, python-format
+#, fuzzy
+#~ msgid "%d months"
+#~ msgstr "%d月"
+
+#, python-format
+#, fuzzy
+#~ msgid "%d week"
+#~ msgstr "%d週"
+
+#, python-format
+#, fuzzy
+#~ msgid "%d weeks"
+#~ msgstr "%d週"
+
+#, python-format
+#, fuzzy
+#~ msgid "%d day"
+#~ msgstr "%d日"
+
+#, python-format
+#, fuzzy
+#~ msgid "%d days"
+#~ msgstr "%d日"
+
+#, python-format
+#, fuzzy
+#~ msgid "%d hour"
+#~ msgstr "%d時間"
+
+#, python-format
+#, fuzzy
+#~ msgid "%d hours"
+#~ msgstr "%d時間"
+
+#, python-format
+#, fuzzy
+#~ msgid "%d minute"
+#~ msgstr "%d分"
+
+#, python-format
+#, fuzzy
+#~ msgid "%d minutes"
+#~ msgstr "%d分"
+
+#, python-format
+#, fuzzy
+#~ msgid "%d second"
+#~ msgstr "%d秒"
+
+#~ msgid " and "
+#~ msgstr "と"
+
+#, fuzzy
+#~ msgid ", "
+#~ msgstr "、"
diff --git a/shell/po/km.po b/shell/po/km.po
new file mode 100644
index 0000000..110c316
--- /dev/null
+++ b/shell/po/km.po
@@ -0,0 +1,584 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# Rit Lim <rit.lim@gmail.com>, 2008.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-21 00:30-0400\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Pootle 1.1.0rc2\n"
+
+#: ../src/intro/intro.py:65 ../src/controlpanel/view/aboutme.py:100
+msgid "Name:"
+msgstr "ឈ្មោះ: "
+
+#: ../src/intro/intro.py:94
+msgid "Click to change color:"
+msgstr "ដូរ​ព័ណ"
+
+#: ../src/intro/intro.py:145
+msgid "Back"
+msgstr "ថយ​ក្រោយ"
+
+#: ../src/intro/intro.py:159 ../src/controlpanel/toolbar.py:61
+msgid "Done"
+msgstr "រួច​ហើយ"
+
+#: ../src/intro/intro.py:162
+msgid "Next"
+msgstr "ថតទៅ​មុខ"
+
+#: ../src/view/BuddyMenu.py:58
+msgid "Remove friend"
+msgstr "ដក​មិត្ត​ចេញ"
+
+#: ../src/view/BuddyMenu.py:61
+msgid "Make friend"
+msgstr "កសាង​មិត្ត​ភាព"
+
+#: ../src/view/BuddyMenu.py:91
+#, python-format
+msgid "Invite to %s"
+msgstr "អញ្ជើញទៅ %s"
+
+#: ../src/view/clipboardmenu.py:48
+msgid "Remove"
+msgstr "យក​ចេញ"
+
+#: ../src/view/clipboardmenu.py:53 ../src/view/clipboardmenu.py:79
+msgid "Open"
+msgstr "បើក"
+
+#. self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+#. TODO: Implement stopping downloads
+#. self._stop_item.connect('activate', self._stop_item_activate_cb)
+#. self.append_menu_item(self._stop_item)
+#: ../src/view/clipboardmenu.py:63
+msgid "Keep"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:84
+msgid "Open with"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:212
+#, python-format
+msgid "Clipboard object: %s."
+msgstr "ក្ដារ​ខ្ទាស់​វត្ថុ: %s."
+
+#: ../src/hardware/keydialog.py:150
+msgid "Key Type:"
+msgstr "ប្រភេទ​ឆ្នុច: "
+
+#: ../src/hardware/keydialog.py:170
+msgid "Authentication Type:"
+msgstr "ប្រភេទផ្ទៀង: "
+
+#: ../src/hardware/keydialog.py:251
+msgid "Encryption Type:"
+msgstr "ប្រភេទ​អ៊ិនគ្រីប: "
+
+#: ../src/view/Shell.py:262
+msgid "Screenshot"
+msgstr "រូបថត​អេក្រង់​"
+
+#: ../src/view/home/HomeBox.py:147
+msgid "List view"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:148
+msgid "<Ctrl>L"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:204
+msgid "Favorites view"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:205
+msgid "<Ctrl>R"
+msgstr ""
+
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/view/home/HomeBox.py:211
+msgid "Freeform"
+msgstr ""
+
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/view/home/HomeBox.py:218
+msgid "Ring"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:97
+msgid "Connect"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:106
+#, fuzzy
+msgid "Disconnect"
+msgstr "ផ្ដាច់"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#: ../src/view/home/MeshBox.py:118
+#, fuzzy
+msgid "Disconnecting..."
+msgstr "ផ្ដាច់បណ្ដាញ"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#: ../src/view/home/MeshBox.py:152
+#, fuzzy
+msgid "Connecting..."
+msgstr "ផ្ដាច់បណ្ដាញ"
+
+#. TODO: show the channel number
+#: ../src/view/home/MeshBox.py:159
+msgid "Connected"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:211 ../src/view/devices/network/mesh.py:38
+#: ../src/view/devices/network/mesh.py:65
+#: ../src/view/devices/network/mesh.py:69
+msgid "Mesh Network"
+msgstr "បណ្ដាញត"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#: ../src/view/home/MeshBox.py:214 ../src/view/devices/network/wireless.py:116
+#: ../src/view/devices/network/mesh.py:86
+msgid "Disconnect..."
+msgstr "ផ្ដាច់បណ្ដាញ"
+
+#: ../src/view/home/MeshBox.py:302 ../src/view/palettes.py:60
+msgid "Resume"
+msgstr "បន្ត"
+
+#: ../src/view/home/MeshBox.py:307 ../src/view/frame/activitiestray.py:219
+msgid "Join"
+msgstr "ចូល​រួម"
+
+#: ../src/view/devices/battery.py:42
+msgid "My Battery"
+msgstr ""
+
+#: ../src/view/devices/battery.py:111
+msgid "Charging"
+msgstr ""
+
+#: ../src/view/devices/battery.py:114
+msgid "Very little power remaining"
+msgstr ""
+
+#: ../src/view/devices/battery.py:120
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr ""
+
+#: ../src/view/devices/battery.py:124
+msgid "Charged"
+msgstr ""
+
+#: ../src/view/devices/speaker.py:40
+msgid "My Speakers"
+msgstr ""
+
+#: ../src/view/devices/speaker.py:104
+msgid "Unmute"
+msgstr ""
+
+#: ../src/view/devices/speaker.py:107
+msgid "Mute"
+msgstr ""
+
+#: ../src/view/devices/network/wireless.py:64
+msgid "Disconnected"
+msgstr "ផ្ដាច់"
+
+#: ../src/view/devices/network/wireless.py:134
+msgid "Channel"
+msgstr "ប៉ុស្ដិ៍"
+
+#: ../src/view/frame/zoomtoolbar.py:34
+msgid "Neighborhood"
+msgstr "ភូមិ"
+
+#: ../src/view/frame/zoomtoolbar.py:36
+msgid "Group"
+msgstr "ក្រុម"
+
+#: ../src/view/frame/zoomtoolbar.py:38
+msgid "Home"
+msgstr "ផ្ទះ"
+
+#: ../src/view/frame/zoomtoolbar.py:40
+msgid "Activity"
+msgstr "សកម្មភាព"
+
+#: ../src/controlpanel/cmd.py:26
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:28
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr "sugar-control-panel: key=%s ជម្រើស​អត់​មាន​ទេ"
+
+#: ../src/controlpanel/cmd.py:29
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr "sugar-control-panel: %s"
+
+#: ../src/controlpanel/cmd.py:33
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" "
+msgstr ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+"Control for the sugar environment. \n"
+"Options: \n"
+"-h show this help message and exit \n"
+"-l list all the available options \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"
+" "
+
+#: ../src/controlpanel/cmd.py:45
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr ""
+
+#: ../src/controlpanel/toolbar.py:115
+msgid "Cancel"
+msgstr ""
+
+#: ../src/controlpanel/toolbar.py:121
+msgid "Ok"
+msgstr ""
+
+#: ../src/controlpanel/sectionview.py:34 ../src/controlpanel/gui.py:250
+msgid "Changes require restart"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:249
+msgid "Warning"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:253
+msgid "Cancel changes"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:257
+msgid "Later"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:261
+msgid "Restart now"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:44
+msgid "You must enter a name."
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:67
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:70
+#, python-format
+msgid "stroke: %s"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:72
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:74
+#, python-format
+msgid "fill: %s"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:85
+msgid "Error in specified color modifiers."
+msgstr "កំណែ​សំរាប់​ពណ៌​នេះ​មាន​បញ្ហា"
+
+#: ../src/controlpanel/model/aboutme.py:88
+msgid "Error in specified colors."
+msgstr "ពណ៌​នេះ​មាន​បញ្ហា"
+
+#: ../src/controlpanel/model/aboutxo.py:24
+msgid "Not available"
+msgstr "មិន​អាច​រក​បាន"
+
+#: ../src/controlpanel/model/datetime.py:85
+msgid "Error timezone does not exist."
+msgstr "មានបញ្ហា ។ ដំបន់​ម៉ោង​នេះ​អត់​មាន​ទេ"
+
+#: ../src/controlpanel/model/frame.py:38 ../src/controlpanel/model/frame.py:60
+msgid "Value must be an integer."
+msgstr ""
+
+#: ../src/controlpanel/model/language.py:28
+#, fuzzy
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr "មិន​អាច​អាន %s បាន​ទេ ។ បង្កើតស្ដង់ដារ​​សំរាប់​ការ​រៀបចំ"
+
+#: ../src/controlpanel/model/language.py:104
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr "កូដ​សំរាប់​ភាលា​ code=%s មិន​អាន​រក​ឃើញ​ទេ"
+
+#: ../src/controlpanel/model/language.py:121
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr "សូម​ទោស ខ្ញុំ​មិន​ចេះ​និយាយ​ភាសា '%s' ទេ"
+
+#: ../src/controlpanel/model/network.py:48
+msgid "You must enter a server."
+msgstr ""
+
+#: ../src/controlpanel/model/network.py:63
+msgid "State is unknown."
+msgstr "ស្ថានភាព​មិន​ស្គាល់"
+
+#: ../src/controlpanel/model/network.py:83
+msgid "Error in specified radio argument use on/off."
+msgstr "​មាន​បញ្ហានៅ​ក្នុង​វិទ្យុនេះ"
+
+#: ../src/controlpanel/view/aboutme.py:32
+msgid "About Me"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutme.py:134
+#, fuzzy
+msgid "Click to change your color:"
+msgstr "ដូរ​ព័ណ"
+
+#: ../src/controlpanel/view/aboutxo.py:26
+msgid "About my XO"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutxo.py:47
+msgid "Identity"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutxo.py:56
+msgid "Serial Number:"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutxo.py:79
+msgid "Software"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutxo.py:88
+msgid "Build:"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutxo.py:103
+msgid "Firmware:"
+msgstr ""
+
+#: ../src/controlpanel/view/datetime.py:29
+msgid "Date & Time"
+msgstr ""
+
+#: ../src/controlpanel/view/datetime.py:72
+msgid "Timezone"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:28
+msgid "Frame"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:30
+msgid "never"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:31
+msgid "instantaneous"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:32
+#, python-format
+msgid "%s seconds"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:56
+msgid "Activation Delay"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:80
+msgid "Corner"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:115
+msgid "Edge"
+msgstr ""
+
+#: ../src/controlpanel/view/language.py:29
+#: ../src/controlpanel/view/language.py:74
+msgid "Language"
+msgstr ""
+
+#: ../src/controlpanel/view/network.py:28
+msgid "Network"
+msgstr ""
+
+#: ../src/controlpanel/view/network.py:53
+msgid "Wireless"
+msgstr ""
+
+#: ../src/controlpanel/view/network.py:61
+msgid "Radio:"
+msgstr ""
+
+#: ../src/controlpanel/view/network.py:94
+msgid "Mesh"
+msgstr ""
+
+#: ../src/controlpanel/view/network.py:103
+msgid "Server:"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:108
+msgid "Connected to a School Mesh Portal"
+msgstr "ផ្ជាប់នឹង​បណ្តាញ​ត​របស់​សាលា"
+
+#: ../src/view/devices/network/mesh.py:110
+msgid "Looking for a School Mesh Portal..."
+msgstr "ស្វែង​រក​បណ្តាញ​ត​របស់​សាលា"
+
+#: ../src/view/devices/network/mesh.py:113
+msgid "Connected to an XO Mesh Portal"
+msgstr "ផ្ជាប់​នឹង​បណ្ដាញត​ XO"
+
+#: ../src/view/devices/network/mesh.py:115
+msgid "Looking for an XO Mesh Portal..."
+msgstr "ស្វែង​រក​បណ្តាញ​ត​របស់​ XO"
+
+#: ../src/view/devices/network/mesh.py:118
+msgid "Connected to a Simple Mesh"
+msgstr "ផ្ជាប់​នឹង​បណ្ដាញ​ស៊ីមផល"
+
+#: ../src/view/devices/network/mesh.py:120
+msgid "Starting a Simple Mesh"
+msgstr "ចាប់​ផ្ដើម​បណ្ដាញ​ស៊ីមផល"
+
+#: ../src/view/devices/network/mesh.py:127
+msgid "Unknown Mesh"
+msgstr "បណ្ដាញ​គ្មាន​សំគាល់"
+
+#: ../src/view/frame/activitiestray.py:224
+msgid "Decline"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:351
+msgid "Control Panel"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:362
+msgid "Restart"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:367
+msgid "Shutdown"
+msgstr "បិទ"
+
+#: ../src/view/home/favoritesview.py:373
+msgid "Register"
+msgstr "ចុះ​ឈ្មោះ"
+
+#: ../src/view/palettes.py:41
+msgid "Starting..."
+msgstr "ចាប់​ផ្ដើម..."
+
+#: ../src/view/palettes.py:71
+msgid "Stop"
+msgstr "ឈប់"
+
+#: ../src/view/palettes.py:96
+msgid "Start"
+msgstr ""
+
+#: ../src/view/palettes.py:119
+msgid "Remove favorite"
+msgstr ""
+
+#: ../src/view/palettes.py:123
+msgid "Make favorite"
+msgstr ""
+
+#: ../src/view/palettes.py:169
+msgid "Show contents"
+msgstr ""
+
+#: ../src/view/palettes.py:193
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr ""
+
+#~ msgid "off"
+#~ msgstr "បិត"
+
+#~ msgid "on"
+#~ msgstr "បើក"
+
+#~ msgid "Permission denied. You need to be root to run this method."
+#~ msgstr "គ្មានអនុញ្ញាត ។ វិធី​សាស្ត្រ​នេះ​ត្រូវ​ការ​មេ​"
+
+#~ msgid "Error in reading timezone"
+#~ msgstr "មាន​បញ្ហាក្នុង​ការ​អាន​ដំបន់ម៉ោង​"
+
+#, python-format
+#~ msgid "Error copying timezone (from %s): %s"
+#~ msgstr "មាន​បញ្ហាក្នុង​ការ​ចំលង់​ដំបន់ម៉ោង​ (​ពី %s): %s"
+
+#, python-format
+#~ msgid "Changing permission of timezone: %s"
+#~ msgstr "​ដូរ​សេចក្ដី​អនុញ្ញាត​នៃ​ដំបន់​ម៉ោង: %s"
+
+#~ msgid "About this XO"
+#~ msgstr "អំពី XO នេះ"
+
+#~ msgid "Add to journal"
+#~ msgstr "បញ្ចូលក្នុងកំណន់​ប្រចាំ​ថ្ងៃ"
+
+#~ msgid "Reboot"
+#~ msgstr "ចាប់​ផ្ដើម​ឡើង​វិញ"
+
+#~ msgid "My Battery life"
+#~ msgstr "អាយុថាមពល"
+
+#~ msgid "Battery charging"
+#~ msgstr "កំពុង​សាក​ថាមពល"
+
+#~ msgid "Battery discharging"
+#~ msgstr "កំពុង​ប្រើ​ថាមពល"
+
+#~ msgid "Battery fully charged"
+#~ msgstr "ថាមពល​សាក​ពេញ​ហើយ"
diff --git a/shell/po/ko.po b/shell/po/ko.po
new file mode 100644
index 0000000..a79e711
--- /dev/null
+++ b/shell/po/ko.po
@@ -0,0 +1,420 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-01-18 18:19+0000\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.0.1\n"
+
+#: ../shell/intro/intro.py:67
+msgid "Name:"
+msgstr ""
+
+#: ../shell/intro/intro.py:96
+msgid "Click to change color:"
+msgstr ""
+
+#: ../shell/intro/intro.py:146
+msgid "Back"
+msgstr ""
+
+#: ../shell/intro/intro.py:160
+msgid "Done"
+msgstr ""
+
+#: ../shell/intro/intro.py:163
+msgid "Next"
+msgstr ""
+
+#: ../shell/view/BuddyMenu.py:59
+msgid "Remove friend"
+msgstr ""
+
+#: ../shell/view/BuddyMenu.py:62
+msgid "Make friend"
+msgstr ""
+
+#: ../shell/view/BuddyMenu.py:84
+#, python-format
+msgid "Invite to %s"
+msgstr ""
+
+#: ../shell/view/clipboardmenu.py:58
+msgid "Remove"
+msgstr ""
+
+#: ../shell/view/clipboardmenu.py:63
+msgid "Open"
+msgstr ""
+
+#. self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+#. TODO: Implement stopping downloads
+#. self._stop_item.connect('activate', self._stop_item_activate_cb)
+#. self.append_menu_item(self._stop_item)
+#: ../shell/view/clipboardmenu.py:73
+msgid "Add to journal"
+msgstr ""
+
+#: ../shell/view/clipboardmenu.py:213
+#, python-format
+msgid "Clipboard object: %s."
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:149
+msgid "Key Type:"
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:169
+msgid "Authentication Type:"
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:250
+msgid "Encryption Type:"
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:90
+msgid "Starting..."
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:104 ../shell/view/home/MeshBox.py:295
+msgid "Resume"
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:111
+#: ../lib/sugar/activity/activity.py:132
+msgid "Stop"
+msgstr ""
+
+#: ../shell/view/Shell.py:285
+msgid "Screenshot"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:159
+msgid "Reboot"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:164
+msgid "Shutdown"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:170
+msgid "Register"
+msgstr ""
+
+#. Only show disconnect when there's a mesh device, because mesh takes
+#. priority over the normal wireless device. NM doesn't have a "disconnect"
+#. method for a device either (for various reasons) so this doesn't
+#. have a good mapping
+#: ../shell/view/home/MeshBox.py:90 ../shell/view/home/MeshBox.py:197
+#: ../shell/view/devices/network/wireless.py:113
+#: ../shell/view/devices/network/mesh.py:83
+msgid "Disconnect..."
+msgstr ""
+
+#: ../shell/view/home/MeshBox.py:195 ../shell/view/devices/network/mesh.py:37
+#: ../shell/view/devices/network/mesh.py:62
+#: ../shell/view/devices/network/mesh.py:66
+msgid "Mesh Network"
+msgstr ""
+
+#: ../shell/view/home/MeshBox.py:300
+msgid "Join"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:38
+msgid "My Battery life"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:94
+msgid "Battery charging"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:96
+msgid "Battery discharging"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:98
+msgid "Battery fully charged"
+msgstr ""
+
+#: ../shell/view/devices/network/wireless.py:61
+msgid "Disconnected"
+msgstr ""
+
+#: ../shell/view/devices/network/wireless.py:131
+msgid "Channel"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:42
+msgid "Neighborhood"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:54
+msgid "Group"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:66
+msgid "Home"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:78
+msgid "Activity"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:115
+msgid "Share with:"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:117
+msgid "Private"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:118
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:126
+msgid "Keep"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:245
+msgid "Undo"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:250
+msgid "Redo"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:260
+msgid "Copy"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:265
+msgid "Paste"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:454
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:824
+msgid "Keep error"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:825
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:828
+msgid "Don't stop"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:831
+msgid "Stop anyway"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:164 ../lib/sugar/graphics/alert.py:206
+msgid "Cancel"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:168
+msgid "Ok"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:216
+msgid "Continue"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:244
+msgid "OK"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d year"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d years"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d month"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d months"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d week"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d weeks"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d day"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d days"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hour"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hours"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minute"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minutes"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d second"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d seconds"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:191
+msgid " and "
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:193
+msgid ", "
+msgstr ""
+
+#: ../shell/controlpanel/control.py:213
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:267
+msgid "Error in specified color modifiers."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:270
+msgid "Error in specified colors."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:307
+msgid "off"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:309
+msgid "on"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:310
+msgid "State is unknown."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:332
+msgid "Error in specified radio argument use on/off."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:336
+msgid "Permission denied. You need to be root to run this method."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:366
+msgid "Error in reading timezone"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:397
+#, python-format
+msgid "Error copying timezone (from %s): %s"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:402
+#, python-format
+msgid "Changing permission of timezone: %s"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:412
+msgid "Error timezone does not exist."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:417 ../shell/controlpanel/control.py:436
+#, python-format
+msgid "Could not access %s. Create standard settings."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:463
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:473
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:105
+msgid "Connected to a School Mesh Portal"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:107
+msgid "Looking for a School Mesh Portal..."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:110
+msgid "Connected to an XO Mesh Portal"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:112
+msgid "Looking for an XO Mesh Portal..."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:115
+msgid "Connected to a Simple Mesh"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:117
+msgid "Starting a Simple Mesh"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:124
+msgid "Unknown Mesh"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:175 ../shell/view/home/HomeBox.py:216
+msgid "About this XO"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:222
+msgid "Not available"
+msgstr ""
diff --git a/shell/po/kos.po b/shell/po/kos.po
new file mode 100644
index 0000000..2dac090
--- /dev/null
+++ b/shell/po/kos.po
@@ -0,0 +1,1211 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-08-26 00:31-0400\n"
+"PO-Revision-Date: 2009-09-02 21:21-0400\n"
+"Last-Translator: Chris Leonard <cjl@laptop.org>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: kos\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Pootle 1.2.1\n"
+
+#: ../extensions/cpsection/aboutme/__init__.py:24
+msgid "About Me"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:43
+msgid "You must enter a name."
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:68
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:71
+#, python-format
+msgid "stroke: %s"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:73
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:75
+#, python-format
+msgid "fill: %s"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:86
+msgid "Error in specified color modifiers."
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:89
+msgid "Error in specified colors."
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/view.py:94 ../src/jarabe/intro/window.py:92
+msgid "Name:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/view.py:128
+msgid "Click to change your color:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/__init__.py:21
+msgid "About my Computer"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/model.py:28
+msgid "Not available"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:60
+msgid "Identity"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:69
+msgid "Serial Number:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:91
+msgid "Software"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:100
+msgid "Build:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:115
+msgid "Sugar:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:131
+msgid "Firmware:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:146
+msgid "Wireless Firmware:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:169
+msgid "Copyright and License"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:184
+msgid ""
+"Sugar is the graphical user interface that you are looking at. Sugar is free "
+"software, covered by the GNU General Public License, and you are welcome to "
+"change it and/or distribute copies of it under certain conditions described "
+"therein."
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:196
+msgid "Full license:"
+msgstr ""
+
+#: ../extensions/cpsection/datetime/__init__.py:21
+msgid "Date & Time"
+msgstr ""
+
+#: ../extensions/cpsection/datetime/model.py:87
+msgid "Error timezone does not exist."
+msgstr ""
+
+#: ../extensions/cpsection/datetime/view.py:68 ../data/sugar.schemas.in.h:27
+msgid "Timezone"
+msgstr ""
+
+#: ../extensions/cpsection/frame/__init__.py:21
+msgid "Frame"
+msgstr ""
+
+#: ../extensions/cpsection/frame/model.py:38
+#: ../extensions/cpsection/frame/model.py:60
+msgid "Value must be an integer."
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:26
+msgid "never"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:27
+msgid "instantaneous"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:28
+#, python-format
+msgid "%s seconds"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:52
+msgid "Activation Delay"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:76
+msgid "Corner"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:111
+msgid "Edge"
+msgstr ""
+
+#: ../extensions/cpsection/keyboard/__init__.py:21
+#: ../extensions/cpsection/keyboard/view.py:31
+msgid "Keyboard"
+msgstr ""
+
+#: ../extensions/cpsection/keyboard/view.py:187
+msgid "Keyboard Model"
+msgstr ""
+
+#: ../extensions/cpsection/keyboard/view.py:243
+msgid "Key(s) to change layout"
+msgstr ""
+
+#: ../extensions/cpsection/keyboard/view.py:311
+msgid "Keyboard Layout(s)"
+msgstr ""
+
+#: ../extensions/cpsection/language/__init__.py:21
+#: ../extensions/cpsection/language/view.py:33
+msgid "Language"
+msgstr ""
+
+#: ../extensions/cpsection/language/model.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr ""
+
+#: ../extensions/cpsection/language/model.py:124
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr ""
+
+#: ../extensions/cpsection/language/model.py:144
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr ""
+
+#: ../extensions/cpsection/language/view.py:56
+msgid ""
+"Add languages in the order you prefer. If a translation is not available, "
+"the next in the list will be used."
+msgstr ""
+
+#: ../extensions/cpsection/network/__init__.py:21
+#: ../extensions/cpsection/network/view.py:28
+msgid "Network"
+msgstr ""
+
+#: ../extensions/cpsection/network/model.py:79
+msgid "State is unknown."
+msgstr ""
+
+#: ../extensions/cpsection/network/model.py:105
+msgid "Error in specified radio argument use on/off."
+msgstr ""
+
+#: ../extensions/cpsection/network/model.py:137
+msgid "Error in specified argument use 0/1."
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:59
+msgid "Wireless"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:67
+msgid "Turn off the wireless radio to save battery life"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:80
+msgid "Radio"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:96
+msgid "Discard network history if you have trouble connecting to the network"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:105
+msgid "Discard network history"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:118
+msgid "Collaboration"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:126
+msgid ""
+"The server is the equivalent of what room you are in; people on the same "
+"server will be able to see each other, even when they aren't on the same "
+"network."
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:136
+msgid "Server:"
+msgstr ""
+
+#: ../extensions/cpsection/power/__init__.py:21
+msgid "Power"
+msgstr ""
+
+#: ../extensions/cpsection/power/model.py:54
+msgid "Error in automatic pm argument, use on/off."
+msgstr ""
+
+#: ../extensions/cpsection/power/model.py:81
+msgid "Error in extreme pm argument, use on/off."
+msgstr ""
+
+#: ../extensions/cpsection/power/view.py:47
+msgid "Power management"
+msgstr ""
+
+#: ../extensions/cpsection/power/view.py:57
+msgid "Automatic power management (increases battery life)"
+msgstr ""
+
+#: ../extensions/cpsection/power/view.py:85
+msgid ""
+"Extreme power management (disableswireless radio, increases battery life)"
+msgstr ""
+
+#: ../extensions/cpsection/updater/__init__.py:21
+msgid "Software update"
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:62
+msgid ""
+"Software updates correct errors, eliminate security vulnerabilities, and "
+"provide new features."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:122
+#, python-format
+msgid "Checking %s..."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:124
+#, python-format
+msgid "Downloading %s..."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:126
+#, python-format
+msgid "Updating %s..."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:135
+msgid "Your software is up-to-date"
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:137
+#, python-format
+msgid "You can install %s update"
+msgid_plural "You can install %s updates"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../extensions/cpsection/updater/view.py:155
+msgid "Checking for updates..."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:160
+msgid "Installing updates..."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:165
+#, python-format
+msgid "%s update was installed"
+msgid_plural "%s updates were installed"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../extensions/cpsection/updater/view.py:244
+msgid "Install selected"
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:265
+#, python-format
+msgid "Download size: %s"
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:353
+#, python-format
+msgid "From version %(current)d to %(new)s (Size: %(size)s)"
+msgstr ""
+
+#. TRANS: download size is 0
+#: ../extensions/cpsection/updater/view.py:373
+msgid "None"
+msgstr ""
+
+#. TRANS: download size of very small updates
+#: ../extensions/cpsection/updater/view.py:376
+msgid "1 KB"
+msgstr ""
+
+#. TRANS: download size of small updates, e.g. '250 KB'
+#: ../extensions/cpsection/updater/view.py:379
+#, python-format
+msgid "%.0f KB"
+msgstr ""
+
+#. TRANS: download size of updates, e.g. '2.3 MB'
+#: ../extensions/cpsection/updater/view.py:382
+#, python-format
+msgid "%.1f MB"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:58
+msgid "My Battery"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:137
+msgid "Removed"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:140
+msgid "Charging"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:143
+msgid "Very little power remaining"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:149
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:152
+msgid "Charged"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:44
+#, python-format
+msgid "IP address: %s"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:110
+msgid "Disconnect..."
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:114
+msgid "Create new wireless network"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:120
+#: ../src/jarabe/desktop/meshbox.py:261
+msgid "Connecting..."
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:124
+#: ../extensions/deviceicon/network.py:186
+#: ../src/jarabe/desktop/meshbox.py:267
+msgid "Connected"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:146
+msgid "Channel"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:161
+msgid "Wired Network"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:189
+msgid "Speed"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:415
+#, python-format
+msgid "%s's network %s"
+msgstr ""
+
+#: ../extensions/deviceicon/speaker.py:59
+msgid "My Speakers"
+msgstr ""
+
+#: ../extensions/deviceicon/speaker.py:133
+msgid "Unmute"
+msgstr ""
+
+#: ../extensions/deviceicon/speaker.py:136
+msgid "Mute"
+msgstr ""
+
+#: ../extensions/globalkey/screenshot.py:56
+msgid "Mesh"
+msgstr ""
+
+#: ../extensions/globalkey/screenshot.py:58
+#: ../src/jarabe/frame/zoomtoolbar.py:39
+msgid "Group"
+msgstr ""
+
+#: ../extensions/globalkey/screenshot.py:60
+#: ../src/jarabe/frame/zoomtoolbar.py:41
+msgid "Home"
+msgstr ""
+
+#: ../extensions/globalkey/screenshot.py:66
+#: ../src/jarabe/frame/zoomtoolbar.py:43
+msgid "Activity"
+msgstr ""
+
+#: ../extensions/globalkey/screenshot.py:69
+msgid "Screenshot"
+msgstr ""
+
+#: ../extensions/globalkey/screenshot.py:71
+#, python-format
+msgid "Screenshot of \"%s\""
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:1
+msgid "Backup URL"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:2
+msgid ""
+"Color for the XO icon that is used throughout the desktop. The string is "
+"composed of the stroke color and fill color, format is that of rbg colors. "
+"Example: #AC32FF,#9A5200"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:3
+msgid "Corner Delay"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:4
+msgid "Delay for the activation of the frame using the corners."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:5
+msgid "Delay for the activation of the frame using the edges."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:6
+msgid "Edge Delay"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:7
+msgid "Favorites Layout"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:8
+msgid "Favorites resume mode"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:9
+msgid ""
+"If TRUE, Sugar will make us searchable for the other users of the Jabber "
+"server."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:10
+msgid "If TRUE, Sugar will show a \"Log out\" option."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:11
+msgid "Jabber Server"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:12
+msgid "Keyboard layouts"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:13
+msgid "Keyboard model"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:14
+msgid "Keyboard options"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:15
+msgid "Layout of the favorites view."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:16
+msgid ""
+"List of keyboard layouts. Each entry should be in the form layout(variant)"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:17
+msgid "List of keyboard options."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:18
+msgid "Power Automatic"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:19
+msgid "Power Automatic."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:20
+msgid "Power Extreme"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:21
+msgid "Power Extreme."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:22
+msgid "Publish to Gadget"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:23
+msgid "Setting for muting the sound device."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:24
+msgid "Show Log out"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:25
+msgid "Sound Muted"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:26
+msgid "The keyboard model to be used"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:28
+msgid "Timezone setting for the system."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:29
+msgid "Url of the jabber server to use."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:30
+msgid "Url where the backup is saved to."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:31
+msgid "User Color"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:32
+msgid "User Name"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:33
+msgid "User name that is used throughout the desktop."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:34
+msgid "Volume Level"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:35
+msgid "Volume level for the sound device."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:36
+msgid ""
+"When in resume mode, clicking on a favorite icon will cause the last entry "
+"for that activity to be resumed."
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:28
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:30
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:31
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr ""
+
+#. TRANS: Translators, there's a empty line at the end of this string,
+#. which must appear in the translated string (msgstr) as well.
+#: ../src/jarabe/controlpanel/cmd.py:37
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" -c key clear the current value for the key \n"
+" "
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:50
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:280
+msgid "Warning"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:281
+#: ../src/jarabe/controlpanel/sectionview.py:42
+msgid "Changes require restart"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:284
+msgid "Cancel changes"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:289 ../src/jarabe/desktop/homebox.py:70
+msgid "Later"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:293
+msgid "Restart now"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/toolbar.py:61 ../src/jarabe/intro/window.py:188
+msgid "Done"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/toolbar.py:115
+#: ../src/jarabe/desktop/homebox.py:68
+#: ../src/jarabe/frame/activitiestray.py:726
+#: ../src/jarabe/frame/activitiestray.py:822
+#: ../src/jarabe/frame/activitiestray.py:850
+msgid "Cancel"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/toolbar.py:121
+#: ../src/jarabe/desktop/favoritesview.py:332
+msgid "Ok"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:80
+#: ../src/jarabe/journal/listview.py:147
+msgid "Title"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:91
+msgid "Version"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:105
+#: ../src/jarabe/journal/listview.py:178
+msgid "Date"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:234
+#, python-format
+msgid "Version %s"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:355
+msgid "Confirm erase"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:357
+#, python-format
+msgid "Confirm erase: Do you want to permanently erase %s?"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:361
+#: ../src/jarabe/frame/clipboardmenu.py:62
+#: ../src/jarabe/view/viewsource.py:218
+msgid "Keep"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:364
+#: ../src/jarabe/desktop/activitieslist.py:406
+#: ../src/jarabe/journal/journaltoolbox.py:360
+#: ../src/jarabe/journal/palettes.py:112
+msgid "Erase"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:427
+msgid "Remove favorite"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:431
+msgid "Make favorite"
+msgstr ""
+
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:116
+msgid "Freeform"
+msgstr ""
+
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:198
+msgid "Ring"
+msgstr ""
+
+#. TRANS: label for the spiral layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:334
+msgid "Spiral"
+msgstr ""
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:401
+msgid "Box"
+msgstr ""
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:442
+msgid "Triangle"
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:323
+msgid "Registration Failed"
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:324
+#, python-format
+msgid "%s"
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:326
+msgid "Registration Successful"
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:327
+msgid "You are now registered with your school server."
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:671
+msgid "Register"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:63
+msgid "Software Update"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:64
+msgid "Update your activities to ensure compatibility with your new software"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:73
+msgid "Check now"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:192
+msgid "List view"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:193
+msgid "<Ctrl>2"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:255
+msgid "Favorites view"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:256
+msgid "<Ctrl>1"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:131
+msgid "Key Type:"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:151
+msgid "Authentication Type:"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:215
+msgid "WPA & WPA2 Personal"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:224
+msgid "Wireless Security:"
+msgstr ""
+
+#: ../src/jarabe/desktop/meshbox.py:136
+msgid "Connect"
+msgstr ""
+
+#: ../src/jarabe/desktop/meshbox.py:140
+msgid "Disconnect"
+msgstr ""
+
+#. TRANS: Action label for resuming an activity.
+#: ../src/jarabe/desktop/meshbox.py:463
+#: ../src/jarabe/frame/activitiestray.py:761
+#: ../src/jarabe/journal/journaltoolbox.py:428
+#: ../src/jarabe/journal/palettes.py:72 ../src/jarabe/view/palettes.py:64
+msgid "Resume"
+msgstr ""
+
+#: ../src/jarabe/desktop/meshbox.py:468
+#: ../src/jarabe/frame/activitiestray.py:235
+msgid "Join"
+msgstr ""
+
+#: ../src/jarabe/desktop/schoolserver.py:34
+msgid "Cannot obtain data needed for registration."
+msgstr ""
+
+#: ../src/jarabe/desktop/schoolserver.py:51
+msgid "Cannot connect to the server."
+msgstr ""
+
+#: ../src/jarabe/desktop/schoolserver.py:56
+msgid "The server could not complete the request."
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:240
+#: ../src/jarabe/frame/activitiestray.py:698
+msgid "Decline"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:650
+#, python-format
+msgid "%dB"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:652
+#, python-format
+msgid "%dKB"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:654
+#, python-format
+msgid "%dMB"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:671
+#, python-format
+msgid "%s of %s"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:683
+#, python-format
+msgid "Transfer from %r"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:693
+msgid "Accept"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:716
+#: ../src/jarabe/frame/activitiestray.py:840
+#, python-format
+msgid "%s (%s)"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:750
+#: ../src/jarabe/frame/activitiestray.py:875
+msgid "Dismiss"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:810
+#, python-format
+msgid "Transfer to %r"
+msgstr ""
+
+#: ../src/jarabe/frame/clipboardmenu.py:52
+msgid "Remove"
+msgstr ""
+
+#: ../src/jarabe/frame/clipboardmenu.py:57
+#: ../src/jarabe/frame/clipboardmenu.py:80
+msgid "Open"
+msgstr ""
+
+#: ../src/jarabe/frame/clipboardmenu.py:85
+msgid "Open with"
+msgstr ""
+
+#: ../src/jarabe/frame/clipboardobject.py:49
+#, python-format
+msgid "%s clipping"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:37
+msgid "Neighborhood"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:37
+msgid "F1"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:39
+msgid "F2"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:41
+msgid "F3"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:43
+msgid "F4"
+msgstr ""
+
+#: ../src/jarabe/intro/window.py:124
+msgid "Click to change color:"
+msgstr ""
+
+#: ../src/jarabe/intro/window.py:174 ../src/jarabe/journal/detailview.py:103
+msgid "Back"
+msgstr ""
+
+#: ../src/jarabe/intro/window.py:191
+msgid "Next"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:164
+#: ../src/jarabe/journal/palettes.py:66
+msgid "Untitled"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:210
+msgid "No preview"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:229
+#, python-format
+msgid "Kind: %s"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:229
+msgid "Unknown"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:230
+#, python-format
+msgid "Date: %s"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:231
+#, python-format
+msgid "Size: %s"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:253 ../src/jarabe/journal/misc.py:92
+msgid "No date"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:260
+msgid "Participants:"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:283
+msgid "Description:"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:309
+msgid "Tags:"
+msgstr ""
+
+#: ../src/jarabe/journal/journalactivity.py:108
+#: ../src/jarabe/journal/volumestoolbar.py:47
+msgid "Journal"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:67
+msgid "Search"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:126
+msgid "Anytime"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:128
+msgid "Today"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:130
+msgid "Since yesterday"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 7 days.
+#: ../src/jarabe/journal/journaltoolbox.py:132
+msgid "Past week"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 30 days.
+#: ../src/jarabe/journal/journaltoolbox.py:134
+msgid "Past month"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 356 days.
+#: ../src/jarabe/journal/journaltoolbox.py:136
+msgid "Past year"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:143
+msgid "Anyone"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:145
+msgid "My friends"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:146
+msgid "My class"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:274
+msgid "Anything"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:350
+#: ../src/jarabe/journal/palettes.py:90
+msgid "Copy"
+msgstr ""
+
+#. TRANS: Action label for starting an entry.
+#: ../src/jarabe/journal/journaltoolbox.py:431
+#: ../src/jarabe/journal/palettes.py:75
+msgid "Start"
+msgstr ""
+
+#: ../src/jarabe/journal/listview.py:361
+msgid "Your Journal is empty"
+msgstr ""
+
+#: ../src/jarabe/journal/listview.py:363
+msgid "No matching entries"
+msgstr ""
+
+#: ../src/jarabe/journal/listview.py:374
+msgid "Clear search"
+msgstr ""
+
+#: ../src/jarabe/journal/modalalert.py:63
+msgid "Your Journal is full"
+msgstr ""
+
+#: ../src/jarabe/journal/modalalert.py:67
+msgid "Please delete some old Journal entries to make space for new ones."
+msgstr ""
+
+#: ../src/jarabe/journal/modalalert.py:79
+msgid "Show Journal"
+msgstr ""
+
+#: ../src/jarabe/journal/objectchooser.py:146
+msgid "Choose an object"
+msgstr ""
+
+#: ../src/jarabe/journal/objectchooser.py:151
+#: ../src/jarabe/view/viewsource.py:308
+msgid "Close"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:73
+msgid "Resume with"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:76
+msgid "Start with"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:98
+msgid "Send to"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:107
+msgid "View Details"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:185
+msgid "No friends present"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:190
+msgid "No valid connection found"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:218
+msgid "No activity to resume entry"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:220
+msgid "No activity to start entry"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:62
+msgid "Remove friend"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:65
+msgid "Make friend"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:82
+msgid "Shutdown"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:90
+msgid "Logout"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:95
+msgid "My Settings"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:130
+#, python-format
+msgid "Invite to %s"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:45
+msgid "Starting..."
+msgstr ""
+
+#. TODO: share-with, keep
+#: ../src/jarabe/view/palettes.py:71
+msgid "View Source"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:82
+msgid "Stop"
+msgstr "Tui"
+
+#: ../src/jarabe/view/palettes.py:122
+msgid "Start new"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:171
+msgid "Show contents"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:193 ../src/jarabe/view/palettes.py:243
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:218
+msgid "Unmount"
+msgstr ""
+
+#: ../src/jarabe/view/viewsource.py:208
+msgid "Instance Source"
+msgstr ""
+
+#: ../src/jarabe/view/viewsource.py:233
+msgid "Source"
+msgstr ""
+
+#: ../src/jarabe/view/viewsource.py:292
+msgid "Activity Bundle Source"
+msgstr ""
+
+#: ../src/jarabe/view/viewsource.py:299
+#, python-format
+msgid "View source: %r"
+msgstr ""
diff --git a/shell/po/mg.po b/shell/po/mg.po
new file mode 100644
index 0000000..694551c
--- /dev/null
+++ b/shell/po/mg.po
@@ -0,0 +1,1213 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-08-26 00:31-0400\n"
+"PO-Revision-Date: 2009-05-21 15:10-0400\n"
+"Last-Translator: dina garnier oeliarisoa <dina.garnier.oeliarisoa@gmail.com>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: mg\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+"X-Generator: Pootle 1.2.1\n"
+
+#: ../extensions/cpsection/aboutme/__init__.py:24
+msgid "About Me"
+msgstr "Momba ahy"
+
+#: ../extensions/cpsection/aboutme/model.py:43
+msgid "You must enter a name."
+msgstr "Tsy maintsy mampiditra anarana ianao"
+
+#: ../extensions/cpsection/aboutme/model.py:68
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:71
+#, python-format
+msgid "stroke: %s"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:73
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:75
+#, python-format
+msgid "fill: %s"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:86
+msgid "Error in specified color modifiers."
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:89
+msgid "Error in specified colors."
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/view.py:94 ../src/jarabe/intro/window.py:92
+msgid "Name:"
+msgstr "Anarana:"
+
+#: ../extensions/cpsection/aboutme/view.py:128
+msgid "Click to change your color:"
+msgstr "Tsindrio raha hanova ny lokonao:"
+
+#: ../extensions/cpsection/aboutcomputer/__init__.py:21
+msgid "About my Computer"
+msgstr "Ny momba ny solosaina"
+
+#: ../extensions/cpsection/aboutcomputer/model.py:28
+msgid "Not available"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:60
+msgid "Identity"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:69
+msgid "Serial Number:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:91
+msgid "Software"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:100
+msgid "Build:"
+msgstr "Aoreno:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:115
+msgid "Sugar:"
+msgstr "Sugar:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:131
+msgid "Firmware:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:146
+msgid "Wireless Firmware:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:169
+msgid "Copyright and License"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:184
+msgid ""
+"Sugar is the graphical user interface that you are looking at. Sugar is free "
+"software, covered by the GNU General Public License, and you are welcome to "
+"change it and/or distribute copies of it under certain conditions described "
+"therein."
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:196
+msgid "Full license:"
+msgstr ""
+
+#: ../extensions/cpsection/datetime/__init__.py:21
+msgid "Date & Time"
+msgstr ""
+
+#: ../extensions/cpsection/datetime/model.py:87
+msgid "Error timezone does not exist."
+msgstr ""
+
+#: ../extensions/cpsection/datetime/view.py:68 ../data/sugar.schemas.in.h:27
+msgid "Timezone"
+msgstr ""
+
+#: ../extensions/cpsection/frame/__init__.py:21
+msgid "Frame"
+msgstr "Faritra"
+
+#: ../extensions/cpsection/frame/model.py:38
+#: ../extensions/cpsection/frame/model.py:60
+msgid "Value must be an integer."
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:26
+msgid "never"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:27
+msgid "instantaneous"
+msgstr "eo no ho eo"
+
+#: ../extensions/cpsection/frame/view.py:28
+#, python-format
+msgid "%s seconds"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:52
+msgid "Activation Delay"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:76
+msgid "Corner"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:111
+msgid "Edge"
+msgstr ""
+
+#: ../extensions/cpsection/keyboard/__init__.py:21
+#: ../extensions/cpsection/keyboard/view.py:31
+msgid "Keyboard"
+msgstr ""
+
+#: ../extensions/cpsection/keyboard/view.py:187
+msgid "Keyboard Model"
+msgstr ""
+
+#: ../extensions/cpsection/keyboard/view.py:243
+msgid "Key(s) to change layout"
+msgstr ""
+
+#: ../extensions/cpsection/keyboard/view.py:311
+msgid "Keyboard Layout(s)"
+msgstr ""
+
+#: ../extensions/cpsection/language/__init__.py:21
+#: ../extensions/cpsection/language/view.py:33
+msgid "Language"
+msgstr "Fiteny"
+
+#: ../extensions/cpsection/language/model.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr ""
+
+#: ../extensions/cpsection/language/model.py:124
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr "Tsy fantatra ny fitenin'ny sora-drindra=%s."
+
+#: ../extensions/cpsection/language/model.py:144
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr "Azafady izaho tsy miteny '%s'."
+
+#: ../extensions/cpsection/language/view.py:56
+msgid ""
+"Add languages in the order you prefer. If a translation is not available, "
+"the next in the list will be used."
+msgstr ""
+
+#: ../extensions/cpsection/network/__init__.py:21
+#: ../extensions/cpsection/network/view.py:28
+msgid "Network"
+msgstr ""
+
+#: ../extensions/cpsection/network/model.py:79
+msgid "State is unknown."
+msgstr ""
+
+#: ../extensions/cpsection/network/model.py:105
+msgid "Error in specified radio argument use on/off."
+msgstr ""
+
+#: ../extensions/cpsection/network/model.py:137
+msgid "Error in specified argument use 0/1."
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:59
+msgid "Wireless"
+msgstr "Tsy misy taroby"
+
+#: ../extensions/cpsection/network/view.py:67
+msgid "Turn off the wireless radio to save battery life"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:80
+msgid "Radio"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:96
+msgid "Discard network history if you have trouble connecting to the network"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:105
+msgid "Discard network history"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:118
+msgid "Collaboration"
+msgstr "Fiaraha-miasa"
+
+#: ../extensions/cpsection/network/view.py:126
+msgid ""
+"The server is the equivalent of what room you are in; people on the same "
+"server will be able to see each other, even when they aren't on the same "
+"network."
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:136
+msgid "Server:"
+msgstr "Mpamatsy, Mpizara"
+
+#: ../extensions/cpsection/power/__init__.py:21
+msgid "Power"
+msgstr "Hery, Tanjaka"
+
+#: ../extensions/cpsection/power/model.py:54
+msgid "Error in automatic pm argument, use on/off."
+msgstr ""
+
+#: ../extensions/cpsection/power/model.py:81
+msgid "Error in extreme pm argument, use on/off."
+msgstr ""
+
+#: ../extensions/cpsection/power/view.py:47
+msgid "Power management"
+msgstr ""
+
+#: ../extensions/cpsection/power/view.py:57
+msgid "Automatic power management (increases battery life)"
+msgstr ""
+
+#: ../extensions/cpsection/power/view.py:85
+msgid ""
+"Extreme power management (disableswireless radio, increases battery life)"
+msgstr ""
+
+#: ../extensions/cpsection/updater/__init__.py:21
+msgid "Software update"
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:62
+msgid ""
+"Software updates correct errors, eliminate security vulnerabilities, and "
+"provide new features."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:122
+#, python-format
+msgid "Checking %s..."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:124
+#, python-format
+msgid "Downloading %s..."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:126
+#, python-format
+msgid "Updating %s..."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:135
+msgid "Your software is up-to-date"
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:137
+#, python-format
+msgid "You can install %s update"
+msgid_plural "You can install %s updates"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../extensions/cpsection/updater/view.py:155
+msgid "Checking for updates..."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:160
+msgid "Installing updates..."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:165
+#, python-format
+msgid "%s update was installed"
+msgid_plural "%s updates were installed"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../extensions/cpsection/updater/view.py:244
+msgid "Install selected"
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:265
+#, python-format
+msgid "Download size: %s"
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:353
+#, python-format
+msgid "From version %(current)d to %(new)s (Size: %(size)s)"
+msgstr ""
+
+#. TRANS: download size is 0
+#: ../extensions/cpsection/updater/view.py:373
+#, fuzzy
+msgid "None"
+msgstr "Vita"
+
+#. TRANS: download size of very small updates
+#: ../extensions/cpsection/updater/view.py:376
+msgid "1 KB"
+msgstr ""
+
+#. TRANS: download size of small updates, e.g. '250 KB'
+#: ../extensions/cpsection/updater/view.py:379
+#, python-format
+msgid "%.0f KB"
+msgstr ""
+
+#. TRANS: download size of updates, e.g. '2.3 MB'
+#: ../extensions/cpsection/updater/view.py:382
+#, python-format
+msgid "%.1f MB"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:58
+msgid "My Battery"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:137
+msgid "Removed"
+msgstr "Esory"
+
+#: ../extensions/deviceicon/battery.py:140
+msgid "Charging"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:143
+msgid "Very little power remaining"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:149
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:152
+msgid "Charged"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:44
+#, python-format
+msgid "IP address: %s"
+msgstr "Adiresy IP: %s"
+
+#: ../extensions/deviceicon/network.py:110
+msgid "Disconnect..."
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:114
+msgid "Create new wireless network"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:120
+#: ../src/jarabe/desktop/meshbox.py:261
+msgid "Connecting..."
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:124
+#: ../extensions/deviceicon/network.py:186
+#: ../src/jarabe/desktop/meshbox.py:267
+msgid "Connected"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:146
+msgid "Channel"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:161
+msgid "Wired Network"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:189
+msgid "Speed"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:415
+#, python-format
+msgid "%s's network %s"
+msgstr ""
+
+#: ../extensions/deviceicon/speaker.py:59
+msgid "My Speakers"
+msgstr ""
+
+#: ../extensions/deviceicon/speaker.py:133
+msgid "Unmute"
+msgstr "Avereno ny feo"
+
+#: ../extensions/deviceicon/speaker.py:136
+msgid "Mute"
+msgstr "Esory ny feo"
+
+#: ../extensions/globalkey/screenshot.py:56
+msgid "Mesh"
+msgstr ""
+
+#: ../extensions/globalkey/screenshot.py:58
+#: ../src/jarabe/frame/zoomtoolbar.py:39
+msgid "Group"
+msgstr ""
+
+#: ../extensions/globalkey/screenshot.py:60
+#: ../src/jarabe/frame/zoomtoolbar.py:41
+msgid "Home"
+msgstr ""
+
+#: ../extensions/globalkey/screenshot.py:66
+#: ../src/jarabe/frame/zoomtoolbar.py:43
+msgid "Activity"
+msgstr ""
+
+#: ../extensions/globalkey/screenshot.py:69
+msgid "Screenshot"
+msgstr ""
+
+#: ../extensions/globalkey/screenshot.py:71
+#, python-format
+msgid "Screenshot of \"%s\""
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:1
+msgid "Backup URL"
+msgstr "Tahirizo ny URL"
+
+#: ../data/sugar.schemas.in.h:2
+msgid ""
+"Color for the XO icon that is used throughout the desktop. The string is "
+"composed of the stroke color and fill color, format is that of rbg colors. "
+"Example: #AC32FF,#9A5200"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:3
+msgid "Corner Delay"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:4
+msgid "Delay for the activation of the frame using the corners."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:5
+msgid "Delay for the activation of the frame using the edges."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:6
+msgid "Edge Delay"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:7
+msgid "Favorites Layout"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:8
+msgid "Favorites resume mode"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:9
+msgid ""
+"If TRUE, Sugar will make us searchable for the other users of the Jabber "
+"server."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:10
+msgid "If TRUE, Sugar will show a \"Log out\" option."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:11
+msgid "Jabber Server"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:12
+msgid "Keyboard layouts"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:13
+msgid "Keyboard model"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:14
+msgid "Keyboard options"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:15
+msgid "Layout of the favorites view."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:16
+msgid ""
+"List of keyboard layouts. Each entry should be in the form layout(variant)"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:17
+msgid "List of keyboard options."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:18
+msgid "Power Automatic"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:19
+msgid "Power Automatic."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:20
+msgid "Power Extreme"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:21
+msgid "Power Extreme."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:22
+msgid "Publish to Gadget"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:23
+msgid "Setting for muting the sound device."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:24
+msgid "Show Log out"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:25
+msgid "Sound Muted"
+msgstr "Nesorina ny feo"
+
+#: ../data/sugar.schemas.in.h:26
+msgid "The keyboard model to be used"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:28
+msgid "Timezone setting for the system."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:29
+msgid "Url of the jabber server to use."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:30
+msgid "Url where the backup is saved to."
+msgstr "Url misy ny tahiry."
+
+#: ../data/sugar.schemas.in.h:31
+msgid "User Color"
+msgstr "Lokon'ny mpampiasa"
+
+#: ../data/sugar.schemas.in.h:32
+msgid "User Name"
+msgstr "Anaran'ny mpampiasa"
+
+#: ../data/sugar.schemas.in.h:33
+msgid "User name that is used throughout the desktop."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:34
+msgid "Volume Level"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:35
+msgid "Volume level for the sound device."
+msgstr "Fanovàna ny hadirim-peo."
+
+#: ../data/sugar.schemas.in.h:36
+msgid ""
+"When in resume mode, clicking on a favorite icon will cause the last entry "
+"for that activity to be resumed."
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:28
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:30
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:31
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr ""
+
+#. TRANS: Translators, there's a empty line at the end of this string,
+#. which must appear in the translated string (msgstr) as well.
+#: ../src/jarabe/controlpanel/cmd.py:37
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" -c key clear the current value for the key \n"
+" "
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:50
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:280
+msgid "Warning"
+msgstr "Tandremo"
+
+#: ../src/jarabe/controlpanel/gui.py:281
+#: ../src/jarabe/controlpanel/sectionview.py:42
+msgid "Changes require restart"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:284
+msgid "Cancel changes"
+msgstr "Foano ny fanovàna"
+
+#: ../src/jarabe/controlpanel/gui.py:289 ../src/jarabe/desktop/homebox.py:70
+msgid "Later"
+msgstr "Amin'ny manaraka"
+
+#: ../src/jarabe/controlpanel/gui.py:293
+msgid "Restart now"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/toolbar.py:61 ../src/jarabe/intro/window.py:188
+msgid "Done"
+msgstr "Vita"
+
+#: ../src/jarabe/controlpanel/toolbar.py:115
+#: ../src/jarabe/desktop/homebox.py:68
+#: ../src/jarabe/frame/activitiestray.py:726
+#: ../src/jarabe/frame/activitiestray.py:822
+#: ../src/jarabe/frame/activitiestray.py:850
+msgid "Cancel"
+msgstr "Foano"
+
+#: ../src/jarabe/controlpanel/toolbar.py:121
+#: ../src/jarabe/desktop/favoritesview.py:332
+msgid "Ok"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:80
+#: ../src/jarabe/journal/listview.py:147
+msgid "Title"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:91
+msgid "Version"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:105
+#: ../src/jarabe/journal/listview.py:178
+msgid "Date"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:234
+#, python-format
+msgid "Version %s"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:355
+msgid "Confirm erase"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:357
+#, python-format
+msgid "Confirm erase: Do you want to permanently erase %s?"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:361
+#: ../src/jarabe/frame/clipboardmenu.py:62
+#: ../src/jarabe/view/viewsource.py:218
+msgid "Keep"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:364
+#: ../src/jarabe/desktop/activitieslist.py:406
+#: ../src/jarabe/journal/journaltoolbox.py:360
+#: ../src/jarabe/journal/palettes.py:112
+msgid "Erase"
+msgstr "Soloy"
+
+#: ../src/jarabe/desktop/activitieslist.py:427
+msgid "Remove favorite"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:431
+msgid "Make favorite"
+msgstr ""
+
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:116
+msgid "Freeform"
+msgstr ""
+
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:198
+msgid "Ring"
+msgstr ""
+
+#. TRANS: label for the spiral layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:334
+msgid "Spiral"
+msgstr ""
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:401
+msgid "Box"
+msgstr "Boaty"
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:442
+msgid "Triangle"
+msgstr "Telozoro"
+
+#: ../src/jarabe/desktop/favoritesview.py:323
+msgid "Registration Failed"
+msgstr "Nisy olana ny fisoratana anarana"
+
+#: ../src/jarabe/desktop/favoritesview.py:324
+#, python-format
+msgid "%s"
+msgstr "%s"
+
+#: ../src/jarabe/desktop/favoritesview.py:326
+msgid "Registration Successful"
+msgstr "Vita soa aman-tsara ny fisoratana anarana"
+
+#: ../src/jarabe/desktop/favoritesview.py:327
+msgid "You are now registered with your school server."
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:671
+msgid "Register"
+msgstr "Voasoratra anarana"
+
+#: ../src/jarabe/desktop/homebox.py:63
+msgid "Software Update"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:64
+msgid "Update your activities to ensure compatibility with your new software"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:73
+msgid "Check now"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:192
+msgid "List view"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:193
+msgid "<Ctrl>2"
+msgstr "<Ctrl>2"
+
+#: ../src/jarabe/desktop/homebox.py:255
+msgid "Favorites view"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:256
+msgid "<Ctrl>1"
+msgstr "<Ctrl>1"
+
+#: ../src/jarabe/desktop/keydialog.py:131
+msgid "Key Type:"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:151
+msgid "Authentication Type:"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:215
+msgid "WPA & WPA2 Personal"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:224
+msgid "Wireless Security:"
+msgstr ""
+
+#: ../src/jarabe/desktop/meshbox.py:136
+msgid "Connect"
+msgstr ""
+
+#: ../src/jarabe/desktop/meshbox.py:140
+msgid "Disconnect"
+msgstr ""
+
+#. TRANS: Action label for resuming an activity.
+#: ../src/jarabe/desktop/meshbox.py:463
+#: ../src/jarabe/frame/activitiestray.py:761
+#: ../src/jarabe/journal/journaltoolbox.py:428
+#: ../src/jarabe/journal/palettes.py:72 ../src/jarabe/view/palettes.py:64
+msgid "Resume"
+msgstr ""
+
+#: ../src/jarabe/desktop/meshbox.py:468
+#: ../src/jarabe/frame/activitiestray.py:235
+msgid "Join"
+msgstr ""
+
+#: ../src/jarabe/desktop/schoolserver.py:34
+msgid "Cannot obtain data needed for registration."
+msgstr "Tsy azo ny laza ilaina amin'ny fisoratana anarana."
+
+# je proposerai aussi "Tsy afaka mifandray amin'ny mpamatsy". Cela dépend si on souhaite traduire "server" par "mpizara" ou "mpamatsy".
+#: ../src/jarabe/desktop/schoolserver.py:51
+msgid "Cannot connect to the server."
+msgstr "Tsy afaka mifandray amin'ny mpizara."
+
+#: ../src/jarabe/desktop/schoolserver.py:56
+msgid "The server could not complete the request."
+msgstr "Tsy afaka mameno ny fangatahana ny mpamatsy."
+
+#: ../src/jarabe/frame/activitiestray.py:240
+#: ../src/jarabe/frame/activitiestray.py:698
+msgid "Decline"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:650
+#, python-format
+msgid "%dB"
+msgstr "%dB"
+
+#: ../src/jarabe/frame/activitiestray.py:652
+#, python-format
+msgid "%dKB"
+msgstr "%dKB"
+
+#: ../src/jarabe/frame/activitiestray.py:654
+#, python-format
+msgid "%dMB"
+msgstr "%dMB"
+
+#: ../src/jarabe/frame/activitiestray.py:671
+#, python-format
+msgid "%s of %s"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:683
+#, python-format
+msgid "Transfer from %r"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:693
+msgid "Accept"
+msgstr "Ekeo"
+
+#: ../src/jarabe/frame/activitiestray.py:716
+#: ../src/jarabe/frame/activitiestray.py:840
+#, python-format
+msgid "%s (%s)"
+msgstr "%s (%s)"
+
+#: ../src/jarabe/frame/activitiestray.py:750
+#: ../src/jarabe/frame/activitiestray.py:875
+msgid "Dismiss"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:810
+#, python-format
+msgid "Transfer to %r"
+msgstr ""
+
+#: ../src/jarabe/frame/clipboardmenu.py:52
+msgid "Remove"
+msgstr "Fafao"
+
+#: ../src/jarabe/frame/clipboardmenu.py:57
+#: ../src/jarabe/frame/clipboardmenu.py:80
+msgid "Open"
+msgstr "Sokafy"
+
+#: ../src/jarabe/frame/clipboardmenu.py:85
+msgid "Open with"
+msgstr "Sokafy miaraka amin'ny"
+
+#: ../src/jarabe/frame/clipboardobject.py:49
+#, python-format
+msgid "%s clipping"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:37
+msgid "Neighborhood"
+msgstr "Ny manodidina"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:37
+msgid "F1"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:39
+msgid "F2"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:41
+msgid "F3"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:43
+msgid "F4"
+msgstr ""
+
+#: ../src/jarabe/intro/window.py:124
+msgid "Click to change color:"
+msgstr "Tsindrio raha te hanova ny loko:"
+
+#: ../src/jarabe/intro/window.py:174 ../src/jarabe/journal/detailview.py:103
+msgid "Back"
+msgstr "Miverina"
+
+#: ../src/jarabe/intro/window.py:191
+msgid "Next"
+msgstr "Manaraka"
+
+#: ../src/jarabe/journal/expandedentry.py:164
+#: ../src/jarabe/journal/palettes.py:66
+msgid "Untitled"
+msgstr "Tsy misy lohateny"
+
+#: ../src/jarabe/journal/expandedentry.py:210
+msgid "No preview"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:229
+#, python-format
+msgid "Kind: %s"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:229
+msgid "Unknown"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:230
+#, python-format
+msgid "Date: %s"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:231
+#, python-format
+msgid "Size: %s"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:253 ../src/jarabe/journal/misc.py:92
+msgid "No date"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:260
+msgid "Participants:"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:283
+msgid "Description:"
+msgstr "Mombamomba:"
+
+#: ../src/jarabe/journal/expandedentry.py:309
+msgid "Tags:"
+msgstr ""
+
+#: ../src/jarabe/journal/journalactivity.py:108
+#: ../src/jarabe/journal/volumestoolbar.py:47
+msgid "Journal"
+msgstr "Firaiketana"
+
+#: ../src/jarabe/journal/journaltoolbox.py:67
+msgid "Search"
+msgstr "Tadiavo"
+
+#: ../src/jarabe/journal/journaltoolbox.py:126
+msgid "Anytime"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:128
+msgid "Today"
+msgstr "Anio, Androany"
+
+#: ../src/jarabe/journal/journaltoolbox.py:130
+msgid "Since yesterday"
+msgstr "Nanomboka omaly"
+
+#. TRANS: Filter entries modified during the last 7 days.
+#: ../src/jarabe/journal/journaltoolbox.py:132
+msgid "Past week"
+msgstr "Tamin'ny herinandro lasa"
+
+#. TRANS: Filter entries modified during the last 30 days.
+#: ../src/jarabe/journal/journaltoolbox.py:134
+msgid "Past month"
+msgstr "Tamin'ny iray volana"
+
+#. TRANS: Filter entries modified during the last 356 days.
+#: ../src/jarabe/journal/journaltoolbox.py:136
+msgid "Past year"
+msgstr "Tamin'ny herin-taona"
+
+#: ../src/jarabe/journal/journaltoolbox.py:143
+msgid "Anyone"
+msgstr "Ny olona rehetra"
+
+#: ../src/jarabe/journal/journaltoolbox.py:145
+msgid "My friends"
+msgstr "Ny namako"
+
+#: ../src/jarabe/journal/journaltoolbox.py:146
+msgid "My class"
+msgstr "Ny kilasiko"
+
+#: ../src/jarabe/journal/journaltoolbox.py:274
+msgid "Anything"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:350
+#: ../src/jarabe/journal/palettes.py:90
+msgid "Copy"
+msgstr "Adikao"
+
+#. TRANS: Action label for starting an entry.
+#: ../src/jarabe/journal/journaltoolbox.py:431
+#: ../src/jarabe/journal/palettes.py:75
+msgid "Start"
+msgstr "Atomboy"
+
+#: ../src/jarabe/journal/listview.py:361
+msgid "Your Journal is empty"
+msgstr "Tsy misy n'inoninona ao anatin'ny firaiketanao"
+
+#: ../src/jarabe/journal/listview.py:363
+msgid "No matching entries"
+msgstr ""
+
+#: ../src/jarabe/journal/listview.py:374
+msgid "Clear search"
+msgstr ""
+
+#: ../src/jarabe/journal/modalalert.py:63
+msgid "Your Journal is full"
+msgstr "Feno ny firaiketanao"
+
+#: ../src/jarabe/journal/modalalert.py:67
+msgid "Please delete some old Journal entries to make space for new ones."
+msgstr ""
+
+#: ../src/jarabe/journal/modalalert.py:79
+msgid "Show Journal"
+msgstr "Asehoy ny firaiketana"
+
+#: ../src/jarabe/journal/objectchooser.py:146
+msgid "Choose an object"
+msgstr ""
+
+#: ../src/jarabe/journal/objectchooser.py:151
+#: ../src/jarabe/view/viewsource.py:308
+msgid "Close"
+msgstr "Akatony"
+
+#: ../src/jarabe/journal/palettes.py:73
+msgid "Resume with"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:76
+msgid "Start with"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:98
+msgid "Send to"
+msgstr "Alefaso any amin'i"
+
+#: ../src/jarabe/journal/palettes.py:107
+msgid "View Details"
+msgstr "Jereo ny antsipiriany"
+
+#: ../src/jarabe/journal/palettes.py:185
+msgid "No friends present"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:190
+msgid "No valid connection found"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:218
+msgid "No activity to resume entry"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:220
+msgid "No activity to start entry"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:62
+msgid "Remove friend"
+msgstr "Esory io namana io"
+
+#: ../src/jarabe/view/buddymenu.py:65
+msgid "Make friend"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:82
+msgid "Shutdown"
+msgstr "Pio, Vonoy"
+
+#: ../src/jarabe/view/buddymenu.py:90
+msgid "Logout"
+msgstr "Miala sehatra"
+
+#: ../src/jarabe/view/buddymenu.py:95
+msgid "My Settings"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:130
+#, python-format
+msgid "Invite to %s"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:45
+msgid "Starting..."
+msgstr ""
+
+#. TODO: share-with, keep
+#: ../src/jarabe/view/palettes.py:71
+msgid "View Source"
+msgstr "Asehoy ny sora-drindra"
+
+#: ../src/jarabe/view/palettes.py:82
+msgid "Stop"
+msgstr "Ajanony"
+
+#: ../src/jarabe/view/palettes.py:122
+msgid "Start new"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:171
+msgid "Show contents"
+msgstr "Asehoy ny atiny"
+
+#: ../src/jarabe/view/palettes.py:193 ../src/jarabe/view/palettes.py:243
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr "%(toerana_malalaka)d MB Malalaka"
+
+#: ../src/jarabe/view/palettes.py:218
+msgid "Unmount"
+msgstr "Esory"
+
+#: ../src/jarabe/view/viewsource.py:208
+msgid "Instance Source"
+msgstr ""
+
+#: ../src/jarabe/view/viewsource.py:233
+msgid "Source"
+msgstr ""
+
+#: ../src/jarabe/view/viewsource.py:292
+msgid "Activity Bundle Source"
+msgstr ""
+
+#: ../src/jarabe/view/viewsource.py:299
+#, python-format
+msgid "View source: %r"
+msgstr ""
diff --git a/shell/po/mi.po b/shell/po/mi.po
new file mode 100644
index 0000000..a45bff2
--- /dev/null
+++ b/shell/po/mi.po
@@ -0,0 +1,979 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-01-27 13:34-0500\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../extensions/cpsection/aboutme/__init__.py:24
+msgid "About Me"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:43
+msgid "You must enter a name."
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:68
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:71
+#, python-format
+msgid "stroke: %s"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:73
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:75
+#, python-format
+msgid "fill: %s"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:86
+msgid "Error in specified color modifiers."
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:89
+msgid "Error in specified colors."
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/view.py:94 ../src/jarabe/intro/window.py:92
+msgid "Name:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/view.py:128
+msgid "Click to change your color:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/__init__.py:21
+msgid "About my Computer"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/model.py:26
+msgid "Not available"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:59
+msgid "Identity"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:68
+msgid "Serial Number:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:90
+msgid "Software"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:99
+msgid "Build:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:114
+msgid "Sugar:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:130
+msgid "Firmware:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:145
+msgid "Wireless Firmware:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:168
+msgid "Copyright and License"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:176
+msgid ""
+"© 2008 One Laptop per Child Association Inc; Red Hat Inc; and Contributors."
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:183
+msgid ""
+"Sugar is the graphical user interface that you are looking at. Sugar is free "
+"software, covered by the GNU General Public License, and you are welcome to "
+"change it and/or distribute copies of it under certain conditions described "
+"therein."
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:195
+msgid "Full license:"
+msgstr ""
+
+#: ../extensions/cpsection/datetime/__init__.py:21
+msgid "Date & Time"
+msgstr ""
+
+#: ../extensions/cpsection/datetime/model.py:87
+msgid "Error timezone does not exist."
+msgstr ""
+
+#: ../extensions/cpsection/datetime/view.py:68 ../data/sugar.schemas.in.h:19
+msgid "Timezone"
+msgstr ""
+
+#: ../extensions/cpsection/frame/__init__.py:21
+msgid "Frame"
+msgstr ""
+
+#: ../extensions/cpsection/frame/model.py:38
+#: ../extensions/cpsection/frame/model.py:60
+msgid "Value must be an integer."
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:26
+msgid "never"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:27
+msgid "instantaneous"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:28
+#, python-format
+msgid "%s seconds"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:52
+msgid "Activation Delay"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:76
+msgid "Corner"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:111
+msgid "Edge"
+msgstr ""
+
+#: ../extensions/cpsection/language/__init__.py:21
+#: ../extensions/cpsection/language/view.py:32
+msgid "Language"
+msgstr ""
+
+#: ../extensions/cpsection/language/model.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr ""
+
+#: ../extensions/cpsection/language/model.py:124
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr ""
+
+#: ../extensions/cpsection/language/model.py:144
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr ""
+
+#: ../extensions/cpsection/network/__init__.py:21
+#: ../extensions/cpsection/network/view.py:28
+msgid "Network"
+msgstr ""
+
+#: ../extensions/cpsection/network/model.py:79
+msgid "State is unknown."
+msgstr ""
+
+#: ../extensions/cpsection/network/model.py:105
+msgid "Error in specified radio argument use on/off."
+msgstr ""
+
+#: ../extensions/cpsection/network/model.py:137
+msgid "Error in specified argument use 0/1."
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:56
+msgid "Wireless"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:64
+msgid "Turn off the wireless radio to save battery life"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:77
+msgid "Radio"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:93
+msgid "Discard network history if you have trouble connecting to the network"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:102
+msgid "Discard network history"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:115
+msgid "Collaboration"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:123
+msgid ""
+"The server is the equivalent of what room you are in; people on the same "
+"server will be able to see each other, even when they aren't on the same "
+"network."
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:133
+msgid "Server:"
+msgstr ""
+
+#: ../extensions/cpsection/power/__init__.py:21
+msgid "Power"
+msgstr ""
+
+#: ../extensions/cpsection/power/model.py:54
+msgid "Error in automatic pm argument, use on/off."
+msgstr ""
+
+#: ../extensions/cpsection/power/model.py:81
+msgid "Error in extreme pm argument, use on/off."
+msgstr ""
+
+#: ../extensions/cpsection/power/view.py:47
+msgid "Power management"
+msgstr ""
+
+#: ../extensions/cpsection/power/view.py:57
+msgid "Automatic power management (increases battery life)"
+msgstr ""
+
+#: ../extensions/cpsection/power/view.py:85
+msgid ""
+"Extreme power management (disableswireless radio, increases battery life)"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:56
+msgid "My Battery"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:137
+msgid "Removed"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:140
+msgid "Charging"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:143
+msgid "Very little power remaining"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:149
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:153
+msgid "Charged"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:40
+#, python-format
+msgid "IP address: %s"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:104
+msgid "Disconnect..."
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:109
+#: ../src/jarabe/desktop/meshbox.py:246
+msgid "Connecting..."
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:113
+#: ../extensions/deviceicon/network.py:166
+#: ../src/jarabe/desktop/meshbox.py:252
+msgid "Connected"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:126
+msgid "Channel"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:141
+msgid "Wired Network"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:169
+msgid "Speed"
+msgstr ""
+
+#: ../extensions/deviceicon/speaker.py:46
+msgid "My Speakers"
+msgstr ""
+
+#: ../extensions/deviceicon/speaker.py:128
+msgid "Unmute"
+msgstr ""
+
+#: ../extensions/deviceicon/speaker.py:131
+msgid "Mute"
+msgstr ""
+
+#: ../extensions/globalkey/screenshot.py:50
+msgid "Screenshot"
+msgstr ""
+
+#: ../extensions/globalkey/viewsource.py:196
+#, python-format
+msgid "View source: %r"
+msgstr ""
+
+#: ../extensions/globalkey/viewsource.py:205
+#: ../src/jarabe/frame/zoomtoolbar.py:42
+msgid "Activity"
+msgstr ""
+
+#: ../extensions/globalkey/viewsource.py:212
+msgid "Document"
+msgstr ""
+
+#: ../extensions/globalkey/viewsource.py:226
+#: ../src/jarabe/journal/objectchooser.py:141
+msgid "Close"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:1
+msgid "Backup URL"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:2
+msgid ""
+"Color for the XO icon that is used throughout the desktop. The string is "
+"composed of the stroke color and fill color, format is that of rbg colors. "
+"Example: #AC32FF,#9A5200"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:3
+msgid "Corner Delay"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:4
+msgid "Delay for the activation of the frame using the corners."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:5
+msgid "Delay for the activation of the frame using the edges."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:6
+msgid "Edge Delay"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:7
+msgid "Favorites Layout"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:8
+msgid "Favorites resume mode"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:9
+msgid ""
+"If TRUE, Sugar will make us searchable for the other users of the Jabber "
+"server."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:10
+msgid "Jabber Server"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:11
+msgid "Layout of the favorites view."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:12
+msgid "Power Automatic"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:13
+msgid "Power Automatic."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:14
+msgid "Power Extreme"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:15
+msgid "Power Extreme."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:16
+msgid "Publish to Gadget"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:17
+msgid "Setting for muting the sound device."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:18
+msgid "Sound Muted"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:20
+msgid "Timezone setting for the system."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:21
+msgid "Url of the jabber server to use."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:22
+msgid "Url where the backup is saved to."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:23
+msgid "User Color"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:24
+msgid "User Name"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:25
+msgid "User name that is used throughout the desktop."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:26
+msgid "Volume Level"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:27
+msgid "Volume level for the sound device."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:28
+msgid ""
+"When in resume mode, clicking on a favorite icon will cause the last entry "
+"for that activity to be resumed."
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:28
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:30
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:31
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr ""
+
+#. TRANS: Translators, there's a empty line at the end of this string,
+#. which must appear in the translated string (msgstr) as well.
+#: ../src/jarabe/controlpanel/cmd.py:37
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" -c key clear the current value for the key \n"
+" "
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:50
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:272
+msgid "Warning"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:273
+#: ../src/jarabe/controlpanel/sectionview.py:42
+msgid "Changes require restart"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:276
+msgid "Cancel changes"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:281 ../src/jarabe/desktop/homebox.py:113
+msgid "Later"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:285
+msgid "Restart now"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/toolbar.py:61 ../src/jarabe/intro/window.py:188
+msgid "Done"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/toolbar.py:115
+#: ../src/jarabe/desktop/homebox.py:111
+#: ../src/jarabe/frame/activitiestray.py:683
+#: ../src/jarabe/frame/activitiestray.py:762
+#: ../src/jarabe/frame/activitiestray.py:790
+msgid "Cancel"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/toolbar.py:121
+#: ../src/jarabe/desktop/favoritesview.py:334
+msgid "Ok"
+msgstr ""
+
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:114
+msgid "Freeform"
+msgstr ""
+
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:196
+msgid "Ring"
+msgstr ""
+
+#. TRANS: label for the spiral layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:341
+msgid "Spiral"
+msgstr ""
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:408
+msgid "Box"
+msgstr ""
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:449
+msgid "Triangle"
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:325
+msgid "Registration Failed"
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:326
+#, python-format
+msgid "%s"
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:328
+msgid "Registration Successful"
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:329
+msgid "You are now registered with your school server."
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:666
+msgid "Register"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:67
+msgid "Confirm erase"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:69
+#, python-format
+msgid "Confirm erase: Do you want to permanently erase %s?"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:73 ../src/jarabe/frame/clipboardmenu.py:62
+msgid "Keep"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:76
+#: ../src/jarabe/journal/journaltoolbox.py:357
+#: ../src/jarabe/journal/palettes.py:97 ../src/jarabe/view/palettes.py:127
+msgid "Erase"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:106
+msgid "Software Update"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:107
+msgid "Update your activities to ensure compatibility with your new software"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:116
+msgid "Check now"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:233
+msgid "List view"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:234
+msgid "<Ctrl>2"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:296
+msgid "Favorites view"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:297
+msgid "<Ctrl>1"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:304
+msgid "Resume by default"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:131
+msgid "Key Type:"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:151
+msgid "Authentication Type:"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:215
+msgid "WPA & WPA2 Personal"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:224
+msgid "Wireless Security:"
+msgstr ""
+
+#: ../src/jarabe/desktop/meshbox.py:130
+msgid "Connect"
+msgstr ""
+
+#: ../src/jarabe/desktop/meshbox.py:134
+msgid "Disconnect"
+msgstr ""
+
+#. TRANS: Action label for resuming an activity.
+#: ../src/jarabe/desktop/meshbox.py:440
+#: ../src/jarabe/frame/activitiestray.py:707
+#: ../src/jarabe/journal/journaltoolbox.py:425
+#: ../src/jarabe/journal/palettes.py:63 ../src/jarabe/view/palettes.py:62
+msgid "Resume"
+msgstr ""
+
+#: ../src/jarabe/desktop/meshbox.py:445
+#: ../src/jarabe/frame/activitiestray.py:221
+msgid "Join"
+msgstr ""
+
+#: ../src/jarabe/desktop/schoolserver.py:18
+msgid "Cannot obtain data needed for registration."
+msgstr ""
+
+#: ../src/jarabe/desktop/schoolserver.py:35
+msgid "Cannot connect to the server."
+msgstr ""
+
+#: ../src/jarabe/desktop/schoolserver.py:40
+msgid "The server could not complete the request."
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:226
+#: ../src/jarabe/frame/activitiestray.py:655
+msgid "Decline"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:608
+#, python-format
+msgid "%dB"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:610
+#, python-format
+msgid "%dKB"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:612
+#, python-format
+msgid "%dMB"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:629
+#, python-format
+msgid "%s of %s"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:640
+#, python-format
+msgid "Transfer from %r"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:650
+msgid "Accept"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:673
+#: ../src/jarabe/frame/activitiestray.py:780
+#, python-format
+msgid "%s (%s)"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:751
+#, python-format
+msgid "Transfer to %r"
+msgstr ""
+
+#: ../src/jarabe/frame/clipboardmenu.py:52
+msgid "Remove"
+msgstr ""
+
+#: ../src/jarabe/frame/clipboardmenu.py:57
+#: ../src/jarabe/frame/clipboardmenu.py:80
+msgid "Open"
+msgstr ""
+
+#: ../src/jarabe/frame/clipboardmenu.py:85
+msgid "Open with"
+msgstr ""
+
+#: ../src/jarabe/frame/clipboardobject.py:47
+#, python-format
+msgid "%s clipping"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:36
+msgid "Neighborhood"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:38
+msgid "Group"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:40
+msgid "Home"
+msgstr ""
+
+#: ../src/jarabe/intro/window.py:124
+msgid "Click to change color:"
+msgstr ""
+
+#: ../src/jarabe/intro/window.py:174 ../src/jarabe/journal/detailview.py:103
+msgid "Back"
+msgstr ""
+
+#: ../src/jarabe/intro/window.py:191
+msgid "Next"
+msgstr ""
+
+#: ../src/jarabe/journal/collapsedentry.py:243
+#: ../src/jarabe/journal/expandedentry.py:159
+#: ../src/jarabe/journal/palettes.py:57
+msgid "Untitled"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:205
+msgid "No preview"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:224
+msgid "Participants:"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:247
+msgid "Description:"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:273
+msgid "Tags:"
+msgstr ""
+
+#: ../src/jarabe/journal/journalactivity.py:108
+#: ../src/jarabe/journal/volumestoolbar.py:47
+msgid "Journal"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:65
+msgid "Search"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:124
+msgid "Anytime"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:126
+msgid "Today"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:128
+msgid "Since yesterday"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 7 days.
+#: ../src/jarabe/journal/journaltoolbox.py:130
+msgid "Past week"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 30 days.
+#: ../src/jarabe/journal/journaltoolbox.py:132
+msgid "Past month"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 356 days.
+#: ../src/jarabe/journal/journaltoolbox.py:134
+msgid "Past year"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:141
+msgid "Anyone"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:143
+msgid "My friends"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:144
+msgid "My class"
+msgstr ""
+
+#. TRANS: Item in a combo box that filters by entry type.
+#: ../src/jarabe/journal/journaltoolbox.py:271
+msgid "Anything"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:347
+#: ../src/jarabe/journal/palettes.py:81
+msgid "Copy"
+msgstr ""
+
+#. TRANS: Action label for starting an entry.
+#: ../src/jarabe/journal/journaltoolbox.py:428
+#: ../src/jarabe/journal/palettes.py:66 ../src/jarabe/view/palettes.py:111
+msgid "Start"
+msgstr ""
+
+#: ../src/jarabe/journal/listview.py:40
+msgid "Your Journal is empty"
+msgstr ""
+
+#: ../src/jarabe/journal/listview.py:41
+msgid "No matching entries "
+msgstr ""
+
+#: ../src/jarabe/journal/listview.py:369
+msgid "Clear search"
+msgstr ""
+
+#: ../src/jarabe/journal/misc.py:92
+msgid "No date"
+msgstr ""
+
+#: ../src/jarabe/journal/modalalert.py:63
+msgid "Your Journal is full"
+msgstr ""
+
+#: ../src/jarabe/journal/modalalert.py:67
+msgid "Please delete some old Journal entries to make space for new ones."
+msgstr ""
+
+#: ../src/jarabe/journal/modalalert.py:79
+msgid "Show Journal"
+msgstr ""
+
+#: ../src/jarabe/journal/objectchooser.py:136
+msgid "Choose an object"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:64
+msgid "Resume with"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:67
+msgid "Start with"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:89
+msgid "Send to"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:167
+msgid "No friends present"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:172
+msgid "No valid connection found"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:200
+msgid "No activity to resume entry"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:202
+msgid "No activity to start entry"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:61
+msgid "Remove friend"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:64
+msgid "Make friend"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:81
+msgid "My Settings"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:86
+msgid "Logout"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:91
+msgid "Restart"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:96
+msgid "Shutdown"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:131
+#, python-format
+msgid "Invite to %s"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:43
+msgid "Starting..."
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:73
+msgid "Stop"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:145
+msgid "Remove favorite"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:149
+msgid "Make favorite"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:201
+msgid "Show contents"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:223 ../src/jarabe/view/palettes.py:272
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:248
+msgid "Unmount"
+msgstr ""
diff --git a/shell/po/mk.po b/shell/po/mk.po
new file mode 100644
index 0000000..4f5a51f
--- /dev/null
+++ b/shell/po/mk.po
@@ -0,0 +1,432 @@
+# translation of olpc-sugar.master.po to Macedonian
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# Arangel Angov <arangel@linux.net.mk>, 2007.
+msgid ""
+msgstr ""
+"Project-Id-Version: olpc-sugar.master\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-01-18 18:19+0000\n"
+"PO-Revision-Date: 2007-08-08 15:40+0200\n"
+"Last-Translator: Arangel Angov <arangel@linux.net.mk>\n"
+"Language-Team: Macedonian <ossm-members@hedona.on.net.mk>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: KBabel 1.11.4\n"
+
+#: ../shell/intro/intro.py:67
+msgid "Name:"
+msgstr "Име:"
+
+#: ../shell/intro/intro.py:96
+msgid "Click to change color:"
+msgstr "Кликни да смениш боја:"
+
+#: ../shell/intro/intro.py:146
+msgid "Back"
+msgstr "Назад"
+
+#: ../shell/intro/intro.py:160
+msgid "Done"
+msgstr "Завршено"
+
+#: ../shell/intro/intro.py:163
+msgid "Next"
+msgstr "Напред"
+
+#: ../shell/view/BuddyMenu.py:59
+msgid "Remove friend"
+msgstr "Отстрани пријател"
+
+#: ../shell/view/BuddyMenu.py:62
+msgid "Make friend"
+msgstr "Додај пријател"
+
+#. FIXME check that the buddy is not in the activity already
+#: ../shell/view/BuddyMenu.py:96
+msgid "Invite"
+msgstr "Покани"
+
+#: ../shell/view/clipboardmenu.py:58
+msgid "Remove"
+msgstr "Отстрани"
+
+#: ../shell/view/clipboardmenu.py:63
+msgid "Open"
+msgstr "Отвори"
+
+#. self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+#. TODO: Implement stopping downloads
+#. self._stop_item.connect('activate', self._stop_item_activate_cb)
+#. self.append_menu_item(self._stop_item)
+#: ../shell/view/clipboardmenu.py:73
+msgid "Add to journal"
+msgstr "Додај во дневникот"
+
+#: ../shell/view/clipboardmenu.py:213
+#, python-format
+msgid "Clipboard object: %s."
+msgstr "Објект од таблата со исечоци: %s"
+
+#: ../shell/view/frame/zoomtoolbar.py:42
+msgid "Neighborhood"
+msgstr "Соседство"
+
+#: ../shell/view/frame/zoomtoolbar.py:54
+msgid "Group"
+msgstr "Група"
+
+#: ../shell/view/frame/zoomtoolbar.py:66
+msgid "Home"
+msgstr "Дома"
+
+#: ../shell/view/frame/zoomtoolbar.py:78
+msgid "Activity"
+msgstr "Активност"
+
+#: ../services/clipboard/objecttypeservice.py:32
+msgid "Text"
+msgstr "Текст"
+
+#: ../services/clipboard/objecttypeservice.py:36
+msgid "Image"
+msgstr "Слика"
+
+#: ../shell/view/Shell.py:285
+msgid "Screenshot"
+msgstr "Слика од екранот"
+
+#: ../shell/view/home/HomeBox.py:164
+msgid "Shutdown"
+msgstr "Исклучи"
+
+#: ../shell/view/home/MeshBox.py:195 ../shell/view/devices/network/mesh.py:37
+#: ../shell/view/devices/network/mesh.py:62
+#: ../shell/view/devices/network/mesh.py:66
+msgid "Mesh Network"
+msgstr "Соседство"
+
+#: ../shell/view/devices/battery.py:38
+msgid "My Battery life"
+msgstr "Мојата батерија"
+
+#: ../shell/view/devices/battery.py:94
+msgid "Battery charging"
+msgstr "Батеријата се полни"
+
+#: ../shell/view/devices/battery.py:96
+msgid "Battery discharging"
+msgstr "Батерјате се празни"
+
+#: ../shell/view/devices/battery.py:98
+msgid "Battery fully charged"
+msgstr "Батеријата е наполнета"
+
+#: ../lib/sugar/activity/activity.py:117
+msgid "Private"
+msgstr "Приватно"
+
+#: ../lib/sugar/activity/activity.py:118
+msgid "My Neighborhood"
+msgstr "Мое соседство"
+
+#: ../lib/sugar/activity/activity.py:126
+msgid "Keep"
+msgstr "Зачувај"
+
+#: ../shell/view/home/activitiesdonut.py:111
+#: ../lib/sugar/activity/activity.py:132
+msgid "Stop"
+msgstr "Стоп"
+
+#: ../lib/sugar/activity/activity.py:454
+#, python-format
+msgid "%s Activity"
+msgstr "%s активност"
+
+#: ../shell/view/BuddyMenu.py:84
+#, python-format
+msgid "Invite to %s"
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:149
+msgid "Key Type:"
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:169
+msgid "Authentication Type:"
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:250
+msgid "Encryption Type:"
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:90
+msgid "Starting..."
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:104 ../shell/view/home/MeshBox.py:295
+msgid "Resume"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:159
+msgid "Reboot"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:170
+msgid "Register"
+msgstr ""
+
+#. Only show disconnect when there's a mesh device, because mesh takes
+#. priority over the normal wireless device. NM doesn't have a "disconnect"
+#. method for a device either (for various reasons) so this doesn't
+#. have a good mapping
+#: ../shell/view/home/MeshBox.py:90 ../shell/view/home/MeshBox.py:197
+#: ../shell/view/devices/network/wireless.py:113
+#: ../shell/view/devices/network/mesh.py:83
+msgid "Disconnect..."
+msgstr ""
+
+#: ../shell/view/home/MeshBox.py:300
+msgid "Join"
+msgstr ""
+
+#: ../shell/view/devices/network/wireless.py:61
+msgid "Disconnected"
+msgstr ""
+
+#: ../shell/view/devices/network/wireless.py:131
+msgid "Channel"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:115
+msgid "Share with:"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:245
+msgid "Undo"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:250
+msgid "Redo"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:260
+msgid "Copy"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:265
+msgid "Paste"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:824
+msgid "Keep error"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:825
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:828
+msgid "Don't stop"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:831
+msgid "Stop anyway"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:164 ../lib/sugar/graphics/alert.py:206
+msgid "Cancel"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:168
+msgid "Ok"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:216
+msgid "Continue"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:244
+msgid "OK"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d year"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d years"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d month"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d months"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d week"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d weeks"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d day"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d days"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hour"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hours"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minute"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minutes"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d second"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d seconds"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:191
+msgid " and "
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:193
+msgid ", "
+msgstr ""
+
+#: ../shell/controlpanel/control.py:213
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:267
+msgid "Error in specified color modifiers."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:270
+msgid "Error in specified colors."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:307
+msgid "off"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:309
+msgid "on"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:310
+msgid "State is unknown."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:332
+msgid "Error in specified radio argument use on/off."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:336
+msgid "Permission denied. You need to be root to run this method."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:366
+msgid "Error in reading timezone"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:397
+#, python-format
+msgid "Error copying timezone (from %s): %s"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:402
+#, python-format
+msgid "Changing permission of timezone: %s"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:412
+msgid "Error timezone does not exist."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:417 ../shell/controlpanel/control.py:436
+#, python-format
+msgid "Could not access %s. Create standard settings."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:463
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:473
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:105
+msgid "Connected to a School Mesh Portal"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:107
+msgid "Looking for a School Mesh Portal..."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:110
+msgid "Connected to an XO Mesh Portal"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:112
+msgid "Looking for an XO Mesh Portal..."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:115
+msgid "Connected to a Simple Mesh"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:117
+msgid "Starting a Simple Mesh"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:124
+msgid "Unknown Mesh"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:175 ../shell/view/home/HomeBox.py:216
+msgid "About this XO"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:222
+msgid "Not available"
+msgstr ""
diff --git a/shell/po/ml.po b/shell/po/ml.po
new file mode 100644
index 0000000..a79e711
--- /dev/null
+++ b/shell/po/ml.po
@@ -0,0 +1,420 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-01-18 18:19+0000\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.0.1\n"
+
+#: ../shell/intro/intro.py:67
+msgid "Name:"
+msgstr ""
+
+#: ../shell/intro/intro.py:96
+msgid "Click to change color:"
+msgstr ""
+
+#: ../shell/intro/intro.py:146
+msgid "Back"
+msgstr ""
+
+#: ../shell/intro/intro.py:160
+msgid "Done"
+msgstr ""
+
+#: ../shell/intro/intro.py:163
+msgid "Next"
+msgstr ""
+
+#: ../shell/view/BuddyMenu.py:59
+msgid "Remove friend"
+msgstr ""
+
+#: ../shell/view/BuddyMenu.py:62
+msgid "Make friend"
+msgstr ""
+
+#: ../shell/view/BuddyMenu.py:84
+#, python-format
+msgid "Invite to %s"
+msgstr ""
+
+#: ../shell/view/clipboardmenu.py:58
+msgid "Remove"
+msgstr ""
+
+#: ../shell/view/clipboardmenu.py:63
+msgid "Open"
+msgstr ""
+
+#. self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+#. TODO: Implement stopping downloads
+#. self._stop_item.connect('activate', self._stop_item_activate_cb)
+#. self.append_menu_item(self._stop_item)
+#: ../shell/view/clipboardmenu.py:73
+msgid "Add to journal"
+msgstr ""
+
+#: ../shell/view/clipboardmenu.py:213
+#, python-format
+msgid "Clipboard object: %s."
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:149
+msgid "Key Type:"
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:169
+msgid "Authentication Type:"
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:250
+msgid "Encryption Type:"
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:90
+msgid "Starting..."
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:104 ../shell/view/home/MeshBox.py:295
+msgid "Resume"
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:111
+#: ../lib/sugar/activity/activity.py:132
+msgid "Stop"
+msgstr ""
+
+#: ../shell/view/Shell.py:285
+msgid "Screenshot"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:159
+msgid "Reboot"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:164
+msgid "Shutdown"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:170
+msgid "Register"
+msgstr ""
+
+#. Only show disconnect when there's a mesh device, because mesh takes
+#. priority over the normal wireless device. NM doesn't have a "disconnect"
+#. method for a device either (for various reasons) so this doesn't
+#. have a good mapping
+#: ../shell/view/home/MeshBox.py:90 ../shell/view/home/MeshBox.py:197
+#: ../shell/view/devices/network/wireless.py:113
+#: ../shell/view/devices/network/mesh.py:83
+msgid "Disconnect..."
+msgstr ""
+
+#: ../shell/view/home/MeshBox.py:195 ../shell/view/devices/network/mesh.py:37
+#: ../shell/view/devices/network/mesh.py:62
+#: ../shell/view/devices/network/mesh.py:66
+msgid "Mesh Network"
+msgstr ""
+
+#: ../shell/view/home/MeshBox.py:300
+msgid "Join"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:38
+msgid "My Battery life"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:94
+msgid "Battery charging"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:96
+msgid "Battery discharging"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:98
+msgid "Battery fully charged"
+msgstr ""
+
+#: ../shell/view/devices/network/wireless.py:61
+msgid "Disconnected"
+msgstr ""
+
+#: ../shell/view/devices/network/wireless.py:131
+msgid "Channel"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:42
+msgid "Neighborhood"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:54
+msgid "Group"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:66
+msgid "Home"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:78
+msgid "Activity"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:115
+msgid "Share with:"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:117
+msgid "Private"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:118
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:126
+msgid "Keep"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:245
+msgid "Undo"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:250
+msgid "Redo"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:260
+msgid "Copy"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:265
+msgid "Paste"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:454
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:824
+msgid "Keep error"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:825
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:828
+msgid "Don't stop"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:831
+msgid "Stop anyway"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:164 ../lib/sugar/graphics/alert.py:206
+msgid "Cancel"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:168
+msgid "Ok"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:216
+msgid "Continue"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:244
+msgid "OK"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d year"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d years"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d month"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d months"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d week"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d weeks"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d day"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d days"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hour"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hours"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minute"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minutes"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d second"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d seconds"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:191
+msgid " and "
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:193
+msgid ", "
+msgstr ""
+
+#: ../shell/controlpanel/control.py:213
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:267
+msgid "Error in specified color modifiers."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:270
+msgid "Error in specified colors."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:307
+msgid "off"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:309
+msgid "on"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:310
+msgid "State is unknown."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:332
+msgid "Error in specified radio argument use on/off."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:336
+msgid "Permission denied. You need to be root to run this method."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:366
+msgid "Error in reading timezone"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:397
+#, python-format
+msgid "Error copying timezone (from %s): %s"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:402
+#, python-format
+msgid "Changing permission of timezone: %s"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:412
+msgid "Error timezone does not exist."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:417 ../shell/controlpanel/control.py:436
+#, python-format
+msgid "Could not access %s. Create standard settings."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:463
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:473
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:105
+msgid "Connected to a School Mesh Portal"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:107
+msgid "Looking for a School Mesh Portal..."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:110
+msgid "Connected to an XO Mesh Portal"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:112
+msgid "Looking for an XO Mesh Portal..."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:115
+msgid "Connected to a Simple Mesh"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:117
+msgid "Starting a Simple Mesh"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:124
+msgid "Unknown Mesh"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:175 ../shell/view/home/HomeBox.py:216
+msgid "About this XO"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:222
+msgid "Not available"
+msgstr ""
diff --git a/shell/po/mn.po b/shell/po/mn.po
new file mode 100644
index 0000000..f48fbfb
--- /dev/null
+++ b/shell/po/mn.po
@@ -0,0 +1,1516 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-02-11 00:32-0500\n"
+"PO-Revision-Date: 2009-09-22 14:33-0400\n"
+"Last-Translator: Odontsetseg Bat-Erdene <obat-erdene@suffolk.edu>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: mn\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Pootle 1.2.1\n"
+
+#: ../extensions/cpsection/aboutme/__init__.py:24
+msgid "About Me"
+msgstr "Миний тухай"
+
+#: ../extensions/cpsection/aboutme/model.py:43
+msgid "You must enter a name."
+msgstr "Та нэр оруулах шаардлагатай."
+
+#: ../extensions/cpsection/aboutme/model.py:68
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr "зурагдах хэмжээ: өнгө=%s тодролт=%s"
+
+#: ../extensions/cpsection/aboutme/model.py:71
+#, python-format
+msgid "stroke: %s"
+msgstr "зурагдах хэмжээ: %s"
+
+#: ../extensions/cpsection/aboutme/model.py:73
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr "дүүргэх: өнгө=%s тодролт=%s"
+
+#: ../extensions/cpsection/aboutme/model.py:75
+#, python-format
+msgid "fill: %s"
+msgstr "дүүргэх: %s"
+
+#: ../extensions/cpsection/aboutme/model.py:86
+msgid "Error in specified color modifiers."
+msgstr "Сонгосон өнгийг солиход алдаа гарлаа."
+
+#: ../extensions/cpsection/aboutme/model.py:89
+msgid "Error in specified colors."
+msgstr "Сонгосон өнгөнд алдаа гарлаа."
+
+#: ../extensions/cpsection/aboutme/view.py:94 ../src/jarabe/intro/window.py:93
+msgid "Name:"
+msgstr "Нэр:"
+
+#: ../extensions/cpsection/aboutme/view.py:128
+msgid "Click to change your color:"
+msgstr "Товшиж өнгөө солино уу:"
+
+#: ../extensions/cpsection/aboutcomputer/__init__.py:21
+msgid "About my Computer"
+msgstr "Миний компьютерийн тухай"
+
+#: ../extensions/cpsection/aboutcomputer/model.py:28
+msgid "Not available"
+msgstr "Боломжгүй байна"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:60
+msgid "Identity"
+msgstr "Онцлох шинж"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:69
+msgid "Serial Number:"
+msgstr "Цувралын дугаар:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:91
+msgid "Software"
+msgstr "Програм хангамж"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:100
+msgid "Build:"
+msgstr "Хувилбар:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:115
+msgid "Sugar:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:131
+msgid "Firmware:"
+msgstr "Firmware:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:146
+msgid "Wireless Firmware:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:169
+msgid "Copyright and License"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:184
+msgid ""
+"Sugar is the graphical user interface that you are looking at. Sugar is free "
+"software, covered by the GNU General Public License, and you are welcome to "
+"change it and/or distribute copies of it under certain conditions described "
+"therein."
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:196
+msgid "Full license:"
+msgstr ""
+
+#: ../extensions/cpsection/datetime/__init__.py:21
+msgid "Date & Time"
+msgstr "Огноо ба Цаг"
+
+#: ../extensions/cpsection/datetime/model.py:87
+msgid "Error timezone does not exist."
+msgstr "Алдаа: цагийн бүс байхгүй байна."
+
+#: ../extensions/cpsection/datetime/view.py:68 ../data/sugar.schemas.in.h:33
+msgid "Timezone"
+msgstr "Цагийн бүс"
+
+#: ../extensions/cpsection/frame/__init__.py:21
+msgid "Frame"
+msgstr "Жааз"
+
+#: ../extensions/cpsection/frame/model.py:38
+#: ../extensions/cpsection/frame/model.py:60
+msgid "Value must be an integer."
+msgstr "Тоо хэмжээ нь бүхэл байх ёстой."
+
+#: ../extensions/cpsection/frame/view.py:26
+msgid "never"
+msgstr "хэзээ ч үгүй"
+
+#: ../extensions/cpsection/frame/view.py:27
+msgid "instantaneous"
+msgstr "эгшин зуур"
+
+#: ../extensions/cpsection/frame/view.py:28
+#, python-format
+msgid "%s seconds"
+msgstr "%s секунд"
+
+#: ../extensions/cpsection/frame/view.py:52
+msgid "Activation Delay"
+msgstr "Идэвхжилтийн хоцролт"
+
+#: ../extensions/cpsection/frame/view.py:76
+msgid "Corner"
+msgstr "Булан"
+
+#: ../extensions/cpsection/frame/view.py:111
+msgid "Edge"
+msgstr "Ирмэг"
+
+#: ../extensions/cpsection/keyboard/__init__.py:21
+#: ../extensions/cpsection/keyboard/view.py:31
+msgid "Keyboard"
+msgstr ""
+
+#: ../extensions/cpsection/keyboard/view.py:189
+msgid "Keyboard Model"
+msgstr ""
+
+#: ../extensions/cpsection/keyboard/view.py:248
+msgid "Key(s) to change layout"
+msgstr ""
+
+#: ../extensions/cpsection/keyboard/view.py:318
+msgid "Keyboard Layout(s)"
+msgstr ""
+
+#: ../extensions/cpsection/language/__init__.py:21
+#: ../extensions/cpsection/language/view.py:33
+msgid "Language"
+msgstr "Хэл"
+
+#: ../extensions/cpsection/language/model.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr "~/.i18n хандаж чадсангүй. Үндсэн тохиргоог үүсгэнэ үү."
+
+#: ../extensions/cpsection/language/model.py:124
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr "Кодны хэл=%s тодорхойлж чадсангүй."
+
+#: ../extensions/cpsection/language/model.py:144
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr "Уучлаарай би '%s' хэлээр ярьдаггүй."
+
+#: ../extensions/cpsection/language/view.py:56
+msgid ""
+"Add languages in the order you prefer. If a translation is not available, "
+"the next in the list will be used."
+msgstr ""
+
+#: ../extensions/cpsection/modemconfiguration/__init__.py:21
+msgid "Modem Configuration"
+msgstr ""
+
+#: ../extensions/cpsection/modemconfiguration/view.py:90
+msgid "Username:"
+msgstr ""
+
+#: ../extensions/cpsection/modemconfiguration/view.py:101
+msgid "Password:"
+msgstr ""
+
+#: ../extensions/cpsection/modemconfiguration/view.py:112
+msgid "Number:"
+msgstr ""
+
+#: ../extensions/cpsection/modemconfiguration/view.py:123
+msgid "APN:"
+msgstr ""
+
+#: ../extensions/cpsection/network/__init__.py:21
+#: ../extensions/cpsection/network/view.py:28
+msgid "Network"
+msgstr "Сүлжээ"
+
+#: ../extensions/cpsection/network/model.py:79
+msgid "State is unknown."
+msgstr "Төлөв тодорхойгүй."
+
+#: ../extensions/cpsection/network/model.py:105
+msgid "Error in specified radio argument use on/off."
+msgstr "Сонгосон тохиргооны төлөвд алдаа гарлаа."
+
+#: ../extensions/cpsection/network/model.py:137
+msgid "Error in specified argument use 0/1."
+msgstr "Сонгосон тохиргооны төлөвд алдаа гарлаа 0/1 aшиглах хэрэгтэй."
+
+#: ../extensions/cpsection/network/view.py:59
+msgid "Wireless"
+msgstr "Утасгүй холболт"
+
+#: ../extensions/cpsection/network/view.py:67
+msgid "Turn off the wireless radio to save battery life"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:80
+msgid "Radio"
+msgstr "Радио"
+
+#: ../extensions/cpsection/network/view.py:96
+msgid "Discard network history if you have trouble connecting to the network"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:105
+msgid "Discard network history"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:118
+msgid "Collaboration"
+msgstr "Хамтын ажиллагаа"
+
+#: ../extensions/cpsection/network/view.py:126
+msgid ""
+"The server is the equivalent of what room you are in; people on the same "
+"server will be able to see each other, even when they aren't on the same "
+"network."
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:136
+msgid "Server:"
+msgstr "Сервер:"
+
+#: ../extensions/cpsection/power/__init__.py:21
+msgid "Power"
+msgstr "Хүчдэл"
+
+#: ../extensions/cpsection/power/model.py:54
+msgid "Error in automatic pm argument, use on/off."
+msgstr "Автомат үүсгүүр зохицуулалтын төлөвд алдаа гарлаа."
+
+#: ../extensions/cpsection/power/model.py:81
+msgid "Error in extreme pm argument, use on/off."
+msgstr "Хэт үүсгүүр зохицуулалтын төлөвд алдаа гарлаа."
+
+#: ../extensions/cpsection/power/view.py:47
+msgid "Power management"
+msgstr "Хүчдэл зохицуулагч"
+
+#: ../extensions/cpsection/power/view.py:57
+msgid "Automatic power management (increases battery life)"
+msgstr "Хүчдэлийг автоматаар зохицуулагч (батерейг хэмнэх)"
+
+#: ../extensions/cpsection/power/view.py:85
+msgid ""
+"Extreme power management (disableswireless radio, increases battery life)"
+msgstr ""
+"Хүчдэлийг дээд зэргээр хэмнэх (радио долгионыг хааснаар батерейг хэмнэх)"
+
+#: ../extensions/cpsection/updater/__init__.py:21
+msgid "Software update"
+msgstr "Хангамж шинэчлэх"
+
+#: ../extensions/cpsection/updater/view.py:63
+msgid ""
+"Software updates correct errors, eliminate security vulnerabilities, and "
+"provide new features."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:125
+#, python-format
+msgid "Checking %s..."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:127
+#, python-format
+msgid "Downloading %s..."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:129
+#, python-format
+msgid "Updating %s..."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:139
+msgid "Your software is up-to-date"
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:141
+#, python-format
+msgid "You can install %s update"
+msgid_plural "You can install %s updates"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../extensions/cpsection/updater/view.py:159
+msgid "Checking for updates..."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:164
+msgid "Installing updates..."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:172
+#, python-format
+msgid "%s update was installed"
+msgid_plural "%s updates were installed"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../extensions/cpsection/updater/view.py:253
+msgid "Install selected"
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:274
+#, python-format
+msgid "Download size: %s"
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:362
+#, python-format
+msgid "From version %(current)d to %(new)s (Size: %(size)s)"
+msgstr ""
+
+#. TRANS: download size is 0
+#: ../extensions/cpsection/updater/view.py:382
+msgid "None"
+msgstr "Огт үгүй"
+
+#. TRANS: download size of very small updates
+#: ../extensions/cpsection/updater/view.py:385
+msgid "1 KB"
+msgstr ""
+
+#. TRANS: download size of small updates, e.g. '250 KB'
+#: ../extensions/cpsection/updater/view.py:388
+#, python-format
+msgid "%.0f KB"
+msgstr ""
+
+#. TRANS: download size of updates, e.g. '2.3 MB'
+#: ../extensions/cpsection/updater/view.py:391
+#, python-format
+msgid "%.1f MB"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:58
+msgid "My Battery"
+msgstr "Миний батарей"
+
+#: ../extensions/deviceicon/battery.py:137
+msgid "Removed"
+msgstr "Устгасан"
+
+#: ../extensions/deviceicon/battery.py:140
+msgid "Charging"
+msgstr "Цэнэглэгдэж байна"
+
+#: ../extensions/deviceicon/battery.py:143
+msgid "Very little power remaining"
+msgstr "Цэнэг маш бага байна"
+
+#: ../extensions/deviceicon/battery.py:149
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr "%(hour)d:%(min).2d үлдлээ"
+
+#: ../extensions/deviceicon/battery.py:152
+msgid "Charged"
+msgstr "Цэнэглэгдсэн"
+
+#: ../extensions/deviceicon/network.py:49
+#, python-format
+msgid "IP address: %s"
+msgstr "IP хаяг: %s"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#: ../extensions/deviceicon/network.py:111
+msgid "Disconnect..."
+msgstr "Салгах..."
+
+#: ../extensions/deviceicon/network.py:116
+msgid "Create new wireless network"
+msgstr ""
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#: ../extensions/deviceicon/network.py:122
+#: ../extensions/deviceicon/network.py:284
+#: ../src/jarabe/desktop/meshbox.py:248 ../src/jarabe/desktop/meshbox.py:537
+msgid "Connecting..."
+msgstr "Холбогдож байна..."
+
+# TODO: show the channel number
+#: ../extensions/deviceicon/network.py:126
+#: ../extensions/deviceicon/network.py:198
+#: ../extensions/deviceicon/network.py:288
+#: ../src/jarabe/desktop/meshbox.py:254 ../src/jarabe/desktop/meshbox.py:543
+msgid "Connected"
+msgstr "Холбогдсон"
+
+#: ../extensions/deviceicon/network.py:158
+msgid "Channel"
+msgstr "Суваг"
+
+#: ../extensions/deviceicon/network.py:173
+msgid "Wired Network"
+msgstr "Утастай Сүлжээ"
+
+#: ../extensions/deviceicon/network.py:201
+msgid "Speed"
+msgstr "Хурд"
+
+#: ../extensions/deviceicon/network.py:228
+msgid "Wireless modem"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:276
+msgid "Please wait..."
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:279
+#: ../src/jarabe/desktop/meshbox.py:164 ../src/jarabe/desktop/meshbox.py:494
+msgid "Connect"
+msgstr "Холбох"
+
+#: ../extensions/deviceicon/network.py:280
+msgid "Disconnected"
+msgstr "Салгагдсан"
+
+#: ../extensions/deviceicon/network.py:283
+#: ../src/jarabe/controlpanel/toolbar.py:115
+#: ../src/jarabe/desktop/homebox.py:68
+#: ../src/jarabe/frame/activitiestray.py:700
+#: ../src/jarabe/frame/activitiestray.py:799
+#: ../src/jarabe/frame/activitiestray.py:827
+msgid "Cancel"
+msgstr "Болих"
+
+#: ../extensions/deviceicon/network.py:287
+#: ../src/jarabe/desktop/meshbox.py:168
+msgid "Disconnect"
+msgstr "Салгах"
+
+#: ../extensions/deviceicon/network.py:530
+#, python-format
+msgid "%s's network"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:597
+#: ../extensions/deviceicon/network.py:656
+msgid "Mesh Network"
+msgstr "Тархалтын сүлжээ"
+
+#: ../extensions/deviceicon/network.py:857
+#, python-format
+msgid "Data sent %d kb / received %d kb"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:868
+msgid "Connection time "
+msgstr ""
+
+#: ../extensions/deviceicon/speaker.py:59
+msgid "My Speakers"
+msgstr "Миний чанга яригч"
+
+#: ../extensions/deviceicon/speaker.py:133
+msgid "Unmute"
+msgstr "Дуутай болгох"
+
+#: ../extensions/deviceicon/speaker.py:136
+msgid "Mute"
+msgstr "Дуугүй болгох"
+
+#: ../extensions/globalkey/screenshot.py:59
+msgid "Mesh"
+msgstr "Тархалт"
+
+#: ../extensions/globalkey/screenshot.py:61
+#: ../src/jarabe/frame/zoomtoolbar.py:39
+msgid "Group"
+msgstr "Бүлэг"
+
+#: ../extensions/globalkey/screenshot.py:63
+#: ../src/jarabe/frame/zoomtoolbar.py:41
+msgid "Home"
+msgstr "Нүүр хуудас"
+
+#: ../extensions/globalkey/screenshot.py:69
+#: ../src/jarabe/frame/zoomtoolbar.py:43
+msgid "Activity"
+msgstr "Үйл ажиллагаа"
+
+#: ../extensions/globalkey/screenshot.py:72
+msgid "Screenshot"
+msgstr "Дэлгэцний зураг"
+
+#: ../extensions/globalkey/screenshot.py:74
+#, python-format
+msgid "Screenshot of \"%s\""
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:1
+msgid ""
+"\"disabled\" to ask nick on initialization; \"system\" to reuse UNIX account "
+"long name."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:2
+msgid "Backup URL"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:3
+msgid ""
+"Color for the XO icon that is used throughout the desktop. The string is "
+"composed of the stroke color and fill color, format is that of rbg colors. "
+"Example: #AC32FF,#9A5200"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:4
+msgid "Corner Delay"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:5
+msgid "Default font face"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:6
+msgid "Default font size"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:7
+msgid "Default nick"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:8
+msgid "Delay for the activation of the frame using the corners."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:9
+msgid "Delay for the activation of the frame using the edges."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:10
+msgid "Edge Delay"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:11
+msgid "Favorites Layout"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:12
+msgid "Favorites resume mode"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:13
+#, fuzzy
+msgid "Font face that is used throughout the desktop."
+msgstr "Хэрэглэгчийн нэр олон байдалд ашиглаж байдаг."
+
+#: ../data/sugar.schemas.in.h:14
+#, fuzzy
+msgid "Font size that is used throughout the desktop."
+msgstr "Хэрэглэгчийн нэр олон байдалд ашиглаж байдаг."
+
+#: ../data/sugar.schemas.in.h:15
+msgid ""
+"If TRUE, Sugar will make us searchable for the other users of the Jabber "
+"server."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:16
+msgid "If TRUE, Sugar will show a \"Log out\" option."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:17
+msgid "Jabber Server"
+msgstr "Жаббер Сервер"
+
+#: ../data/sugar.schemas.in.h:18
+msgid "Keyboard layouts"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:19
+msgid "Keyboard model"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:20
+msgid "Keyboard options"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:21
+msgid "Layout of the favorites view."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:22
+msgid ""
+"List of keyboard layouts. Each entry should be in the form layout(variant)"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:23
+msgid "List of keyboard options."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:24
+msgid "Power Automatic"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:25
+msgid "Power Automatic."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:26
+msgid "Power Extreme"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:27
+msgid "Power Extreme."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:28
+msgid "Publish to Gadget"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:29
+msgid "Setting for muting the sound device."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:30
+msgid "Show Log out"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:31
+msgid "Sound Muted"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:32
+msgid "The keyboard model to be used"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:34
+msgid "Timezone setting for the system."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:35
+msgid "Url of the jabber server to use."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:36
+msgid "Url where the backup is saved to."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:37
+msgid "User Color"
+msgstr "Хэрэглэгчийн Өнгө"
+
+#: ../data/sugar.schemas.in.h:38
+msgid "User Name"
+msgstr "Хэрэглэгчийн Нэр"
+
+#: ../data/sugar.schemas.in.h:39
+msgid "User name that is used throughout the desktop."
+msgstr "Хэрэглэгчийн нэр олон байдалд ашиглаж байдаг."
+
+#: ../data/sugar.schemas.in.h:40
+msgid "Volume Level"
+msgstr "Дууны Хэмжээ"
+
+#: ../data/sugar.schemas.in.h:41
+msgid "Volume level for the sound device."
+msgstr "Дуу авианы тоноглолын дууны жэмжээ."
+
+#: ../data/sugar.schemas.in.h:42
+msgid ""
+"When in resume mode, clicking on a favorite icon will cause the last entry "
+"for that activity to be resumed."
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:28
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr "sugar-удирдах-самбар: АНХААР, нэр давхцаж байна: %s модуль: %r"
+
+#: ../src/jarabe/controlpanel/cmd.py:30
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr "sugar-удирдах-самбар: товч=%s сонгох боломжгүй"
+
+#: ../src/jarabe/controlpanel/cmd.py:31
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr "sugar-удирдах-самбар: %s"
+
+# TRANS: Translators, there's a empty line at the end of this string,&amp;amp;amp;amp;amp;amp;amp;lt;br /&amp;amp;amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;amp;lt;br /&amp;amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;lt;br /&amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;lt;br /&amp;amp;amp;amp;gt;&amp;amp;amp;lt;br /&amp;amp;amp;gt;&amp;amp;lt;br /&amp;amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;<br />
+# which must appear in the translated string (msgstr) as well.
+#. TRANS: Translators, there's a empty line at the end of this string,
+#. which must appear in the translated string (msgstr) as well.
+#: ../src/jarabe/controlpanel/cmd.py:37
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" -c key clear the current value for the key \n"
+" "
+msgstr ""
+"Хэрэглээ: sugar-удирдах-самбар [ сонголт ] товч [ аргумент ... ] \n"
+" Sugar-ийн орчны хяналт. \n"
+" Сонголтууд: \n"
+" -х Энэ тусламжын мэдээ үзүүлэх ба гаргах \n"
+" -л Боломжит сонголтын үзүүлэлтүүд \n"
+" -х товч Товчны тухай мэдээлэл \n"
+" -а товч Товчны одоогийн ач холбогдлыг мэдэх \n"
+" -ы товч Товчны одоогийн ач холбогдлыг тохируулах \n"
+" -с товч Товчны одоогийн ач холбогдлыг арилгах \n"
+" "
+
+#: ../src/jarabe/controlpanel/cmd.py:50
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr "Өөрчлөлтийг идэвхжүүлэхийн тулд та дахин эхлүүлэх шаардлагатай.\n"
+
+#: ../src/jarabe/controlpanel/gui.py:281
+msgid "Warning"
+msgstr "Анхаар"
+
+#: ../src/jarabe/controlpanel/gui.py:282
+#: ../src/jarabe/controlpanel/sectionview.py:42
+msgid "Changes require restart"
+msgstr "Өөрчлөхийн тулд дахин эхлүүлэх хэрэгтэй"
+
+#: ../src/jarabe/controlpanel/gui.py:285
+msgid "Cancel changes"
+msgstr "Өөрчлөлтийг цуцлах"
+
+#: ../src/jarabe/controlpanel/gui.py:290 ../src/jarabe/desktop/homebox.py:70
+msgid "Later"
+msgstr "Дараа"
+
+#: ../src/jarabe/controlpanel/gui.py:294
+msgid "Restart now"
+msgstr "Одоо эхлүүлэх үү"
+
+#: ../src/jarabe/controlpanel/toolbar.py:61 ../src/jarabe/intro/window.py:206
+msgid "Done"
+msgstr "Боллоо"
+
+#: ../src/jarabe/controlpanel/toolbar.py:121
+#: ../src/jarabe/desktop/favoritesview.py:333
+msgid "Ok"
+msgstr "Тийм"
+
+#: ../src/jarabe/desktop/activitieslist.py:236
+#, python-format
+msgid "Version %s"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:357
+msgid "Confirm erase"
+msgstr "Арилгахыг зөвшөөрөх"
+
+#: ../src/jarabe/desktop/activitieslist.py:359
+#, python-format
+msgid "Confirm erase: Do you want to permanently erase %s?"
+msgstr "Арилгахыг зөвшөөрөх: Та %s бүр мөсөн арилгахыг хүсч байна уу?"
+
+# self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+# TODO: Implement stopping downloads
+# self._stop_item.connect('activate', self._stop_item_activate_cb)
+# self.append_menu_item(self._stop_item)
+#: ../src/jarabe/desktop/activitieslist.py:363
+#: ../src/jarabe/frame/clipboardmenu.py:63
+#: ../src/jarabe/view/viewsource.py:218
+msgid "Keep"
+msgstr "Хадгалах"
+
+#: ../src/jarabe/desktop/activitieslist.py:366
+#: ../src/jarabe/desktop/activitieslist.py:409
+#: ../src/jarabe/journal/journaltoolbox.py:360
+#: ../src/jarabe/journal/palettes.py:105
+msgid "Erase"
+msgstr "Арилгах"
+
+#: ../src/jarabe/desktop/activitieslist.py:430
+msgid "Remove favorite"
+msgstr "Дуртай зүйлийг устгах"
+
+#: ../src/jarabe/desktop/activitieslist.py:434
+msgid "Make favorite"
+msgstr "Дуртай зүйл болгох"
+
+# TRANS: label for the freeform layout in the favorites view
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:116
+msgid "Freeform"
+msgstr "Дурын хэлбэр"
+
+# TRANS: label for the ring layout in the favorites view
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:198
+msgid "Ring"
+msgstr "Цагираг"
+
+# TRANS: label for the spiral layout in the favorites view
+#. TRANS: label for the spiral layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:337
+msgid "Spiral"
+msgstr "Спираль"
+
+# TRANS: label for the box layout in the favorites view
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:404
+msgid "Box"
+msgstr "Хайрцаг"
+
+# TRANS: label for the box layout in the favorites view
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:445
+msgid "Triangle"
+msgstr "Гурвалжин"
+
+#: ../src/jarabe/desktop/favoritesview.py:324
+msgid "Registration Failed"
+msgstr "Бүртгэл бүтэлгүйтлээ"
+
+#: ../src/jarabe/desktop/favoritesview.py:325
+#, python-format
+msgid "%s"
+msgstr "%s"
+
+#: ../src/jarabe/desktop/favoritesview.py:327
+msgid "Registration Successful"
+msgstr "Бүртгэл амжилттай"
+
+#: ../src/jarabe/desktop/favoritesview.py:328
+msgid "You are now registered with your school server."
+msgstr "Та одоо сургуулийн серверт бүртгэгдлээ."
+
+#: ../src/jarabe/desktop/favoritesview.py:630
+msgid "Register"
+msgstr "Бүртгүүлэх"
+
+#: ../src/jarabe/desktop/homebox.py:63
+msgid "Software Update"
+msgstr "Хангамж Шинэчлэх"
+
+#: ../src/jarabe/desktop/homebox.py:64
+msgid "Update your activities to ensure compatibility with your new software"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:73
+msgid "Check now"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:192
+msgid "List view"
+msgstr "Жагсаалтыг харах"
+
+#: ../src/jarabe/desktop/homebox.py:193
+msgid "<Ctrl>2"
+msgstr "<Ctrl>2"
+
+#: ../src/jarabe/desktop/homebox.py:255
+msgid "Favorites view"
+msgstr "Дуртай зүйлсийг үзэх"
+
+#: ../src/jarabe/desktop/homebox.py:256
+msgid "<Ctrl>1"
+msgstr "<Ctrl>1"
+
+#: ../src/jarabe/desktop/keydialog.py:135
+msgid "Key Type:"
+msgstr "Үндсэн төрөл:"
+
+#: ../src/jarabe/desktop/keydialog.py:155
+msgid "Authentication Type:"
+msgstr "Нэвтрэх нууцлалын төрөл:"
+
+#: ../src/jarabe/desktop/keydialog.py:220
+msgid "WPA & WPA2 Personal"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:229
+msgid "Wireless Security:"
+msgstr ""
+
+#: ../src/jarabe/desktop/meshbox.py:492
+#, python-format
+#, fuzzy
+msgid "Mesh Network %d"
+msgstr "Тархалтын сүлжээ"
+
+# TRANS: Action label for resuming an activity.
+#. TRANS: Action label for resuming an activity.
+#: ../src/jarabe/desktop/meshbox.py:629
+#: ../src/jarabe/frame/activitiestray.py:735
+#: ../src/jarabe/journal/journaltoolbox.py:428
+#: ../src/jarabe/journal/palettes.py:65 ../src/jarabe/view/palettes.py:67
+msgid "Resume"
+msgstr "Эргэж орох"
+
+#: ../src/jarabe/desktop/meshbox.py:634
+#: ../src/jarabe/frame/activitiestray.py:233
+msgid "Join"
+msgstr "Нэгдэх"
+
+#: ../src/jarabe/desktop/schoolserver.py:103
+msgid "Cannot connect to the server."
+msgstr ""
+
+#: ../src/jarabe/desktop/schoolserver.py:108
+msgid "The server could not complete the request."
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:238
+#: ../src/jarabe/frame/activitiestray.py:672
+msgid "Decline"
+msgstr "Үл зөвшөөрөх"
+
+#: ../src/jarabe/frame/activitiestray.py:624
+#, python-format
+msgid "%dB"
+msgstr "%dB"
+
+#: ../src/jarabe/frame/activitiestray.py:626
+#, python-format
+msgid "%dKB"
+msgstr "%dKB"
+
+#: ../src/jarabe/frame/activitiestray.py:628
+#, python-format
+msgid "%dMB"
+msgstr "%dMB"
+
+#: ../src/jarabe/frame/activitiestray.py:645
+#, python-format
+msgid "%s of %s"
+msgstr "%s-ны %s"
+
+#: ../src/jarabe/frame/activitiestray.py:657
+#, python-format
+msgid "Transfer from %r"
+msgstr "%r-аас дамжуулах"
+
+#: ../src/jarabe/frame/activitiestray.py:667
+msgid "Accept"
+msgstr "Зөвшөөрөх"
+
+#: ../src/jarabe/frame/activitiestray.py:690
+#: ../src/jarabe/frame/activitiestray.py:817
+#, python-format
+msgid "%s (%s)"
+msgstr "%s (%s)"
+
+#: ../src/jarabe/frame/activitiestray.py:724
+#: ../src/jarabe/frame/activitiestray.py:852
+msgid "Dismiss"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:787
+#, python-format
+msgid "Transfer to %r"
+msgstr "%r-д дамжуулах"
+
+#: ../src/jarabe/frame/clipboardmenu.py:53 ../src/jarabe/view/palettes.py:221
+msgid "Remove"
+msgstr "Устгах"
+
+#: ../src/jarabe/frame/clipboardmenu.py:58
+#: ../src/jarabe/frame/clipboardmenu.py:81
+msgid "Open"
+msgstr "Нээх"
+
+#: ../src/jarabe/frame/clipboardmenu.py:86
+msgid "Open with"
+msgstr "Сонгож нээх"
+
+#: ../src/jarabe/frame/clipboardobject.py:49
+#, python-format
+msgid "%s clipping"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:37
+msgid "Neighborhood"
+msgstr "Хөршүүд"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:37
+msgid "F1"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:39
+msgid "F2"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:41
+msgid "F3"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:43
+msgid "F4"
+msgstr ""
+
+#: ../src/jarabe/intro/window.py:128
+msgid "Click to change color:"
+msgstr "Энд дарж өнгөө солино уу:"
+
+#: ../src/jarabe/intro/window.py:192 ../src/jarabe/journal/detailview.py:103
+msgid "Back"
+msgstr "Буцах"
+
+#: ../src/jarabe/intro/window.py:209
+msgid "Next"
+msgstr "Дараах"
+
+#: ../src/jarabe/journal/expandedentry.py:152
+#: ../src/jarabe/journal/palettes.py:59
+msgid "Untitled"
+msgstr "Гарчиггүй"
+
+#: ../src/jarabe/journal/expandedentry.py:243
+msgid "No preview"
+msgstr "Урьдчилан харах боломжгүй"
+
+#: ../src/jarabe/journal/expandedentry.py:262
+#, python-format
+msgid "Kind: %s"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:262
+msgid "Unknown"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:263
+#, python-format
+msgid "Date: %s"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:264
+#, python-format
+msgid "Size: %s"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:286 ../src/jarabe/journal/misc.py:93
+msgid "No date"
+msgstr "Огноо байхгүй"
+
+#: ../src/jarabe/journal/expandedentry.py:293
+msgid "Participants:"
+msgstr "Гүйцэтгэгчид:"
+
+#: ../src/jarabe/journal/expandedentry.py:316
+msgid "Description:"
+msgstr "Тайлбар:"
+
+#: ../src/jarabe/journal/expandedentry.py:341
+msgid "Tags:"
+msgstr "Шошго:"
+
+#: ../src/jarabe/journal/journalactivity.py:108
+#: ../src/jarabe/journal/volumestoolbar.py:47
+msgid "Journal"
+msgstr "Журнал"
+
+#: ../src/jarabe/journal/journaltoolbox.py:67
+msgid "Search"
+msgstr "Хайлт"
+
+#: ../src/jarabe/journal/journaltoolbox.py:126
+msgid "Anytime"
+msgstr "Ямар ч үед"
+
+#: ../src/jarabe/journal/journaltoolbox.py:128
+msgid "Today"
+msgstr "Өнөөдөр"
+
+#: ../src/jarabe/journal/journaltoolbox.py:130
+msgid "Since yesterday"
+msgstr "Өчигдрөөс хойших"
+
+# TRANS: Filter entries modified during the last 7 days.
+#. TRANS: Filter entries modified during the last 7 days.
+#: ../src/jarabe/journal/journaltoolbox.py:132
+msgid "Past week"
+msgstr "Өнгөрсөн долоо хоногт"
+
+# TRANS: Filter entries modified during the last 30 days.
+#. TRANS: Filter entries modified during the last 30 days.
+#: ../src/jarabe/journal/journaltoolbox.py:134
+msgid "Past month"
+msgstr "Өнгөрсөн сард"
+
+# TRANS: Filter entries modified during the last 356 days.
+#. TRANS: Filter entries modified during the last 356 days.
+#: ../src/jarabe/journal/journaltoolbox.py:136
+msgid "Past year"
+msgstr "Өнгөрсөн жил"
+
+#: ../src/jarabe/journal/journaltoolbox.py:143
+msgid "Anyone"
+msgstr "Хэн ч гэсэн"
+
+#: ../src/jarabe/journal/journaltoolbox.py:145
+msgid "My friends"
+msgstr "Миний найзууд"
+
+#: ../src/jarabe/journal/journaltoolbox.py:146
+msgid "My class"
+msgstr "Манай анги"
+
+# TRANS: Item in a combo box that filters by entry type.
+#: ../src/jarabe/journal/journaltoolbox.py:274
+msgid "Anything"
+msgstr "Бүх юм"
+
+# TODO: Add "Start with" menu item
+#: ../src/jarabe/journal/journaltoolbox.py:350
+#: ../src/jarabe/journal/palettes.py:83
+msgid "Copy"
+msgstr "Хуулах"
+
+# TRANS: Action label for starting an entry.
+#. TRANS: Action label for starting an entry.
+#: ../src/jarabe/journal/journaltoolbox.py:431
+#: ../src/jarabe/journal/palettes.py:68
+msgid "Start"
+msgstr "Эхлэх"
+
+#: ../src/jarabe/journal/listview.py:373
+msgid "Your Journal is empty"
+msgstr "Таны Журнал хоосон байна"
+
+#: ../src/jarabe/journal/listview.py:375
+msgid "No matching entries"
+msgstr "Тохирсон оролт алга"
+
+#: ../src/jarabe/journal/listview.py:386
+msgid "Clear search"
+msgstr "Хайлт арилгах"
+
+#: ../src/jarabe/journal/modalalert.py:63
+msgid "Your Journal is full"
+msgstr "Таны Журнал дүүрэн байна"
+
+#: ../src/jarabe/journal/modalalert.py:67
+msgid "Please delete some old Journal entries to make space for new ones."
+msgstr ""
+
+#: ../src/jarabe/journal/modalalert.py:79
+msgid "Show Journal"
+msgstr "Журнал Үзүүлэх"
+
+#: ../src/jarabe/journal/objectchooser.py:146
+msgid "Choose an object"
+msgstr "Обьектыг сонгох"
+
+#: ../src/jarabe/journal/objectchooser.py:151
+#: ../src/jarabe/view/viewsource.py:308
+msgid "Close"
+msgstr "Хаах"
+
+#: ../src/jarabe/journal/palettes.py:66
+msgid "Resume with"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:69
+msgid "Start with"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:91
+msgid "Send to"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:100
+msgid "View Details"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:178
+msgid "No friends present"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:183
+msgid "No valid connection found"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:211
+msgid "No activity to resume entry"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:213
+msgid "No activity to start entry"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:62
+msgid "Remove friend"
+msgstr "Найзыг хасах"
+
+#: ../src/jarabe/view/buddymenu.py:65
+msgid "Make friend"
+msgstr "Найз нэмэх"
+
+#: ../src/jarabe/view/buddymenu.py:82
+msgid "Shutdown"
+msgstr "Унтраах"
+
+#: ../src/jarabe/view/buddymenu.py:90
+msgid "Logout"
+msgstr "Гарах"
+
+#: ../src/jarabe/view/buddymenu.py:95
+msgid "My Settings"
+msgstr "Миний Тохиргоо"
+
+#: ../src/jarabe/view/buddymenu.py:130
+#, python-format
+msgid "Invite to %s"
+msgstr "%s-д урих"
+
+#: ../src/jarabe/view/palettes.py:45
+msgid "Starting..."
+msgstr "Эхэлж байна..."
+
+#. TODO: share-with, keep
+#: ../src/jarabe/view/palettes.py:74
+msgid "View Source"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:85
+msgid "Stop"
+msgstr "Зогсоох"
+
+#: ../src/jarabe/view/palettes.py:125
+msgid "Start new"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:174
+msgid "Show contents"
+msgstr "Агуулгийг үзэх"
+
+#: ../src/jarabe/view/palettes.py:196 ../src/jarabe/view/palettes.py:246
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr "%(free_space)d MB ашиглаагүй"
+
+#: ../src/jarabe/view/viewsource.py:208
+msgid "Instance Source"
+msgstr ""
+
+#: ../src/jarabe/view/viewsource.py:233
+msgid "Source"
+msgstr ""
+
+#: ../src/jarabe/view/viewsource.py:292
+msgid "Activity Bundle Source"
+msgstr ""
+
+#: ../src/jarabe/view/viewsource.py:299
+#, python-format
+msgid "View source: %r"
+msgstr ""
+
+#~ msgid "Document"
+#~ msgstr "Бичиг баримт"
+
+#~ msgid "No matching entries "
+#~ msgstr "Тохирсон оролт алга "
+
+#~ msgid "Restart"
+#~ msgstr "Дахин эхлүүлэх"
+
+#~ msgid "Unmount"
+#~ msgstr "Салгах"
+
+#~ msgid "Encryption Type:"
+#~ msgstr "Нууцлалын төрөл:"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#~ msgid "Disconnecting..."
+#~ msgstr "Салгагдаж байна..."
+
+#~ msgid "About my XO"
+#~ msgstr "Миний ХО-гын тухай"
+
+#~ msgid "Connected to a School Mesh Portal"
+#~ msgstr "Сургуулийн тархалтын порталд холбогдлоо."
+
+#~ msgid "Looking for a School Mesh Portal..."
+#~ msgstr "Сургуулийн тархалтын порталыг хайж байна ..."
+
+#~ msgid "Connected to an XO Mesh Portal"
+#~ msgstr "XO-ын тархалтын порталд холбогдлоо"
+
+#~ msgid "Looking for an XO Mesh Portal..."
+#~ msgstr "XO нэгдсэн порталыг хайж байна ..."
+
+#~ msgid "Connected to a Simple Mesh"
+#~ msgstr "Энгийн тархалтын сүлжээнд холбогдлоо"
+
+#~ msgid "Starting a Simple Mesh"
+#~ msgstr "Энгийн тархалтын сүлжээ эхэлж байна"
+
+#~ msgid "Unknown Mesh"
+#~ msgstr "Үл мэдэгдэх тархалтын сүлжээ"
+
+#, python-format
+#~ msgid "Clipboard object: %s."
+#~ msgstr "Самбарын санасан зүйлс: %s."
+
+#~ msgid "You must enter a server."
+#~ msgstr "Сервер оруулах шаардлагатай."
+
+#~ msgid "Control Panel"
+#~ msgstr "Удирдах Самбар"
+
+#~ msgid "<Ctrl>R"
+#~ msgstr "<Ctrl>R"
+
+#~ msgid "off"
+#~ msgstr "Үгүй"
+
+#~ msgid "on"
+#~ msgstr "Тийм"
+
+#~ msgid "Permission denied. You need to be root to run this method."
+#~ msgstr ""
+#~ "Зөвшөөрөгдсөнгүй. Үүнийг ажиллуулахын тулд та root эрхтэй байх хэрэгтэй."
+
+#~ msgid "Error in reading timezone"
+#~ msgstr "Цагийн бүсийг уншихад алдаа гарлаа"
+
+#, python-format
+#~ msgid "Error copying timezone (from %s): %s"
+#~ msgstr "Цагийн бүсийг хуулахад алдаа гарлаа (%s-с): %s"
+
+#, python-format
+#~ msgid "Changing permission of timezone: %s"
+#~ msgstr "Цагийн бүсийн зөвшөөрлийг өөрчлөх: %s"
+
+#~ msgid "About this XO"
+#~ msgstr "Энэ компьютерын тухай"
+
+#~ msgid "Add to journal"
+#~ msgstr "Бүртгэлийн дэвтэрт нэмэх"
+
+#~ msgid "Reboot"
+#~ msgstr "Дахин ачаалах"
+
+#~ msgid "My Battery life"
+#~ msgstr "Миний цэнэгний хугацаа"
+
+#~ msgid "Battery charging"
+#~ msgstr "Зайг цэнэглэж байна"
+
+#~ msgid "Battery discharging"
+#~ msgstr "Зай цэнэгээ алдаж байна"
+
+#~ msgid "Battery fully charged"
+#~ msgstr "Зай дүүрэн цэнэглэгдлээ"
+
+#~ msgid "Share with:"
+#~ msgstr "Хуваалцах:"
+
+#~ msgid "Private"
+#~ msgstr "Хувийн"
+
+#~ msgid "My Neighborhood"
+#~ msgstr "Миний хөршүүд"
+
+#~ msgid "Undo"
+#~ msgstr "Болих"
+
+#~ msgid "Redo"
+#~ msgstr "Давтах"
+
+#~ msgid "Paste"
+#~ msgstr "Хуулж тавих"
+
+#, python-format
+#~ msgid "%s Activity"
+#~ msgstr "%s ажил"
+
+#~ msgid "Keep error"
+#~ msgstr "Хадгалахад алдаа гарлаа"
+
+#~ msgid "Keep error: all changes will be lost"
+#~ msgstr "Хадгалахад алдаа гарлаа: бүх өөрчлөлтүүд устана"
+
+#~ msgid "Don't stop"
+#~ msgstr "Бүү зогсоо"
+
+#~ msgid "Stop anyway"
+#~ msgstr "Шууд хаах"
+
+#~ msgid "Continue"
+#~ msgstr "Үргэлжлүүлэх"
+
+#~ msgid "OK"
+#~ msgstr "ЗА"
+
+#, python-format
+#~ msgid "%d year"
+#~ msgstr "% жил"
+
+#, python-format
+#~ msgid "%d years"
+#~ msgstr "% жил"
+
+#, python-format
+#~ msgid "%d month"
+#~ msgstr "% сар"
+
+#, python-format
+#~ msgid "%d months"
+#~ msgstr "% сар"
+
+#, python-format
+#~ msgid "%d week"
+#~ msgstr "% долоо хоног"
+
+#, python-format
+#~ msgid "%d weeks"
+#~ msgstr "% долоо хоног"
+
+#, python-format
+#~ msgid "%d day"
+#~ msgstr "% өдөр"
+
+#, python-format
+#~ msgid "%d days"
+#~ msgstr "% өдөр "
+
+#, python-format
+#~ msgid "%d hour"
+#~ msgstr "% цаг"
+
+#, python-format
+#~ msgid "%d hours"
+#~ msgstr "% цаг"
+
+#, python-format
+#~ msgid "%d minute"
+#~ msgstr "% минут"
+
+#, python-format
+#~ msgid "%d minutes"
+#~ msgstr "% минут"
+
+#, python-format
+#~ msgid "%d second"
+#~ msgstr "% секунд"
+
+#~ msgid " and "
+#~ msgstr "ба"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#, python-format
+#~ msgid ", "
+#~ msgstr ","
diff --git a/shell/po/mr.po b/shell/po/mr.po
new file mode 100644
index 0000000..7ae554d
--- /dev/null
+++ b/shell/po/mr.po
@@ -0,0 +1,651 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-07-23 07:44-0400\n"
+"PO-Revision-Date: 2008-07-07 03:15-0400\n"
+"Last-Translator: Sandesh Patil <patil.sandesh@gmail.com>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Pootle 1.1.0rc2\n"
+
+#: ../src/intro/intro.py:65 ../src/controlpanel/view/aboutme.py:100
+msgid "Name:"
+msgstr "नाव"
+
+#: ../src/intro/intro.py:94
+msgid "Click to change color:"
+msgstr "रंग बदलण्यासाठी क्लिक करा"
+
+#: ../src/intro/intro.py:145
+msgid "Back"
+msgstr "मागे"
+
+#: ../src/intro/intro.py:159 ../src/controlpanel/toolbar.py:61
+msgid "Done"
+msgstr "पूर्ण करा"
+
+#: ../src/intro/intro.py:162
+msgid "Next"
+msgstr "पूढे"
+
+#: ../src/view/BuddyMenu.py:58
+msgid "Remove friend"
+msgstr "मित्राला काढून टाका"
+
+#: ../src/view/BuddyMenu.py:61
+msgid "Make friend"
+msgstr "मित्रा करा"
+
+#: ../src/view/BuddyMenu.py:91
+#, python-format
+msgid "Invite to %s"
+msgstr "%s ला आमंत्रण द्या"
+
+#: ../src/view/clipboardmenu.py:48
+msgid "Remove"
+msgstr "काढून टाकणे"
+
+#: ../src/view/clipboardmenu.py:53 ../src/view/clipboardmenu.py:79
+msgid "Open"
+msgstr "उघडणे"
+
+#. self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+#. TODO: Implement stopping downloads
+#. self._stop_item.connect('activate', self._stop_item_activate_cb)
+#. self.append_menu_item(self._stop_item)
+#: ../src/view/clipboardmenu.py:63 ../src/view/home/HomeBox.py:86
+msgid "Keep"
+msgstr "संभाला"
+
+#: ../src/view/clipboardmenu.py:84
+msgid "Open with"
+msgstr "ने उघडा"
+
+#: ../src/view/clipboardmenu.py:216
+#, python-format
+msgid "Clipboard object: %s."
+msgstr "क्‍लिपबॉअर्डची वस्तू:%s"
+
+#: ../src/hardware/keydialog.py:150
+msgid "Key Type:"
+msgstr "कळ प्रकार"
+
+#: ../src/hardware/keydialog.py:170
+msgid "Authentication Type:"
+msgstr "पमाणिकरणचा प्रकार"
+
+#: ../src/hardware/keydialog.py:251
+msgid "Encryption Type:"
+msgstr "गोपीनियचे प्रकार"
+
+#: ../src/view/Shell.py:240
+msgid "Screenshot"
+msgstr "पडदा चित्रा"
+
+#: ../src/view/home/HomeBox.py:80
+msgid "Confirm erase"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:82
+#, python-format
+msgid "Confirm erase: Do you want to permanently erase %s?"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:89 ../src/view/palettes.py:120
+msgid "Erase"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:215
+msgid "List view"
+msgstr "यादी दृष्य"
+
+#: ../src/view/home/HomeBox.py:216
+#, fuzzy
+msgid "<Ctrl>2"
+msgstr "<ctr>L"
+
+#: ../src/view/home/HomeBox.py:273
+msgid "Favorites view"
+msgstr "आवडते दृश्‍ा"
+
+#: ../src/view/home/HomeBox.py:274
+#, fuzzy
+msgid "<Ctrl>1"
+msgstr "<ctr>L"
+
+# TRANS: label for the freeform layout in the favorites view
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/view/home/HomeBox.py:282
+msgid "Freeform"
+msgstr "पासून मुक्त"
+
+# TRANS: label for the ring layout in the favorites view
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/view/home/HomeBox.py:289
+msgid "Ring"
+msgstr "रिंग"
+
+#: ../src/view/home/MeshBox.py:97
+msgid "Connect"
+msgstr "संपर्क साधा"
+
+#: ../src/view/home/MeshBox.py:106
+msgid "Disconnect"
+msgstr "संपर्क तोडा"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#: ../src/view/home/MeshBox.py:118
+msgid "Disconnecting..."
+msgstr "संपर्क तुटत आहे"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#: ../src/view/home/MeshBox.py:152
+msgid "Connecting..."
+msgstr "संपर्क होत आहे"
+
+#. TODO: show the channel number
+#: ../src/view/home/MeshBox.py:159
+msgid "Connected"
+msgstr "संपर्क जुळले"
+
+#: ../src/view/home/MeshBox.py:211 ../src/view/devices/network/mesh.py:41
+#: ../src/view/devices/network/mesh.py:68
+#: ../src/view/devices/network/mesh.py:72
+msgid "Mesh Network"
+msgstr "मेष जाळे"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#: ../src/view/home/MeshBox.py:214 ../src/view/devices/network/wireless.py:119
+#: ../src/view/devices/network/mesh.py:89
+msgid "Disconnect..."
+msgstr "संपर्क तोडने"
+
+#: ../src/view/home/MeshBox.py:302 ../src/view/palettes.py:61
+msgid "Resume"
+msgstr "पुन्हा आरंभ करणे"
+
+#: ../src/view/home/MeshBox.py:307 ../src/view/frame/activitiestray.py:205
+msgid "Join"
+msgstr "सामील व्हा"
+
+#: ../src/view/devices/battery.py:45
+msgid "My Battery"
+msgstr "माज़ी बॅटरी"
+
+#: ../src/view/devices/battery.py:114
+msgid "Charging"
+msgstr "चार्ज होत आहे"
+
+#: ../src/view/devices/battery.py:117
+msgid "Very little power remaining"
+msgstr "थोडाकाच पावर उरला आहे"
+
+#: ../src/view/devices/battery.py:123
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr "%(hour)d:%(min).2d उरले आहे"
+
+#: ../src/view/devices/battery.py:127
+msgid "Charged"
+msgstr "चार्ज झाले"
+
+#: ../src/view/devices/speaker.py:44
+msgid "My Speakers"
+msgstr "माझे स्पीकर"
+
+#: ../src/view/devices/speaker.py:119
+msgid "Unmute"
+msgstr "आवज़ चालू"
+
+#: ../src/view/devices/speaker.py:122
+msgid "Mute"
+msgstr "आवज़ बंद"
+
+#: ../src/view/devices/network/wireless.py:67
+msgid "Disconnected"
+msgstr "संपर्क तुटणे"
+
+#: ../src/view/devices/network/wireless.py:137
+msgid "Channel"
+msgstr "मार्ग"
+
+#: ../src/view/frame/zoomtoolbar.py:34
+msgid "Neighborhood"
+msgstr "शेजार"
+
+#: ../src/view/frame/zoomtoolbar.py:36
+msgid "Group"
+msgstr "गट"
+
+#: ../src/view/frame/zoomtoolbar.py:38
+msgid "Home"
+msgstr "घर"
+
+#: ../src/view/frame/zoomtoolbar.py:40
+msgid "Activity"
+msgstr "क्रिया"
+
+#: ../src/controlpanel/cmd.py:26
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+"शुगर - कंट्रोल - पॅनेल: सूचना, एकनावाचे एकापेक्षा जास्त विकल्प मिळाले: %s "
+"मॉडयुल: %r"
+
+#: ../src/controlpanel/cmd.py:28
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr "शुगर-कंट्रोल-पॅनेल:की=%s पर्याय उपलब्ध नाही"
+
+#: ../src/controlpanel/cmd.py:29
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr "शुगर-कंट्रोल-पॅनेल:%s"
+
+#: ../src/controlpanel/cmd.py:33
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" "
+msgstr ""
+"वापर:शुगर-कंट्रोल-पॅनेल[पर्याय]किल्ली[तर्क .....] \n"
+" शुगर वातावरणचे नियंत्रण. \n"
+" पर्याय: \n"
+" -h_ हा मदतीचा संदेश पाठवा आणि बंद करा \n"
+" -l_ उपलब्ध सूची दाखवा \n"
+" -h_ ह्या किल्लीबद्दल माहिती दाखवा -g किल्ली_ "
+" किल्लीची सध्याची किंमत घ्या \n"
+" -s किल्ली_ किल्लीला सध्याची किंमत द्या \n"
+"\t"
+
+#: ../src/controlpanel/cmd.py:45
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr "तुम्ही केलेले बदल लागू करण्यासाठी शुगर परत चालू करा.\n"
+
+#: ../src/controlpanel/toolbar.py:115
+msgid "Cancel"
+msgstr "रद्द करणे"
+
+#: ../src/controlpanel/toolbar.py:121 ../src/view/home/favoritesview.py:294
+msgid "Ok"
+msgstr "ठीक"
+
+#: ../src/controlpanel/sectionview.py:34 ../src/controlpanel/gui.py:260
+msgid "Changes require restart"
+msgstr "बदली लागू करण्यासाठी परत सुरू करा"
+
+#: ../src/controlpanel/gui.py:259
+msgid "Warning"
+msgstr "सूचना"
+
+#: ../src/controlpanel/gui.py:263
+msgid "Cancel changes"
+msgstr "बदली रद्द करा"
+
+#: ../src/controlpanel/gui.py:267
+msgid "Later"
+msgstr "नंतर"
+
+#: ../src/controlpanel/gui.py:271
+msgid "Restart now"
+msgstr "परत सुरू करा"
+
+#: ../src/controlpanel/model/aboutme.py:44
+msgid "You must enter a name."
+msgstr "तुम्ही नाव टाकला पाहिजे"
+
+#: ../src/controlpanel/model/aboutme.py:69
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr "stroke: color=%s hue=%s"
+
+#: ../src/controlpanel/model/aboutme.py:72
+#, python-format
+msgid "stroke: %s"
+msgstr "stroke: %s"
+
+#: ../src/controlpanel/model/aboutme.py:74
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr "fill: color=%s hue=%s"
+
+#: ../src/controlpanel/model/aboutme.py:76
+#, python-format
+msgid "fill: %s"
+msgstr "fill: %s"
+
+#: ../src/controlpanel/model/aboutme.py:87
+msgid "Error in specified color modifiers."
+msgstr "दिल्या गेलेल्या रंग रुपांतरच्या वर्णनात चुक आहे "
+
+#: ../src/controlpanel/model/aboutme.py:90
+msgid "Error in specified colors."
+msgstr "दिलेल्या रंगामध्ये चुक आहे"
+
+#: ../src/controlpanel/model/aboutxo.py:24
+msgid "Not available"
+msgstr "उपलब्ध नाही"
+
+#: ../src/controlpanel/model/datetime.py:85
+msgid "Error timezone does not exist."
+msgstr "चुक:वेळक्षेत्र अस्तित्वात नाही"
+
+#: ../src/controlpanel/model/frame.py:38 ../src/controlpanel/model/frame.py:60
+msgid "Value must be an integer."
+msgstr "किमान पूर्णांक असला पाहिजे"
+
+#: ../src/controlpanel/model/language.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr "प्रवेश नाही %s.योग्य सेटिंग बनवा."
+
+#: ../src/controlpanel/model/language.py:104
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr "कोडसाठी भाषा=%s अस्तित्वात नाही"
+
+#: ../src/controlpanel/model/language.py:121
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr "माफ करा मी %s बोलू शकत नाही"
+
+#: ../src/controlpanel/model/network.py:48
+msgid "You must enter a server."
+msgstr "तुम्ही सर्वर टाकला पाहिजे"
+
+#: ../src/controlpanel/model/network.py:63
+msgid "State is unknown."
+msgstr "स्थिती अद्यात आहे"
+
+#: ../src/controlpanel/model/network.py:83
+msgid "Error in specified radio argument use on/off."
+msgstr "दिलेल्या रेडिओ सारांशमध्ये चुक आहे, चालू/बंद वापरा"
+
+#: ../src/controlpanel/model/power.py:57
+msgid "Error in automatic pm argument, use on/off."
+msgstr ""
+
+#: ../src/controlpanel/model/power.py:86
+msgid "Error in extreme pm argument, use on/off."
+msgstr ""
+
+#: ../src/controlpanel/view/aboutme.py:32
+msgid "About Me"
+msgstr "माज़ा बद्दल"
+
+#: ../src/controlpanel/view/aboutme.py:134
+msgid "Click to change your color:"
+msgstr "रंग बदलण्यासाठी क्लिक करा"
+
+#: ../src/controlpanel/view/aboutxo.py:26
+msgid "About my XO"
+msgstr "माज़ा एक्सओ बद्दल"
+
+#: ../src/controlpanel/view/aboutxo.py:47
+msgid "Identity"
+msgstr "ओळख"
+
+#: ../src/controlpanel/view/aboutxo.py:56
+msgid "Serial Number:"
+msgstr "क्रमशः क्रमांक"
+
+#: ../src/controlpanel/view/aboutxo.py:79
+msgid "Software"
+msgstr "सॉफ्टवेर"
+
+#: ../src/controlpanel/view/aboutxo.py:88
+msgid "Build:"
+msgstr "बिल्ड :"
+
+#: ../src/controlpanel/view/aboutxo.py:103
+msgid "Firmware:"
+msgstr "फर्मवेर :"
+
+#: ../src/controlpanel/view/datetime.py:29
+msgid "Date & Time"
+msgstr "तारीख आणि वेळ"
+
+#: ../src/controlpanel/view/datetime.py:72
+msgid "Timezone"
+msgstr "समयक्षेत्र"
+
+#: ../src/controlpanel/view/frame.py:28
+msgid "Frame"
+msgstr "रचना"
+
+#: ../src/controlpanel/view/frame.py:30
+msgid "never"
+msgstr "कधीच नाही"
+
+#: ../src/controlpanel/view/frame.py:31
+msgid "instantaneous"
+msgstr "एकाच वेळी घडणारा"
+
+#: ../src/controlpanel/view/frame.py:32
+#, python-format
+msgid "%s seconds"
+msgstr "%s सेकेंड"
+
+#: ../src/controlpanel/view/frame.py:56
+msgid "Activation Delay"
+msgstr "चालू करण्यात होणारा उशीर"
+
+#: ../src/controlpanel/view/frame.py:80
+msgid "Corner"
+msgstr "कोपरा"
+
+#: ../src/controlpanel/view/frame.py:115
+msgid "Edge"
+msgstr "धार"
+
+#: ../src/controlpanel/view/language.py:29
+#: ../src/controlpanel/view/language.py:74
+msgid "Language"
+msgstr "भाषा"
+
+#: ../src/controlpanel/view/network.py:28
+msgid "Network"
+msgstr "जाळ"
+
+#: ../src/controlpanel/view/network.py:53
+msgid "Wireless"
+msgstr "विनतार "
+
+#: ../src/controlpanel/view/network.py:61
+msgid "Radio:"
+msgstr "रेडियो"
+
+#: ../src/controlpanel/view/network.py:94
+msgid "Mesh"
+msgstr "मेष"
+
+#: ../src/controlpanel/view/network.py:103
+msgid "Server:"
+msgstr "सर्वर :"
+
+#: ../src/controlpanel/view/power.py:27
+msgid "Power"
+msgstr ""
+
+#: ../src/controlpanel/view/power.py:51
+msgid "Power management"
+msgstr ""
+
+#: ../src/controlpanel/view/power.py:61
+msgid "Automatic power management (increases battery life)"
+msgstr ""
+
+#: ../src/controlpanel/view/power.py:89
+msgid ""
+"Extreme power management (disables wireless radio, increases battery life)"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:111
+msgid "Connected to a School Mesh Portal"
+msgstr "स्कूल मेष पोर्टला जोडत आहे"
+
+#: ../src/view/devices/network/mesh.py:113
+msgid "Looking for a School Mesh Portal..."
+msgstr " स्कूल मेष पोर्टला शोधत आहे"
+
+#: ../src/view/devices/network/mesh.py:116
+msgid "Connected to an XO Mesh Portal"
+msgstr "xo मेष पोर्टला जोडत आहे"
+
+#: ../src/view/devices/network/mesh.py:118
+msgid "Looking for an XO Mesh Portal..."
+msgstr "xo मेष पोर्टला शोधत आहे"
+
+#: ../src/view/devices/network/mesh.py:121
+msgid "Connected to a Simple Mesh"
+msgstr "साधारण मेषला जोडत आहे"
+
+#: ../src/view/devices/network/mesh.py:123
+msgid "Starting a Simple Mesh"
+msgstr "साधारण मेष चालू होत आहे"
+
+#: ../src/view/devices/network/mesh.py:130
+msgid "Unknown Mesh"
+msgstr "अनोळखी मेष"
+
+#: ../src/view/frame/activitiestray.py:210
+msgid "Decline"
+msgstr "अस्वीकार"
+
+#: ../src/view/home/favoritesview.py:285
+msgid "Registration Failed"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:286
+#, python-format
+msgid "%s"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:288
+msgid "Registration Successful"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:289
+msgid "You are now registered with your school server."
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:405
+msgid "Control Panel"
+msgstr "कंट्रोल पॅनेल "
+
+#: ../src/view/home/favoritesview.py:416
+msgid "Restart"
+msgstr "पुन्हा चालू करा"
+
+#: ../src/view/home/favoritesview.py:421
+msgid "Shutdown"
+msgstr "बंद करा"
+
+#: ../src/view/home/favoritesview.py:427
+msgid "Register"
+msgstr "नोंद"
+
+#: ../src/view/palettes.py:42
+msgid "Starting..."
+msgstr "चालू करणे"
+
+#: ../src/view/palettes.py:72
+msgid "Stop"
+msgstr "थांबा"
+
+#: ../src/view/palettes.py:104
+msgid "Start"
+msgstr "चालू करा"
+
+#: ../src/view/palettes.py:132
+msgid "Remove favorite"
+msgstr " आवडते काढून टाका"
+
+#: ../src/view/palettes.py:136
+msgid "Make favorite"
+msgstr " आवडते नोंद करा"
+
+#: ../src/view/palettes.py:185
+msgid "Show contents"
+msgstr "आतला दाखवा"
+
+#: ../src/view/palettes.py:209
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr "%(free_space)d MB फ्री"
+
+#~ msgid "<Ctrl>R"
+#~ msgstr "<ctr>R"
+
+#~ msgid "Ring view"
+#~ msgstr "वर्तुळ दृष्य"
+
+#~ msgid "Remove from ring"
+#~ msgstr "वर्तुळा मधून काढा"
+
+#~ msgid "Add to ring"
+#~ msgstr "वर्तुळा मधे जोडा "
+
+#~ msgid "off"
+#~ msgstr "बंद"
+
+#~ msgid "on"
+#~ msgstr "चालू"
+
+#~ msgid "Permission denied. You need to be root to run this method."
+#~ msgstr "परवानगी नाही. या पद्धतीचा वापर करण्यासाठी तुम्ही रूट असणे आवश्यक आहे"
+
+#~ msgid "Error in reading timezone"
+#~ msgstr "वेळशेत्राच्या माहिती मध्ये चुकी आहे"
+
+#, python-format
+#~ msgid "Error copying timezone (from %s): %s"
+#~ msgstr "वेळक्षेत्रेची नक्कल करण्यात चुक(%s पासून):%s"
+
+#, python-format
+#~ msgid "Changing permission of timezone: %s"
+#~ msgstr "वेळक्षेत्रेची परवानगी बदलली जात आहे:%s"
+
+#~ msgid "About this XO"
+#~ msgstr "एक्सो बद्दल"
+
+#~ msgid "Add to journal"
+#~ msgstr "नियतकालिका मध्ये समावेश करा"
+
+#~ msgid "Reboot"
+#~ msgstr "परत चालू करा"
+
+#~ msgid "My Battery life"
+#~ msgstr "माझ्या बॅटरीचे आयुष्य"
+
+#~ msgid "Battery charging"
+#~ msgstr "बॅटरी चाजॅ होत आहे"
+
+#~ msgid "Battery discharging"
+#~ msgstr "बॅटरीचे आयुष्य कमी होत आहे"
+
+#~ msgid "Battery fully charged"
+#~ msgstr "बॅटरी पूर्ण चार्ज झाली"
diff --git a/shell/po/ms.po b/shell/po/ms.po
new file mode 100644
index 0000000..93c6b6e
--- /dev/null
+++ b/shell/po/ms.po
@@ -0,0 +1,958 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-01-31 00:30-0500\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../extensions/cpsection/aboutme/__init__.py:24
+msgid "About Me"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:43
+msgid "You must enter a name."
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:68
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:71
+#, python-format
+msgid "stroke: %s"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:73
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:75
+#, python-format
+msgid "fill: %s"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:86
+msgid "Error in specified color modifiers."
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:89
+msgid "Error in specified colors."
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/view.py:94 ../src/jarabe/intro/window.py:92
+msgid "Name:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/view.py:128
+msgid "Click to change your color:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/__init__.py:21
+msgid "About my Computer"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/model.py:26
+msgid "Not available"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:59
+msgid "Identity"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:68
+msgid "Serial Number:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:90
+msgid "Software"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:99
+msgid "Build:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:114
+msgid "Sugar:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:130
+msgid "Firmware:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:145
+msgid "Wireless Firmware:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:168
+msgid "Copyright and License"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:176
+msgid ""
+"© 2008 One Laptop per Child Association Inc; Red Hat Inc; and Contributors."
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:183
+msgid ""
+"Sugar is the graphical user interface that you are looking at. Sugar is free "
+"software, covered by the GNU General Public License, and you are welcome to "
+"change it and/or distribute copies of it under certain conditions described "
+"therein."
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:195
+msgid "Full license:"
+msgstr ""
+
+#: ../extensions/cpsection/datetime/__init__.py:21
+msgid "Date & Time"
+msgstr ""
+
+#: ../extensions/cpsection/datetime/model.py:87
+msgid "Error timezone does not exist."
+msgstr ""
+
+#: ../extensions/cpsection/datetime/view.py:68 ../data/sugar.schemas.in.h:19
+msgid "Timezone"
+msgstr ""
+
+#: ../extensions/cpsection/frame/__init__.py:21
+msgid "Frame"
+msgstr ""
+
+#: ../extensions/cpsection/frame/model.py:38
+#: ../extensions/cpsection/frame/model.py:60
+msgid "Value must be an integer."
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:26
+msgid "never"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:27
+msgid "instantaneous"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:28
+#, python-format
+msgid "%s seconds"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:52
+msgid "Activation Delay"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:76
+msgid "Corner"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:111
+msgid "Edge"
+msgstr ""
+
+#: ../extensions/cpsection/language/__init__.py:21
+#: ../extensions/cpsection/language/view.py:32
+msgid "Language"
+msgstr ""
+
+#: ../extensions/cpsection/language/model.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr ""
+
+#: ../extensions/cpsection/language/model.py:124
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr ""
+
+#: ../extensions/cpsection/language/model.py:144
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr ""
+
+#: ../extensions/cpsection/network/__init__.py:21
+#: ../extensions/cpsection/network/view.py:28
+msgid "Network"
+msgstr ""
+
+#: ../extensions/cpsection/network/model.py:79
+msgid "State is unknown."
+msgstr ""
+
+#: ../extensions/cpsection/network/model.py:105
+msgid "Error in specified radio argument use on/off."
+msgstr ""
+
+#: ../extensions/cpsection/network/model.py:137
+msgid "Error in specified argument use 0/1."
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:56
+msgid "Wireless"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:64
+msgid "Turn off the wireless radio to save battery life"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:77
+msgid "Radio"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:93
+msgid "Discard network history if you have trouble connecting to the network"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:102
+msgid "Discard network history"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:115
+msgid "Collaboration"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:123
+msgid ""
+"The server is the equivalent of what room you are in; people on the same "
+"server will be able to see each other, even when they aren't on the same "
+"network."
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:133
+msgid "Server:"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:56
+msgid "My Battery"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:137
+msgid "Removed"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:140
+msgid "Charging"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:143
+msgid "Very little power remaining"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:149
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:153
+msgid "Charged"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:40
+#, python-format
+msgid "IP address: %s"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:104
+msgid "Disconnect..."
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:109
+#: ../src/jarabe/desktop/meshbox.py:246
+msgid "Connecting..."
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:113
+#: ../extensions/deviceicon/network.py:166
+#: ../src/jarabe/desktop/meshbox.py:252
+msgid "Connected"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:126
+msgid "Channel"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:141
+msgid "Wired Network"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:169
+msgid "Speed"
+msgstr ""
+
+#: ../extensions/deviceicon/speaker.py:46
+msgid "My Speakers"
+msgstr ""
+
+#: ../extensions/deviceicon/speaker.py:128
+msgid "Unmute"
+msgstr ""
+
+#: ../extensions/deviceicon/speaker.py:131
+msgid "Mute"
+msgstr ""
+
+#: ../extensions/globalkey/screenshot.py:50
+msgid "Screenshot"
+msgstr ""
+
+#: ../extensions/globalkey/viewsource.py:196
+#, python-format
+msgid "View source: %r"
+msgstr ""
+
+#: ../extensions/globalkey/viewsource.py:205
+#: ../src/jarabe/frame/zoomtoolbar.py:42
+msgid "Activity"
+msgstr ""
+
+#: ../extensions/globalkey/viewsource.py:212
+msgid "Document"
+msgstr ""
+
+#: ../extensions/globalkey/viewsource.py:226
+#: ../src/jarabe/journal/objectchooser.py:141
+msgid "Close"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:1
+msgid "Backup URL"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:2
+msgid ""
+"Color for the XO icon that is used throughout the desktop. The string is "
+"composed of the stroke color and fill color, format is that of rbg colors. "
+"Example: #AC32FF,#9A5200"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:3
+msgid "Corner Delay"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:4
+msgid "Delay for the activation of the frame using the corners."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:5
+msgid "Delay for the activation of the frame using the edges."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:6
+msgid "Edge Delay"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:7
+msgid "Favorites Layout"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:8
+msgid "Favorites resume mode"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:9
+msgid ""
+"If TRUE, Sugar will make us searchable for the other users of the Jabber "
+"server."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:10
+msgid "Jabber Server"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:11
+msgid "Layout of the favorites view."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:12
+msgid "Power Automatic"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:13
+msgid "Power Automatic."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:14
+msgid "Power Extreme"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:15
+msgid "Power Extreme."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:16
+msgid "Publish to Gadget"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:17
+msgid "Setting for muting the sound device."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:18
+msgid "Sound Muted"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:20
+msgid "Timezone setting for the system."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:21
+msgid "Url of the jabber server to use."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:22
+msgid "Url where the backup is saved to."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:23
+msgid "User Color"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:24
+msgid "User Name"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:25
+msgid "User name that is used throughout the desktop."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:26
+msgid "Volume Level"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:27
+msgid "Volume level for the sound device."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:28
+msgid ""
+"When in resume mode, clicking on a favorite icon will cause the last entry "
+"for that activity to be resumed."
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:28
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:30
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:31
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr ""
+
+#. TRANS: Translators, there's a empty line at the end of this string,
+#. which must appear in the translated string (msgstr) as well.
+#: ../src/jarabe/controlpanel/cmd.py:37
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" -c key clear the current value for the key \n"
+" "
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:50
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:272
+msgid "Warning"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:273
+#: ../src/jarabe/controlpanel/sectionview.py:42
+msgid "Changes require restart"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:276
+msgid "Cancel changes"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:281 ../src/jarabe/desktop/homebox.py:113
+msgid "Later"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:285
+msgid "Restart now"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/toolbar.py:61 ../src/jarabe/intro/window.py:188
+msgid "Done"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/toolbar.py:115
+#: ../src/jarabe/desktop/homebox.py:111
+#: ../src/jarabe/frame/activitiestray.py:683
+#: ../src/jarabe/frame/activitiestray.py:762
+#: ../src/jarabe/frame/activitiestray.py:790
+msgid "Cancel"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/toolbar.py:121
+#: ../src/jarabe/desktop/favoritesview.py:334
+msgid "Ok"
+msgstr ""
+
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:114
+msgid "Freeform"
+msgstr ""
+
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:196
+msgid "Ring"
+msgstr ""
+
+#. TRANS: label for the spiral layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:341
+msgid "Spiral"
+msgstr ""
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:408
+msgid "Box"
+msgstr ""
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:449
+msgid "Triangle"
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:325
+msgid "Registration Failed"
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:326
+#, python-format
+msgid "%s"
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:328
+msgid "Registration Successful"
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:329
+msgid "You are now registered with your school server."
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:666
+msgid "Register"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:67
+msgid "Confirm erase"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:69
+#, python-format
+msgid "Confirm erase: Do you want to permanently erase %s?"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:73 ../src/jarabe/frame/clipboardmenu.py:62
+msgid "Keep"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:76
+#: ../src/jarabe/journal/journaltoolbox.py:357
+#: ../src/jarabe/journal/palettes.py:112 ../src/jarabe/view/palettes.py:127
+msgid "Erase"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:106
+msgid "Software Update"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:107
+msgid "Update your activities to ensure compatibility with your new software"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:116
+msgid "Check now"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:233
+msgid "List view"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:234
+msgid "<Ctrl>2"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:296
+msgid "Favorites view"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:297
+msgid "<Ctrl>1"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:304
+msgid "Resume by default"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:131
+msgid "Key Type:"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:151
+msgid "Authentication Type:"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:215
+msgid "WPA & WPA2 Personal"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:224
+msgid "Wireless Security:"
+msgstr ""
+
+#: ../src/jarabe/desktop/meshbox.py:130
+msgid "Connect"
+msgstr ""
+
+#: ../src/jarabe/desktop/meshbox.py:134
+msgid "Disconnect"
+msgstr ""
+
+#. TRANS: Action label for resuming an activity.
+#: ../src/jarabe/desktop/meshbox.py:440
+#: ../src/jarabe/frame/activitiestray.py:707
+#: ../src/jarabe/journal/journaltoolbox.py:425
+#: ../src/jarabe/journal/palettes.py:72 ../src/jarabe/view/palettes.py:62
+msgid "Resume"
+msgstr ""
+
+#: ../src/jarabe/desktop/meshbox.py:445
+#: ../src/jarabe/frame/activitiestray.py:221
+msgid "Join"
+msgstr ""
+
+#: ../src/jarabe/desktop/schoolserver.py:18
+msgid "Cannot obtain data needed for registration."
+msgstr ""
+
+#: ../src/jarabe/desktop/schoolserver.py:35
+msgid "Cannot connect to the server."
+msgstr ""
+
+#: ../src/jarabe/desktop/schoolserver.py:40
+msgid "The server could not complete the request."
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:226
+#: ../src/jarabe/frame/activitiestray.py:655
+msgid "Decline"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:608
+#, python-format
+msgid "%dB"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:610
+#, python-format
+msgid "%dKB"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:612
+#, python-format
+msgid "%dMB"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:629
+#, python-format
+msgid "%s of %s"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:640
+#, python-format
+msgid "Transfer from %r"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:650
+msgid "Accept"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:673
+#: ../src/jarabe/frame/activitiestray.py:780
+#, python-format
+msgid "%s (%s)"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:751
+#, python-format
+msgid "Transfer to %r"
+msgstr ""
+
+#: ../src/jarabe/frame/clipboardmenu.py:52
+msgid "Remove"
+msgstr ""
+
+#: ../src/jarabe/frame/clipboardmenu.py:57
+#: ../src/jarabe/frame/clipboardmenu.py:80
+msgid "Open"
+msgstr ""
+
+#: ../src/jarabe/frame/clipboardmenu.py:85
+msgid "Open with"
+msgstr ""
+
+#: ../src/jarabe/frame/clipboardobject.py:47
+#, python-format
+msgid "%s clipping"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:36
+msgid "Neighborhood"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:38
+msgid "Group"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:40
+msgid "Home"
+msgstr ""
+
+#: ../src/jarabe/intro/window.py:124
+msgid "Click to change color:"
+msgstr ""
+
+#: ../src/jarabe/intro/window.py:174 ../src/jarabe/journal/detailview.py:103
+msgid "Back"
+msgstr ""
+
+#: ../src/jarabe/intro/window.py:191
+msgid "Next"
+msgstr ""
+
+#: ../src/jarabe/journal/collapsedentry.py:258
+#: ../src/jarabe/journal/expandedentry.py:159
+#: ../src/jarabe/journal/palettes.py:66
+msgid "Untitled"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:205
+msgid "No preview"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:224
+msgid "Participants:"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:247
+msgid "Description:"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:273
+msgid "Tags:"
+msgstr ""
+
+#: ../src/jarabe/journal/journalactivity.py:108
+#: ../src/jarabe/journal/volumestoolbar.py:47
+msgid "Journal"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:65
+msgid "Search"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:124
+msgid "Anytime"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:126
+msgid "Today"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:128
+msgid "Since yesterday"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 7 days.
+#: ../src/jarabe/journal/journaltoolbox.py:130
+msgid "Past week"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 30 days.
+#: ../src/jarabe/journal/journaltoolbox.py:132
+msgid "Past month"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 356 days.
+#: ../src/jarabe/journal/journaltoolbox.py:134
+msgid "Past year"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:141
+msgid "Anyone"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:143
+msgid "My friends"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:144
+msgid "My class"
+msgstr ""
+
+#. TRANS: Item in a combo box that filters by entry type.
+#: ../src/jarabe/journal/journaltoolbox.py:271
+msgid "Anything"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:347
+#: ../src/jarabe/journal/palettes.py:90
+msgid "Copy"
+msgstr ""
+
+#. TRANS: Action label for starting an entry.
+#: ../src/jarabe/journal/journaltoolbox.py:428
+#: ../src/jarabe/journal/palettes.py:75 ../src/jarabe/view/palettes.py:111
+msgid "Start"
+msgstr ""
+
+#: ../src/jarabe/journal/listview.py:40
+msgid "Your Journal is empty"
+msgstr ""
+
+#: ../src/jarabe/journal/listview.py:41
+msgid "No matching entries "
+msgstr ""
+
+#: ../src/jarabe/journal/listview.py:369
+msgid "Clear search"
+msgstr ""
+
+#: ../src/jarabe/journal/misc.py:92
+msgid "No date"
+msgstr ""
+
+#: ../src/jarabe/journal/modalalert.py:63
+msgid "Your Journal is full"
+msgstr ""
+
+#: ../src/jarabe/journal/modalalert.py:67
+msgid "Please delete some old Journal entries to make space for new ones."
+msgstr ""
+
+#: ../src/jarabe/journal/modalalert.py:79
+msgid "Show Journal"
+msgstr ""
+
+#: ../src/jarabe/journal/objectchooser.py:136
+msgid "Choose an object"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:73
+msgid "Resume with"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:76
+msgid "Start with"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:98
+msgid "Send to"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:107
+msgid "View Details"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:185
+msgid "No friends present"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:190
+msgid "No valid connection found"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:218
+msgid "No activity to resume entry"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:220
+msgid "No activity to start entry"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:61
+msgid "Remove friend"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:64
+msgid "Make friend"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:81
+msgid "My Settings"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:86
+msgid "Logout"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:91
+msgid "Restart"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:96
+msgid "Shutdown"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:131
+#, python-format
+msgid "Invite to %s"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:43
+msgid "Starting..."
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:73
+msgid "Stop"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:145
+msgid "Remove favorite"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:149
+msgid "Make favorite"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:201
+msgid "Show contents"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:223 ../src/jarabe/view/palettes.py:272
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:248
+msgid "Unmount"
+msgstr ""
diff --git a/shell/po/mvo.po b/shell/po/mvo.po
new file mode 100644
index 0000000..e184567
--- /dev/null
+++ b/shell/po/mvo.po
@@ -0,0 +1,517 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-21 00:30-0400\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/intro/intro.py:65 ../src/controlpanel/view/aboutme.py:100
+msgid "Name:"
+msgstr ""
+
+#: ../src/intro/intro.py:94
+msgid "Click to change color:"
+msgstr ""
+
+#: ../src/intro/intro.py:145
+msgid "Back"
+msgstr ""
+
+#: ../src/intro/intro.py:159 ../src/controlpanel/toolbar.py:61
+msgid "Done"
+msgstr ""
+
+#: ../src/intro/intro.py:162
+msgid "Next"
+msgstr ""
+
+#: ../src/view/BuddyMenu.py:58
+msgid "Remove friend"
+msgstr ""
+
+#: ../src/view/BuddyMenu.py:61
+msgid "Make friend"
+msgstr ""
+
+#: ../src/view/BuddyMenu.py:91
+#, python-format
+msgid "Invite to %s"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:48
+msgid "Remove"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:53 ../src/view/clipboardmenu.py:79
+msgid "Open"
+msgstr ""
+
+#. self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+#. TODO: Implement stopping downloads
+#. self._stop_item.connect('activate', self._stop_item_activate_cb)
+#. self.append_menu_item(self._stop_item)
+#: ../src/view/clipboardmenu.py:63
+msgid "Keep"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:84
+msgid "Open with"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:212
+#, python-format
+msgid "Clipboard object: %s."
+msgstr ""
+
+#: ../src/hardware/keydialog.py:150
+msgid "Key Type:"
+msgstr ""
+
+#: ../src/hardware/keydialog.py:170
+msgid "Authentication Type:"
+msgstr ""
+
+#: ../src/hardware/keydialog.py:251
+msgid "Encryption Type:"
+msgstr ""
+
+#: ../src/view/Shell.py:262
+msgid "Screenshot"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:147
+msgid "List view"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:148
+msgid "<Ctrl>L"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:204
+msgid "Favorites view"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:205
+msgid "<Ctrl>R"
+msgstr ""
+
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/view/home/HomeBox.py:211
+msgid "Freeform"
+msgstr ""
+
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/view/home/HomeBox.py:218
+msgid "Ring"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:97
+msgid "Connect"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:106
+msgid "Disconnect"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:118
+msgid "Disconnecting..."
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:152
+msgid "Connecting..."
+msgstr ""
+
+#. TODO: show the channel number
+#: ../src/view/home/MeshBox.py:159
+msgid "Connected"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:211 ../src/view/devices/network/mesh.py:38
+#: ../src/view/devices/network/mesh.py:65
+#: ../src/view/devices/network/mesh.py:69
+msgid "Mesh Network"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:214 ../src/view/devices/network/wireless.py:116
+#: ../src/view/devices/network/mesh.py:86
+msgid "Disconnect..."
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:302 ../src/view/palettes.py:60
+msgid "Resume"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:307 ../src/view/frame/activitiestray.py:219
+msgid "Join"
+msgstr ""
+
+#: ../src/view/devices/battery.py:42
+msgid "My Battery"
+msgstr ""
+
+#: ../src/view/devices/battery.py:111
+msgid "Charging"
+msgstr ""
+
+#: ../src/view/devices/battery.py:114
+msgid "Very little power remaining"
+msgstr ""
+
+#: ../src/view/devices/battery.py:120
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr ""
+
+#: ../src/view/devices/battery.py:124
+msgid "Charged"
+msgstr ""
+
+#: ../src/view/devices/speaker.py:40
+msgid "My Speakers"
+msgstr ""
+
+#: ../src/view/devices/speaker.py:104
+msgid "Unmute"
+msgstr ""
+
+#: ../src/view/devices/speaker.py:107
+msgid "Mute"
+msgstr ""
+
+#: ../src/view/devices/network/wireless.py:64
+msgid "Disconnected"
+msgstr ""
+
+#: ../src/view/devices/network/wireless.py:134
+msgid "Channel"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:34
+msgid "Neighborhood"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:36
+msgid "Group"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:38
+msgid "Home"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:40
+msgid "Activity"
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:26
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:28
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:29
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:33
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" "
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:45
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr ""
+
+#: ../src/controlpanel/toolbar.py:115
+msgid "Cancel"
+msgstr ""
+
+#: ../src/controlpanel/toolbar.py:121
+msgid "Ok"
+msgstr ""
+
+#: ../src/controlpanel/sectionview.py:34 ../src/controlpanel/gui.py:250
+msgid "Changes require restart"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:249
+msgid "Warning"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:253
+msgid "Cancel changes"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:257
+msgid "Later"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:261
+msgid "Restart now"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:44
+msgid "You must enter a name."
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:67
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:70
+#, python-format
+msgid "stroke: %s"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:72
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:74
+#, python-format
+msgid "fill: %s"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:85
+msgid "Error in specified color modifiers."
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:88
+msgid "Error in specified colors."
+msgstr ""
+
+#: ../src/controlpanel/model/aboutxo.py:24
+msgid "Not available"
+msgstr ""
+
+#: ../src/controlpanel/model/datetime.py:85
+msgid "Error timezone does not exist."
+msgstr ""
+
+#: ../src/controlpanel/model/frame.py:38 ../src/controlpanel/model/frame.py:60
+msgid "Value must be an integer."
+msgstr ""
+
+#: ../src/controlpanel/model/language.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr ""
+
+#: ../src/controlpanel/model/language.py:104
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr ""
+
+#: ../src/controlpanel/model/language.py:121
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr ""
+
+#: ../src/controlpanel/model/network.py:48
+msgid "You must enter a server."
+msgstr ""
+
+#: ../src/controlpanel/model/network.py:63
+msgid "State is unknown."
+msgstr ""
+
+#: ../src/controlpanel/model/network.py:83
+msgid "Error in specified radio argument use on/off."
+msgstr ""
+
+#: ../src/controlpanel/view/aboutme.py:32
+msgid "About Me"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutme.py:134
+msgid "Click to change your color:"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutxo.py:26
+msgid "About my XO"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutxo.py:47
+msgid "Identity"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutxo.py:56
+msgid "Serial Number:"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutxo.py:79
+msgid "Software"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutxo.py:88
+msgid "Build:"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutxo.py:103
+msgid "Firmware:"
+msgstr ""
+
+#: ../src/controlpanel/view/datetime.py:29
+msgid "Date & Time"
+msgstr ""
+
+#: ../src/controlpanel/view/datetime.py:72
+msgid "Timezone"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:28
+msgid "Frame"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:30
+msgid "never"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:31
+msgid "instantaneous"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:32
+#, python-format
+msgid "%s seconds"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:56
+msgid "Activation Delay"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:80
+msgid "Corner"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:115
+msgid "Edge"
+msgstr ""
+
+#: ../src/controlpanel/view/language.py:29
+#: ../src/controlpanel/view/language.py:74
+msgid "Language"
+msgstr ""
+
+#: ../src/controlpanel/view/network.py:28
+msgid "Network"
+msgstr ""
+
+#: ../src/controlpanel/view/network.py:53
+msgid "Wireless"
+msgstr ""
+
+#: ../src/controlpanel/view/network.py:61
+msgid "Radio:"
+msgstr ""
+
+#: ../src/controlpanel/view/network.py:94
+msgid "Mesh"
+msgstr ""
+
+#: ../src/controlpanel/view/network.py:103
+msgid "Server:"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:108
+msgid "Connected to a School Mesh Portal"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:110
+msgid "Looking for a School Mesh Portal..."
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:113
+msgid "Connected to an XO Mesh Portal"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:115
+msgid "Looking for an XO Mesh Portal..."
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:118
+msgid "Connected to a Simple Mesh"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:120
+msgid "Starting a Simple Mesh"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:127
+msgid "Unknown Mesh"
+msgstr ""
+
+#: ../src/view/frame/activitiestray.py:224
+msgid "Decline"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:351
+msgid "Control Panel"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:362
+msgid "Restart"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:367
+msgid "Shutdown"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:373
+msgid "Register"
+msgstr ""
+
+#: ../src/view/palettes.py:41
+msgid "Starting..."
+msgstr ""
+
+#: ../src/view/palettes.py:71
+msgid "Stop"
+msgstr ""
+
+#: ../src/view/palettes.py:96
+msgid "Start"
+msgstr ""
+
+#: ../src/view/palettes.py:119
+msgid "Remove favorite"
+msgstr ""
+
+#: ../src/view/palettes.py:123
+msgid "Make favorite"
+msgstr ""
+
+#: ../src/view/palettes.py:169
+msgid "Show contents"
+msgstr ""
+
+#: ../src/view/palettes.py:193
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr ""
diff --git a/shell/po/nb.po b/shell/po/nb.po
new file mode 100644
index 0000000..a96a394
--- /dev/null
+++ b/shell/po/nb.po
@@ -0,0 +1,621 @@
+# translation of sugar.po to Norsk bokmål
+# Kent Dahl <kentda@pvv.org>, 2008.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+msgid ""
+msgstr ""
+"Project-Id-Version: sugar\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-07-16 00:30-0400\n"
+"PO-Revision-Date: 2008-07-26 13:31+0200\n"
+"Last-Translator: Kent Dahl <kentda@pvv.org>\n"
+"Language-Team: Norsk bokmål <i18n-no@lister.ping.uio.no>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/intro/intro.py:65
+#: ../src/controlpanel/view/aboutme.py:100
+msgid "Name:"
+msgstr "Navn:"
+
+#: ../src/intro/intro.py:94
+msgid "Click to change color:"
+msgstr "Klikk her for å endre farge"
+
+#: ../src/intro/intro.py:145
+msgid "Back"
+msgstr "Tilbake"
+
+#: ../src/intro/intro.py:159
+#: ../src/controlpanel/toolbar.py:61
+msgid "Done"
+msgstr "Ferdig"
+
+#: ../src/intro/intro.py:162
+msgid "Next"
+msgstr "Neste"
+
+#: ../src/view/BuddyMenu.py:58
+msgid "Remove friend"
+msgstr "Fjern venn"
+
+#: ../src/view/BuddyMenu.py:61
+msgid "Make friend"
+msgstr "Bli venn"
+
+#: ../src/view/BuddyMenu.py:91
+#, python-format
+msgid "Invite to %s"
+msgstr "Inviter til %s"
+
+#: ../src/view/clipboardmenu.py:48
+msgid "Remove"
+msgstr "Fjern"
+
+#: ../src/view/clipboardmenu.py:53
+#: ../src/view/clipboardmenu.py:79
+msgid "Open"
+msgstr "Åpne"
+
+#. self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+#. TODO: Implement stopping downloads
+#. self._stop_item.connect('activate', self._stop_item_activate_cb)
+#. self.append_menu_item(self._stop_item)
+#: ../src/view/clipboardmenu.py:63
+#: ../src/view/home/HomeBox.py:86
+msgid "Keep"
+msgstr "Behold"
+
+#: ../src/view/clipboardmenu.py:84
+msgid "Open with"
+msgstr "Åpne med"
+
+#: ../src/view/clipboardmenu.py:212
+#, python-format
+#, fuzzy, python-format
+msgid "Clipboard object: %s."
+msgstr "Utklipp: %s"
+
+#: ../src/hardware/keydialog.py:150
+msgid "Key Type:"
+msgstr "Nøkkelform:"
+
+#: ../src/hardware/keydialog.py:170
+msgid "Authentication Type:"
+msgstr "Autentiseringsform:"
+
+#: ../src/hardware/keydialog.py:251
+msgid "Encryption Type:"
+msgstr "Krypteringsform:"
+
+#: ../src/view/Shell.py:235
+#, fuzzy
+msgid "Screenshot"
+msgstr "Skjermbilde"
+
+#: ../src/view/home/HomeBox.py:80
+msgid "Confirm erase"
+msgstr "Bekreft sletting"
+
+#: ../src/view/home/HomeBox.py:82
+#, python-format
+msgid "Confirm erase: Do you want to permanently erase %s?"
+msgstr "Bekreft sletting: Ønsker du å slette %s for godt?"
+
+#: ../src/view/home/HomeBox.py:89
+#: ../src/view/palettes.py:120
+msgid "Erase"
+msgstr "Slett"
+
+#: ../src/view/home/HomeBox.py:215
+msgid "List view"
+msgstr "Listevisning"
+
+#: ../src/view/home/HomeBox.py:216
+msgid "<Ctrl>2"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:273
+#, fuzzy
+msgid "Favorites view"
+msgstr "Favoritvisning"
+
+#: ../src/view/home/HomeBox.py:274
+msgid "<Ctrl>1"
+msgstr ""
+
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/view/home/HomeBox.py:282
+#, fuzzy
+msgid "Freeform"
+msgstr "Frihånd"
+
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/view/home/HomeBox.py:289
+msgid "Ring"
+msgstr "Ring"
+
+#: ../src/view/home/MeshBox.py:97
+msgid "Connect"
+msgstr "Koble til"
+
+#: ../src/view/home/MeshBox.py:106
+msgid "Disconnect"
+msgstr "Koble fra"
+
+#: ../src/view/home/MeshBox.py:118
+msgid "Disconnecting..."
+msgstr "Kobler fra..."
+
+#: ../src/view/home/MeshBox.py:152
+msgid "Connecting..."
+msgstr "Kobler til..."
+
+#. TODO: show the channel number
+#: ../src/view/home/MeshBox.py:159
+msgid "Connected"
+msgstr "Tilkoblet"
+
+#: ../src/view/home/MeshBox.py:211
+#: ../src/view/devices/network/mesh.py:38
+#: ../src/view/devices/network/mesh.py:65
+#: ../src/view/devices/network/mesh.py:69
+#, fuzzy
+msgid "Mesh Network"
+msgstr "Maskenettverk"
+
+#: ../src/view/home/MeshBox.py:214
+#: ../src/view/devices/network/wireless.py:116
+#: ../src/view/devices/network/mesh.py:86
+msgid "Disconnect..."
+msgstr "Koble fra..."
+
+#: ../src/view/home/MeshBox.py:302
+#: ../src/view/palettes.py:61
+msgid "Resume"
+msgstr "Gjenoppta"
+
+#: ../src/view/home/MeshBox.py:307
+#: ../src/view/frame/activitiestray.py:205
+msgid "Join"
+msgstr "Bli med"
+
+#: ../src/view/devices/battery.py:42
+msgid "My Battery"
+msgstr "Mitt Batteri"
+
+#: ../src/view/devices/battery.py:111
+msgid "Charging"
+msgstr "Lader"
+
+#: ../src/view/devices/battery.py:114
+#, fuzzy
+msgid "Very little power remaining"
+msgstr "Veldig lite strøm igjen"
+
+#: ../src/view/devices/battery.py:120
+#, python-format
+#, fuzzy, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr "%(hour)d:%(min).2d gjenstår"
+
+#: ../src/view/devices/battery.py:124
+msgid "Charged"
+msgstr "Oppladet"
+
+#: ../src/view/devices/speaker.py:41
+msgid "My Speakers"
+msgstr "Mine Høytalere"
+
+#: ../src/view/devices/speaker.py:116
+#, fuzzy
+msgid "Unmute"
+msgstr "Ikke demp"
+
+#: ../src/view/devices/speaker.py:119
+msgid "Mute"
+msgstr "Demp"
+
+#: ../src/view/devices/network/wireless.py:64
+msgid "Disconnected"
+msgstr "Avkoblet"
+
+#: ../src/view/devices/network/wireless.py:134
+msgid "Channel"
+msgstr "Kanal"
+
+#: ../src/view/frame/zoomtoolbar.py:34
+msgid "Neighborhood"
+msgstr "Nabolag"
+
+#: ../src/view/frame/zoomtoolbar.py:36
+msgid "Group"
+msgstr "Gruppe"
+
+#: ../src/view/frame/zoomtoolbar.py:38
+msgid "Home"
+msgstr "Hjem"
+
+#: ../src/view/frame/zoomtoolbar.py:40
+msgid "Activity"
+msgstr "Lek"
+
+#: ../src/controlpanel/cmd.py:26
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+"sugar-control-panel: ADVARSEL, fant mer enn en opsjon med samme navnet: %s "
+"modul: %r"
+
+#: ../src/controlpanel/cmd.py:28
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr "sugar-control-panel: key=%s er ikke en tilgjengelig opsjon."
+
+#: ../src/controlpanel/cmd.py:29
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr ""
+
+# Remember to fix indenting.
+#: ../src/controlpanel/cmd.py:33
+#, fuzzy
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" "
+msgstr ""
+"Bruk: sugar-control-panel [ opsjon ] nøkkel [ argumenter ... ] \n"
+" Kontrollerer sugar-miljøet. \n"
+" Opsjoner: \n"
+" -h vis denne hjelpen og avsluttt \n"
+" -l lister alle tilgjengelig opsjoner \n"
+" -h nøkkel vis informasjon om denne nøkkelen \n"
+" -g nøkkel hent verdien til denne nøkkelen \n"
+" -s nøkkel sett denne nøkkelen sin verdi \n"
+" "
+
+#: ../src/controlpanel/cmd.py:45
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr "For å gjennomføre endringene må du starte sugar på nytt.\n"
+
+#: ../src/controlpanel/toolbar.py:115
+msgid "Cancel"
+msgstr "Avbryt"
+
+#: ../src/controlpanel/toolbar.py:121
+msgid "Ok"
+msgstr ""
+
+#: ../src/controlpanel/sectionview.py:34
+#: ../src/controlpanel/gui.py:260
+msgid "Changes require restart"
+msgstr "Endringer krever omstart"
+
+#: ../src/controlpanel/gui.py:259
+msgid "Warning"
+msgstr "Advarsel"
+
+#: ../src/controlpanel/gui.py:263
+msgid "Cancel changes"
+msgstr "Avbryt endringer"
+
+#: ../src/controlpanel/gui.py:267
+msgid "Later"
+msgstr "Senere"
+
+#: ../src/controlpanel/gui.py:271
+msgid "Restart now"
+msgstr "Start om nå"
+
+#: ../src/controlpanel/model/aboutme.py:44
+msgid "You must enter a name."
+msgstr "Du må skrive inn et navn."
+
+#: ../src/controlpanel/model/aboutme.py:67
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:70
+#, python-format
+msgid "stroke: %s"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:72
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:74
+#, python-format
+msgid "fill: %s"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:85
+#, fuzzy
+msgid "Error in specified color modifiers."
+msgstr "Feil i de angitte fargemodifikatorene."
+
+#: ../src/controlpanel/model/aboutme.py:88
+msgid "Error in specified colors."
+msgstr "Feil i de angitte fargene."
+
+#: ../src/controlpanel/model/aboutxo.py:24
+msgid "Not available"
+msgstr "Ikke tilgjengelig"
+
+#: ../src/controlpanel/model/datetime.py:85
+msgid "Error timezone does not exist."
+msgstr "Feil: Tidssonen finnes ikke."
+
+#: ../src/controlpanel/model/frame.py:38
+#: ../src/controlpanel/model/frame.py:60
+msgid "Value must be an integer."
+msgstr "Verdien må være et heltall."
+
+#: ../src/controlpanel/model/language.py:28
+#, fuzzy
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr "Fikk ikke tilgang til ~/.i18n. Lag standardinnstillinger."
+
+#: ../src/controlpanel/model/language.py:104
+#, python-format
+#, fuzzy, python-format
+msgid "Language for code=%s could not be determined."
+msgstr "Kunne ikke fastslå språket for koden=%s."
+
+#: ../src/controlpanel/model/language.py:121
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr "Beklager, jeg snakker ikke '%s'."
+
+#: ../src/controlpanel/model/network.py:48
+msgid "You must enter a server."
+msgstr "Du må angi en tjener."
+
+#: ../src/controlpanel/model/network.py:63
+msgid "State is unknown."
+msgstr "Tilstanden er ukjent."
+
+#: ../src/controlpanel/model/network.py:83
+msgid "Error in specified radio argument use on/off."
+msgstr ""
+
+#: ../src/controlpanel/model/power.py:57
+msgid "Error in automatic pm argument, use on/off."
+msgstr "Feil i argument til automatisk strømstyring, bruk on/off."
+
+#: ../src/controlpanel/model/power.py:86
+#, fuzzy
+msgid "Error in extreme pm argument, use on/off."
+msgstr "Feil i argument til ekstrem strømstyring, bruk on/off."
+
+#: ../src/controlpanel/view/aboutme.py:32
+msgid "About Me"
+msgstr "Om meg"
+
+#: ../src/controlpanel/view/aboutme.py:134
+msgid "Click to change your color:"
+msgstr "Klikk for å endre din farge:"
+
+#: ../src/controlpanel/view/aboutxo.py:26
+msgid "About my XO"
+msgstr "Om min XO"
+
+#: ../src/controlpanel/view/aboutxo.py:47
+msgid "Identity"
+msgstr "Identitet"
+
+#: ../src/controlpanel/view/aboutxo.py:56
+msgid "Serial Number:"
+msgstr "Serienummer:"
+
+#: ../src/controlpanel/view/aboutxo.py:79
+msgid "Software"
+msgstr "Programvare"
+
+#: ../src/controlpanel/view/aboutxo.py:88
+msgid "Build:"
+msgstr "Bygg:"
+
+#: ../src/controlpanel/view/aboutxo.py:103
+#, fuzzy
+msgid "Firmware:"
+msgstr "Fastvare:"
+
+#: ../src/controlpanel/view/datetime.py:29
+msgid "Date & Time"
+msgstr "Dato og tid"
+
+#: ../src/controlpanel/view/datetime.py:72
+msgid "Timezone"
+msgstr "Tidssone"
+
+#: ../src/controlpanel/view/frame.py:28
+msgid "Frame"
+msgstr "Ramme"
+
+#: ../src/controlpanel/view/frame.py:30
+msgid "never"
+msgstr "aldri"
+
+#: ../src/controlpanel/view/frame.py:31
+#, fuzzy
+msgid "instantaneous"
+msgstr "umiddelbar"
+
+#: ../src/controlpanel/view/frame.py:32
+#, python-format
+msgid "%s seconds"
+msgstr "%s sekunder"
+
+#: ../src/controlpanel/view/frame.py:56
+msgid "Activation Delay"
+msgstr "Aktiveringsforsinkelse"
+
+#: ../src/controlpanel/view/frame.py:80
+msgid "Corner"
+msgstr "Hjørne"
+
+#: ../src/controlpanel/view/frame.py:115
+msgid "Edge"
+msgstr "Kant"
+
+#: ../src/controlpanel/view/language.py:29
+#: ../src/controlpanel/view/language.py:74
+msgid "Language"
+msgstr "Språk"
+
+#: ../src/controlpanel/view/network.py:28
+msgid "Network"
+msgstr "Nettverk"
+
+#: ../src/controlpanel/view/network.py:53
+msgid "Wireless"
+msgstr "Trådløs"
+
+#: ../src/controlpanel/view/network.py:61
+msgid "Radio:"
+msgstr "Radio:"
+
+# Maskenett
+#: ../src/controlpanel/view/network.py:94
+#, fuzzy
+msgid "Mesh"
+msgstr "Maskenett"
+
+#: ../src/controlpanel/view/network.py:103
+msgid "Server:"
+msgstr "Tjener:"
+
+#: ../src/controlpanel/view/power.py:27
+msgid "Power"
+msgstr "Strøm"
+
+#: ../src/controlpanel/view/power.py:51
+msgid "Power management"
+msgstr "Strømstyring"
+
+#: ../src/controlpanel/view/power.py:61
+msgid "Automatic power management (increases battery life)"
+msgstr "Automatisk strømstyring (øker batterilevetiden)"
+
+#: ../src/controlpanel/view/power.py:89
+#, fuzzy
+msgid ""
+"Extreme power management (disables wireless radio, increases battery life)"
+msgstr "Ekstrem strømstyring (slår av trådløs radio, øker batterilevetiden)"
+
+#: ../src/view/devices/network/mesh.py:108
+#, fuzzy
+msgid "Connected to a School Mesh Portal"
+msgstr "Koblet til en skolemaskenettportal"
+
+#: ../src/view/devices/network/mesh.py:110
+#, fuzzy
+msgid "Looking for a School Mesh Portal..."
+msgstr "Leter etter en skolemaskenettportal"
+
+#: ../src/view/devices/network/mesh.py:113
+#, fuzzy
+msgid "Connected to an XO Mesh Portal"
+msgstr "Koblet til en XO maskenett portal"
+
+#: ../src/view/devices/network/mesh.py:115
+#, fuzzy
+msgid "Looking for an XO Mesh Portal..."
+msgstr "Leter etter en XO maskenett portal..."
+
+#: ../src/view/devices/network/mesh.py:118
+#, fuzzy
+msgid "Connected to a Simple Mesh"
+msgstr "Koblet til et enkelt maskenett"
+
+#: ../src/view/devices/network/mesh.py:120
+#, fuzzy
+msgid "Starting a Simple Mesh"
+msgstr "Starter et enkelt maskenett"
+
+#: ../src/view/devices/network/mesh.py:127
+#, fuzzy
+msgid "Unknown Mesh"
+msgstr "Ukjent maskenett"
+
+#: ../src/view/frame/activitiestray.py:210
+msgid "Decline"
+msgstr "Avslå"
+
+#: ../src/view/home/favoritesview.py:285
+msgid "Registration Failed"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:286
+#, python-format
+msgid "%s"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:288
+msgid "Registration Successful"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:289
+msgid "You are now registered with your school server."
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:379
+msgid "Control Panel"
+msgstr "Kontrollpanel"
+
+#: ../src/view/home/favoritesview.py:390
+msgid "Restart"
+msgstr "Omstart"
+
+#: ../src/view/home/favoritesview.py:395
+msgid "Shutdown"
+msgstr "Slå av"
+
+#: ../src/view/home/favoritesview.py:401
+msgid "Register"
+msgstr "Registrer"
+
+#: ../src/view/palettes.py:42
+msgid "Starting..."
+msgstr "Starter..."
+
+#: ../src/view/palettes.py:72
+msgid "Stop"
+msgstr "Stans"
+
+#: ../src/view/palettes.py:104
+msgid "Start"
+msgstr "Start"
+
+#: ../src/view/palettes.py:132
+msgid "Remove favorite"
+msgstr "Fjern favoritt"
+
+#: ../src/view/palettes.py:136
+#, fuzzy
+msgid "Make favorite"
+msgstr "Lagre som favoritt"
+
+#: ../src/view/palettes.py:185
+msgid "Show contents"
+msgstr "Vis innhold"
+
+#: ../src/view/palettes.py:209
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr "%(free_space)d MB ledig"
diff --git a/shell/po/ne.po b/shell/po/ne.po
new file mode 100644
index 0000000..dd2c9a7
--- /dev/null
+++ b/shell/po/ne.po
@@ -0,0 +1,1267 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-06-11 00:31-0400\n"
+"PO-Revision-Date: 2009-07-27 00:08-0400\n"
+"Last-Translator: Daniel Drake <dsd@laptop.org>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: ne\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Pootle 1.2.1\n"
+
+#: ../extensions/cpsection/aboutme/__init__.py:24
+msgid "About Me"
+msgstr "मेरो बारेमा"
+
+#: ../extensions/cpsection/aboutme/model.py:43
+msgid "You must enter a name."
+msgstr "तिमीले नाम हाल्नै पर्छ।"
+
+#: ../extensions/cpsection/aboutme/model.py:68
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr "ब्रसको धर्को: रङ्ग=%s ह्‍यु‍=%s"
+
+#: ../extensions/cpsection/aboutme/model.py:71
+#, python-format
+msgid "stroke: %s"
+msgstr "ब्रसको धर्का: %s"
+
+#: ../extensions/cpsection/aboutme/model.py:73
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr "भर: रङ्ग=%s ह्‍यु=%s"
+
+#: ../extensions/cpsection/aboutme/model.py:75
+#, python-format
+msgid "fill: %s"
+msgstr "भर: %s"
+
+#: ../extensions/cpsection/aboutme/model.py:86
+msgid "Error in specified color modifiers."
+msgstr "तोकिएको रङ्ग परिमार्जकहरुमा त्रुटी छ।"
+
+#: ../extensions/cpsection/aboutme/model.py:89
+msgid "Error in specified colors."
+msgstr "तोकिएको रङ्गहरुमा त्रुटी छ।"
+
+#: ../extensions/cpsection/aboutme/view.py:94 ../src/jarabe/intro/window.py:92
+msgid "Name:"
+msgstr "नामः"
+
+#: ../extensions/cpsection/aboutme/view.py:128
+msgid "Click to change your color:"
+msgstr "रङ्ग परिवर्तन गर्न क्लिक गरः"
+
+#: ../extensions/cpsection/aboutcomputer/__init__.py:21
+msgid "About my Computer"
+msgstr "मेरो कम्पयूटरको बारेमा"
+
+#: ../extensions/cpsection/aboutcomputer/model.py:28
+msgid "Not available"
+msgstr "उपलब्ध छैन"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:60
+msgid "Identity"
+msgstr "व्यक्तित्ब"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:69
+msgid "Serial Number:"
+msgstr "क्रमिक अङ्क"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:91
+msgid "Software"
+msgstr "सफ्टवेर"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:100
+msgid "Build:"
+msgstr "बनाइ:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:115
+msgid "Sugar:"
+msgstr "सुगर:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:131
+msgid "Firmware:"
+msgstr "फर्मवेर:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:146
+msgid "Wireless Firmware:"
+msgstr "तारबिनाको फर्मवेर:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:169
+msgid "Copyright and License"
+msgstr "कपिराइट र अनुमतिपत्र"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:184
+msgid ""
+"Sugar is the graphical user interface that you are looking at. Sugar is free "
+"software, covered by the GNU General Public License, and you are welcome to "
+"change it and/or distribute copies of it under certain conditions described "
+"therein."
+msgstr ""
+"अहिले तपाईंले हेरिराखेको ग्रफीकल युजर इन्टरफेस सुगर हो। सुगर फ्री सफ्टवेर "
+"हो, सुगर ले जि एन यु जनरल पब्लिक अनुमति पत्र प्रयोग गर्छ र तपाईंले यसलाई "
+"परिवर्तन गरेर नियम अनुसार अरुलाई दिन सक्नुहुन्छ।"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:196
+msgid "Full license:"
+msgstr "सम्पूर्ण अनुमति पत्र:"
+
+#: ../extensions/cpsection/datetime/__init__.py:21
+msgid "Date & Time"
+msgstr "मिती & समय"
+
+#: ../extensions/cpsection/datetime/model.py:87
+msgid "Error timezone does not exist."
+msgstr "त्रुटि, समयक्षेत्र उपलब्ध छैन।"
+
+#: ../extensions/cpsection/datetime/view.py:68 ../data/sugar.schemas.in.h:19
+msgid "Timezone"
+msgstr "समयक्षेत्र"
+
+#: ../extensions/cpsection/frame/__init__.py:21
+msgid "Frame"
+msgstr "चौखट"
+
+#: ../extensions/cpsection/frame/model.py:38
+#: ../extensions/cpsection/frame/model.py:60
+msgid "Value must be an integer."
+msgstr "गुण संख्या हुनु पर्छ।"
+
+#: ../extensions/cpsection/frame/view.py:26
+msgid "never"
+msgstr "कहिलेपनि नगर"
+
+#: ../extensions/cpsection/frame/view.py:27
+msgid "instantaneous"
+msgstr "तात्कालिक"
+
+#: ../extensions/cpsection/frame/view.py:28
+#, python-format
+msgid "%s seconds"
+msgstr "%s सेकेण्ड"
+
+#: ../extensions/cpsection/frame/view.py:52
+msgid "Activation Delay"
+msgstr "सुरु गर्न ढिलाइ"
+
+#: ../extensions/cpsection/frame/view.py:76
+msgid "Corner"
+msgstr "कुना"
+
+#: ../extensions/cpsection/frame/view.py:111
+msgid "Edge"
+msgstr "कुना"
+
+#: ../extensions/cpsection/language/__init__.py:21
+#: ../extensions/cpsection/language/view.py:32
+msgid "Language"
+msgstr "भाषा"
+
+#: ../extensions/cpsection/language/model.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr "~/.i18n. मा पहुँच भएन। मानक सेटिङ्गहरु बनाऊ।"
+
+#: ../extensions/cpsection/language/model.py:124
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr "कोड=%sको भाषा निश्चित गर्न सकिएन।"
+
+#: ../extensions/cpsection/language/model.py:144
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr "माफ गर्नुस, म '%s' बोल्दिन।"
+
+#: ../extensions/cpsection/network/__init__.py:21
+#: ../extensions/cpsection/network/view.py:28
+msgid "Network"
+msgstr "सञ्जाल"
+
+#: ../extensions/cpsection/network/model.py:79
+msgid "State is unknown."
+msgstr "अवस्था अज्ञात छ।"
+
+#: ../extensions/cpsection/network/model.py:105
+msgid "Error in specified radio argument use on/off."
+msgstr "तोकिएको रेडियो निर्देशनमा त्रुटि छ, बन्द/खुला प्रयोग गर।"
+
+#: ../extensions/cpsection/network/model.py:137
+msgid "Error in specified argument use 0/1."
+msgstr "तोकिएको निर्देशनमा त्रुटि छ, 0/1 प्रयोग गर।"
+
+#: ../extensions/cpsection/network/view.py:56
+msgid "Wireless"
+msgstr "तार विना"
+
+#: ../extensions/cpsection/network/view.py:64
+msgid "Turn off the wireless radio to save battery life"
+msgstr "ब्याटरि बचाउन तारबिनाको रेडियो बन्द गर्नुहोस।"
+
+#: ../extensions/cpsection/network/view.py:77
+msgid "Radio"
+msgstr "रेडियो"
+
+#: ../extensions/cpsection/network/view.py:93
+msgid "Discard network history if you have trouble connecting to the network"
+msgstr "सञ्जालमा जोष्न समस्या भए सञ्जालको ईतिहास मेट्नुहोस्"
+
+#: ../extensions/cpsection/network/view.py:102
+msgid "Discard network history"
+msgstr "सञ्जालको ईतिहास मेट्ने"
+
+#: ../extensions/cpsection/network/view.py:115
+msgid "Collaboration"
+msgstr "सहकार्य"
+
+#: ../extensions/cpsection/network/view.py:123
+msgid ""
+"The server is the equivalent of what room you are in; people on the same "
+"server will be able to see each other, even when they aren't on the same "
+"network."
+msgstr ""
+"सर्भर भनेको तिमी बसेको कोठा जस्तो हो, ‍एउटै सर्भरमा भएका XO हरु एक अर्कालाई "
+"देख्न सक्छन्, एउटै नेटवर्कमा नहुन्दा पनि।"
+
+#: ../extensions/cpsection/network/view.py:133
+msgid "Server:"
+msgstr "सर्भर"
+
+#: ../extensions/cpsection/power/__init__.py:21
+msgid "Power"
+msgstr "शक्ति"
+
+#: ../extensions/cpsection/power/model.py:54
+msgid "Error in automatic pm argument, use on/off."
+msgstr "आफैँ चल्ने pm निर्देशनमा त्रुटि छ, बन्द/खुला प्रयोग गर।"
+
+#: ../extensions/cpsection/power/model.py:81
+msgid "Error in extreme pm argument, use on/off."
+msgstr "अन्तिम रुपको pm निर्देशनमा त्रुटि छ, बन्द/खुला प्रयोग गर।"
+
+#: ../extensions/cpsection/power/view.py:47
+msgid "Power management"
+msgstr "शक्तिको प्रभन्धकर्ता"
+
+#: ../extensions/cpsection/power/view.py:57
+msgid "Automatic power management (increases battery life)"
+msgstr "आफैँ चल्ने शक्तिको प्रबन्धकर्ता (ब्याटरिको जीवन बढाउँछ)"
+
+#: ../extensions/cpsection/power/view.py:85
+msgid ""
+"Extreme power management (disableswireless radio, increases battery life)"
+msgstr ""
+"अति किफायत शक्तिको प्रबन्धकर्ता (तार बिनाको रेडियो असमर्थ पार्छ, ब्याटरिको "
+"जीवन बढाउँछ)"
+
+#: ../extensions/deviceicon/battery.py:58
+msgid "My Battery"
+msgstr "मेरो बेटरी"
+
+#: ../extensions/deviceicon/battery.py:137
+msgid "Removed"
+msgstr "हट्यो"
+
+#: ../extensions/deviceicon/battery.py:140
+msgid "Charging"
+msgstr "चार्ज हुँदैछ"
+
+#: ../extensions/deviceicon/battery.py:143
+msgid "Very little power remaining"
+msgstr "एकदमै थोरै शक्‍ति बाँकी छ"
+
+#: ../extensions/deviceicon/battery.py:149
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr "%(hour)d:%(min).2d बाँकी"
+
+#: ../extensions/deviceicon/battery.py:152
+msgid "Charged"
+msgstr "चार्ज भयो"
+
+#: ../extensions/deviceicon/network.py:43
+#, python-format
+msgid "IP address: %s"
+msgstr "आइपी अड्रेस: %s"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#: ../extensions/deviceicon/network.py:109
+msgid "Disconnect..."
+msgstr "जडान विच्छेद..."
+
+#: ../extensions/deviceicon/network.py:113
+msgid "Create new wireless network"
+msgstr ""
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#: ../extensions/deviceicon/network.py:119
+#: ../src/jarabe/desktop/meshbox.py:250
+msgid "Connecting..."
+msgstr "जडान हुदैछ..."
+
+# TODO: show the channel number
+#: ../extensions/deviceicon/network.py:123
+#: ../extensions/deviceicon/network.py:179
+#: ../src/jarabe/desktop/meshbox.py:256
+msgid "Connected"
+msgstr "जडान भयो"
+
+#: ../extensions/deviceicon/network.py:139
+msgid "Channel"
+msgstr "माध्यम"
+
+#: ../extensions/deviceicon/network.py:154
+msgid "Wired Network"
+msgstr "तार युक्त संजाल"
+
+#: ../extensions/deviceicon/network.py:182
+msgid "Speed"
+msgstr "गति"
+
+#: ../extensions/deviceicon/network.py:389
+#, python-format
+msgid "%s's network"
+msgstr ""
+
+#: ../extensions/deviceicon/speaker.py:59
+msgid "My Speakers"
+msgstr "मेरो स्पिकरहरु"
+
+#: ../extensions/deviceicon/speaker.py:133
+msgid "Unmute"
+msgstr "आवाज खोल"
+
+#: ../extensions/deviceicon/speaker.py:136
+msgid "Mute"
+msgstr "आवाज बन्द"
+
+#: ../extensions/globalkey/screenshot.py:56
+msgid "Mesh"
+msgstr "मेश"
+
+#: ../extensions/globalkey/screenshot.py:58
+#: ../src/jarabe/frame/zoomtoolbar.py:38
+msgid "Group"
+msgstr "समुह"
+
+#: ../extensions/globalkey/screenshot.py:60
+#: ../src/jarabe/frame/zoomtoolbar.py:40
+msgid "Home"
+msgstr "गृह"
+
+#: ../extensions/globalkey/screenshot.py:66
+#: ../src/jarabe/frame/zoomtoolbar.py:42
+msgid "Activity"
+msgstr "क्रियाकलाप"
+
+#: ../extensions/globalkey/screenshot.py:69
+msgid "Screenshot"
+msgstr "पर्दाछवि"
+
+#: ../extensions/globalkey/screenshot.py:71
+#, python-format
+msgid "Screenshot of \"%s\""
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:1
+msgid "Backup URL"
+msgstr "बैकअप URL"
+
+#: ../data/sugar.schemas.in.h:2
+msgid ""
+"Color for the XO icon that is used throughout the desktop. The string is "
+"composed of the stroke color and fill color, format is that of rbg colors. "
+"Example: #AC32FF,#9A5200"
+msgstr ""
+"XO आईकनको रंग जुन डेस्कटप भरि प्रयोग गरिन्छ । स्ट्रिङ्ग स्ट्रोक रंग र फिल "
+"रंगले बनेको हो, फर्म्याट rbg रंगको हुन्छ। उदाहरण: #AC32FF,#9A5200"
+
+#: ../data/sugar.schemas.in.h:3
+msgid "Corner Delay"
+msgstr "फ्रेम आउन ढिलाइ"
+
+#: ../data/sugar.schemas.in.h:4
+msgid "Delay for the activation of the frame using the corners."
+msgstr "कनँर प्रयोग गरेर ढांचाको सक्रियतालाई ढिलाइ गर्ने।"
+
+#: ../data/sugar.schemas.in.h:5
+msgid "Delay for the activation of the frame using the edges."
+msgstr "उद्विग्न प्रयोग गरेर ढांचाको सक्रियतालाई ढिलाइ गर्ने।"
+
+#: ../data/sugar.schemas.in.h:6
+msgid "Edge Delay"
+msgstr "उद्विग्न ढिलाइ"
+
+#: ../data/sugar.schemas.in.h:7
+msgid "Favorites Layout"
+msgstr "मनपर्दो रूपरेखा"
+
+#: ../data/sugar.schemas.in.h:8
+msgid "Favorites resume mode"
+msgstr "मनपर्दो पुनरारम्भ विधा"
+
+#: ../data/sugar.schemas.in.h:9
+msgid ""
+"If TRUE, Sugar will make us searchable for the other users of the Jabber "
+"server."
+msgstr ""
+"यदि सही हो भने, सुगरले हामिलाई अरु ज्याबर सर्भरको अरु युजरहरुले खोज्न मिल्छ।"
+
+#: ../data/sugar.schemas.in.h:10
+msgid "Jabber Server"
+msgstr "ज्याबर सर्भर"
+
+#: ../data/sugar.schemas.in.h:11
+msgid "Layout of the favorites view."
+msgstr "मनपर्दो दर्श्यको रूपरेखा।"
+
+#: ../data/sugar.schemas.in.h:12
+msgid "Power Automatic"
+msgstr "स्वचालित शक्ती"
+
+#: ../data/sugar.schemas.in.h:13
+msgid "Power Automatic."
+msgstr "स्वचालित शक्ती."
+
+#: ../data/sugar.schemas.in.h:14
+msgid "Power Extreme"
+msgstr "अत्यधिक शक्ती"
+
+#: ../data/sugar.schemas.in.h:15
+msgid "Power Extreme."
+msgstr "अत्यधिक शक्ती."
+
+#: ../data/sugar.schemas.in.h:16
+msgid "Publish to Gadget"
+msgstr "यन्त्रमा प्रकाशित गर"
+
+#: ../data/sugar.schemas.in.h:17
+msgid "Setting for muting the sound device."
+msgstr "ध्वनि यन्त्रको आवाज बन्द गर्ने सेटिङ्ग।"
+
+#: ../data/sugar.schemas.in.h:18
+msgid "Sound Muted"
+msgstr "आवाज बन्द"
+
+#: ../data/sugar.schemas.in.h:20
+msgid "Timezone setting for the system."
+msgstr "यस सिस्टमको समय अंचल सेटिङ्ग।"
+
+#: ../data/sugar.schemas.in.h:21
+msgid "Url of the jabber server to use."
+msgstr "ज्याबर सर्भर चलाउनको लागि चाहिने Url"
+
+#: ../data/sugar.schemas.in.h:22
+msgid "Url where the backup is saved to."
+msgstr "ब्याकप बचत गर्ने ठाउँको Url"
+
+#: ../data/sugar.schemas.in.h:23
+msgid "User Color"
+msgstr "युजरको रङ्ग"
+
+#: ../data/sugar.schemas.in.h:24
+msgid "User Name"
+msgstr "युजरको नाम"
+
+#: ../data/sugar.schemas.in.h:25
+msgid "User name that is used throughout the desktop."
+msgstr "डेस्कटपभरि प्रयोग हुने युजरको नाम।"
+
+#: ../data/sugar.schemas.in.h:26
+msgid "Volume Level"
+msgstr "आवाज सतह"
+
+#: ../data/sugar.schemas.in.h:27
+msgid "Volume level for the sound device."
+msgstr "आवाज सतह यन्त्रको लागि।"
+
+#: ../data/sugar.schemas.in.h:28
+msgid ""
+"When in resume mode, clicking on a favorite icon will cause the last entry "
+"for that activity to be resumed."
+msgstr ""
+"पुनरारम्भ मोडमा भएको बेलामा कृपापात्रमा क्लिक गरेमा क्रियाकलापको अन्तिम "
+"प्रविष्टि पुनरारम्भ हुन्छ।"
+
+#: ../src/jarabe/controlpanel/cmd.py:28
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+"सुगर-कन्ट्रोल-प्यानल: सावधान, त्यही नाम भएको एउटा भन्दा धेरै रोजाई भेटियो: %"
+"s मोडियुल: %r"
+
+#: ../src/jarabe/controlpanel/cmd.py:30
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr "सुगर-कन्ट्रोल-प्यानल: key=%s उपलब्ध छैन"
+
+#: ../src/jarabe/controlpanel/cmd.py:31
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr "सुगर-कन्ट्रोल-प्यानल: %s"
+
+# TRANS: Translators, there's a empty line at the end of this string,
+# which must appear in the translated string (msgstr) as well.
+#. TRANS: Translators, there's a empty line at the end of this string,
+#. which must appear in the translated string (msgstr) as well.
+#: ../src/jarabe/controlpanel/cmd.py:37
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" -c key clear the current value for the key \n"
+" "
+msgstr ""
+"प्रयोग: सुगर-कन्ट्रोल-प्यानल [ option ] कि [ args ... ] \n"
+" सुगर वातावरणको लागी कन्ट्रोल।\n"
+" Option: \n"
+" -h यो सहयोग सन्देश देखाउ र निस्क \n"
+" -l भएका सबै सेवाहरु सुचिमा देखाउ \n"
+" -h कि यो कि सम्बन्धि जानकारी देखाउ \n"
+" -g कि किको अहिलेको मान लिउ \n"
+" -s कि किको लागी अहिलेको मान राख \n"
+" -c कि किको लागी अहिलेको मान सफा गर \n"
+" "
+
+#: ../src/jarabe/controlpanel/cmd.py:50
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr "परिवर्तन लागू गर्न सुगर पुन:शुरु गर्नु पर्छ । \n"
+
+#: ../src/jarabe/controlpanel/gui.py:275
+msgid "Warning"
+msgstr "सावधान"
+
+#: ../src/jarabe/controlpanel/gui.py:276
+#: ../src/jarabe/controlpanel/sectionview.py:42
+msgid "Changes require restart"
+msgstr "परिवर्तनका लागी पुण: सुरु गर्न आवश्यक छ"
+
+#: ../src/jarabe/controlpanel/gui.py:279
+msgid "Cancel changes"
+msgstr "परिवर्तनहरु रद्द गर"
+
+#: ../src/jarabe/controlpanel/gui.py:284 ../src/jarabe/desktop/homebox.py:113
+msgid "Later"
+msgstr "पछी"
+
+#: ../src/jarabe/controlpanel/gui.py:288
+msgid "Restart now"
+msgstr "पुन: सुरु गर"
+
+#: ../src/jarabe/controlpanel/toolbar.py:61 ../src/jarabe/intro/window.py:188
+msgid "Done"
+msgstr "भयो"
+
+#: ../src/jarabe/controlpanel/toolbar.py:115
+#: ../src/jarabe/desktop/homebox.py:111
+#: ../src/jarabe/frame/activitiestray.py:726
+#: ../src/jarabe/frame/activitiestray.py:821
+#: ../src/jarabe/frame/activitiestray.py:849
+msgid "Cancel"
+msgstr "रद्द"
+
+#: ../src/jarabe/controlpanel/toolbar.py:121
+#: ../src/jarabe/desktop/favoritesview.py:339
+msgid "Ok"
+msgstr "हुन्छ"
+
+# TRANS: label for the freeform layout in the favorites view
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:116
+msgid "Freeform"
+msgstr "स्वतन्त्र आकारको"
+
+# TRANS: label for the ring layout in the favorites view
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:198
+msgid "Ring"
+msgstr "औँठी"
+
+# TRANS: label for the spiral layout in the favorites view
+#. TRANS: label for the spiral layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:334
+msgid "Spiral"
+msgstr "पेंचदार"
+
+# TRANS: label for the box layout in the favorites view
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:401
+msgid "Box"
+msgstr "बाकस"
+
+# TRANS: label for the box layout in the favorites view
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:442
+msgid "Triangle"
+msgstr "त्रिकोण"
+
+#: ../src/jarabe/desktop/favoritesview.py:330
+msgid "Registration Failed"
+msgstr "दर्ता असफल भयो"
+
+#: ../src/jarabe/desktop/favoritesview.py:331
+#, python-format
+msgid "%s"
+msgstr "%s"
+
+#: ../src/jarabe/desktop/favoritesview.py:333
+msgid "Registration Successful"
+msgstr "दर्ता सफल"
+
+#: ../src/jarabe/desktop/favoritesview.py:334
+msgid "You are now registered with your school server."
+msgstr "अब तपाईँको नाम बिद्यालयको सर्भरमा दर्ता भइसक्यो।"
+
+#: ../src/jarabe/desktop/favoritesview.py:674
+msgid "Register"
+msgstr "दर्ता"
+
+#: ../src/jarabe/desktop/homebox.py:67
+msgid "Confirm erase"
+msgstr "साँच्चै मेट्ने"
+
+#: ../src/jarabe/desktop/homebox.py:69
+#, python-format
+msgid "Confirm erase: Do you want to permanently erase %s?"
+msgstr "साँच्छै मेट्ने: %s लाई सधैँको लागी मेट्न चहान्छौ?"
+
+# self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+# TODO: Implement stopping downloads
+# self._stop_item.connect('activate', self._stop_item_activate_cb)
+# self.append_menu_item(self._stop_item)
+#: ../src/jarabe/desktop/homebox.py:73 ../src/jarabe/frame/clipboardmenu.py:62
+#: ../src/jarabe/view/viewsource.py:218
+msgid "Keep"
+msgstr "राख"
+
+#: ../src/jarabe/desktop/homebox.py:76
+#: ../src/jarabe/journal/journaltoolbox.py:357
+#: ../src/jarabe/journal/palettes.py:112 ../src/jarabe/view/palettes.py:153
+msgid "Erase"
+msgstr "मेट"
+
+#: ../src/jarabe/desktop/homebox.py:106
+msgid "Software Update"
+msgstr "सॉफ्टवेयर आधुनिकीकरण"
+
+#: ../src/jarabe/desktop/homebox.py:107
+msgid "Update your activities to ensure compatibility with your new software"
+msgstr "नया सफ्टवेर चलाउनका निमीत्त तिम्रो क्रियाकलापहरु अप्डेट गर"
+
+#: ../src/jarabe/desktop/homebox.py:116
+msgid "Check now"
+msgstr "अहिले जाँच गर"
+
+#: ../src/jarabe/desktop/homebox.py:233
+msgid "List view"
+msgstr "सुची दृश्य"
+
+#: ../src/jarabe/desktop/homebox.py:234
+msgid "<Ctrl>2"
+msgstr "<Ctrl>२"
+
+#: ../src/jarabe/desktop/homebox.py:296
+msgid "Favorites view"
+msgstr "मन्पर्दो दृश्य"
+
+#: ../src/jarabe/desktop/homebox.py:297
+msgid "<Ctrl>1"
+msgstr "<Ctrl>१"
+
+#: ../src/jarabe/desktop/keydialog.py:131
+msgid "Key Type:"
+msgstr "कुञ्जी प्रकार:"
+
+#: ../src/jarabe/desktop/keydialog.py:151
+msgid "Authentication Type:"
+msgstr "प्रमाणीकरण प्रकार:"
+
+#: ../src/jarabe/desktop/keydialog.py:215
+msgid "WPA & WPA2 Personal"
+msgstr "WPA र WPA2 निजी"
+
+#: ../src/jarabe/desktop/keydialog.py:224
+msgid "Wireless Security:"
+msgstr "तारबिनाको लागि सुरक्षा"
+
+#: ../src/jarabe/desktop/meshbox.py:134
+msgid "Connect"
+msgstr "जोड"
+
+#: ../src/jarabe/desktop/meshbox.py:138
+msgid "Disconnect"
+msgstr "विच्छेद भयो"
+
+# TRANS: Action label for resuming an activity.
+#. TRANS: Action label for resuming an activity.
+#: ../src/jarabe/desktop/meshbox.py:449
+#: ../src/jarabe/frame/activitiestray.py:761
+#: ../src/jarabe/journal/journaltoolbox.py:425
+#: ../src/jarabe/journal/palettes.py:72 ../src/jarabe/view/palettes.py:66
+msgid "Resume"
+msgstr "पुनरारम्भ"
+
+#: ../src/jarabe/desktop/meshbox.py:454
+#: ../src/jarabe/frame/activitiestray.py:235
+msgid "Join"
+msgstr "सहभागी होऊ"
+
+#: ../src/jarabe/desktop/schoolserver.py:34
+msgid "Cannot obtain data needed for registration."
+msgstr "रजिस्टरको लागि चाइने तथ्याङ्क प्राप्त गर्न सकिएन।"
+
+#: ../src/jarabe/desktop/schoolserver.py:51
+msgid "Cannot connect to the server."
+msgstr "सर्भरमा जड़ान हुन सकेन।"
+
+#: ../src/jarabe/desktop/schoolserver.py:56
+msgid "The server could not complete the request."
+msgstr "सर्भरले अनुरोध पुरा गर्न सकेन।"
+
+#: ../src/jarabe/frame/activitiestray.py:240
+#: ../src/jarabe/frame/activitiestray.py:698
+msgid "Decline"
+msgstr "नाई"
+
+#: ../src/jarabe/frame/activitiestray.py:650
+#, python-format
+msgid "%dB"
+msgstr "%dB"
+
+#: ../src/jarabe/frame/activitiestray.py:652
+#, python-format
+msgid "%dKB"
+msgstr "%dKB"
+
+#: ../src/jarabe/frame/activitiestray.py:654
+#, python-format
+msgid "%dMB"
+msgstr "%dMB"
+
+#: ../src/jarabe/frame/activitiestray.py:671
+#, python-format
+msgid "%s of %s"
+msgstr "%s को %s"
+
+#: ../src/jarabe/frame/activitiestray.py:683
+#, python-format
+msgid "Transfer from %r"
+msgstr "%r बाट सार्नुहोस"
+
+#: ../src/jarabe/frame/activitiestray.py:693
+msgid "Accept"
+msgstr "स्विकार"
+
+#: ../src/jarabe/frame/activitiestray.py:716
+#: ../src/jarabe/frame/activitiestray.py:839
+#, python-format
+msgid "%s (%s)"
+msgstr "%s (%s)"
+
+#: ../src/jarabe/frame/activitiestray.py:750
+#: ../src/jarabe/frame/activitiestray.py:873
+msgid "Dismiss"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:810
+#, python-format
+msgid "Transfer to %r"
+msgstr "%r मा सार्नुहोस"
+
+#: ../src/jarabe/frame/clipboardmenu.py:52
+msgid "Remove"
+msgstr "हटाऊ"
+
+#: ../src/jarabe/frame/clipboardmenu.py:57
+#: ../src/jarabe/frame/clipboardmenu.py:80
+msgid "Open"
+msgstr "खोल"
+
+#: ../src/jarabe/frame/clipboardmenu.py:85
+msgid "Open with"
+msgstr "साथ खोल"
+
+#: ../src/jarabe/frame/clipboardobject.py:49
+#, python-format
+msgid "%s clipping"
+msgstr "%s क्लिपिङ"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:36
+msgid "Neighborhood"
+msgstr "छिमेक"
+
+#: ../src/jarabe/intro/window.py:124
+msgid "Click to change color:"
+msgstr "रङ्ग परिवर्तन गर्न क्लिक गरः"
+
+#: ../src/jarabe/intro/window.py:174 ../src/jarabe/journal/detailview.py:103
+msgid "Back"
+msgstr "पछाडि"
+
+#: ../src/jarabe/intro/window.py:191
+msgid "Next"
+msgstr "अर्को"
+
+#: ../src/jarabe/journal/collapsedentry.py:258
+#: ../src/jarabe/journal/expandedentry.py:159
+#: ../src/jarabe/journal/palettes.py:66
+msgid "Untitled"
+msgstr "बिना शिर्षक"
+
+#: ../src/jarabe/journal/expandedentry.py:205
+msgid "No preview"
+msgstr "पूर्वावलोकन छैन"
+
+#: ../src/jarabe/journal/expandedentry.py:224
+msgid "Participants:"
+msgstr "भागिदारहरु"
+
+#: ../src/jarabe/journal/expandedentry.py:247
+msgid "Description:"
+msgstr "वर्णनः"
+
+#: ../src/jarabe/journal/expandedentry.py:273
+msgid "Tags:"
+msgstr "चिनोहरु:"
+
+#: ../src/jarabe/journal/journalactivity.py:108
+#: ../src/jarabe/journal/volumestoolbar.py:47
+msgid "Journal"
+msgstr "पंजिका"
+
+#: ../src/jarabe/journal/journaltoolbox.py:65
+msgid "Search"
+msgstr "खोज"
+
+#: ../src/jarabe/journal/journaltoolbox.py:124
+msgid "Anytime"
+msgstr "कुनैबेला"
+
+#: ../src/jarabe/journal/journaltoolbox.py:126
+msgid "Today"
+msgstr "आज"
+
+#: ../src/jarabe/journal/journaltoolbox.py:128
+msgid "Since yesterday"
+msgstr "हिजो देखि"
+
+# TRANS: Filter entries modified during the last 7 days.
+#. TRANS: Filter entries modified during the last 7 days.
+#: ../src/jarabe/journal/journaltoolbox.py:130
+msgid "Past week"
+msgstr "गत हप्ता"
+
+# TRANS: Filter entries modified during the last 30 days.
+#. TRANS: Filter entries modified during the last 30 days.
+#: ../src/jarabe/journal/journaltoolbox.py:132
+msgid "Past month"
+msgstr "गत महिना"
+
+# TRANS: Filter entries modified during the last 356 days.
+#. TRANS: Filter entries modified during the last 356 days.
+#: ../src/jarabe/journal/journaltoolbox.py:134
+msgid "Past year"
+msgstr "गत वर्ष"
+
+#: ../src/jarabe/journal/journaltoolbox.py:141
+msgid "Anyone"
+msgstr "जो सुकै"
+
+#: ../src/jarabe/journal/journaltoolbox.py:143
+msgid "My friends"
+msgstr "मेरा साथीहरु"
+
+#: ../src/jarabe/journal/journaltoolbox.py:144
+msgid "My class"
+msgstr "मेरो कक्षा"
+
+# TRANS: Item in a combo box that filters by entry type.
+#. TRANS: Item in a combo box that filters by entry type.
+#: ../src/jarabe/journal/journaltoolbox.py:271
+msgid "Anything"
+msgstr "कुनैपनि"
+
+# TODO: Add "Start with" menu item
+#: ../src/jarabe/journal/journaltoolbox.py:347
+#: ../src/jarabe/journal/palettes.py:90
+msgid "Copy"
+msgstr "प्रतिलिपि"
+
+# TRANS: Action label for starting an entry.
+#. TRANS: Action label for starting an entry.
+#: ../src/jarabe/journal/journaltoolbox.py:428
+#: ../src/jarabe/journal/palettes.py:75 ../src/jarabe/view/palettes.py:135
+msgid "Start"
+msgstr "सुरु गर"
+
+#: ../src/jarabe/journal/listview.py:40
+msgid "Your Journal is empty"
+msgstr "तिम्रो पंजिका खाली छ"
+
+#: ../src/jarabe/journal/listview.py:41
+msgid "No matching entries "
+msgstr "मिल्ने लेखा छैन "
+
+#: ../src/jarabe/journal/listview.py:370
+msgid "Clear search"
+msgstr "खोजलाई सफा गर"
+
+#: ../src/jarabe/journal/misc.py:93
+msgid "No date"
+msgstr "मिति छैन"
+
+#: ../src/jarabe/journal/modalalert.py:63
+msgid "Your Journal is full"
+msgstr "तिम्रो पंजिका भरिएको छ"
+
+#: ../src/jarabe/journal/modalalert.py:67
+msgid "Please delete some old Journal entries to make space for new ones."
+msgstr "क्रिपया कुनै पुरानो पंजिकाहरु हटाएर नया पंजिकाहरु को लागि ठाउ बनाउ।"
+
+#: ../src/jarabe/journal/modalalert.py:79
+msgid "Show Journal"
+msgstr "पंजिका देखाउ"
+
+#: ../src/jarabe/journal/objectchooser.py:147
+msgid "Choose an object"
+msgstr "वस्तु छान"
+
+#: ../src/jarabe/journal/objectchooser.py:152
+#: ../src/jarabe/view/viewsource.py:308
+msgid "Close"
+msgstr "बन्द"
+
+#: ../src/jarabe/journal/palettes.py:73
+msgid "Resume with"
+msgstr "पुनरारम्भ सहित"
+
+#: ../src/jarabe/journal/palettes.py:76
+msgid "Start with"
+msgstr "शुरु सहित"
+
+#: ../src/jarabe/journal/palettes.py:98
+msgid "Send to"
+msgstr "पठाउनु"
+
+#: ../src/jarabe/journal/palettes.py:107
+msgid "View Details"
+msgstr "वृत्तांत हेर"
+
+#: ../src/jarabe/journal/palettes.py:185
+msgid "No friends present"
+msgstr "कुनै पनि साथीहरु हाजीर छैन"
+
+#: ../src/jarabe/journal/palettes.py:190
+msgid "No valid connection found"
+msgstr "कुनै पनि सदर संयोग भेटिएन"
+
+#: ../src/jarabe/journal/palettes.py:218
+msgid "No activity to resume entry"
+msgstr "पुनरारम्भ गर्न कुनै क्रियाकलाप छैन"
+
+#: ../src/jarabe/journal/palettes.py:220
+msgid "No activity to start entry"
+msgstr "सुरु गर्न कुनै क्रियाकलाप छैन"
+
+#: ../src/jarabe/view/buddymenu.py:62
+msgid "Remove friend"
+msgstr "साथी हटाऊ"
+
+#: ../src/jarabe/view/buddymenu.py:65
+msgid "Make friend"
+msgstr "साथी बनाऊ"
+
+#: ../src/jarabe/view/buddymenu.py:82
+msgid "My Settings"
+msgstr "मेरो योजनाहरु"
+
+#: ../src/jarabe/view/buddymenu.py:90
+msgid "Logout"
+msgstr "लगआउट"
+
+#: ../src/jarabe/view/buddymenu.py:95
+msgid "Restart"
+msgstr "पून: सुरु"
+
+#: ../src/jarabe/view/buddymenu.py:100
+msgid "Shutdown"
+msgstr "बन्द"
+
+#: ../src/jarabe/view/buddymenu.py:135
+#, python-format
+msgid "Invite to %s"
+msgstr "%s लाई निम्ता देऊ"
+
+#: ../src/jarabe/view/palettes.py:47
+msgid "Starting..."
+msgstr "शुरु हुदैछ..."
+
+#. TODO: share-with, keep
+#: ../src/jarabe/view/palettes.py:73
+msgid "View Source"
+msgstr "स्रोत हेर"
+
+#: ../src/jarabe/view/palettes.py:84
+msgid "Stop"
+msgstr "बन्द गर"
+
+#: ../src/jarabe/view/palettes.py:174
+msgid "Remove favorite"
+msgstr "प्रिय हटाउ"
+
+#: ../src/jarabe/view/palettes.py:178
+msgid "Make favorite"
+msgstr "प्रिय बनाउ"
+
+#: ../src/jarabe/view/palettes.py:241
+msgid "Show contents"
+msgstr "वस्तुहरु देखाउ"
+
+#: ../src/jarabe/view/palettes.py:263 ../src/jarabe/view/palettes.py:313
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr "%(free_space)d MB खालि"
+
+#: ../src/jarabe/view/palettes.py:288
+msgid "Unmount"
+msgstr "हटाऊ"
+
+#: ../src/jarabe/view/viewsource.py:208
+msgid "Instance Source"
+msgstr "दृष्टांत मुल"
+
+#: ../src/jarabe/view/viewsource.py:233
+msgid "Source"
+msgstr "मुल"
+
+#: ../src/jarabe/view/viewsource.py:292
+msgid "Activity Bundle Source"
+msgstr "क्रियाकलाप बन्डलको मुल"
+
+#: ../src/jarabe/view/viewsource.py:299
+#, python-format
+msgid "View source: %r"
+msgstr "स्रोत हेर : %r"
+
+#~ msgid ""
+#~ "© 2008 One Laptop per Child Association Inc; Red Hat Inc; and Contributors."
+#~ msgstr ""
+#~ "© २००८ वान ल्यप् टप पर चाईल्ड असोसिएसन आइ एन सि; रेड ह्यट आइ एन सि; र अरु "
+#~ "निर्माताहरु।"
+
+#~ msgid "Document"
+#~ msgstr "कागतपत्र"
+
+#~ msgid "Resume by default"
+#~ msgstr "पुनरारम्भद्वारा बकाया"
+
+#~ msgid "Encryption Type:"
+#~ msgstr "गुप्तीकरण प्रकार:"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#, fuzzy
+#~ msgid "Disconnecting..."
+#~ msgstr "जडान विच्छेद"
+
+#~ msgid "Mesh Network"
+#~ msgstr "मेश सञ्जाल"
+
+#~ msgid "Disconnected"
+#~ msgstr "विच्छेद भयो"
+
+#~ msgid "About my XO"
+#~ msgstr "मिरो XO बारे"
+
+#~ msgid "Connected to a School Mesh Portal"
+#~ msgstr "विद्यालय मेश पोर्टलमा जडान भयो"
+
+#~ msgid "Looking for a School Mesh Portal..."
+#~ msgstr "विद्यालय मेश पोर्टल खोज्दै..."
+
+#~ msgid "Connected to an XO Mesh Portal"
+#~ msgstr "XO मेश पोर्टलमा जडान भयो"
+
+#~ msgid "Looking for an XO Mesh Portal..."
+#~ msgstr "XO मेश पोर्टल खोज्दै..."
+
+#~ msgid "Connected to a Simple Mesh"
+#~ msgstr "साधारण मेशमा जडान भयो"
+
+#~ msgid "Starting a Simple Mesh"
+#~ msgstr "साधारण मेश शुरु गर्दै"
+
+#~ msgid "Unknown Mesh"
+#~ msgstr "अज्ञात मेश"
+
+#, python-format
+#~ msgid "Clipboard object: %s."
+#~ msgstr "क्लिपपाटी वस्तु: %s."
+
+#~ msgid "You must enter a server."
+#~ msgstr "तिमिले सर्भर हाल्नु पर्छ"
+
+#~ msgid "Control Panel"
+#~ msgstr "नियन्त्रण चौकोस"
+
+#~ msgid "off"
+#~ msgstr "बन्द"
+
+#~ msgid "on"
+#~ msgstr "खुला"
+
+#~ msgid "Permission denied. You need to be root to run this method."
+#~ msgstr "अनुमति दिईएन। यो काम गर्न तिमी रुट हुनुपर्छ।"
+
+#~ msgid "Error in reading timezone"
+#~ msgstr "समयक्षेत्र पढ्दा त्रुटि भयो"
+
+#, python-format
+#~ msgid "Error copying timezone (from %s): %s"
+#~ msgstr "समयक्षेत्र प्रतिलिपि गर्दा त्रुटि भयो (%s बाट): %s"
+
+#, python-format
+#~ msgid "Changing permission of timezone: %s"
+#~ msgstr "%s समयक्षेत्रको अनुमति परिवर्तन गर्दै "
+
+#~ msgid "About this XO"
+#~ msgstr "यो XO को बारेमा"
+
+#~ msgid "Add to journal"
+#~ msgstr "खातामा राख"
+
+#~ msgid "Reboot"
+#~ msgstr "बन्द गरि फेरि खोल"
+
+#~ msgid "My Battery life"
+#~ msgstr "मेरो बेटरी"
+
+#~ msgid "Battery charging"
+#~ msgstr "बेटरी चार्ज हुँदै"
+
+#~ msgid "Battery discharging"
+#~ msgstr "बेटरी सकिदै छ"
+
+#~ msgid "Battery fully charged"
+#~ msgstr "बेटरीमा पूरा चार्ज छ"
+
+#~ msgid "Share with:"
+#~ msgstr "साझा गर:"
+
+#~ msgid "Private"
+#~ msgstr "निजी"
+
+#~ msgid "My Neighborhood"
+#~ msgstr "मेरो छिमेक"
+
+#~ msgid "Undo"
+#~ msgstr "पूर्वस्थिति"
+
+#~ msgid "Redo"
+#~ msgstr "नयाँस्थिति"
+
+#~ msgid "Paste"
+#~ msgstr "टाँस"
+
+#, python-format
+#~ msgid "%s Activity"
+#~ msgstr "%s क्रियाकलाप"
+
+#~ msgid "Keep error"
+#~ msgstr "त्रुटि राख"
+
+#~ msgid "Keep error: all changes will be lost"
+#~ msgstr "त्रुटि राख: सवै परिवर्तनहरु हराउनेछन्"
+
+#~ msgid "Don't stop"
+#~ msgstr "नरोक"
+
+#~ msgid "Stop anyway"
+#~ msgstr "जसरी पनि रोक"
+
+#~ msgid "Continue"
+#~ msgstr "जारी राख"
+
+#~ msgid "OK"
+#~ msgstr "हुन्छ"
+
+#, python-format
+#~ msgid "%d year"
+#~ msgstr "%d वर्ष"
+
+#, python-format
+#~ msgid "%d years"
+#~ msgstr "%d वर्ष"
+
+#, python-format
+#~ msgid "%d month"
+#~ msgstr "%d महिना"
+
+#, python-format
+#~ msgid "%d months"
+#~ msgstr "%d महिना"
+
+#, python-format
+#~ msgid "%d week"
+#~ msgstr "%d हप्ता"
+
+#, python-format
+#~ msgid "%d weeks"
+#~ msgstr "%d हप्ता"
+
+#, python-format
+#~ msgid "%d day"
+#~ msgstr "%d दिन"
+
+#, python-format
+#~ msgid "%d days"
+#~ msgstr "%d दिन"
+
+#, python-format
+#~ msgid "%d hour"
+#~ msgstr "%d घण्टा"
+
+#, python-format
+#~ msgid "%d hours"
+#~ msgstr "%d घण्टा"
+
+#, python-format
+#~ msgid "%d minute"
+#~ msgstr "%d मिनेट"
+
+#, python-format
+#~ msgid "%d minutes"
+#~ msgstr "%d मिनेट"
+
+#, python-format
+#~ msgid "%d second"
+#~ msgstr "%d सेकेण्ड"
+
+#~ msgid " and "
+#~ msgstr "_र_"
+
+#~ msgid ", "
+#~ msgstr ",_"
diff --git a/shell/po/nl.po b/shell/po/nl.po
new file mode 100644
index 0000000..bf437de
--- /dev/null
+++ b/shell/po/nl.po
@@ -0,0 +1,1556 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-06-08 00:31-0400\n"
+"PO-Revision-Date: 2010-08-07 21:55+0200\n"
+"Last-Translator: Myckel Habets <myckel@sdf.lonestar.org>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: nl\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Pootle 2.0.3\n"
+
+#: ../extensions/cpsection/aboutme/__init__.py:24
+msgid "About Me"
+msgstr "Over mij"
+
+#: ../extensions/cpsection/aboutme/model.py:43
+msgid "You must enter a name."
+msgstr "Je moet een naam invoeren."
+
+#: ../extensions/cpsection/aboutme/model.py:68
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr "streep: kleur=%s tint=%s"
+
+#: ../extensions/cpsection/aboutme/model.py:71
+#, python-format
+msgid "stroke: %s"
+msgstr "streep: %s"
+
+#: ../extensions/cpsection/aboutme/model.py:73
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr "vulling: kleur=%s tint=%s"
+
+#: ../extensions/cpsection/aboutme/model.py:75
+#, python-format
+msgid "fill: %s"
+msgstr "vulling: %s"
+
+#: ../extensions/cpsection/aboutme/model.py:86
+msgid "Error in specified color modifiers."
+msgstr "Fout in opgegeven kleurenmodificaties."
+
+#: ../extensions/cpsection/aboutme/model.py:89
+msgid "Error in specified colors."
+msgstr "Fout in opgegeven kleuren."
+
+#: ../extensions/cpsection/aboutme/view.py:94 ../src/jarabe/intro/window.py:93
+msgid "Name:"
+msgstr "Naam:"
+
+#: ../extensions/cpsection/aboutme/view.py:128
+msgid "Click to change your color:"
+msgstr "Klik om de kleur te veranderen:"
+
+#: ../extensions/cpsection/aboutcomputer/__init__.py:21
+msgid "About my Computer"
+msgstr "Over mijn computer"
+
+#: ../extensions/cpsection/aboutcomputer/model.py:28
+msgid "Not available"
+msgstr "Niet beschikbaar"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:60
+msgid "Identity"
+msgstr "Identiteit"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:69
+msgid "Serial Number:"
+msgstr "Serienummer:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:91
+msgid "Software"
+msgstr "Software"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:100
+msgid "Build:"
+msgstr "Bouw:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:115
+msgid "Sugar:"
+msgstr "Sugar:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:131
+msgid "Firmware:"
+msgstr "Firmware:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:146
+msgid "Wireless Firmware:"
+msgstr "Firmware draadloos netwerk:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:169
+msgid "Copyright and License"
+msgstr "Copyright en licentie"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:184
+msgid ""
+"Sugar is the graphical user interface that you are looking at. Sugar is free "
+"software, covered by the GNU General Public License, and you are welcome to "
+"change it and/or distribute copies of it under certain conditions described "
+"therein."
+msgstr ""
+"Sugar is de grafische gebruikersinterface waar je momenteel naar kijkt. "
+"Sugar is vrije software, uitgegeven onder de GNU General Public License, en "
+"je mag het aanpassen en/of kopieën distribueren onder de condities zoals "
+"vermeld in de licentie."
+
+#: ../extensions/cpsection/aboutcomputer/view.py:196
+msgid "Full license:"
+msgstr "Volledige licentie:"
+
+#: ../extensions/cpsection/datetime/__init__.py:21
+msgid "Date & Time"
+msgstr "Datum en Tijd"
+
+#: ../extensions/cpsection/datetime/model.py:87
+msgid "Error timezone does not exist."
+msgstr "Fout tijdzone bestaat niet."
+
+#: ../extensions/cpsection/datetime/view.py:68 ../data/sugar.schemas.in.h:47
+msgid "Timezone"
+msgstr "Tijdzone"
+
+#: ../extensions/cpsection/frame/__init__.py:21
+msgid "Frame"
+msgstr "Kader"
+
+#: ../extensions/cpsection/frame/model.py:38
+#: ../extensions/cpsection/frame/model.py:60
+msgid "Value must be an integer."
+msgstr "Waarde moet een geheel getal zijn."
+
+#: ../extensions/cpsection/frame/view.py:26
+msgid "never"
+msgstr "nooit"
+
+#: ../extensions/cpsection/frame/view.py:27
+msgid "instantaneous"
+msgstr "direct"
+
+#: ../extensions/cpsection/frame/view.py:28
+#, python-format
+msgid "%s seconds"
+msgstr "%s seconden"
+
+#: ../extensions/cpsection/frame/view.py:52
+msgid "Activation Delay"
+msgstr "Activatievertraging"
+
+#: ../extensions/cpsection/frame/view.py:76
+msgid "Corner"
+msgstr "Hoek"
+
+#: ../extensions/cpsection/frame/view.py:111
+msgid "Edge"
+msgstr "Rand"
+
+#: ../extensions/cpsection/keyboard/__init__.py:21
+#: ../extensions/cpsection/keyboard/view.py:31
+msgid "Keyboard"
+msgstr "Toetsenbord"
+
+#: ../extensions/cpsection/keyboard/view.py:189
+msgid "Keyboard Model"
+msgstr "Toetsenbordmodel"
+
+#: ../extensions/cpsection/keyboard/view.py:248
+msgid "Key(s) to change layout"
+msgstr "Toets(en) om de indeling te veranderen"
+
+#: ../extensions/cpsection/keyboard/view.py:318
+msgid "Keyboard Layout(s)"
+msgstr "Toetsenbordindeling(en)"
+
+#: ../extensions/cpsection/language/__init__.py:21
+#: ../extensions/cpsection/language/view.py:33
+msgid "Language"
+msgstr "Taal"
+
+#: ../extensions/cpsection/language/model.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr "Kon niet bij ~/.i18n komen. Standaard instellingen aanmaken."
+
+#: ../extensions/cpsection/language/model.py:124
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr "Taal voor code=%s kon niet bepaald worden."
+
+#: ../extensions/cpsection/language/model.py:144
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr "Sorry ik spreek geen '%s'."
+
+#: ../extensions/cpsection/language/view.py:56
+msgid ""
+"Add languages in the order you prefer. If a translation is not available, "
+"the next in the list will be used."
+msgstr ""
+"Voeg talen toe in de volgorde die je wenst. Als een vertaling niet "
+"beschikbaar is, zal de volgende in de lijst gebruikt worden."
+
+#: ../extensions/cpsection/modemconfiguration/__init__.py:21
+msgid "Modem Configuration"
+msgstr "Modem configuratie"
+
+#: ../extensions/cpsection/modemconfiguration/view.py:91
+msgid "Username:"
+msgstr "Gebruikersnaam:"
+
+#: ../extensions/cpsection/modemconfiguration/view.py:102
+msgid "Password:"
+msgstr "Wachtwoord:"
+
+#: ../extensions/cpsection/modemconfiguration/view.py:113
+msgid "Number:"
+msgstr "Getal:"
+
+#: ../extensions/cpsection/modemconfiguration/view.py:124
+msgid "Access Point Name (APN):"
+msgstr "Access Point Naam (APN):"
+
+#: ../extensions/cpsection/modemconfiguration/view.py:135
+msgid "Personal Identity Number (PIN):"
+msgstr "Persoonlijk Identiteitsnummer (PIN):"
+
+#: ../extensions/cpsection/modemconfiguration/view.py:146
+msgid "Personal Unblocking Key (PUK):"
+msgstr "Persoonlijke Ontsluitingssleutel (PUK):"
+
+#: ../extensions/cpsection/modemconfiguration/view.py:167
+msgid ""
+"You will need to provide the following information to set up a mobile "
+"broadband connection to a cellular (3G) network."
+msgstr ""
+"Je moet de volgende informatie opgeven om een mobiele breedbandverbinding "
+"via een mobiel (3G) netwerk op te zetten."
+
+#: ../extensions/cpsection/network/__init__.py:21
+#: ../extensions/cpsection/network/view.py:28
+msgid "Network"
+msgstr "Netwerk"
+
+#: ../extensions/cpsection/network/model.py:79
+msgid "State is unknown."
+msgstr "Status is onbekend."
+
+#: ../extensions/cpsection/network/model.py:105
+msgid "Error in specified radio argument use on/off."
+msgstr "Fout in opgegeven keuze argument gebruik aan/uit."
+
+#: ../extensions/cpsection/network/model.py:137
+msgid "Error in specified argument use 0/1."
+msgstr "Fout in opgegeven keuze argument gebruik 0/1."
+
+#: ../extensions/cpsection/network/view.py:59
+msgid "Wireless"
+msgstr "Draadloos"
+
+#: ../extensions/cpsection/network/view.py:67
+msgid "Turn off the wireless radio to save battery life"
+msgstr "Draadloze zender uitzetten om de batterij te besparen"
+
+#: ../extensions/cpsection/network/view.py:80
+msgid "Radio"
+msgstr "Zender"
+
+#: ../extensions/cpsection/network/view.py:96
+msgid "Discard network history if you have trouble connecting to the network"
+msgstr ""
+"Gooi de netwerkgeschiedenis weg als je problemen hebt om met het netwerk te "
+"verbinden"
+
+#: ../extensions/cpsection/network/view.py:105
+msgid "Discard network history"
+msgstr "Netwerkgeschiedenis weggooien"
+
+#: ../extensions/cpsection/network/view.py:118
+msgid "Collaboration"
+msgstr "Samenwerking"
+
+#: ../extensions/cpsection/network/view.py:126
+msgid ""
+"The server is the equivalent of what room you are in; people on the same "
+"server will be able to see each other, even when they aren't on the same "
+"network."
+msgstr ""
+"De server is hetzelfde als de ruimte waar jij je in bevindt; mensen op "
+"dezelfde server kunnen elkaar zien, ook als ze niet op hetzelfde netwerk "
+"zitten."
+
+#: ../extensions/cpsection/network/view.py:136
+msgid "Server:"
+msgstr "Server:"
+
+#: ../extensions/cpsection/power/__init__.py:21
+msgid "Power"
+msgstr "Energie"
+
+#: ../extensions/cpsection/power/model.py:85
+msgid "Error in automatic pm argument, use on/off."
+msgstr "Fout in automatisch energiebeheer argument, gebruik on/off."
+
+#: ../extensions/cpsection/power/model.py:112
+msgid "Error in extreme pm argument, use on/off."
+msgstr "Fout in extreem energiebeheer argument, gebruik on/off."
+
+#: ../extensions/cpsection/power/view.py:47
+msgid "Power management"
+msgstr "Energiebeheer"
+
+#: ../extensions/cpsection/power/view.py:57
+msgid "Automatic power management (increases battery life)"
+msgstr "Automatisch energiebeheer (verhoogt gebruiksduur accu)"
+
+#: ../extensions/cpsection/power/view.py:85
+msgid ""
+"Extreme power management (disableswireless radio, increases battery life)"
+msgstr ""
+"Extreem energiebeheer (deactiveert draadloze zender, verhoogt gebruiksduur "
+"accu)"
+
+#: ../extensions/cpsection/updater/__init__.py:21
+msgid "Software update"
+msgstr "Software-update"
+
+#: ../extensions/cpsection/updater/view.py:63
+msgid ""
+"Software updates correct errors, eliminate security vulnerabilities, and "
+"provide new features."
+msgstr ""
+"Software-updates verbeteren fouten, verhelpen beveiligingsproblemen en "
+"bieden nieuwe mogelijkheden."
+
+#: ../extensions/cpsection/updater/view.py:125
+#, python-format
+msgid "Checking %s..."
+msgstr "Controleren van %s..."
+
+#: ../extensions/cpsection/updater/view.py:127
+#, python-format
+msgid "Downloading %s..."
+msgstr "Downloaden van %s..."
+
+#: ../extensions/cpsection/updater/view.py:129
+#, python-format
+msgid "Updating %s..."
+msgstr "Updaten van %s..."
+
+#: ../extensions/cpsection/updater/view.py:139
+msgid "Your software is up-to-date"
+msgstr "Je software is bijgewerkt"
+
+#: ../extensions/cpsection/updater/view.py:141
+#, python-format
+msgid "You can install %s update"
+msgid_plural "You can install %s updates"
+msgstr[0] "Je kan %s update installeren"
+msgstr[1] "Je kan %s updates installeren"
+
+#: ../extensions/cpsection/updater/view.py:159
+msgid "Checking for updates..."
+msgstr "Controleren op updates..."
+
+#: ../extensions/cpsection/updater/view.py:164
+msgid "Installing updates..."
+msgstr "Updates installeren..."
+
+#: ../extensions/cpsection/updater/view.py:172
+#, python-format
+msgid "%s update was installed"
+msgid_plural "%s updates were installed"
+msgstr[0] "%s update is geïnstalleerd"
+msgstr[1] "%s updates zijn geïnstalleerd"
+
+#: ../extensions/cpsection/updater/view.py:253
+msgid "Install selected"
+msgstr "Installatie geselecteerd"
+
+#: ../extensions/cpsection/updater/view.py:274
+#, python-format
+msgid "Download size: %s"
+msgstr "Downloadgrootte: %s"
+
+#: ../extensions/cpsection/updater/view.py:362
+#, python-format
+msgid "From version %(current)d to %(new)s (Size: %(size)s)"
+msgstr "Van versie %(current)d naar %(new)s (Grootte: %(size)s)"
+
+#. TRANS: download size is 0
+#: ../extensions/cpsection/updater/view.py:382
+msgid "None"
+msgstr "Niets"
+
+#. TRANS: download size of very small updates
+#: ../extensions/cpsection/updater/view.py:385
+msgid "1 KB"
+msgstr "1 KB"
+
+#. TRANS: download size of small updates, e.g. '250 KB'
+#: ../extensions/cpsection/updater/view.py:388
+#, python-format
+msgid "%.0f KB"
+msgstr "%.0f KB"
+
+#. TRANS: download size of updates, e.g. '2.3 MB'
+#: ../extensions/cpsection/updater/view.py:391
+#, python-format
+msgid "%.1f MB"
+msgstr "%.1f MB"
+
+#: ../extensions/deviceicon/battery.py:58
+msgid "My Battery"
+msgstr "Mijn Accu"
+
+#: ../extensions/deviceicon/battery.py:137
+msgid "Removed"
+msgstr "Verwijderd"
+
+#: ../extensions/deviceicon/battery.py:140
+msgid "Charging"
+msgstr "Opladen"
+
+#: ../extensions/deviceicon/battery.py:143
+msgid "Very little power remaining"
+msgstr "Heel weinig energie over"
+
+#: ../extensions/deviceicon/battery.py:149
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr "%(hour)d:%(min).2d over"
+
+#: ../extensions/deviceicon/battery.py:152
+msgid "Charged"
+msgstr "Opgeladen"
+
+#: ../extensions/deviceicon/network.py:49
+#, python-format
+msgid "IP address: %s"
+msgstr "IP adres: %s"
+
+#: ../extensions/deviceicon/network.py:112
+msgid "Disconnect..."
+msgstr "Verbinding verbreken..."
+
+#: ../extensions/deviceicon/network.py:117
+msgid "Create new wireless network"
+msgstr "Nieuw draadloos netwerk aanmaken"
+
+#: ../extensions/deviceicon/network.py:123
+#: ../extensions/deviceicon/network.py:285
+#: ../src/jarabe/desktop/meshbox.py:247 ../src/jarabe/desktop/meshbox.py:536
+msgid "Connecting..."
+msgstr "Verbinden..."
+
+# TODO: show the channel number
+#: ../extensions/deviceicon/network.py:127
+#: ../extensions/deviceicon/network.py:199
+#: ../extensions/deviceicon/network.py:289
+#: ../src/jarabe/desktop/meshbox.py:253 ../src/jarabe/desktop/meshbox.py:542
+msgid "Connected"
+msgstr "Verbonden"
+
+#: ../extensions/deviceicon/network.py:159
+msgid "Channel"
+msgstr "Kanaal"
+
+#: ../extensions/deviceicon/network.py:174
+msgid "Wired Network"
+msgstr "Bedraad netwerk"
+
+#: ../extensions/deviceicon/network.py:202
+msgid "Speed"
+msgstr "Snelheid"
+
+#: ../extensions/deviceicon/network.py:229
+msgid "Wireless modem"
+msgstr "Draadloze modem"
+
+#: ../extensions/deviceicon/network.py:277
+msgid "Please wait..."
+msgstr "Even geduld aub..."
+
+#: ../extensions/deviceicon/network.py:280
+#: ../src/jarabe/desktop/meshbox.py:163 ../src/jarabe/desktop/meshbox.py:493
+msgid "Connect"
+msgstr "Verbinden"
+
+#: ../extensions/deviceicon/network.py:281
+msgid "Disconnected"
+msgstr "Verbinding verbroken"
+
+#: ../extensions/deviceicon/network.py:284
+#: ../src/jarabe/controlpanel/toolbar.py:115
+#: ../src/jarabe/desktop/homebox.py:68
+#: ../src/jarabe/frame/activitiestray.py:708
+#: ../src/jarabe/frame/activitiestray.py:807
+#: ../src/jarabe/frame/activitiestray.py:835
+msgid "Cancel"
+msgstr "Annuleren"
+
+#: ../extensions/deviceicon/network.py:288
+#: ../src/jarabe/desktop/meshbox.py:167
+msgid "Disconnect"
+msgstr "Verbinding verbreken"
+
+#: ../extensions/deviceicon/network.py:292
+msgid "Sim requires Pin/Puk"
+msgstr "Sim vereist Pin/Puk"
+
+#: ../extensions/deviceicon/network.py:293
+msgid "Authentication Error"
+msgstr "Authenticatiefout"
+
+#: ../extensions/deviceicon/network.py:538
+#, python-format
+msgid "%s's network"
+msgstr "%s's netwerk"
+
+#: ../extensions/deviceicon/network.py:605
+#: ../extensions/deviceicon/network.py:664
+msgid "Mesh Network"
+msgstr "Mesh netwerk"
+
+#: ../extensions/deviceicon/network.py:869
+#, python-format
+msgid "Data sent %d KB / received %d KB"
+msgstr "Gegevens verstuurd %d KB / ontvangen %d KB"
+
+#: ../extensions/deviceicon/network.py:880
+msgid "Connection time "
+msgstr "Tijd verbonden "
+
+#: ../extensions/deviceicon/speaker.py:59
+msgid "My Speakers"
+msgstr "Mijn Speakers"
+
+#: ../extensions/deviceicon/speaker.py:133
+msgid "Unmute"
+msgstr "Ontdempen"
+
+#: ../extensions/deviceicon/speaker.py:136
+msgid "Mute"
+msgstr "Dempen"
+
+#: ../extensions/globalkey/screenshot.py:59
+msgid "Mesh"
+msgstr "Mesh"
+
+#: ../extensions/globalkey/screenshot.py:61
+#: ../src/jarabe/frame/zoomtoolbar.py:39
+msgid "Group"
+msgstr "Groep"
+
+#: ../extensions/globalkey/screenshot.py:63
+#: ../src/jarabe/frame/zoomtoolbar.py:41
+msgid "Home"
+msgstr "Thuis"
+
+#: ../extensions/globalkey/screenshot.py:69
+#: ../src/jarabe/frame/zoomtoolbar.py:43
+msgid "Activity"
+msgstr "Activiteit"
+
+#: ../extensions/globalkey/screenshot.py:72
+msgid "Screenshot"
+msgstr "Schermafdruk"
+
+#: ../extensions/globalkey/screenshot.py:74
+#, python-format
+msgid "Screenshot of \"%s\""
+msgstr "Schermafdruk van \"%s\""
+
+#: ../data/sugar.schemas.in.h:1
+msgid ""
+"\"disabled\" to ask nick on initialization; \"system\" to reuse UNIX account "
+"long name."
+msgstr ""
+"\"uitgezet\" om bijnaam te vragen bij initialisatie; \"systeem\" hergebruikt de "
+"UNIX gebruiker lange naam."
+
+#: ../data/sugar.schemas.in.h:2
+msgid "Additional directories which can contain updated translations."
+msgstr "Additionele directories die bijgewerkte vertalingen kunnen bevatten."
+
+#: ../data/sugar.schemas.in.h:3
+msgid "Backup URL"
+msgstr "Back-up URL"
+
+#: ../data/sugar.schemas.in.h:4
+msgid ""
+"Color for the XO icon that is used throughout the desktop. The string is "
+"composed of the stroke color and fill color, format is that of rbg colors. "
+"Example: #AC32FF,#9A5200"
+msgstr ""
+"Kleur van het XO icoon dat op de desktop gebruikt word. De waarden bestaan "
+"uit een lijnkleur en een vulkleur, formaat is gelijk aan rbg kleuren. "
+"Bijvoorbeeld: #AC32FF,#9A5200"
+
+#: ../data/sugar.schemas.in.h:5
+msgid "Corner Delay"
+msgstr "Hoekvertraging"
+
+#: ../data/sugar.schemas.in.h:6
+msgid "Default font face"
+msgstr "Standaard lettertype"
+
+#: ../data/sugar.schemas.in.h:7
+msgid "Default font size"
+msgstr "Standaard lettergrootte"
+
+#: ../data/sugar.schemas.in.h:8
+msgid "Default nick"
+msgstr "Standaard bijnaam"
+
+#: ../data/sugar.schemas.in.h:9
+msgid "Delay for the activation of the frame using the corners."
+msgstr "Vertraging voor de activatie van het kader door de hoeken te gebruiken."
+
+#: ../data/sugar.schemas.in.h:10
+msgid "Delay for the activation of the frame using the edges."
+msgstr "Vertraging voor de activatie van het kader door de randen te gebruiken."
+
+#: ../data/sugar.schemas.in.h:11
+msgid "Directory to search for translations"
+msgstr "Directory om naar vertalingen te zoeken"
+
+#: ../data/sugar.schemas.in.h:12
+msgid "Edge Delay"
+msgstr "Randvertraging"
+
+#: ../data/sugar.schemas.in.h:13
+msgid "Favorites Layout"
+msgstr "Favorietenlayout"
+
+#: ../data/sugar.schemas.in.h:14
+msgid "Favorites resume mode"
+msgstr "Favorieten hervatmodus"
+
+#: ../data/sugar.schemas.in.h:15
+msgid "Font face that is used throughout the desktop."
+msgstr "Lettertype dat overal gebruikt wordt op het bureaublad."
+
+#: ../data/sugar.schemas.in.h:16
+msgid "Font size that is used throughout the desktop."
+msgstr "Lettergrootte die overal gebruikt wordt op het bureaublad."
+
+#: ../data/sugar.schemas.in.h:17
+msgid "GSM network APN"
+msgstr "GSM netwerk APN"
+
+#: ../data/sugar.schemas.in.h:18
+msgid "GSM network PIN"
+msgstr "GSM netwerk PIN"
+
+#: ../data/sugar.schemas.in.h:19
+msgid "GSM network PUK"
+msgstr "GSM netwerk PUK"
+
+#: ../data/sugar.schemas.in.h:20
+msgid "GSM network access point name configuration"
+msgstr "GSM netwerk access point naam configuratie"
+
+#: ../data/sugar.schemas.in.h:21
+msgid "GSM network number"
+msgstr "GSM netwerknummer"
+
+#: ../data/sugar.schemas.in.h:22
+msgid "GSM network password"
+msgstr "GSM netwerkwachtwoord"
+
+#: ../data/sugar.schemas.in.h:23
+msgid "GSM network password configuration"
+msgstr "GSM netwerkwachtwoord configuratie"
+
+#: ../data/sugar.schemas.in.h:24
+msgid "GSM network personal identification number configuration"
+msgstr "GSM netwerk persoonlijk identificatienummer configuratie"
+
+#: ../data/sugar.schemas.in.h:25
+msgid "GSM network personal unlock key configuration"
+msgstr "GSM netwerk persoonlijk ontsluitsleutel configuratie"
+
+#: ../data/sugar.schemas.in.h:26
+msgid "GSM network telephone number configuration"
+msgstr "GSM netwerk telefoonnummer configuratie"
+
+#: ../data/sugar.schemas.in.h:27
+msgid "GSM network username"
+msgstr "GSM netwerkgebruikersnaam"
+
+#: ../data/sugar.schemas.in.h:28
+msgid "GSM network username configuration"
+msgstr "GSM netwerkgebruikersnaam configuratie"
+
+#: ../data/sugar.schemas.in.h:29
+msgid ""
+"If TRUE, Sugar will make us searchable for the other users of the Jabber "
+"server."
+msgstr ""
+"Indien WAAR, dan zal Sugar ons opzoekbaar maken voor andere gebruikers op de "
+"Jabber server."
+
+#: ../data/sugar.schemas.in.h:30
+msgid "If TRUE, Sugar will show a \"Log out\" option."
+msgstr "Indien WAAR, zal Sugar een \"Afmelden\" optie geven."
+
+#: ../data/sugar.schemas.in.h:31
+msgid "Jabber Server"
+msgstr "Jabber-server"
+
+#: ../data/sugar.schemas.in.h:32
+msgid "Keyboard layouts"
+msgstr "Toetsenbordindelingen"
+
+#: ../data/sugar.schemas.in.h:33
+msgid "Keyboard model"
+msgstr "Toetsenbordmodel"
+
+#: ../data/sugar.schemas.in.h:34
+msgid "Keyboard options"
+msgstr "Toetsenbordopties"
+
+#: ../data/sugar.schemas.in.h:35
+msgid "Layout of the favorites view."
+msgstr "Layout van de favorieten weergave."
+
+#: ../data/sugar.schemas.in.h:36
+msgid ""
+"List of keyboard layouts. Each entry should be in the form layout(variant)"
+msgstr ""
+"Lijst met toetsenbordindelingen. Elke invoer moet in de "
+"vormindeling(variant) zijn"
+
+#: ../data/sugar.schemas.in.h:37
+msgid "List of keyboard options."
+msgstr "Lijst met toetsenbordopties."
+
+#: ../data/sugar.schemas.in.h:38
+msgid "Power Automatic"
+msgstr "Energiebeheer: automatisch"
+
+#: ../data/sugar.schemas.in.h:39
+msgid "Power Automatic."
+msgstr "Energiebeheer: automatisch."
+
+#: ../data/sugar.schemas.in.h:40
+msgid "Power Extreme"
+msgstr "Energiebeheer: extreem"
+
+#: ../data/sugar.schemas.in.h:41
+msgid "Power Extreme."
+msgstr "Energiebeheer: extreem."
+
+#: ../data/sugar.schemas.in.h:42
+msgid "Publish to Gadget"
+msgstr "Publiceren naar gadget"
+
+#: ../data/sugar.schemas.in.h:43
+msgid "Setting for muting the sound device."
+msgstr "Instelling voor dempen van het audio-apparaat."
+
+#: ../data/sugar.schemas.in.h:44
+msgid "Show Log out"
+msgstr "Afmelden weergeven"
+
+#: ../data/sugar.schemas.in.h:45
+msgid "Sound Muted"
+msgstr "Geluid gedempt"
+
+#: ../data/sugar.schemas.in.h:46
+msgid "The keyboard model to be used"
+msgstr "Het toetsenbordmodel om te gebruiken"
+
+#: ../data/sugar.schemas.in.h:48
+msgid "Timezone setting for the system."
+msgstr "Tijdzone-instelling voor het systeem."
+
+#: ../data/sugar.schemas.in.h:49
+msgid "Url of the jabber server to use."
+msgstr "Url van de te gebruiken jabber-server."
+
+#: ../data/sugar.schemas.in.h:50
+msgid "Url where the backup is saved to."
+msgstr "Url waar de back-up opgeslagen wordt."
+
+#: ../data/sugar.schemas.in.h:51
+msgid "User Color"
+msgstr "Gebruikerskleur"
+
+#: ../data/sugar.schemas.in.h:52
+msgid "User Name"
+msgstr "Gebruikersnaam"
+
+#: ../data/sugar.schemas.in.h:53
+msgid "User name that is used throughout the desktop."
+msgstr "Gebruikersnaam die op de desktop gebruikt wordt."
+
+#: ../data/sugar.schemas.in.h:54
+msgid "Volume Level"
+msgstr "Volumeniveau"
+
+#: ../data/sugar.schemas.in.h:55
+msgid "Volume level for the sound device."
+msgstr "Volumeniveau voor het audio-apparaat."
+
+#: ../data/sugar.schemas.in.h:56
+msgid ""
+"When in resume mode, clicking on a favorite icon will cause the last entry "
+"for that activity to be resumed."
+msgstr ""
+"Indien in hervatmodus, kun je door te klikken op een favorietenicoon ervoor "
+"zorgen dat de laatste invoer van die activiteit hervat wordt."
+
+#: ../src/jarabe/controlpanel/cmd.py:28
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+"sugar-stuur-paneel: WAARSCHUWING, meer dan één optie met dezelfde naam "
+"gevonden: %s module: %r"
+
+#: ../src/jarabe/controlpanel/cmd.py:30
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr "sugar-control-panel: sleutel=%s is geen beschikbare optie"
+
+#: ../src/jarabe/controlpanel/cmd.py:31
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr "sugar-stuur-paneel: %s"
+
+# TRANS: Translators, there's a empty line at the end of this string,
+# which must appear in the translated string (msgstr) as well.
+#. TRANS: Translators, there's a empty line at the end of this string,
+#. which must appear in the translated string (msgstr) as well.
+#: ../src/jarabe/controlpanel/cmd.py:37
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" -c key clear the current value for the key \n"
+" "
+msgstr ""
+"Gebruik: sugar-stuur-paneel [ optie ] sleutel [ args ... ] \n"
+" Configuratie voor de sugar omgeving. \n"
+" Opties: \n"
+" -h geef dit helpbericht weer en sluit af \n"
+" -l lijst van alle beschikbare opties \n"
+" -h sleutel geef informatie over deze sleutel \n"
+" -g sleutel verkrijg de huidige waarde van de sleutel \n"
+" -s sleutel zet de huidige waarde naar de sleutel \n"
+" -c sleutel wis de huidige waarde van de sleutel \n"
+" "
+
+#: ../src/jarabe/controlpanel/cmd.py:50
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr "Om je veranderingen toe te passen moet je sugar herstarten.\n"
+
+#: ../src/jarabe/controlpanel/gui.py:281
+msgid "Warning"
+msgstr "Waarschuwing"
+
+#: ../src/jarabe/controlpanel/gui.py:282
+#: ../src/jarabe/controlpanel/sectionview.py:42
+msgid "Changes require restart"
+msgstr "Verandering vereist een herstart"
+
+#: ../src/jarabe/controlpanel/gui.py:285
+msgid "Cancel changes"
+msgstr "Veranderingen annuleren"
+
+#: ../src/jarabe/controlpanel/gui.py:290 ../src/jarabe/desktop/homebox.py:70
+msgid "Later"
+msgstr "Later"
+
+#: ../src/jarabe/controlpanel/gui.py:294
+msgid "Restart now"
+msgstr "Herstart nu"
+
+#: ../src/jarabe/controlpanel/toolbar.py:61 ../src/jarabe/intro/window.py:206
+msgid "Done"
+msgstr "Klaar"
+
+#: ../src/jarabe/controlpanel/toolbar.py:121
+#: ../src/jarabe/desktop/favoritesview.py:333
+msgid "Ok"
+msgstr "Ok"
+
+#: ../src/jarabe/desktop/activitieslist.py:236
+#, python-format
+msgid "Version %s"
+msgstr "Versie %s"
+
+#: ../src/jarabe/desktop/activitieslist.py:357
+msgid "Confirm erase"
+msgstr "Bevestig wissen"
+
+#: ../src/jarabe/desktop/activitieslist.py:359
+#, python-format
+msgid "Confirm erase: Do you want to permanently erase %s?"
+msgstr "Bevestig wissen: Wilt u permanent %s wissen?"
+
+# lijkt misschien teveel op een "sla op" actie?
+# self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+# TODO: Implement stopping downloads
+# self._stop_item.connect('activate', self._stop_item_activate_cb)
+# self.append_menu_item(self._stop_item)
+#: ../src/jarabe/desktop/activitieslist.py:363
+#: ../src/jarabe/frame/clipboardmenu.py:63
+#: ../src/jarabe/view/viewsource.py:218
+msgid "Keep"
+msgstr "Bewaar"
+
+#: ../src/jarabe/desktop/activitieslist.py:366
+#: ../src/jarabe/desktop/activitieslist.py:409
+#: ../src/jarabe/journal/journaltoolbox.py:361
+#: ../src/jarabe/journal/palettes.py:106
+msgid "Erase"
+msgstr "Wissen"
+
+#: ../src/jarabe/desktop/activitieslist.py:430
+msgid "Remove favorite"
+msgstr "Verwijder favoriet"
+
+#: ../src/jarabe/desktop/activitieslist.py:434
+msgid "Make favorite"
+msgstr "Maak favoriet"
+
+# TRANS: label for the freeform layout in the favorites view
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:116
+msgid "Freeform"
+msgstr "Vrijevorm"
+
+# TRANS: label for the ring layout in the favorites view
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:198
+msgid "Ring"
+msgstr "Ring"
+
+# TRANS: label for the spiral layout in the favorites view
+#. TRANS: label for the spiral layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:337
+msgid "Spiral"
+msgstr "Spiraal"
+
+# TRANS: label for the box layout in the favorites view
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:404
+msgid "Box"
+msgstr "Vierkant"
+
+# TRANS: label for the box layout in the favorites view
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:445
+msgid "Triangle"
+msgstr "Driehoek"
+
+#: ../src/jarabe/desktop/favoritesview.py:324
+msgid "Registration Failed"
+msgstr "Registratie mislukt"
+
+#: ../src/jarabe/desktop/favoritesview.py:325
+#, python-format
+msgid "%s"
+msgstr "%s"
+
+#: ../src/jarabe/desktop/favoritesview.py:327
+msgid "Registration Successful"
+msgstr "Registratie succesvol uitgevoerd"
+
+#: ../src/jarabe/desktop/favoritesview.py:328
+msgid "You are now registered with your school server."
+msgstr "Je bent nu geregistreerd bij je school-server."
+
+#: ../src/jarabe/desktop/favoritesview.py:631
+msgid "Register"
+msgstr "Registreren"
+
+#: ../src/jarabe/desktop/homebox.py:63
+msgid "Software Update"
+msgstr "Software Bijwerken"
+
+#: ../src/jarabe/desktop/homebox.py:64
+msgid "Update your activities to ensure compatibility with your new software"
+msgstr ""
+"Update je activiteiten om zeker ervan te zijn dat ze met je nieuwe software "
+"compatibel zijn"
+
+#: ../src/jarabe/desktop/homebox.py:73
+msgid "Check now"
+msgstr "Controleer nu"
+
+#: ../src/jarabe/desktop/homebox.py:192
+msgid "List view"
+msgstr "Lijstweergave"
+
+#: ../src/jarabe/desktop/homebox.py:193
+msgid "<Ctrl>2"
+msgstr "<Ctrl>2"
+
+#: ../src/jarabe/desktop/homebox.py:255
+msgid "Favorites view"
+msgstr "Favorietenweergave"
+
+#: ../src/jarabe/desktop/homebox.py:256
+msgid "<Ctrl>1"
+msgstr "<Ctrl>1"
+
+#: ../src/jarabe/desktop/keydialog.py:135
+msgid "Key Type:"
+msgstr "Sleutel type:"
+
+#: ../src/jarabe/desktop/keydialog.py:155
+msgid "Authentication Type:"
+msgstr "Authenticatie type:"
+
+#: ../src/jarabe/desktop/keydialog.py:220
+msgid "WPA & WPA2 Personal"
+msgstr "WPA & WPA2 Persoonlijk"
+
+#: ../src/jarabe/desktop/keydialog.py:229
+msgid "Wireless Security:"
+msgstr "Draadloze netwerkbeveiliging:"
+
+#: ../src/jarabe/desktop/meshbox.py:491
+#, python-format
+msgid "Mesh Network %d"
+msgstr "Mesh Netwerk %d"
+
+# TRANS: Action label for resuming an activity.
+#. TRANS: Action label for resuming an activity.
+#: ../src/jarabe/desktop/meshbox.py:628
+#: ../src/jarabe/frame/activitiestray.py:743
+#: ../src/jarabe/journal/journaltoolbox.py:440
+#: ../src/jarabe/journal/palettes.py:66 ../src/jarabe/view/palettes.py:79
+msgid "Resume"
+msgstr "Hervatten"
+
+#: ../src/jarabe/desktop/meshbox.py:633
+#: ../src/jarabe/frame/activitiestray.py:241
+msgid "Join"
+msgstr "Bijvoegen"
+
+#: ../src/jarabe/desktop/schoolserver.py:104
+msgid "Cannot connect to the server."
+msgstr "Kan niet met de server verbinden."
+
+#: ../src/jarabe/desktop/schoolserver.py:109
+msgid "The server could not complete the request."
+msgstr "De server kon de aanvraag niet voltooien."
+
+#: ../src/jarabe/frame/activitiestray.py:246
+#: ../src/jarabe/frame/activitiestray.py:680
+msgid "Decline"
+msgstr "Weigeren"
+
+#: ../src/jarabe/frame/activitiestray.py:632
+#, python-format
+msgid "%dB"
+msgstr "%dB"
+
+#: ../src/jarabe/frame/activitiestray.py:634
+#, python-format
+msgid "%dKB"
+msgstr "%dKB"
+
+#: ../src/jarabe/frame/activitiestray.py:636
+#, python-format
+msgid "%dMB"
+msgstr "%dMB"
+
+#: ../src/jarabe/frame/activitiestray.py:653
+#, python-format
+msgid "%s of %s"
+msgstr "%s van %s"
+
+#: ../src/jarabe/frame/activitiestray.py:665
+#, python-format
+msgid "Transfer from %r"
+msgstr "Overdragen van %r"
+
+#: ../src/jarabe/frame/activitiestray.py:675
+msgid "Accept"
+msgstr "Accepteren"
+
+#: ../src/jarabe/frame/activitiestray.py:698
+#: ../src/jarabe/frame/activitiestray.py:825
+#, python-format
+msgid "%s (%s)"
+msgstr "%s (%s)"
+
+#: ../src/jarabe/frame/activitiestray.py:732
+#: ../src/jarabe/frame/activitiestray.py:860
+msgid "Dismiss"
+msgstr "Wegdoen"
+
+#: ../src/jarabe/frame/activitiestray.py:795
+#, python-format
+msgid "Transfer to %r"
+msgstr "Overdragen naar %r"
+
+#: ../src/jarabe/frame/clipboardmenu.py:53 ../src/jarabe/view/palettes.py:233
+msgid "Remove"
+msgstr "Verwijderen"
+
+#: ../src/jarabe/frame/clipboardmenu.py:58
+#: ../src/jarabe/frame/clipboardmenu.py:81
+msgid "Open"
+msgstr "Openen"
+
+#: ../src/jarabe/frame/clipboardmenu.py:86
+msgid "Open with"
+msgstr "Openen met"
+
+#: ../src/jarabe/frame/clipboardobject.py:49
+#, python-format
+msgid "%s clipping"
+msgstr "%s in klembord zetten"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:37
+msgid "Neighborhood"
+msgstr "Omgeving"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:37
+msgid "F1"
+msgstr "F1"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:39
+msgid "F2"
+msgstr "F2"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:41
+msgid "F3"
+msgstr "F3"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:43
+msgid "F4"
+msgstr "F4"
+
+#: ../src/jarabe/intro/window.py:128
+msgid "Click to change color:"
+msgstr "Klik om de kleur te veranderen:"
+
+#: ../src/jarabe/intro/window.py:192 ../src/jarabe/journal/detailview.py:103
+msgid "Back"
+msgstr "Terug"
+
+#: ../src/jarabe/intro/window.py:209
+msgid "Next"
+msgstr "Volgende"
+
+#: ../src/jarabe/journal/expandedentry.py:151
+#: ../src/jarabe/journal/palettes.py:60
+msgid "Untitled"
+msgstr "Naamloos"
+
+#: ../src/jarabe/journal/expandedentry.py:242
+msgid "No preview"
+msgstr "Geen voorbeeld"
+
+#: ../src/jarabe/journal/expandedentry.py:261
+#, python-format
+msgid "Kind: %s"
+msgstr "Type: %s"
+
+#: ../src/jarabe/journal/expandedentry.py:261
+msgid "Unknown"
+msgstr "Onbekend"
+
+#: ../src/jarabe/journal/expandedentry.py:262
+#, python-format
+msgid "Date: %s"
+msgstr "Datum: %s"
+
+#: ../src/jarabe/journal/expandedentry.py:263
+#, python-format
+msgid "Size: %s"
+msgstr "Grootte: %s"
+
+#: ../src/jarabe/journal/expandedentry.py:285 ../src/jarabe/journal/misc.py:93
+msgid "No date"
+msgstr "Geen datum"
+
+#: ../src/jarabe/journal/expandedentry.py:292
+msgid "Participants:"
+msgstr "Deelnemers:"
+
+#: ../src/jarabe/journal/expandedentry.py:315
+msgid "Description:"
+msgstr "Omschrijving:"
+
+#: ../src/jarabe/journal/expandedentry.py:340
+msgid "Tags:"
+msgstr "Labels:"
+
+#: ../src/jarabe/journal/journalactivity.py:108
+#: ../src/jarabe/journal/journaltoolbox.py:411
+#: ../src/jarabe/journal/volumestoolbar.py:47
+msgid "Journal"
+msgstr "Dagboek"
+
+#: ../src/jarabe/journal/journaltoolbox.py:68
+msgid "Search"
+msgstr "Zoeken"
+
+#: ../src/jarabe/journal/journaltoolbox.py:127
+msgid "Anytime"
+msgstr "Ieder tijdstip"
+
+#: ../src/jarabe/journal/journaltoolbox.py:129
+msgid "Today"
+msgstr "Vandaag"
+
+#: ../src/jarabe/journal/journaltoolbox.py:131
+msgid "Since yesterday"
+msgstr "Sinds gisteren"
+
+# TRANS: Filter entries modified during the last 7 days.
+#. TRANS: Filter entries modified during the last 7 days.
+#: ../src/jarabe/journal/journaltoolbox.py:133
+msgid "Past week"
+msgstr "Afgelopen week"
+
+# TRANS: Filter entries modified during the last 30 days.
+#. TRANS: Filter entries modified during the last 30 days.
+#: ../src/jarabe/journal/journaltoolbox.py:135
+msgid "Past month"
+msgstr "Afgelopen maand"
+
+# TRANS: Filter entries modified during the last 356 days.
+#. TRANS: Filter entries modified during the last 356 days.
+#: ../src/jarabe/journal/journaltoolbox.py:137
+msgid "Past year"
+msgstr "Afgelopen jaar"
+
+#: ../src/jarabe/journal/journaltoolbox.py:144
+msgid "Anyone"
+msgstr "Iedereen"
+
+#: ../src/jarabe/journal/journaltoolbox.py:146
+msgid "My friends"
+msgstr "Mijn vrienden"
+
+#: ../src/jarabe/journal/journaltoolbox.py:147
+msgid "My class"
+msgstr "Mijn klas"
+
+# TRANS: Item in a combo box that filters by entry type.
+#: ../src/jarabe/journal/journaltoolbox.py:275
+msgid "Anything"
+msgstr "Alles"
+
+# TODO: Add "Start with" menu item
+#: ../src/jarabe/journal/journaltoolbox.py:351
+#: ../src/jarabe/journal/palettes.py:84
+msgid "Copy"
+msgstr "Kopieer"
+
+# TRANS: Action label for starting an entry.
+#. TRANS: Action label for starting an entry.
+#: ../src/jarabe/journal/journaltoolbox.py:443
+#: ../src/jarabe/journal/palettes.py:69
+msgid "Start"
+msgstr "Start"
+
+#: ../src/jarabe/journal/listview.py:373
+msgid "Your Journal is empty"
+msgstr "Je dagboek is leeg"
+
+#: ../src/jarabe/journal/listview.py:375
+msgid "No matching entries"
+msgstr "Geen overeenkomende ingangen"
+
+#: ../src/jarabe/journal/listview.py:386
+msgid "Clear search"
+msgstr "Zoekopdracht wissen"
+
+#: ../src/jarabe/journal/modalalert.py:63
+msgid "Your Journal is full"
+msgstr "Je dagboek is vol"
+
+#: ../src/jarabe/journal/modalalert.py:67
+msgid "Please delete some old Journal entries to make space for new ones."
+msgstr "Verwijder a.u.b. oude dagboekingangen om ruimte te maken voor nieuwe."
+
+#: ../src/jarabe/journal/modalalert.py:79
+msgid "Show Journal"
+msgstr "Dagboek weergeven"
+
+#: ../src/jarabe/journal/objectchooser.py:146
+msgid "Choose an object"
+msgstr "Kies een object"
+
+#: ../src/jarabe/journal/objectchooser.py:151
+#: ../src/jarabe/view/viewsource.py:310
+msgid "Close"
+msgstr "Sluiten"
+
+#: ../src/jarabe/journal/palettes.py:67
+msgid "Resume with"
+msgstr "Hervatten met"
+
+#: ../src/jarabe/journal/palettes.py:70
+msgid "Start with"
+msgstr "Starten met"
+
+#: ../src/jarabe/journal/palettes.py:92
+msgid "Send to"
+msgstr "Verstuur naar"
+
+#: ../src/jarabe/journal/palettes.py:101
+msgid "View Details"
+msgstr "Details weergeven"
+
+#: ../src/jarabe/journal/palettes.py:179
+msgid "No friends present"
+msgstr "Geen vrienden aanwezig"
+
+#: ../src/jarabe/journal/palettes.py:184
+msgid "No valid connection found"
+msgstr "Geen bruikbare verbinding gevonden"
+
+#: ../src/jarabe/journal/palettes.py:212
+msgid "No activity to resume entry"
+msgstr "Geen activiteit om invoer mee te hervatten"
+
+#: ../src/jarabe/journal/palettes.py:214
+msgid "No activity to start entry"
+msgstr "Geen activiteit om invoer mee te starten"
+
+#: ../src/jarabe/view/buddymenu.py:62
+msgid "Remove friend"
+msgstr "Verwijder vriend"
+
+#: ../src/jarabe/view/buddymenu.py:65
+msgid "Make friend"
+msgstr "Maak vriend"
+
+#: ../src/jarabe/view/buddymenu.py:82
+msgid "Shutdown"
+msgstr "Afsluiten"
+
+#: ../src/jarabe/view/buddymenu.py:90
+msgid "Logout"
+msgstr "Afmelden"
+
+#: ../src/jarabe/view/buddymenu.py:95
+msgid "My Settings"
+msgstr "Mijn instellingen"
+
+#: ../src/jarabe/view/buddymenu.py:130
+#, python-format
+msgid "Invite to %s"
+msgstr "Nodig uit voor %s"
+
+#: ../src/jarabe/view/launcher.py:190
+#, python-format
+msgid "<b>%s</b> failed to start."
+msgstr "<b>%s</b> kon niet starten."
+
+#: ../src/jarabe/view/palettes.py:47
+msgid "Starting..."
+msgstr "Beginnen..."
+
+#: ../src/jarabe/view/palettes.py:57
+msgid "Activity failed to start"
+msgstr "Activiteit kon niet starten"
+
+#. TODO: share-with, keep
+#: ../src/jarabe/view/palettes.py:86
+msgid "View Source"
+msgstr "Bron weergeven"
+
+#: ../src/jarabe/view/palettes.py:97
+msgid "Stop"
+msgstr "Stop"
+
+#: ../src/jarabe/view/palettes.py:137
+msgid "Start new"
+msgstr "Begin nieuw"
+
+#: ../src/jarabe/view/palettes.py:186
+msgid "Show contents"
+msgstr "Inhoud weergeven"
+
+#: ../src/jarabe/view/palettes.py:208 ../src/jarabe/view/palettes.py:258
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr "%(free_space)d MB vrij"
+
+#: ../src/jarabe/view/viewsource.py:208
+msgid "Instance Source"
+msgstr "Werkkopie van bron maken"
+
+#: ../src/jarabe/view/viewsource.py:233
+msgid "Source"
+msgstr "Bron"
+
+#: ../src/jarabe/view/viewsource.py:294
+msgid "Activity Bundle Source"
+msgstr "Activiteitsbundel Bron"
+
+#: ../src/jarabe/view/viewsource.py:301
+#, python-format
+msgid "View source: %r"
+msgstr "Bron weergeven: %r"
+
+#~ msgid "APN:"
+#~ msgstr "APN:"
+
+#~ msgid "Title"
+#~ msgstr "Titel"
+
+#~ msgid "Version"
+#~ msgstr "Versie"
+
+#~ msgid "Date"
+#~ msgstr "Datum"
+
+#~ msgid "Cannot obtain data needed for registration."
+#~ msgstr ""
+#~ "Kan vereiste gegevens die nodig zijn voor de registratie niet verkrijgen."
+
+#~ msgid "Unmount"
+#~ msgstr "Loskoppelen"
+
+#~ msgid "Restart"
+#~ msgstr "Herstarten"
+
+#~ msgid ""
+#~ "© 2008 One Laptop per Child Association Inc; Red Hat Inc; and Contributors."
+#~ msgstr ""
+#~ "© 2008 One Laptop per Child Association Inc; Red Hat Inc; en anderen die "
+#~ "bijgedragen hebben."
+
+#~ msgid "Document"
+#~ msgstr "Document"
+
+#~ msgid "Resume by default"
+#~ msgstr "Standaard hervatten"
+
+#~ msgid "Encryption Type:"
+#~ msgstr "Encryptie type:"
+
+#~ msgid "Disconnecting..."
+#~ msgstr "Verbinding verbreken..."
+
+#~ msgid "About my XO"
+#~ msgstr "Over mijn XO"
+
+#~ msgid "Connected to a School Mesh Portal"
+#~ msgstr "Verbonden met een School Mesh Portaal"
+
+#~ msgid "Looking for a School Mesh Portal..."
+#~ msgstr "Zoeken naar een School Mesh Portaal..."
+
+#~ msgid "Connected to an XO Mesh Portal"
+#~ msgstr "Verbonden met een XO Mesh Portaal"
+
+#~ msgid "Looking for an XO Mesh Portal..."
+#~ msgstr "Zoeken naar een XO Mesh Portaal..."
+
+#~ msgid "Connected to a Simple Mesh"
+#~ msgstr "Verbinden met een Eenvoudige Mesh"
+
+#~ msgid "Starting a Simple Mesh"
+#~ msgstr "Start een Eenvoudige Mesh"
+
+#~ msgid "Unknown Mesh"
+#~ msgstr "Onbekende Mesh"
+
+#~ msgid "Settings"
+#~ msgstr "Instellingen"
+
+#, python-format
+#~ msgid "Clipboard object: %s."
+#~ msgstr "Klembord object: %s."
+
+#~ msgid "You must enter a server."
+#~ msgstr "U moet een server opgeven."
+
+#~ msgid "Control Panel"
+#~ msgstr "Configuratiepaneel"
+
+#~ msgid "<Ctrl>R"
+#~ msgstr "<Ctrl>R"
+
+#~ msgid "Add to journal"
+#~ msgstr "Voeg toe aan dagboek"
+
+#~ msgid "Reboot"
+#~ msgstr "Herstart"
+
+#~ msgid "Share with:"
+#~ msgstr "Deel met:"
+
+#~ msgid "My Neighborhood"
+#~ msgstr "Mijn Omgeving"
+
+#~ msgid "Undo"
+#~ msgstr "Maak ongedaan"
+
+#~ msgid "Redo"
+#~ msgstr "Doe opnieuw"
+
+#~ msgid "Paste"
+#~ msgstr "Plak"
+
+#, python-format
+#~ msgid "%s Activity"
+#~ msgstr "%s Activiteit"
+
+#~ msgid "Keep error"
+#~ msgstr "Bewaarfout"
+
+#~ msgid "Keep error: all changes will be lost"
+#~ msgstr "Bewaarfout: alle wijzigingen gaan verloren"
+
+#~ msgid "Don't stop"
+#~ msgstr "Niet stoppen"
+
+#~ msgid "Stop anyway"
+#~ msgstr "Toch stoppen"
+
+#~ msgid "Continue"
+#~ msgstr "Doorgaan"
+
+#~ msgid "OK"
+#~ msgstr "OK"
+
+#, python-format
+#~ msgid "%d year"
+#~ msgstr "%d jaar"
+
+#, python-format
+#~ msgid "%d years"
+#~ msgstr "%d jaren"
diff --git a/shell/po/pa.po b/shell/po/pa.po
new file mode 100644
index 0000000..a79e711
--- /dev/null
+++ b/shell/po/pa.po
@@ -0,0 +1,420 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-01-18 18:19+0000\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.0.1\n"
+
+#: ../shell/intro/intro.py:67
+msgid "Name:"
+msgstr ""
+
+#: ../shell/intro/intro.py:96
+msgid "Click to change color:"
+msgstr ""
+
+#: ../shell/intro/intro.py:146
+msgid "Back"
+msgstr ""
+
+#: ../shell/intro/intro.py:160
+msgid "Done"
+msgstr ""
+
+#: ../shell/intro/intro.py:163
+msgid "Next"
+msgstr ""
+
+#: ../shell/view/BuddyMenu.py:59
+msgid "Remove friend"
+msgstr ""
+
+#: ../shell/view/BuddyMenu.py:62
+msgid "Make friend"
+msgstr ""
+
+#: ../shell/view/BuddyMenu.py:84
+#, python-format
+msgid "Invite to %s"
+msgstr ""
+
+#: ../shell/view/clipboardmenu.py:58
+msgid "Remove"
+msgstr ""
+
+#: ../shell/view/clipboardmenu.py:63
+msgid "Open"
+msgstr ""
+
+#. self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+#. TODO: Implement stopping downloads
+#. self._stop_item.connect('activate', self._stop_item_activate_cb)
+#. self.append_menu_item(self._stop_item)
+#: ../shell/view/clipboardmenu.py:73
+msgid "Add to journal"
+msgstr ""
+
+#: ../shell/view/clipboardmenu.py:213
+#, python-format
+msgid "Clipboard object: %s."
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:149
+msgid "Key Type:"
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:169
+msgid "Authentication Type:"
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:250
+msgid "Encryption Type:"
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:90
+msgid "Starting..."
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:104 ../shell/view/home/MeshBox.py:295
+msgid "Resume"
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:111
+#: ../lib/sugar/activity/activity.py:132
+msgid "Stop"
+msgstr ""
+
+#: ../shell/view/Shell.py:285
+msgid "Screenshot"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:159
+msgid "Reboot"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:164
+msgid "Shutdown"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:170
+msgid "Register"
+msgstr ""
+
+#. Only show disconnect when there's a mesh device, because mesh takes
+#. priority over the normal wireless device. NM doesn't have a "disconnect"
+#. method for a device either (for various reasons) so this doesn't
+#. have a good mapping
+#: ../shell/view/home/MeshBox.py:90 ../shell/view/home/MeshBox.py:197
+#: ../shell/view/devices/network/wireless.py:113
+#: ../shell/view/devices/network/mesh.py:83
+msgid "Disconnect..."
+msgstr ""
+
+#: ../shell/view/home/MeshBox.py:195 ../shell/view/devices/network/mesh.py:37
+#: ../shell/view/devices/network/mesh.py:62
+#: ../shell/view/devices/network/mesh.py:66
+msgid "Mesh Network"
+msgstr ""
+
+#: ../shell/view/home/MeshBox.py:300
+msgid "Join"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:38
+msgid "My Battery life"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:94
+msgid "Battery charging"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:96
+msgid "Battery discharging"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:98
+msgid "Battery fully charged"
+msgstr ""
+
+#: ../shell/view/devices/network/wireless.py:61
+msgid "Disconnected"
+msgstr ""
+
+#: ../shell/view/devices/network/wireless.py:131
+msgid "Channel"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:42
+msgid "Neighborhood"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:54
+msgid "Group"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:66
+msgid "Home"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:78
+msgid "Activity"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:115
+msgid "Share with:"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:117
+msgid "Private"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:118
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:126
+msgid "Keep"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:245
+msgid "Undo"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:250
+msgid "Redo"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:260
+msgid "Copy"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:265
+msgid "Paste"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:454
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:824
+msgid "Keep error"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:825
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:828
+msgid "Don't stop"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:831
+msgid "Stop anyway"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:164 ../lib/sugar/graphics/alert.py:206
+msgid "Cancel"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:168
+msgid "Ok"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:216
+msgid "Continue"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:244
+msgid "OK"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d year"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d years"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d month"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d months"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d week"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d weeks"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d day"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d days"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hour"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hours"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minute"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minutes"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d second"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d seconds"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:191
+msgid " and "
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:193
+msgid ", "
+msgstr ""
+
+#: ../shell/controlpanel/control.py:213
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:267
+msgid "Error in specified color modifiers."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:270
+msgid "Error in specified colors."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:307
+msgid "off"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:309
+msgid "on"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:310
+msgid "State is unknown."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:332
+msgid "Error in specified radio argument use on/off."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:336
+msgid "Permission denied. You need to be root to run this method."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:366
+msgid "Error in reading timezone"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:397
+#, python-format
+msgid "Error copying timezone (from %s): %s"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:402
+#, python-format
+msgid "Changing permission of timezone: %s"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:412
+msgid "Error timezone does not exist."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:417 ../shell/controlpanel/control.py:436
+#, python-format
+msgid "Could not access %s. Create standard settings."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:463
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:473
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:105
+msgid "Connected to a School Mesh Portal"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:107
+msgid "Looking for a School Mesh Portal..."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:110
+msgid "Connected to an XO Mesh Portal"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:112
+msgid "Looking for an XO Mesh Portal..."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:115
+msgid "Connected to a Simple Mesh"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:117
+msgid "Starting a Simple Mesh"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:124
+msgid "Unknown Mesh"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:175 ../shell/view/home/HomeBox.py:216
+msgid "About this XO"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:222
+msgid "Not available"
+msgstr ""
diff --git a/shell/po/pap.po b/shell/po/pap.po
new file mode 100644
index 0000000..2523f6e
--- /dev/null
+++ b/shell/po/pap.po
@@ -0,0 +1,535 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-21 00:30-0400\n"
+"PO-Revision-Date: 2008-06-23 12:17-0400\n"
+"Last-Translator: Urso Wieske <uwieske@gmail.com>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Pootle 1.1.0rc2\n"
+
+#: ../src/intro/intro.py:65 ../src/controlpanel/view/aboutme.py:100
+msgid "Name:"
+msgstr "Nomber:"
+
+#: ../src/intro/intro.py:94
+msgid "Click to change color:"
+msgstr "Klik pa kambia kolor:"
+
+#: ../src/intro/intro.py:145
+#, fuzzy
+msgid "Back"
+msgstr "Bek"
+
+#: ../src/intro/intro.py:159 ../src/controlpanel/toolbar.py:61
+msgid "Done"
+msgstr "Kla"
+
+#: ../src/intro/intro.py:162
+msgid "Next"
+msgstr "Sigiente"
+
+#: ../src/view/BuddyMenu.py:58
+#, fuzzy
+msgid "Remove friend"
+msgstr "Kita amigu"
+
+#: ../src/view/BuddyMenu.py:61
+#, fuzzy
+msgid "Make friend"
+msgstr "Hasi amigu"
+
+#: ../src/view/BuddyMenu.py:91
+#, python-format
+msgid "Invite to %s"
+msgstr "Invita pa %s"
+
+#: ../src/view/clipboardmenu.py:48
+msgid "Remove"
+msgstr "Elimina"
+
+#: ../src/view/clipboardmenu.py:53 ../src/view/clipboardmenu.py:79
+msgid "Open"
+msgstr "Habri"
+
+#. self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+#. TODO: Implement stopping downloads
+#. self._stop_item.connect('activate', self._stop_item_activate_cb)
+#. self.append_menu_item(self._stop_item)
+#: ../src/view/clipboardmenu.py:63
+msgid "Keep"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:84
+msgid "Open with"
+msgstr "Habri ku"
+
+#: ../src/view/clipboardmenu.py:212
+#, python-format
+msgid "Clipboard object: %s."
+msgstr "Clipboard obgeto: %s."
+
+#: ../src/hardware/keydialog.py:150
+msgid "Key Type:"
+msgstr "Tip di Tecla"
+
+#: ../src/hardware/keydialog.py:170
+msgid "Authentication Type:"
+msgstr "Tipo di Autentikashon"
+
+#: ../src/hardware/keydialog.py:251
+msgid "Encryption Type:"
+msgstr "Tipo di Enkriptashon:"
+
+#: ../src/view/Shell.py:262
+msgid "Screenshot"
+msgstr "Kaptura di pantaya"
+
+#: ../src/view/home/HomeBox.py:147
+#, fuzzy
+msgid "List view"
+msgstr "Bista di lista"
+
+#: ../src/view/home/HomeBox.py:148
+msgid "<Ctrl>L"
+msgstr "<CTRL>L"
+
+#: ../src/view/home/HomeBox.py:204
+msgid "Favorites view"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:205
+msgid "<Ctrl>R"
+msgstr "<Ctrl>R"
+
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/view/home/HomeBox.py:211
+msgid "Freeform"
+msgstr ""
+
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/view/home/HomeBox.py:218
+msgid "Ring"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:97
+msgid "Connect"
+msgstr "Konekta"
+
+#: ../src/view/home/MeshBox.py:106
+msgid "Disconnect"
+msgstr "Diskonekta"
+
+#: ../src/view/home/MeshBox.py:118
+msgid "Disconnecting..."
+msgstr "Deskonektando..."
+
+#: ../src/view/home/MeshBox.py:152
+msgid "Connecting..."
+msgstr "Konektando..."
+
+#. TODO: show the channel number
+#: ../src/view/home/MeshBox.py:159
+msgid "Connected"
+msgstr "Konektá"
+
+#: ../src/view/home/MeshBox.py:211 ../src/view/devices/network/mesh.py:38
+#: ../src/view/devices/network/mesh.py:65
+#: ../src/view/devices/network/mesh.py:69
+msgid "Mesh Network"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:214 ../src/view/devices/network/wireless.py:116
+#: ../src/view/devices/network/mesh.py:86
+msgid "Disconnect..."
+msgstr "Deskonekta..."
+
+#: ../src/view/home/MeshBox.py:302 ../src/view/palettes.py:60
+msgid "Resume"
+msgstr "Resumí"
+
+#: ../src/view/home/MeshBox.py:307 ../src/view/frame/activitiestray.py:219
+msgid "Join"
+msgstr "Uni"
+
+#: ../src/view/devices/battery.py:42
+msgid "My Battery"
+msgstr "Mi Bateria"
+
+#: ../src/view/devices/battery.py:111
+msgid "Charging"
+msgstr "Kargando"
+
+#: ../src/view/devices/battery.py:114
+msgid "Very little power remaining"
+msgstr "A sobra masha tiki power "
+
+#: ../src/view/devices/battery.py:120
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr "A sobra %(ora)d:%(min).2d "
+
+#: ../src/view/devices/battery.py:124
+msgid "Charged"
+msgstr "Kargá"
+
+#: ../src/view/devices/speaker.py:40
+#, fuzzy
+msgid "My Speakers"
+msgstr "Mi spikernan"
+
+#: ../src/view/devices/speaker.py:104
+msgid "Unmute"
+msgstr ""
+
+#: ../src/view/devices/speaker.py:107
+msgid "Mute"
+msgstr "Silensia"
+
+#: ../src/view/devices/network/wireless.py:64
+msgid "Disconnected"
+msgstr "Deskonektá"
+
+#: ../src/view/devices/network/wireless.py:134
+msgid "Channel"
+msgstr "Kanal"
+
+#: ../src/view/frame/zoomtoolbar.py:34
+msgid "Neighborhood"
+msgstr "Besindario"
+
+#: ../src/view/frame/zoomtoolbar.py:36
+msgid "Group"
+msgstr "Grupo"
+
+#: ../src/view/frame/zoomtoolbar.py:38
+msgid "Home"
+msgstr "Home"
+
+#: ../src/view/frame/zoomtoolbar.py:40
+msgid "Activity"
+msgstr "Aktividad"
+
+#: ../src/controlpanel/cmd.py:26
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+"sugar-control-panel: KIDOU, a hanja mas ku un opshon ku e mesun nomber: %s "
+"modulo: %r"
+
+#: ../src/controlpanel/cmd.py:28
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr "sugar-control-panel: klave=%s no ta un opshon disponibel"
+
+#: ../src/controlpanel/cmd.py:29
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr "sugar-control-panel: %s"
+
+#: ../src/controlpanel/cmd.py:33
+#, fuzzy
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" "
+msgstr ""
+"Uso: sugar-control-panel [opshon] klave [argumentonan ...] \n"
+" Control pa e ambiente di sugar.\n"
+" Opshonnan: \n"
+" -h mustra e help mensahe aki i sali -l "
+" lista tur opshon disponibel\n"
+" -h klave mustra informashon riba e klave aki\n"
+" -g klave hanja e bolr koriente di e klave aki\n"
+" -s klave sèt e bolor koriente di klave aki\n"
+
+#: ../src/controlpanel/cmd.py:45
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr "Pa aplika bo kambionan bo tin ku restart sugar di nobo.\n"
+
+#: ../src/controlpanel/toolbar.py:115
+msgid "Cancel"
+msgstr "Kansela"
+
+#: ../src/controlpanel/toolbar.py:121
+msgid "Ok"
+msgstr "Ok"
+
+#: ../src/controlpanel/sectionview.py:34 ../src/controlpanel/gui.py:250
+msgid "Changes require restart"
+msgstr "Kambionan ta eksigi restart"
+
+#: ../src/controlpanel/gui.py:249
+msgid "Warning"
+msgstr "Kidou"
+
+#: ../src/controlpanel/gui.py:253
+msgid "Cancel changes"
+msgstr "Kansela kambionan"
+
+#: ../src/controlpanel/gui.py:257
+msgid "Later"
+msgstr "Despues"
+
+#: ../src/controlpanel/gui.py:261
+msgid "Restart now"
+msgstr "Restart aworaki"
+
+#: ../src/controlpanel/model/aboutme.py:44
+#, fuzzy
+msgid "You must enter a name."
+msgstr "Bo mester fill in un nomber."
+
+#: ../src/controlpanel/model/aboutme.py:67
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:70
+#, python-format
+msgid "stroke: %s"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:72
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:74
+#, python-format
+msgid "fill: %s"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:85
+msgid "Error in specified color modifiers."
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:88
+msgid "Error in specified colors."
+msgstr ""
+
+#: ../src/controlpanel/model/aboutxo.py:24
+msgid "Not available"
+msgstr ""
+
+#: ../src/controlpanel/model/datetime.py:85
+msgid "Error timezone does not exist."
+msgstr ""
+
+#: ../src/controlpanel/model/frame.py:38 ../src/controlpanel/model/frame.py:60
+msgid "Value must be an integer."
+msgstr ""
+
+#: ../src/controlpanel/model/language.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr ""
+
+#: ../src/controlpanel/model/language.py:104
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr ""
+
+#: ../src/controlpanel/model/language.py:121
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr ""
+
+#: ../src/controlpanel/model/network.py:48
+#, fuzzy
+msgid "You must enter a server."
+msgstr "Bo mester fill in un nomber."
+
+#: ../src/controlpanel/model/network.py:63
+msgid "State is unknown."
+msgstr ""
+
+#: ../src/controlpanel/model/network.py:83
+msgid "Error in specified radio argument use on/off."
+msgstr ""
+
+#: ../src/controlpanel/view/aboutme.py:32
+msgid "About Me"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutme.py:134
+#, fuzzy
+msgid "Click to change your color:"
+msgstr "Klik pa kambia kolor:"
+
+#: ../src/controlpanel/view/aboutxo.py:26
+msgid "About my XO"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutxo.py:47
+msgid "Identity"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutxo.py:56
+msgid "Serial Number:"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutxo.py:79
+msgid "Software"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutxo.py:88
+msgid "Build:"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutxo.py:103
+msgid "Firmware:"
+msgstr ""
+
+#: ../src/controlpanel/view/datetime.py:29
+msgid "Date & Time"
+msgstr ""
+
+#: ../src/controlpanel/view/datetime.py:72
+msgid "Timezone"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:28
+msgid "Frame"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:30
+msgid "never"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:31
+msgid "instantaneous"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:32
+#, python-format
+msgid "%s seconds"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:56
+msgid "Activation Delay"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:80
+msgid "Corner"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:115
+msgid "Edge"
+msgstr ""
+
+#: ../src/controlpanel/view/language.py:29
+#: ../src/controlpanel/view/language.py:74
+msgid "Language"
+msgstr ""
+
+#: ../src/controlpanel/view/network.py:28
+msgid "Network"
+msgstr ""
+
+#: ../src/controlpanel/view/network.py:53
+msgid "Wireless"
+msgstr ""
+
+#: ../src/controlpanel/view/network.py:61
+msgid "Radio:"
+msgstr ""
+
+#: ../src/controlpanel/view/network.py:94
+msgid "Mesh"
+msgstr ""
+
+#: ../src/controlpanel/view/network.py:103
+msgid "Server:"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:108
+msgid "Connected to a School Mesh Portal"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:110
+msgid "Looking for a School Mesh Portal..."
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:113
+msgid "Connected to an XO Mesh Portal"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:115
+msgid "Looking for an XO Mesh Portal..."
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:118
+msgid "Connected to a Simple Mesh"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:120
+msgid "Starting a Simple Mesh"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:127
+msgid "Unknown Mesh"
+msgstr ""
+
+#: ../src/view/frame/activitiestray.py:224
+msgid "Decline"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:351
+msgid "Control Panel"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:362
+msgid "Restart"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:367
+msgid "Shutdown"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:373
+msgid "Register"
+msgstr ""
+
+#: ../src/view/palettes.py:41
+msgid "Starting..."
+msgstr ""
+
+#: ../src/view/palettes.py:71
+msgid "Stop"
+msgstr ""
+
+#: ../src/view/palettes.py:96
+msgid "Start"
+msgstr ""
+
+#: ../src/view/palettes.py:119
+msgid "Remove favorite"
+msgstr ""
+
+#: ../src/view/palettes.py:123
+msgid "Make favorite"
+msgstr ""
+
+#: ../src/view/palettes.py:169
+msgid "Show contents"
+msgstr ""
+
+#: ../src/view/palettes.py:193
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr ""
diff --git a/shell/po/pis.po b/shell/po/pis.po
new file mode 100644
index 0000000..e184567
--- /dev/null
+++ b/shell/po/pis.po
@@ -0,0 +1,517 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-21 00:30-0400\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/intro/intro.py:65 ../src/controlpanel/view/aboutme.py:100
+msgid "Name:"
+msgstr ""
+
+#: ../src/intro/intro.py:94
+msgid "Click to change color:"
+msgstr ""
+
+#: ../src/intro/intro.py:145
+msgid "Back"
+msgstr ""
+
+#: ../src/intro/intro.py:159 ../src/controlpanel/toolbar.py:61
+msgid "Done"
+msgstr ""
+
+#: ../src/intro/intro.py:162
+msgid "Next"
+msgstr ""
+
+#: ../src/view/BuddyMenu.py:58
+msgid "Remove friend"
+msgstr ""
+
+#: ../src/view/BuddyMenu.py:61
+msgid "Make friend"
+msgstr ""
+
+#: ../src/view/BuddyMenu.py:91
+#, python-format
+msgid "Invite to %s"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:48
+msgid "Remove"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:53 ../src/view/clipboardmenu.py:79
+msgid "Open"
+msgstr ""
+
+#. self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+#. TODO: Implement stopping downloads
+#. self._stop_item.connect('activate', self._stop_item_activate_cb)
+#. self.append_menu_item(self._stop_item)
+#: ../src/view/clipboardmenu.py:63
+msgid "Keep"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:84
+msgid "Open with"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:212
+#, python-format
+msgid "Clipboard object: %s."
+msgstr ""
+
+#: ../src/hardware/keydialog.py:150
+msgid "Key Type:"
+msgstr ""
+
+#: ../src/hardware/keydialog.py:170
+msgid "Authentication Type:"
+msgstr ""
+
+#: ../src/hardware/keydialog.py:251
+msgid "Encryption Type:"
+msgstr ""
+
+#: ../src/view/Shell.py:262
+msgid "Screenshot"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:147
+msgid "List view"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:148
+msgid "<Ctrl>L"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:204
+msgid "Favorites view"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:205
+msgid "<Ctrl>R"
+msgstr ""
+
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/view/home/HomeBox.py:211
+msgid "Freeform"
+msgstr ""
+
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/view/home/HomeBox.py:218
+msgid "Ring"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:97
+msgid "Connect"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:106
+msgid "Disconnect"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:118
+msgid "Disconnecting..."
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:152
+msgid "Connecting..."
+msgstr ""
+
+#. TODO: show the channel number
+#: ../src/view/home/MeshBox.py:159
+msgid "Connected"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:211 ../src/view/devices/network/mesh.py:38
+#: ../src/view/devices/network/mesh.py:65
+#: ../src/view/devices/network/mesh.py:69
+msgid "Mesh Network"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:214 ../src/view/devices/network/wireless.py:116
+#: ../src/view/devices/network/mesh.py:86
+msgid "Disconnect..."
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:302 ../src/view/palettes.py:60
+msgid "Resume"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:307 ../src/view/frame/activitiestray.py:219
+msgid "Join"
+msgstr ""
+
+#: ../src/view/devices/battery.py:42
+msgid "My Battery"
+msgstr ""
+
+#: ../src/view/devices/battery.py:111
+msgid "Charging"
+msgstr ""
+
+#: ../src/view/devices/battery.py:114
+msgid "Very little power remaining"
+msgstr ""
+
+#: ../src/view/devices/battery.py:120
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr ""
+
+#: ../src/view/devices/battery.py:124
+msgid "Charged"
+msgstr ""
+
+#: ../src/view/devices/speaker.py:40
+msgid "My Speakers"
+msgstr ""
+
+#: ../src/view/devices/speaker.py:104
+msgid "Unmute"
+msgstr ""
+
+#: ../src/view/devices/speaker.py:107
+msgid "Mute"
+msgstr ""
+
+#: ../src/view/devices/network/wireless.py:64
+msgid "Disconnected"
+msgstr ""
+
+#: ../src/view/devices/network/wireless.py:134
+msgid "Channel"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:34
+msgid "Neighborhood"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:36
+msgid "Group"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:38
+msgid "Home"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:40
+msgid "Activity"
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:26
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:28
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:29
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:33
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" "
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:45
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr ""
+
+#: ../src/controlpanel/toolbar.py:115
+msgid "Cancel"
+msgstr ""
+
+#: ../src/controlpanel/toolbar.py:121
+msgid "Ok"
+msgstr ""
+
+#: ../src/controlpanel/sectionview.py:34 ../src/controlpanel/gui.py:250
+msgid "Changes require restart"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:249
+msgid "Warning"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:253
+msgid "Cancel changes"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:257
+msgid "Later"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:261
+msgid "Restart now"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:44
+msgid "You must enter a name."
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:67
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:70
+#, python-format
+msgid "stroke: %s"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:72
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:74
+#, python-format
+msgid "fill: %s"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:85
+msgid "Error in specified color modifiers."
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:88
+msgid "Error in specified colors."
+msgstr ""
+
+#: ../src/controlpanel/model/aboutxo.py:24
+msgid "Not available"
+msgstr ""
+
+#: ../src/controlpanel/model/datetime.py:85
+msgid "Error timezone does not exist."
+msgstr ""
+
+#: ../src/controlpanel/model/frame.py:38 ../src/controlpanel/model/frame.py:60
+msgid "Value must be an integer."
+msgstr ""
+
+#: ../src/controlpanel/model/language.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr ""
+
+#: ../src/controlpanel/model/language.py:104
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr ""
+
+#: ../src/controlpanel/model/language.py:121
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr ""
+
+#: ../src/controlpanel/model/network.py:48
+msgid "You must enter a server."
+msgstr ""
+
+#: ../src/controlpanel/model/network.py:63
+msgid "State is unknown."
+msgstr ""
+
+#: ../src/controlpanel/model/network.py:83
+msgid "Error in specified radio argument use on/off."
+msgstr ""
+
+#: ../src/controlpanel/view/aboutme.py:32
+msgid "About Me"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutme.py:134
+msgid "Click to change your color:"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutxo.py:26
+msgid "About my XO"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutxo.py:47
+msgid "Identity"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutxo.py:56
+msgid "Serial Number:"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutxo.py:79
+msgid "Software"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutxo.py:88
+msgid "Build:"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutxo.py:103
+msgid "Firmware:"
+msgstr ""
+
+#: ../src/controlpanel/view/datetime.py:29
+msgid "Date & Time"
+msgstr ""
+
+#: ../src/controlpanel/view/datetime.py:72
+msgid "Timezone"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:28
+msgid "Frame"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:30
+msgid "never"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:31
+msgid "instantaneous"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:32
+#, python-format
+msgid "%s seconds"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:56
+msgid "Activation Delay"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:80
+msgid "Corner"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:115
+msgid "Edge"
+msgstr ""
+
+#: ../src/controlpanel/view/language.py:29
+#: ../src/controlpanel/view/language.py:74
+msgid "Language"
+msgstr ""
+
+#: ../src/controlpanel/view/network.py:28
+msgid "Network"
+msgstr ""
+
+#: ../src/controlpanel/view/network.py:53
+msgid "Wireless"
+msgstr ""
+
+#: ../src/controlpanel/view/network.py:61
+msgid "Radio:"
+msgstr ""
+
+#: ../src/controlpanel/view/network.py:94
+msgid "Mesh"
+msgstr ""
+
+#: ../src/controlpanel/view/network.py:103
+msgid "Server:"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:108
+msgid "Connected to a School Mesh Portal"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:110
+msgid "Looking for a School Mesh Portal..."
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:113
+msgid "Connected to an XO Mesh Portal"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:115
+msgid "Looking for an XO Mesh Portal..."
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:118
+msgid "Connected to a Simple Mesh"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:120
+msgid "Starting a Simple Mesh"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:127
+msgid "Unknown Mesh"
+msgstr ""
+
+#: ../src/view/frame/activitiestray.py:224
+msgid "Decline"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:351
+msgid "Control Panel"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:362
+msgid "Restart"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:367
+msgid "Shutdown"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:373
+msgid "Register"
+msgstr ""
+
+#: ../src/view/palettes.py:41
+msgid "Starting..."
+msgstr ""
+
+#: ../src/view/palettes.py:71
+msgid "Stop"
+msgstr ""
+
+#: ../src/view/palettes.py:96
+msgid "Start"
+msgstr ""
+
+#: ../src/view/palettes.py:119
+msgid "Remove favorite"
+msgstr ""
+
+#: ../src/view/palettes.py:123
+msgid "Make favorite"
+msgstr ""
+
+#: ../src/view/palettes.py:169
+msgid "Show contents"
+msgstr ""
+
+#: ../src/view/palettes.py:193
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr ""
diff --git a/shell/po/pl.po b/shell/po/pl.po
new file mode 100644
index 0000000..a48e32e
--- /dev/null
+++ b/shell/po/pl.po
@@ -0,0 +1,485 @@
+# translation of pl.po to Polish
+# Piotr Drąg <raven@pmail.pl>, 2007.
+msgid ""
+msgstr ""
+"Project-Id-Version: pl\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-01-18 18:19+0000\n"
+"PO-Revision-Date: 2008-01-07 22:22+0000\n"
+"Last-Translator: Wiktor Idzikowski <wiktor.idzikowski@gmail.com>\n"
+"Language-Team: Polish <pl@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Pootle 1.0.2\n"
+
+#: ../shell/intro/intro.py:77
+msgid "Pick a buddy picture"
+msgstr "Wybierz obrazek znajomego"
+
+#: ../shell/intro/intro.py:100
+msgid "My Picture:"
+msgstr "Mój obrazek:"
+
+#: ../shell/intro/intro.py:180
+msgid "My Name:"
+msgstr "Moje imię:"
+
+#: ../shell/intro/intro.py:224
+msgid "My Color:"
+msgstr "Mój kolor:"
+
+#: ../shell/view/BuddyMenu.py:59
+msgid "Remove friend"
+msgstr "Usuń przyjaciela"
+
+#: ../shell/view/BuddyMenu.py:62
+msgid "Make friend"
+msgstr "Uczyń przyjacielem"
+
+#. FIXME check that the buddy is not in the activity already
+#: ../shell/view/BuddyMenu.py:96
+msgid "Invite"
+msgstr "Zaproś"
+
+#: ../shell/view/clipboardmenu.py:58
+msgid "Remove"
+msgstr "Usuń"
+
+#: ../shell/view/clipboardmenu.py:63
+msgid "Open"
+msgstr "Otwórz"
+
+#: ../shell/view/clipboardmenu.py:76
+msgid "Stop download"
+msgstr "Zatrzymaj pobieranie"
+
+#. self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+#. TODO: Implement stopping downloads
+#. self._stop_item.connect('activate', self._stop_item_activate_cb)
+#. self.append_menu_item(self._stop_item)
+#: ../shell/view/clipboardmenu.py:73
+msgid "Add to journal"
+msgstr "Dodaj do dziennika"
+
+#: ../shell/view/clipboardmenu.py:213
+#, python-format
+msgid "Clipboard object: %s."
+msgstr "Obiekt w schowku: %s."
+
+#: ../services/clipboard/objecttypeservice.py:32
+msgid "Text"
+msgstr "Tekst"
+
+#: ../services/clipboard/objecttypeservice.py:36
+msgid "Image"
+msgstr "Obrazek"
+
+#: ../shell/view/Shell.py:285
+msgid "Screenshot"
+msgstr "Zrzut ekranu"
+
+#: ../shell/view/home/HomeBox.py:164
+msgid "Shutdown"
+msgstr "Zakończ"
+
+#: ../shell/view/home/MeshBox.py:195 ../shell/view/devices/network/mesh.py:37
+#: ../shell/view/devices/network/mesh.py:62
+#: ../shell/view/devices/network/mesh.py:66
+msgid "Mesh Network"
+msgstr "Sieć"
+
+#: ../sugar/activity/activity.py:68
+msgid "Save"
+msgstr "Zapisz"
+
+#: ../sugar/activity/activity.py:74
+msgid "Share"
+msgstr "Podziel się"
+
+#: ../sugar/activity/activity.py:87
+msgid "Close"
+msgstr "Zamknij"
+
+#: ../lib/sugar/activity/activity.py:454
+#, python-format
+msgid "%s Activity"
+msgstr "Aktywność %s"
+
+#: ../shell/intro/intro.py:67
+msgid "Name:"
+msgstr "Imię:"
+
+#: ../shell/intro/intro.py:96
+msgid "Click to change color:"
+msgstr "Kliknij aby zmienić kolor:"
+
+#: ../shell/intro/intro.py:146
+msgid "Back"
+msgstr "Wstecz"
+
+#: ../shell/intro/intro.py:160
+#, fuzzy
+msgid "Done"
+msgstr "Zakończono"
+
+#: ../shell/intro/intro.py:163
+msgid "Next"
+msgstr "Dalej"
+
+#: ../shell/view/BuddyMenu.py:84
+#, python-format
+msgid "Invite to %s"
+msgstr "Zaproś do %s"
+
+#: ../shell/hardware/keydialog.py:149
+msgid "Key Type:"
+msgstr "Rodzaj klucza:"
+
+#: ../shell/hardware/keydialog.py:169
+msgid "Authentication Type:"
+msgstr "Rodzaj uwierzytelniania:"
+
+#: ../shell/hardware/keydialog.py:250
+msgid "Encryption Type:"
+msgstr "Rodzaj szyfrowania:"
+
+#: ../shell/view/home/activitiesdonut.py:90
+#, fuzzy
+msgid "Starting..."
+msgstr "Rozpoczynanie..."
+
+#: ../shell/view/home/activitiesdonut.py:104 ../shell/view/home/MeshBox.py:295
+msgid "Resume"
+msgstr "Wznów"
+
+#: ../shell/view/home/activitiesdonut.py:111
+#: ../lib/sugar/activity/activity.py:132
+msgid "Stop"
+msgstr "Zatrzymaj"
+
+#: ../shell/view/home/HomeBox.py:159
+msgid "Reboot"
+msgstr "Uruchom ponownie"
+
+#: ../shell/view/home/HomeBox.py:170
+msgid "Register"
+msgstr "Zarejestruj"
+
+#. Only show disconnect when there's a mesh device, because mesh takes
+#. priority over the normal wireless device. NM doesn't have a "disconnect"
+#. method for a device either (for various reasons) so this doesn't
+#. have a good mapping
+#: ../shell/view/home/MeshBox.py:90 ../shell/view/home/MeshBox.py:197
+#: ../shell/view/devices/network/wireless.py:113
+#: ../shell/view/devices/network/mesh.py:83
+#, fuzzy
+msgid "Disconnect..."
+msgstr "Rozłączanie..."
+
+#: ../shell/view/home/MeshBox.py:300
+msgid "Join"
+msgstr "Dołącz"
+
+#: ../shell/view/devices/battery.py:38
+#, fuzzy
+msgid "My Battery life"
+msgstr "Życie mojej baterii"
+
+#: ../shell/view/devices/battery.py:94
+msgid "Battery charging"
+msgstr "Ładowanie baterii"
+
+#: ../shell/view/devices/battery.py:96
+msgid "Battery discharging"
+msgstr "Rozładowywanie baterii"
+
+#: ../shell/view/devices/battery.py:98
+msgid "Battery fully charged"
+msgstr "Bateria w pełni naładowana"
+
+#: ../shell/view/devices/network/wireless.py:61
+msgid "Disconnected"
+msgstr "Rozłączony"
+
+#: ../shell/view/devices/network/wireless.py:131
+msgid "Channel"
+msgstr "Kanał"
+
+#: ../shell/view/frame/zoomtoolbar.py:42
+msgid "Neighborhood"
+msgstr "Sąsiedztwo"
+
+#: ../shell/view/frame/zoomtoolbar.py:54
+msgid "Group"
+msgstr "Grupa"
+
+#: ../shell/view/frame/zoomtoolbar.py:66
+msgid "Home"
+msgstr "Dom"
+
+#: ../shell/view/frame/zoomtoolbar.py:78
+msgid "Activity"
+msgstr "Czynność"
+
+#: ../lib/sugar/activity/activity.py:115
+#, fuzzy
+msgid "Share with:"
+msgstr "Podziel się z:"
+
+#: ../lib/sugar/activity/activity.py:117
+msgid "Private"
+msgstr "Prywatny"
+
+#: ../lib/sugar/activity/activity.py:118
+msgid "My Neighborhood"
+msgstr "Moje sąsiedztwo"
+
+#: ../lib/sugar/activity/activity.py:126
+msgid "Keep"
+msgstr "Zachowaj"
+
+#: ../lib/sugar/activity/activity.py:245
+msgid "Undo"
+msgstr "Cofnij"
+
+#: ../lib/sugar/activity/activity.py:250
+msgid "Redo"
+msgstr "Ponów"
+
+#: ../lib/sugar/activity/activity.py:260
+msgid "Copy"
+msgstr "Kopiuj"
+
+#: ../lib/sugar/activity/activity.py:265
+msgid "Paste"
+msgstr "Wklej"
+
+#: ../lib/sugar/activity/activity.py:824
+msgid "Keep error"
+msgstr "Zachowaj błąd"
+
+#: ../lib/sugar/activity/activity.py:825
+msgid "Keep error: all changes will be lost"
+msgstr "Zachowaj błąd: wszystkie zmiany zostaną utracone"
+
+#: ../lib/sugar/activity/activity.py:828
+msgid "Don't stop"
+msgstr "Nie zatrzymuj"
+
+#: ../lib/sugar/activity/activity.py:831
+#, fuzzy
+msgid "Stop anyway"
+msgstr "Zatrzymaj mimo wszystko"
+
+#: ../lib/sugar/graphics/alert.py:164 ../lib/sugar/graphics/alert.py:206
+msgid "Cancel"
+msgstr "Anuluj"
+
+#: ../lib/sugar/graphics/alert.py:168
+msgid "Ok"
+msgstr "Ok"
+
+#: ../lib/sugar/graphics/alert.py:216
+msgid "Continue"
+msgstr "Kontynuuj"
+
+#: ../lib/sugar/graphics/alert.py:244
+msgid "OK"
+msgstr "OK"
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d year"
+msgstr "%d rok"
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d years"
+msgstr "%d lata"
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d month"
+msgstr "%d miesiąc"
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d months"
+msgstr "%d miesiące"
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d week"
+msgstr "%d tydzień"
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d weeks"
+msgstr "%d tygodnie"
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d day"
+msgstr "%d dzień"
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+#, fuzzy
+msgid "%d days"
+msgstr "%d dni"
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hour"
+msgstr "%d godzina"
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hours"
+msgstr "%d godziny"
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minute"
+msgstr "%d minuta"
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minutes"
+msgstr "%d minuty"
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d second"
+msgstr "%d sekunda"
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d seconds"
+msgstr "%d sekundy"
+
+#: ../lib/sugar/graphics/objectchooser.py:191
+msgid " and "
+msgstr "_i_"
+
+#: ../lib/sugar/graphics/objectchooser.py:193
+#, fuzzy
+msgid ", "
+msgstr ", "
+
+#: ../shell/controlpanel/control.py:213
+#, fuzzy
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr "Aby zastosować zmiany należy uruchomić ponownie sugar."
+
+#: ../shell/controlpanel/control.py:267
+#, fuzzy
+msgid "Error in specified color modifiers."
+msgstr "Błąd w podanych modifikatorach kolorów."
+
+#: ../shell/controlpanel/control.py:270
+msgid "Error in specified colors."
+msgstr "Błąd w podanych kolorach."
+
+#: ../shell/controlpanel/control.py:307
+msgid "off"
+msgstr "Wyłączony"
+
+#: ../shell/controlpanel/control.py:309
+msgid "on"
+msgstr "Włączony"
+
+#: ../shell/controlpanel/control.py:310
+#, fuzzy
+msgid "State is unknown."
+msgstr "Nieokreślony stan."
+
+#: ../shell/controlpanel/control.py:332
+#, fuzzy
+msgid "Error in specified radio argument use on/off."
+msgstr "Błąd w podanych arugumentach radia użyj Włącz/Wyłącz"
+
+#: ../shell/controlpanel/control.py:336
+msgid "Permission denied. You need to be root to run this method."
+msgstr "Brak dostępu. Musisz być superużytkownikiem aby użyć tej metody."
+
+#: ../shell/controlpanel/control.py:366
+msgid "Error in reading timezone"
+msgstr "Błąd przy wczytywaniu strefy czasowej"
+
+#: ../shell/controlpanel/control.py:397
+#, python-format
+msgid "Error copying timezone (from %s): %s"
+msgstr "Bład przy wczytywaniu strefy czasowej (z %s): %s"
+
+#: ../shell/controlpanel/control.py:402
+#, python-format
+#, fuzzy
+msgid "Changing permission of timezone: %s"
+msgstr "Zmienianie uprawnień do strefy czasowej: %s"
+
+#: ../shell/controlpanel/control.py:412
+#, fuzzy
+msgid "Error timezone does not exist."
+msgstr "Błąd, strefa czasowa nie istnieje."
+
+#: ../shell/controlpanel/control.py:417 ../shell/controlpanel/control.py:436
+#, python-format
+#, fuzzy
+msgid "Could not access %s. Create standard settings."
+msgstr "Nie można odczytać %s. Stwórz standardowe ustawienia."
+
+#: ../shell/controlpanel/control.py:463
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr "Nie można określić języka dla kodu=%s."
+
+#: ../shell/controlpanel/control.py:473
+#, python-format
+#, fuzzy
+msgid "Sorry I do not speak '%s'."
+msgstr "Przykro mi, nie mówię po '%s'."
+
+#: ../shell/view/devices/network/mesh.py:105
+#, fuzzy
+msgid "Connected to a School Mesh Portal"
+msgstr "Połączono z serwerem szkolnym"
+
+#: ../shell/view/devices/network/mesh.py:107
+#, fuzzy
+msgid "Looking for a School Mesh Portal..."
+msgstr "Szukanie serwera szkolnego..."
+
+#: ../shell/view/devices/network/mesh.py:110
+#, fuzzy
+msgid "Connected to an XO Mesh Portal"
+msgstr "Połączono z portalem XO"
+
+#: ../shell/view/devices/network/mesh.py:112
+#, fuzzy
+msgid "Looking for an XO Mesh Portal..."
+msgstr "Szukanie portalu XO..."
+
+#: ../shell/view/devices/network/mesh.py:115
+#, fuzzy
+msgid "Connected to a Simple Mesh"
+msgstr "Połączono z Simple Mesh"
+
+#: ../shell/view/devices/network/mesh.py:117
+#, fuzzy
+msgid "Starting a Simple Mesh"
+msgstr "Rozpoczynanie Simple Mesh"
+
+#: ../shell/view/devices/network/mesh.py:124
+#, fuzzy
+msgid "Unknown Mesh"
+msgstr "Nieznany Mesh"
+
+#: ../shell/view/home/HomeBox.py:175 ../shell/view/home/HomeBox.py:216
+msgid "About this XO"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:222
+msgid "Not available"
+msgstr ""
diff --git a/shell/po/ps.po b/shell/po/ps.po
new file mode 100644
index 0000000..1af6cf1
--- /dev/null
+++ b/shell/po/ps.po
@@ -0,0 +1,424 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-01-18 18:19+0000\n"
+"PO-Revision-Date: 2008-01-09 11:20+0000\n"
+"Last-Translator: usman mansoor ansari <jalalkut@gmail.com>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Pootle 1.0.2\n"
+
+#: ../shell/intro/intro.py:67
+msgid "Name:"
+msgstr "نوم:"
+
+#: ../shell/intro/intro.py:96
+msgid "Click to change color:"
+msgstr "د رنګ بدلون لپاره ټك كړئ:"
+
+#: ../shell/intro/intro.py:146
+msgid "Back"
+msgstr "شا"
+
+#: ../shell/intro/intro.py:160
+msgid "Done"
+msgstr "هوكې"
+
+#: ../shell/intro/intro.py:163
+msgid "Next"
+msgstr "بل"
+
+#: ../shell/view/BuddyMenu.py:59
+msgid "Remove friend"
+msgstr "ملګرې لرکول"
+
+#: ../shell/view/BuddyMenu.py:62
+msgid "Make friend"
+msgstr "ملګرې جوړول"
+
+#: ../shell/view/BuddyMenu.py:84
+#, python-format
+msgid "Invite to %s"
+msgstr "بلون %s ته"
+
+#: ../shell/view/clipboardmenu.py:58
+msgid "Remove"
+msgstr "لركول"
+
+#: ../shell/view/clipboardmenu.py:63
+msgid "Open"
+msgstr "پرانېستل"
+
+#. self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+#. TODO: Implement stopping downloads
+#. self._stop_item.connect('activate', self._stop_item_activate_cb)
+#. self.append_menu_item(self._stop_item)
+#: ../shell/view/clipboardmenu.py:73
+msgid "Add to journal"
+msgstr "ورځپاڼې ته زياتول"
+
+#: ../shell/view/clipboardmenu.py:213
+#, python-format
+msgid "Clipboard object: %s."
+msgstr "د ټوټه دړې څيزونه: %s."
+
+#: ../shell/hardware/keydialog.py:149
+msgid "Key Type:"
+msgstr "كلي ځېل:"
+
+#: ../shell/hardware/keydialog.py:169
+msgid "Authentication Type:"
+msgstr "د كره توب ځېل:"
+
+#: ../shell/hardware/keydialog.py:250
+msgid "Encryption Type:"
+msgstr "د کوډه کښنه ځېل:"
+
+#: ../shell/view/home/activitiesdonut.py:90
+msgid "Starting..."
+msgstr "پيلونه..."
+
+#: ../shell/view/home/activitiesdonut.py:104 ../shell/view/home/MeshBox.py:295
+msgid "Resume"
+msgstr "کارمخینه"
+
+#: ../shell/view/home/activitiesdonut.py:111
+#: ../lib/sugar/activity/activity.py:132
+msgid "Stop"
+msgstr "تمېدل"
+
+#: ../shell/view/Shell.py:285
+#, fuzzy
+msgid "Screenshot"
+msgstr "پرده انځور"
+
+#: ../shell/view/home/HomeBox.py:159
+msgid "Reboot"
+msgstr "بیاپیلون"
+
+#: ../shell/view/home/HomeBox.py:164
+msgid "Shutdown"
+msgstr "ګلول-بندول"
+
+#: ../shell/view/home/HomeBox.py:170
+msgid "Register"
+msgstr "نومکښل"
+
+#. Only show disconnect when there's a mesh device, because mesh takes
+#. priority over the normal wireless device. NM doesn't have a "disconnect"
+#. method for a device either (for various reasons) so this doesn't
+#. have a good mapping
+#: ../shell/view/home/MeshBox.py:90 ../shell/view/home/MeshBox.py:197
+#: ../shell/view/devices/network/wireless.py:113
+#: ../shell/view/devices/network/mesh.py:83
+msgid "Disconnect..."
+msgstr "ناپيوستل"
+
+#: ../shell/view/home/MeshBox.py:195 ../shell/view/devices/network/mesh.py:37
+#: ../shell/view/devices/network/mesh.py:62
+#: ../shell/view/devices/network/mesh.py:66
+msgid "Mesh Network"
+msgstr "مېش جال"
+
+#: ../shell/view/home/MeshBox.py:300
+msgid "Join"
+msgstr "يوځايېنه"
+
+#: ../shell/view/devices/battery.py:38
+msgid "My Battery life"
+msgstr "زما د بېټرۍ ژوند"
+
+#: ../shell/view/devices/battery.py:94
+msgid "Battery charging"
+msgstr "بېټرۍ چارجېږي"
+
+#: ../shell/view/devices/battery.py:96
+#, fuzzy
+msgid "Battery discharging"
+msgstr "بېټري نه چارجېږي"
+
+#: ../shell/view/devices/battery.py:98
+msgid "Battery fully charged"
+msgstr "بېټري بشپړه چارج شوه"
+
+#: ../shell/view/devices/network/wireless.py:61
+#, fuzzy
+msgid "Disconnected"
+msgstr "ناپيوستل"
+
+#: ../shell/view/devices/network/wireless.py:131
+msgid "Channel"
+msgstr "چېنل"
+
+#: ../shell/view/frame/zoomtoolbar.py:42
+msgid "Neighborhood"
+msgstr "ګاونډېتوب"
+
+#: ../shell/view/frame/zoomtoolbar.py:54
+msgid "Group"
+msgstr "ډله"
+
+#: ../shell/view/frame/zoomtoolbar.py:66
+msgid "Home"
+msgstr "کور"
+
+#: ../shell/view/frame/zoomtoolbar.py:78
+msgid "Activity"
+msgstr "چارندتیا"
+
+#: ../lib/sugar/activity/activity.py:115
+msgid "Share with:"
+msgstr "ونډول له:"
+
+#: ../lib/sugar/activity/activity.py:117
+msgid "Private"
+msgstr "ځاني"
+
+#: ../lib/sugar/activity/activity.py:118
+msgid "My Neighborhood"
+msgstr "زما ګاونډيتوب"
+
+#: ../lib/sugar/activity/activity.py:126
+msgid "Keep"
+msgstr "خوندي كول"
+
+#: ../lib/sugar/activity/activity.py:245
+msgid "Undo"
+msgstr "ناکړ"
+
+#: ../lib/sugar/activity/activity.py:250
+msgid "Redo"
+msgstr "بیاکړ"
+
+#: ../lib/sugar/activity/activity.py:260
+msgid "Copy"
+msgstr "لمېسل"
+
+#: ../lib/sugar/activity/activity.py:265
+msgid "Paste"
+msgstr "سرېښل"
+
+#: ../lib/sugar/activity/activity.py:454
+#, python-format
+msgid "%s Activity"
+msgstr "%s چارندتیا"
+
+#: ../lib/sugar/activity/activity.py:824
+msgid "Keep error"
+msgstr "تېروتنې ساتل"
+
+#: ../lib/sugar/activity/activity.py:825
+msgid "Keep error: all changes will be lost"
+msgstr "تېروتنې ساتل: ټول بدلونونه به له لاسه وركړئ"
+
+#: ../lib/sugar/activity/activity.py:828
+msgid "Don't stop"
+msgstr "مه تمېږه"
+
+#: ../lib/sugar/activity/activity.py:831
+msgid "Stop anyway"
+msgstr "پ هره توګه تمېدل"
+
+#: ../lib/sugar/graphics/alert.py:164 ../lib/sugar/graphics/alert.py:206
+msgid "Cancel"
+msgstr "رنګول"
+
+#: ../lib/sugar/graphics/alert.py:168
+msgid "Ok"
+msgstr "هو"
+
+#: ../lib/sugar/graphics/alert.py:216
+msgid "Continue"
+msgstr "ادامه وركول"
+
+#: ../lib/sugar/graphics/alert.py:244
+msgid "OK"
+msgstr "هو"
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d year"
+msgstr "%d كال"
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d years"
+msgstr "%d كلونه"
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d month"
+msgstr "%d مياشت"
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d months"
+msgstr "%d مياشتې"
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d week"
+msgstr "%d اونۍ"
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d weeks"
+msgstr "%d اونۍ"
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d day"
+msgstr "%d ورځ"
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d days"
+msgstr "%d ورځې"
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hour"
+msgstr "%d ساعت"
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hours"
+msgstr "%d ساعتونه"
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minute"
+msgstr "%d دقيقه"
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minutes"
+msgstr "%d دقيقې"
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d second"
+msgstr "%d ثانيه"
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d seconds"
+msgstr "%d ثانيې"
+
+#: ../lib/sugar/graphics/objectchooser.py:191
+msgid " and "
+msgstr "_او_"
+
+#: ../lib/sugar/graphics/objectchooser.py:193
+msgid ", "
+msgstr "،_"
+
+#: ../shell/controlpanel/control.py:213
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr "د بدلونونو كارولو لپاره تاسې بايد شوګر بیاپیل كړئ.\n"
+
+#: ../shell/controlpanel/control.py:267
+msgid "Error in specified color modifiers."
+msgstr "د بدلوونې رنګ په ټاكنه كۍ تېروتنه."
+
+#: ../shell/controlpanel/control.py:270
+msgid "Error in specified colors."
+msgstr "په مالومو رنګونو كې تېروتنې بدلوونې"
+
+#: ../shell/controlpanel/control.py:307
+msgid "off"
+msgstr "بندول"
+
+#: ../shell/controlpanel/control.py:309
+msgid "on"
+msgstr "روښانول"
+
+#: ../shell/controlpanel/control.py:310
+msgid "State is unknown."
+msgstr "ايالت ناڅرګند دى."
+
+#: ../shell/controlpanel/control.py:332
+#, fuzzy
+msgid "Error in specified radio argument use on/off."
+msgstr "په ټاكلي راډيو كې تېروتنې "
+
+#: ../shell/controlpanel/control.py:336
+#, fuzzy
+msgid "Permission denied. You need to be root to run this method."
+msgstr "پرېښلې رد شو. ددې لېلې چلولو لپاره بايد تاسې ريښه اوسـئ"
+
+#: ../shell/controlpanel/control.py:366
+msgid "Error in reading timezone"
+msgstr "د مهالزون په لوستتنه كې تېروتنې"
+
+#: ../shell/controlpanel/control.py:397
+#, python-format
+msgid "Error copying timezone (from %s): %s"
+msgstr "تېروتنه مهالزون لمېسل (له %s): %s"
+
+#: ../shell/controlpanel/control.py:402
+#, python-format
+msgid "Changing permission of timezone: %s"
+msgstr "مهالزون پرېښلې د بدلون په حال كې: %s"
+
+#: ../shell/controlpanel/control.py:412
+msgid "Error timezone does not exist."
+msgstr "تېروتنه، مهالزون شتون نلري."
+
+#: ../shell/controlpanel/control.py:417 ../shell/controlpanel/control.py:436
+#, python-format
+msgid "Could not access %s. Create standard settings."
+msgstr "%s ته لاسرسې نه كېږي. كره امستنې وپنځوئ"
+
+#: ../shell/controlpanel/control.py:463
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr "د code=%s لپاره ژبه مالومه نكړاى شوه."
+
+#: ../shell/controlpanel/control.py:473
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr "بښنه غواړم زه خبرې نه كوم '%s'."
+
+#: ../shell/view/devices/network/mesh.py:105
+msgid "Connected to a School Mesh Portal"
+msgstr "د ښوونځي مېش ور سره پيوست شو."
+
+#: ../shell/view/devices/network/mesh.py:107
+msgid "Looking for a School Mesh Portal..."
+msgstr "د ښوونځي مېش ور لپاره لټون"
+
+#: ../shell/view/devices/network/mesh.py:110
+msgid "Connected to an XO Mesh Portal"
+msgstr "د XO مېش ور سره پيوست شو."
+
+#: ../shell/view/devices/network/mesh.py:112
+msgid "Looking for an XO Mesh Portal..."
+msgstr "د XO مېش ور لپاره لټون"
+
+#: ../shell/view/devices/network/mesh.py:115
+msgid "Connected to a Simple Mesh"
+msgstr "له ساده مېش سره پيوست شو."
+
+#: ../shell/view/devices/network/mesh.py:117
+msgid "Starting a Simple Mesh"
+msgstr "د ساده مېش پيلېدنه"
+
+#: ../shell/view/devices/network/mesh.py:124
+msgid "Unknown Mesh"
+msgstr "ناڅرګنده مېش"
+
+#: ../shell/view/home/HomeBox.py:175 ../shell/view/home/HomeBox.py:216
+msgid "About this XO"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:222
+msgid "Not available"
+msgstr ""
diff --git a/shell/po/pseudo.po b/shell/po/pseudo.po
new file mode 100644
index 0000000..e184567
--- /dev/null
+++ b/shell/po/pseudo.po
@@ -0,0 +1,517 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-21 00:30-0400\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/intro/intro.py:65 ../src/controlpanel/view/aboutme.py:100
+msgid "Name:"
+msgstr ""
+
+#: ../src/intro/intro.py:94
+msgid "Click to change color:"
+msgstr ""
+
+#: ../src/intro/intro.py:145
+msgid "Back"
+msgstr ""
+
+#: ../src/intro/intro.py:159 ../src/controlpanel/toolbar.py:61
+msgid "Done"
+msgstr ""
+
+#: ../src/intro/intro.py:162
+msgid "Next"
+msgstr ""
+
+#: ../src/view/BuddyMenu.py:58
+msgid "Remove friend"
+msgstr ""
+
+#: ../src/view/BuddyMenu.py:61
+msgid "Make friend"
+msgstr ""
+
+#: ../src/view/BuddyMenu.py:91
+#, python-format
+msgid "Invite to %s"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:48
+msgid "Remove"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:53 ../src/view/clipboardmenu.py:79
+msgid "Open"
+msgstr ""
+
+#. self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+#. TODO: Implement stopping downloads
+#. self._stop_item.connect('activate', self._stop_item_activate_cb)
+#. self.append_menu_item(self._stop_item)
+#: ../src/view/clipboardmenu.py:63
+msgid "Keep"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:84
+msgid "Open with"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:212
+#, python-format
+msgid "Clipboard object: %s."
+msgstr ""
+
+#: ../src/hardware/keydialog.py:150
+msgid "Key Type:"
+msgstr ""
+
+#: ../src/hardware/keydialog.py:170
+msgid "Authentication Type:"
+msgstr ""
+
+#: ../src/hardware/keydialog.py:251
+msgid "Encryption Type:"
+msgstr ""
+
+#: ../src/view/Shell.py:262
+msgid "Screenshot"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:147
+msgid "List view"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:148
+msgid "<Ctrl>L"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:204
+msgid "Favorites view"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:205
+msgid "<Ctrl>R"
+msgstr ""
+
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/view/home/HomeBox.py:211
+msgid "Freeform"
+msgstr ""
+
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/view/home/HomeBox.py:218
+msgid "Ring"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:97
+msgid "Connect"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:106
+msgid "Disconnect"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:118
+msgid "Disconnecting..."
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:152
+msgid "Connecting..."
+msgstr ""
+
+#. TODO: show the channel number
+#: ../src/view/home/MeshBox.py:159
+msgid "Connected"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:211 ../src/view/devices/network/mesh.py:38
+#: ../src/view/devices/network/mesh.py:65
+#: ../src/view/devices/network/mesh.py:69
+msgid "Mesh Network"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:214 ../src/view/devices/network/wireless.py:116
+#: ../src/view/devices/network/mesh.py:86
+msgid "Disconnect..."
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:302 ../src/view/palettes.py:60
+msgid "Resume"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:307 ../src/view/frame/activitiestray.py:219
+msgid "Join"
+msgstr ""
+
+#: ../src/view/devices/battery.py:42
+msgid "My Battery"
+msgstr ""
+
+#: ../src/view/devices/battery.py:111
+msgid "Charging"
+msgstr ""
+
+#: ../src/view/devices/battery.py:114
+msgid "Very little power remaining"
+msgstr ""
+
+#: ../src/view/devices/battery.py:120
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr ""
+
+#: ../src/view/devices/battery.py:124
+msgid "Charged"
+msgstr ""
+
+#: ../src/view/devices/speaker.py:40
+msgid "My Speakers"
+msgstr ""
+
+#: ../src/view/devices/speaker.py:104
+msgid "Unmute"
+msgstr ""
+
+#: ../src/view/devices/speaker.py:107
+msgid "Mute"
+msgstr ""
+
+#: ../src/view/devices/network/wireless.py:64
+msgid "Disconnected"
+msgstr ""
+
+#: ../src/view/devices/network/wireless.py:134
+msgid "Channel"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:34
+msgid "Neighborhood"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:36
+msgid "Group"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:38
+msgid "Home"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:40
+msgid "Activity"
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:26
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:28
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:29
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:33
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" "
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:45
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr ""
+
+#: ../src/controlpanel/toolbar.py:115
+msgid "Cancel"
+msgstr ""
+
+#: ../src/controlpanel/toolbar.py:121
+msgid "Ok"
+msgstr ""
+
+#: ../src/controlpanel/sectionview.py:34 ../src/controlpanel/gui.py:250
+msgid "Changes require restart"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:249
+msgid "Warning"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:253
+msgid "Cancel changes"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:257
+msgid "Later"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:261
+msgid "Restart now"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:44
+msgid "You must enter a name."
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:67
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:70
+#, python-format
+msgid "stroke: %s"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:72
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:74
+#, python-format
+msgid "fill: %s"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:85
+msgid "Error in specified color modifiers."
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:88
+msgid "Error in specified colors."
+msgstr ""
+
+#: ../src/controlpanel/model/aboutxo.py:24
+msgid "Not available"
+msgstr ""
+
+#: ../src/controlpanel/model/datetime.py:85
+msgid "Error timezone does not exist."
+msgstr ""
+
+#: ../src/controlpanel/model/frame.py:38 ../src/controlpanel/model/frame.py:60
+msgid "Value must be an integer."
+msgstr ""
+
+#: ../src/controlpanel/model/language.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr ""
+
+#: ../src/controlpanel/model/language.py:104
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr ""
+
+#: ../src/controlpanel/model/language.py:121
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr ""
+
+#: ../src/controlpanel/model/network.py:48
+msgid "You must enter a server."
+msgstr ""
+
+#: ../src/controlpanel/model/network.py:63
+msgid "State is unknown."
+msgstr ""
+
+#: ../src/controlpanel/model/network.py:83
+msgid "Error in specified radio argument use on/off."
+msgstr ""
+
+#: ../src/controlpanel/view/aboutme.py:32
+msgid "About Me"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutme.py:134
+msgid "Click to change your color:"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutxo.py:26
+msgid "About my XO"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutxo.py:47
+msgid "Identity"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutxo.py:56
+msgid "Serial Number:"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutxo.py:79
+msgid "Software"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutxo.py:88
+msgid "Build:"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutxo.py:103
+msgid "Firmware:"
+msgstr ""
+
+#: ../src/controlpanel/view/datetime.py:29
+msgid "Date & Time"
+msgstr ""
+
+#: ../src/controlpanel/view/datetime.py:72
+msgid "Timezone"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:28
+msgid "Frame"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:30
+msgid "never"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:31
+msgid "instantaneous"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:32
+#, python-format
+msgid "%s seconds"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:56
+msgid "Activation Delay"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:80
+msgid "Corner"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:115
+msgid "Edge"
+msgstr ""
+
+#: ../src/controlpanel/view/language.py:29
+#: ../src/controlpanel/view/language.py:74
+msgid "Language"
+msgstr ""
+
+#: ../src/controlpanel/view/network.py:28
+msgid "Network"
+msgstr ""
+
+#: ../src/controlpanel/view/network.py:53
+msgid "Wireless"
+msgstr ""
+
+#: ../src/controlpanel/view/network.py:61
+msgid "Radio:"
+msgstr ""
+
+#: ../src/controlpanel/view/network.py:94
+msgid "Mesh"
+msgstr ""
+
+#: ../src/controlpanel/view/network.py:103
+msgid "Server:"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:108
+msgid "Connected to a School Mesh Portal"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:110
+msgid "Looking for a School Mesh Portal..."
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:113
+msgid "Connected to an XO Mesh Portal"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:115
+msgid "Looking for an XO Mesh Portal..."
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:118
+msgid "Connected to a Simple Mesh"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:120
+msgid "Starting a Simple Mesh"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:127
+msgid "Unknown Mesh"
+msgstr ""
+
+#: ../src/view/frame/activitiestray.py:224
+msgid "Decline"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:351
+msgid "Control Panel"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:362
+msgid "Restart"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:367
+msgid "Shutdown"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:373
+msgid "Register"
+msgstr ""
+
+#: ../src/view/palettes.py:41
+msgid "Starting..."
+msgstr ""
+
+#: ../src/view/palettes.py:71
+msgid "Stop"
+msgstr ""
+
+#: ../src/view/palettes.py:96
+msgid "Start"
+msgstr ""
+
+#: ../src/view/palettes.py:119
+msgid "Remove favorite"
+msgstr ""
+
+#: ../src/view/palettes.py:123
+msgid "Make favorite"
+msgstr ""
+
+#: ../src/view/palettes.py:169
+msgid "Show contents"
+msgstr ""
+
+#: ../src/view/palettes.py:193
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr ""
diff --git a/shell/po/pt.po b/shell/po/pt.po
new file mode 100644
index 0000000..f2814fb
--- /dev/null
+++ b/shell/po/pt.po
@@ -0,0 +1,1481 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-09-05 00:31-0400\n"
+"PO-Revision-Date: 2009-09-24 19:08-0400\n"
+"Last-Translator: Eduardo H. Silva <HoboPrimate@gmail.com>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: pt\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Pootle 1.2.1\n"
+
+#: ../extensions/cpsection/aboutme/__init__.py:24
+msgid "About Me"
+msgstr "Sobre mim"
+
+#: ../extensions/cpsection/aboutme/model.py:43
+msgid "You must enter a name."
+msgstr "Precisas de introduzir um nome."
+
+#: ../extensions/cpsection/aboutme/model.py:68
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr "traço: cor=%s tonalidade=%s"
+
+#: ../extensions/cpsection/aboutme/model.py:71
+#, python-format
+msgid "stroke: %s"
+msgstr "traço: %s"
+
+#: ../extensions/cpsection/aboutme/model.py:73
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr "preenchimento: cor=%s tonalidade=%s"
+
+#: ../extensions/cpsection/aboutme/model.py:75
+#, python-format
+msgid "fill: %s"
+msgstr "preenchimento: %s"
+
+#: ../extensions/cpsection/aboutme/model.py:86
+msgid "Error in specified color modifiers."
+msgstr "Erro nos modificadores de cor especificados."
+
+#: ../extensions/cpsection/aboutme/model.py:89
+msgid "Error in specified colors."
+msgstr "Erro nas cores especificadas."
+
+#: ../extensions/cpsection/aboutme/view.py:94 ../src/jarabe/intro/window.py:92
+msgid "Name:"
+msgstr "Nome:"
+
+#: ../extensions/cpsection/aboutme/view.py:128
+msgid "Click to change your color:"
+msgstr "Clica para mudar a tua cor:"
+
+#: ../extensions/cpsection/aboutcomputer/__init__.py:21
+msgid "About my Computer"
+msgstr "Sobre o meu Computador"
+
+#: ../extensions/cpsection/aboutcomputer/model.py:28
+msgid "Not available"
+msgstr "Não disponível"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:60
+msgid "Identity"
+msgstr "Identidade"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:69
+msgid "Serial Number:"
+msgstr "Número de Série:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:91
+msgid "Software"
+msgstr "Software"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:100
+msgid "Build:"
+msgstr "Versão do software:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:115
+msgid "Sugar:"
+msgstr "Sugar:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:131
+msgid "Firmware:"
+msgstr "Firmware:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:146
+msgid "Wireless Firmware:"
+msgstr "Firmware da rede sem fios"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:169
+msgid "Copyright and License"
+msgstr "Copyright e Licença"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:184
+msgid ""
+"Sugar is the graphical user interface that you are looking at. Sugar is free "
+"software, covered by the GNU General Public License, and you are welcome to "
+"change it and/or distribute copies of it under certain conditions described "
+"therein."
+msgstr ""
+"O Sugar é a interface gráfica de utilizador que estás a utilizar. O Sugar é "
+"software livre, licenciado sob a GNU General Public License, e és bem vindo "
+"a fazer alterações a ele e/ou a distribuir cópias dele, segundo certas "
+"condições descritas a seguir."
+
+#: ../extensions/cpsection/aboutcomputer/view.py:196
+msgid "Full license:"
+msgstr "Licença Completa:"
+
+#: ../extensions/cpsection/datetime/__init__.py:21
+msgid "Date & Time"
+msgstr "Data & Hora"
+
+#: ../extensions/cpsection/datetime/model.py:87
+msgid "Error timezone does not exist."
+msgstr "Erro: fuso horário não existe."
+
+#: ../extensions/cpsection/datetime/view.py:68 ../data/sugar.schemas.in.h:27
+msgid "Timezone"
+msgstr "Fuso horário"
+
+#: ../extensions/cpsection/frame/__init__.py:21
+msgid "Frame"
+msgstr "Moldura"
+
+#: ../extensions/cpsection/frame/model.py:38
+#: ../extensions/cpsection/frame/model.py:60
+msgid "Value must be an integer."
+msgstr "O valor tem que ser um número inteiro."
+
+#: ../extensions/cpsection/frame/view.py:26
+msgid "never"
+msgstr "nunca"
+
+#: ../extensions/cpsection/frame/view.py:27
+msgid "instantaneous"
+msgstr "instantâneo"
+
+#: ../extensions/cpsection/frame/view.py:28
+#, python-format
+msgid "%s seconds"
+msgstr "%s segundos"
+
+#: ../extensions/cpsection/frame/view.py:52
+msgid "Activation Delay"
+msgstr "Atraso na Activação"
+
+#: ../extensions/cpsection/frame/view.py:76
+msgid "Corner"
+msgstr "Canto"
+
+#: ../extensions/cpsection/frame/view.py:111
+msgid "Edge"
+msgstr "Lado"
+
+#: ../extensions/cpsection/keyboard/__init__.py:21
+#: ../extensions/cpsection/keyboard/view.py:31
+msgid "Keyboard"
+msgstr "Teclado"
+
+#: ../extensions/cpsection/keyboard/view.py:187
+msgid "Keyboard Model"
+msgstr "Modelo de teclado"
+
+#: ../extensions/cpsection/keyboard/view.py:243
+msgid "Key(s) to change layout"
+msgstr "Tecla(s) para alterar disposição"
+
+#: ../extensions/cpsection/keyboard/view.py:311
+msgid "Keyboard Layout(s)"
+msgstr "Disposições de teclado"
+
+#: ../extensions/cpsection/language/__init__.py:21
+#: ../extensions/cpsection/language/view.py:33
+msgid "Language"
+msgstr "Linguagem"
+
+#: ../extensions/cpsection/language/model.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr "Não foi possível aceder a ~/.i18n. Cria definições normais."
+
+#: ../extensions/cpsection/language/model.py:124
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr "Não foi possível determinar a linguagem para o código=%s."
+
+#: ../extensions/cpsection/language/model.py:144
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr "Desculpa, eu não falo '%s'."
+
+#: ../extensions/cpsection/language/view.py:56
+msgid ""
+"Add languages in the order you prefer. If a translation is not available, "
+"the next in the list will be used."
+msgstr ""
+"Adiciona linguagens na ordem que preferires. Se uma tradução não estiver "
+"disponível, será usada a próxima na lista."
+
+#: ../extensions/cpsection/network/__init__.py:21
+#: ../extensions/cpsection/network/view.py:28
+msgid "Network"
+msgstr "Rede"
+
+#: ../extensions/cpsection/network/model.py:79
+msgid "State is unknown."
+msgstr "O estado é desconhecido."
+
+#: ../extensions/cpsection/network/model.py:105
+msgid "Error in specified radio argument use on/off."
+msgstr "Erro no argumento de rádio especificado, utiliza on/off."
+
+#: ../extensions/cpsection/network/model.py:137
+msgid "Error in specified argument use 0/1."
+msgstr "Erro no argumento especificado, utiliza 0/1."
+
+#: ../extensions/cpsection/network/view.py:59
+msgid "Wireless"
+msgstr "Rede sem fios"
+
+#: ../extensions/cpsection/network/view.py:67
+msgid "Turn off the wireless radio to save battery life"
+msgstr "Desliga o rádio da rede sem fios para poupar na carga da bateria"
+
+#: ../extensions/cpsection/network/view.py:80
+msgid "Radio"
+msgstr "Rádio:"
+
+#: ../extensions/cpsection/network/view.py:96
+msgid "Discard network history if you have trouble connecting to the network"
+msgstr "Descarta o histórico da rede se tiveres problemas em ligares-te à rede"
+
+#: ../extensions/cpsection/network/view.py:105
+msgid "Discard network history"
+msgstr "Descartar histórico da rede"
+
+#: ../extensions/cpsection/network/view.py:118
+msgid "Collaboration"
+msgstr "Colaboração"
+
+#: ../extensions/cpsection/network/view.py:126
+msgid ""
+"The server is the equivalent of what room you are in; people on the same "
+"server will be able to see each other, even when they aren't on the same "
+"network."
+msgstr ""
+"O servidor é o equivalente a em que qual quarto estás tu; pessoas no mesmo "
+"servidor poderão ver-se umas às outras, mesmo que não estejam na mesma rede."
+
+#: ../extensions/cpsection/network/view.py:136
+msgid "Server:"
+msgstr "Servidor:"
+
+#: ../extensions/cpsection/power/__init__.py:21
+msgid "Power"
+msgstr "Energia"
+
+#: ../extensions/cpsection/power/model.py:54
+msgid "Error in automatic pm argument, use on/off."
+msgstr "Erro no argumento de gestão de energia automática, utiliza on/off."
+
+#: ../extensions/cpsection/power/model.py:81
+msgid "Error in extreme pm argument, use on/off."
+msgstr "Erro no argumento de gestão de energia extrema, utiliza on/off."
+
+#: ../extensions/cpsection/power/view.py:47
+msgid "Power management"
+msgstr "Gestão de energia"
+
+#: ../extensions/cpsection/power/view.py:57
+msgid "Automatic power management (increases battery life)"
+msgstr "Gestão automática de energia (aumenta o tempo de bateria)"
+
+#: ../extensions/cpsection/power/view.py:85
+msgid ""
+"Extreme power management (disableswireless radio, increases battery life)"
+msgstr ""
+"Gestão extrema de energia (desliga o rádio da rede sem fios, aumenta o tempo "
+"de bateria)"
+
+#: ../extensions/cpsection/updater/__init__.py:21
+msgid "Software update"
+msgstr "Actualização do software"
+
+#: ../extensions/cpsection/updater/view.py:62
+msgid ""
+"Software updates correct errors, eliminate security vulnerabilities, and "
+"provide new features."
+msgstr ""
+"Actualizações do software corrigem erros, eliminam vulnerabilidades de "
+"segurança, e fornecem novas funcionalidades."
+
+#: ../extensions/cpsection/updater/view.py:122
+#, python-format
+msgid "Checking %s..."
+msgstr "A verificar %s..."
+
+#: ../extensions/cpsection/updater/view.py:124
+#, python-format
+msgid "Downloading %s..."
+msgstr "A transferir %s..."
+
+#: ../extensions/cpsection/updater/view.py:126
+#, python-format
+msgid "Updating %s..."
+msgstr "A actualizar %s..."
+
+#: ../extensions/cpsection/updater/view.py:135
+msgid "Your software is up-to-date"
+msgstr "O teu software está actualizado"
+
+#: ../extensions/cpsection/updater/view.py:137
+#, python-format
+msgid "You can install %s update"
+msgid_plural "You can install %s updates"
+msgstr[0] "Podes instalar %s actualização"
+msgstr[1] "Podes instalar %s actualizações"
+
+#: ../extensions/cpsection/updater/view.py:155
+msgid "Checking for updates..."
+msgstr "A verificar actualizações..."
+
+#: ../extensions/cpsection/updater/view.py:160
+msgid "Installing updates..."
+msgstr "A instalar actualizações..."
+
+#: ../extensions/cpsection/updater/view.py:165
+#, python-format
+msgid "%s update was installed"
+msgid_plural "%s updates were installed"
+msgstr[0] "%s actualização foi instalada"
+msgstr[1] "%s actualizações foram instaladas"
+
+#: ../extensions/cpsection/updater/view.py:244
+msgid "Install selected"
+msgstr "Instalação selecionada"
+
+#: ../extensions/cpsection/updater/view.py:265
+#, python-format
+msgid "Download size: %s"
+msgstr "Tamanho da transferência: %s"
+
+#: ../extensions/cpsection/updater/view.py:353
+#, python-format
+msgid "From version %(current)d to %(new)s (Size: %(size)s)"
+msgstr "Da versão %(current)d para a versão %(new)s (Tamanho: %(size)s)"
+
+#. TRANS: download size is 0
+#: ../extensions/cpsection/updater/view.py:373
+msgid "None"
+msgstr "Nenhum"
+
+#. TRANS: download size of very small updates
+#: ../extensions/cpsection/updater/view.py:376
+msgid "1 KB"
+msgstr "1 KB"
+
+#. TRANS: download size of small updates, e.g. '250 KB'
+#: ../extensions/cpsection/updater/view.py:379
+#, python-format
+msgid "%.0f KB"
+msgstr "%.0f KB"
+
+#. TRANS: download size of updates, e.g. '2.3 MB'
+#: ../extensions/cpsection/updater/view.py:382
+#, python-format
+msgid "%.1f MB"
+msgstr "%.1f MB"
+
+#: ../extensions/deviceicon/battery.py:58
+msgid "My Battery"
+msgstr "A minha bateria"
+
+#: ../extensions/deviceicon/battery.py:137
+msgid "Removed"
+msgstr "Removida"
+
+#: ../extensions/deviceicon/battery.py:140
+msgid "Charging"
+msgstr "A recarregar"
+
+#: ../extensions/deviceicon/battery.py:143
+msgid "Very little power remaining"
+msgstr "Resta muito pouca energia"
+
+#: ../extensions/deviceicon/battery.py:149
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr "%(hour)d:%(min).2d restantes"
+
+#: ../extensions/deviceicon/battery.py:152
+msgid "Charged"
+msgstr "Recarregada"
+
+#: ../extensions/deviceicon/network.py:44
+#, python-format
+msgid "IP address: %s"
+msgstr "Endereço IP: %s"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#: ../extensions/deviceicon/network.py:110
+msgid "Disconnect..."
+msgstr "Desligar..."
+
+#: ../extensions/deviceicon/network.py:114
+msgid "Create new wireless network"
+msgstr "Criar uma nova rede sem fios"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#: ../extensions/deviceicon/network.py:120
+#: ../src/jarabe/desktop/meshbox.py:264
+msgid "Connecting..."
+msgstr "A Ligar..."
+
+# TODO: show the channel number
+#: ../extensions/deviceicon/network.py:124
+#: ../extensions/deviceicon/network.py:186
+#: ../src/jarabe/desktop/meshbox.py:270
+msgid "Connected"
+msgstr "Ligado"
+
+#: ../extensions/deviceicon/network.py:146
+msgid "Channel"
+msgstr "Canal"
+
+#: ../extensions/deviceicon/network.py:161
+msgid "Wired Network"
+msgstr "Rede com fios"
+
+#: ../extensions/deviceicon/network.py:189
+msgid "Speed"
+msgstr "Velocidade"
+
+#: ../extensions/deviceicon/network.py:415
+#, python-format
+msgid "%s's network %s"
+msgstr "Rede %s do %s"
+
+#: ../extensions/deviceicon/speaker.py:59
+msgid "My Speakers"
+msgstr "Os meus altifalantes"
+
+#: ../extensions/deviceicon/speaker.py:133
+msgid "Unmute"
+msgstr "Retirar do silêncio"
+
+#: ../extensions/deviceicon/speaker.py:136
+msgid "Mute"
+msgstr "Silenciar"
+
+#: ../extensions/globalkey/screenshot.py:56
+msgid "Mesh"
+msgstr "Mesh"
+
+#: ../extensions/globalkey/screenshot.py:58
+#: ../src/jarabe/frame/zoomtoolbar.py:39
+msgid "Group"
+msgstr "Grupo"
+
+#: ../extensions/globalkey/screenshot.py:60
+#: ../src/jarabe/frame/zoomtoolbar.py:41
+msgid "Home"
+msgstr "Casa"
+
+#: ../extensions/globalkey/screenshot.py:66
+#: ../src/jarabe/frame/zoomtoolbar.py:43
+msgid "Activity"
+msgstr "Actividade"
+
+#: ../extensions/globalkey/screenshot.py:69
+msgid "Screenshot"
+msgstr "Imagem do ecrã"
+
+#: ../extensions/globalkey/screenshot.py:71
+#, python-format
+msgid "Screenshot of \"%s\""
+msgstr "Imagem do ecrã de \"%s\""
+
+#: ../data/sugar.schemas.in.h:1
+msgid "Backup URL"
+msgstr "URL da cópia de segurança"
+
+#: ../data/sugar.schemas.in.h:2
+msgid ""
+"Color for the XO icon that is used throughout the desktop. The string is "
+"composed of the stroke color and fill color, format is that of rbg colors. "
+"Example: #AC32FF,#9A5200"
+msgstr ""
+"A cor do ícone XO que é utilizada em todo o ambiente gráfico. O texto é "
+"composto pela cor de traço e a de preenchimento, e o formato é o das cores "
+"rgb. Por exemplo: #AC32FF, #9A5200"
+
+#: ../data/sugar.schemas.in.h:3
+msgid "Corner Delay"
+msgstr "Atraso nos cantos"
+
+#: ../data/sugar.schemas.in.h:4
+msgid "Delay for the activation of the frame using the corners."
+msgstr "Atraso na activação da moldura utilizando os cantos."
+
+#: ../data/sugar.schemas.in.h:5
+msgid "Delay for the activation of the frame using the edges."
+msgstr "Atraso na activação da moldura utilizando os lados."
+
+#: ../data/sugar.schemas.in.h:6
+msgid "Edge Delay"
+msgstr "Atraso nos lados"
+
+#: ../data/sugar.schemas.in.h:7
+msgid "Favorites Layout"
+msgstr "Posicionamento dos favoritos"
+
+#: ../data/sugar.schemas.in.h:8
+msgid "Favorites resume mode"
+msgstr "Modo de continuação dos favoritos"
+
+#: ../data/sugar.schemas.in.h:9
+msgid ""
+"If TRUE, Sugar will make us searchable for the other users of the Jabber "
+"server."
+msgstr ""
+"Se TRUE (verdadeiro), o Sugar irá tornar-nos pesquisáveis pelos outros "
+"utilizadores do servidor Jabber."
+
+#: ../data/sugar.schemas.in.h:10
+msgid "If TRUE, Sugar will show a \"Log out\" option."
+msgstr ""
+"Se TRUE (verdadeiro), o Sugar irá mostrar uma opção de \"Terminar a sessão\"."
+
+#: ../data/sugar.schemas.in.h:11
+msgid "Jabber Server"
+msgstr "Servidor Jabber"
+
+#: ../data/sugar.schemas.in.h:12
+msgid "Keyboard layouts"
+msgstr "Disposições de teclado"
+
+#: ../data/sugar.schemas.in.h:13
+msgid "Keyboard model"
+msgstr "Modelos de teclado"
+
+#: ../data/sugar.schemas.in.h:14
+msgid "Keyboard options"
+msgstr "Opções de teclado"
+
+#: ../data/sugar.schemas.in.h:15
+msgid "Layout of the favorites view."
+msgstr "Posicionamento da vista de favoritos"
+
+#: ../data/sugar.schemas.in.h:16
+msgid ""
+"List of keyboard layouts. Each entry should be in the form layout(variant)"
+msgstr ""
+"Lista de disposições de teclado. Cada entrada deve estar no formato "
+"disposição(variante)"
+
+#: ../data/sugar.schemas.in.h:17
+msgid "List of keyboard options."
+msgstr "Lista de opções do teclado."
+
+#: ../data/sugar.schemas.in.h:18
+msgid "Power Automatic"
+msgstr "Energia automática"
+
+#: ../data/sugar.schemas.in.h:19
+msgid "Power Automatic."
+msgstr "Energia automática."
+
+#: ../data/sugar.schemas.in.h:20
+msgid "Power Extreme"
+msgstr "Energia extrema"
+
+#: ../data/sugar.schemas.in.h:21
+msgid "Power Extreme."
+msgstr "Energia Extrema."
+
+#: ../data/sugar.schemas.in.h:22
+msgid "Publish to Gadget"
+msgstr "Publicar ao Gadget"
+
+#: ../data/sugar.schemas.in.h:23
+msgid "Setting for muting the sound device."
+msgstr "Definição para silenciar o dispositivo de som"
+
+#: ../data/sugar.schemas.in.h:24
+msgid "Show Log out"
+msgstr "Mostrar terminar a sessão"
+
+#: ../data/sugar.schemas.in.h:25
+msgid "Sound Muted"
+msgstr "Som silenciado"
+
+#: ../data/sugar.schemas.in.h:26
+msgid "The keyboard model to be used"
+msgstr "O modelo de teclado a ser utilizado"
+
+#: ../data/sugar.schemas.in.h:28
+msgid "Timezone setting for the system."
+msgstr "Definição do fuso horário para o sistema."
+
+#: ../data/sugar.schemas.in.h:29
+msgid "Url of the jabber server to use."
+msgstr "O Url do servidor jabber a utilizar."
+
+#: ../data/sugar.schemas.in.h:30
+msgid "Url where the backup is saved to."
+msgstr "O Url onde a cópia de segurança é guardada"
+
+#: ../data/sugar.schemas.in.h:31
+msgid "User Color"
+msgstr "Cor do utilizador"
+
+#: ../data/sugar.schemas.in.h:32
+msgid "User Name"
+msgstr "Nome do utilizador"
+
+#: ../data/sugar.schemas.in.h:33
+msgid "User name that is used throughout the desktop."
+msgstr "Nome do utilizador que é utilizado em todo o ambiente gráfico."
+
+#: ../data/sugar.schemas.in.h:34
+msgid "Volume Level"
+msgstr "Volume do som"
+
+#: ../data/sugar.schemas.in.h:35
+msgid "Volume level for the sound device."
+msgstr "Volume de som para o dispositivo de som."
+
+#: ../data/sugar.schemas.in.h:36
+msgid ""
+"When in resume mode, clicking on a favorite icon will cause the last entry "
+"for that activity to be resumed."
+msgstr ""
+"Quando está no modo de continuação, clicar num ícone favorito irá fazer com "
+"que seja continuada a última entrada dessa actividade."
+
+#: ../src/jarabe/controlpanel/cmd.py:28
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+"sugar-control-panel: AVISO, foram encontradas mais que uma opção com o mesmo "
+"nome: módulo %s: %r "
+
+#: ../src/jarabe/controlpanel/cmd.py:30
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr "sugar-control-panel: key=%s não é uma opção disponível"
+
+#: ../src/jarabe/controlpanel/cmd.py:31
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr "sugar-control-panel: %s"
+
+# TRANS: Translators, there's a empty line at the end of this string,
+# which must appear in the translated string (msgstr) as well.
+#. TRANS: Translators, there's a empty line at the end of this string,
+#. which must appear in the translated string (msgstr) as well.
+#: ../src/jarabe/controlpanel/cmd.py:37
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" -c key clear the current value for the key \n"
+" "
+msgstr ""
+"Utilização: sugar-control-panel [ opção] key [ args ...] \n"
+" Controle do ambiente sugar. \n"
+" Opções: \n"
+" -h mostrar esta mensagem de ajuda e sair \n"
+" -l listar todas as opções disponíveis \n"
+" -h key mostrar informação sobre esta chave \n"
+" -g key obter o valor actual desta chave \n"
+" -s key colocar o valor actual desta chave \n"
+" -c key eliminar o valor actual desta chave \n"
+" "
+
+#: ../src/jarabe/controlpanel/cmd.py:50
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr "Para aplicar as alterações tens que reiniciar o açúcar (sugar).\n"
+
+#: ../src/jarabe/controlpanel/gui.py:280
+msgid "Warning"
+msgstr "Aviso"
+
+#: ../src/jarabe/controlpanel/gui.py:281
+#: ../src/jarabe/controlpanel/sectionview.py:42
+msgid "Changes require restart"
+msgstr "As alterações feitas necessitam que reinicies"
+
+#: ../src/jarabe/controlpanel/gui.py:284
+msgid "Cancel changes"
+msgstr "Cancelar alterações"
+
+#: ../src/jarabe/controlpanel/gui.py:289 ../src/jarabe/desktop/homebox.py:70
+msgid "Later"
+msgstr "Mais tarde"
+
+#: ../src/jarabe/controlpanel/gui.py:293
+msgid "Restart now"
+msgstr "Reiniciar agora"
+
+#: ../src/jarabe/controlpanel/toolbar.py:61 ../src/jarabe/intro/window.py:188
+msgid "Done"
+msgstr "Finalizar"
+
+#: ../src/jarabe/controlpanel/toolbar.py:115
+#: ../src/jarabe/desktop/homebox.py:68
+#: ../src/jarabe/frame/activitiestray.py:726
+#: ../src/jarabe/frame/activitiestray.py:822
+#: ../src/jarabe/frame/activitiestray.py:850
+msgid "Cancel"
+msgstr "Cancelar"
+
+#: ../src/jarabe/controlpanel/toolbar.py:121
+#: ../src/jarabe/desktop/favoritesview.py:332
+msgid "Ok"
+msgstr "Ok"
+
+#: ../src/jarabe/desktop/activitieslist.py:80
+#: ../src/jarabe/journal/listview.py:147
+msgid "Title"
+msgstr "Título"
+
+#: ../src/jarabe/desktop/activitieslist.py:91
+msgid "Version"
+msgstr "Versão"
+
+#: ../src/jarabe/desktop/activitieslist.py:105
+#: ../src/jarabe/journal/listview.py:178
+msgid "Date"
+msgstr "Data"
+
+#: ../src/jarabe/desktop/activitieslist.py:234
+#, python-format
+msgid "Version %s"
+msgstr "Versão %s"
+
+#: ../src/jarabe/desktop/activitieslist.py:355
+msgid "Confirm erase"
+msgstr "Confirma apagar"
+
+#: ../src/jarabe/desktop/activitieslist.py:357
+#, python-format
+msgid "Confirm erase: Do you want to permanently erase %s?"
+msgstr "Confirma apagar: Queres apagar permanentemente %s?"
+
+# self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+# TODO: Implement stopping downloads
+# self._stop_item.connect('activate', self._stop_item_activate_cb)
+# self.append_menu_item(self._stop_item)
+#: ../src/jarabe/desktop/activitieslist.py:361
+#: ../src/jarabe/frame/clipboardmenu.py:62
+#: ../src/jarabe/view/viewsource.py:218
+msgid "Keep"
+msgstr "Guardar"
+
+#: ../src/jarabe/desktop/activitieslist.py:364
+#: ../src/jarabe/desktop/activitieslist.py:407
+#: ../src/jarabe/journal/journaltoolbox.py:360
+#: ../src/jarabe/journal/palettes.py:112
+msgid "Erase"
+msgstr "Apagar"
+
+#: ../src/jarabe/desktop/activitieslist.py:428
+msgid "Remove favorite"
+msgstr "Remover favorito"
+
+#: ../src/jarabe/desktop/activitieslist.py:432
+msgid "Make favorite"
+msgstr "Tornar favorito"
+
+# TRANS: label for the freeform layout in the favorites view
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:116
+msgid "Freeform"
+msgstr "Disposição livre"
+
+# TRANS: label for the ring layout in the favorites view
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:198
+msgid "Ring"
+msgstr "Anel"
+
+# TRANS: label for the spiral layout in the favorites view
+#. TRANS: label for the spiral layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:337
+msgid "Spiral"
+msgstr "Espiral"
+
+# TRANS: label for the box layout in the favorites view
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:404
+msgid "Box"
+msgstr "Caixa"
+
+# TRANS: label for the box layout in the favorites view
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:445
+msgid "Triangle"
+msgstr "Triângulo "
+
+#: ../src/jarabe/desktop/favoritesview.py:323
+msgid "Registration Failed"
+msgstr "O Registo Falhou"
+
+#: ../src/jarabe/desktop/favoritesview.py:324
+#, python-format
+msgid "%s"
+msgstr "%s"
+
+#: ../src/jarabe/desktop/favoritesview.py:326
+msgid "Registration Successful"
+msgstr "O Registo teve sucesso"
+
+#: ../src/jarabe/desktop/favoritesview.py:327
+msgid "You are now registered with your school server."
+msgstr "Estás agora registado com o teu servidor de escola."
+
+#: ../src/jarabe/desktop/favoritesview.py:671
+msgid "Register"
+msgstr "Registar"
+
+#: ../src/jarabe/desktop/homebox.py:63
+msgid "Software Update"
+msgstr "Actualização do software"
+
+#: ../src/jarabe/desktop/homebox.py:64
+msgid "Update your activities to ensure compatibility with your new software"
+msgstr ""
+"Actualiza as tuas actividades para assegurar a compatibilidade com o teu "
+"novo software"
+
+#: ../src/jarabe/desktop/homebox.py:73
+msgid "Check now"
+msgstr "Verificar agora"
+
+#: ../src/jarabe/desktop/homebox.py:192
+msgid "List view"
+msgstr "Vista de lista"
+
+#: ../src/jarabe/desktop/homebox.py:193
+msgid "<Ctrl>2"
+msgstr "<Ctrl>2"
+
+#: ../src/jarabe/desktop/homebox.py:255
+msgid "Favorites view"
+msgstr "Vista de favoritos"
+
+#: ../src/jarabe/desktop/homebox.py:256
+msgid "<Ctrl>1"
+msgstr "<Ctrl>1"
+
+#: ../src/jarabe/desktop/keydialog.py:131
+msgid "Key Type:"
+msgstr "Tipo de chave:"
+
+#: ../src/jarabe/desktop/keydialog.py:151
+msgid "Authentication Type:"
+msgstr "Tipo de Autenticação:"
+
+#: ../src/jarabe/desktop/keydialog.py:215
+msgid "WPA & WPA2 Personal"
+msgstr "WPA e WPA2 pessoal"
+
+#: ../src/jarabe/desktop/keydialog.py:224
+msgid "Wireless Security:"
+msgstr "Segurança da rede sem fios:"
+
+#: ../src/jarabe/desktop/meshbox.py:136
+msgid "Connect"
+msgstr "Ligar"
+
+#: ../src/jarabe/desktop/meshbox.py:140
+msgid "Disconnect"
+msgstr "Desligar"
+
+# TRANS: Action label for resuming an activity.
+#. TRANS: Action label for resuming an activity.
+#: ../src/jarabe/desktop/meshbox.py:466
+#: ../src/jarabe/frame/activitiestray.py:761
+#: ../src/jarabe/journal/journaltoolbox.py:428
+#: ../src/jarabe/journal/palettes.py:72 ../src/jarabe/view/palettes.py:64
+msgid "Resume"
+msgstr "Continuar"
+
+#: ../src/jarabe/desktop/meshbox.py:471
+#: ../src/jarabe/frame/activitiestray.py:235
+msgid "Join"
+msgstr "Juntar-se"
+
+#: ../src/jarabe/desktop/schoolserver.py:103
+msgid "Cannot connect to the server."
+msgstr "Não foi possível ligar ao servidor."
+
+#: ../src/jarabe/desktop/schoolserver.py:108
+msgid "The server could not complete the request."
+msgstr "Não foi possível ao servidor completar o pedido."
+
+#: ../src/jarabe/frame/activitiestray.py:240
+#: ../src/jarabe/frame/activitiestray.py:698
+msgid "Decline"
+msgstr "Recusar"
+
+#: ../src/jarabe/frame/activitiestray.py:650
+#, python-format
+msgid "%dB"
+msgstr "%dB"
+
+#: ../src/jarabe/frame/activitiestray.py:652
+#, python-format
+msgid "%dKB"
+msgstr "%dKB"
+
+#: ../src/jarabe/frame/activitiestray.py:654
+#, python-format
+msgid "%dMB"
+msgstr "%dMB"
+
+#: ../src/jarabe/frame/activitiestray.py:671
+#, python-format
+msgid "%s of %s"
+msgstr "%s de %s"
+
+#: ../src/jarabe/frame/activitiestray.py:683
+#, python-format
+msgid "Transfer from %r"
+msgstr "Transferência de %r"
+
+#: ../src/jarabe/frame/activitiestray.py:693
+msgid "Accept"
+msgstr "Aceitar"
+
+#: ../src/jarabe/frame/activitiestray.py:716
+#: ../src/jarabe/frame/activitiestray.py:840
+#, python-format
+msgid "%s (%s)"
+msgstr "%s (%s)"
+
+#: ../src/jarabe/frame/activitiestray.py:750
+#: ../src/jarabe/frame/activitiestray.py:875
+#, fuzzy
+msgid "Dismiss"
+msgstr "Descartar"
+
+#: ../src/jarabe/frame/activitiestray.py:810
+#, python-format
+msgid "Transfer to %r"
+msgstr "Transferir para %r"
+
+#: ../src/jarabe/frame/clipboardmenu.py:52 ../src/jarabe/view/palettes.py:218
+msgid "Remove"
+msgstr "Remover"
+
+#: ../src/jarabe/frame/clipboardmenu.py:57
+#: ../src/jarabe/frame/clipboardmenu.py:80
+msgid "Open"
+msgstr "Abrir"
+
+#: ../src/jarabe/frame/clipboardmenu.py:85
+msgid "Open with"
+msgstr "Abrir com"
+
+#: ../src/jarabe/frame/clipboardobject.py:49
+#, python-format
+msgid "%s clipping"
+msgstr "Recorte de %s"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:37
+msgid "Neighborhood"
+msgstr "Vizinhança"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:37
+msgid "F1"
+msgstr "F1"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:39
+msgid "F2"
+msgstr "F2"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:41
+msgid "F3"
+msgstr "F3"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:43
+msgid "F4"
+msgstr "F4"
+
+#: ../src/jarabe/intro/window.py:124
+msgid "Click to change color:"
+msgstr "Clica para mudar a cor:"
+
+#: ../src/jarabe/intro/window.py:174 ../src/jarabe/journal/detailview.py:103
+msgid "Back"
+msgstr "Voltar"
+
+#: ../src/jarabe/intro/window.py:191
+msgid "Next"
+msgstr "Próximo"
+
+#: ../src/jarabe/journal/expandedentry.py:164
+#: ../src/jarabe/journal/palettes.py:66
+msgid "Untitled"
+msgstr "Sem título"
+
+#: ../src/jarabe/journal/expandedentry.py:210
+msgid "No preview"
+msgstr "Sem pré-visualização"
+
+#: ../src/jarabe/journal/expandedentry.py:229
+#, python-format
+msgid "Kind: %s"
+msgstr "Tipo: %s"
+
+#: ../src/jarabe/journal/expandedentry.py:229
+msgid "Unknown"
+msgstr "Desconhecido"
+
+#: ../src/jarabe/journal/expandedentry.py:230
+#, python-format
+msgid "Date: %s"
+msgstr "Data: %s"
+
+#: ../src/jarabe/journal/expandedentry.py:231
+#, python-format
+msgid "Size: %s"
+msgstr "Tamanho: %s"
+
+#: ../src/jarabe/journal/expandedentry.py:253 ../src/jarabe/journal/misc.py:92
+msgid "No date"
+msgstr "Sem data"
+
+#: ../src/jarabe/journal/expandedentry.py:260
+msgid "Participants:"
+msgstr "Participantes:"
+
+#: ../src/jarabe/journal/expandedentry.py:283
+msgid "Description:"
+msgstr "Descrição:"
+
+#: ../src/jarabe/journal/expandedentry.py:309
+msgid "Tags:"
+msgstr "Etiquetas:"
+
+#: ../src/jarabe/journal/journalactivity.py:108
+#: ../src/jarabe/journal/volumestoolbar.py:47
+msgid "Journal"
+msgstr "Diário"
+
+#: ../src/jarabe/journal/journaltoolbox.py:67
+msgid "Search"
+msgstr "Procurar"
+
+#: ../src/jarabe/journal/journaltoolbox.py:126
+msgid "Anytime"
+msgstr "Qualquer altura"
+
+#: ../src/jarabe/journal/journaltoolbox.py:128
+msgid "Today"
+msgstr "Hoje"
+
+#: ../src/jarabe/journal/journaltoolbox.py:130
+msgid "Since yesterday"
+msgstr "Desde ontem"
+
+# TRANS: Filter entries modified during the last 7 days.
+#. TRANS: Filter entries modified during the last 7 days.
+#: ../src/jarabe/journal/journaltoolbox.py:132
+msgid "Past week"
+msgstr "Esta semana"
+
+# TRANS: Filter entries modified during the last 30 days.
+#. TRANS: Filter entries modified during the last 30 days.
+#: ../src/jarabe/journal/journaltoolbox.py:134
+msgid "Past month"
+msgstr "Este mês"
+
+# TRANS: Filter entries modified during the last 356 days.
+#. TRANS: Filter entries modified during the last 356 days.
+#: ../src/jarabe/journal/journaltoolbox.py:136
+msgid "Past year"
+msgstr "Este ano"
+
+#: ../src/jarabe/journal/journaltoolbox.py:143
+msgid "Anyone"
+msgstr "Qualquer pessoa"
+
+#: ../src/jarabe/journal/journaltoolbox.py:145
+msgid "My friends"
+msgstr "Os meus amigos"
+
+#: ../src/jarabe/journal/journaltoolbox.py:146
+msgid "My class"
+msgstr "A minha turma"
+
+# TRANS: Item in a combo box that filters by entry type.
+#: ../src/jarabe/journal/journaltoolbox.py:274
+msgid "Anything"
+msgstr "Qualquer coisa"
+
+# TODO: Add "Start with" menu item
+#: ../src/jarabe/journal/journaltoolbox.py:350
+#: ../src/jarabe/journal/palettes.py:90
+msgid "Copy"
+msgstr "Copiar"
+
+# TRANS: Action label for starting an entry.
+#. TRANS: Action label for starting an entry.
+#: ../src/jarabe/journal/journaltoolbox.py:431
+#: ../src/jarabe/journal/palettes.py:75
+msgid "Start"
+msgstr "Iniciar"
+
+#: ../src/jarabe/journal/listview.py:361
+msgid "Your Journal is empty"
+msgstr "O teu Diário está vazio"
+
+#: ../src/jarabe/journal/listview.py:363
+msgid "No matching entries"
+msgstr "Nenhuma entrada encontrada"
+
+#: ../src/jarabe/journal/listview.py:374
+msgid "Clear search"
+msgstr "Limpar a busca"
+
+#: ../src/jarabe/journal/modalalert.py:63
+msgid "Your Journal is full"
+msgstr "O teu Diário está cheio"
+
+#: ../src/jarabe/journal/modalalert.py:67
+msgid "Please delete some old Journal entries to make space for new ones."
+msgstr ""
+"Por favor apaga algumas entradas antigas do teu diário para libertar espaço "
+"para novas."
+
+#: ../src/jarabe/journal/modalalert.py:79
+msgid "Show Journal"
+msgstr "Mostrar o diário"
+
+#: ../src/jarabe/journal/objectchooser.py:146
+msgid "Choose an object"
+msgstr "Escolhe um objecto"
+
+#: ../src/jarabe/journal/objectchooser.py:151
+#: ../src/jarabe/view/viewsource.py:308
+msgid "Close"
+msgstr "Fechar"
+
+#: ../src/jarabe/journal/palettes.py:73
+msgid "Resume with"
+msgstr "Continuar com"
+
+#: ../src/jarabe/journal/palettes.py:76
+msgid "Start with"
+msgstr "Iniciar com"
+
+#: ../src/jarabe/journal/palettes.py:98
+msgid "Send to"
+msgstr "Enviar para"
+
+#: ../src/jarabe/journal/palettes.py:107
+msgid "View Details"
+msgstr "Ver detalhes"
+
+#: ../src/jarabe/journal/palettes.py:185
+msgid "No friends present"
+msgstr "Não está presente nenhum amigo"
+
+#: ../src/jarabe/journal/palettes.py:190
+msgid "No valid connection found"
+msgstr "Não foi encontrada uma ligação válida"
+
+#: ../src/jarabe/journal/palettes.py:218
+msgid "No activity to resume entry"
+msgstr "Não existe nenhuma actividade para continuar a entrada"
+
+#: ../src/jarabe/journal/palettes.py:220
+msgid "No activity to start entry"
+msgstr "Não existe nenhuma actividade para iniciar a entrada"
+
+#: ../src/jarabe/view/buddymenu.py:62
+msgid "Remove friend"
+msgstr "Remover amigo"
+
+#: ../src/jarabe/view/buddymenu.py:65
+msgid "Make friend"
+msgstr "Fazer amigo"
+
+#: ../src/jarabe/view/buddymenu.py:82
+msgid "Shutdown"
+msgstr "Desligar"
+
+#: ../src/jarabe/view/buddymenu.py:90
+msgid "Logout"
+msgstr "Terminar a sessão"
+
+#: ../src/jarabe/view/buddymenu.py:95
+msgid "My Settings"
+msgstr "As minhas definições"
+
+#: ../src/jarabe/view/buddymenu.py:130
+#, python-format
+msgid "Invite to %s"
+msgstr "Convidar para %s"
+
+#: ../src/jarabe/view/palettes.py:45
+msgid "Starting..."
+msgstr "A Iniciar..."
+
+#. TODO: share-with, keep
+#: ../src/jarabe/view/palettes.py:71
+msgid "View Source"
+msgstr "Ver código-fonte"
+
+#: ../src/jarabe/view/palettes.py:82
+msgid "Stop"
+msgstr "Parar"
+
+#: ../src/jarabe/view/palettes.py:122
+msgid "Start new"
+msgstr "Iniciar novo"
+
+#: ../src/jarabe/view/palettes.py:171
+msgid "Show contents"
+msgstr "Mostrar os conteúdos"
+
+#: ../src/jarabe/view/palettes.py:193 ../src/jarabe/view/palettes.py:243
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr "%(free_space)d MB Livres"
+
+#: ../src/jarabe/view/viewsource.py:208
+msgid "Instance Source"
+msgstr "Código-fonte da instância"
+
+#: ../src/jarabe/view/viewsource.py:233
+msgid "Source"
+msgstr "Código-fonte"
+
+#: ../src/jarabe/view/viewsource.py:292
+msgid "Activity Bundle Source"
+msgstr "Código-fonte do pacote de actividade"
+
+#: ../src/jarabe/view/viewsource.py:299
+#, python-format
+msgid "View source: %r"
+msgstr "Ver o código-fonte: %r"
+
+#~ msgid "Cannot obtain data needed for registration."
+#~ msgstr "Não foi possível obter os dados necessários para o registo."
+
+#~ msgid "Unmount"
+#~ msgstr "Remover"
+
+#~ msgid "Restart"
+#~ msgstr "Reiniciar"
+
+#~ msgid ""
+#~ "© 2008 One Laptop per Child Association Inc; Red Hat Inc; and Contributors."
+#~ msgstr ""
+#~ "© 2008 One Laptop per Child Association Inc; Red Hat Inc; e Contribuidores."
+
+#~ msgid "Document"
+#~ msgstr "Documento"
+
+#~ msgid "Resume by default"
+#~ msgstr "Continuar por omissão"
+
+#~ msgid "Encryption Type:"
+#~ msgstr "Tipo de Encriptação:"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#~ msgid "Disconnecting..."
+#~ msgstr "A Desligar..."
+
+#~ msgid "Mesh Network"
+#~ msgstr "Rede Mesh"
+
+#~ msgid "Disconnected"
+#~ msgstr "Desligado"
+
+#~ msgid "About my XO"
+#~ msgstr "Sobre o meu XO"
+
+#~ msgid "Connected to a School Mesh Portal"
+#~ msgstr "Ligado a um Portal Mesh de Escola"
+
+#~ msgid "Looking for a School Mesh Portal..."
+#~ msgstr "À procura de um Portal Mesh de Escola..."
+
+#~ msgid "Connected to an XO Mesh Portal"
+#~ msgstr "Ligado a um Portal Mesh de XO"
+
+#~ msgid "Looking for an XO Mesh Portal..."
+#~ msgstr "À procura de um Portal Mesh de XO..."
+
+#~ msgid "Connected to a Simple Mesh"
+#~ msgstr "Ligado a uma Mesh Simples"
+
+#~ msgid "Starting a Simple Mesh"
+#~ msgstr "A iniciar uma Mesh Simples"
+
+#~ msgid "Unknown Mesh"
+#~ msgstr "Mesh Desconhecida"
+
+#, python-format
+#~ msgid "Clipboard object: %s."
+#~ msgstr "Objecto da Área de Transferência: %s"
+
+#~ msgid "You must enter a server."
+#~ msgstr "Precisas de introduzir um servidor."
+
+#~ msgid "Control Panel"
+#~ msgstr "Painel de Controlo"
+
+#~ msgid "© 2008 One Laptop per Child Assocation "
+#~ msgstr "© 2008 One Laptop per Child Association"
+
+#~ msgid "Sugar is the graphical user interface that "
+#~ msgstr "Sugar é a interface gráfica que estás a utilizar."
+
+#~ msgid "off"
+#~ msgstr "desligar"
+
+#~ msgid "on"
+#~ msgstr "ligar"
+
+#~ msgid "Permission denied. You need to be root to run this method."
+#~ msgstr "Permissão negada. Tens que ser administrador para correr este método."
+
+#~ msgid "Error in reading timezone"
+#~ msgstr "Erro ao ler a zona horária"
+
+#, python-format
+#~ msgid "Error copying timezone (from %s): %s"
+#~ msgstr "Erro ao copiar a zona horária (de %s): %s"
+
+#, python-format
+#~ msgid "Changing permission of timezone: %s"
+#~ msgstr "Modificando permissão da zona horária: %s"
+
+#~ msgid "Add to journal"
+#~ msgstr "Adicionar ao Diário"
+
+#~ msgid "Reboot"
+#~ msgstr "Reiniciar"
+
+#~ msgid "My Battery life"
+#~ msgstr "Energia da bateria"
+
+#~ msgid "Battery charging"
+#~ msgstr "Bateria a carregar"
+
+#~ msgid "Battery discharging"
+#~ msgstr "Bateria a descarregar"
+
+#~ msgid "Battery fully charged"
+#~ msgstr "Bateria totalmente carregada"
+
+#~ msgid "Share with:"
+#~ msgstr "Partilhar com:"
+
+#~ msgid "Private"
+#~ msgstr "Privado"
+
+#~ msgid "My Neighborhood"
+#~ msgstr "Minha Vizinhança"
+
+#~ msgid "Undo"
+#~ msgstr "Desfazer"
+
+#~ msgid "Redo"
+#~ msgstr "Refazer"
+
+#~ msgid "Paste"
+#~ msgstr "Colar"
+
+#, python-format
+#~ msgid "%s Activity"
+#~ msgstr "%s Actividade"
+
+#~ msgid "Keep error"
+#~ msgstr "Guardar erro"
+
+#~ msgid "Keep error: all changes will be lost"
+#~ msgstr "Guardar erro: todas as mudanças serão perdidas"
+
+#~ msgid "Don't stop"
+#~ msgstr "Não pares"
+
+#~ msgid "Stop anyway"
+#~ msgstr "Parar à mesma"
+
+#~ msgid "Continue"
+#~ msgstr "Continuar"
+
+#~ msgid "OK"
+#~ msgstr "OK"
+
+#, python-format
+#~ msgid "%d year"
+#~ msgstr "%d ano"
+
+#, python-format
+#~ msgid "%d years"
+#~ msgstr "%d anos"
+
+#, python-format
+#~ msgid "%d month"
+#~ msgstr "%d mês"
+
+#, python-format
+#~ msgid "%d months"
+#~ msgstr "%d meses"
+
+#, python-format
+#~ msgid "%d week"
+#~ msgstr "%d semana"
+
+#, python-format
+#~ msgid "%d weeks"
+#~ msgstr "%d semanas"
+
+#, python-format
+#~ msgid "%d day"
+#~ msgstr "%d dia"
+
+#, python-format
+#~ msgid "%d days"
+#~ msgstr "%d dias"
+
+#, python-format
+#~ msgid "%d hour"
+#~ msgstr "%d hora"
+
+#, python-format
+#~ msgid "%d hours"
+#~ msgstr "%d horas"
+
+#, python-format
+#~ msgid "%d minute"
+#~ msgstr "%d minuto"
+
+#, python-format
+#~ msgid "%d minutes"
+#~ msgstr "%d minutos"
+
+#, python-format
+#~ msgid "%d second"
+#~ msgstr "%d segundo"
+
+#~ msgid " and "
+#~ msgstr " e "
+
+#~ msgid ", "
+#~ msgstr ", "
diff --git a/shell/po/pt_BR.po b/shell/po/pt_BR.po
new file mode 100644
index 0000000..6e5eb3e
--- /dev/null
+++ b/shell/po/pt_BR.po
@@ -0,0 +1,438 @@
+# translation of olpc-sugar-pt_BR.po to Brazilian Portuguese
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# Diego Búrigo Zacarão <diegobz@gmail.com>, 2007.
+msgid ""
+msgstr ""
+"Project-Id-Version: olpc-sugar-pt_BR\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2007-12-11 21:39+0530\n"
+"PO-Revision-Date: 2008-01-27 18:23-0500\n"
+"Last-Translator: Eduardo H. Silva <HoboPrimate@gmail.com>\n"
+"Language-Team: Brazilian Portuguese <fedora-docs-br@redhat.com>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Pootle 1.0.2\n"
+
+#: ../shell/intro/intro.py:67
+msgid "Name:"
+msgstr "Nome:"
+
+#: ../shell/intro/intro.py:96
+msgid "Click to change color:"
+msgstr "Clique para mudar a cor:"
+
+#: ../shell/intro/intro.py:146
+msgid "Back"
+msgstr "Voltar"
+
+#: ../shell/intro/intro.py:160
+msgid "Done"
+msgstr "Pronto"
+
+#: ../shell/intro/intro.py:163
+msgid "Next"
+msgstr "Próximo"
+
+#: ../shell/view/BuddyMenu.py:84
+msgid "Remove friend"
+msgstr "Remover amigo"
+
+#: ../shell/view/BuddyMenu.py:87
+msgid "Make friend"
+msgstr "Fazer amigo"
+
+#. FIXME check that the buddy is not in the activity already
+#: ../shell/view/BuddyMenu.py:96
+msgid "Invite"
+msgstr "Convidar"
+
+#: ../shell/view/clipboardmenu.py:58
+msgid "Remove"
+msgstr "Remover"
+
+#: ../shell/view/clipboardmenu.py:63
+msgid "Open"
+msgstr "Abrir"
+
+#. self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+#. TODO: Implement stopping downloads
+#. self._stop_item.connect('activate', self._stop_item_activate_cb)
+#. self.append_menu_item(self._stop_item)
+#: ../shell/view/clipboardmenu.py:73
+msgid "Add to journal"
+msgstr "Adicionar ao diário"
+
+#: ../shell/view/clipboardmenu.py:213
+#, python-format
+msgid "Clipboard object: %s."
+msgstr "Objeto da prancheta: %s"
+
+#: ../shell/view/frame/zoomtoolbar.py:42
+msgid "Neighborhood"
+msgstr "Vizinhança"
+
+#: ../shell/view/frame/zoomtoolbar.py:54
+msgid "Group"
+msgstr "Grupo"
+
+#: ../shell/view/frame/zoomtoolbar.py:66
+msgid "Home"
+msgstr "Casa"
+
+#: ../shell/view/frame/zoomtoolbar.py:78
+msgid "Activity"
+msgstr "Atividade"
+
+#: ../services/clipboard/objecttypeservice.py:32
+msgid "Text"
+msgstr "Texto"
+
+#: ../services/clipboard/objecttypeservice.py:36
+msgid "Image"
+msgstr "Imagem"
+
+#: ../shell/view/Shell.py:276
+msgid "Screenshot"
+msgstr "Foto da tela"
+
+#: ../shell/view/home/HomeBox.py:164
+msgid "Shutdown"
+msgstr "Desligar"
+
+#: ../shell/view/home/MeshBox.py:195 ../shell/view/devices/network/mesh.py:37
+#: ../shell/view/devices/network/mesh.py:62
+#: ../shell/view/devices/network/mesh.py:66
+msgid "Mesh Network"
+msgstr "Rede Mesh"
+
+#: ../shell/view/devices/battery.py:38
+msgid "My Battery life"
+msgstr "Carga de minha Bateria"
+
+#: ../shell/view/devices/battery.py:94
+msgid "Battery charging"
+msgstr "Carregando a bateria"
+
+#: ../shell/view/devices/battery.py:96
+msgid "Battery discharging"
+msgstr "Descarregando a bateria"
+
+#: ../shell/view/devices/battery.py:98
+msgid "Battery fully charged"
+msgstr "Bateria completamente carregada"
+
+#: ../lib/sugar/activity/activity.py:122
+msgid "Keep"
+msgstr "Manter"
+
+#: ../sugar/activity/activity.py:74
+msgid "Share"
+msgstr "Compartilhar"
+
+#: ../shell/view/home/activitiesdonut.py:111
+#: ../lib/sugar/activity/activity.py:128
+msgid "Stop"
+msgstr "Parar"
+
+#: ../lib/sugar/activity/activity.py:450
+#, python-format
+msgid "%s Activity"
+msgstr "Atividade %s"
+
+#: ../shell/view/BuddyMenu.py:109
+#, python-format
+msgid "Invite to %s"
+msgstr "Convidar para %s"
+
+#: ../shell/hardware/keydialog.py:149
+msgid "Key Type:"
+msgstr "Tipo de chave:"
+
+#: ../shell/hardware/keydialog.py:169
+msgid "Authentication Type:"
+msgstr "Tipo de Autenticação:"
+
+#: ../shell/hardware/keydialog.py:250
+msgid "Encryption Type:"
+msgstr "Tipo de Encriptação:"
+
+#: ../shell/view/home/activitiesdonut.py:90
+msgid "Starting..."
+msgstr "Iniciando..."
+
+#: ../shell/view/home/activitiesdonut.py:104 ../shell/view/home/MeshBox.py:295
+msgid "Resume"
+msgstr "Continuar"
+
+#: ../shell/view/home/HomeBox.py:159
+msgid "Reboot"
+msgstr "Reiniciar"
+
+#: ../shell/view/home/HomeBox.py:170
+msgid "Register"
+msgstr "Cadastrar"
+
+#. Only show disconnect when there's a mesh device, because mesh takes
+#. priority over the normal wireless device. NM doesn't have a "disconnect"
+#. method for a device either (for various reasons) so this doesn't
+#. have a good mapping
+#: ../shell/view/home/MeshBox.py:90 ../shell/view/home/MeshBox.py:197
+#: ../shell/view/devices/network/wireless.py:113
+#: ../shell/view/devices/network/mesh.py:83
+msgid "Disconnect..."
+msgstr "Desconectar"
+
+#: ../shell/view/devices/network/wireless.py:61
+msgid "Disconnected"
+msgstr "Desconectado"
+
+#: ../shell/view/devices/network/wireless.py:131
+msgid "Channel"
+msgstr "Canal"
+
+#: ../lib/sugar/activity/activity.py:111
+msgid "Share with:"
+msgstr "Partilhar com:"
+
+#: ../lib/sugar/activity/activity.py:113
+msgid "Private"
+msgstr "Privado"
+
+#: ../lib/sugar/activity/activity.py:114
+msgid "My Neighborhood"
+msgstr "Minha Vizinhança"
+
+#: ../lib/sugar/activity/activity.py:241
+msgid "Undo"
+msgstr "Desfazer"
+
+# optei pela forma de "fazer de novo" ao invés de refazer por acreditar que ela seja mais fácil de compreender para crianças.
+#: ../lib/sugar/activity/activity.py:246
+msgid "Redo"
+msgstr "Fazer de novo"
+
+#: ../lib/sugar/activity/activity.py:256
+msgid "Copy"
+msgstr "Copiar"
+
+#: ../lib/sugar/activity/activity.py:261
+msgid "Paste"
+msgstr "Colar"
+
+#: ../lib/sugar/activity/activity.py:820
+msgid "Keep error"
+msgstr "Erro ao manter"
+
+#: ../lib/sugar/activity/activity.py:821
+msgid "Keep error: all changes will be lost"
+msgstr "Erro ao manter: todas as alterações serão desfeitas"
+
+#: ../lib/sugar/activity/activity.py:824
+msgid "Don't stop"
+msgstr "Não pare"
+
+#: ../lib/sugar/activity/activity.py:827
+msgid "Stop anyway"
+msgstr "Pare mesmo assim"
+
+#: ../lib/sugar/graphics/alert.py:164 ../lib/sugar/graphics/alert.py:206
+msgid "Cancel"
+msgstr "Cancelar"
+
+#: ../lib/sugar/graphics/alert.py:168
+msgid "Ok"
+msgstr "Ok"
+
+#: ../lib/sugar/graphics/alert.py:216
+msgid "Continue"
+msgstr "Continuar"
+
+#: ../lib/sugar/graphics/alert.py:244
+msgid "OK"
+msgstr "OK"
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d year"
+msgstr "%d ano"
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d years"
+msgstr "%d anos"
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d month"
+msgstr "%d mês"
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d months"
+msgstr "%d meses"
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d week"
+msgstr "%d semana"
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d weeks"
+msgstr "%d semanas"
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d day"
+msgstr "%d dia"
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d days"
+msgstr "%d dias"
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hour"
+msgstr "%d hora"
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hours"
+msgstr "%d horas"
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minute"
+msgstr "%d minuto"
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minutes"
+msgstr "%d minutos"
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d second"
+msgstr "%d segundo"
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d seconds"
+msgstr "%d segundos"
+
+#: ../lib/sugar/graphics/objectchooser.py:191
+msgid " and "
+msgstr " e "
+
+#: ../lib/sugar/graphics/objectchooser.py:193
+msgid ", "
+msgstr ", "
+
+#: ../shell/controlpanel/control.py:213
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr "Para terminar suas mudanças você deve reiniciar o sugar.\n"
+
+#: ../shell/controlpanel/control.py:267
+msgid "Error in specified color modifiers."
+msgstr "Erro nos alteradores de cor selecionados."
+
+#: ../shell/controlpanel/control.py:270
+msgid "Error in specified colors."
+msgstr "Erro nas cores especificadas."
+
+#: ../shell/controlpanel/control.py:307
+msgid "off"
+msgstr "desligado"
+
+#: ../shell/controlpanel/control.py:309
+msgid "on"
+msgstr "ligado"
+
+#: ../shell/controlpanel/control.py:310
+msgid "State is unknown."
+msgstr "Estado desconhecido"
+
+#: ../shell/controlpanel/control.py:332
+msgid "Error in specified radio argument use on/off."
+msgstr "Erro no argumento de rádio especificado, use on/off."
+
+#: ../shell/controlpanel/control.py:336
+msgid "Permission denied. You need to be root to run this method."
+msgstr ""
+"Permissão negada. Você precisa ser o usuário root para executar esse método."
+
+#: ../shell/controlpanel/control.py:366
+msgid "Error in reading timezone"
+msgstr "Erro ao ler o fuso horário"
+
+#: ../shell/controlpanel/control.py:397
+#, python-format
+msgid "Error copying timezone (from %s): %s"
+msgstr "Erro ao copiar o fuso horário (de %s): %s"
+
+#: ../shell/controlpanel/control.py:402
+#, python-format
+msgid "Changing permission of timezone: %s"
+msgstr "Mudando a permissão do fuso horário: %s"
+
+#: ../shell/controlpanel/control.py:413
+msgid "Error timezone does not exist."
+msgstr "Erro: fuso horário não existe."
+
+#: ../shell/controlpanel/control.py:418 ../shell/controlpanel/control.py:438
+#, python-format
+msgid "Could not access %s. Create standard settings."
+msgstr "Não foi possível acessar %s. Crie configurações padrão."
+
+#: ../shell/controlpanel/control.py:466
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr "Não foi possível determinar a língua para o código=%s."
+
+#: ../shell/controlpanel/control.py:476
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr "Desculpe, eu não falo '%s'."
+
+#: ../shell/view/devices/network/mesh.py:105
+msgid "Connected to a School Mesh Portal"
+msgstr "Conectado ao Portal Mesh da Escola"
+
+#: ../shell/view/devices/network/mesh.py:107
+msgid "Looking for a School Mesh Portal..."
+msgstr "Procurando por um Portal Mesh da Escola..."
+
+#: ../shell/view/devices/network/mesh.py:110
+msgid "Connected to an XO Mesh Portal"
+msgstr "Conectado ao Portal Mesh de um XO"
+
+#: ../shell/view/devices/network/mesh.py:112
+msgid "Looking for an XO Mesh Portal..."
+msgstr "Procurando pelo Portal Mesh de algum XO..."
+
+#: ../shell/view/devices/network/mesh.py:115
+msgid "Connected to a Simple Mesh"
+msgstr "Conectado a uma Mesh Simples"
+
+#: ../shell/view/devices/network/mesh.py:117
+msgid "Starting a Simple Mesh"
+msgstr "Iniciando uma Mesh Simples"
+
+#: ../shell/view/devices/network/mesh.py:124
+msgid "Unknown Mesh"
+msgstr "Mesh desconhecida"
+
+#: ../shell/view/home/HomeBox.py:175 ../shell/view/home/HomeBox.py:216
+msgid "About this XO"
+msgstr "Sobre este XO"
+
+#: ../shell/view/home/HomeBox.py:222
+msgid "Not available"
+msgstr "Não disponível"
+
+#: ../shell/view/home/MeshBox.py:300
+msgid "Join"
+msgstr "Juntar-se"
diff --git a/shell/po/qu.po b/shell/po/qu.po
new file mode 100644
index 0000000..a79e711
--- /dev/null
+++ b/shell/po/qu.po
@@ -0,0 +1,420 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-01-18 18:19+0000\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.0.1\n"
+
+#: ../shell/intro/intro.py:67
+msgid "Name:"
+msgstr ""
+
+#: ../shell/intro/intro.py:96
+msgid "Click to change color:"
+msgstr ""
+
+#: ../shell/intro/intro.py:146
+msgid "Back"
+msgstr ""
+
+#: ../shell/intro/intro.py:160
+msgid "Done"
+msgstr ""
+
+#: ../shell/intro/intro.py:163
+msgid "Next"
+msgstr ""
+
+#: ../shell/view/BuddyMenu.py:59
+msgid "Remove friend"
+msgstr ""
+
+#: ../shell/view/BuddyMenu.py:62
+msgid "Make friend"
+msgstr ""
+
+#: ../shell/view/BuddyMenu.py:84
+#, python-format
+msgid "Invite to %s"
+msgstr ""
+
+#: ../shell/view/clipboardmenu.py:58
+msgid "Remove"
+msgstr ""
+
+#: ../shell/view/clipboardmenu.py:63
+msgid "Open"
+msgstr ""
+
+#. self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+#. TODO: Implement stopping downloads
+#. self._stop_item.connect('activate', self._stop_item_activate_cb)
+#. self.append_menu_item(self._stop_item)
+#: ../shell/view/clipboardmenu.py:73
+msgid "Add to journal"
+msgstr ""
+
+#: ../shell/view/clipboardmenu.py:213
+#, python-format
+msgid "Clipboard object: %s."
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:149
+msgid "Key Type:"
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:169
+msgid "Authentication Type:"
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:250
+msgid "Encryption Type:"
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:90
+msgid "Starting..."
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:104 ../shell/view/home/MeshBox.py:295
+msgid "Resume"
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:111
+#: ../lib/sugar/activity/activity.py:132
+msgid "Stop"
+msgstr ""
+
+#: ../shell/view/Shell.py:285
+msgid "Screenshot"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:159
+msgid "Reboot"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:164
+msgid "Shutdown"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:170
+msgid "Register"
+msgstr ""
+
+#. Only show disconnect when there's a mesh device, because mesh takes
+#. priority over the normal wireless device. NM doesn't have a "disconnect"
+#. method for a device either (for various reasons) so this doesn't
+#. have a good mapping
+#: ../shell/view/home/MeshBox.py:90 ../shell/view/home/MeshBox.py:197
+#: ../shell/view/devices/network/wireless.py:113
+#: ../shell/view/devices/network/mesh.py:83
+msgid "Disconnect..."
+msgstr ""
+
+#: ../shell/view/home/MeshBox.py:195 ../shell/view/devices/network/mesh.py:37
+#: ../shell/view/devices/network/mesh.py:62
+#: ../shell/view/devices/network/mesh.py:66
+msgid "Mesh Network"
+msgstr ""
+
+#: ../shell/view/home/MeshBox.py:300
+msgid "Join"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:38
+msgid "My Battery life"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:94
+msgid "Battery charging"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:96
+msgid "Battery discharging"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:98
+msgid "Battery fully charged"
+msgstr ""
+
+#: ../shell/view/devices/network/wireless.py:61
+msgid "Disconnected"
+msgstr ""
+
+#: ../shell/view/devices/network/wireless.py:131
+msgid "Channel"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:42
+msgid "Neighborhood"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:54
+msgid "Group"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:66
+msgid "Home"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:78
+msgid "Activity"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:115
+msgid "Share with:"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:117
+msgid "Private"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:118
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:126
+msgid "Keep"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:245
+msgid "Undo"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:250
+msgid "Redo"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:260
+msgid "Copy"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:265
+msgid "Paste"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:454
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:824
+msgid "Keep error"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:825
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:828
+msgid "Don't stop"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:831
+msgid "Stop anyway"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:164 ../lib/sugar/graphics/alert.py:206
+msgid "Cancel"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:168
+msgid "Ok"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:216
+msgid "Continue"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:244
+msgid "OK"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d year"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d years"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d month"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d months"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d week"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d weeks"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d day"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d days"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hour"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hours"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minute"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minutes"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d second"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d seconds"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:191
+msgid " and "
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:193
+msgid ", "
+msgstr ""
+
+#: ../shell/controlpanel/control.py:213
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:267
+msgid "Error in specified color modifiers."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:270
+msgid "Error in specified colors."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:307
+msgid "off"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:309
+msgid "on"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:310
+msgid "State is unknown."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:332
+msgid "Error in specified radio argument use on/off."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:336
+msgid "Permission denied. You need to be root to run this method."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:366
+msgid "Error in reading timezone"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:397
+#, python-format
+msgid "Error copying timezone (from %s): %s"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:402
+#, python-format
+msgid "Changing permission of timezone: %s"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:412
+msgid "Error timezone does not exist."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:417 ../shell/controlpanel/control.py:436
+#, python-format
+msgid "Could not access %s. Create standard settings."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:463
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:473
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:105
+msgid "Connected to a School Mesh Portal"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:107
+msgid "Looking for a School Mesh Portal..."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:110
+msgid "Connected to an XO Mesh Portal"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:112
+msgid "Looking for an XO Mesh Portal..."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:115
+msgid "Connected to a Simple Mesh"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:117
+msgid "Starting a Simple Mesh"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:124
+msgid "Unknown Mesh"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:175 ../shell/view/home/HomeBox.py:216
+msgid "About this XO"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:222
+msgid "Not available"
+msgstr ""
diff --git a/shell/po/ro.po b/shell/po/ro.po
new file mode 100644
index 0000000..69b541f
--- /dev/null
+++ b/shell/po/ro.po
@@ -0,0 +1,419 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-01-18 18:19+0000\n"
+"PO-Revision-Date: 2008-01-07 18:20+0000\n"
+"Last-Translator: David Lazar <david@davidlazar.org>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Pootle 1.0.2\n"
+
+#: ../shell/intro/intro.py:67
+msgid "Name:"
+msgstr "Nume:"
+
+#: ../shell/intro/intro.py:96
+msgid "Click to change color:"
+msgstr ""
+
+#: ../shell/intro/intro.py:146
+msgid "Back"
+msgstr "Înapoi"
+
+#: ../shell/intro/intro.py:160
+msgid "Done"
+msgstr "Gata"
+
+#: ../shell/intro/intro.py:163
+msgid "Next"
+msgstr "Următor"
+
+#: ../shell/view/BuddyMenu.py:59
+msgid "Remove friend"
+msgstr "Şterge prieten"
+
+#: ../shell/view/BuddyMenu.py:62
+msgid "Make friend"
+msgstr "Adaugă prieten"
+
+#: ../shell/view/BuddyMenu.py:84
+#, python-format
+msgid "Invite to %s"
+msgstr ""
+
+#: ../shell/view/clipboardmenu.py:58
+msgid "Remove"
+msgstr ""
+
+#: ../shell/view/clipboardmenu.py:63
+msgid "Open"
+msgstr "Deschide"
+
+#. self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+#. TODO: Implement stopping downloads
+#. self._stop_item.connect('activate', self._stop_item_activate_cb)
+#. self.append_menu_item(self._stop_item)
+#: ../shell/view/clipboardmenu.py:73
+msgid "Add to journal"
+msgstr "Adaugă la jurnal"
+
+#: ../shell/view/clipboardmenu.py:213
+#, python-format
+msgid "Clipboard object: %s."
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:149
+msgid "Key Type:"
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:169
+msgid "Authentication Type:"
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:250
+msgid "Encryption Type:"
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:90
+msgid "Starting..."
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:104 ../shell/view/home/MeshBox.py:295
+msgid "Resume"
+msgstr "Reia"
+
+#: ../shell/view/home/activitiesdonut.py:111
+#: ../lib/sugar/activity/activity.py:132
+msgid "Stop"
+msgstr "Oprește"
+
+#: ../shell/view/Shell.py:285
+msgid "Screenshot"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:159
+msgid "Reboot"
+msgstr "Reporneşte"
+
+#: ../shell/view/home/HomeBox.py:164
+msgid "Shutdown"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:170
+msgid "Register"
+msgstr "Înregistrează"
+
+#. Only show disconnect when there's a mesh device, because mesh takes
+#. priority over the normal wireless device. NM doesn't have a "disconnect"
+#. method for a device either (for various reasons) so this doesn't
+#. have a good mapping
+#: ../shell/view/home/MeshBox.py:90 ../shell/view/home/MeshBox.py:197
+#: ../shell/view/devices/network/wireless.py:113
+#: ../shell/view/devices/network/mesh.py:83
+msgid "Disconnect..."
+msgstr ""
+
+#: ../shell/view/home/MeshBox.py:195 ../shell/view/devices/network/mesh.py:37
+#: ../shell/view/devices/network/mesh.py:62
+#: ../shell/view/devices/network/mesh.py:66
+msgid "Mesh Network"
+msgstr ""
+
+#: ../shell/view/home/MeshBox.py:300
+msgid "Join"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:38
+msgid "My Battery life"
+msgstr "Durata bateriei mele"
+
+#: ../shell/view/devices/battery.py:94
+msgid "Battery charging"
+msgstr "Bateria se încarcă"
+
+#: ../shell/view/devices/battery.py:96
+msgid "Battery discharging"
+msgstr "Bateria se descarcă"
+
+#: ../shell/view/devices/battery.py:98
+msgid "Battery fully charged"
+msgstr ""
+
+#: ../shell/view/devices/network/wireless.py:61
+msgid "Disconnected"
+msgstr ""
+
+#: ../shell/view/devices/network/wireless.py:131
+msgid "Channel"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:42
+msgid "Neighborhood"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:54
+msgid "Group"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:66
+msgid "Home"
+msgstr "Acasă"
+
+#: ../shell/view/frame/zoomtoolbar.py:78
+msgid "Activity"
+msgstr "Activitate"
+
+#: ../lib/sugar/activity/activity.py:115
+msgid "Share with:"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:117
+msgid "Private"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:118
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:126
+msgid "Keep"
+msgstr "Păstrează"
+
+#: ../lib/sugar/activity/activity.py:245
+msgid "Undo"
+msgstr "Anulează"
+
+#: ../lib/sugar/activity/activity.py:250
+msgid "Redo"
+msgstr "Refă"
+
+#: ../lib/sugar/activity/activity.py:260
+msgid "Copy"
+msgstr "Copiază"
+
+#: ../lib/sugar/activity/activity.py:265
+msgid "Paste"
+msgstr "Lipeşte"
+
+#: ../lib/sugar/activity/activity.py:454
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:824
+msgid "Keep error"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:825
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:828
+msgid "Don't stop"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:831
+msgid "Stop anyway"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:164 ../lib/sugar/graphics/alert.py:206
+msgid "Cancel"
+msgstr "Anulează"
+
+#: ../lib/sugar/graphics/alert.py:168
+msgid "Ok"
+msgstr "Ok"
+
+#: ../lib/sugar/graphics/alert.py:216
+msgid "Continue"
+msgstr "Continuă"
+
+#: ../lib/sugar/graphics/alert.py:244
+msgid "OK"
+msgstr "OK"
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d year"
+msgstr "%d an"
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d years"
+msgstr "%d ani"
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d month"
+msgstr "%d lună"
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d months"
+msgstr "%d luni"
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d week"
+msgstr "%d săptămână"
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d weeks"
+msgstr "%d săptămâni"
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d day"
+msgstr "%d zi"
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d days"
+msgstr "%d zile"
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hour"
+msgstr "%d oră"
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hours"
+msgstr "%d ore"
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minute"
+msgstr "%d minut"
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minutes"
+msgstr "%d minute"
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d second"
+msgstr "%d secundă"
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d seconds"
+msgstr "%d secunde"
+
+#: ../lib/sugar/graphics/objectchooser.py:191
+msgid " and "
+msgstr " şi "
+
+#: ../lib/sugar/graphics/objectchooser.py:193
+msgid ", "
+msgstr ", "
+
+#: ../shell/controlpanel/control.py:213
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:267
+msgid "Error in specified color modifiers."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:270
+msgid "Error in specified colors."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:307
+msgid "off"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:309
+msgid "on"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:310
+msgid "State is unknown."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:332
+msgid "Error in specified radio argument use on/off."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:336
+msgid "Permission denied. You need to be root to run this method."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:366
+msgid "Error in reading timezone"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:397
+#, python-format
+msgid "Error copying timezone (from %s): %s"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:402
+#, python-format
+msgid "Changing permission of timezone: %s"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:412
+msgid "Error timezone does not exist."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:417 ../shell/controlpanel/control.py:436
+#, python-format
+msgid "Could not access %s. Create standard settings."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:463
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:473
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:105
+msgid "Connected to a School Mesh Portal"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:107
+msgid "Looking for a School Mesh Portal..."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:110
+msgid "Connected to an XO Mesh Portal"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:112
+msgid "Looking for an XO Mesh Portal..."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:115
+msgid "Connected to a Simple Mesh"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:117
+msgid "Starting a Simple Mesh"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:124
+msgid "Unknown Mesh"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:175 ../shell/view/home/HomeBox.py:216
+msgid "About this XO"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:222
+msgid "Not available"
+msgstr ""
diff --git a/shell/po/ru.po b/shell/po/ru.po
new file mode 100644
index 0000000..b49f59a
--- /dev/null
+++ b/shell/po/ru.po
@@ -0,0 +1,420 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-01-18 18:19+0000\n"
+"PO-Revision-Date: 2007-12-23 09:35+0000\n"
+"Last-Translator: Maxim Osipov <maxim.osipov@gmail.com>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Pootle 1.0.2\n"
+
+#: ../shell/intro/intro.py:67
+msgid "Name:"
+msgstr "Имя:"
+
+#: ../shell/intro/intro.py:96
+msgid "Click to change color:"
+msgstr "Кликните для изменения цвета:"
+
+#: ../shell/intro/intro.py:146
+msgid "Back"
+msgstr "Назад"
+
+#: ../shell/intro/intro.py:160
+msgid "Done"
+msgstr "Готово"
+
+#: ../shell/intro/intro.py:163
+msgid "Next"
+msgstr "Далее"
+
+#: ../shell/view/BuddyMenu.py:59
+msgid "Remove friend"
+msgstr "Убрать друга"
+
+#: ../shell/view/BuddyMenu.py:62
+msgid "Make friend"
+msgstr "Создать друга"
+
+#: ../shell/view/BuddyMenu.py:84
+#, python-format
+msgid "Invite to %s"
+msgstr "Пригласить в %s"
+
+#: ../shell/view/clipboardmenu.py:58
+msgid "Remove"
+msgstr "Удалить"
+
+#: ../shell/view/clipboardmenu.py:63
+msgid "Open"
+msgstr "Открыть"
+
+#. self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+#. TODO: Implement stopping downloads
+#. self._stop_item.connect('activate', self._stop_item_activate_cb)
+#. self.append_menu_item(self._stop_item)
+#: ../shell/view/clipboardmenu.py:73
+msgid "Add to journal"
+msgstr "Добавить в журнал"
+
+#: ../shell/view/clipboardmenu.py:213
+#, python-format
+msgid "Clipboard object: %s."
+msgstr "Объект буфера: %s."
+
+#: ../shell/hardware/keydialog.py:149
+msgid "Key Type:"
+msgstr "Тип ключа:"
+
+#: ../shell/hardware/keydialog.py:169
+msgid "Authentication Type:"
+msgstr "Тип аутентификации:"
+
+#: ../shell/hardware/keydialog.py:250
+msgid "Encryption Type:"
+msgstr "Тип шифрования:"
+
+#: ../shell/view/home/activitiesdonut.py:90
+msgid "Starting..."
+msgstr "Запуск..."
+
+#: ../shell/view/home/activitiesdonut.py:104 ../shell/view/home/MeshBox.py:295
+msgid "Resume"
+msgstr "Продолжить"
+
+#: ../shell/view/home/activitiesdonut.py:111
+#: ../lib/sugar/activity/activity.py:132
+msgid "Stop"
+msgstr "Стоп"
+
+#: ../shell/view/Shell.py:285
+msgid "Screenshot"
+msgstr "Снимок экрана"
+
+#: ../shell/view/home/HomeBox.py:159
+msgid "Reboot"
+msgstr "Перезагрузить"
+
+#: ../shell/view/home/HomeBox.py:164
+msgid "Shutdown"
+msgstr "Выключить"
+
+#: ../shell/view/home/HomeBox.py:170
+msgid "Register"
+msgstr "Зарегистрироваться"
+
+#. Only show disconnect when there's a mesh device, because mesh takes
+#. priority over the normal wireless device. NM doesn't have a "disconnect"
+#. method for a device either (for various reasons) so this doesn't
+#. have a good mapping
+#: ../shell/view/home/MeshBox.py:90 ../shell/view/home/MeshBox.py:197
+#: ../shell/view/devices/network/wireless.py:113
+#: ../shell/view/devices/network/mesh.py:83
+msgid "Disconnect..."
+msgstr "Разъединить..."
+
+#: ../shell/view/home/MeshBox.py:195 ../shell/view/devices/network/mesh.py:37
+#: ../shell/view/devices/network/mesh.py:62
+#: ../shell/view/devices/network/mesh.py:66
+msgid "Mesh Network"
+msgstr "Местная Сеть"
+
+#: ../shell/view/home/MeshBox.py:300
+msgid "Join"
+msgstr "Присоединиться"
+
+#: ../shell/view/devices/battery.py:38
+msgid "My Battery life"
+msgstr "Моя Батарейка"
+
+#: ../shell/view/devices/battery.py:94
+msgid "Battery charging"
+msgstr "Батарейка заряжается"
+
+#: ../shell/view/devices/battery.py:96
+msgid "Battery discharging"
+msgstr "Батарейка разряжается"
+
+#: ../shell/view/devices/battery.py:98
+msgid "Battery fully charged"
+msgstr "Батарейка полностью заряжена"
+
+#: ../shell/view/devices/network/wireless.py:61
+msgid "Disconnected"
+msgstr "Не подключен"
+
+#: ../shell/view/devices/network/wireless.py:131
+msgid "Channel"
+msgstr "Канал"
+
+#: ../shell/view/frame/zoomtoolbar.py:42
+msgid "Neighborhood"
+msgstr "Соседи"
+
+#: ../shell/view/frame/zoomtoolbar.py:54
+msgid "Group"
+msgstr "Группа"
+
+#: ../shell/view/frame/zoomtoolbar.py:66
+msgid "Home"
+msgstr "Дом"
+
+#: ../shell/view/frame/zoomtoolbar.py:78
+msgid "Activity"
+msgstr "Активность"
+
+#: ../lib/sugar/activity/activity.py:115
+msgid "Share with:"
+msgstr "Разделить с:"
+
+#: ../lib/sugar/activity/activity.py:117
+msgid "Private"
+msgstr "Личное"
+
+# в русском языке мы не Пишем Каждое Слово с Заглавной Буквы
+#: ../lib/sugar/activity/activity.py:118
+msgid "My Neighborhood"
+msgstr "Мои соседи"
+
+#: ../lib/sugar/activity/activity.py:126
+msgid "Keep"
+msgstr "Хранить"
+
+#: ../lib/sugar/activity/activity.py:245
+msgid "Undo"
+msgstr "Отменить"
+
+#: ../lib/sugar/activity/activity.py:250
+msgid "Redo"
+msgstr "Повторить"
+
+#: ../lib/sugar/activity/activity.py:260
+msgid "Copy"
+msgstr "Копировать"
+
+#: ../lib/sugar/activity/activity.py:265
+msgid "Paste"
+msgstr "Вставить"
+
+#: ../lib/sugar/activity/activity.py:454
+#, python-format
+msgid "%s Activity"
+msgstr "%s Активность"
+
+#: ../lib/sugar/activity/activity.py:824
+msgid "Keep error"
+msgstr "Ошибка сохранения"
+
+#: ../lib/sugar/activity/activity.py:825
+msgid "Keep error: all changes will be lost"
+msgstr "Ошибка сохранения: все изменения будут потеряны"
+
+#: ../lib/sugar/activity/activity.py:828
+msgid "Don't stop"
+msgstr "Не останавливаться"
+
+#: ../lib/sugar/activity/activity.py:831
+msgid "Stop anyway"
+msgstr "Все равно остановиться"
+
+#: ../lib/sugar/graphics/alert.py:164 ../lib/sugar/graphics/alert.py:206
+msgid "Cancel"
+msgstr "Отмена"
+
+#: ../lib/sugar/graphics/alert.py:168
+msgid "Ok"
+msgstr "Ок"
+
+#: ../lib/sugar/graphics/alert.py:216
+msgid "Continue"
+msgstr "Продолжить"
+
+#: ../lib/sugar/graphics/alert.py:244
+msgid "OK"
+msgstr "ОК"
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d year"
+msgstr "%d год"
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d years"
+msgstr "%d лет"
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d month"
+msgstr "%d месяц"
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d months"
+msgstr "%d месяцев"
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d week"
+msgstr "%d неделя"
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d weeks"
+msgstr "%d недель"
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d day"
+msgstr "%d день"
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d days"
+msgstr "%d дней"
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hour"
+msgstr "%d час"
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hours"
+msgstr "%d часов"
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minute"
+msgstr "%d минута"
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minutes"
+msgstr "%d минут"
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d second"
+msgstr "%d секунда"
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d seconds"
+msgstr "%d секунд"
+
+#: ../lib/sugar/graphics/objectchooser.py:191
+msgid " and "
+msgstr " и "
+
+#: ../lib/sugar/graphics/objectchooser.py:193
+msgid ", "
+msgstr ", "
+
+#: ../shell/controlpanel/control.py:213
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr "Чтобы применить изменения перезапустите sugar.\n"
+
+#: ../shell/controlpanel/control.py:267
+msgid "Error in specified color modifiers."
+msgstr "Ошибка в определении цветов."
+
+#: ../shell/controlpanel/control.py:270
+msgid "Error in specified colors."
+msgstr "Ошибка выбора цветов."
+
+#: ../shell/controlpanel/control.py:307
+msgid "off"
+msgstr "выкл"
+
+#: ../shell/controlpanel/control.py:309
+msgid "on"
+msgstr "вкл"
+
+#: ../shell/controlpanel/control.py:310
+msgid "State is unknown."
+msgstr "Состояние неизвестно."
+
+#: ../shell/controlpanel/control.py:332
+msgid "Error in specified radio argument use on/off."
+msgstr "Ошибка выбора, используйте вкл/выкл."
+
+#: ../shell/controlpanel/control.py:336
+msgid "Permission denied. You need to be root to run this method."
+msgstr "Доступ запрещен. Вы должны быть root-ом чтобы выполнить этот метод."
+
+#: ../shell/controlpanel/control.py:366
+msgid "Error in reading timezone"
+msgstr "Ошибка чтения часового пояса."
+
+#: ../shell/controlpanel/control.py:397
+#, python-format
+msgid "Error copying timezone (from %s): %s"
+msgstr "Ошибка копирования часового пояса (из %s): %s"
+
+#: ../shell/controlpanel/control.py:402
+#, python-format
+msgid "Changing permission of timezone: %s"
+msgstr "Изменяются права доступа часового пояса: %s"
+
+#: ../shell/controlpanel/control.py:412
+msgid "Error timezone does not exist."
+msgstr "Ошибка - часовой пояс не существует."
+
+#: ../shell/controlpanel/control.py:417 ../shell/controlpanel/control.py:436
+#, python-format
+msgid "Could not access %s. Create standard settings."
+msgstr "Нет доступа к %s. Создайте стандартные настройки."
+
+#: ../shell/controlpanel/control.py:463
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr "Язык с кодом=%s не определен."
+
+#: ../shell/controlpanel/control.py:473
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr "Я не говорю по '%s'."
+
+#: ../shell/view/devices/network/mesh.py:105
+msgid "Connected to a School Mesh Portal"
+msgstr "Подключен к Местному Школьному Серверу"
+
+#: ../shell/view/devices/network/mesh.py:107
+msgid "Looking for a School Mesh Portal..."
+msgstr "Поиск Местного Школьного Сервера..."
+
+#: ../shell/view/devices/network/mesh.py:110
+msgid "Connected to an XO Mesh Portal"
+msgstr "Подключен к Местному Серверу XO"
+
+#: ../shell/view/devices/network/mesh.py:112
+msgid "Looking for an XO Mesh Portal..."
+msgstr "Поиск Местного Сервера XO..."
+
+#: ../shell/view/devices/network/mesh.py:115
+msgid "Connected to a Simple Mesh"
+msgstr "Подключен к Простой Местной Сети"
+
+#: ../shell/view/devices/network/mesh.py:117
+msgid "Starting a Simple Mesh"
+msgstr "Запуск Простой Местной Сети"
+
+#: ../shell/view/devices/network/mesh.py:124
+msgid "Unknown Mesh"
+msgstr "Неизвестная Местная Сеть"
+
+#: ../shell/view/home/HomeBox.py:175 ../shell/view/home/HomeBox.py:216
+msgid "About this XO"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:222
+msgid "Not available"
+msgstr ""
diff --git a/shell/po/rw.po b/shell/po/rw.po
new file mode 100644
index 0000000..c1809ae
--- /dev/null
+++ b/shell/po/rw.po
@@ -0,0 +1,596 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-07-23 07:44-0400\n"
+"PO-Revision-Date: 2008-08-06 03:51-0400\n"
+"Last-Translator: Carine Umutesi <carine.umutesi@rita.rw>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Pootle 1.1.0rc2\n"
+
+#: ../src/intro/intro.py:65
+#: ../src/controlpanel/view/aboutme.py:100
+msgid "Name:"
+msgstr "Izina"
+
+#: ../src/intro/intro.py:94
+msgid "Click to change color:"
+msgstr "kanda uhindure ibara"
+
+#: ../src/intro/intro.py:145
+msgid "Back"
+msgstr " Inyuma"
+
+#: ../src/intro/intro.py:159
+#: ../src/controlpanel/toolbar.py:61
+msgid "Done"
+msgstr "Icyakozwe"
+
+#: ../src/intro/intro.py:162
+msgid "Next"
+msgstr "Ibikurikira"
+
+#: ../src/view/BuddyMenu.py:58
+msgid "Remove friend"
+msgstr "Gira inshuti"
+
+#: ../src/view/BuddyMenu.py:61
+msgid "Make friend"
+msgstr "Gira inshuti"
+
+#: ../src/view/BuddyMenu.py:91
+#, python-format
+msgid "Invite to %s"
+msgstr "Tumira ku %s"
+
+#: ../src/view/clipboardmenu.py:48
+msgid "Remove"
+msgstr "Kuraho"
+
+#: ../src/view/clipboardmenu.py:53
+#: ../src/view/clipboardmenu.py:79
+msgid "Open"
+msgstr "Fungura"
+
+#. self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+#. TODO: Implement stopping downloads
+#. self._stop_item.connect('activate', self._stop_item_activate_cb)
+#. self.append_menu_item(self._stop_item)
+#: ../src/view/clipboardmenu.py:63
+#: ../src/view/home/HomeBox.py:86
+msgid "Keep"
+msgstr "Gumana"
+
+#: ../src/view/clipboardmenu.py:84
+msgid "Open with"
+msgstr "Fungura ukoresheje"
+
+#: ../src/view/clipboardmenu.py:216
+#, python-format
+msgid "Clipboard object: %s."
+msgstr "object y`ikibaho: %s."
+
+#: ../src/hardware/keydialog.py:150
+msgid "Key Type:"
+msgstr "Ubwoko bw'Urufunguzo:"
+
+#: ../src/hardware/keydialog.py:170
+msgid "Authentication Type:"
+msgstr "Ubwoko bwo Kwivuga:"
+
+#: ../src/hardware/keydialog.py:251
+msgid "Encryption Type:"
+msgstr "Encryption Type:"
+
+#: ../src/view/Shell.py:240
+msgid "Screenshot"
+msgstr "Ekara ntoya"
+
+#: ../src/view/home/HomeBox.py:80
+msgid "Confirm erase"
+msgstr "Emeza gusiba"
+
+#: ../src/view/home/HomeBox.py:82
+#, python-format
+msgid "Confirm erase: Do you want to permanently erase %s?"
+msgstr "Emeza gusiba: urashaka gusiba burundu %s?"
+
+#: ../src/view/home/HomeBox.py:89
+#: ../src/view/palettes.py:120
+msgid "Erase"
+msgstr "Gusiba"
+
+#: ../src/view/home/HomeBox.py:215
+msgid "List view"
+msgstr "Ibigaragara ku rutonde"
+
+#: ../src/view/home/HomeBox.py:216
+#, fuzzy
+msgid "<Ctrl>2"
+msgstr "<Ctrl>2"
+
+#: ../src/view/home/HomeBox.py:273
+msgid "Favorites view"
+msgstr "Reba ibyagushimishije"
+
+#: ../src/view/home/HomeBox.py:274
+#, fuzzy
+msgid "<Ctrl>1"
+msgstr "<Ctrl>1"
+
+# TRANS: label for the freeform layout in the favorites view
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/view/home/HomeBox.py:282
+msgid "Freeform"
+msgstr "Freeform"
+
+# TRANS: label for the ring layout in the favorites view
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/view/home/HomeBox.py:289
+msgid "Ring"
+msgstr "Isone"
+
+#: ../src/view/home/MeshBox.py:97
+msgid "Connect"
+msgstr "Huza"
+
+#: ../src/view/home/MeshBox.py:106
+msgid "Disconnect"
+msgstr "Tandukanya"
+
+#: ../src/view/home/MeshBox.py:118
+msgid "Disconnecting..."
+msgstr "Gutandukanya"
+
+#: ../src/view/home/MeshBox.py:152
+msgid "Connecting..."
+msgstr "Guhuza"
+
+#. TODO: show the channel number
+#: ../src/view/home/MeshBox.py:159
+msgid "Connected"
+msgstr "Byahujwe"
+
+#: ../src/view/home/MeshBox.py:211
+#: ../src/view/devices/network/mesh.py:41
+#: ../src/view/devices/network/mesh.py:68
+#: ../src/view/devices/network/mesh.py:72
+msgid "Mesh Network"
+msgstr "urusobe rwa rezo"
+
+#: ../src/view/home/MeshBox.py:214
+#: ../src/view/devices/network/wireless.py:119
+#: ../src/view/devices/network/mesh.py:89
+msgid "Disconnect..."
+msgstr "Byatandukanijwe"
+
+#: ../src/view/home/MeshBox.py:302
+#: ../src/view/palettes.py:61
+msgid "Resume"
+msgstr "Komeza"
+
+#: ../src/view/home/MeshBox.py:307
+#: ../src/view/frame/activitiestray.py:205
+msgid "Join"
+msgstr "Huza"
+
+#: ../src/view/devices/battery.py:45
+msgid "My Battery"
+msgstr "Batiri yanjye"
+
+#: ../src/view/devices/battery.py:114
+msgid "Charging"
+msgstr "Gusharija "
+
+#: ../src/view/devices/battery.py:117
+msgid "Very little power remaining"
+msgstr "Hasiagaye ingufu nke cyane"
+
+#: ../src/view/devices/battery.py:123
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr "%(hour)d:%(min).2d isigaye"
+
+#: ../src/view/devices/battery.py:127
+msgid "Charged"
+msgstr "irasharije"
+
+#: ../src/view/devices/speaker.py:44
+msgid "My Speakers"
+msgstr "Imizindaro Yanjye"
+
+#: ../src/view/devices/speaker.py:119
+msgid "Unmute"
+msgstr "Unmute"
+
+#: ../src/view/devices/speaker.py:122
+msgid "Mute"
+msgstr "Mute"
+
+#: ../src/view/devices/network/wireless.py:67
+msgid "Disconnected"
+msgstr "Byatandukanijwe"
+
+#: ../src/view/devices/network/wireless.py:137
+msgid "Channel"
+msgstr "Umuyoboro"
+
+#: ../src/view/frame/zoomtoolbar.py:34
+msgid "Neighborhood"
+msgstr "Ubuturanyi"
+
+#: ../src/view/frame/zoomtoolbar.py:36
+msgid "Group"
+msgstr "Itsinda"
+
+#: ../src/view/frame/zoomtoolbar.py:38
+msgid "Home"
+msgstr "Murugo"
+
+#: ../src/view/frame/zoomtoolbar.py:40
+msgid "Activity"
+msgstr "Igikorwa"
+
+#: ../src/controlpanel/cmd.py:26
+#, python-format
+msgid "sugar-control-panel: WARNING, found more than one option with the same name: %s module: %r"
+msgstr "sugar-control-panel: WARNING, found more than one option with the same name: %s module: %r"
+
+#: ../src/controlpanel/cmd.py:28
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr "sugar-control-panel: key=%s not an available option"
+
+#: ../src/controlpanel/cmd.py:29
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr "sugar-control-panel: %s"
+
+#: ../src/controlpanel/cmd.py:33
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" "
+msgstr ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" "
+
+#: ../src/controlpanel/cmd.py:45
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr "To apply your changes you have to restart sugar.\n"
+
+#: ../src/controlpanel/toolbar.py:115
+msgid "Cancel"
+msgstr "Kuraho"
+
+#: ../src/controlpanel/toolbar.py:121
+#: ../src/view/home/favoritesview.py:294
+msgid "Ok"
+msgstr "Nibyo"
+
+#: ../src/controlpanel/sectionview.py:34
+#: ../src/controlpanel/gui.py:260
+msgid "Changes require restart"
+msgstr "impinduka zikenera gusubirwamo"
+
+#: ../src/controlpanel/gui.py:259
+msgid "Warning"
+msgstr "Kwisubiraho"
+
+#: ../src/controlpanel/gui.py:263
+msgid "Cancel changes"
+msgstr "kureka impinduka"
+
+#: ../src/controlpanel/gui.py:267
+msgid "Later"
+msgstr "nyumaho gato"
+
+#: ../src/controlpanel/gui.py:271
+msgid "Restart now"
+msgstr "kongera gutangira ubungubu"
+
+#: ../src/controlpanel/model/aboutme.py:44
+msgid "You must enter a name."
+msgstr "ugomba kwinjiza izina"
+
+#: ../src/controlpanel/model/aboutme.py:69
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr "stroke: ibara=%s hue=%s"
+
+#: ../src/controlpanel/model/aboutme.py:72
+#, python-format
+msgid "stroke: %s"
+msgstr "stroke: %s"
+
+#: ../src/controlpanel/model/aboutme.py:74
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr "fill: color=%s hue=%s"
+
+#: ../src/controlpanel/model/aboutme.py:76
+#, python-format
+msgid "fill: %s"
+msgstr "fill: %s"
+
+#: ../src/controlpanel/model/aboutme.py:87
+msgid "Error in specified color modifiers."
+msgstr "Ikosa muri specified ibara modifiers."
+
+#: ../src/controlpanel/model/aboutme.py:90
+msgid "Error in specified colors."
+msgstr "Ikosa mu mabara yatoranijwe"
+
+#: ../src/controlpanel/model/aboutxo.py:24
+msgid "Not available"
+msgstr "ntago byabashije kuboneka"
+
+#: ../src/controlpanel/model/datetime.py:85
+msgid "Error timezone does not exist."
+msgstr "Ikosa timezonentiribaho."
+
+#: ../src/controlpanel/model/frame.py:38
+#: ../src/controlpanel/model/frame.py:60
+msgid "Value must be an integer."
+msgstr "umubare ugomba kuba udafite undi mubare inyuma yakitso"
+
+#: ../src/controlpanel/model/language.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr "Could not access ~/.i18n. Create standard settings."
+
+#: ../src/controlpanel/model/language.py:104
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr "Ururimi rwa code=%s could not be determined."
+
+#: ../src/controlpanel/model/language.py:121
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr "ihangane sinshoboye kuvuga '%s'"
+
+#: ../src/controlpanel/model/network.py:48
+msgid "You must enter a server."
+msgstr "You must enter a server."
+
+#: ../src/controlpanel/model/network.py:63
+msgid "State is unknown."
+msgstr "States ntizizwi"
+
+#: ../src/controlpanel/model/network.py:83
+msgid "Error in specified radio argument use on/off."
+msgstr "Ikosain specified radio argument use on/off."
+
+#: ../src/controlpanel/model/power.py:57
+msgid "Error in automatic pm argument, use on/off."
+msgstr "Ikosa muri automatic pm argument,koresha on/off."
+
+#: ../src/controlpanel/model/power.py:86
+msgid "Error in extreme pm argument, use on/off."
+msgstr "Ikosa muri extreme pm argument, use on/of."
+
+#: ../src/controlpanel/view/aboutme.py:32
+msgid "About Me"
+msgstr "Ibinyerecyeyeho"
+
+#: ../src/controlpanel/view/aboutme.py:134
+msgid "Click to change your color:"
+msgstr "kanda kugirango uhindure irange"
+
+#: ../src/controlpanel/view/aboutxo.py:26
+msgid "About my XO"
+msgstr "About my XO"
+
+#: ../src/controlpanel/view/aboutxo.py:47
+msgid "Identity"
+msgstr "Erekana"
+
+#: ../src/controlpanel/view/aboutxo.py:56
+msgid "Serial Number:"
+msgstr "Serial Number:"
+
+#: ../src/controlpanel/view/aboutxo.py:79
+msgid "Software"
+msgstr "porogurame"
+
+#: ../src/controlpanel/view/aboutxo.py:88
+msgid "Build:"
+msgstr "Kubaka"
+
+#: ../src/controlpanel/view/aboutxo.py:103
+msgid "Firmware:"
+msgstr "Firmware:"
+
+#: ../src/controlpanel/view/datetime.py:29
+msgid "Date & Time"
+msgstr "Itariki n`igihe"
+
+#: ../src/controlpanel/view/datetime.py:72
+msgid "Timezone"
+msgstr "Timezone"
+
+#: ../src/controlpanel/view/frame.py:28
+msgid "Frame"
+msgstr "Frame"
+
+#: ../src/controlpanel/view/frame.py:30
+msgid "never"
+msgstr "ntibizongere"
+
+#: ../src/controlpanel/view/frame.py:31
+msgid "instantaneous"
+msgstr "instantaneous"
+
+#: ../src/controlpanel/view/frame.py:32
+#, python-format
+msgid "%s seconds"
+msgstr "%s seconds"
+
+#: ../src/controlpanel/view/frame.py:56
+msgid "Activation Delay"
+msgstr "Activation Delay"
+
+#: ../src/controlpanel/view/frame.py:80
+msgid "Corner"
+msgstr "Inguni"
+
+#: ../src/controlpanel/view/frame.py:115
+msgid "Edge"
+msgstr "Inshonda"
+
+#: ../src/controlpanel/view/language.py:29
+#: ../src/controlpanel/view/language.py:74
+msgid "Language"
+msgstr "Ururimi"
+
+#: ../src/controlpanel/view/network.py:28
+msgid "Network"
+msgstr "Rezo"
+
+#: ../src/controlpanel/view/network.py:53
+msgid "Wireless"
+msgstr "Rezo itagira intsinga"
+
+#: ../src/controlpanel/view/network.py:61
+msgid "Radio:"
+msgstr "Isakaza majwi"
+
+#: ../src/controlpanel/view/network.py:94
+msgid "Mesh"
+msgstr "Urusobekerane"
+
+#: ../src/controlpanel/view/network.py:103
+msgid "Server:"
+msgstr "Server:"
+
+#: ../src/controlpanel/view/power.py:27
+msgid "Power"
+msgstr "Ingufu"
+
+#: ../src/controlpanel/view/power.py:51
+msgid "Power management"
+msgstr "Ubugenzuzi bw`ingufu"
+
+#: ../src/controlpanel/view/power.py:61
+msgid "Automatic power management (increases battery life)"
+msgstr "Ubugenzuzi bw`ingufu buri otomatike (ongera ubuzima bwa batiri)"
+
+#: ../src/controlpanel/view/power.py:89
+msgid "Extreme power management (disables wireless radio, increases battery life)"
+msgstr "Extreme power management (disables wireless radio, increases battery life)"
+
+#: ../src/view/devices/network/mesh.py:111
+msgid "Connected to a School Mesh Portal"
+msgstr "Connected to a School Mesh Portal"
+
+#: ../src/view/devices/network/mesh.py:113
+msgid "Looking for a School Mesh Portal..."
+msgstr "Looking for a School Mesh Portal..."
+
+#: ../src/view/devices/network/mesh.py:116
+msgid "Connected to an XO Mesh Portal"
+msgstr "Connected to an XO Mesh Portal"
+
+#: ../src/view/devices/network/mesh.py:118
+msgid "Looking for an XO Mesh Portal..."
+msgstr "Looking for an XO Mesh Portal..."
+
+#: ../src/view/devices/network/mesh.py:121
+msgid "Connected to a Simple Mesh"
+msgstr "Connected to a Simple Mesh"
+
+#: ../src/view/devices/network/mesh.py:123
+msgid "Starting a Simple Mesh"
+msgstr "Starting a Simple Mesh"
+
+#: ../src/view/devices/network/mesh.py:130
+msgid "Unknown Mesh"
+msgstr "isobekerane ritazwi"
+
+#: ../src/view/frame/activitiestray.py:210
+msgid "Decline"
+msgstr "kugabanyuka kwikintu"
+
+#: ../src/view/home/favoritesview.py:285
+msgid "Registration Failed"
+msgstr "Kwiyandikisha birananiranye"
+
+#: ../src/view/home/favoritesview.py:286
+#, python-format
+msgid "%s"
+msgstr "%s"
+
+#: ../src/view/home/favoritesview.py:288
+msgid "Registration Successful"
+msgstr "Kwiyandikisha byatunganye"
+
+#: ../src/view/home/favoritesview.py:289
+msgid "You are now registered with your school server."
+msgstr "Ubu wanditswe muri server y`ishuri"
+
+#: ../src/view/home/favoritesview.py:405
+msgid "Control Panel"
+msgstr "Control Panel"
+
+#: ../src/view/home/favoritesview.py:416
+msgid "Restart"
+msgstr "kongera gutangira"
+
+#: ../src/view/home/favoritesview.py:421
+msgid "Shutdown"
+msgstr "kuzimya"
+
+#: ../src/view/home/favoritesview.py:427
+msgid "Register"
+msgstr "kwiyandikisha"
+
+#: ../src/view/palettes.py:42
+msgid "Starting..."
+msgstr "Gutangira..."
+
+#: ../src/view/palettes.py:72
+msgid "Stop"
+msgstr "Hagarara"
+
+#: ../src/view/palettes.py:104
+msgid "Start"
+msgstr "Tangira"
+
+#: ../src/view/palettes.py:132
+msgid "Remove favorite"
+msgstr "Kuraho icyo ukunda"
+
+#: ../src/view/palettes.py:136
+msgid "Make favorite"
+msgstr "Kora icyo ukunda"
+
+#: ../src/view/palettes.py:185
+msgid "Show contents"
+msgstr "Erekana ibigize"
+
+#: ../src/view/palettes.py:209
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr "%(free_space)d MB Free"
+
+#~ msgid "<Ctrl>R"
+#~ msgstr "<Ctrl>R"
diff --git a/shell/po/sd.po b/shell/po/sd.po
new file mode 100644
index 0000000..e184567
--- /dev/null
+++ b/shell/po/sd.po
@@ -0,0 +1,517 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-21 00:30-0400\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/intro/intro.py:65 ../src/controlpanel/view/aboutme.py:100
+msgid "Name:"
+msgstr ""
+
+#: ../src/intro/intro.py:94
+msgid "Click to change color:"
+msgstr ""
+
+#: ../src/intro/intro.py:145
+msgid "Back"
+msgstr ""
+
+#: ../src/intro/intro.py:159 ../src/controlpanel/toolbar.py:61
+msgid "Done"
+msgstr ""
+
+#: ../src/intro/intro.py:162
+msgid "Next"
+msgstr ""
+
+#: ../src/view/BuddyMenu.py:58
+msgid "Remove friend"
+msgstr ""
+
+#: ../src/view/BuddyMenu.py:61
+msgid "Make friend"
+msgstr ""
+
+#: ../src/view/BuddyMenu.py:91
+#, python-format
+msgid "Invite to %s"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:48
+msgid "Remove"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:53 ../src/view/clipboardmenu.py:79
+msgid "Open"
+msgstr ""
+
+#. self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+#. TODO: Implement stopping downloads
+#. self._stop_item.connect('activate', self._stop_item_activate_cb)
+#. self.append_menu_item(self._stop_item)
+#: ../src/view/clipboardmenu.py:63
+msgid "Keep"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:84
+msgid "Open with"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:212
+#, python-format
+msgid "Clipboard object: %s."
+msgstr ""
+
+#: ../src/hardware/keydialog.py:150
+msgid "Key Type:"
+msgstr ""
+
+#: ../src/hardware/keydialog.py:170
+msgid "Authentication Type:"
+msgstr ""
+
+#: ../src/hardware/keydialog.py:251
+msgid "Encryption Type:"
+msgstr ""
+
+#: ../src/view/Shell.py:262
+msgid "Screenshot"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:147
+msgid "List view"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:148
+msgid "<Ctrl>L"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:204
+msgid "Favorites view"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:205
+msgid "<Ctrl>R"
+msgstr ""
+
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/view/home/HomeBox.py:211
+msgid "Freeform"
+msgstr ""
+
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/view/home/HomeBox.py:218
+msgid "Ring"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:97
+msgid "Connect"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:106
+msgid "Disconnect"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:118
+msgid "Disconnecting..."
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:152
+msgid "Connecting..."
+msgstr ""
+
+#. TODO: show the channel number
+#: ../src/view/home/MeshBox.py:159
+msgid "Connected"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:211 ../src/view/devices/network/mesh.py:38
+#: ../src/view/devices/network/mesh.py:65
+#: ../src/view/devices/network/mesh.py:69
+msgid "Mesh Network"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:214 ../src/view/devices/network/wireless.py:116
+#: ../src/view/devices/network/mesh.py:86
+msgid "Disconnect..."
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:302 ../src/view/palettes.py:60
+msgid "Resume"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:307 ../src/view/frame/activitiestray.py:219
+msgid "Join"
+msgstr ""
+
+#: ../src/view/devices/battery.py:42
+msgid "My Battery"
+msgstr ""
+
+#: ../src/view/devices/battery.py:111
+msgid "Charging"
+msgstr ""
+
+#: ../src/view/devices/battery.py:114
+msgid "Very little power remaining"
+msgstr ""
+
+#: ../src/view/devices/battery.py:120
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr ""
+
+#: ../src/view/devices/battery.py:124
+msgid "Charged"
+msgstr ""
+
+#: ../src/view/devices/speaker.py:40
+msgid "My Speakers"
+msgstr ""
+
+#: ../src/view/devices/speaker.py:104
+msgid "Unmute"
+msgstr ""
+
+#: ../src/view/devices/speaker.py:107
+msgid "Mute"
+msgstr ""
+
+#: ../src/view/devices/network/wireless.py:64
+msgid "Disconnected"
+msgstr ""
+
+#: ../src/view/devices/network/wireless.py:134
+msgid "Channel"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:34
+msgid "Neighborhood"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:36
+msgid "Group"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:38
+msgid "Home"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:40
+msgid "Activity"
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:26
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:28
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:29
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:33
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" "
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:45
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr ""
+
+#: ../src/controlpanel/toolbar.py:115
+msgid "Cancel"
+msgstr ""
+
+#: ../src/controlpanel/toolbar.py:121
+msgid "Ok"
+msgstr ""
+
+#: ../src/controlpanel/sectionview.py:34 ../src/controlpanel/gui.py:250
+msgid "Changes require restart"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:249
+msgid "Warning"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:253
+msgid "Cancel changes"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:257
+msgid "Later"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:261
+msgid "Restart now"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:44
+msgid "You must enter a name."
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:67
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:70
+#, python-format
+msgid "stroke: %s"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:72
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:74
+#, python-format
+msgid "fill: %s"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:85
+msgid "Error in specified color modifiers."
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:88
+msgid "Error in specified colors."
+msgstr ""
+
+#: ../src/controlpanel/model/aboutxo.py:24
+msgid "Not available"
+msgstr ""
+
+#: ../src/controlpanel/model/datetime.py:85
+msgid "Error timezone does not exist."
+msgstr ""
+
+#: ../src/controlpanel/model/frame.py:38 ../src/controlpanel/model/frame.py:60
+msgid "Value must be an integer."
+msgstr ""
+
+#: ../src/controlpanel/model/language.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr ""
+
+#: ../src/controlpanel/model/language.py:104
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr ""
+
+#: ../src/controlpanel/model/language.py:121
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr ""
+
+#: ../src/controlpanel/model/network.py:48
+msgid "You must enter a server."
+msgstr ""
+
+#: ../src/controlpanel/model/network.py:63
+msgid "State is unknown."
+msgstr ""
+
+#: ../src/controlpanel/model/network.py:83
+msgid "Error in specified radio argument use on/off."
+msgstr ""
+
+#: ../src/controlpanel/view/aboutme.py:32
+msgid "About Me"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutme.py:134
+msgid "Click to change your color:"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutxo.py:26
+msgid "About my XO"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutxo.py:47
+msgid "Identity"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutxo.py:56
+msgid "Serial Number:"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutxo.py:79
+msgid "Software"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutxo.py:88
+msgid "Build:"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutxo.py:103
+msgid "Firmware:"
+msgstr ""
+
+#: ../src/controlpanel/view/datetime.py:29
+msgid "Date & Time"
+msgstr ""
+
+#: ../src/controlpanel/view/datetime.py:72
+msgid "Timezone"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:28
+msgid "Frame"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:30
+msgid "never"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:31
+msgid "instantaneous"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:32
+#, python-format
+msgid "%s seconds"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:56
+msgid "Activation Delay"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:80
+msgid "Corner"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:115
+msgid "Edge"
+msgstr ""
+
+#: ../src/controlpanel/view/language.py:29
+#: ../src/controlpanel/view/language.py:74
+msgid "Language"
+msgstr ""
+
+#: ../src/controlpanel/view/network.py:28
+msgid "Network"
+msgstr ""
+
+#: ../src/controlpanel/view/network.py:53
+msgid "Wireless"
+msgstr ""
+
+#: ../src/controlpanel/view/network.py:61
+msgid "Radio:"
+msgstr ""
+
+#: ../src/controlpanel/view/network.py:94
+msgid "Mesh"
+msgstr ""
+
+#: ../src/controlpanel/view/network.py:103
+msgid "Server:"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:108
+msgid "Connected to a School Mesh Portal"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:110
+msgid "Looking for a School Mesh Portal..."
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:113
+msgid "Connected to an XO Mesh Portal"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:115
+msgid "Looking for an XO Mesh Portal..."
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:118
+msgid "Connected to a Simple Mesh"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:120
+msgid "Starting a Simple Mesh"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:127
+msgid "Unknown Mesh"
+msgstr ""
+
+#: ../src/view/frame/activitiestray.py:224
+msgid "Decline"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:351
+msgid "Control Panel"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:362
+msgid "Restart"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:367
+msgid "Shutdown"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:373
+msgid "Register"
+msgstr ""
+
+#: ../src/view/palettes.py:41
+msgid "Starting..."
+msgstr ""
+
+#: ../src/view/palettes.py:71
+msgid "Stop"
+msgstr ""
+
+#: ../src/view/palettes.py:96
+msgid "Start"
+msgstr ""
+
+#: ../src/view/palettes.py:119
+msgid "Remove favorite"
+msgstr ""
+
+#: ../src/view/palettes.py:123
+msgid "Make favorite"
+msgstr ""
+
+#: ../src/view/palettes.py:169
+msgid "Show contents"
+msgstr ""
+
+#: ../src/view/palettes.py:193
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr ""
diff --git a/shell/po/si.po b/shell/po/si.po
new file mode 100644
index 0000000..c86330e
--- /dev/null
+++ b/shell/po/si.po
@@ -0,0 +1,899 @@
+# translation of sugar.po to Sinhala
+# Rashan Anushka <rashan.uoc@gmail.com>, 2008.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+msgid ""
+msgstr ""
+"Project-Id-Version: sugar\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-09-25 00:30-0400\n"
+"PO-Revision-Date: 2008-09-30 07:45-0400\n"
+"Last-Translator: Rashan Anushka <rashan.uoc@gmail.com>\n"
+"Language-Team: Sinhala <si@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Pootle 1.1.0rc2\n"
+
+#: ../src/intro/window.py:93 ../src/controlpanel/aboutme/view.py:100
+msgid "Name:"
+msgstr "නම:"
+
+#: ../src/intro/window.py:125
+msgid "Click to change color:"
+msgstr "පාට වෙනස් කිරීමට ක්ලික් කරන්න:"
+
+#: ../src/intro/window.py:175 ../src/journal/detailview.py:119
+msgid "Back"
+msgstr "ආපසු"
+
+#: ../src/intro/window.py:189 ../src/controlpanel/toolbar.py:61
+msgid "Done"
+msgstr "කළා"
+
+#: ../src/intro/window.py:192
+msgid "Next"
+msgstr "ඊළඟ"
+
+#: ../src/view/BuddyMenu.py:60
+msgid "Remove friend"
+msgstr "මිතුරා ඉවත් කරන්න"
+
+#: ../src/view/BuddyMenu.py:63
+msgid "Make friend"
+msgstr "මිතුරු වන්න"
+
+#: ../src/view/BuddyMenu.py:92
+#, python-format
+msgid "Invite to %s"
+msgstr "%s ට අඬගහන්න"
+
+#: ../src/view/clipboardmenu.py:51
+msgid "Remove"
+msgstr "ඉවත් කරන්න"
+
+#: ../src/view/clipboardmenu.py:56 ../src/view/clipboardmenu.py:78
+msgid "Open"
+msgstr "විවෘත කරන්න"
+
+# self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+# TODO: Implement stopping downloads
+# self._stop_item.connect('activate', self._stop_item_activate_cb)
+# self.append_menu_item(self._stop_item)
+#: ../src/view/clipboardmenu.py:61 ../src/view/home/HomeBox.py:84
+msgid "Keep"
+msgstr "තබාගන්න"
+
+#: ../src/view/clipboardmenu.py:83
+msgid "Open with"
+msgstr "විවෘත කළ යුත්තේ"
+
+#: ../src/view/clipboardmenu.py:228
+#, python-format
+msgid "%s clipping"
+msgstr "කප්පාදුව %s"
+
+#: ../src/hardware/keydialog.py:150
+msgid "Key Type:"
+msgstr "යතුරු වර්ගය:"
+
+#: ../src/hardware/keydialog.py:170
+msgid "Authentication Type:"
+msgstr "සත්‍යාපන වර්ගය:"
+
+#: ../src/hardware/keydialog.py:251
+msgid "Encryption Type:"
+msgstr "සංකේතන වර්ගය:"
+
+#: ../src/hardware/schoolserver.py:17
+msgid "Cannot obtain data needed for registration."
+msgstr "ලියාපදිංචිය සඳහා අවශ්‍ය දත්ත ලබා ගත නොහැක."
+
+#: ../src/hardware/schoolserver.py:31
+msgid "Cannot connect to the server."
+msgstr "සේවාදායකයට සම්බන්ධ විය නොහැක."
+
+#: ../src/hardware/schoolserver.py:36
+msgid "The server could not complete the request."
+msgstr "සේවාදායකයට අයදුම සම්පූර්ණ කළ නොහැක."
+
+#: ../src/view/Shell.py:251
+msgid "Screenshot"
+msgstr "තිරසටහන"
+
+#: ../src/view/home/HomeBox.py:78
+msgid "Confirm erase"
+msgstr "මකා දැමීම ස්ථීර කරන්න"
+
+#: ../src/view/home/HomeBox.py:80
+#, python-format
+msgid "Confirm erase: Do you want to permanently erase %s?"
+msgstr "මකා දැමීම ස්ථීරයි: ඔබට %s සදහටම මකා දැමීමට අවශ්‍යයද?"
+
+#: ../src/view/home/HomeBox.py:87 ../src/view/palettes.py:120
+#: ../src/journal/journaltoolbox.py:335 ../src/journal/palettes.py:75
+msgid "Erase"
+msgstr "මකන්න"
+
+#: ../src/view/home/HomeBox.py:117
+msgid "Software Update"
+msgstr "මෘදුකාංග යාවත්කාල කිරීම"
+
+#: ../src/view/home/HomeBox.py:118
+msgid "Update your activities to ensure compatibility with your new software"
+msgstr "ඔබගේ නව මෘදුකාංගය සමඟ අනුකූලතාව සඳහා ඔබගේ ක්‍රියාකාරකම් යාවත්කාල කරන්න"
+
+#: ../src/view/home/HomeBox.py:122 ../src/controlpanel/toolbar.py:115
+msgid "Cancel"
+msgstr "අවලංගු කරන්න"
+
+#: ../src/view/home/HomeBox.py:124 ../src/controlpanel/gui.py:273
+msgid "Later"
+msgstr "පසුව"
+
+#: ../src/view/home/HomeBox.py:127
+msgid "Check now"
+msgstr "දැන් පරික්ෂා කරන්න"
+
+#: ../src/view/home/HomeBox.py:261
+msgid "List view"
+msgstr "ලැයිස්තු දසුන"
+
+#: ../src/view/home/HomeBox.py:262
+msgid "<Ctrl>2"
+msgstr "<Ctrl>2"
+
+#: ../src/view/home/HomeBox.py:320
+msgid "Favorites view"
+msgstr "කැමති දසුන"
+
+#: ../src/view/home/HomeBox.py:321
+msgid "<Ctrl>1"
+msgstr "<Ctrl>1"
+
+#: ../src/view/home/MeshBox.py:97
+msgid "Connect"
+msgstr "සම්බන්ධ වෙන්න"
+
+#: ../src/view/home/MeshBox.py:106
+msgid "Disconnect"
+msgstr "විසන්ධි වන්න"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#: ../src/view/home/MeshBox.py:118
+msgid "Disconnecting..."
+msgstr "විසන්ධිවේ..."
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#: ../src/view/home/MeshBox.py:159
+msgid "Connecting..."
+msgstr "සම්බන්ධවේ..."
+
+# TODO: show the channel number
+#. TODO: show the channel number
+#: ../src/view/home/MeshBox.py:166
+msgid "Connected"
+msgstr "සම්බන්ධ විය"
+
+#: ../src/view/home/MeshBox.py:218 ../src/view/devices/network/mesh.py:41
+#: ../src/view/devices/network/mesh.py:68
+#: ../src/view/devices/network/mesh.py:72
+msgid "Mesh Network"
+msgstr "දැලැස ජාලය"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#: ../src/view/home/MeshBox.py:221 ../src/view/devices/network/wireless.py:125
+#: ../src/view/devices/network/mesh.py:89
+msgid "Disconnect..."
+msgstr "විසන්ධි කරන්න..."
+
+#. TRANS: Action label for resuming an activity.
+#: ../src/view/home/MeshBox.py:309 ../src/view/palettes.py:61
+#: ../src/journal/journaltoolbox.py:399 ../src/journal/palettes.py:57
+msgid "Resume"
+msgstr "නැවත ආරම්භ කරන්න"
+
+#: ../src/view/home/MeshBox.py:314 ../src/view/frame/activitiestray.py:206
+msgid "Join"
+msgstr "සම්බන්ධ වෙන්න"
+
+#: ../src/view/devices/battery.py:45
+msgid "My Battery"
+msgstr "මගේ බැටරිය"
+
+#: ../src/view/devices/battery.py:114
+msgid "Charging"
+msgstr "ආරෝපනය වේ"
+
+#: ../src/view/devices/battery.py:117
+msgid "Very little power remaining"
+msgstr "ඉතා සුළු බලයක් පවතී"
+
+#: ../src/view/devices/battery.py:123
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr "%(hour)d:%(min).2d ඉතිරියි"
+
+#: ../src/view/devices/battery.py:127
+msgid "Charged"
+msgstr "ආරෝපිතයි"
+
+#: ../src/view/devices/speaker.py:44
+msgid "My Speakers"
+msgstr "මගේ ස්පීකරය"
+
+#: ../src/view/devices/speaker.py:125
+msgid "Unmute"
+msgstr "නිෂ්ශබ්ධතාව නවතන්න"
+
+#: ../src/view/devices/speaker.py:128
+msgid "Mute"
+msgstr "නිශ්ශබ්ද කරන්න"
+
+#: ../src/view/devices/network/wireless.py:67
+msgid "Disconnected"
+msgstr "විසන්ධිවී ඇත"
+
+#: ../src/view/devices/network/wireless.py:143
+msgid "Channel"
+msgstr "නාලිකාව"
+
+#: ../src/view/frame/zoomtoolbar.py:37
+msgid "Neighborhood"
+msgstr "වටපිටාව"
+
+#: ../src/view/frame/zoomtoolbar.py:39
+msgid "Group"
+msgstr "සමූහය"
+
+#: ../src/view/frame/zoomtoolbar.py:41
+msgid "Home"
+msgstr "නිවස"
+
+#: ../src/view/frame/zoomtoolbar.py:43
+msgid "Activity"
+msgstr "ක්‍රියාකාරකම"
+
+#: ../src/controlpanel/cmd.py:26
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+"sugar-control-panel: අවවාදයයි, එකම නම (%s) සහිත විකල්ප කිහිපයක් හමු විය.: "
+"මොඩියුලය: %r"
+
+#: ../src/controlpanel/cmd.py:28
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr "sugar-control-panel: key=%s පවතින විකල්පයක් නොවේ"
+
+#: ../src/controlpanel/cmd.py:29
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr "sugar-control-panel: %s"
+
+#. TRANS: Translators, there's a empty line at the end of this string,
+#. which must appear in the translated string (msgstr) as well.
+#: ../src/controlpanel/cmd.py:35
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" -c key clear the current value for the key \n"
+" "
+msgstr ""
+"භාවිතාව: sugar-control-panel [ විකල්පය ] key [ තර්ක ... ] \n"
+" ශුගර් පරිසරය සඳහා පාලනය. \n"
+" විකල්ප: \n"
+" -h මෙම උදව් පණිවිඩය පෙන්වා පිටවන්න\n"
+" -l සියළු විකල්ප ලැයිස්තුගත කරන්න\n"
+" -h key key ගැන විස්තර පෙන්වන්න \n"
+" -g key key හි පවතින අගය පෙන්වන්න \n"
+" -s key key හි අගය සිටුචම් කරන්න \n"
+" "
+
+#: ../src/controlpanel/cmd.py:48
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr "ඔබගේ වෙනස්කිරීම් බලපෑම සඳහා ෂුගර් යළි ඇරඹිය යුතුය.\n"
+
+#: ../src/controlpanel/toolbar.py:121 ../src/view/home/favoritesview.py:305
+msgid "Ok"
+msgstr "හරි"
+
+#: ../src/controlpanel/sectionview.py:42 ../src/controlpanel/gui.py:265
+msgid "Changes require restart"
+msgstr "වෙනස්කිරීම සඳහා යළි ඇරඹුමක් අවශ්‍යවේ"
+
+#: ../src/controlpanel/gui.py:264
+msgid "Warning"
+msgstr "අවවාදයයි"
+
+#: ../src/controlpanel/gui.py:268
+msgid "Cancel changes"
+msgstr "වෙනස් කිරීම් අවලංගු කරන්න"
+
+#: ../src/controlpanel/gui.py:277
+msgid "Restart now"
+msgstr "දැන් යළි ආරම්භ කරන්න"
+
+#: ../src/controlpanel/aboutme/model.py:44
+msgid "You must enter a name."
+msgstr "ඔබ විසින් නමක් ඇතුලත් කළ යුතුය."
+
+#: ../src/controlpanel/aboutme/model.py:69
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr "පහර: වර්ණය=%s පැහැය=%s"
+
+#: ../src/controlpanel/aboutme/model.py:72
+#, python-format
+msgid "stroke: %s"
+msgstr "පහර: %s"
+
+#: ../src/controlpanel/aboutme/model.py:74
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr "පිරවුම: වර්ණය=%s පැහැය=%s"
+
+#: ../src/controlpanel/aboutme/model.py:76
+#, python-format
+msgid "fill: %s"
+msgstr "පිරවුම: %s"
+
+#: ../src/controlpanel/aboutme/model.py:87
+msgid "Error in specified color modifiers."
+msgstr "සැපයූ වර්ණ විකරණකාරකයේ දෝෂයකි"
+
+#: ../src/controlpanel/aboutme/model.py:90
+msgid "Error in specified colors."
+msgstr "සැපයූ වර්ණවල දෝෂයකි"
+
+#: ../src/controlpanel/aboutme/view.py:32
+#: ../src/controlpanel/aboutme/__init__.py:22
+msgid "About Me"
+msgstr "මම ගැන"
+
+#: ../src/controlpanel/aboutme/view.py:134
+msgid "Click to change your color:"
+msgstr "ඔබගේ පාට වෙනස් කිරීමට ක්ලික් කරන්න:"
+
+#: ../src/controlpanel/aboutxo/model.py:24
+msgid "Not available"
+msgstr "නොපවතී"
+
+#: ../src/controlpanel/aboutxo/view.py:55
+msgid "Identity"
+msgstr "අනන්‍යතාව"
+
+#: ../src/controlpanel/aboutxo/view.py:64
+msgid "Serial Number:"
+msgstr "අනුක්‍රමික අංකය:"
+
+#: ../src/controlpanel/aboutxo/view.py:87
+msgid "Software"
+msgstr "මෘදුකාංගය"
+
+#: ../src/controlpanel/aboutxo/view.py:96
+msgid "Build:"
+msgstr "ගොඩ නැඟුම:"
+
+#: ../src/controlpanel/aboutxo/view.py:111
+msgid "Sugar:"
+msgstr "ශුගර්:"
+
+#: ../src/controlpanel/aboutxo/view.py:126
+msgid "Firmware:"
+msgstr "ස්ථීරාංගය:"
+
+#: ../src/controlpanel/aboutxo/view.py:148
+msgid "Copyright and License"
+msgstr "කතු හිමිකම හා වරපත"
+
+#: ../src/controlpanel/aboutxo/view.py:156
+msgid ""
+"© 2008 One Laptop per Child Association Inc; Red Hat Inc; and Contributors."
+msgstr ""
+"© 2008 One Laptop per Child Association Inc; Red Hat Inc; හා සහදායකයින්."
+
+#: ../src/controlpanel/aboutxo/view.py:163
+msgid ""
+"Sugar is the graphical user interface that you are looking at. Sugar is free "
+"software, covered by the GNU General Public License, and you are welcome to "
+"change it and/or distribute copies of it under certain conditions described "
+"therein."
+msgstr ""
+"ශුගර් යනු ඔබ බලා සිටින මෙම චිත්‍රක පරිශීලක අතුරුමුහුණතයි. ශුගර් GNU සාධාරණ "
+"පොදු වරපත මගින් ආවරණය වන නිදහස් මෘදුකාංගයක් වන අතර එහි සඳහන් ඇතැම් කොන්දේසි "
+"වලට යටත්ව මෙය වෙනස් කිරීමට හා/හෝ පිටපත් බෙදාහැරීමට ඔබට හැකිය."
+
+#: ../src/controlpanel/aboutxo/view.py:175
+msgid "Full license:"
+msgstr "සම්පූර්ණ වරපත:"
+
+#: ../src/controlpanel/aboutxo/__init__.py:21
+msgid "About my XO"
+msgstr "මගේ XO ගැන"
+
+#: ../src/controlpanel/datetime/model.py:89
+msgid "Error timezone does not exist."
+msgstr "දෝෂයකි වේලාකලාපය නොපවතී."
+
+#: ../src/controlpanel/datetime/view.py:68
+msgid "Timezone"
+msgstr "වේලා කලාපය"
+
+#: ../src/controlpanel/datetime/__init__.py:21
+msgid "Date & Time"
+msgstr "දිනය හා වේලාව"
+
+#: ../src/controlpanel/frame/model.py:38 ../src/controlpanel/frame/model.py:60
+msgid "Value must be an integer."
+msgstr "අගය නිඛිලයක් විය යුතුය."
+
+#: ../src/controlpanel/frame/view.py:26
+msgid "never"
+msgstr "කිසිම දිනෙක"
+
+#: ../src/controlpanel/frame/view.py:27
+msgid "instantaneous"
+msgstr "ක්ෂණිකව"
+
+#: ../src/controlpanel/frame/view.py:28
+#, python-format
+msgid "%s seconds"
+msgstr "තත්පර %s"
+
+#: ../src/controlpanel/frame/view.py:52
+msgid "Activation Delay"
+msgstr "සක්‍රියනය ප්‍රමාදය"
+
+#: ../src/controlpanel/frame/view.py:76
+msgid "Corner"
+msgstr "මුල්ල"
+
+#: ../src/controlpanel/frame/view.py:111
+msgid "Edge"
+msgstr "අයින"
+
+#: ../src/controlpanel/frame/__init__.py:21
+msgid "Frame"
+msgstr "කවුළුව"
+
+#: ../src/controlpanel/language/model.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr "~/.i18n වෙත ප්‍රවේශ විය නොහැක. සම්මත සිටුවම් සාදන්න."
+
+#: ../src/controlpanel/language/model.py:114
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr "කේතය=%s වන භාෂාව නිර්ණය කළ නොහැක."
+
+#: ../src/controlpanel/language/model.py:131
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr "සමාවන්න මම '%s' කථා නොකරමි."
+
+#: ../src/controlpanel/language/view.py:70
+#: ../src/controlpanel/language/__init__.py:21
+msgid "Language"
+msgstr "භාෂාව"
+
+#: ../src/controlpanel/network/model.py:62
+msgid "State is unknown."
+msgstr "තත්ත්වය නොදනී."
+
+#: ../src/controlpanel/network/model.py:82
+msgid "Error in specified radio argument use on/off."
+msgstr "සැපයූ විකල්ප විස්තාරකයේ දෝෂයකි. සක්‍රීයයි/අක්‍රීයයි භාවිතා කරන්න."
+
+#: ../src/controlpanel/network/view.py:28
+#: ../src/controlpanel/network/__init__.py:21
+msgid "Network"
+msgstr "ජාලය"
+
+#: ../src/controlpanel/network/view.py:54
+msgid "Wireless"
+msgstr "රැහැන් රහිත"
+
+#: ../src/controlpanel/network/view.py:62
+msgid "Turn of the wireless radio to save battery life"
+msgstr "බැටරි ආයු කාලය සුරැකීම සඳහා රැහැන් රහිත රේඩියෝව නිවා දමන්න"
+
+#: ../src/controlpanel/network/view.py:75
+msgid "Radio"
+msgstr "රේඩියෝව"
+
+#: ../src/controlpanel/network/view.py:91
+msgid "Discard network history if you have trouble connecting to the network"
+msgstr "ොබට ජාලයට සම්බන්ධ වීමේ ගැටළු ඇතිනම් ජාල ඉතිහාසය ඉවතලන්න"
+
+#: ../src/controlpanel/network/view.py:100
+msgid "Discard network history"
+msgstr "ජාල ඉතිහාසය ඉවතලන්න"
+
+#: ../src/controlpanel/network/view.py:113
+msgid "Mesh"
+msgstr "දැලැස"
+
+#: ../src/controlpanel/network/view.py:122
+msgid "Server:"
+msgstr "සේවාදායකය:"
+
+#: ../src/controlpanel/power/model.py:55
+msgid "Error in automatic pm argument, use on/off."
+msgstr "ස්වයංක්‍රීය pm විස්තාරකයේ දෝෂයකි, සක්‍රීයයි/අක්‍රීයයි භාවිතා කරන්න."
+
+#: ../src/controlpanel/power/model.py:84
+msgid "Error in extreme pm argument, use on/off."
+msgstr "ආන්තික pm විස්තාරකයේ දෝෂයකි, සක්‍රීයයි/අක්‍රීයයි භාවිතා කරන්න."
+
+#: ../src/controlpanel/power/view.py:47
+msgid "Power management"
+msgstr "බල කළමණාකරනය"
+
+#: ../src/controlpanel/power/view.py:57
+msgid "Automatic power management (increases battery life)"
+msgstr "ස්වයංක්‍රීය බල කළමණාකරනය (බැටරි ආයු කාලය වැඩි කරයි)"
+
+#: ../src/controlpanel/power/view.py:85
+msgid ""
+"Extreme power management (disableswireless radio, increases battery life)"
+msgstr ""
+"උපරිම බල කළමණාකරනය ( රැහැන් රහිත රේඩියෝව අක්‍රිය කරයි, බැටරි ආයු කාලය වැඩි "
+"කරයි)"
+
+#: ../src/controlpanel/power/__init__.py:21
+msgid "Power"
+msgstr "ශක්තිය"
+
+#: ../src/view/devices/network/mesh.py:111
+msgid "Connected to a School Mesh Portal"
+msgstr "පාසල් දැලැස් බිහිදොරකට සම්බන්ධ විය"
+
+#: ../src/view/devices/network/mesh.py:113
+msgid "Looking for a School Mesh Portal..."
+msgstr "පාසල් දැලැස් බිහිදොරක් සොයයි..."
+
+#: ../src/view/devices/network/mesh.py:116
+msgid "Connected to an XO Mesh Portal"
+msgstr "XO දැලැස් බිහිදොරකට සම්බන්ධ විය"
+
+#: ../src/view/devices/network/mesh.py:118
+msgid "Looking for an XO Mesh Portal..."
+msgstr "XO දැලැස් බිහිදොරක් සොයයි..."
+
+#: ../src/view/devices/network/mesh.py:121
+msgid "Connected to a Simple Mesh"
+msgstr "සරල දැලැස් බිහිදොරකට සම්බන්ධ විය"
+
+#: ../src/view/devices/network/mesh.py:123
+msgid "Starting a Simple Mesh"
+msgstr "සරල දැලැස් බිහිදොරක් ආරම්භ කෙරේ"
+
+#: ../src/view/devices/network/mesh.py:130
+msgid "Unknown Mesh"
+msgstr "නොදන්නා දැලැසකි"
+
+#: ../src/view/frame/activitiestray.py:211
+msgid "Decline"
+msgstr "ක්‍රමයෙන් පිරිහෙනවා"
+
+# TRANS: label for the freeform layout in the favorites view
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/view/home/favoriteslayout.py:107
+msgid "Freeform"
+msgstr "නිදහස් පෝර්මය"
+
+# TRANS: label for the ring layout in the favorites view
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/view/home/favoriteslayout.py:189
+msgid "Ring"
+msgstr "කවය"
+
+#. TRANS: label for the spiral layout in the favorites view
+#: ../src/view/home/favoriteslayout.py:334
+msgid "Spiral"
+msgstr "සර්පිලය"
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/view/home/favoriteslayout.py:401
+msgid "Box"
+msgstr "පෙට්ටිය"
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/view/home/favoriteslayout.py:442
+msgid "Triangle"
+msgstr "ත්‍රිකෝණය"
+
+#: ../src/view/home/favoritesview.py:295
+msgid "Registration Failed"
+msgstr "ලියාපදිංචිය අසමත් විය"
+
+#: ../src/view/home/favoritesview.py:296
+#, python-format
+msgid "%s"
+msgstr "%s"
+
+#: ../src/view/home/favoritesview.py:298
+msgid "Registration Successful"
+msgstr "ලියාපදිංචිය සාර්ථක විය"
+
+#: ../src/view/home/favoritesview.py:299
+msgid "You are now registered with your school server."
+msgstr "ඔබ දැන් ඔබගේ පාසල් සේවාදායකය සමඟ ලියාපදිංචි වී ඇත."
+
+#: ../src/view/home/favoritesview.py:420
+msgid "Settings"
+msgstr "සිටුවම්"
+
+#: ../src/view/home/favoritesview.py:425
+msgid "Restart"
+msgstr "යළි ආරම්භ කරන්න"
+
+#: ../src/view/home/favoritesview.py:430
+msgid "Shutdown"
+msgstr "වසා දමන්න"
+
+#: ../src/view/home/favoritesview.py:436
+msgid "Register"
+msgstr "ලියාපදිංචි කරන්න"
+
+#: ../src/view/palettes.py:42
+msgid "Starting..."
+msgstr "ආරම්භවේ..."
+
+#: ../src/view/palettes.py:72
+msgid "Stop"
+msgstr "නවත්වන්න"
+
+#. TRANS: Action label for starting an entry.
+#: ../src/view/palettes.py:104 ../src/journal/journaltoolbox.py:402
+#: ../src/journal/palettes.py:59
+msgid "Start"
+msgstr "ආරම්භ කරන්න"
+
+#: ../src/view/palettes.py:138
+msgid "Remove favorite"
+msgstr "අභිරුචිය ඉවත් කරන්න"
+
+#: ../src/view/palettes.py:142
+msgid "Make favorite"
+msgstr "අභිරුචියක් කරන්න"
+
+#: ../src/view/palettes.py:191
+msgid "Show contents"
+msgstr "අන්තර්ගතය පෙන්වන්න"
+
+#: ../src/view/palettes.py:215
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr "%(free_space)d MB ඉතිරියි"
+
+#: ../src/journal/journaltoolbox.py:62
+msgid "Search"
+msgstr "සෙවිමට"
+
+#: ../src/journal/journaltoolbox.py:119
+msgid "Anytime"
+msgstr "ඕනෑම වෙලාවක"
+
+#: ../src/journal/journaltoolbox.py:121
+msgid "Today"
+msgstr "අද"
+
+#: ../src/journal/journaltoolbox.py:123
+msgid "Since yesterday"
+msgstr "ඊයේ සිට"
+
+#. TRANS: Filter entries modified during the last 7 days.
+#: ../src/journal/journaltoolbox.py:125
+msgid "Past week"
+msgstr "පසුගිය සතිය"
+
+#. TRANS: Filter entries modified during the last 30 days.
+#: ../src/journal/journaltoolbox.py:127
+msgid "Past month"
+msgstr "පසුගිය මාසය"
+
+#. TRANS: Filter entries modified during the last 356 days.
+#: ../src/journal/journaltoolbox.py:129
+msgid "Past year"
+msgstr "පසුගිය වසර"
+
+#: ../src/journal/journaltoolbox.py:136
+msgid "Anyone"
+msgstr "ඕනෑම අයෙක්"
+
+#: ../src/journal/journaltoolbox.py:138
+msgid "My friends"
+msgstr "මාගේ මිතුරන්"
+
+#: ../src/journal/journaltoolbox.py:139
+msgid "My class"
+msgstr "මාගේ පන්තිය"
+
+#. TRANS: Item in a combo box that filters by entry type.
+#: ../src/journal/journaltoolbox.py:255
+msgid "Anything"
+msgstr "ඕනෑ දෙයක්"
+
+#. TODO: Add "Start with" menu item
+#: ../src/journal/journaltoolbox.py:325 ../src/journal/palettes.py:67
+msgid "Copy"
+msgstr "පිටපත් කරන්න"
+
+#: ../src/journal/collapsedentry.py:248 ../src/journal/expandedentry.py:176
+#: ../src/journal/palettes.py:51
+msgid "Untitled"
+msgstr "ශීර්ෂ පාඨ නොමැති"
+
+#: ../src/journal/journalactivity.py:119 ../src/journal/volumesmanager.py:57
+msgid "Journal"
+msgstr "ජ'නලය"
+
+#: ../src/journal/expandedentry.py:222
+msgid "No preview"
+msgstr "පූර්වදර්ශනයක් නැත"
+
+#: ../src/journal/expandedentry.py:241
+msgid "Participants:"
+msgstr "සහභාගිවන්නන්:"
+
+#: ../src/journal/expandedentry.py:266
+msgid "Description:"
+msgstr "විස්තරය:"
+
+#: ../src/journal/expandedentry.py:292
+msgid "Tags:"
+msgstr "හැඳුනුම් වදන්:"
+
+#: ../src/journal/objectchooser.py:134
+msgid "Choose an object"
+msgstr "වස්තුවක් තෝරන්න"
+
+#: ../src/journal/objectchooser.py:139
+msgid "Close"
+msgstr "චසාදමන්න"
+
+#: ../src/journal/volumestoolbar.py:93
+msgid "Unmount"
+msgstr "ගලවන්න"
+
+#: ../src/journal/misc.py:95
+msgid "No date"
+msgstr "දිනයක් නැත"
+
+#: ../src/journal/listview.py:39
+msgid "Your Journal is empty"
+msgstr "ඔබගේ ජර්නලය හිස්ය"
+
+#: ../src/journal/listview.py:40
+msgid "No matching entries "
+msgstr "ගැළපෙන නිවේශන නොමැත "
+
+#: ../src/journal/modalalert.py:59
+msgid "Your Journal is full"
+msgstr "ඔබගේ ජර්නලය සම්පූර්ණයෙන් පිරී ඇත"
+
+#: ../src/journal/modalalert.py:63
+msgid "Please delete some old Journal entries to make space for new ones."
+msgstr ""
+"කරුණාකර නව නිවේශන සඳහා ඉඩ ලබා ගැනීමට පැරණි ජර්නල නිවේශන කිහිපයක් මකා දමන්න."
+
+#: ../src/journal/modalalert.py:75
+msgid "Show Journal"
+msgstr "ජර්නලය පෙන්වන්න"
+
+#, python-format
+#~ msgid "Clipboard object: %s."
+#~ msgstr "පසුරු පුවරු වස්තුව: %s."
+
+#~ msgid "You must enter a server."
+#~ msgstr "ඔබ විසින් සේවාදායකයක් ඇතුලත් කළ යුතුය."
+
+#~ msgid "Control Panel"
+#~ msgstr "පාලක පුවරුව"
+
+#~ msgid "<Ctrl>R"
+#~ msgstr "<Ctrl>R"
+
+#~ msgid "off"
+#~ msgstr "අක්‍රීය කරන්න"
+
+#~ msgid "on"
+#~ msgstr "සක්‍රීය කරන්න"
+
+#~ msgid "Permission denied. You need to be root to run this method."
+#~ msgstr "අවසර දිය නොහැක. මෙම විධිය ක්‍රියාත්මක කිරීමට ඔබ root විය යුතුය."
+
+#~ msgid "Error in reading timezone"
+#~ msgstr "වේලා කලාපය කියවීමේ දෝෂයකි"
+
+#, python-format
+#~ msgid "Error copying timezone (from %s): %s"
+#~ msgstr "වේලා කලාපය පිටපත් කිරීමේ දෝෂයකි (%s ගෙන්): %s"
+
+#, python-format
+#~ msgid "Changing permission of timezone: %s"
+#~ msgstr "වේලා කලාපයේ අවසර වෙනස් කෙරේ: %s"
+
+#~ msgid "About this XO"
+#~ msgstr "මෙම XO ගැන"
+
+# self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+# TODO: Implement stopping downloads
+# self._stop_item.connect('activate', self._stop_item_activate_cb)
+# self.append_menu_item(self._stop_item)
+#~ msgid "Add to journal"
+#~ msgstr "ජර්නලයට එක් කරන්න"
+
+#~ msgid "Reboot"
+#~ msgstr "යළි අරඹන්න"
+
+#~ msgid "My Battery life"
+#~ msgstr "මගේ බැටරි ආයු කාලය"
+
+#~ msgid "Battery charging"
+#~ msgstr "බැටරිය ආරෝපණය වේ"
+
+#~ msgid "Battery discharging"
+#~ msgstr "බැටරිය විසර්ජනය වේ"
+
+#~ msgid "Battery fully charged"
+#~ msgstr "බැටරිය සම්පූර්ණයෙන්ම ආරෝපිතයි"
+
+#~ msgid "Share with:"
+#~ msgstr "සමඟ බෙදාගන්න:"
+
+#~ msgid "Private"
+#~ msgstr "පුද්ගලික"
+
+#~ msgid "My Neighborhood"
+#~ msgstr "මගේ වටපිටාව"
+
+#~ msgid "Undo"
+#~ msgstr "පෙර ලෙස"
+
+#~ msgid "Redo"
+#~ msgstr "නැවත කරන්න"
+
+#~ msgid "Paste"
+#~ msgstr "අලවන්න"
+
+#, python-format
+#~ msgid "%s Activity"
+#~ msgstr "%s ක්‍රියාකාරකම"
+
+#~ msgid "Keep error"
+#~ msgstr "නබා ගැනීමේ දෝෂයකි"
+
+#~ msgid "Keep error: all changes will be lost"
+#~ msgstr "තබා ගැනීමේ දෝෂයකි: සියළු වෙනස් කිරීම් නැතිවනු ඇත"
+
+#~ msgid "Don't stop"
+#~ msgstr "නවත්වන්න එපා"
+
+#~ msgid "Stop anyway"
+#~ msgstr "කෙසේ හෝ නවත්වන්න"
+
+#~ msgid "Continue"
+#~ msgstr "පවත්වාගෙන යන්න"
+
+#~ msgid "OK"
+#~ msgstr "හරි"
diff --git a/shell/po/sk.po b/shell/po/sk.po
new file mode 100644
index 0000000..63d4e34
--- /dev/null
+++ b/shell/po/sk.po
@@ -0,0 +1,764 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-09-25 00:30-0400\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/intro/window.py:93 ../src/controlpanel/aboutme/view.py:100
+msgid "Name:"
+msgstr ""
+
+#: ../src/intro/window.py:125
+msgid "Click to change color:"
+msgstr ""
+
+#: ../src/intro/window.py:175 ../src/journal/detailview.py:119
+msgid "Back"
+msgstr ""
+
+#: ../src/intro/window.py:189 ../src/controlpanel/toolbar.py:61
+msgid "Done"
+msgstr ""
+
+#: ../src/intro/window.py:192
+msgid "Next"
+msgstr ""
+
+#: ../src/view/BuddyMenu.py:60
+msgid "Remove friend"
+msgstr ""
+
+#: ../src/view/BuddyMenu.py:63
+msgid "Make friend"
+msgstr ""
+
+#: ../src/view/BuddyMenu.py:92
+#, python-format
+msgid "Invite to %s"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:51
+msgid "Remove"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:56 ../src/view/clipboardmenu.py:78
+msgid "Open"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:61 ../src/view/home/HomeBox.py:84
+msgid "Keep"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:83
+msgid "Open with"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:228
+#, python-format
+msgid "%s clipping"
+msgstr ""
+
+#: ../src/hardware/keydialog.py:150
+msgid "Key Type:"
+msgstr ""
+
+#: ../src/hardware/keydialog.py:170
+msgid "Authentication Type:"
+msgstr ""
+
+#: ../src/hardware/keydialog.py:251
+msgid "Encryption Type:"
+msgstr ""
+
+#: ../src/hardware/schoolserver.py:17
+msgid "Cannot obtain data needed for registration."
+msgstr ""
+
+#: ../src/hardware/schoolserver.py:31
+msgid "Cannot connect to the server."
+msgstr ""
+
+#: ../src/hardware/schoolserver.py:36
+msgid "The server could not complete the request."
+msgstr ""
+
+#: ../src/view/Shell.py:251
+msgid "Screenshot"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:78
+msgid "Confirm erase"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:80
+#, python-format
+msgid "Confirm erase: Do you want to permanently erase %s?"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:87 ../src/view/palettes.py:120
+#: ../src/journal/journaltoolbox.py:335 ../src/journal/palettes.py:75
+msgid "Erase"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:117
+msgid "Software Update"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:118
+msgid "Update your activities to ensure compatibility with your new software"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:122 ../src/controlpanel/toolbar.py:115
+msgid "Cancel"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:124 ../src/controlpanel/gui.py:273
+msgid "Later"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:127
+msgid "Check now"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:261
+msgid "List view"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:262
+msgid "<Ctrl>2"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:320
+msgid "Favorites view"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:321
+msgid "<Ctrl>1"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:97
+msgid "Connect"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:106
+msgid "Disconnect"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:118
+msgid "Disconnecting..."
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:159
+msgid "Connecting..."
+msgstr ""
+
+#. TODO: show the channel number
+#: ../src/view/home/MeshBox.py:166
+msgid "Connected"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:218 ../src/view/devices/network/mesh.py:41
+#: ../src/view/devices/network/mesh.py:68
+#: ../src/view/devices/network/mesh.py:72
+msgid "Mesh Network"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:221 ../src/view/devices/network/wireless.py:125
+#: ../src/view/devices/network/mesh.py:89
+msgid "Disconnect..."
+msgstr ""
+
+#. TRANS: Action label for resuming an activity.
+#: ../src/view/home/MeshBox.py:309 ../src/view/palettes.py:61
+#: ../src/journal/journaltoolbox.py:399 ../src/journal/palettes.py:57
+msgid "Resume"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:314 ../src/view/frame/activitiestray.py:206
+msgid "Join"
+msgstr ""
+
+#: ../src/view/devices/battery.py:45
+msgid "My Battery"
+msgstr ""
+
+#: ../src/view/devices/battery.py:114
+msgid "Charging"
+msgstr ""
+
+#: ../src/view/devices/battery.py:117
+msgid "Very little power remaining"
+msgstr ""
+
+#: ../src/view/devices/battery.py:123
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr ""
+
+#: ../src/view/devices/battery.py:127
+msgid "Charged"
+msgstr ""
+
+#: ../src/view/devices/speaker.py:44
+msgid "My Speakers"
+msgstr ""
+
+#: ../src/view/devices/speaker.py:125
+msgid "Unmute"
+msgstr ""
+
+#: ../src/view/devices/speaker.py:128
+msgid "Mute"
+msgstr ""
+
+#: ../src/view/devices/network/wireless.py:67
+msgid "Disconnected"
+msgstr ""
+
+#: ../src/view/devices/network/wireless.py:143
+msgid "Channel"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:37
+msgid "Neighborhood"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:39
+msgid "Group"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:41
+msgid "Home"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:43
+msgid "Activity"
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:26
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:28
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:29
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr ""
+
+#. TRANS: Translators, there's a empty line at the end of this string,
+#. which must appear in the translated string (msgstr) as well.
+#: ../src/controlpanel/cmd.py:35
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" -c key clear the current value for the key \n"
+" "
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:48
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr ""
+
+#: ../src/controlpanel/toolbar.py:121 ../src/view/home/favoritesview.py:305
+msgid "Ok"
+msgstr ""
+
+#: ../src/controlpanel/sectionview.py:42 ../src/controlpanel/gui.py:265
+msgid "Changes require restart"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:264
+msgid "Warning"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:268
+msgid "Cancel changes"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:277
+msgid "Restart now"
+msgstr ""
+
+#: ../src/controlpanel/aboutme/model.py:44
+msgid "You must enter a name."
+msgstr ""
+
+#: ../src/controlpanel/aboutme/model.py:69
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr ""
+
+#: ../src/controlpanel/aboutme/model.py:72
+#, python-format
+msgid "stroke: %s"
+msgstr ""
+
+#: ../src/controlpanel/aboutme/model.py:74
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr ""
+
+#: ../src/controlpanel/aboutme/model.py:76
+#, python-format
+msgid "fill: %s"
+msgstr ""
+
+#: ../src/controlpanel/aboutme/model.py:87
+msgid "Error in specified color modifiers."
+msgstr ""
+
+#: ../src/controlpanel/aboutme/model.py:90
+msgid "Error in specified colors."
+msgstr ""
+
+#: ../src/controlpanel/aboutme/view.py:32
+#: ../src/controlpanel/aboutme/__init__.py:22
+msgid "About Me"
+msgstr ""
+
+#: ../src/controlpanel/aboutme/view.py:134
+msgid "Click to change your color:"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/model.py:24
+msgid "Not available"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:55
+msgid "Identity"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:64
+msgid "Serial Number:"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:87
+msgid "Software"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:96
+msgid "Build:"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:111
+msgid "Sugar:"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:126
+msgid "Firmware:"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:148
+msgid "Copyright and License"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:156
+msgid ""
+"© 2008 One Laptop per Child Association Inc; Red Hat Inc; and Contributors."
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:163
+msgid ""
+"Sugar is the graphical user interface that you are looking at. Sugar is free "
+"software, covered by the GNU General Public License, and you are welcome to "
+"change it and/or distribute copies of it under certain conditions described "
+"therein."
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:175
+msgid "Full license:"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/__init__.py:21
+msgid "About my XO"
+msgstr ""
+
+#: ../src/controlpanel/datetime/model.py:89
+msgid "Error timezone does not exist."
+msgstr ""
+
+#: ../src/controlpanel/datetime/view.py:68
+msgid "Timezone"
+msgstr ""
+
+#: ../src/controlpanel/datetime/__init__.py:21
+msgid "Date & Time"
+msgstr ""
+
+#: ../src/controlpanel/frame/model.py:38 ../src/controlpanel/frame/model.py:60
+msgid "Value must be an integer."
+msgstr ""
+
+#: ../src/controlpanel/frame/view.py:26
+msgid "never"
+msgstr ""
+
+#: ../src/controlpanel/frame/view.py:27
+msgid "instantaneous"
+msgstr ""
+
+#: ../src/controlpanel/frame/view.py:28
+#, python-format
+msgid "%s seconds"
+msgstr ""
+
+#: ../src/controlpanel/frame/view.py:52
+msgid "Activation Delay"
+msgstr ""
+
+#: ../src/controlpanel/frame/view.py:76
+msgid "Corner"
+msgstr ""
+
+#: ../src/controlpanel/frame/view.py:111
+msgid "Edge"
+msgstr ""
+
+#: ../src/controlpanel/frame/__init__.py:21
+msgid "Frame"
+msgstr ""
+
+#: ../src/controlpanel/language/model.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr ""
+
+#: ../src/controlpanel/language/model.py:114
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr ""
+
+#: ../src/controlpanel/language/model.py:131
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr ""
+
+#: ../src/controlpanel/language/view.py:70
+#: ../src/controlpanel/language/__init__.py:21
+msgid "Language"
+msgstr ""
+
+#: ../src/controlpanel/network/model.py:62
+msgid "State is unknown."
+msgstr ""
+
+#: ../src/controlpanel/network/model.py:82
+msgid "Error in specified radio argument use on/off."
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:28
+#: ../src/controlpanel/network/__init__.py:21
+msgid "Network"
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:54
+msgid "Wireless"
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:62
+msgid "Turn of the wireless radio to save battery life"
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:75
+msgid "Radio"
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:91
+msgid "Discard network history if you have trouble connecting to the network"
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:100
+msgid "Discard network history"
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:113
+msgid "Mesh"
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:122
+msgid "Server:"
+msgstr ""
+
+#: ../src/controlpanel/power/model.py:55
+msgid "Error in automatic pm argument, use on/off."
+msgstr ""
+
+#: ../src/controlpanel/power/model.py:84
+msgid "Error in extreme pm argument, use on/off."
+msgstr ""
+
+#: ../src/controlpanel/power/view.py:47
+msgid "Power management"
+msgstr ""
+
+#: ../src/controlpanel/power/view.py:57
+msgid "Automatic power management (increases battery life)"
+msgstr ""
+
+#: ../src/controlpanel/power/view.py:85
+msgid ""
+"Extreme power management (disableswireless radio, increases battery life)"
+msgstr ""
+
+#: ../src/controlpanel/power/__init__.py:21
+msgid "Power"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:111
+msgid "Connected to a School Mesh Portal"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:113
+msgid "Looking for a School Mesh Portal..."
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:116
+msgid "Connected to an XO Mesh Portal"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:118
+msgid "Looking for an XO Mesh Portal..."
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:121
+msgid "Connected to a Simple Mesh"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:123
+msgid "Starting a Simple Mesh"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:130
+msgid "Unknown Mesh"
+msgstr ""
+
+#: ../src/view/frame/activitiestray.py:211
+msgid "Decline"
+msgstr ""
+
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/view/home/favoriteslayout.py:107
+msgid "Freeform"
+msgstr ""
+
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/view/home/favoriteslayout.py:189
+msgid "Ring"
+msgstr ""
+
+#. TRANS: label for the spiral layout in the favorites view
+#: ../src/view/home/favoriteslayout.py:334
+msgid "Spiral"
+msgstr ""
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/view/home/favoriteslayout.py:401
+msgid "Box"
+msgstr ""
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/view/home/favoriteslayout.py:442
+msgid "Triangle"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:295
+msgid "Registration Failed"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:296
+#, python-format
+msgid "%s"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:298
+msgid "Registration Successful"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:299
+msgid "You are now registered with your school server."
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:420
+msgid "Settings"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:425
+msgid "Restart"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:430
+msgid "Shutdown"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:436
+msgid "Register"
+msgstr ""
+
+#: ../src/view/palettes.py:42
+msgid "Starting..."
+msgstr ""
+
+#: ../src/view/palettes.py:72
+msgid "Stop"
+msgstr ""
+
+#. TRANS: Action label for starting an entry.
+#: ../src/view/palettes.py:104 ../src/journal/journaltoolbox.py:402
+#: ../src/journal/palettes.py:59
+msgid "Start"
+msgstr ""
+
+#: ../src/view/palettes.py:138
+msgid "Remove favorite"
+msgstr ""
+
+#: ../src/view/palettes.py:142
+msgid "Make favorite"
+msgstr ""
+
+#: ../src/view/palettes.py:191
+msgid "Show contents"
+msgstr ""
+
+#: ../src/view/palettes.py:215
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr ""
+
+#: ../src/journal/journaltoolbox.py:62
+msgid "Search"
+msgstr ""
+
+#: ../src/journal/journaltoolbox.py:119
+msgid "Anytime"
+msgstr ""
+
+#: ../src/journal/journaltoolbox.py:121
+msgid "Today"
+msgstr ""
+
+#: ../src/journal/journaltoolbox.py:123
+msgid "Since yesterday"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 7 days.
+#: ../src/journal/journaltoolbox.py:125
+msgid "Past week"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 30 days.
+#: ../src/journal/journaltoolbox.py:127
+msgid "Past month"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 356 days.
+#: ../src/journal/journaltoolbox.py:129
+msgid "Past year"
+msgstr ""
+
+#: ../src/journal/journaltoolbox.py:136
+msgid "Anyone"
+msgstr ""
+
+#: ../src/journal/journaltoolbox.py:138
+msgid "My friends"
+msgstr ""
+
+#: ../src/journal/journaltoolbox.py:139
+msgid "My class"
+msgstr ""
+
+#. TRANS: Item in a combo box that filters by entry type.
+#: ../src/journal/journaltoolbox.py:255
+msgid "Anything"
+msgstr ""
+
+#. TODO: Add "Start with" menu item
+#: ../src/journal/journaltoolbox.py:325 ../src/journal/palettes.py:67
+msgid "Copy"
+msgstr ""
+
+#: ../src/journal/collapsedentry.py:248 ../src/journal/expandedentry.py:176
+#: ../src/journal/palettes.py:51
+msgid "Untitled"
+msgstr ""
+
+#: ../src/journal/journalactivity.py:119 ../src/journal/volumesmanager.py:57
+msgid "Journal"
+msgstr ""
+
+#: ../src/journal/expandedentry.py:222
+msgid "No preview"
+msgstr ""
+
+#: ../src/journal/expandedentry.py:241
+msgid "Participants:"
+msgstr ""
+
+#: ../src/journal/expandedentry.py:266
+msgid "Description:"
+msgstr ""
+
+#: ../src/journal/expandedentry.py:292
+msgid "Tags:"
+msgstr ""
+
+#: ../src/journal/objectchooser.py:134
+msgid "Choose an object"
+msgstr ""
+
+#: ../src/journal/objectchooser.py:139
+msgid "Close"
+msgstr ""
+
+#: ../src/journal/volumestoolbar.py:93
+msgid "Unmount"
+msgstr ""
+
+#: ../src/journal/misc.py:95
+msgid "No date"
+msgstr ""
+
+#: ../src/journal/listview.py:39
+msgid "Your Journal is empty"
+msgstr ""
+
+#: ../src/journal/listview.py:40
+msgid "No matching entries "
+msgstr ""
+
+#: ../src/journal/modalalert.py:59
+msgid "Your Journal is full"
+msgstr ""
+
+#: ../src/journal/modalalert.py:63
+msgid "Please delete some old Journal entries to make space for new ones."
+msgstr ""
+
+#: ../src/journal/modalalert.py:75
+msgid "Show Journal"
+msgstr ""
diff --git a/shell/po/sl.po b/shell/po/sl.po
new file mode 100644
index 0000000..6981824
--- /dev/null
+++ b/shell/po/sl.po
@@ -0,0 +1,1098 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-02-21 00:30-0500\n"
+"PO-Revision-Date: 2009-02-28 04:26-0500\n"
+"Last-Translator: Denis Oštir <denis.ostir@gmail.com>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Pootle 1.1.0rc2\n"
+
+#: ../extensions/cpsection/aboutme/__init__.py:24
+msgid "About Me"
+msgstr "O meni"
+
+#: ../extensions/cpsection/aboutme/model.py:43
+msgid "You must enter a name."
+msgstr "Vpisati morat ime."
+
+#: ../extensions/cpsection/aboutme/model.py:68
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr "pisalo: barva=%s odtenek=%s"
+
+#: ../extensions/cpsection/aboutme/model.py:71
+#, python-format
+msgid "stroke: %s"
+msgstr "pisalo: %s"
+
+#: ../extensions/cpsection/aboutme/model.py:73
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr "zapolni: barva=%s odtenek=%s"
+
+#: ../extensions/cpsection/aboutme/model.py:75
+#, python-format
+msgid "fill: %s"
+msgstr "zapolni: %s"
+
+#: ../extensions/cpsection/aboutme/model.py:86
+msgid "Error in specified color modifiers."
+msgstr "Napačno določena sprememba barve"
+
+#: ../extensions/cpsection/aboutme/model.py:89
+msgid "Error in specified colors."
+msgstr "Napačno določena barva"
+
+#: ../extensions/cpsection/aboutme/view.py:94 ../src/jarabe/intro/window.py:92
+msgid "Name:"
+msgstr "Ime:"
+
+#: ../extensions/cpsection/aboutme/view.py:128
+msgid "Click to change your color:"
+msgstr "Klikni za spremembo barve:"
+
+#: ../extensions/cpsection/aboutcomputer/__init__.py:21
+msgid "About my Computer"
+msgstr "O mojem računalniku"
+
+#: ../extensions/cpsection/aboutcomputer/model.py:26
+msgid "Not available"
+msgstr "Ni na voljo"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:59
+msgid "Identity"
+msgstr "Identiteta"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:68
+msgid "Serial Number:"
+msgstr "Serijska številka:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:90
+msgid "Software"
+msgstr "Program"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:99
+msgid "Build:"
+msgstr "Različica:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:114
+msgid "Sugar:"
+msgstr "Sugar:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:130
+msgid "Firmware:"
+msgstr "Firmware:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:145
+msgid "Wireless Firmware:"
+msgstr "Brezžična oprema:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:168
+msgid "Copyright and License"
+msgstr "Lastnik pravic"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:176
+msgid ""
+"© 2008 One Laptop per Child Association Inc; Red Hat Inc; and Contributors."
+msgstr "© 2008 One Laptop per Child Association Inc, Red Hat Inc in sodelavci."
+
+#: ../extensions/cpsection/aboutcomputer/view.py:183
+msgid ""
+"Sugar is the graphical user interface that you are looking at. Sugar is free "
+"software, covered by the GNU General Public License, and you are welcome to "
+"change it and/or distribute copies of it under certain conditions described "
+"therein."
+msgstr ""
+"Sugar je grafični vmesnik, ki ga uporabljate. Je brezplačen, avtorske "
+"pravice glede njegove uporabe pa ureja GNU General Public License. Zato ga "
+"lahko spreminjate in/ali distribuirate, a pod nekaterimi pogoji, zapisanimi "
+"v omenjeni licenci."
+
+#: ../extensions/cpsection/aboutcomputer/view.py:195
+msgid "Full license:"
+msgstr "Licenčne pravice:"
+
+#: ../extensions/cpsection/datetime/__init__.py:21
+msgid "Date & Time"
+msgstr "Datum in ura"
+
+#: ../extensions/cpsection/datetime/model.py:87
+msgid "Error timezone does not exist."
+msgstr "Napaka: časovno območje ne obstaja."
+
+#: ../extensions/cpsection/datetime/view.py:68 ../data/sugar.schemas.in.h:19
+msgid "Timezone"
+msgstr "Časovno območje"
+
+#: ../extensions/cpsection/frame/__init__.py:21
+msgid "Frame"
+msgstr "Okvir"
+
+#: ../extensions/cpsection/frame/model.py:38
+#: ../extensions/cpsection/frame/model.py:60
+msgid "Value must be an integer."
+msgstr "Vrednost mora biti številka."
+
+#: ../extensions/cpsection/frame/view.py:26
+msgid "never"
+msgstr "nikoli"
+
+#: ../extensions/cpsection/frame/view.py:27
+msgid "instantaneous"
+msgstr "takojšnji"
+
+#: ../extensions/cpsection/frame/view.py:28
+#, python-format
+msgid "%s seconds"
+msgstr "%s sekund"
+
+#: ../extensions/cpsection/frame/view.py:52
+msgid "Activation Delay"
+msgstr "Zamik aktivacije"
+
+#: ../extensions/cpsection/frame/view.py:76
+msgid "Corner"
+msgstr "Kot"
+
+#: ../extensions/cpsection/frame/view.py:111
+msgid "Edge"
+msgstr "Rob"
+
+#: ../extensions/cpsection/language/__init__.py:21
+#: ../extensions/cpsection/language/view.py:32
+msgid "Language"
+msgstr "Jezik"
+
+#: ../extensions/cpsection/language/model.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr "Dostop do ~/.i18n ni mogoče. Ustvari standardne nastavitve."
+
+#: ../extensions/cpsection/language/model.py:124
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr "Jezika za kodo=%s ni bilo mogoče določiti."
+
+#: ../extensions/cpsection/language/model.py:144
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr "Oprosti, ne govorim '%s'."
+
+#: ../extensions/cpsection/network/__init__.py:21
+#: ../extensions/cpsection/network/view.py:28
+msgid "Network"
+msgstr "Omrežje"
+
+#: ../extensions/cpsection/network/model.py:79
+msgid "State is unknown."
+msgstr "Neznana država."
+
+#: ../extensions/cpsection/network/model.py:105
+msgid "Error in specified radio argument use on/off."
+msgstr "Napaka pri določanju stanja radijske povezave (vključen/izključen)."
+
+#: ../extensions/cpsection/network/model.py:137
+msgid "Error in specified argument use 0/1."
+msgstr "Napaka pri uporabi podatka 0/1."
+
+#: ../extensions/cpsection/network/view.py:56
+msgid "Wireless"
+msgstr "Brezžično"
+
+#: ../extensions/cpsection/network/view.py:64
+msgid "Turn off the wireless radio to save battery life"
+msgstr "Izklopi brezžično radijsko povezavo za podaljšanje trajanja baterije"
+
+#: ../extensions/cpsection/network/view.py:77
+msgid "Radio"
+msgstr "Radijska povezava:"
+
+#: ../extensions/cpsection/network/view.py:93
+msgid "Discard network history if you have trouble connecting to the network"
+msgstr "Zavrži zgodovino mreže, če imate težave pri povezavi z mrežo"
+
+#: ../extensions/cpsection/network/view.py:102
+msgid "Discard network history"
+msgstr "Zavrži zgodovino mreže"
+
+#: ../extensions/cpsection/network/view.py:115
+msgid "Collaboration"
+msgstr "Sodelovanje"
+
+#: ../extensions/cpsection/network/view.py:123
+msgid ""
+"The server is the equivalent of what room you are in; people on the same "
+"server will be able to see each other, even when they aren't on the same "
+"network."
+msgstr ""
+"Strežnik predstavlja sobo v kateri se nahajate. Uporabniki na istem "
+"strežniku se lahko vidijo, tudi če niso priključeni na isto omrežje."
+
+#: ../extensions/cpsection/network/view.py:133
+msgid "Server:"
+msgstr "Strežnik:"
+
+#: ../extensions/cpsection/power/__init__.py:21
+msgid "Power"
+msgstr "Energija"
+
+#: ../extensions/cpsection/power/model.py:54
+msgid "Error in automatic pm argument, use on/off."
+msgstr "Napaka pri samodejni rabi ukaza pm, uporabi izklop/vklop."
+
+#: ../extensions/cpsection/power/model.py:81
+msgid "Error in extreme pm argument, use on/off."
+msgstr "Napaka pri ekstremni vrednosti ukaza pm, uporabi izklop/vklop."
+
+#: ../extensions/cpsection/power/view.py:47
+msgid "Power management"
+msgstr "Nazdor porabe električne energije"
+
+#: ../extensions/cpsection/power/view.py:57
+msgid "Automatic power management (increases battery life)"
+msgstr ""
+"Samodejni nadzor porabe električne energije (podalšuje življenjsko dobo "
+"baterije)"
+
+#: ../extensions/cpsection/power/view.py:85
+msgid ""
+"Extreme power management (disableswireless radio, increases battery life)"
+msgstr ""
+"Ekstremni nadzor porabe električne energije (ugasnjen radijski del, podaljša "
+"življenjsko dobo baterije)"
+
+#: ../extensions/deviceicon/battery.py:56
+msgid "My Battery"
+msgstr "Moja baterija"
+
+#: ../extensions/deviceicon/battery.py:137
+msgid "Removed"
+msgstr "Odstranjeno"
+
+#: ../extensions/deviceicon/battery.py:140
+msgid "Charging"
+msgstr "Polnjenje"
+
+#: ../extensions/deviceicon/battery.py:143
+msgid "Very little power remaining"
+msgstr "Baterija je skoraj prazna"
+
+#: ../extensions/deviceicon/battery.py:149
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr "Ostaja %(hour)d:%(min).2d"
+
+#: ../extensions/deviceicon/battery.py:152
+msgid "Charged"
+msgstr "Polno"
+
+#: ../extensions/deviceicon/network.py:40
+#, python-format
+msgid "IP address: %s"
+msgstr "IP naslov: %s"
+
+#: ../extensions/deviceicon/network.py:104
+msgid "Disconnect..."
+msgstr "Prekini ... "
+
+#: ../extensions/deviceicon/network.py:109
+#: ../src/jarabe/desktop/meshbox.py:247
+msgid "Connecting..."
+msgstr "Povezujem ..."
+
+# TODO: show the channel number
+#: ../extensions/deviceicon/network.py:113
+#: ../extensions/deviceicon/network.py:166
+#: ../src/jarabe/desktop/meshbox.py:253
+msgid "Connected"
+msgstr "Povezan"
+
+#: ../extensions/deviceicon/network.py:126
+msgid "Channel"
+msgstr "Kanal"
+
+#: ../extensions/deviceicon/network.py:141
+msgid "Wired Network"
+msgstr "Žično omrežje"
+
+#: ../extensions/deviceicon/network.py:169
+msgid "Speed"
+msgstr "Hitrost"
+
+#: ../extensions/deviceicon/speaker.py:46
+msgid "My Speakers"
+msgstr "Moji zvočniki"
+
+# malo težko je UNMUTE prevest drugače
+#: ../extensions/deviceicon/speaker.py:128
+msgid "Unmute"
+msgstr "Glasno"
+
+#: ../extensions/deviceicon/speaker.py:131
+msgid "Mute"
+msgstr "Tiho"
+
+#: ../extensions/globalkey/screenshot.py:50
+msgid "Screenshot"
+msgstr "Slika zaslona"
+
+#: ../data/sugar.schemas.in.h:1
+msgid "Backup URL"
+msgstr "Nadomestni spletni naslov"
+
+#: ../data/sugar.schemas.in.h:2
+msgid ""
+"Color for the XO icon that is used throughout the desktop. The string is "
+"composed of the stroke color and fill color, format is that of rbg colors. "
+"Example: #AC32FF,#9A5200"
+msgstr ""
+"Barva ikone XO, ki jo uporabljate na celotnem namizju. Zaporedje je "
+"sestavljeno iz barve črte in barve polnila v formatu RGB. Primer: "
+"#AC32FF,#9A5200"
+
+#: ../data/sugar.schemas.in.h:3
+msgid "Corner Delay"
+msgstr "Zamik pri kotu"
+
+#: ../data/sugar.schemas.in.h:4
+msgid "Delay for the activation of the frame using the corners."
+msgstr "Zamik pri prikazu okvirja ob premiku v kot"
+
+#: ../data/sugar.schemas.in.h:5
+msgid "Delay for the activation of the frame using the edges."
+msgstr "Zamik pri prikazu okvirja ob premiku na rob"
+
+#: ../data/sugar.schemas.in.h:6
+msgid "Edge Delay"
+msgstr "Zamik ob robu"
+
+#: ../data/sugar.schemas.in.h:7
+msgid "Favorites Layout"
+msgstr "Izgled priljubljenih"
+
+#: ../data/sugar.schemas.in.h:8
+msgid "Favorites resume mode"
+msgstr "Način ponovitve priljubljenih"
+
+#: ../data/sugar.schemas.in.h:9
+msgid ""
+"If TRUE, Sugar will make us searchable for the other users of the Jabber "
+"server."
+msgstr ""
+"Če je oznaka DA, bo Sugar omogočil vidnost drugim uporabnikom na strežniku "
+"Jabber"
+
+#: ../data/sugar.schemas.in.h:10
+msgid "Jabber Server"
+msgstr "Strežnik Jabber"
+
+#: ../data/sugar.schemas.in.h:11
+msgid "Layout of the favorites view."
+msgstr "Izgled pogleda priljubljenih"
+
+#: ../data/sugar.schemas.in.h:12
+msgid "Power Automatic"
+msgstr "Samodejno upravljanje z elektriko"
+
+#: ../data/sugar.schemas.in.h:13
+msgid "Power Automatic."
+msgstr "Samodejno upravljanje z elektriko"
+
+#: ../data/sugar.schemas.in.h:14
+msgid "Power Extreme"
+msgstr "Ekstremno upravljanje z elektriko"
+
+#: ../data/sugar.schemas.in.h:15
+msgid "Power Extreme."
+msgstr "Ekstremno upravljanje z elektriko"
+
+#: ../data/sugar.schemas.in.h:16
+msgid "Publish to Gadget"
+msgstr "Objavi v napravi"
+
+#: ../data/sugar.schemas.in.h:17
+msgid "Setting for muting the sound device."
+msgstr "Nastavitev utišanja zvočne naprave"
+
+#: ../data/sugar.schemas.in.h:18
+msgid "Sound Muted"
+msgstr "Zvok utišan"
+
+#: ../data/sugar.schemas.in.h:20
+msgid "Timezone setting for the system."
+msgstr "Nastavitev časovnega pasu za sistem"
+
+#: ../data/sugar.schemas.in.h:21
+msgid "Url of the jabber server to use."
+msgstr "Spletni naslov uporabljenega strežnika jabber"
+
+#: ../data/sugar.schemas.in.h:22
+msgid "Url where the backup is saved to."
+msgstr "Spletni naslov arhiva"
+
+#: ../data/sugar.schemas.in.h:23
+msgid "User Color"
+msgstr "Barva uporabnika"
+
+#: ../data/sugar.schemas.in.h:24
+msgid "User Name"
+msgstr "Uporabnikovo ime"
+
+#: ../data/sugar.schemas.in.h:25
+msgid "User name that is used throughout the desktop."
+msgstr "Uporabnikovo ime, ki se uporablja po celotnem namizju."
+
+#: ../data/sugar.schemas.in.h:26
+msgid "Volume Level"
+msgstr "Raven glasnosti"
+
+#: ../data/sugar.schemas.in.h:27
+msgid "Volume level for the sound device."
+msgstr "Raven glasnosti za zvočno napravo."
+
+#: ../data/sugar.schemas.in.h:28
+msgid ""
+"When in resume mode, clicking on a favorite icon will cause the last entry "
+"for that activity to be resumed."
+msgstr ""
+"V načinu nadaljevanja s klikom na priljubljeno ikono v aktivnosti "
+"nadaljujete pri zadnji shranjeni spremembi."
+
+#: ../src/jarabe/controlpanel/cmd.py:28
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+"nadzorna plošča programa sugar: OPOZORILO, najdena je več kot ena možnost z "
+"enakim imenom: %s modul: %r"
+
+#: ../src/jarabe/controlpanel/cmd.py:30
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr "nadzorna plošča programa sugar: ključ=%s ni veljavna možnost"
+
+#: ../src/jarabe/controlpanel/cmd.py:31
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr "nadzorna plošča programa sugar: %s"
+
+# TRANS: Translators, there's a empty line at the end of this string,
+# which must appear in the translated string (msgstr) as well.
+#. TRANS: Translators, there's a empty line at the end of this string,
+#. which must appear in the translated string (msgstr) as well.
+#: ../src/jarabe/controlpanel/cmd.py:37
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" -c key clear the current value for the key \n"
+" "
+msgstr ""
+"Uporaba: nadzorna plošča programa sugar [ možnost ] tipka [ ukaz ... ] \n"
+" Nadzor v okolju sugar. \n"
+" Možnosti: \n"
+" -h prikaži to sporočilo in končaj \n"
+" -l prikaži vse možnosti, ki so na voljo \n"
+" -h tipka prikaži informacije za to tipko \n"
+" -g tipka pridobi trenutno vrednost tipke \n"
+" -s tipka nastavi trenutno vrednost tipke \n"
+" -c tipka izbriši trenurno vrednost tipke \n"
+" "
+
+#: ../src/jarabe/controlpanel/cmd.py:50
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr "Za uveljavitev sprememb je potreben ponovni zagon programa sugar.\n"
+
+#: ../src/jarabe/controlpanel/gui.py:275
+msgid "Warning"
+msgstr "Opozorilo"
+
+#: ../src/jarabe/controlpanel/gui.py:276
+#: ../src/jarabe/controlpanel/sectionview.py:42
+msgid "Changes require restart"
+msgstr "Sprembe zahtevajo ponovni zagon"
+
+#: ../src/jarabe/controlpanel/gui.py:279
+msgid "Cancel changes"
+msgstr "Prekliči spremembe"
+
+#: ../src/jarabe/controlpanel/gui.py:284 ../src/jarabe/desktop/homebox.py:113
+msgid "Later"
+msgstr "Kasneje"
+
+#: ../src/jarabe/controlpanel/gui.py:288
+msgid "Restart now"
+msgstr "Ponovni zagon"
+
+#: ../src/jarabe/controlpanel/toolbar.py:61 ../src/jarabe/intro/window.py:188
+msgid "Done"
+msgstr "Končano"
+
+#: ../src/jarabe/controlpanel/toolbar.py:115
+#: ../src/jarabe/desktop/homebox.py:111
+#: ../src/jarabe/frame/activitiestray.py:687
+#: ../src/jarabe/frame/activitiestray.py:766
+#: ../src/jarabe/frame/activitiestray.py:794
+msgid "Cancel"
+msgstr "Prekliči"
+
+#: ../src/jarabe/controlpanel/toolbar.py:121
+#: ../src/jarabe/desktop/favoritesview.py:338
+msgid "Ok"
+msgstr "V redu"
+
+# TRANS: label for the freeform layout in the favorites view
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:114
+msgid "Freeform"
+msgstr "Prostoročno"
+
+# TRANS: label for the ring layout in the favorites view
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:196
+msgid "Ring"
+msgstr "Obroč"
+
+# TRANS: label for the spiral layout in the favorites view
+#. TRANS: label for the spiral layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:332
+msgid "Spiral"
+msgstr "Spirala"
+
+# TRANS: label for the box layout in the favorites view
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:399
+msgid "Box"
+msgstr "Okvir"
+
+# TRANS: label for the box layout in the favorites view
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:440
+msgid "Triangle"
+msgstr "Trikotnik"
+
+#: ../src/jarabe/desktop/favoritesview.py:329
+msgid "Registration Failed"
+msgstr "Registracija ni uspela"
+
+#: ../src/jarabe/desktop/favoritesview.py:330
+#, python-format
+msgid "%s"
+msgstr "%s"
+
+#: ../src/jarabe/desktop/favoritesview.py:332
+msgid "Registration Successful"
+msgstr "Uspešna registracija"
+
+#: ../src/jarabe/desktop/favoritesview.py:333
+msgid "You are now registered with your school server."
+msgstr "Vpisan si v šolski strežnik."
+
+#: ../src/jarabe/desktop/favoritesview.py:668
+msgid "Register"
+msgstr "Registracija"
+
+#: ../src/jarabe/desktop/homebox.py:67
+msgid "Confirm erase"
+msgstr "Potrdi brisanje"
+
+#: ../src/jarabe/desktop/homebox.py:69
+#, python-format
+msgid "Confirm erase: Do you want to permanently erase %s?"
+msgstr "Potrdi brisanje: Želiš dokočno izbrisati %s?"
+
+#: ../src/jarabe/desktop/homebox.py:73 ../src/jarabe/frame/clipboardmenu.py:62
+#: ../src/jarabe/view/viewsource.py:218
+msgid "Keep"
+msgstr "Obdrži"
+
+#: ../src/jarabe/desktop/homebox.py:76
+#: ../src/jarabe/journal/journaltoolbox.py:357
+#: ../src/jarabe/journal/palettes.py:112 ../src/jarabe/view/palettes.py:153
+msgid "Erase"
+msgstr "Izbriši"
+
+#: ../src/jarabe/desktop/homebox.py:106
+msgid "Software Update"
+msgstr "Nadgradnja programa"
+
+#: ../src/jarabe/desktop/homebox.py:107
+msgid "Update your activities to ensure compatibility with your new software"
+msgstr ""
+"Nadgradite aktivnosti, da zagotovite ustreznost z novo programsko opremo."
+
+#: ../src/jarabe/desktop/homebox.py:116
+msgid "Check now"
+msgstr "Preveri zdaj"
+
+#: ../src/jarabe/desktop/homebox.py:233
+msgid "List view"
+msgstr "Seznam"
+
+#: ../src/jarabe/desktop/homebox.py:234
+msgid "<Ctrl>2"
+msgstr "<Ctrl>2"
+
+#: ../src/jarabe/desktop/homebox.py:296
+msgid "Favorites view"
+msgstr "Priljubljene"
+
+#: ../src/jarabe/desktop/homebox.py:297
+msgid "<Ctrl>1"
+msgstr "<Ctrl>1"
+
+#: ../src/jarabe/desktop/keydialog.py:131
+msgid "Key Type:"
+msgstr "Tip ključa:"
+
+#: ../src/jarabe/desktop/keydialog.py:151
+msgid "Authentication Type:"
+msgstr "Tip prepoznave:"
+
+#: ../src/jarabe/desktop/keydialog.py:215
+msgid "WPA & WPA2 Personal"
+msgstr "WPA in WPA2 Osebni ključ"
+
+#: ../src/jarabe/desktop/keydialog.py:224
+msgid "Wireless Security:"
+msgstr "Varovanje brezžičnega omrežja:"
+
+#: ../src/jarabe/desktop/meshbox.py:131
+msgid "Connect"
+msgstr "Poveži"
+
+#: ../src/jarabe/desktop/meshbox.py:135
+msgid "Disconnect"
+msgstr "Prekini"
+
+# TRANS: Action label for resuming an activity.
+#. TRANS: Action label for resuming an activity.
+#: ../src/jarabe/desktop/meshbox.py:441
+#: ../src/jarabe/frame/activitiestray.py:711
+#: ../src/jarabe/journal/journaltoolbox.py:425
+#: ../src/jarabe/journal/palettes.py:72 ../src/jarabe/view/palettes.py:66
+msgid "Resume"
+msgstr "Nadaljuj"
+
+#: ../src/jarabe/desktop/meshbox.py:446
+#: ../src/jarabe/frame/activitiestray.py:227
+msgid "Join"
+msgstr "Pridruži se"
+
+#: ../src/jarabe/desktop/schoolserver.py:34
+msgid "Cannot obtain data needed for registration."
+msgstr "Ne morem pridobiti podatkov, potrebnih za registracijo."
+
+#: ../src/jarabe/desktop/schoolserver.py:51
+msgid "Cannot connect to the server."
+msgstr "Povezava s strežnikom ni mogoča."
+
+#: ../src/jarabe/desktop/schoolserver.py:56
+msgid "The server could not complete the request."
+msgstr "Strežnik ne more izpolniti zahteve."
+
+#: ../src/jarabe/frame/activitiestray.py:232
+#: ../src/jarabe/frame/activitiestray.py:659
+msgid "Decline"
+msgstr "Zavrni"
+
+#: ../src/jarabe/frame/activitiestray.py:612
+#, python-format
+msgid "%dB"
+msgstr "%dB"
+
+#: ../src/jarabe/frame/activitiestray.py:614
+#, python-format
+msgid "%dKB"
+msgstr "%dKB"
+
+#: ../src/jarabe/frame/activitiestray.py:616
+#, python-format
+msgid "%dMB"
+msgstr "%dMB"
+
+#: ../src/jarabe/frame/activitiestray.py:633
+#, python-format
+msgid "%s of %s"
+msgstr "%s od %s"
+
+#: ../src/jarabe/frame/activitiestray.py:644
+#, python-format
+msgid "Transfer from %r"
+msgstr "Prenesi z %r"
+
+#: ../src/jarabe/frame/activitiestray.py:654
+msgid "Accept"
+msgstr "Sprejmi"
+
+#: ../src/jarabe/frame/activitiestray.py:677
+#: ../src/jarabe/frame/activitiestray.py:784
+#, python-format
+msgid "%s (%s)"
+msgstr "%s (%s)"
+
+#: ../src/jarabe/frame/activitiestray.py:755
+#, python-format
+msgid "Transfer to %r"
+msgstr "Prenesi na %r"
+
+#: ../src/jarabe/frame/clipboardmenu.py:52
+msgid "Remove"
+msgstr "Odstrani"
+
+#: ../src/jarabe/frame/clipboardmenu.py:57
+#: ../src/jarabe/frame/clipboardmenu.py:80
+msgid "Open"
+msgstr "Odpri"
+
+#: ../src/jarabe/frame/clipboardmenu.py:85
+msgid "Open with"
+msgstr "Odpri z"
+
+#: ../src/jarabe/frame/clipboardobject.py:49
+#, python-format
+msgid "%s clipping"
+msgstr "%s strižem"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:36
+msgid "Neighborhood"
+msgstr "Soseščina"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:38
+msgid "Group"
+msgstr "Skupina"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:40
+msgid "Home"
+msgstr "Domov"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:42
+msgid "Activity"
+msgstr "Aktivnost"
+
+#: ../src/jarabe/intro/window.py:124
+msgid "Click to change color:"
+msgstr "Klikni za spremembo barve:"
+
+#: ../src/jarabe/intro/window.py:174 ../src/jarabe/journal/detailview.py:103
+msgid "Back"
+msgstr "Nazaj"
+
+#: ../src/jarabe/intro/window.py:191
+msgid "Next"
+msgstr "Naslednji"
+
+#: ../src/jarabe/journal/collapsedentry.py:258
+#: ../src/jarabe/journal/expandedentry.py:159
+#: ../src/jarabe/journal/palettes.py:66
+msgid "Untitled"
+msgstr "Neimenovan"
+
+#: ../src/jarabe/journal/expandedentry.py:205
+msgid "No preview"
+msgstr "Predogled ni na voljo"
+
+#: ../src/jarabe/journal/expandedentry.py:224
+msgid "Participants:"
+msgstr "Udeleženci:"
+
+#: ../src/jarabe/journal/expandedentry.py:247
+msgid "Description:"
+msgstr "Opis:"
+
+#: ../src/jarabe/journal/expandedentry.py:273
+msgid "Tags:"
+msgstr "Oznake:"
+
+#: ../src/jarabe/journal/journalactivity.py:108
+#: ../src/jarabe/journal/volumestoolbar.py:47
+msgid "Journal"
+msgstr "Beležka"
+
+#: ../src/jarabe/journal/journaltoolbox.py:65
+msgid "Search"
+msgstr "Išči"
+
+#: ../src/jarabe/journal/journaltoolbox.py:124
+msgid "Anytime"
+msgstr "Kadarkoli"
+
+#: ../src/jarabe/journal/journaltoolbox.py:126
+msgid "Today"
+msgstr "Danes"
+
+#: ../src/jarabe/journal/journaltoolbox.py:128
+msgid "Since yesterday"
+msgstr "Od včeraj"
+
+# TRANS: Filter entries modified during the last 7 days.
+#. TRANS: Filter entries modified during the last 7 days.
+#: ../src/jarabe/journal/journaltoolbox.py:130
+msgid "Past week"
+msgstr "V zadnjem tednu"
+
+# TRANS: Filter entries modified during the last 30 days.
+#. TRANS: Filter entries modified during the last 30 days.
+#: ../src/jarabe/journal/journaltoolbox.py:132
+msgid "Past month"
+msgstr "V zadnjem mesecu"
+
+# TRANS: Filter entries modified during the last 356 days.
+#. TRANS: Filter entries modified during the last 356 days.
+#: ../src/jarabe/journal/journaltoolbox.py:134
+msgid "Past year"
+msgstr "V zadnjem letu"
+
+#: ../src/jarabe/journal/journaltoolbox.py:141
+msgid "Anyone"
+msgstr "Kdorkoli"
+
+#: ../src/jarabe/journal/journaltoolbox.py:143
+msgid "My friends"
+msgstr "Moji prijatelji"
+
+#: ../src/jarabe/journal/journaltoolbox.py:144
+msgid "My class"
+msgstr "Moj razred"
+
+# TRANS: Item in a combo box that filters by entry type.
+#. TRANS: Item in a combo box that filters by entry type.
+#: ../src/jarabe/journal/journaltoolbox.py:271
+msgid "Anything"
+msgstr "Karkoli"
+
+# TODO: Add "Start with" menu item
+#: ../src/jarabe/journal/journaltoolbox.py:347
+#: ../src/jarabe/journal/palettes.py:90
+msgid "Copy"
+msgstr "Kopiraj"
+
+# TRANS: Action label for starting an entry.
+#. TRANS: Action label for starting an entry.
+#: ../src/jarabe/journal/journaltoolbox.py:428
+#: ../src/jarabe/journal/palettes.py:75 ../src/jarabe/view/palettes.py:135
+msgid "Start"
+msgstr "Zaženi"
+
+#: ../src/jarabe/journal/listview.py:40
+msgid "Your Journal is empty"
+msgstr "Tvoja beležka je prazna"
+
+#: ../src/jarabe/journal/listview.py:41
+msgid "No matching entries "
+msgstr "Ne najdem vsebine za "
+
+#: ../src/jarabe/journal/listview.py:369
+msgid "Clear search"
+msgstr "Počisti iskanje"
+
+#: ../src/jarabe/journal/misc.py:91
+msgid "No date"
+msgstr "Brez datuma"
+
+#: ../src/jarabe/journal/modalalert.py:63
+msgid "Your Journal is full"
+msgstr "Tvoja beležka je polna"
+
+#: ../src/jarabe/journal/modalalert.py:67
+msgid "Please delete some old Journal entries to make space for new ones."
+msgstr "Izbriši starejše vpise v beležko, da narediš prostor za nove."
+
+#: ../src/jarabe/journal/modalalert.py:79
+msgid "Show Journal"
+msgstr "Prikaži beležko"
+
+#: ../src/jarabe/journal/objectchooser.py:147
+msgid "Choose an object"
+msgstr "Izberi predmet"
+
+#: ../src/jarabe/journal/objectchooser.py:152
+#: ../src/jarabe/view/viewsource.py:308
+msgid "Close"
+msgstr "Zapri"
+
+#: ../src/jarabe/journal/palettes.py:73
+msgid "Resume with"
+msgstr "Nadaljuj z"
+
+#: ../src/jarabe/journal/palettes.py:76
+msgid "Start with"
+msgstr "Začni z"
+
+#: ../src/jarabe/journal/palettes.py:98
+msgid "Send to"
+msgstr "Pošlji"
+
+#: ../src/jarabe/journal/palettes.py:107
+msgid "View Details"
+msgstr "Poglej podrobnosti"
+
+#: ../src/jarabe/journal/palettes.py:185
+msgid "No friends present"
+msgstr "Prisoten ni noben prijatelj"
+
+#: ../src/jarabe/journal/palettes.py:190
+msgid "No valid connection found"
+msgstr "Na voljo ni nobena veljavna povezava"
+
+#: ../src/jarabe/journal/palettes.py:218
+msgid "No activity to resume entry"
+msgstr "Ni aktivnosti za nadaljevanje"
+
+#: ../src/jarabe/journal/palettes.py:220
+msgid "No activity to start entry"
+msgstr "Ni aktivnosti za začetek"
+
+#: ../src/jarabe/view/buddymenu.py:62
+msgid "Remove friend"
+msgstr "Odstrani prijatelja"
+
+#: ../src/jarabe/view/buddymenu.py:65
+msgid "Make friend"
+msgstr "Dodaj prijatelja"
+
+#: ../src/jarabe/view/buddymenu.py:82
+msgid "My Settings"
+msgstr "Moje nastavitve"
+
+#: ../src/jarabe/view/buddymenu.py:90
+msgid "Logout"
+msgstr "Odjava"
+
+#: ../src/jarabe/view/buddymenu.py:95
+msgid "Restart"
+msgstr "Ponovni zagon"
+
+#: ../src/jarabe/view/buddymenu.py:100
+msgid "Shutdown"
+msgstr "Zaustavitev"
+
+#: ../src/jarabe/view/buddymenu.py:135
+#, python-format
+msgid "Invite to %s"
+msgstr "Povabi na %s"
+
+#: ../src/jarabe/view/palettes.py:47
+msgid "Starting..."
+msgstr "Zaganjam ..."
+
+#. TODO: share-with, keep
+#: ../src/jarabe/view/palettes.py:73
+msgid "View Source"
+msgstr "Poglej izvor"
+
+#: ../src/jarabe/view/palettes.py:84
+msgid "Stop"
+msgstr "Ustavi"
+
+#: ../src/jarabe/view/palettes.py:171
+msgid "Remove favorite"
+msgstr "Odstrani priljubljeno"
+
+#: ../src/jarabe/view/palettes.py:175
+msgid "Make favorite"
+msgstr "Nastavi priljubljeno"
+
+#: ../src/jarabe/view/palettes.py:238
+msgid "Show contents"
+msgstr "Prikaži vsebino"
+
+#: ../src/jarabe/view/palettes.py:260 ../src/jarabe/view/palettes.py:309
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr "%(free_space)d MB prosto"
+
+#: ../src/jarabe/view/palettes.py:285
+msgid "Unmount"
+msgstr "Odstrani"
+
+#: ../src/jarabe/view/viewsource.py:208
+msgid "Instance Source"
+msgstr "Primer izvora"
+
+#: ../src/jarabe/view/viewsource.py:233
+msgid "Source"
+msgstr "Izvor"
+
+#: ../src/jarabe/view/viewsource.py:292
+msgid "Activity Bundle Source"
+msgstr "Izvor kompleta aktivnosti"
+
+#: ../src/jarabe/view/viewsource.py:299
+#, python-format
+msgid "View source: %r"
+msgstr "Poglej vir: %r"
+
+#~ msgid "Document"
+#~ msgstr "Dokument"
+
+#~ msgid "Resume by default"
+#~ msgstr "Vedno nadaljuj"
+
+#~ msgid "Encryption Type:"
+#~ msgstr "Tip kodiranja:"
+
+#~ msgid "Disconnecting..."
+#~ msgstr "Prekinjam ..."
+
+#~ msgid "Mesh Network"
+#~ msgstr "Omrežje Mesh"
+
+#~ msgid "Disconnected"
+#~ msgstr "Odklopljen"
+
+#~ msgid "About my XO"
+#~ msgstr "O mojem XO"
+
+#~ msgid "Mesh"
+#~ msgstr "Mesh"
+
+#~ msgid "Connected to a School Mesh Portal"
+#~ msgstr "Povezan na šolski Mesh portal"
+
+#~ msgid "Looking for a School Mesh Portal..."
+#~ msgstr "Iskanje šolskega Mesh portala ..."
+
+#~ msgid "Connected to an XO Mesh Portal"
+#~ msgstr "Povezan na XO Mesh portal"
+
+#~ msgid "Looking for an XO Mesh Portal..."
+#~ msgstr "Iskanje XO Mesh portala"
+
+#~ msgid "Connected to a Simple Mesh"
+#~ msgstr "Povezan v enostavno Mesh omrežje"
+
+#~ msgid "Starting a Simple Mesh"
+#~ msgstr "Ustvarjam enostavno Mesh omrežje"
+
+#~ msgid "Unknown Mesh"
+#~ msgstr "Neznano Mesh omrežje"
+
+#~ msgid "Settings"
+#~ msgstr "Nastavitve"
+
+#, python-format
+#~ msgid "Clipboard object: %s."
+#~ msgstr "Predmet na odložišču: %s."
+
+#~ msgid "You must enter a server."
+#~ msgstr "Vpisati moraš strežnik."
+
+#~ msgid "Control Panel"
+#~ msgstr "Nadzorna plošča"
+
+#~ msgid "<Ctrl>R"
+#~ msgstr "<Ctrl>R"
diff --git a/shell/po/sq.po b/shell/po/sq.po
new file mode 100644
index 0000000..bf4ba67
--- /dev/null
+++ b/shell/po/sq.po
@@ -0,0 +1,1215 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-08-26 00:31-0400\n"
+"PO-Revision-Date: 2009-06-18 11:32+0100\n"
+"Last-Translator: Heroid <heroid@ayih.org>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.3.0\n"
+
+#: ../extensions/cpsection/aboutme/__init__.py:24
+msgid "About Me"
+msgstr "Rreth Meje"
+
+#: ../extensions/cpsection/aboutme/model.py:43
+msgid "You must enter a name."
+msgstr "Vetem emri duhet shkruar"
+
+#: ../extensions/cpsection/aboutme/model.py:68
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr "goditje: color=%s hue=%s"
+
+#: ../extensions/cpsection/aboutme/model.py:71
+#, python-format
+msgid "stroke: %s"
+msgstr "goditje: %s"
+
+#: ../extensions/cpsection/aboutme/model.py:73
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr "mbushje: color=%s hue=%s"
+
+#: ../extensions/cpsection/aboutme/model.py:75
+#, python-format
+msgid "fill: %s"
+msgstr "mbushje: %s"
+
+#: ../extensions/cpsection/aboutme/model.py:86
+msgid "Error in specified color modifiers."
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:89
+msgid "Error in specified colors."
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/view.py:94 ../src/jarabe/intro/window.py:92
+msgid "Name:"
+msgstr "Emri:"
+
+#: ../extensions/cpsection/aboutme/view.py:128
+msgid "Click to change your color:"
+msgstr "Kliko për ta ndrryshuar ngjyrën:"
+
+#: ../extensions/cpsection/aboutcomputer/__init__.py:21
+msgid "About my Computer"
+msgstr "Rreth Kompjuterit tim"
+
+#: ../extensions/cpsection/aboutcomputer/model.py:28
+msgid "Not available"
+msgstr "Nuk gjindet"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:60
+msgid "Identity"
+msgstr "Identiteti"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:69
+msgid "Serial Number:"
+msgstr "Numri Serik:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:91
+msgid "Software"
+msgstr "Softueri"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:100
+msgid "Build:"
+msgstr "Ndërto:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:115
+msgid "Sugar:"
+msgstr "Sugar:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:131
+msgid "Firmware:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:146
+msgid "Wireless Firmware:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:169
+msgid "Copyright and License"
+msgstr "Liqencë me Leje Kopjimi"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:184
+msgid ""
+"Sugar is the graphical user interface that you are looking at. Sugar is free "
+"software, covered by the GNU General Public License, and you are welcome to "
+"change it and/or distribute copies of it under certain conditions described "
+"therein."
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:196
+msgid "Full license:"
+msgstr "Liqencë e plotë:"
+
+#: ../extensions/cpsection/datetime/__init__.py:21
+msgid "Date & Time"
+msgstr "Data & Koha"
+
+#: ../extensions/cpsection/datetime/model.py:87
+msgid "Error timezone does not exist."
+msgstr "Gabim zona e kohës nuk ekziston"
+
+#: ../extensions/cpsection/datetime/view.py:68 ../data/sugar.schemas.in.h:27
+msgid "Timezone"
+msgstr "Zona e kohës"
+
+#: ../extensions/cpsection/frame/__init__.py:21
+msgid "Frame"
+msgstr "Fërmi"
+
+#: ../extensions/cpsection/frame/model.py:38
+#: ../extensions/cpsection/frame/model.py:60
+msgid "Value must be an integer."
+msgstr "Shifra duhet të jetë integer."
+
+#: ../extensions/cpsection/frame/view.py:26
+msgid "never"
+msgstr "kurrë"
+
+#: ../extensions/cpsection/frame/view.py:27
+msgid "instantaneous"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:28
+#, python-format
+msgid "%s seconds"
+msgstr "%s sekonda"
+
+#: ../extensions/cpsection/frame/view.py:52
+msgid "Activation Delay"
+msgstr "Koha e aktivizimit"
+
+#: ../extensions/cpsection/frame/view.py:76
+msgid "Corner"
+msgstr "Qoshe"
+
+#: ../extensions/cpsection/frame/view.py:111
+msgid "Edge"
+msgstr "Skaj"
+
+#: ../extensions/cpsection/keyboard/__init__.py:21
+#: ../extensions/cpsection/keyboard/view.py:31
+msgid "Keyboard"
+msgstr ""
+
+#: ../extensions/cpsection/keyboard/view.py:187
+msgid "Keyboard Model"
+msgstr ""
+
+#: ../extensions/cpsection/keyboard/view.py:243
+msgid "Key(s) to change layout"
+msgstr ""
+
+#: ../extensions/cpsection/keyboard/view.py:311
+msgid "Keyboard Layout(s)"
+msgstr ""
+
+#: ../extensions/cpsection/language/__init__.py:21
+#: ../extensions/cpsection/language/view.py:33
+msgid "Language"
+msgstr "Gjuhë"
+
+#: ../extensions/cpsection/language/model.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr ""
+
+#: ../extensions/cpsection/language/model.py:124
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr ""
+
+#: ../extensions/cpsection/language/model.py:144
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr "Më vjen keq unë nuk flas ´%s´."
+
+#: ../extensions/cpsection/language/view.py:56
+msgid ""
+"Add languages in the order you prefer. If a translation is not available, "
+"the next in the list will be used."
+msgstr ""
+
+#: ../extensions/cpsection/network/__init__.py:21
+#: ../extensions/cpsection/network/view.py:28
+msgid "Network"
+msgstr "Rrjet"
+
+#: ../extensions/cpsection/network/model.py:79
+msgid "State is unknown."
+msgstr "Shtet i panjohur."
+
+#: ../extensions/cpsection/network/model.py:105
+msgid "Error in specified radio argument use on/off."
+msgstr ""
+
+#: ../extensions/cpsection/network/model.py:137
+msgid "Error in specified argument use 0/1."
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:59
+msgid "Wireless"
+msgstr "Pa tela"
+
+#: ../extensions/cpsection/network/view.py:67
+msgid "Turn off the wireless radio to save battery life"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:80
+msgid "Radio"
+msgstr "Radio"
+
+#: ../extensions/cpsection/network/view.py:96
+msgid "Discard network history if you have trouble connecting to the network"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:105
+msgid "Discard network history"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:118
+msgid "Collaboration"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:126
+msgid ""
+"The server is the equivalent of what room you are in; people on the same "
+"server will be able to see each other, even when they aren't on the same "
+"network."
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:136
+msgid "Server:"
+msgstr "Server:"
+
+#: ../extensions/cpsection/power/__init__.py:21
+msgid "Power"
+msgstr ""
+
+#: ../extensions/cpsection/power/model.py:54
+msgid "Error in automatic pm argument, use on/off."
+msgstr ""
+
+#: ../extensions/cpsection/power/model.py:81
+msgid "Error in extreme pm argument, use on/off."
+msgstr ""
+
+#: ../extensions/cpsection/power/view.py:47
+msgid "Power management"
+msgstr "Menagjimi i energjisë"
+
+#: ../extensions/cpsection/power/view.py:57
+msgid "Automatic power management (increases battery life)"
+msgstr ""
+
+#: ../extensions/cpsection/power/view.py:85
+msgid ""
+"Extreme power management (disableswireless radio, increases battery life)"
+msgstr ""
+
+#: ../extensions/cpsection/updater/__init__.py:21
+#, fuzzy
+msgid "Software update"
+msgstr "Përditsim Softueri"
+
+#: ../extensions/cpsection/updater/view.py:62
+msgid ""
+"Software updates correct errors, eliminate security vulnerabilities, and "
+"provide new features."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:122
+#, python-format
+msgid "Checking %s..."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:124
+#, python-format
+msgid "Downloading %s..."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:126
+#, python-format
+msgid "Updating %s..."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:135
+msgid "Your software is up-to-date"
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:137
+#, python-format
+msgid "You can install %s update"
+msgid_plural "You can install %s updates"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../extensions/cpsection/updater/view.py:155
+msgid "Checking for updates..."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:160
+msgid "Installing updates..."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:165
+#, python-format
+msgid "%s update was installed"
+msgid_plural "%s updates were installed"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../extensions/cpsection/updater/view.py:244
+msgid "Install selected"
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:265
+#, python-format
+msgid "Download size: %s"
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:353
+#, python-format
+msgid "From version %(current)d to %(new)s (Size: %(size)s)"
+msgstr ""
+
+#. TRANS: download size is 0
+#: ../extensions/cpsection/updater/view.py:373
+#, fuzzy
+msgid "None"
+msgstr "E kryer"
+
+#. TRANS: download size of very small updates
+#: ../extensions/cpsection/updater/view.py:376
+msgid "1 KB"
+msgstr ""
+
+#. TRANS: download size of small updates, e.g. '250 KB'
+#: ../extensions/cpsection/updater/view.py:379
+#, python-format
+msgid "%.0f KB"
+msgstr ""
+
+#. TRANS: download size of updates, e.g. '2.3 MB'
+#: ../extensions/cpsection/updater/view.py:382
+#, python-format
+msgid "%.1f MB"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:58
+msgid "My Battery"
+msgstr "Baterija Ime"
+
+#: ../extensions/deviceicon/battery.py:137
+msgid "Removed"
+msgstr "E fshirë"
+
+#: ../extensions/deviceicon/battery.py:140
+msgid "Charging"
+msgstr "Duke u mbushur"
+
+#: ../extensions/deviceicon/battery.py:143
+msgid "Very little power remaining"
+msgstr "Shumë pak energji ka mbetur"
+
+#: ../extensions/deviceicon/battery.py:149
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr "%(orë)d:%(min).2d kanë mbetur"
+
+#: ../extensions/deviceicon/battery.py:152
+msgid "Charged"
+msgstr "I mbushur"
+
+#: ../extensions/deviceicon/network.py:44
+#, python-format
+msgid "IP address: %s"
+msgstr "IP addresa: %s"
+
+#: ../extensions/deviceicon/network.py:110
+msgid "Disconnect..."
+msgstr "Shkëputu..."
+
+#: ../extensions/deviceicon/network.py:114
+msgid "Create new wireless network"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:120
+#: ../src/jarabe/desktop/meshbox.py:261
+msgid "Connecting..."
+msgstr "Duke u lidhur..."
+
+#: ../extensions/deviceicon/network.py:124
+#: ../extensions/deviceicon/network.py:186
+#: ../src/jarabe/desktop/meshbox.py:267
+msgid "Connected"
+msgstr "I lidhur"
+
+#: ../extensions/deviceicon/network.py:146
+msgid "Channel"
+msgstr "Kanali"
+
+#: ../extensions/deviceicon/network.py:161
+msgid "Wired Network"
+msgstr "Rrjet i Quditshëm"
+
+#: ../extensions/deviceicon/network.py:189
+msgid "Speed"
+msgstr "Shpejtësija"
+
+#: ../extensions/deviceicon/network.py:415
+#, python-format
+msgid "%s's network %s"
+msgstr ""
+
+#: ../extensions/deviceicon/speaker.py:59
+msgid "My Speakers"
+msgstr "Spikerët e mi"
+
+#: ../extensions/deviceicon/speaker.py:133
+msgid "Unmute"
+msgstr "Lësho zërin"
+
+#: ../extensions/deviceicon/speaker.py:136
+msgid "Mute"
+msgstr "Hiqe zërin"
+
+#: ../extensions/globalkey/screenshot.py:56
+msgid "Mesh"
+msgstr ""
+
+#: ../extensions/globalkey/screenshot.py:58
+#: ../src/jarabe/frame/zoomtoolbar.py:39
+msgid "Group"
+msgstr "Grupi"
+
+#: ../extensions/globalkey/screenshot.py:60
+#: ../src/jarabe/frame/zoomtoolbar.py:41
+msgid "Home"
+msgstr "Shtëpija"
+
+#: ../extensions/globalkey/screenshot.py:66
+#: ../src/jarabe/frame/zoomtoolbar.py:43
+msgid "Activity"
+msgstr "Aktiviteti"
+
+#: ../extensions/globalkey/screenshot.py:69
+msgid "Screenshot"
+msgstr "Sekuencë ekrani"
+
+#: ../extensions/globalkey/screenshot.py:71
+#, python-format
+msgid "Screenshot of \"%s\""
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:1
+msgid "Backup URL"
+msgstr "Rikthe URL-në"
+
+#: ../data/sugar.schemas.in.h:2
+msgid ""
+"Color for the XO icon that is used throughout the desktop. The string is "
+"composed of the stroke color and fill color, format is that of rbg colors. "
+"Example: #AC32FF,#9A5200"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:3
+msgid "Corner Delay"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:4
+msgid "Delay for the activation of the frame using the corners."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:5
+msgid "Delay for the activation of the frame using the edges."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:6
+msgid "Edge Delay"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:7
+msgid "Favorites Layout"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:8
+msgid "Favorites resume mode"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:9
+msgid ""
+"If TRUE, Sugar will make us searchable for the other users of the Jabber "
+"server."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:10
+msgid "If TRUE, Sugar will show a \"Log out\" option."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:11
+msgid "Jabber Server"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:12
+msgid "Keyboard layouts"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:13
+msgid "Keyboard model"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:14
+msgid "Keyboard options"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:15
+msgid "Layout of the favorites view."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:16
+msgid ""
+"List of keyboard layouts. Each entry should be in the form layout(variant)"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:17
+msgid "List of keyboard options."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:18
+msgid "Power Automatic"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:19
+msgid "Power Automatic."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:20
+msgid "Power Extreme"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:21
+msgid "Power Extreme."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:22
+msgid "Publish to Gadget"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:23
+msgid "Setting for muting the sound device."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:24
+msgid "Show Log out"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:25
+msgid "Sound Muted"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:26
+msgid "The keyboard model to be used"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:28
+msgid "Timezone setting for the system."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:29
+msgid "Url of the jabber server to use."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:30
+msgid "Url where the backup is saved to."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:31
+msgid "User Color"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:32
+msgid "User Name"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:33
+msgid "User name that is used throughout the desktop."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:34
+msgid "Volume Level"
+msgstr "Niveli i Volumit"
+
+#: ../data/sugar.schemas.in.h:35
+msgid "Volume level for the sound device."
+msgstr "Niveli i volumit për pajisjen"
+
+#: ../data/sugar.schemas.in.h:36
+msgid ""
+"When in resume mode, clicking on a favorite icon will cause the last entry "
+"for that activity to be resumed."
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:28
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:30
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:31
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr ""
+
+#. TRANS: Translators, there's a empty line at the end of this string,
+#. which must appear in the translated string (msgstr) as well.
+#: ../src/jarabe/controlpanel/cmd.py:37
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" -c key clear the current value for the key \n"
+" "
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:50
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:280
+msgid "Warning"
+msgstr "Paralajmërim"
+
+#: ../src/jarabe/controlpanel/gui.py:281
+#: ../src/jarabe/controlpanel/sectionview.py:42
+msgid "Changes require restart"
+msgstr "Ndrryshimet kanë nevojë për rinisje"
+
+#: ../src/jarabe/controlpanel/gui.py:284
+msgid "Cancel changes"
+msgstr "Anulo ndrryshimet"
+
+#: ../src/jarabe/controlpanel/gui.py:289 ../src/jarabe/desktop/homebox.py:70
+msgid "Later"
+msgstr "Më vonë"
+
+#: ../src/jarabe/controlpanel/gui.py:293
+msgid "Restart now"
+msgstr "Rinis tani"
+
+#: ../src/jarabe/controlpanel/toolbar.py:61 ../src/jarabe/intro/window.py:188
+msgid "Done"
+msgstr "E kryer"
+
+#: ../src/jarabe/controlpanel/toolbar.py:115
+#: ../src/jarabe/desktop/homebox.py:68
+#: ../src/jarabe/frame/activitiestray.py:726
+#: ../src/jarabe/frame/activitiestray.py:822
+#: ../src/jarabe/frame/activitiestray.py:850
+msgid "Cancel"
+msgstr "Anulo"
+
+#: ../src/jarabe/controlpanel/toolbar.py:121
+#: ../src/jarabe/desktop/favoritesview.py:332
+msgid "Ok"
+msgstr "Në rregull"
+
+#: ../src/jarabe/desktop/activitieslist.py:80
+#: ../src/jarabe/journal/listview.py:147
+msgid "Title"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:91
+msgid "Version"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:105
+#: ../src/jarabe/journal/listview.py:178
+msgid "Date"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:234
+#, python-format
+msgid "Version %s"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:355
+msgid "Confirm erase"
+msgstr "Konfirmo fshirjen"
+
+#: ../src/jarabe/desktop/activitieslist.py:357
+#, python-format
+msgid "Confirm erase: Do you want to permanently erase %s?"
+msgstr "Konfirmo fshirjen: A doni që përgjithmonë ta fshini %s?"
+
+#: ../src/jarabe/desktop/activitieslist.py:361
+#: ../src/jarabe/frame/clipboardmenu.py:62
+#: ../src/jarabe/view/viewsource.py:218
+msgid "Keep"
+msgstr "Mbaje"
+
+#: ../src/jarabe/desktop/activitieslist.py:364
+#: ../src/jarabe/desktop/activitieslist.py:406
+#: ../src/jarabe/journal/journaltoolbox.py:360
+#: ../src/jarabe/journal/palettes.py:112
+msgid "Erase"
+msgstr "Fshije"
+
+#: ../src/jarabe/desktop/activitieslist.py:427
+msgid "Remove favorite"
+msgstr "Fshije favoritin"
+
+#: ../src/jarabe/desktop/activitieslist.py:431
+msgid "Make favorite"
+msgstr "Bëje favorit"
+
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:116
+msgid "Freeform"
+msgstr "Formë e lirë"
+
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:198
+msgid "Ring"
+msgstr "Unazë"
+
+#. TRANS: label for the spiral layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:334
+msgid "Spiral"
+msgstr "Spirale"
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:401
+msgid "Box"
+msgstr "Kuti"
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:442
+msgid "Triangle"
+msgstr "Trekëndësh"
+
+#: ../src/jarabe/desktop/favoritesview.py:323
+msgid "Registration Failed"
+msgstr "Regjistrimi Dsëhtoi"
+
+#: ../src/jarabe/desktop/favoritesview.py:324
+#, python-format
+msgid "%s"
+msgstr "%s"
+
+#: ../src/jarabe/desktop/favoritesview.py:326
+msgid "Registration Successful"
+msgstr "Regjistrimi u krye me Sukses"
+
+#: ../src/jarabe/desktop/favoritesview.py:327
+msgid "You are now registered with your school server."
+msgstr "Ju jeni regjistruar në serverin e shkollës suaj."
+
+#: ../src/jarabe/desktop/favoritesview.py:671
+msgid "Register"
+msgstr "Regjistrohu"
+
+#: ../src/jarabe/desktop/homebox.py:63
+msgid "Software Update"
+msgstr "Përditsim Softueri"
+
+#: ../src/jarabe/desktop/homebox.py:64
+msgid "Update your activities to ensure compatibility with your new software"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:73
+msgid "Check now"
+msgstr "Shiqo tani"
+
+#: ../src/jarabe/desktop/homebox.py:192
+msgid "List view"
+msgstr "Pamje në listë"
+
+#: ../src/jarabe/desktop/homebox.py:193
+msgid "<Ctrl>2"
+msgstr "<Ctrl>2"
+
+#: ../src/jarabe/desktop/homebox.py:255
+msgid "Favorites view"
+msgstr "Shfaq Favoritët"
+
+#: ../src/jarabe/desktop/homebox.py:256
+msgid "<Ctrl>1"
+msgstr "<Ctrl>1"
+
+#: ../src/jarabe/desktop/keydialog.py:131
+msgid "Key Type:"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:151
+msgid "Authentication Type:"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:215
+msgid "WPA & WPA2 Personal"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:224
+msgid "Wireless Security:"
+msgstr "Sigurija e rrjetit pa tela:"
+
+#: ../src/jarabe/desktop/meshbox.py:136
+msgid "Connect"
+msgstr "Lidhu"
+
+#: ../src/jarabe/desktop/meshbox.py:140
+msgid "Disconnect"
+msgstr "Shkëputu"
+
+#. TRANS: Action label for resuming an activity.
+#: ../src/jarabe/desktop/meshbox.py:463
+#: ../src/jarabe/frame/activitiestray.py:761
+#: ../src/jarabe/journal/journaltoolbox.py:428
+#: ../src/jarabe/journal/palettes.py:72 ../src/jarabe/view/palettes.py:64
+msgid "Resume"
+msgstr "Vazhdo"
+
+#: ../src/jarabe/desktop/meshbox.py:468
+#: ../src/jarabe/frame/activitiestray.py:235
+msgid "Join"
+msgstr "Bashkohu"
+
+#: ../src/jarabe/desktop/schoolserver.py:34
+msgid "Cannot obtain data needed for registration."
+msgstr ""
+
+#: ../src/jarabe/desktop/schoolserver.py:51
+msgid "Cannot connect to the server."
+msgstr ""
+
+#: ../src/jarabe/desktop/schoolserver.py:56
+msgid "The server could not complete the request."
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:240
+#: ../src/jarabe/frame/activitiestray.py:698
+msgid "Decline"
+msgstr "Mos prano"
+
+#: ../src/jarabe/frame/activitiestray.py:650
+#, python-format
+msgid "%dB"
+msgstr "%dB"
+
+#: ../src/jarabe/frame/activitiestray.py:652
+#, python-format
+msgid "%dKB"
+msgstr "%dKB"
+
+#: ../src/jarabe/frame/activitiestray.py:654
+#, python-format
+msgid "%dMB"
+msgstr "%dMB"
+
+#: ../src/jarabe/frame/activitiestray.py:671
+#, python-format
+msgid "%s of %s"
+msgstr "%s prej %s"
+
+#: ../src/jarabe/frame/activitiestray.py:683
+#, python-format
+msgid "Transfer from %r"
+msgstr "Transfer nga %r"
+
+#: ../src/jarabe/frame/activitiestray.py:693
+msgid "Accept"
+msgstr "Prano"
+
+#: ../src/jarabe/frame/activitiestray.py:716
+#: ../src/jarabe/frame/activitiestray.py:840
+#, python-format
+msgid "%s (%s)"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:750
+#: ../src/jarabe/frame/activitiestray.py:875
+msgid "Dismiss"
+msgstr "Pusho"
+
+#: ../src/jarabe/frame/activitiestray.py:810
+#, python-format
+msgid "Transfer to %r"
+msgstr "Transfer tek %r"
+
+#: ../src/jarabe/frame/clipboardmenu.py:52
+msgid "Remove"
+msgstr "Fshije"
+
+#: ../src/jarabe/frame/clipboardmenu.py:57
+#: ../src/jarabe/frame/clipboardmenu.py:80
+msgid "Open"
+msgstr "Hape"
+
+#: ../src/jarabe/frame/clipboardmenu.py:85
+msgid "Open with"
+msgstr "Hape me"
+
+#: ../src/jarabe/frame/clipboardobject.py:49
+#, python-format
+msgid "%s clipping"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:37
+msgid "Neighborhood"
+msgstr "Lagjja"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:37
+msgid "F1"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:39
+msgid "F2"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:41
+msgid "F3"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:43
+msgid "F4"
+msgstr ""
+
+#: ../src/jarabe/intro/window.py:124
+msgid "Click to change color:"
+msgstr "Kliko për ta ndrruar ngjyrën:"
+
+#: ../src/jarabe/intro/window.py:174 ../src/jarabe/journal/detailview.py:103
+msgid "Back"
+msgstr "Prapa"
+
+#: ../src/jarabe/intro/window.py:191
+msgid "Next"
+msgstr "Përpara"
+
+#: ../src/jarabe/journal/expandedentry.py:164
+#: ../src/jarabe/journal/palettes.py:66
+msgid "Untitled"
+msgstr "E paemërtuar"
+
+#: ../src/jarabe/journal/expandedentry.py:210
+msgid "No preview"
+msgstr "Pa provë"
+
+#: ../src/jarabe/journal/expandedentry.py:229
+#, python-format
+msgid "Kind: %s"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:229
+msgid "Unknown"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:230
+#, python-format
+msgid "Date: %s"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:231
+#, python-format
+msgid "Size: %s"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:253 ../src/jarabe/journal/misc.py:92
+msgid "No date"
+msgstr "Pa datë"
+
+#: ../src/jarabe/journal/expandedentry.py:260
+msgid "Participants:"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:283
+msgid "Description:"
+msgstr "Përshkrimi:"
+
+#: ../src/jarabe/journal/expandedentry.py:309
+msgid "Tags:"
+msgstr ""
+
+#: ../src/jarabe/journal/journalactivity.py:108
+#: ../src/jarabe/journal/volumestoolbar.py:47
+msgid "Journal"
+msgstr "Ditar"
+
+#: ../src/jarabe/journal/journaltoolbox.py:67
+msgid "Search"
+msgstr "Kërko"
+
+#: ../src/jarabe/journal/journaltoolbox.py:126
+msgid "Anytime"
+msgstr "Gjithnjë"
+
+#: ../src/jarabe/journal/journaltoolbox.py:128
+msgid "Today"
+msgstr "Sot"
+
+#: ../src/jarabe/journal/journaltoolbox.py:130
+msgid "Since yesterday"
+msgstr "Që dje"
+
+#. TRANS: Filter entries modified during the last 7 days.
+#: ../src/jarabe/journal/journaltoolbox.py:132
+msgid "Past week"
+msgstr "Java e kaluar"
+
+#. TRANS: Filter entries modified during the last 30 days.
+#: ../src/jarabe/journal/journaltoolbox.py:134
+msgid "Past month"
+msgstr "Muaji i kaluar"
+
+#. TRANS: Filter entries modified during the last 356 days.
+#: ../src/jarabe/journal/journaltoolbox.py:136
+msgid "Past year"
+msgstr "Viti i kaluar"
+
+#: ../src/jarabe/journal/journaltoolbox.py:143
+msgid "Anyone"
+msgstr "Gjithkush"
+
+#: ../src/jarabe/journal/journaltoolbox.py:145
+msgid "My friends"
+msgstr "Shokët e mi"
+
+#: ../src/jarabe/journal/journaltoolbox.py:146
+msgid "My class"
+msgstr "Klasa ime"
+
+#: ../src/jarabe/journal/journaltoolbox.py:274
+msgid "Anything"
+msgstr "Gjithqka"
+
+#: ../src/jarabe/journal/journaltoolbox.py:350
+#: ../src/jarabe/journal/palettes.py:90
+msgid "Copy"
+msgstr "Kopjo"
+
+#. TRANS: Action label for starting an entry.
+#: ../src/jarabe/journal/journaltoolbox.py:431
+#: ../src/jarabe/journal/palettes.py:75
+msgid "Start"
+msgstr "Fillo"
+
+#: ../src/jarabe/journal/listview.py:361
+msgid "Your Journal is empty"
+msgstr "Ditari juaj është i thatë"
+
+#: ../src/jarabe/journal/listview.py:363
+msgid "No matching entries"
+msgstr ""
+
+#: ../src/jarabe/journal/listview.py:374
+msgid "Clear search"
+msgstr "Pastro kërkimin"
+
+#: ../src/jarabe/journal/modalalert.py:63
+msgid "Your Journal is full"
+msgstr "Ditari yt është mbushur"
+
+#: ../src/jarabe/journal/modalalert.py:67
+msgid "Please delete some old Journal entries to make space for new ones."
+msgstr ""
+"Ju lutem fshini disa gjëra nga Ditari juaj për të lëshuar vend për të reja."
+
+#: ../src/jarabe/journal/modalalert.py:79
+msgid "Show Journal"
+msgstr "Shfaq ditarin"
+
+#: ../src/jarabe/journal/objectchooser.py:146
+msgid "Choose an object"
+msgstr "Zgjidh një objekt"
+
+#: ../src/jarabe/journal/objectchooser.py:151
+#: ../src/jarabe/view/viewsource.py:308
+msgid "Close"
+msgstr "Mbyll"
+
+#: ../src/jarabe/journal/palettes.py:73
+msgid "Resume with"
+msgstr "Vazhdo me"
+
+#: ../src/jarabe/journal/palettes.py:76
+msgid "Start with"
+msgstr "Fillo me"
+
+#: ../src/jarabe/journal/palettes.py:98
+msgid "Send to"
+msgstr "Qo në"
+
+#: ../src/jarabe/journal/palettes.py:107
+msgid "View Details"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:185
+msgid "No friends present"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:190
+msgid "No valid connection found"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:218
+msgid "No activity to resume entry"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:220
+msgid "No activity to start entry"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:62
+msgid "Remove friend"
+msgstr "Fshije mikun"
+
+#: ../src/jarabe/view/buddymenu.py:65
+msgid "Make friend"
+msgstr "Bëje mik"
+
+#: ../src/jarabe/view/buddymenu.py:82
+msgid "Shutdown"
+msgstr "Ndale"
+
+#: ../src/jarabe/view/buddymenu.py:90
+msgid "Logout"
+msgstr "Dil"
+
+#: ../src/jarabe/view/buddymenu.py:95
+msgid "My Settings"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:130
+#, python-format
+msgid "Invite to %s"
+msgstr " Fto në %s"
+
+#: ../src/jarabe/view/palettes.py:45
+msgid "Starting..."
+msgstr "Duke filluar..."
+
+#. TODO: share-with, keep
+#: ../src/jarabe/view/palettes.py:71
+msgid "View Source"
+msgstr "Shfaq Burimin"
+
+#: ../src/jarabe/view/palettes.py:82
+msgid "Stop"
+msgstr "Ndalo"
+
+#: ../src/jarabe/view/palettes.py:122
+msgid "Start new"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:171
+msgid "Show contents"
+msgstr "Shfaq përmbajtjen"
+
+#: ../src/jarabe/view/palettes.py:193 ../src/jarabe/view/palettes.py:243
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:218
+msgid "Unmount"
+msgstr ""
+
+#: ../src/jarabe/view/viewsource.py:208
+msgid "Instance Source"
+msgstr ""
+
+#: ../src/jarabe/view/viewsource.py:233
+msgid "Source"
+msgstr "Burimi"
+
+#: ../src/jarabe/view/viewsource.py:292
+msgid "Activity Bundle Source"
+msgstr ""
+
+#: ../src/jarabe/view/viewsource.py:299
+#, python-format
+msgid "View source: %r"
+msgstr ""
+
+#~ msgid "Restart"
+#~ msgstr "Rinis"
diff --git a/shell/po/sv.po b/shell/po/sv.po
new file mode 100644
index 0000000..b70c9d4
--- /dev/null
+++ b/shell/po/sv.po
@@ -0,0 +1,1339 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-09-05 00:31-0400\n"
+"PO-Revision-Date: 2010-02-03 22:14+0200\n"
+"Last-Translator: Nicci Manns <nicci@saunalahti.fi>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: sv\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Pootle 2.0.1\n"
+
+#: ../extensions/cpsection/aboutme/__init__.py:24
+msgid "About Me"
+msgstr "Om mig"
+
+#: ../extensions/cpsection/aboutme/model.py:43
+msgid "You must enter a name."
+msgstr "Du måste ange ett namn."
+
+# Varför ska detta översättas? Är det inte direkta kommandon till grafikuppritaren?
+#: ../extensions/cpsection/aboutme/model.py:68
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr "stroke: color=%s hue=%s"
+
+#: ../extensions/cpsection/aboutme/model.py:71
+#, python-format
+msgid "stroke: %s"
+msgstr "stroke: %s"
+
+#: ../extensions/cpsection/aboutme/model.py:73
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr "fill: color=%s hue=%s"
+
+#: ../extensions/cpsection/aboutme/model.py:75
+#, python-format
+msgid "fill: %s"
+msgstr "fill: %s"
+
+#: ../extensions/cpsection/aboutme/model.py:86
+msgid "Error in specified color modifiers."
+msgstr "Error in specified color modifiers."
+
+#: ../extensions/cpsection/aboutme/model.py:89
+msgid "Error in specified colors."
+msgstr "Fel i angivna färger."
+
+#: ../extensions/cpsection/aboutme/view.py:94 ../src/jarabe/intro/window.py:92
+msgid "Name:"
+msgstr "Namn:"
+
+#: ../extensions/cpsection/aboutme/view.py:128
+msgid "Click to change your color:"
+msgstr "Klicka för att ändra din färg:"
+
+#: ../extensions/cpsection/aboutcomputer/__init__.py:21
+msgid "About my Computer"
+msgstr "Om min dator"
+
+#: ../extensions/cpsection/aboutcomputer/model.py:28
+msgid "Not available"
+msgstr "Finns inte tillgänglig"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:60
+msgid "Identity"
+msgstr "Identitet"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:69
+msgid "Serial Number:"
+msgstr "Serienummer:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:91
+msgid "Software"
+msgstr "Programvara"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:100
+msgid "Build:"
+msgstr "Byggnummer:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:115
+msgid "Sugar:"
+msgstr "Sockerversion:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:131
+msgid "Firmware:"
+msgstr "Fast mjukvara:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:146
+msgid "Wireless Firmware:"
+msgstr "Trådlös fast mjukvara:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:169
+msgid "Copyright and License"
+msgstr "Copyright och Licens"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:184
+msgid ""
+"Sugar is the graphical user interface that you are looking at. Sugar is free "
+"software, covered by the GNU General Public License, and you are welcome to "
+"change it and/or distribute copies of it under certain conditions described "
+"therein."
+msgstr ""
+"Socker är det grafiska användargränssnittet som du använder just nu. Socker "
+"är fri programvara och licenserad under GNU GPL vilket innebär att du fritt "
+"får ändra programmet och/eller sprida vidare kopior av programmet."
+
+#: ../extensions/cpsection/aboutcomputer/view.py:196
+msgid "Full license:"
+msgstr "Detaljerad licens:"
+
+#: ../extensions/cpsection/datetime/__init__.py:21
+msgid "Date & Time"
+msgstr "Datum och tid"
+
+#: ../extensions/cpsection/datetime/model.py:87
+msgid "Error timezone does not exist."
+msgstr "Fel: Tidszonen finns inte."
+
+#: ../extensions/cpsection/datetime/view.py:68 ../data/sugar.schemas.in.h:27
+msgid "Timezone"
+msgstr "Tidszon"
+
+#: ../extensions/cpsection/frame/__init__.py:21
+msgid "Frame"
+msgstr "Ram"
+
+#: ../extensions/cpsection/frame/model.py:38
+#: ../extensions/cpsection/frame/model.py:60
+msgid "Value must be an integer."
+msgstr "Talet måste vara ett heltal."
+
+#: ../extensions/cpsection/frame/view.py:26
+msgid "never"
+msgstr "aldrig"
+
+#: ../extensions/cpsection/frame/view.py:27
+msgid "instantaneous"
+msgstr "direkt"
+
+#: ../extensions/cpsection/frame/view.py:28
+#, python-format
+msgid "%s seconds"
+msgstr "efter %s sekunder"
+
+#: ../extensions/cpsection/frame/view.py:52
+msgid "Activation Delay"
+msgstr "Tid tills aktivering"
+
+#: ../extensions/cpsection/frame/view.py:76
+msgid "Corner"
+msgstr "Hörn"
+
+#: ../extensions/cpsection/frame/view.py:111
+msgid "Edge"
+msgstr "Kant"
+
+#: ../extensions/cpsection/keyboard/__init__.py:21
+#: ../extensions/cpsection/keyboard/view.py:31
+msgid "Keyboard"
+msgstr "Tangentbord"
+
+#: ../extensions/cpsection/keyboard/view.py:187
+msgid "Keyboard Model"
+msgstr "Tangentbordstyp"
+
+#: ../extensions/cpsection/keyboard/view.py:243
+msgid "Key(s) to change layout"
+msgstr "Tangent för att växla mellan tangentbordstyper"
+
+#: ../extensions/cpsection/keyboard/view.py:311
+msgid "Keyboard Layout(s)"
+msgstr "Tangentbordstyp"
+
+#: ../extensions/cpsection/language/__init__.py:21
+#: ../extensions/cpsection/language/view.py:33
+msgid "Language"
+msgstr "Språk"
+
+#: ../extensions/cpsection/language/model.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr "Kunde inte komma åt ~/.i18n. Skapar standardinställningar."
+
+#: ../extensions/cpsection/language/model.py:124
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr "Kunde inte avgöra vilket språk det är som har koden %s."
+
+#: ../extensions/cpsection/language/model.py:144
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr "Tyvärr så pratar jag inte '%s'."
+
+#: ../extensions/cpsection/language/view.py:56
+msgid ""
+"Add languages in the order you prefer. If a translation is not available, "
+"the next in the list will be used."
+msgstr ""
+"Lägg till språk i den ordningen du förstår dem. Finns det inte en "
+"översättning i det översta språket kommer texten visas i den näst översta "
+"osv."
+
+#: ../extensions/cpsection/network/__init__.py:21
+#: ../extensions/cpsection/network/view.py:28
+msgid "Network"
+msgstr "Nätverk"
+
+#: ../extensions/cpsection/network/model.py:79
+msgid "State is unknown."
+msgstr "Status är okänd."
+
+#: ../extensions/cpsection/network/model.py:105
+msgid "Error in specified radio argument use on/off."
+msgstr "Fel i angiven trådlös nätverksinställning. Använd på eller av."
+
+#: ../extensions/cpsection/network/model.py:137
+msgid "Error in specified argument use 0/1."
+msgstr "Fel i angivet argument. Använd 0/1"
+
+#: ../extensions/cpsection/network/view.py:59
+msgid "Wireless"
+msgstr "Trådlöst nätverk"
+
+#: ../extensions/cpsection/network/view.py:67
+msgid "Turn off the wireless radio to save battery life"
+msgstr "Stäng av det trådlösa nätverket för att få batteriet att räcka längre"
+
+#: ../extensions/cpsection/network/view.py:80
+msgid "Radio"
+msgstr "Trådlöst nätverk"
+
+#: ../extensions/cpsection/network/view.py:96
+msgid "Discard network history if you have trouble connecting to the network"
+msgstr ""
+"Ta bort nätverkshistoriken om du har problem med att ansluta till ett "
+"nätverk"
+
+#: ../extensions/cpsection/network/view.py:105
+msgid "Discard network history"
+msgstr "Ta bort nätverkshistorik"
+
+#: ../extensions/cpsection/network/view.py:118
+msgid "Collaboration"
+msgstr "Samarbete"
+
+#: ../extensions/cpsection/network/view.py:126
+msgid ""
+"The server is the equivalent of what room you are in; people on the same "
+"server will be able to see each other, even when they aren't on the same "
+"network."
+msgstr ""
+"Servern motsvarar det rum som du är i; de som är på samma server kan se "
+"varandra även när de inte är på samma nätverk."
+
+#: ../extensions/cpsection/network/view.py:136
+msgid "Server:"
+msgstr "Server:"
+
+#: ../extensions/cpsection/power/__init__.py:21
+msgid "Power"
+msgstr "Ström"
+
+#: ../extensions/cpsection/power/model.py:54
+msgid "Error in automatic pm argument, use on/off."
+msgstr "Fel i angiven strömbesparingsinställning. Använd på eller av."
+
+#: ../extensions/cpsection/power/model.py:81
+msgid "Error in extreme pm argument, use on/off."
+msgstr "Fel i angiven extrem strömbesparingsinställning. Använd på eller av."
+
+#: ../extensions/cpsection/power/view.py:47
+msgid "Power management"
+msgstr "Strömhantering"
+
+#: ../extensions/cpsection/power/view.py:57
+msgid "Automatic power management (increases battery life)"
+msgstr "Automatisk strömhantering (förlänger tiden som batteriet varar)"
+
+#: ../extensions/cpsection/power/view.py:85
+msgid ""
+"Extreme power management (disableswireless radio, increases battery life)"
+msgstr ""
+"Extrem strömhantering (stänger av det trådlösa nätverket, ökar "
+"batterilivslängden)"
+
+#: ../extensions/cpsection/updater/__init__.py:21
+msgid "Software update"
+msgstr "Mjukvaruuppdatering"
+
+#: ../extensions/cpsection/updater/view.py:62
+msgid ""
+"Software updates correct errors, eliminate security vulnerabilities, and "
+"provide new features."
+msgstr ""
+"Mjukvaruuppdateringar rättar till fel, tar bort säkerhetsrisker och lägger "
+"till nya funktioner."
+
+#: ../extensions/cpsection/updater/view.py:122
+#, python-format
+msgid "Checking %s..."
+msgstr "Kontrollerar %s"
+
+#: ../extensions/cpsection/updater/view.py:124
+#, python-format
+msgid "Downloading %s..."
+msgstr "Laddar ned %s..."
+
+#: ../extensions/cpsection/updater/view.py:126
+#, python-format
+msgid "Updating %s..."
+msgstr "Uppdaterar %s..."
+
+#: ../extensions/cpsection/updater/view.py:135
+msgid "Your software is up-to-date"
+msgstr "Den senaste versionen av mjukvaran är redan installerad"
+
+#: ../extensions/cpsection/updater/view.py:137
+#, python-format
+msgid "You can install %s update"
+msgid_plural "You can install %s updates"
+msgstr[0] "Det finns %s tillgänglig uppdatering"
+msgstr[1] "Det finns %s tillgängliga uppdateringar"
+
+#: ../extensions/cpsection/updater/view.py:155
+msgid "Checking for updates..."
+msgstr "Letar efter uppdateringar..."
+
+#: ../extensions/cpsection/updater/view.py:160
+msgid "Installing updates..."
+msgstr "Installerar uppdateringar..."
+
+#: ../extensions/cpsection/updater/view.py:165
+#, python-format
+msgid "%s update was installed"
+msgid_plural "%s updates were installed"
+msgstr[0] "%s uppdatering installerades"
+msgstr[1] "%s uppdateringar installerades"
+
+#: ../extensions/cpsection/updater/view.py:244
+msgid "Install selected"
+msgstr "Installera de valda objekten"
+
+#: ../extensions/cpsection/updater/view.py:265
+#, python-format
+msgid "Download size: %s"
+msgstr "Nedladdningsstorlek: %s"
+
+#: ../extensions/cpsection/updater/view.py:353
+#, python-format
+msgid "From version %(current)d to %(new)s (Size: %(size)s)"
+msgstr "Frän version %(current)d till %(new)s (Storlek: %(size)s)"
+
+#. TRANS: download size is 0
+#: ../extensions/cpsection/updater/view.py:373
+msgid "None"
+msgstr "Inget"
+
+#. TRANS: download size of very small updates
+#: ../extensions/cpsection/updater/view.py:376
+msgid "1 KB"
+msgstr "1 kb"
+
+#. TRANS: download size of small updates, e.g. '250 KB'
+#: ../extensions/cpsection/updater/view.py:379
+#, python-format
+msgid "%.0f KB"
+msgstr "%.0f kb"
+
+#. TRANS: download size of updates, e.g. '2.3 MB'
+#: ../extensions/cpsection/updater/view.py:382
+#, python-format
+msgid "%.1f MB"
+msgstr "%.1f MB"
+
+#: ../extensions/deviceicon/battery.py:58
+msgid "My Battery"
+msgstr "Mitt batteri"
+
+#: ../extensions/deviceicon/battery.py:137
+msgid "Removed"
+msgstr "Borttaget"
+
+#: ../extensions/deviceicon/battery.py:140
+msgid "Charging"
+msgstr "Laddar"
+
+#: ../extensions/deviceicon/battery.py:143
+msgid "Very little power remaining"
+msgstr "Batteriet är nästan slut"
+
+#: ../extensions/deviceicon/battery.py:149
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr "%(hour)d:%(min).2d återstår"
+
+#: ../extensions/deviceicon/battery.py:152
+msgid "Charged"
+msgstr "Fulladdad"
+
+#: ../extensions/deviceicon/network.py:44
+#, python-format
+msgid "IP address: %s"
+msgstr "IP-adress: %s"
+
+#: ../extensions/deviceicon/network.py:110
+msgid "Disconnect..."
+msgstr "Avbryt anslutning..."
+
+#: ../extensions/deviceicon/network.py:114
+msgid "Create new wireless network"
+msgstr "Skapa ett nytt trådlöst nätverk"
+
+#: ../extensions/deviceicon/network.py:120
+#: ../src/jarabe/desktop/meshbox.py:264
+msgid "Connecting..."
+msgstr "Kopplar upp..."
+
+# TODO: show the channel number
+#: ../extensions/deviceicon/network.py:124
+#: ../extensions/deviceicon/network.py:186
+#: ../src/jarabe/desktop/meshbox.py:270
+msgid "Connected"
+msgstr "Ansluten"
+
+#: ../extensions/deviceicon/network.py:146
+msgid "Channel"
+msgstr "Kanal"
+
+#: ../extensions/deviceicon/network.py:161
+msgid "Wired Network"
+msgstr "Trådbundet nätverk"
+
+#: ../extensions/deviceicon/network.py:189
+msgid "Speed"
+msgstr "Hastighet"
+
+#: ../extensions/deviceicon/network.py:415
+#, python-format
+msgid "%s's network %s"
+msgstr "%ss nätverk %s"
+
+#: ../extensions/deviceicon/speaker.py:59
+msgid "My Speakers"
+msgstr "Mina högtalare"
+
+#: ../extensions/deviceicon/speaker.py:133
+msgid "Unmute"
+msgstr "Slå på ljud"
+
+#: ../extensions/deviceicon/speaker.py:136
+msgid "Mute"
+msgstr "Stäng av ljud"
+
+#: ../extensions/globalkey/screenshot.py:56
+msgid "Mesh"
+msgstr "Mesh"
+
+#: ../extensions/globalkey/screenshot.py:58
+#: ../src/jarabe/frame/zoomtoolbar.py:39
+msgid "Group"
+msgstr "Grupp"
+
+#: ../extensions/globalkey/screenshot.py:60
+#: ../src/jarabe/frame/zoomtoolbar.py:41
+msgid "Home"
+msgstr "Hem"
+
+#: ../extensions/globalkey/screenshot.py:66
+#: ../src/jarabe/frame/zoomtoolbar.py:43
+msgid "Activity"
+msgstr "Aktivitet"
+
+#: ../extensions/globalkey/screenshot.py:69
+msgid "Screenshot"
+msgstr "Skärmbild"
+
+#: ../extensions/globalkey/screenshot.py:71
+#, python-format
+msgid "Screenshot of \"%s\""
+msgstr "\"%s\"-Skärmbild"
+
+#: ../data/sugar.schemas.in.h:1
+msgid "Backup URL"
+msgstr "URL för säkerhetskopiering"
+
+# Error in original string! Never heard of "rbg colors".
+#: ../data/sugar.schemas.in.h:2
+msgid ""
+"Color for the XO icon that is used throughout the desktop. The string is "
+"composed of the stroke color and fill color, format is that of rbg colors. "
+"Example: #AC32FF,#9A5200"
+msgstr ""
+"Den färg som används för XO-ikonen överallt på skrivbordet. Det skall vara "
+"en sträng som består av streckfärg och fyllnadsfärg båda formaterade som "
+"rgb-färger. Exempel: #AC32FF,#9A5200"
+
+#: ../data/sugar.schemas.in.h:3
+msgid "Corner Delay"
+msgstr "Hörnfördröjning"
+
+#: ../data/sugar.schemas.in.h:4
+msgid "Delay for the activation of the frame using the corners."
+msgstr "Fördröjning innan aktivering av ramen med hjälp av hörnen."
+
+#: ../data/sugar.schemas.in.h:5
+msgid "Delay for the activation of the frame using the edges."
+msgstr "Fördröjning innan aktivering av ramen med hjälp av kanterna."
+
+#: ../data/sugar.schemas.in.h:6
+msgid "Edge Delay"
+msgstr "Kantfördröjning"
+
+#: ../data/sugar.schemas.in.h:7
+msgid "Favorites Layout"
+msgstr "Utseende på favoriter"
+
+#: ../data/sugar.schemas.in.h:8
+msgid "Favorites resume mode"
+msgstr "Fortsättningsläge för favoriter"
+
+#: ../data/sugar.schemas.in.h:9
+msgid ""
+"If TRUE, Sugar will make us searchable for the other users of the Jabber "
+"server."
+msgstr ""
+"Om SANT, så gör Sugar så att vi kan sökas fram av andra användare av jabber-"
+"servern."
+
+#: ../data/sugar.schemas.in.h:10
+msgid "If TRUE, Sugar will show a \"Log out\" option."
+msgstr "Om SANT, så kommer Sugar att visa ett utloggningsalternativ."
+
+#: ../data/sugar.schemas.in.h:11
+msgid "Jabber Server"
+msgstr "Jabber-server"
+
+#: ../data/sugar.schemas.in.h:12
+msgid "Keyboard layouts"
+msgstr "Tangentbordstyp"
+
+#: ../data/sugar.schemas.in.h:13
+msgid "Keyboard model"
+msgstr "Tangentbordstyp"
+
+#: ../data/sugar.schemas.in.h:14
+msgid "Keyboard options"
+msgstr "Tangentbordsalternativ"
+
+#: ../data/sugar.schemas.in.h:15
+msgid "Layout of the favorites view."
+msgstr "Hur favoritvyn ser ut."
+
+#: ../data/sugar.schemas.in.h:16
+msgid ""
+"List of keyboard layouts. Each entry should be in the form layout(variant)"
+msgstr ""
+"Lista över tangentbordslayouter. Varje post bör vara i formen "
+"layout(variant)"
+
+#: ../data/sugar.schemas.in.h:17
+msgid "List of keyboard options."
+msgstr "Lista över Tangentbordsval."
+
+#: ../data/sugar.schemas.in.h:18
+msgid "Power Automatic"
+msgstr "Automatisk ström"
+
+#: ../data/sugar.schemas.in.h:19
+msgid "Power Automatic."
+msgstr "Automatisk ström."
+
+#: ../data/sugar.schemas.in.h:20
+msgid "Power Extreme"
+msgstr "Extrem ström"
+
+#: ../data/sugar.schemas.in.h:21
+msgid "Power Extreme."
+msgstr "Extrem ström."
+
+#: ../data/sugar.schemas.in.h:22
+msgid "Publish to Gadget"
+msgstr "Publicera till Gadget"
+
+#: ../data/sugar.schemas.in.h:23
+msgid "Setting for muting the sound device."
+msgstr "Inställningar för att stänga av ljudenheten."
+
+#: ../data/sugar.schemas.in.h:24
+msgid "Show Log out"
+msgstr "Visa utloggning"
+
+#: ../data/sugar.schemas.in.h:25
+msgid "Sound Muted"
+msgstr "Ljud avstängt"
+
+#: ../data/sugar.schemas.in.h:26
+msgid "The keyboard model to be used"
+msgstr "Tangentbordstyp som kommer att användas:"
+
+#: ../data/sugar.schemas.in.h:28
+msgid "Timezone setting for the system."
+msgstr "Systemets tidszonsinställning."
+
+#: ../data/sugar.schemas.in.h:29
+msgid "Url of the jabber server to use."
+msgstr "URL för den jabber-server som skall användas."
+
+#: ../data/sugar.schemas.in.h:30
+msgid "Url where the backup is saved to."
+msgstr "URL för den plats där säkerhetskopieringar skall lagras."
+
+#: ../data/sugar.schemas.in.h:31
+msgid "User Color"
+msgstr "Användarfärg"
+
+#: ../data/sugar.schemas.in.h:32
+msgid "User Name"
+msgstr "Användarnamn"
+
+#: ../data/sugar.schemas.in.h:33
+msgid "User name that is used throughout the desktop."
+msgstr "Det användarnamn som används överallt på skrivbordet."
+
+#: ../data/sugar.schemas.in.h:34
+msgid "Volume Level"
+msgstr "Volymnivå"
+
+#: ../data/sugar.schemas.in.h:35
+msgid "Volume level for the sound device."
+msgstr "Ljudenhetens volymnivå."
+
+#: ../data/sugar.schemas.in.h:36
+msgid ""
+"When in resume mode, clicking on a favorite icon will cause the last entry "
+"for that activity to be resumed."
+msgstr ""
+"Om fortsättningsläge är aktiverat så kommer ett klick på en favoritikon "
+"innebära att den senaste posten för den aktiviteten kommer att återupptas."
+
+#: ../src/jarabe/controlpanel/cmd.py:28
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+"sugar-control-panel: VARNING, Hittade mer än ett alternativ med samma namn: "
+"%s modul: %r"
+
+#: ../src/jarabe/controlpanel/cmd.py:30
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr "sugar-control-panel: key=%s är inte ett giltigt val."
+
+#: ../src/jarabe/controlpanel/cmd.py:31
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr "sugar-control-panel: %s"
+
+# TRANS: Translators, there's a empty line at the end of this string,
+# which must appear in the translated string (msgstr) as well.
+#. TRANS: Translators, there's a empty line at the end of this string,
+#. which must appear in the translated string (msgstr) as well.
+#: ../src/jarabe/controlpanel/cmd.py:37
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" -c key clear the current value for the key \n"
+" "
+msgstr ""
+"Användning: sugar-control-panel [ val ] nyckel [ parametrar ... ] \n"
+" Ställ in Sockermiljön. \n"
+" Val: \n"
+" -h visar hjälpmeddelande och avslutar. \n"
+" -l listar upp alla möjliga val \n"
+" -h nyckel Visar information om nyckeln \n"
+" -g nyckel Hämtar nuvarande värde på nyckel \n"
+" -s nyckel Sätter nuvarande värde på nyckel \n"
+" -c nyckel Rensar nuvarande värde på nyckel \n"
+" "
+
+#: ../src/jarabe/controlpanel/cmd.py:50
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr "För att förändringarna ska träda i kraft behöver du starta om Socker.\n"
+
+#: ../src/jarabe/controlpanel/gui.py:280
+msgid "Warning"
+msgstr "Varning"
+
+#: ../src/jarabe/controlpanel/gui.py:281
+#: ../src/jarabe/controlpanel/sectionview.py:42
+msgid "Changes require restart"
+msgstr "Ändringarna kräver omstart"
+
+#: ../src/jarabe/controlpanel/gui.py:284
+msgid "Cancel changes"
+msgstr "Ångra ändringar"
+
+#: ../src/jarabe/controlpanel/gui.py:289 ../src/jarabe/desktop/homebox.py:70
+msgid "Later"
+msgstr "Senare"
+
+#: ../src/jarabe/controlpanel/gui.py:293
+msgid "Restart now"
+msgstr "Starta om nu"
+
+#: ../src/jarabe/controlpanel/toolbar.py:61 ../src/jarabe/intro/window.py:188
+msgid "Done"
+msgstr "Klar"
+
+#: ../src/jarabe/controlpanel/toolbar.py:115
+#: ../src/jarabe/desktop/homebox.py:68
+#: ../src/jarabe/frame/activitiestray.py:726
+#: ../src/jarabe/frame/activitiestray.py:822
+#: ../src/jarabe/frame/activitiestray.py:850
+msgid "Cancel"
+msgstr "Avbryt"
+
+#: ../src/jarabe/controlpanel/toolbar.py:121
+#: ../src/jarabe/desktop/favoritesview.py:332
+msgid "Ok"
+msgstr "Ok"
+
+#: ../src/jarabe/desktop/activitieslist.py:80
+#: ../src/jarabe/journal/listview.py:147
+msgid "Title"
+msgstr "Rubrik"
+
+#: ../src/jarabe/desktop/activitieslist.py:91
+msgid "Version"
+msgstr "Version"
+
+#: ../src/jarabe/desktop/activitieslist.py:105
+#: ../src/jarabe/journal/listview.py:178
+msgid "Date"
+msgstr "Datum"
+
+#: ../src/jarabe/desktop/activitieslist.py:234
+#, python-format
+msgid "Version %s"
+msgstr "Version %s"
+
+#: ../src/jarabe/desktop/activitieslist.py:355
+msgid "Confirm erase"
+msgstr "Bekräfta borttagning"
+
+#: ../src/jarabe/desktop/activitieslist.py:357
+#, python-format
+msgid "Confirm erase: Do you want to permanently erase %s?"
+msgstr "Bekräfta borttagning: Vill du verkligen ta bort %s för alltid?"
+
+# self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+# TODO: Implement stopping downloads
+# self._stop_item.connect('activate', self._stop_item_activate_cb)
+# self.append_menu_item(self._stop_item)
+#: ../src/jarabe/desktop/activitieslist.py:361
+#: ../src/jarabe/frame/clipboardmenu.py:62
+#: ../src/jarabe/view/viewsource.py:218
+msgid "Keep"
+msgstr "Spara"
+
+#: ../src/jarabe/desktop/activitieslist.py:364
+#: ../src/jarabe/desktop/activitieslist.py:407
+#: ../src/jarabe/journal/journaltoolbox.py:360
+#: ../src/jarabe/journal/palettes.py:112
+msgid "Erase"
+msgstr "Ta bort"
+
+#: ../src/jarabe/desktop/activitieslist.py:428
+msgid "Remove favorite"
+msgstr "Ta bort från favoriter"
+
+#: ../src/jarabe/desktop/activitieslist.py:432
+msgid "Make favorite"
+msgstr "Lägg till till favoriter"
+
+# TRANS: label for the freeform layout in the favorites view
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:116
+msgid "Freeform"
+msgstr "Frihand"
+
+# TRANS: label for the ring layout in the favorites view
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:198
+msgid "Ring"
+msgstr "Ring"
+
+# TRANS: label for the spiral layout in the favorites view
+#. TRANS: label for the spiral layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:337
+msgid "Spiral"
+msgstr "Spiral"
+
+# TRANS: label for the box layout in the favorites view
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:404
+msgid "Box"
+msgstr "Låda"
+
+# TRANS: label for the box layout in the favorites view
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:445
+msgid "Triangle"
+msgstr "Triangel"
+
+#: ../src/jarabe/desktop/favoritesview.py:323
+msgid "Registration Failed"
+msgstr "Registreringen misslyckades"
+
+#: ../src/jarabe/desktop/favoritesview.py:324
+#, python-format
+msgid "%s"
+msgstr "%s"
+
+#: ../src/jarabe/desktop/favoritesview.py:326
+msgid "Registration Successful"
+msgstr "Registreringen lyckades"
+
+#: ../src/jarabe/desktop/favoritesview.py:327
+msgid "You are now registered with your school server."
+msgstr "Du är nu registread hos din skolserver"
+
+#: ../src/jarabe/desktop/favoritesview.py:671
+msgid "Register"
+msgstr "Registrera"
+
+#: ../src/jarabe/desktop/homebox.py:63
+msgid "Software Update"
+msgstr "Mjukvaruuppdatering"
+
+#: ../src/jarabe/desktop/homebox.py:64
+msgid "Update your activities to ensure compatibility with your new software"
+msgstr ""
+"Uppdatera dina aktiviteter så att du är säker på att de fungerar ihop med "
+"din nya mjukvara"
+
+#: ../src/jarabe/desktop/homebox.py:73
+msgid "Check now"
+msgstr "Kontrollera nu"
+
+#: ../src/jarabe/desktop/homebox.py:192
+msgid "List view"
+msgstr "Listvy"
+
+#: ../src/jarabe/desktop/homebox.py:193
+msgid "<Ctrl>2"
+msgstr "<Ctrl> + 2"
+
+#: ../src/jarabe/desktop/homebox.py:255
+msgid "Favorites view"
+msgstr "Favoritvy"
+
+#: ../src/jarabe/desktop/homebox.py:256
+msgid "<Ctrl>1"
+msgstr "<Ctrl> + 1"
+
+#: ../src/jarabe/desktop/keydialog.py:131
+msgid "Key Type:"
+msgstr "Huvudtyp:"
+
+#: ../src/jarabe/desktop/keydialog.py:151
+msgid "Authentication Type:"
+msgstr "Verifieringstyp:"
+
+# Uncertain whether Personal here refers to both protocols or WPA2 only.
+#: ../src/jarabe/desktop/keydialog.py:215
+msgid "WPA & WPA2 Personal"
+msgstr "WPA & WPA2 Personal"
+
+#: ../src/jarabe/desktop/keydialog.py:224
+msgid "Wireless Security:"
+msgstr "Trådlös säkerhet:"
+
+#: ../src/jarabe/desktop/meshbox.py:136
+msgid "Connect"
+msgstr "Anslut"
+
+#: ../src/jarabe/desktop/meshbox.py:140
+msgid "Disconnect"
+msgstr "Avbryt anslutning"
+
+# TRANS: Action label for resuming an activity.
+#. TRANS: Action label for resuming an activity.
+#: ../src/jarabe/desktop/meshbox.py:466
+#: ../src/jarabe/frame/activitiestray.py:761
+#: ../src/jarabe/journal/journaltoolbox.py:428
+#: ../src/jarabe/journal/palettes.py:72 ../src/jarabe/view/palettes.py:64
+msgid "Resume"
+msgstr "Återuppta"
+
+#: ../src/jarabe/desktop/meshbox.py:471
+#: ../src/jarabe/frame/activitiestray.py:235
+msgid "Join"
+msgstr "Gå med i"
+
+#: ../src/jarabe/desktop/schoolserver.py:103
+msgid "Cannot connect to the server."
+msgstr "Kunde inte komma åter servern."
+
+#: ../src/jarabe/desktop/schoolserver.py:108
+msgid "The server could not complete the request."
+msgstr "Servern misslyckades med att fullfölja förfrågan."
+
+#: ../src/jarabe/frame/activitiestray.py:240
+#: ../src/jarabe/frame/activitiestray.py:698
+msgid "Decline"
+msgstr "Avstå"
+
+#: ../src/jarabe/frame/activitiestray.py:650
+#, python-format
+msgid "%dB"
+msgstr "%dB"
+
+#: ../src/jarabe/frame/activitiestray.py:652
+#, python-format
+msgid "%dKB"
+msgstr "%dKB"
+
+#: ../src/jarabe/frame/activitiestray.py:654
+#, python-format
+msgid "%dMB"
+msgstr "%dMB"
+
+# This really needs a TRANS!
+#: ../src/jarabe/frame/activitiestray.py:671
+#, python-format
+msgid "%s of %s"
+msgstr "%s av %s"
+
+#: ../src/jarabe/frame/activitiestray.py:683
+#, python-format
+msgid "Transfer from %r"
+msgstr "Överföring från %r"
+
+#: ../src/jarabe/frame/activitiestray.py:693
+msgid "Accept"
+msgstr "Godta"
+
+#: ../src/jarabe/frame/activitiestray.py:716
+#: ../src/jarabe/frame/activitiestray.py:840
+#, python-format
+msgid "%s (%s)"
+msgstr "%s (%s)"
+
+#: ../src/jarabe/frame/activitiestray.py:750
+#: ../src/jarabe/frame/activitiestray.py:875
+msgid "Dismiss"
+msgstr "Avvisa"
+
+#: ../src/jarabe/frame/activitiestray.py:810
+#, python-format
+msgid "Transfer to %r"
+msgstr "Överföring till %r"
+
+#: ../src/jarabe/frame/clipboardmenu.py:52 ../src/jarabe/view/palettes.py:218
+msgid "Remove"
+msgstr "Ta bort"
+
+#: ../src/jarabe/frame/clipboardmenu.py:57
+#: ../src/jarabe/frame/clipboardmenu.py:80
+msgid "Open"
+msgstr "Öppna"
+
+#: ../src/jarabe/frame/clipboardmenu.py:85
+msgid "Open with"
+msgstr "Öppna med"
+
+#: ../src/jarabe/frame/clipboardobject.py:49
+#, python-format
+msgid "%s clipping"
+msgstr "%s urklipp"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:37
+msgid "Neighborhood"
+msgstr "Grannar"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:37
+msgid "F1"
+msgstr "F1"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:39
+msgid "F2"
+msgstr "F2"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:41
+msgid "F3"
+msgstr "F3"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:43
+msgid "F4"
+msgstr "F4"
+
+#: ../src/jarabe/intro/window.py:124
+msgid "Click to change color:"
+msgstr "Klicka för att ändra färg:"
+
+#: ../src/jarabe/intro/window.py:174 ../src/jarabe/journal/detailview.py:103
+msgid "Back"
+msgstr "Tillbaka"
+
+#: ../src/jarabe/intro/window.py:191
+msgid "Next"
+msgstr "Nästa"
+
+#: ../src/jarabe/journal/expandedentry.py:164
+#: ../src/jarabe/journal/palettes.py:66
+msgid "Untitled"
+msgstr "Ingen titel"
+
+#: ../src/jarabe/journal/expandedentry.py:210
+msgid "No preview"
+msgstr "Ingen förhandsvisning"
+
+#: ../src/jarabe/journal/expandedentry.py:229
+#, python-format
+msgid "Kind: %s"
+msgstr "Typ: %s"
+
+#: ../src/jarabe/journal/expandedentry.py:229
+msgid "Unknown"
+msgstr "Okänd"
+
+#: ../src/jarabe/journal/expandedentry.py:230
+#, python-format
+msgid "Date: %s"
+msgstr "Datum: %s"
+
+#: ../src/jarabe/journal/expandedentry.py:231
+#, python-format
+msgid "Size: %s"
+msgstr "Storlek: %s"
+
+#: ../src/jarabe/journal/expandedentry.py:253 ../src/jarabe/journal/misc.py:92
+msgid "No date"
+msgstr "Inget datum"
+
+#: ../src/jarabe/journal/expandedentry.py:260
+msgid "Participants:"
+msgstr "Medverkande:"
+
+#: ../src/jarabe/journal/expandedentry.py:283
+msgid "Description:"
+msgstr "Beskrivning:"
+
+#: ../src/jarabe/journal/expandedentry.py:309
+msgid "Tags:"
+msgstr "Nyckelord:"
+
+#: ../src/jarabe/journal/journalactivity.py:108
+#: ../src/jarabe/journal/volumestoolbar.py:47
+msgid "Journal"
+msgstr "Dagbok"
+
+#: ../src/jarabe/journal/journaltoolbox.py:67
+msgid "Search"
+msgstr "Sök"
+
+#: ../src/jarabe/journal/journaltoolbox.py:126
+msgid "Anytime"
+msgstr "När som helst"
+
+#: ../src/jarabe/journal/journaltoolbox.py:128
+msgid "Today"
+msgstr "Idag"
+
+#: ../src/jarabe/journal/journaltoolbox.py:130
+msgid "Since yesterday"
+msgstr "Sedan igår"
+
+# TRANS: Filter entries modified during the last 7 days.
+#. TRANS: Filter entries modified during the last 7 days.
+#: ../src/jarabe/journal/journaltoolbox.py:132
+msgid "Past week"
+msgstr "Förra veckan"
+
+# TRANS: Filter entries modified during the last 30 days.
+#. TRANS: Filter entries modified during the last 30 days.
+#: ../src/jarabe/journal/journaltoolbox.py:134
+msgid "Past month"
+msgstr "Senaste månaden"
+
+# TRANS: Filter entries modified during the last 356 days.
+#. TRANS: Filter entries modified during the last 356 days.
+#: ../src/jarabe/journal/journaltoolbox.py:136
+msgid "Past year"
+msgstr "Senaste året"
+
+#: ../src/jarabe/journal/journaltoolbox.py:143
+msgid "Anyone"
+msgstr "Vem som helst"
+
+#: ../src/jarabe/journal/journaltoolbox.py:145
+msgid "My friends"
+msgstr "Mina vänner"
+
+#: ../src/jarabe/journal/journaltoolbox.py:146
+msgid "My class"
+msgstr "Mina klasskompisar"
+
+# TRANS: Item in a combo box that filters by entry type.
+#: ../src/jarabe/journal/journaltoolbox.py:274
+msgid "Anything"
+msgstr "Vad som helst"
+
+# TODO: Add "Start with" menu item
+#: ../src/jarabe/journal/journaltoolbox.py:350
+#: ../src/jarabe/journal/palettes.py:90
+msgid "Copy"
+msgstr "Kopiera"
+
+# TRANS: Action label for starting an entry.
+#. TRANS: Action label for starting an entry.
+#: ../src/jarabe/journal/journaltoolbox.py:431
+#: ../src/jarabe/journal/palettes.py:75
+msgid "Start"
+msgstr "Starta"
+
+#: ../src/jarabe/journal/listview.py:361
+msgid "Your Journal is empty"
+msgstr "Din dagbok är tom."
+
+#: ../src/jarabe/journal/listview.py:363
+msgid "No matching entries"
+msgstr "Kunde inte hitta matchande data"
+
+# Uncertain if this is used to clear the search criteria or the search results. A TRANS would be nice.
+#: ../src/jarabe/journal/listview.py:374
+msgid "Clear search"
+msgstr "Rensa sökning"
+
+#: ../src/jarabe/journal/modalalert.py:63
+msgid "Your Journal is full"
+msgstr "Din dagbok är full"
+
+#: ../src/jarabe/journal/modalalert.py:67
+msgid "Please delete some old Journal entries to make space for new ones."
+msgstr "Ta bort gamla dagboksinlägg för att få plats med nya."
+
+#: ../src/jarabe/journal/modalalert.py:79
+msgid "Show Journal"
+msgstr "Visa Dagbok"
+
+#: ../src/jarabe/journal/objectchooser.py:146
+msgid "Choose an object"
+msgstr "Välj ett objekt"
+
+#: ../src/jarabe/journal/objectchooser.py:151
+#: ../src/jarabe/view/viewsource.py:308
+msgid "Close"
+msgstr "Stäng"
+
+#: ../src/jarabe/journal/palettes.py:73
+msgid "Resume with"
+msgstr "Återuppta med"
+
+#: ../src/jarabe/journal/palettes.py:76
+msgid "Start with"
+msgstr "Börja med"
+
+#: ../src/jarabe/journal/palettes.py:98
+msgid "Send to"
+msgstr "Skicka till"
+
+#: ../src/jarabe/journal/palettes.py:107
+msgid "View Details"
+msgstr "Se detaljer"
+
+#: ../src/jarabe/journal/palettes.py:185
+msgid "No friends present"
+msgstr "Inga kompisar närvarande"
+
+#: ../src/jarabe/journal/palettes.py:190
+msgid "No valid connection found"
+msgstr "Ingen giltig anslutning hittad"
+
+#: ../src/jarabe/journal/palettes.py:218
+msgid "No activity to resume entry"
+msgstr "Ingen aktivitet att fortsätta med"
+
+#: ../src/jarabe/journal/palettes.py:220
+msgid "No activity to start entry"
+msgstr "Ingen aktivitet att börja med"
+
+#: ../src/jarabe/view/buddymenu.py:62
+msgid "Remove friend"
+msgstr "Ta bort kompis"
+
+#: ../src/jarabe/view/buddymenu.py:65
+msgid "Make friend"
+msgstr "Bli kompis med"
+
+#: ../src/jarabe/view/buddymenu.py:82
+msgid "Shutdown"
+msgstr "Stäng av"
+
+#: ../src/jarabe/view/buddymenu.py:90
+msgid "Logout"
+msgstr "Logga ut"
+
+#: ../src/jarabe/view/buddymenu.py:95
+msgid "My Settings"
+msgstr "Mina inställningar"
+
+#: ../src/jarabe/view/buddymenu.py:130
+#, python-format
+msgid "Invite to %s"
+msgstr "Bjud in till %s"
+
+#: ../src/jarabe/view/palettes.py:45
+msgid "Starting..."
+msgstr "Startar..."
+
+#. TODO: share-with, keep
+#: ../src/jarabe/view/palettes.py:71
+msgid "View Source"
+msgstr "Se källa"
+
+#: ../src/jarabe/view/palettes.py:82
+msgid "Stop"
+msgstr "Avsluta"
+
+#: ../src/jarabe/view/palettes.py:122
+msgid "Start new"
+msgstr "Öppna ny"
+
+#: ../src/jarabe/view/palettes.py:171
+msgid "Show contents"
+msgstr "Visa innehåll"
+
+#: ../src/jarabe/view/palettes.py:193 ../src/jarabe/view/palettes.py:243
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr "%(free_space)d MB ledigt"
+
+#: ../src/jarabe/view/viewsource.py:208
+msgid "Instance Source"
+msgstr "Instanskälla"
+
+#: ../src/jarabe/view/viewsource.py:233
+msgid "Source"
+msgstr "Källa"
+
+#: ../src/jarabe/view/viewsource.py:292
+msgid "Activity Bundle Source"
+msgstr "Källa för aktivitetspaket"
+
+#: ../src/jarabe/view/viewsource.py:299
+#, python-format
+msgid "View source: %r"
+msgstr "Se källa: %r"
+
+#~ msgid "Cannot obtain data needed for registration."
+#~ msgstr ""
+#~ "Kunde inte komma åt den data som krävs för att genomföra registreringen."
+
+#~ msgid "Unmount"
+#~ msgstr "Avmontera"
+
+#~ msgid "Restart"
+#~ msgstr "Starta om"
+
+#~ msgid ""
+#~ "© 2008 One Laptop per Child Association Inc; Red Hat Inc; and Contributors."
+#~ msgstr ""
+#~ "© 2008 One Laptop per Child Association Inc; Red Hat Inc; och andra "
+#~ "bidragare."
+
+#~ msgid "Document"
+#~ msgstr "Dokument"
+
+#~ msgid "Resume by default"
+#~ msgstr "Återuppta som förval"
+
+#~ msgid "Encryption Type:"
+#~ msgstr "Krypteringstyp:"
+
+#~ msgid "Disconnecting..."
+#~ msgstr "Kopplar ned..."
+
+# only temporarily
+#~ msgid "Mesh Network"
+#~ msgstr "Meshnätverk"
+
+#~ msgid "Disconnected"
+#~ msgstr "Inte ansluten"
+
+#~ msgid "About my XO"
+#~ msgstr "Om min XO"
+
+#~ msgid "Connected to a School Mesh Portal"
+#~ msgstr "Ansluten till en skolportal"
+
+#~ msgid "Looking for a School Mesh Portal..."
+#~ msgstr "Letar efter en skolportal..."
+
+#~ msgid "Connected to an XO Mesh Portal"
+#~ msgstr "Ansluten till en XO-portal"
+
+#~ msgid "Looking for an XO Mesh Portal..."
+#~ msgstr "Letar efter en XO-portal..."
+
+#~ msgid "Connected to a Simple Mesh"
+#~ msgstr "Ansluten till ett enkelt meshnätverk"
+
+#~ msgid "Starting a Simple Mesh"
+#~ msgstr "Startar ett enkelt meshnätverk..."
+
+#~ msgid "Unknown Mesh"
+#~ msgstr "Okänt meshnätverk"
+
+#~ msgid "Settings"
+#~ msgstr "Inställningar"
+
+#, python-format
+#~ msgid "Clipboard object: %s."
+#~ msgstr "Kopieringsminne: %s."
+
+#~ msgid ""
+#~ "Extreme power management (disables wireless radio, increases battery life)"
+#~ msgstr ""
+#~ "Extrem strömsparning (stänger av det trådlösa nätverket, allt för att öka "
+#~ "tiden innan batteriet laddas ur)"
+
+#~ msgid "Control Panel"
+#~ msgstr "Kontrollpanel"
diff --git a/shell/po/sw.po b/shell/po/sw.po
new file mode 100644
index 0000000..528f114
--- /dev/null
+++ b/shell/po/sw.po
@@ -0,0 +1,1222 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-09-05 00:31-0400\n"
+"PO-Revision-Date: 2008-11-21 16:06-0500\n"
+"Last-Translator: Fanuel Kalugendo <fanosbert@yahoo.com>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Pootle 1.1.0rc2\n"
+
+#: ../extensions/cpsection/aboutme/__init__.py:24
+msgid "About Me"
+msgstr "Kuhusu mimi"
+
+#: ../extensions/cpsection/aboutme/model.py:43
+msgid "You must enter a name."
+msgstr "Lazima uingize jina lako"
+
+#: ../extensions/cpsection/aboutme/model.py:68
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:71
+#, python-format
+msgid "stroke: %s"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:73
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:75
+#, python-format
+msgid "fill: %s"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:86
+msgid "Error in specified color modifiers."
+msgstr "Hitilafu kwenye kirekebisha rangi kilichoelezwa"
+
+#: ../extensions/cpsection/aboutme/model.py:89
+msgid "Error in specified colors."
+msgstr "Hitilafu kwenye rangi zilizoelezwa"
+
+#: ../extensions/cpsection/aboutme/view.py:94 ../src/jarabe/intro/window.py:92
+msgid "Name:"
+msgstr "Jina:"
+
+#: ../extensions/cpsection/aboutme/view.py:128
+#, fuzzy
+msgid "Click to change your color:"
+msgstr "Bofya kubadilisha rangi:"
+
+#: ../extensions/cpsection/aboutcomputer/__init__.py:21
+msgid "About my Computer"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/model.py:28
+msgid "Not available"
+msgstr "Haipo"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:60
+msgid "Identity"
+msgstr "Utambulisho"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:69
+msgid "Serial Number:"
+msgstr "Serial namba"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:91
+msgid "Software"
+msgstr "Software"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:100
+msgid "Build:"
+msgstr "Jenga:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:115
+msgid "Sugar:"
+msgstr "Sukari:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:131
+msgid "Firmware:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:146
+msgid "Wireless Firmware:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:169
+msgid "Copyright and License"
+msgstr "Haki miliki na leseni"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:184
+msgid ""
+"Sugar is the graphical user interface that you are looking at. Sugar is free "
+"software, covered by the GNU General Public License, and you are welcome to "
+"change it and/or distribute copies of it under certain conditions described "
+"therein."
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:196
+msgid "Full license:"
+msgstr "Leseni kamili:"
+
+#: ../extensions/cpsection/datetime/__init__.py:21
+msgid "Date & Time"
+msgstr "Tarehe & Saa"
+
+#: ../extensions/cpsection/datetime/model.py:87
+msgid "Error timezone does not exist."
+msgstr ""
+
+#: ../extensions/cpsection/datetime/view.py:68 ../data/sugar.schemas.in.h:27
+msgid "Timezone"
+msgstr ""
+
+#: ../extensions/cpsection/frame/__init__.py:21
+msgid "Frame"
+msgstr "Fremu"
+
+#: ../extensions/cpsection/frame/model.py:38
+#: ../extensions/cpsection/frame/model.py:60
+msgid "Value must be an integer."
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:26
+msgid "never"
+msgstr "mwiko"
+
+#: ../extensions/cpsection/frame/view.py:27
+msgid "instantaneous"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:28
+#, python-format
+msgid "%s seconds"
+msgstr "%s sekunde"
+
+#: ../extensions/cpsection/frame/view.py:52
+msgid "Activation Delay"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:76
+msgid "Corner"
+msgstr "Kona"
+
+#: ../extensions/cpsection/frame/view.py:111
+msgid "Edge"
+msgstr "Pembeni"
+
+#: ../extensions/cpsection/keyboard/__init__.py:21
+#: ../extensions/cpsection/keyboard/view.py:31
+msgid "Keyboard"
+msgstr ""
+
+#: ../extensions/cpsection/keyboard/view.py:187
+msgid "Keyboard Model"
+msgstr ""
+
+#: ../extensions/cpsection/keyboard/view.py:243
+msgid "Key(s) to change layout"
+msgstr ""
+
+#: ../extensions/cpsection/keyboard/view.py:311
+msgid "Keyboard Layout(s)"
+msgstr ""
+
+#: ../extensions/cpsection/language/__init__.py:21
+#: ../extensions/cpsection/language/view.py:33
+msgid "Language"
+msgstr "Lugha"
+
+#: ../extensions/cpsection/language/model.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr "Haikuweza kufikia ~/.i18n. Tengeneza standard setting"
+
+#: ../extensions/cpsection/language/model.py:124
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr ""
+
+#: ../extensions/cpsection/language/model.py:144
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr "Samahani si wezi kuongea '%s'."
+
+#: ../extensions/cpsection/language/view.py:56
+msgid ""
+"Add languages in the order you prefer. If a translation is not available, "
+"the next in the list will be used."
+msgstr ""
+
+#: ../extensions/cpsection/network/__init__.py:21
+#: ../extensions/cpsection/network/view.py:28
+msgid "Network"
+msgstr "Mtandao"
+
+#: ../extensions/cpsection/network/model.py:79
+msgid "State is unknown."
+msgstr "Hali haijulikani"
+
+#: ../extensions/cpsection/network/model.py:105
+msgid "Error in specified radio argument use on/off."
+msgstr ""
+
+#: ../extensions/cpsection/network/model.py:137
+msgid "Error in specified argument use 0/1."
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:59
+msgid "Wireless"
+msgstr "Isiyonawiya"
+
+#: ../extensions/cpsection/network/view.py:67
+#, fuzzy
+msgid "Turn off the wireless radio to save battery life"
+msgstr "Zima redio isiyotumiawiya kunusuru maisha ya betri"
+
+#: ../extensions/cpsection/network/view.py:80
+msgid "Radio"
+msgstr "Redio"
+
+#: ../extensions/cpsection/network/view.py:96
+msgid "Discard network history if you have trouble connecting to the network"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:105
+msgid "Discard network history"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:118
+msgid "Collaboration"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:126
+msgid ""
+"The server is the equivalent of what room you are in; people on the same "
+"server will be able to see each other, even when they aren't on the same "
+"network."
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:136
+msgid "Server:"
+msgstr "Seva:"
+
+#: ../extensions/cpsection/power/__init__.py:21
+msgid "Power"
+msgstr ""
+
+#: ../extensions/cpsection/power/model.py:54
+msgid "Error in automatic pm argument, use on/off."
+msgstr ""
+
+#: ../extensions/cpsection/power/model.py:81
+msgid "Error in extreme pm argument, use on/off."
+msgstr ""
+
+#: ../extensions/cpsection/power/view.py:47
+msgid "Power management"
+msgstr ""
+
+#: ../extensions/cpsection/power/view.py:57
+msgid "Automatic power management (increases battery life)"
+msgstr ""
+
+#: ../extensions/cpsection/power/view.py:85
+msgid ""
+"Extreme power management (disableswireless radio, increases battery life)"
+msgstr ""
+
+# unaweza kusema 'kujichanora' kwa maana ya 'up to date'
+#: ../extensions/cpsection/updater/__init__.py:21
+#, fuzzy
+msgid "Software update"
+msgstr "Ifanye software iwe ya kisasa"
+
+#: ../extensions/cpsection/updater/view.py:62
+msgid ""
+"Software updates correct errors, eliminate security vulnerabilities, and "
+"provide new features."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:122
+#, python-format
+msgid "Checking %s..."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:124
+#, python-format
+msgid "Downloading %s..."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:126
+#, python-format
+msgid "Updating %s..."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:135
+msgid "Your software is up-to-date"
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:137
+#, python-format
+msgid "You can install %s update"
+msgid_plural "You can install %s updates"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../extensions/cpsection/updater/view.py:155
+msgid "Checking for updates..."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:160
+msgid "Installing updates..."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:165
+#, python-format
+msgid "%s update was installed"
+msgid_plural "%s updates were installed"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../extensions/cpsection/updater/view.py:244
+msgid "Install selected"
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:265
+#, python-format
+msgid "Download size: %s"
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:353
+#, python-format
+msgid "From version %(current)d to %(new)s (Size: %(size)s)"
+msgstr ""
+
+#. TRANS: download size is 0
+#: ../extensions/cpsection/updater/view.py:373
+#, fuzzy
+msgid "None"
+msgstr "Gotoka"
+
+#. TRANS: download size of very small updates
+#: ../extensions/cpsection/updater/view.py:376
+msgid "1 KB"
+msgstr ""
+
+#. TRANS: download size of small updates, e.g. '250 KB'
+#: ../extensions/cpsection/updater/view.py:379
+#, python-format
+msgid "%.0f KB"
+msgstr ""
+
+#. TRANS: download size of updates, e.g. '2.3 MB'
+#: ../extensions/cpsection/updater/view.py:382
+#, python-format
+msgid "%.1f MB"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:58
+msgid "My Battery"
+msgstr "Betri yangu"
+
+#: ../extensions/deviceicon/battery.py:137
+#, fuzzy
+msgid "Removed"
+msgstr "Ondoa"
+
+#: ../extensions/deviceicon/battery.py:140
+msgid "Charging"
+msgstr "Inachaji"
+
+#: ../extensions/deviceicon/battery.py:143
+msgid "Very little power remaining"
+msgstr "Imebakiza nguvu (umeme) kidogo"
+
+#: ../extensions/deviceicon/battery.py:149
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr "%(hour)d:(min).2d zimebaki"
+
+#: ../extensions/deviceicon/battery.py:152
+msgid "Charged"
+msgstr "Ina umeme"
+
+#: ../extensions/deviceicon/network.py:44
+#, python-format
+msgid "IP address: %s"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:110
+msgid "Disconnect..."
+msgstr "Tenganisha..."
+
+#: ../extensions/deviceicon/network.py:114
+msgid "Create new wireless network"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:120
+#: ../src/jarabe/desktop/meshbox.py:264
+msgid "Connecting..."
+msgstr "Inaunganisha..."
+
+# TODO: show the channel number
+#: ../extensions/deviceicon/network.py:124
+#: ../extensions/deviceicon/network.py:186
+#: ../src/jarabe/desktop/meshbox.py:270
+msgid "Connected"
+msgstr "Imeunganishwa"
+
+#: ../extensions/deviceicon/network.py:146
+msgid "Channel"
+msgstr "Mkondo "
+
+#: ../extensions/deviceicon/network.py:161
+msgid "Wired Network"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:189
+msgid "Speed"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:415
+#, python-format
+msgid "%s's network %s"
+msgstr ""
+
+#: ../extensions/deviceicon/speaker.py:59
+msgid "My Speakers"
+msgstr "Spika zangyu"
+
+#: ../extensions/deviceicon/speaker.py:133
+msgid "Unmute"
+msgstr "Rudisha sauti"
+
+#: ../extensions/deviceicon/speaker.py:136
+msgid "Mute"
+msgstr "Kata sauti"
+
+#: ../extensions/globalkey/screenshot.py:56
+msgid "Mesh"
+msgstr ""
+
+#: ../extensions/globalkey/screenshot.py:58
+#: ../src/jarabe/frame/zoomtoolbar.py:39
+msgid "Group"
+msgstr "Kundi"
+
+#: ../extensions/globalkey/screenshot.py:60
+#: ../src/jarabe/frame/zoomtoolbar.py:41
+msgid "Home"
+msgstr "mwanzo/kaya"
+
+#: ../extensions/globalkey/screenshot.py:66
+#: ../src/jarabe/frame/zoomtoolbar.py:43
+msgid "Activity"
+msgstr "Kazi"
+
+#: ../extensions/globalkey/screenshot.py:69
+msgid "Screenshot"
+msgstr ""
+
+#: ../extensions/globalkey/screenshot.py:71
+#, python-format
+msgid "Screenshot of \"%s\""
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:1
+msgid "Backup URL"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:2
+msgid ""
+"Color for the XO icon that is used throughout the desktop. The string is "
+"composed of the stroke color and fill color, format is that of rbg colors. "
+"Example: #AC32FF,#9A5200"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:3
+msgid "Corner Delay"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:4
+msgid "Delay for the activation of the frame using the corners."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:5
+msgid "Delay for the activation of the frame using the edges."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:6
+msgid "Edge Delay"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:7
+msgid "Favorites Layout"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:8
+msgid "Favorites resume mode"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:9
+msgid ""
+"If TRUE, Sugar will make us searchable for the other users of the Jabber "
+"server."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:10
+msgid "If TRUE, Sugar will show a \"Log out\" option."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:11
+msgid "Jabber Server"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:12
+msgid "Keyboard layouts"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:13
+msgid "Keyboard model"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:14
+msgid "Keyboard options"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:15
+msgid "Layout of the favorites view."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:16
+msgid ""
+"List of keyboard layouts. Each entry should be in the form layout(variant)"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:17
+msgid "List of keyboard options."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:18
+msgid "Power Automatic"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:19
+msgid "Power Automatic."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:20
+msgid "Power Extreme"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:21
+msgid "Power Extreme."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:22
+msgid "Publish to Gadget"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:23
+msgid "Setting for muting the sound device."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:24
+msgid "Show Log out"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:25
+msgid "Sound Muted"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:26
+msgid "The keyboard model to be used"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:28
+msgid "Timezone setting for the system."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:29
+msgid "Url of the jabber server to use."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:30
+msgid "Url where the backup is saved to."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:31
+msgid "User Color"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:32
+msgid "User Name"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:33
+msgid "User name that is used throughout the desktop."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:34
+msgid "Volume Level"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:35
+msgid "Volume level for the sound device."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:36
+msgid ""
+"When in resume mode, clicking on a favorite icon will cause the last entry "
+"for that activity to be resumed."
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:28
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:30
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:31
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr ""
+
+#. TRANS: Translators, there's a empty line at the end of this string,
+#. which must appear in the translated string (msgstr) as well.
+#: ../src/jarabe/controlpanel/cmd.py:37
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" -c key clear the current value for the key \n"
+" "
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:50
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr "Ili kuweka mabadiliko yako inabidi kuanzisha upya sukari.\n"
+
+#: ../src/jarabe/controlpanel/gui.py:280
+msgid "Warning"
+msgstr "Tahadhari"
+
+#: ../src/jarabe/controlpanel/gui.py:281
+#: ../src/jarabe/controlpanel/sectionview.py:42
+msgid "Changes require restart"
+msgstr "Mabadiliko yanahitaji kuanzisha upya"
+
+#: ../src/jarabe/controlpanel/gui.py:284
+msgid "Cancel changes"
+msgstr "Ghairisha mabadiliko"
+
+#: ../src/jarabe/controlpanel/gui.py:289 ../src/jarabe/desktop/homebox.py:70
+msgid "Later"
+msgstr "Baadae"
+
+#: ../src/jarabe/controlpanel/gui.py:293
+msgid "Restart now"
+msgstr "Anzisha sasa"
+
+#: ../src/jarabe/controlpanel/toolbar.py:61 ../src/jarabe/intro/window.py:188
+msgid "Done"
+msgstr "Gotoka"
+
+#: ../src/jarabe/controlpanel/toolbar.py:115
+#: ../src/jarabe/desktop/homebox.py:68
+#: ../src/jarabe/frame/activitiestray.py:726
+#: ../src/jarabe/frame/activitiestray.py:822
+#: ../src/jarabe/frame/activitiestray.py:850
+msgid "Cancel"
+msgstr "Ghairi"
+
+#: ../src/jarabe/controlpanel/toolbar.py:121
+#: ../src/jarabe/desktop/favoritesview.py:332
+msgid "Ok"
+msgstr "Sawa"
+
+#: ../src/jarabe/desktop/activitieslist.py:80
+#: ../src/jarabe/journal/listview.py:147
+msgid "Title"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:91
+msgid "Version"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:105
+#: ../src/jarabe/journal/listview.py:178
+msgid "Date"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:234
+#, python-format
+msgid "Version %s"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:355
+msgid "Confirm erase"
+msgstr "Thibitisha kufuta"
+
+#: ../src/jarabe/desktop/activitieslist.py:357
+#, python-format
+msgid "Confirm erase: Do you want to permanently erase %s?"
+msgstr "Thibitisha kufuta: Unataka ifutwe moja kwa moja %s?"
+
+#: ../src/jarabe/desktop/activitieslist.py:361
+#: ../src/jarabe/frame/clipboardmenu.py:62
+#: ../src/jarabe/view/viewsource.py:218
+msgid "Keep"
+msgstr "Hifadhi"
+
+#: ../src/jarabe/desktop/activitieslist.py:364
+#: ../src/jarabe/desktop/activitieslist.py:407
+#: ../src/jarabe/journal/journaltoolbox.py:360
+#: ../src/jarabe/journal/palettes.py:112
+msgid "Erase"
+msgstr "Futa"
+
+#: ../src/jarabe/desktop/activitieslist.py:428
+msgid "Remove favorite"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:432
+msgid "Make favorite"
+msgstr ""
+
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:116
+msgid "Freeform"
+msgstr ""
+
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:198
+msgid "Ring"
+msgstr ""
+
+#. TRANS: label for the spiral layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:337
+msgid "Spiral"
+msgstr ""
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:404
+msgid "Box"
+msgstr ""
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:445
+msgid "Triangle"
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:323
+msgid "Registration Failed"
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:324
+#, python-format
+msgid "%s"
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:326
+msgid "Registration Successful"
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:327
+msgid "You are now registered with your school server."
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:671
+msgid "Register"
+msgstr ""
+
+# unaweza kusema 'kujichanora' kwa maana ya 'up to date'
+#: ../src/jarabe/desktop/homebox.py:63
+msgid "Software Update"
+msgstr "Ifanye software iwe ya kisasa"
+
+#: ../src/jarabe/desktop/homebox.py:64
+msgid "Update your activities to ensure compatibility with your new software"
+msgstr "Zifanye kazi zako kuwa za kisasa ilikusudi ziendane na software mpya"
+
+#: ../src/jarabe/desktop/homebox.py:73
+msgid "Check now"
+msgstr "Tafuta sasa"
+
+#: ../src/jarabe/desktop/homebox.py:192
+msgid "List view"
+msgstr "Ona orodha"
+
+#: ../src/jarabe/desktop/homebox.py:193
+msgid "<Ctrl>2"
+msgstr "<Ctrl>2"
+
+#: ../src/jarabe/desktop/homebox.py:255
+msgid "Favorites view"
+msgstr "Zinazoonwa sana"
+
+#: ../src/jarabe/desktop/homebox.py:256
+msgid "<Ctrl>1"
+msgstr "<Ctrl>1"
+
+#: ../src/jarabe/desktop/keydialog.py:131
+msgid "Key Type:"
+msgstr "Aina ya namba ya siri:"
+
+#: ../src/jarabe/desktop/keydialog.py:151
+msgid "Authentication Type:"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:215
+msgid "WPA & WPA2 Personal"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:224
+msgid "Wireless Security:"
+msgstr ""
+
+#: ../src/jarabe/desktop/meshbox.py:136
+msgid "Connect"
+msgstr "Unganisha"
+
+#: ../src/jarabe/desktop/meshbox.py:140
+msgid "Disconnect"
+msgstr "Tenganisha"
+
+# TRANS: Action label for resuming an activity.
+#. TRANS: Action label for resuming an activity.
+#: ../src/jarabe/desktop/meshbox.py:466
+#: ../src/jarabe/frame/activitiestray.py:761
+#: ../src/jarabe/journal/journaltoolbox.py:428
+#: ../src/jarabe/journal/palettes.py:72 ../src/jarabe/view/palettes.py:64
+msgid "Resume"
+msgstr "Endelea"
+
+#: ../src/jarabe/desktop/meshbox.py:471
+#: ../src/jarabe/frame/activitiestray.py:235
+msgid "Join"
+msgstr "Ingia/unganisha"
+
+#: ../src/jarabe/desktop/schoolserver.py:103
+msgid "Cannot connect to the server."
+msgstr "Haiwezi kuunganisha kwenye seva."
+
+#: ../src/jarabe/desktop/schoolserver.py:108
+msgid "The server could not complete the request."
+msgstr "Seva haiwezi kukamilisha ombi."
+
+#: ../src/jarabe/frame/activitiestray.py:240
+#: ../src/jarabe/frame/activitiestray.py:698
+msgid "Decline"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:650
+#, python-format
+msgid "%dB"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:652
+#, python-format
+msgid "%dKB"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:654
+#, python-format
+msgid "%dMB"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:671
+#, python-format
+msgid "%s of %s"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:683
+#, python-format
+msgid "Transfer from %r"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:693
+msgid "Accept"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:716
+#: ../src/jarabe/frame/activitiestray.py:840
+#, python-format
+msgid "%s (%s)"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:750
+#: ../src/jarabe/frame/activitiestray.py:875
+msgid "Dismiss"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:810
+#, python-format
+msgid "Transfer to %r"
+msgstr ""
+
+#: ../src/jarabe/frame/clipboardmenu.py:52 ../src/jarabe/view/palettes.py:218
+msgid "Remove"
+msgstr "Ondoa"
+
+#: ../src/jarabe/frame/clipboardmenu.py:57
+#: ../src/jarabe/frame/clipboardmenu.py:80
+msgid "Open"
+msgstr "Fungua"
+
+#: ../src/jarabe/frame/clipboardmenu.py:85
+msgid "Open with"
+msgstr "Fungua kwa "
+
+#: ../src/jarabe/frame/clipboardobject.py:49
+#, python-format
+msgid "%s clipping"
+msgstr "%s pogoa"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:37
+msgid "Neighborhood"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:37
+msgid "F1"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:39
+msgid "F2"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:41
+msgid "F3"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:43
+msgid "F4"
+msgstr ""
+
+#: ../src/jarabe/intro/window.py:124
+msgid "Click to change color:"
+msgstr "Bofya kubadilisha rangi:"
+
+#: ../src/jarabe/intro/window.py:174 ../src/jarabe/journal/detailview.py:103
+msgid "Back"
+msgstr "Rejesha"
+
+#: ../src/jarabe/intro/window.py:191
+msgid "Next"
+msgstr "Nyingine"
+
+#: ../src/jarabe/journal/expandedentry.py:164
+#: ../src/jarabe/journal/palettes.py:66
+msgid "Untitled"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:210
+msgid "No preview"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:229
+#, python-format
+msgid "Kind: %s"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:229
+msgid "Unknown"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:230
+#, python-format
+msgid "Date: %s"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:231
+#, python-format
+msgid "Size: %s"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:253 ../src/jarabe/journal/misc.py:92
+msgid "No date"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:260
+msgid "Participants:"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:283
+msgid "Description:"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:309
+msgid "Tags:"
+msgstr ""
+
+#: ../src/jarabe/journal/journalactivity.py:108
+#: ../src/jarabe/journal/volumestoolbar.py:47
+msgid "Journal"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:67
+msgid "Search"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:126
+msgid "Anytime"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:128
+msgid "Today"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:130
+msgid "Since yesterday"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 7 days.
+#: ../src/jarabe/journal/journaltoolbox.py:132
+msgid "Past week"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 30 days.
+#: ../src/jarabe/journal/journaltoolbox.py:134
+msgid "Past month"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 356 days.
+#: ../src/jarabe/journal/journaltoolbox.py:136
+msgid "Past year"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:143
+msgid "Anyone"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:145
+msgid "My friends"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:146
+msgid "My class"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:274
+msgid "Anything"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:350
+#: ../src/jarabe/journal/palettes.py:90
+msgid "Copy"
+msgstr ""
+
+#. TRANS: Action label for starting an entry.
+#: ../src/jarabe/journal/journaltoolbox.py:431
+#: ../src/jarabe/journal/palettes.py:75
+msgid "Start"
+msgstr ""
+
+#: ../src/jarabe/journal/listview.py:361
+msgid "Your Journal is empty"
+msgstr ""
+
+#: ../src/jarabe/journal/listview.py:363
+msgid "No matching entries"
+msgstr ""
+
+#: ../src/jarabe/journal/listview.py:374
+msgid "Clear search"
+msgstr ""
+
+#: ../src/jarabe/journal/modalalert.py:63
+msgid "Your Journal is full"
+msgstr ""
+
+#: ../src/jarabe/journal/modalalert.py:67
+msgid "Please delete some old Journal entries to make space for new ones."
+msgstr ""
+
+#: ../src/jarabe/journal/modalalert.py:79
+msgid "Show Journal"
+msgstr ""
+
+#: ../src/jarabe/journal/objectchooser.py:146
+msgid "Choose an object"
+msgstr ""
+
+#: ../src/jarabe/journal/objectchooser.py:151
+#: ../src/jarabe/view/viewsource.py:308
+msgid "Close"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:73
+msgid "Resume with"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:76
+msgid "Start with"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:98
+msgid "Send to"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:107
+msgid "View Details"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:185
+msgid "No friends present"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:190
+msgid "No valid connection found"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:218
+msgid "No activity to resume entry"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:220
+msgid "No activity to start entry"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:62
+msgid "Remove friend"
+msgstr "Ondoa rafiki"
+
+#: ../src/jarabe/view/buddymenu.py:65
+msgid "Make friend"
+msgstr "Kutana na rafiki"
+
+#: ../src/jarabe/view/buddymenu.py:82
+msgid "Shutdown"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:90
+msgid "Logout"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:95
+msgid "My Settings"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:130
+#, python-format
+msgid "Invite to %s"
+msgstr "Karibisha kwa %s"
+
+#: ../src/jarabe/view/palettes.py:45
+msgid "Starting..."
+msgstr ""
+
+#. TODO: share-with, keep
+#: ../src/jarabe/view/palettes.py:71
+msgid "View Source"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:82
+msgid "Stop"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:122
+msgid "Start new"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:171
+msgid "Show contents"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:193 ../src/jarabe/view/palettes.py:243
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr ""
+
+#: ../src/jarabe/view/viewsource.py:208
+msgid "Instance Source"
+msgstr ""
+
+#: ../src/jarabe/view/viewsource.py:233
+msgid "Source"
+msgstr ""
+
+#: ../src/jarabe/view/viewsource.py:292
+msgid "Activity Bundle Source"
+msgstr ""
+
+#: ../src/jarabe/view/viewsource.py:299
+#, python-format
+msgid "View source: %r"
+msgstr ""
+
+#~ msgid "Cannot obtain data needed for registration."
+#~ msgstr "Haiwezi kupata taarifa zinazohitajika kwa ajili ya usajiri."
+
+#~ msgid "Disconnecting..."
+#~ msgstr "Inatenganisha..."
+
+#~ msgid "Disconnected"
+#~ msgstr "Imetenganishwa"
+
+#~ msgid "About my XO"
+#~ msgstr "Kuhusu XO yangu"
diff --git a/shell/po/ta.po b/shell/po/ta.po
new file mode 100644
index 0000000..2851349
--- /dev/null
+++ b/shell/po/ta.po
@@ -0,0 +1,1260 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-08-26 00:31-0400\n"
+"PO-Revision-Date: 2009-08-28 04:52-0400\n"
+"Last-Translator: Emilianuspillai Amirthanathan Gnanaseelan "
+"<gnanaseelan2001@yahoo.com>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: ta\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Pootle 1.2.1\n"
+
+#: ../extensions/cpsection/aboutme/__init__.py:24
+msgid "About Me"
+msgstr "என்னைப் பற்றி"
+
+#: ../extensions/cpsection/aboutme/model.py:43
+msgid "You must enter a name."
+msgstr "நீங்கள் பெயரை பதிவு செய்யுங்கள்."
+
+#: ../extensions/cpsection/aboutme/model.py:68
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr "stroke: color=%s hue=%s"
+
+#: ../extensions/cpsection/aboutme/model.py:71
+#, python-format
+msgid "stroke: %s"
+msgstr "stroke: %s"
+
+#: ../extensions/cpsection/aboutme/model.py:73
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr "நிரப்பு:........நிறம்=%s hue=%s"
+
+#: ../extensions/cpsection/aboutme/model.py:75
+#, python-format
+msgid "fill: %s"
+msgstr "நிரப்பு:.....%s"
+
+#: ../extensions/cpsection/aboutme/model.py:86
+msgid "Error in specified color modifiers."
+msgstr "குறித்த தவறை நிறத்தால் மாற்றியமைக்க."
+
+#: ../extensions/cpsection/aboutme/model.py:89
+msgid "Error in specified colors."
+msgstr "தவறை குறித்த நிறத்தால் நிறந்தீட்டுக."
+
+#: ../extensions/cpsection/aboutme/view.py:94 ../src/jarabe/intro/window.py:92
+msgid "Name:"
+msgstr "பெயர்"
+
+#: ../extensions/cpsection/aboutme/view.py:128
+msgid "Click to change your color:"
+msgstr "நிறத்தை மாற்ற சொடுக்கு்"
+
+#: ../extensions/cpsection/aboutcomputer/__init__.py:21
+msgid "About my Computer"
+msgstr " என்னுடைய கணினியைப் பற்றி"
+
+#: ../extensions/cpsection/aboutcomputer/model.py:28
+msgid "Not available"
+msgstr "கிடைக்கக்கூடிய வாய்ப்பில்லை."
+
+#: ../extensions/cpsection/aboutcomputer/view.py:60
+msgid "Identity"
+msgstr "அடையாளம்"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:69
+msgid "Serial Number:"
+msgstr "தொடர் இலக்கம்"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:91
+msgid "Software"
+msgstr "மென்பொருள்"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:100
+msgid "Build:"
+msgstr "கட்டியெழுப்பு"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:115
+msgid "Sugar:"
+msgstr "சுகர்"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:131
+msgid "Firmware:"
+msgstr "கூட்டுப்பொருள்"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:146
+msgid "Wireless Firmware:"
+msgstr "இணைப்பில்லாத கூட்டுப்பொருள்"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:169
+msgid "Copyright and License"
+msgstr "பிரதிசெய்து உறுதிப்படுத்து"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:184
+msgid ""
+"Sugar is the graphical user interface that you are looking at. Sugar is free "
+"software, covered by the GNU General Public License, and you are welcome to "
+"change it and/or distribute copies of it under certain conditions described "
+"therein."
+msgstr ""
+" சுகர் என்பது பாவனையாளருக்கு பார்த்து தலையீடு செய்வ தற்கு சிறப்பாக "
+"அமைக்கப்பட்டுள்ளது.சுகர் இலவச மென்பொருளானதோடு,இது GNU பொது மக்கள் "
+"அனுமதியின்கீழ் கையாளப்படுகின்றதை மாற்றுவதற்கோ பிரதிகளை விநியோகிப்பதற்கோ "
+"குறித்த சில நிபந்தனைகளின்கீழ் இதனை மாற்றவோ இதன் பிரதிக ளை விநியோகிக்கவோ "
+"முடியும். "
+
+#: ../extensions/cpsection/aboutcomputer/view.py:196
+msgid "Full license:"
+msgstr "முழு அனுமதி"
+
+#: ../extensions/cpsection/datetime/__init__.py:21
+msgid "Date & Time"
+msgstr "திகதியும் நேரமும்"
+
+#: ../extensions/cpsection/datetime/model.py:87
+msgid "Error timezone does not exist."
+msgstr "தவறு நேர வலையமைப்பு ஏற்கனவே இல்லை."
+
+#: ../extensions/cpsection/datetime/view.py:68 ../data/sugar.schemas.in.h:27
+msgid "Timezone"
+msgstr "நேரவலயம்"
+
+#: ../extensions/cpsection/frame/__init__.py:21
+msgid "Frame"
+msgstr "சட்டகம்"
+
+#: ../extensions/cpsection/frame/model.py:38
+#: ../extensions/cpsection/frame/model.py:60
+msgid "Value must be an integer."
+msgstr "பெறுமதி முழு எண்ணாக இருக்கவேண்டும்."
+
+#: ../extensions/cpsection/frame/view.py:26
+msgid "never"
+msgstr "ஒருபோதும்"
+
+#: ../extensions/cpsection/frame/view.py:27
+msgid "instantaneous"
+msgstr "உடனடியாக"
+
+#: ../extensions/cpsection/frame/view.py:28
+#, python-format
+msgid "%s seconds"
+msgstr "%s செக்கன்கள்-"
+
+#: ../extensions/cpsection/frame/view.py:52
+msgid "Activation Delay"
+msgstr "செயற்படுதல் தாமதம்"
+
+#: ../extensions/cpsection/frame/view.py:76
+msgid "Corner"
+msgstr "மூலை"
+
+#: ../extensions/cpsection/frame/view.py:111
+msgid "Edge"
+msgstr "விளிம்பு"
+
+#: ../extensions/cpsection/keyboard/__init__.py:21
+#: ../extensions/cpsection/keyboard/view.py:31
+msgid "Keyboard"
+msgstr ""
+
+#: ../extensions/cpsection/keyboard/view.py:187
+msgid "Keyboard Model"
+msgstr ""
+
+#: ../extensions/cpsection/keyboard/view.py:243
+msgid "Key(s) to change layout"
+msgstr ""
+
+#: ../extensions/cpsection/keyboard/view.py:311
+msgid "Keyboard Layout(s)"
+msgstr ""
+
+#: ../extensions/cpsection/language/__init__.py:21
+#: ../extensions/cpsection/language/view.py:33
+msgid "Language"
+msgstr "மொழி"
+
+#: ../extensions/cpsection/language/model.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr "அடையவில்லை~/.i18n. தரமானவற்றை உருவாக்கு"
+
+#: ../extensions/cpsection/language/model.py:124
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr "மொழி பரிபாஷை=%s முடிவுசெய்யவில்லை"
+
+#: ../extensions/cpsection/language/model.py:144
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr " மன்னிக்கவும் நான் பேசவில்லை"
+
+#: ../extensions/cpsection/language/view.py:56
+msgid ""
+"Add languages in the order you prefer. If a translation is not available, "
+"the next in the list will be used."
+msgstr ""
+"உங்களது விருப்பத்திற்கு ஏற்றவாறு மொழிகளைச் சேர். ஏதாவது மொழிமாற்றம் "
+"கிடைக்காவிடில் அடுத்த பட்டியல் பாவிக்கப்படும்."
+
+#: ../extensions/cpsection/network/__init__.py:21
+#: ../extensions/cpsection/network/view.py:28
+msgid "Network"
+msgstr "வலையமைப்பு"
+
+#: ../extensions/cpsection/network/model.py:79
+msgid "State is unknown."
+msgstr "தரம் அறியப்படாதது"
+
+#: ../extensions/cpsection/network/model.py:105
+msgid "Error in specified radio argument use on/off."
+msgstr "குறித்த வானொலி விவாதம் தவறு.உபயோகியுங்கள்on/off."
+
+#: ../extensions/cpsection/network/model.py:137
+msgid "Error in specified argument use 0/1."
+msgstr "குறித்த வானொலி விவாதம் தவறு..உபயோகியுங்கள்0/1."
+
+#: ../extensions/cpsection/network/view.py:59
+msgid "Wireless"
+msgstr "இணைப்பில்லாத"
+
+#: ../extensions/cpsection/network/view.py:67
+msgid "Turn off the wireless radio to save battery life"
+msgstr "இணைப்பில்லாத வானொலியை நிறுத்தி மின்கலத்தை சேமி"
+
+#: ../extensions/cpsection/network/view.py:80
+msgid "Radio"
+msgstr "வானொலி"
+
+#: ../extensions/cpsection/network/view.py:96
+msgid "Discard network history if you have trouble connecting to the network"
+msgstr ""
+"வ லையமைப்பு இணைக்கும்போது பிரச்சினைகள் இருப்பின் வலையமைப்பை துண்டித்து "
+"மீண்டும் வலையமைப்பை இணைக்க. "
+
+#: ../extensions/cpsection/network/view.py:105
+msgid "Discard network history"
+msgstr "வலையமைப்பை துண்டிக்கவும்"
+
+#: ../extensions/cpsection/network/view.py:118
+msgid "Collaboration"
+msgstr "உதவி"
+
+#: ../extensions/cpsection/network/view.py:126
+msgid ""
+"The server is the equivalent of what room you are in; people on the same "
+"server will be able to see each other, even when they aren't on the same "
+"network."
+msgstr ""
+"வழங்கியானது (கணினி)நீங்கள் எந்த அறையில் உள்ளீர்கள் என்பதைக்காட்டுவதற்கு "
+"சமனானது:அதே வழங்கியிலுள்ள (கணினி)மக்களால் ஒவ்வொன்றையும் பார்க்க "
+"முடியும்,ஆனால் அவர்கள் ஒரே வலையமைப்பில் இல்லை."
+
+#: ../extensions/cpsection/network/view.py:136
+msgid "Server:"
+msgstr "வழங்கி"
+
+#: ../extensions/cpsection/power/__init__.py:21
+msgid "Power"
+msgstr "வலு"
+
+#: ../extensions/cpsection/power/model.py:54
+msgid "Error in automatic pm argument, use on/off."
+msgstr "தன்னியக்க pmவிவாதத்தில் தவறு. உபயோகிon/off."
+
+#: ../extensions/cpsection/power/model.py:81
+msgid "Error in extreme pm argument, use on/off."
+msgstr "கடுமையான pm விவாதம் தவறு . உபயோகி on/off."
+
+#: ../extensions/cpsection/power/view.py:47
+msgid "Power management"
+msgstr "வலு முகாமைத்துவம்"
+
+#: ../extensions/cpsection/power/view.py:57
+msgid "Automatic power management (increases battery life)"
+msgstr "தன்னியக்க வலு முகாமைத்துவம் (மின் கலத்தின் வலுவைக் கூட்டுதல்)."
+
+#: ../extensions/cpsection/power/view.py:85
+msgid ""
+"Extreme power management (disableswireless radio, increases battery life)"
+msgstr ""
+"கடுமையான வலு முகாமைத்துவம் (கம்பித் தொடுகையற்ற கதிருக்கு மின்கலத்தின் மூலம் "
+"ஆயுளை கொடுத்தல்)"
+
+#: ../extensions/cpsection/updater/__init__.py:21
+#, fuzzy
+msgid "Software update"
+msgstr "மென் பொருளைப் புதுப்பித்தல்"
+
+#: ../extensions/cpsection/updater/view.py:62
+msgid ""
+"Software updates correct errors, eliminate security vulnerabilities, and "
+"provide new features."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:122
+#, python-format
+msgid "Checking %s..."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:124
+#, python-format
+msgid "Downloading %s..."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:126
+#, python-format
+msgid "Updating %s..."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:135
+msgid "Your software is up-to-date"
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:137
+#, python-format
+msgid "You can install %s update"
+msgid_plural "You can install %s updates"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../extensions/cpsection/updater/view.py:155
+msgid "Checking for updates..."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:160
+msgid "Installing updates..."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:165
+#, python-format
+msgid "%s update was installed"
+msgid_plural "%s updates were installed"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../extensions/cpsection/updater/view.py:244
+msgid "Install selected"
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:265
+#, python-format
+msgid "Download size: %s"
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:353
+#, python-format
+msgid "From version %(current)d to %(new)s (Size: %(size)s)"
+msgstr ""
+
+#. TRANS: download size is 0
+#: ../extensions/cpsection/updater/view.py:373
+#, fuzzy
+msgid "None"
+msgstr "முடிந்து விட்டது"
+
+#. TRANS: download size of very small updates
+#: ../extensions/cpsection/updater/view.py:376
+msgid "1 KB"
+msgstr ""
+
+#. TRANS: download size of small updates, e.g. '250 KB'
+#: ../extensions/cpsection/updater/view.py:379
+#, python-format
+msgid "%.0f KB"
+msgstr ""
+
+#. TRANS: download size of updates, e.g. '2.3 MB'
+#: ../extensions/cpsection/updater/view.py:382
+#, python-format
+msgid "%.1f MB"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:58
+msgid "My Battery"
+msgstr "எனது மின்கலம்"
+
+#: ../extensions/deviceicon/battery.py:137
+msgid "Removed"
+msgstr "அகற்று"
+
+#: ../extensions/deviceicon/battery.py:140
+msgid "Charging"
+msgstr "மின்னேற்றல்"
+
+#: ../extensions/deviceicon/battery.py:143
+msgid "Very little power remaining"
+msgstr "மிகச்சிறிய வலு எஞ்சியுள்ளது"
+
+#: ../extensions/deviceicon/battery.py:149
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr "%(மணித்தியாலம்)d:%(நிமி)).2dஎஞ்சியுள்ளது"
+
+#: ../extensions/deviceicon/battery.py:152
+msgid "Charged"
+msgstr "மின்னேற்றம்"
+
+#: ../extensions/deviceicon/network.py:44
+#, python-format
+msgid "IP address: %s"
+msgstr "IP விலாசம்%s"
+
+#: ../extensions/deviceicon/network.py:110
+msgid "Disconnect..."
+msgstr "இணைப்பை துண்டித்தல்"
+
+#: ../extensions/deviceicon/network.py:114
+msgid "Create new wireless network"
+msgstr "புதிய கம்பியில்லா வலையமைப்பை உருவாக்கு"
+
+#: ../extensions/deviceicon/network.py:120
+#: ../src/jarabe/desktop/meshbox.py:261
+msgid "Connecting..."
+msgstr "இணைத்தல்"
+
+#: ../extensions/deviceicon/network.py:124
+#: ../extensions/deviceicon/network.py:186
+#: ../src/jarabe/desktop/meshbox.py:267
+msgid "Connected"
+msgstr "இணைக்கப்பட்டுள்ளது"
+
+#: ../extensions/deviceicon/network.py:146
+msgid "Channel"
+msgstr "தடம்"
+
+#: ../extensions/deviceicon/network.py:161
+msgid "Wired Network"
+msgstr "கம்பி வலையமைப்பு"
+
+#: ../extensions/deviceicon/network.py:189
+msgid "Speed"
+msgstr "வேகம்"
+
+#: ../extensions/deviceicon/network.py:415
+#, python-format
+msgid "%s's network %s"
+msgstr "% யின் வலையமைப்பு"
+
+#: ../extensions/deviceicon/speaker.py:59
+msgid "My Speakers"
+msgstr "எனது ஒலி பெருக்கிகள்"
+
+#: ../extensions/deviceicon/speaker.py:133
+msgid "Unmute"
+msgstr "ஒலியை ஆரம்பி"
+
+#: ../extensions/deviceicon/speaker.py:136
+msgid "Mute"
+msgstr "ஒலியை நிறுத்து"
+
+#: ../extensions/globalkey/screenshot.py:56
+msgid "Mesh"
+msgstr "மெஷ்"
+
+#: ../extensions/globalkey/screenshot.py:58
+#: ../src/jarabe/frame/zoomtoolbar.py:39
+msgid "Group"
+msgstr "குழு"
+
+#: ../extensions/globalkey/screenshot.py:60
+#: ../src/jarabe/frame/zoomtoolbar.py:41
+msgid "Home"
+msgstr "வீடு"
+
+#: ../extensions/globalkey/screenshot.py:66
+#: ../src/jarabe/frame/zoomtoolbar.py:43
+msgid "Activity"
+msgstr "செயற்பாடு"
+
+#: ../extensions/globalkey/screenshot.py:69
+msgid "Screenshot"
+msgstr "திரை வடிவம்"
+
+#: ../extensions/globalkey/screenshot.py:71
+#, python-format
+msgid "Screenshot of \"%s\""
+msgstr "\"%s\" யின் திரையின் படம்"
+
+#: ../data/sugar.schemas.in.h:1
+msgid "Backup URL"
+msgstr "URL ஊடாக மேலே செல்"
+
+#: ../data/sugar.schemas.in.h:2
+msgid ""
+"Color for the XO icon that is used throughout the desktop. The string is "
+"composed of the stroke color and fill color, format is that of rbg colors. "
+"Example: #AC32FF,#9A5200"
+msgstr ""
+" நிறந்தீட்டிய XO வடிவத்தை திரைக்குப் பயன்பரடுத்தலாம்.நூலினை வடிவமைப்பதற்கு "
+"நிறத்தினையும் நிரப்பிய நிறத்தினையும்,rbg நிறக்கலவையினுாடாகப் "
+"பெற்றுக்கொள்ளலாம்."
+
+#: ../data/sugar.schemas.in.h:3
+msgid "Corner Delay"
+msgstr " மூலை தாமதம்"
+
+#: ../data/sugar.schemas.in.h:4
+msgid "Delay for the activation of the frame using the corners."
+msgstr "மூலையைப் பயன்படுத்துவதனால் சட்டகத்தின் செயற்பாடு தாமதம் அடைகின்றது"
+
+#: ../data/sugar.schemas.in.h:5
+msgid "Delay for the activation of the frame using the edges."
+msgstr ""
+"ஒரத்தினைப் பயன் படுத்துவதனால் சட்டகச் செயற்பாட்டில் தாமதம் ஏற்படுகின்றது"
+
+#: ../data/sugar.schemas.in.h:6
+msgid "Edge Delay"
+msgstr "ஒரத்தின் தாமதம்"
+
+#: ../data/sugar.schemas.in.h:7
+msgid "Favorites Layout"
+msgstr "விருப்பமான தளக்கோலம்"
+
+#: ../data/sugar.schemas.in.h:8
+msgid "Favorites resume mode"
+msgstr " விருப்பத்துக்குரியதை மீழ ஆரம்பி "
+
+#: ../data/sugar.schemas.in.h:9
+msgid ""
+"If TRUE, Sugar will make us searchable for the other users of the Jabber "
+"server."
+msgstr ""
+"உண்மையாயின் Sugar எங்களை Jabberவழங்கி (கணினி)யின் ஊடக தேடல் நிலைக்கு இட்டுச் "
+"செல்லும்"
+
+#: ../data/sugar.schemas.in.h:10
+msgid "If TRUE, Sugar will show a \"Log out\" option."
+msgstr "உண்மையாகில், Sugar \"வெளிச்செல்\" தேரிவைக் காட்டும். "
+
+#: ../data/sugar.schemas.in.h:11
+msgid "Jabber Server"
+msgstr "Jabber வழங்கி (கணினி)"
+
+#: ../data/sugar.schemas.in.h:12
+msgid "Keyboard layouts"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:13
+msgid "Keyboard model"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:14
+msgid "Keyboard options"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:15
+msgid "Layout of the favorites view."
+msgstr "விருப்பத்தித்குரிய செயற்பாட்டின் தளக்கோலதின் பார்வை"
+
+#: ../data/sugar.schemas.in.h:16
+msgid ""
+"List of keyboard layouts. Each entry should be in the form layout(variant)"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:17
+msgid "List of keyboard options."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:18
+msgid "Power Automatic"
+msgstr "தன்னியக்க வலு "
+
+#: ../data/sugar.schemas.in.h:19
+msgid "Power Automatic."
+msgstr "தன்னியக்க வலு"
+
+#: ../data/sugar.schemas.in.h:20
+msgid "Power Extreme"
+msgstr "கூடுதலான வலு"
+
+#: ../data/sugar.schemas.in.h:21
+msgid "Power Extreme."
+msgstr "கூடுதலான வலு"
+
+#: ../data/sugar.schemas.in.h:22
+msgid "Publish to Gadget"
+msgstr "கருவிக்கு வெளிப்படுத்து"
+
+#: ../data/sugar.schemas.in.h:23
+msgid "Setting for muting the sound device."
+msgstr "ஒலித்திட்டமிடலை இல்லாமல் செய்வதற்கான செயற்பாடு"
+
+#: ../data/sugar.schemas.in.h:24
+msgid "Show Log out"
+msgstr "வெளிச் செல்கையைக் காட்டு"
+
+#: ../data/sugar.schemas.in.h:25
+msgid "Sound Muted"
+msgstr "ஒலியை நிறுத்து"
+
+#: ../data/sugar.schemas.in.h:26
+msgid "The keyboard model to be used"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:28
+msgid "Timezone setting for the system."
+msgstr "நேர வலையமைப்பை செயற்படுத்துவதற்கான அமைப்பு"
+
+#: ../data/sugar.schemas.in.h:29
+msgid "Url of the jabber server to use."
+msgstr " jabber வழங்கி (கணினி)யிலுள்ள Url ஐ உபயோகி"
+
+#: ../data/sugar.schemas.in.h:30
+msgid "Url where the backup is saved to."
+msgstr "Url எதனுள் சேமிக்கப்பட்டுள்ளது"
+
+#: ../data/sugar.schemas.in.h:31
+msgid "User Color"
+msgstr "உபயோகிப்பவரின் நிறம்"
+
+#: ../data/sugar.schemas.in.h:32
+msgid "User Name"
+msgstr "உபயோகிப்பவரின் பெயர்"
+
+#: ../data/sugar.schemas.in.h:33
+msgid "User name that is used throughout the desktop."
+msgstr ""
+"திரையினுாடாக உட்செல்வதற்கு உபயோகிக்கும் பெயரைப் பயன்படுத்து "
+
+#: ../data/sugar.schemas.in.h:34
+msgid "Volume Level"
+msgstr "சத்தத்தின் அளவு"
+
+#: ../data/sugar.schemas.in.h:35
+msgid "Volume level for the sound device."
+msgstr "ஒலிஉபகரணத்திற்கான சத்தத்தின் அளவு "
+
+#: ../data/sugar.schemas.in.h:36
+msgid ""
+"When in resume mode, clicking on a favorite icon will cause the last entry "
+"for that activity to be resumed."
+msgstr ""
+"மீழ ஆரம்பிப்பதற்கு விரும்பிய icon மீது அழுத்தியவுடன் இறுதியாக செய்த "
+"செயற்பாடுகளைப் பெற்றுக் கொள்ளலாம்"
+
+#: ../src/jarabe/controlpanel/cmd.py:28
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+"sugar -கட்டுப்பாட்டு-பகுதி:எச்சரிக்கை அதே பெயருக்குரிய ஒன்றுக்கு மேற்பட்ட "
+"தெரிவுகளை தேடுதல்:%s மாற்றியமை %r"
+
+#: ../src/jarabe/controlpanel/cmd.py:30
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr "சுகர்-கட்டுப்பாட்டு-பட்டியல்:சாவி=%s கிடைக்கப்பெறாத தேர்வு"
+
+#: ../src/jarabe/controlpanel/cmd.py:31
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr "சுகர்-கட்டுப்பாட்டு-பட்டியல்:%s"
+
+#. TRANS: Translators, there's a empty line at the end of this string,
+#. which must appear in the translated string (msgstr) as well.
+#: ../src/jarabe/controlpanel/cmd.py:37
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" -c key clear the current value for the key \n"
+" "
+msgstr ""
+"பயன்பாடு :சுகர்-கட்டுப்பாட்டு-பட்டியல்[ தேர்வு] சாவி[ args ... ] \n"
+"------சுகர் சூழலுக்கான கட்டுப்பாடு\n"
+"-----தேர்வு\n"
+"------h ------உதவிக்கான தகவலைப் பார்ப்பதுடன் வெளியாகு\n"
+"-------l-----பயன்படுத்தக் கூடிய தேர்வுகளைப் பட்டியல் இடு\n"
+"-------hசாவி----இந்த சாவிக்கான தகவலைக் காட்டு\n"
+"------g சாவி---- தற்போதைய சாவிக்கான பெறுமதியினைப் பெற்றுக்கொள்\n"
+"-----s சாவி------ தற்போதைய சாவிக்கான பெறுமதியினை ஒழுங்குபடுத்து \n"
+"------c சாவி----- தற்போதைய சாவிக்கான பெறுமதியினை நீக்கு\n"
+"------"
+
+#: ../src/jarabe/controlpanel/cmd.py:50
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr "உங்களுடைய மாற்றங்களைப் பதிவதற்கு சுகரை மீள் ஆரம்பி\n"
+
+#: ../src/jarabe/controlpanel/gui.py:280
+msgid "Warning"
+msgstr "எச்சரிக்கை"
+
+#: ../src/jarabe/controlpanel/gui.py:281
+#: ../src/jarabe/controlpanel/sectionview.py:42
+msgid "Changes require restart"
+msgstr "மாற்றத்தினை மீள் ஆரம்பிப்பதற்கான கோரிக்கை"
+
+#: ../src/jarabe/controlpanel/gui.py:284
+msgid "Cancel changes"
+msgstr "மாற்றத்தினை இரத்துசெய்"
+
+#: ../src/jarabe/controlpanel/gui.py:289 ../src/jarabe/desktop/homebox.py:70
+msgid "Later"
+msgstr "பின்னர்"
+
+#: ../src/jarabe/controlpanel/gui.py:293
+msgid "Restart now"
+msgstr "இப்போது மீள் ஆரம்பி"
+
+#: ../src/jarabe/controlpanel/toolbar.py:61 ../src/jarabe/intro/window.py:188
+msgid "Done"
+msgstr "முடிந்து விட்டது"
+
+#: ../src/jarabe/controlpanel/toolbar.py:115
+#: ../src/jarabe/desktop/homebox.py:68
+#: ../src/jarabe/frame/activitiestray.py:726
+#: ../src/jarabe/frame/activitiestray.py:822
+#: ../src/jarabe/frame/activitiestray.py:850
+msgid "Cancel"
+msgstr "இரத்துசெய்"
+
+#: ../src/jarabe/controlpanel/toolbar.py:121
+#: ../src/jarabe/desktop/favoritesview.py:332
+msgid "Ok"
+msgstr "சரி"
+
+#: ../src/jarabe/desktop/activitieslist.py:80
+#: ../src/jarabe/journal/listview.py:147
+msgid "Title"
+msgstr "தலைப்பு"
+
+#: ../src/jarabe/desktop/activitieslist.py:91
+msgid "Version"
+msgstr "பதிப்பு"
+
+#: ../src/jarabe/desktop/activitieslist.py:105
+#: ../src/jarabe/journal/listview.py:178
+msgid "Date"
+msgstr "திகதி"
+
+#: ../src/jarabe/desktop/activitieslist.py:234
+#, python-format
+msgid "Version %s"
+msgstr "பதிப்பு %கள்"
+
+#: ../src/jarabe/desktop/activitieslist.py:355
+msgid "Confirm erase"
+msgstr "அழிப்பதை உறுதி செய்"
+
+#: ../src/jarabe/desktop/activitieslist.py:357
+#, python-format
+msgid "Confirm erase: Do you want to permanently erase %s?"
+msgstr "அழிப்பதை உறுதி செய்:உங்களுக்கு நிரந்தரமாக அழிக்க வேண்டுமா %s?"
+
+#: ../src/jarabe/desktop/activitieslist.py:361
+#: ../src/jarabe/frame/clipboardmenu.py:62
+#: ../src/jarabe/view/viewsource.py:218
+msgid "Keep"
+msgstr "வைத்திரு"
+
+#: ../src/jarabe/desktop/activitieslist.py:364
+#: ../src/jarabe/desktop/activitieslist.py:406
+#: ../src/jarabe/journal/journaltoolbox.py:360
+#: ../src/jarabe/journal/palettes.py:112
+msgid "Erase"
+msgstr "அழி"
+
+#: ../src/jarabe/desktop/activitieslist.py:427
+msgid "Remove favorite"
+msgstr "விரும்பியதை அகற்று"
+
+#: ../src/jarabe/desktop/activitieslist.py:431
+msgid "Make favorite"
+msgstr "விரும்பியதை உருவாக்கு"
+
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:116
+msgid "Freeform"
+msgstr "இலவச படிவம்"
+
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:198
+msgid "Ring"
+msgstr "வளையம்"
+
+#. TRANS: label for the spiral layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:334
+msgid "Spiral"
+msgstr "சுருள்"
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:401
+msgid "Box"
+msgstr "பெட்டி"
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:442
+msgid "Triangle"
+msgstr "முக்கோணம்"
+
+#: ../src/jarabe/desktop/favoritesview.py:323
+msgid "Registration Failed"
+msgstr "பதிவுப்பிழை"
+
+#: ../src/jarabe/desktop/favoritesview.py:324
+#, python-format
+msgid "%s"
+msgstr "%கள்"
+
+#: ../src/jarabe/desktop/favoritesview.py:326
+msgid "Registration Successful"
+msgstr "பதிவு வெற்றிகரமானது"
+
+#: ../src/jarabe/desktop/favoritesview.py:327
+msgid "You are now registered with your school server."
+msgstr ""
+"நீங்கள் தற்போது உங்கள் பாடசாலையிலுள்ள வழங்கி (கணினி) யில் பதிவு "
+"செய்யப்பட்டுள்ளீர்"
+
+#: ../src/jarabe/desktop/favoritesview.py:671
+msgid "Register"
+msgstr "பதிவு"
+
+#: ../src/jarabe/desktop/homebox.py:63
+msgid "Software Update"
+msgstr "மென் பொருளைப் புதுப்பித்தல்"
+
+#: ../src/jarabe/desktop/homebox.py:64
+msgid "Update your activities to ensure compatibility with your new software"
+msgstr "உங்கள் புதிய மென் பொருளுடன் செயற்பாடுகளை உறுதிப்படுத்துவதி புதுப்பி "
+
+#: ../src/jarabe/desktop/homebox.py:73
+msgid "Check now"
+msgstr "தற்போது பரிசீலி"
+
+#: ../src/jarabe/desktop/homebox.py:192
+msgid "List view"
+msgstr "பட்டியல் பார்வை"
+
+#: ../src/jarabe/desktop/homebox.py:193
+msgid "<Ctrl>2"
+msgstr "<Ctrl>2"
+
+#: ../src/jarabe/desktop/homebox.py:255
+msgid "Favorites view"
+msgstr "விருப்புக்குரிய பார்வை"
+
+#: ../src/jarabe/desktop/homebox.py:256
+msgid "<Ctrl>1"
+msgstr "<Ctrl>1"
+
+#: ../src/jarabe/desktop/keydialog.py:131
+msgid "Key Type:"
+msgstr "சாவி டைப்/வகை"
+
+#: ../src/jarabe/desktop/keydialog.py:151
+msgid "Authentication Type:"
+msgstr " நம்பத்தகுந்த டைப்/வகை"
+
+#: ../src/jarabe/desktop/keydialog.py:215
+msgid "WPA & WPA2 Personal"
+msgstr "WPA & WPA2 தனிப்பட்ட"
+
+#: ../src/jarabe/desktop/keydialog.py:224
+msgid "Wireless Security:"
+msgstr "இணைப்பற்ற பாதுகாப்பு"
+
+#: ../src/jarabe/desktop/meshbox.py:136
+msgid "Connect"
+msgstr "இணை"
+
+#: ../src/jarabe/desktop/meshbox.py:140
+msgid "Disconnect"
+msgstr "இணைப்பை துண்டி"
+
+#. TRANS: Action label for resuming an activity.
+#: ../src/jarabe/desktop/meshbox.py:463
+#: ../src/jarabe/frame/activitiestray.py:761
+#: ../src/jarabe/journal/journaltoolbox.py:428
+#: ../src/jarabe/journal/palettes.py:72 ../src/jarabe/view/palettes.py:64
+msgid "Resume"
+msgstr "மீழ ஆரம்பி"
+
+#: ../src/jarabe/desktop/meshbox.py:468
+#: ../src/jarabe/frame/activitiestray.py:235
+msgid "Join"
+msgstr "இணை"
+
+#: ../src/jarabe/desktop/schoolserver.py:34
+msgid "Cannot obtain data needed for registration."
+msgstr "உங்கள் பதிவிற்கு தேவையான தகவல்களைப் பெற்றுக்கொள்ள முடியாது"
+
+#: ../src/jarabe/desktop/schoolserver.py:51
+msgid "Cannot connect to the server."
+msgstr "வழங்கி (கணினி) யினை இணைக்க முடியாது"
+
+#: ../src/jarabe/desktop/schoolserver.py:56
+msgid "The server could not complete the request."
+msgstr "வழங்கி (கணினி) கட்டளையை முழுமையாக ஏற்கவில்லை"
+
+#: ../src/jarabe/frame/activitiestray.py:240
+#: ../src/jarabe/frame/activitiestray.py:698
+msgid "Decline"
+msgstr "கோடு"
+
+#: ../src/jarabe/frame/activitiestray.py:650
+#, python-format
+msgid "%dB"
+msgstr "%dB"
+
+#: ../src/jarabe/frame/activitiestray.py:652
+#, python-format
+msgid "%dKB"
+msgstr "%dKB"
+
+#: ../src/jarabe/frame/activitiestray.py:654
+#, python-format
+msgid "%dMB"
+msgstr "%dMB"
+
+#: ../src/jarabe/frame/activitiestray.py:671
+#, python-format
+msgid "%s of %s"
+msgstr "%s of %s"
+
+#: ../src/jarabe/frame/activitiestray.py:683
+#, python-format
+msgid "Transfer from %r"
+msgstr "%r ல்ருந்து மாற்று"
+
+#: ../src/jarabe/frame/activitiestray.py:693
+msgid "Accept"
+msgstr "ஏற்றுக்கொள்"
+
+#: ../src/jarabe/frame/activitiestray.py:716
+#: ../src/jarabe/frame/activitiestray.py:840
+#, python-format
+msgid "%s (%s)"
+msgstr "%s (%s)"
+
+#: ../src/jarabe/frame/activitiestray.py:750
+#: ../src/jarabe/frame/activitiestray.py:875
+msgid "Dismiss"
+msgstr "இல்லாமல் செய்"
+
+#: ../src/jarabe/frame/activitiestray.py:810
+#, python-format
+msgid "Transfer to %r"
+msgstr "%r ற்கு மாற்றம் செய்"
+
+#: ../src/jarabe/frame/clipboardmenu.py:52
+msgid "Remove"
+msgstr "அகற்று"
+
+#: ../src/jarabe/frame/clipboardmenu.py:57
+#: ../src/jarabe/frame/clipboardmenu.py:80
+msgid "Open"
+msgstr "திற"
+
+#: ../src/jarabe/frame/clipboardmenu.py:85
+msgid "Open with"
+msgstr "திறத்தலுடன்"
+
+#: ../src/jarabe/frame/clipboardobject.py:49
+#, python-format
+msgid "%s clipping"
+msgstr "%s சிறிதாக்குதல்"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:37
+msgid "Neighborhood"
+msgstr "அயல்"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:37
+msgid "F1"
+msgstr "F1"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:39
+msgid "F2"
+msgstr "F2"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:41
+msgid "F3"
+msgstr "F3"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:43
+msgid "F4"
+msgstr "F4"
+
+#: ../src/jarabe/intro/window.py:124
+msgid "Click to change color:"
+msgstr "நிற மாற்றத்திற்காக அழுத்து"
+
+#: ../src/jarabe/intro/window.py:174 ../src/jarabe/journal/detailview.py:103
+msgid "Back"
+msgstr "பின்"
+
+#: ../src/jarabe/intro/window.py:191
+msgid "Next"
+msgstr "அடுத்தது"
+
+#: ../src/jarabe/journal/expandedentry.py:164
+#: ../src/jarabe/journal/palettes.py:66
+msgid "Untitled"
+msgstr "பெயரிடப்படாத"
+
+#: ../src/jarabe/journal/expandedentry.py:210
+msgid "No preview"
+msgstr "பார்வை இன்மை"
+
+#: ../src/jarabe/journal/expandedentry.py:229
+#, python-format
+msgid "Kind: %s"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:229
+msgid "Unknown"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:230
+#, python-format
+msgid "Date: %s"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:231
+#, python-format
+msgid "Size: %s"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:253 ../src/jarabe/journal/misc.py:92
+msgid "No date"
+msgstr "திகதியில்லை"
+
+#: ../src/jarabe/journal/expandedentry.py:260
+msgid "Participants:"
+msgstr "பங்குபற்றுநர்"
+
+#: ../src/jarabe/journal/expandedentry.py:283
+msgid "Description:"
+msgstr "விளக்கம்"
+
+#: ../src/jarabe/journal/expandedentry.py:309
+msgid "Tags:"
+msgstr "இணைக்கயிறு"
+
+#: ../src/jarabe/journal/journalactivity.py:108
+#: ../src/jarabe/journal/volumestoolbar.py:47
+msgid "Journal"
+msgstr "சஞ்சிகை"
+
+#: ../src/jarabe/journal/journaltoolbox.py:67
+msgid "Search"
+msgstr "தேடு"
+
+#: ../src/jarabe/journal/journaltoolbox.py:126
+msgid "Anytime"
+msgstr "எந்நேரம்"
+
+#: ../src/jarabe/journal/journaltoolbox.py:128
+msgid "Today"
+msgstr "இன்று"
+
+#: ../src/jarabe/journal/journaltoolbox.py:130
+msgid "Since yesterday"
+msgstr "நேற்று வரை"
+
+#. TRANS: Filter entries modified during the last 7 days.
+#: ../src/jarabe/journal/journaltoolbox.py:132
+msgid "Past week"
+msgstr "கடந்த வாரம்"
+
+#. TRANS: Filter entries modified during the last 30 days.
+#: ../src/jarabe/journal/journaltoolbox.py:134
+msgid "Past month"
+msgstr "கடந்த மாதம்"
+
+#. TRANS: Filter entries modified during the last 356 days.
+#: ../src/jarabe/journal/journaltoolbox.py:136
+msgid "Past year"
+msgstr "கடந்த வருடம்"
+
+#: ../src/jarabe/journal/journaltoolbox.py:143
+msgid "Anyone"
+msgstr "யாராவது"
+
+#: ../src/jarabe/journal/journaltoolbox.py:145
+msgid "My friends"
+msgstr "எனது நண்பர்"
+
+#: ../src/jarabe/journal/journaltoolbox.py:146
+msgid "My class"
+msgstr "எனது வகுப்பு"
+
+#: ../src/jarabe/journal/journaltoolbox.py:274
+msgid "Anything"
+msgstr "ஏதாவது"
+
+#: ../src/jarabe/journal/journaltoolbox.py:350
+#: ../src/jarabe/journal/palettes.py:90
+msgid "Copy"
+msgstr "பிரதி செய்"
+
+#. TRANS: Action label for starting an entry.
+#: ../src/jarabe/journal/journaltoolbox.py:431
+#: ../src/jarabe/journal/palettes.py:75
+msgid "Start"
+msgstr "ஆரம்பி"
+
+#: ../src/jarabe/journal/listview.py:361
+msgid "Your Journal is empty"
+msgstr "உங்களுடைய சஞ்சிகை வெறுமையானது"
+
+#: ../src/jarabe/journal/listview.py:363
+msgid "No matching entries"
+msgstr "பொருத்தமற்றப் பதிவுகள்"
+
+#: ../src/jarabe/journal/listview.py:374
+msgid "Clear search"
+msgstr "தேடலை நீக்கு"
+
+#: ../src/jarabe/journal/modalalert.py:63
+msgid "Your Journal is full"
+msgstr "உங்கள் சஞ்சிகை நிரம்புயுள்ளது"
+
+#: ../src/jarabe/journal/modalalert.py:67
+msgid "Please delete some old Journal entries to make space for new ones."
+msgstr " தயவு செய்து புதிய தகவல்களைப் பதிவதற்காக சில பழைய தகவல்களை அழிக்கவும்"
+
+#: ../src/jarabe/journal/modalalert.py:79
+msgid "Show Journal"
+msgstr "சஞ்சிகையைக் காட்டு"
+
+#: ../src/jarabe/journal/objectchooser.py:146
+msgid "Choose an object"
+msgstr "பொருளைத் தெரிவுசெய்"
+
+#: ../src/jarabe/journal/objectchooser.py:151
+#: ../src/jarabe/view/viewsource.py:308
+msgid "Close"
+msgstr "மூடு"
+
+#: ../src/jarabe/journal/palettes.py:73
+msgid "Resume with"
+msgstr "மீழ ஆரம்பித்தலுடன்"
+
+#: ../src/jarabe/journal/palettes.py:76
+msgid "Start with"
+msgstr "ஆரம்பி"
+
+#: ../src/jarabe/journal/palettes.py:98
+msgid "Send to"
+msgstr "அனுப்பு"
+
+#: ../src/jarabe/journal/palettes.py:107
+msgid "View Details"
+msgstr "தகவல் பார்வை"
+
+#: ../src/jarabe/journal/palettes.py:185
+msgid "No friends present"
+msgstr "தற்போது நண்பர்கள் இல்லை"
+
+#: ../src/jarabe/journal/palettes.py:190
+msgid "No valid connection found"
+msgstr "அனுமதிக்கப்பட்ட இணைப்பு இடப்படவில்லை"
+
+#: ../src/jarabe/journal/palettes.py:218
+msgid "No activity to resume entry"
+msgstr "மீழ ஆரம்பிப்பதற்கான செயற்பாடு இல்லை"
+
+#: ../src/jarabe/journal/palettes.py:220
+msgid "No activity to start entry"
+msgstr "ஆரம்பிப்பதற்கான செயற்பாடு இல்லை"
+
+#: ../src/jarabe/view/buddymenu.py:62
+msgid "Remove friend"
+msgstr "நண்பணை அகற்று"
+
+#: ../src/jarabe/view/buddymenu.py:65
+msgid "Make friend"
+msgstr "நண்பணை உருவாக்கு"
+
+#: ../src/jarabe/view/buddymenu.py:82
+msgid "Shutdown"
+msgstr "திரையை மூடுதல்"
+
+#: ../src/jarabe/view/buddymenu.py:90
+msgid "Logout"
+msgstr "பதிவை அகற்று"
+
+#: ../src/jarabe/view/buddymenu.py:95
+msgid "My Settings"
+msgstr "எனது அமைப்பு"
+
+#: ../src/jarabe/view/buddymenu.py:130
+#, python-format
+msgid "Invite to %s"
+msgstr "அழை %s"
+
+#: ../src/jarabe/view/palettes.py:45
+msgid "Starting..."
+msgstr "ஆரம்பி"
+
+#. TODO: share-with, keep
+#: ../src/jarabe/view/palettes.py:71
+msgid "View Source"
+msgstr " வளப்(மூலம்)பார்வை"
+
+#: ../src/jarabe/view/palettes.py:82
+msgid "Stop"
+msgstr "நிறுத்து"
+
+#: ../src/jarabe/view/palettes.py:122
+msgid "Start new"
+msgstr "புதிதை ஆரம்பி"
+
+#: ../src/jarabe/view/palettes.py:171
+msgid "Show contents"
+msgstr "உள்ளடக்கியதைக் காட்டு "
+
+#: ../src/jarabe/view/palettes.py:193 ../src/jarabe/view/palettes.py:243
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr "%(மிகுதி_,இடம்)d MB மிகுதி"
+
+#: ../src/jarabe/view/palettes.py:218
+msgid "Unmount"
+msgstr "பதியாதே"
+
+#: ../src/jarabe/view/viewsource.py:208
+msgid "Instance Source"
+msgstr " மாதிரி வளம் (மூலம்)"
+
+#: ../src/jarabe/view/viewsource.py:233
+msgid "Source"
+msgstr " வளம் (மூலம்)"
+
+#: ../src/jarabe/view/viewsource.py:292
+msgid "Activity Bundle Source"
+msgstr "செயற்பாட்டு வளத் (மூலம்) தொகுதி"
+
+#: ../src/jarabe/view/viewsource.py:299
+#, python-format
+msgid "View source: %r"
+msgstr " வளப் (மூலம்)பார்வை:%r"
+
+#~ msgid "Restart"
+#~ msgstr "மீள் ஆரம்பி"
+
+#~ msgid ""
+#~ "© 2008 One Laptop per Child Association Inc; Red Hat Inc; and Contributors."
+#~ msgstr ""
+#~ "© 2008இல் ஒரு பிள்ளைக்கு ஒரு மடிமேல்கணினி கொடுக்க இணக்கம்Inc; Red Hat Inc; "
+#~ "andContributors."
diff --git a/shell/po/te.po b/shell/po/te.po
new file mode 100644
index 0000000..b52f7fe
--- /dev/null
+++ b/shell/po/te.po
@@ -0,0 +1,763 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-07-23 07:44-0400\n"
+"PO-Revision-Date: 2008-07-24 12:16-0400\n"
+"Last-Translator: Sayamindu Dasgupta <sayamindu@gmail.com>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Pootle 1.1.0rc2\n"
+
+#: ../src/intro/intro.py:65
+#: ../src/controlpanel/view/aboutme.py:100
+msgid "Name:"
+msgstr "పేరు"
+
+#: ../src/intro/intro.py:94
+msgid "Click to change color:"
+msgstr "రంగు మార్చడానికి నొక్కు"
+
+#: ../src/intro/intro.py:145
+msgid "Back"
+msgstr "వెనుకకు"
+
+#: ../src/intro/intro.py:159
+#: ../src/controlpanel/toolbar.py:61
+msgid "Done"
+msgstr "పూర్తి అయినది"
+
+#: ../src/intro/intro.py:162
+msgid "Next"
+msgstr "తర్వాత"
+
+#: ../src/view/BuddyMenu.py:58
+msgid "Remove friend"
+msgstr "స్నేహం తీసివేయి"
+
+#: ../src/view/BuddyMenu.py:61
+msgid "Make friend"
+msgstr "స్నేహం చేయి"
+
+#: ../src/view/BuddyMenu.py:91
+#, python-format
+msgid "Invite to %s"
+msgstr "%s కు పిలువు"
+
+#: ../src/view/clipboardmenu.py:48
+msgid "Remove"
+msgstr "తీసివేయి"
+
+#: ../src/view/clipboardmenu.py:53
+#: ../src/view/clipboardmenu.py:79
+msgid "Open"
+msgstr "తెరువు"
+
+# self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+# TODO: Implement stopping downloads
+# self._stop_item.connect('activate', self._stop_item_activate_cb)
+# self.append_menu_item(self._stop_item)
+#. self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+#. TODO: Implement stopping downloads
+#. self._stop_item.connect('activate', self._stop_item_activate_cb)
+#. self.append_menu_item(self._stop_item)
+#: ../src/view/clipboardmenu.py:63
+#: ../src/view/home/HomeBox.py:86
+msgid "Keep"
+msgstr "ఉంచు"
+
+#: ../src/view/clipboardmenu.py:84
+msgid "Open with"
+msgstr "తో తెరువు"
+
+#: ../src/view/clipboardmenu.py:216
+#, python-format
+msgid "Clipboard object: %s."
+msgstr "క్లిప్ బోర్డు వస్తువు : %s."
+
+#: ../src/hardware/keydialog.py:150
+msgid "Key Type:"
+msgstr "తాళం రకము:"
+
+#: ../src/hardware/keydialog.py:170
+msgid "Authentication Type:"
+msgstr "దృడపరచుకొనే రకము:"
+
+#: ../src/hardware/keydialog.py:251
+msgid "Encryption Type:"
+msgstr "గుప్త్తీకరించే రకము:"
+
+#: ../src/view/Shell.py:240
+msgid "Screenshot"
+msgstr "తెరముద్ర"
+
+#: ../src/view/home/HomeBox.py:80
+msgid "Confirm erase"
+msgstr "చెరిపివేయుట నిర్ద్దారణ చేయి"
+
+#: ../src/view/home/HomeBox.py:82
+#, python-format
+msgid "Confirm erase: Do you want to permanently erase %s?"
+msgstr "చెరిపివేయుట నిర్ద్దారణ చేయుట: %s శాశ్వతంగా చెరిపివేయబడుట ఇష్టమేనా?"
+
+#: ../src/view/home/HomeBox.py:89
+#: ../src/view/palettes.py:120
+msgid "Erase"
+msgstr "చెరిపివేయి"
+
+#: ../src/view/home/HomeBox.py:215
+msgid "List view"
+msgstr "వరసగా చూడు"
+
+#: ../src/view/home/HomeBox.py:216
+#, fuzzy
+msgid "<Ctrl>2"
+msgstr "<Ctrl>L"
+
+#: ../src/view/home/HomeBox.py:273
+msgid "Favorites view"
+msgstr "ఇష్టమైనవి చూచుటకు"
+
+#: ../src/view/home/HomeBox.py:274
+#, fuzzy
+msgid "<Ctrl>1"
+msgstr "<Ctrl>L"
+
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/view/home/HomeBox.py:282
+msgid "Freeform"
+msgstr "కలగలుపుగా"
+
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/view/home/HomeBox.py:289
+msgid "Ring"
+msgstr "వ్రుత్తము"
+
+#: ../src/view/home/MeshBox.py:97
+msgid "Connect"
+msgstr "తగిలించు"
+
+#: ../src/view/home/MeshBox.py:106
+#, fuzzy
+msgid "Disconnect"
+msgstr "వేరుచేయబడింది"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#: ../src/view/home/MeshBox.py:118
+#, fuzzy
+msgid "Disconnecting..."
+msgstr "వేరుచేయి . . ."
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#: ../src/view/home/MeshBox.py:152
+#, fuzzy
+msgid "Connecting..."
+msgstr "వేరుచేయి . . ."
+
+#. TODO: show the channel number
+#: ../src/view/home/MeshBox.py:159
+msgid "Connected"
+msgstr "తగిలించబడినది"
+
+#: ../src/view/home/MeshBox.py:211
+#: ../src/view/devices/network/mesh.py:41
+#: ../src/view/devices/network/mesh.py:68
+#: ../src/view/devices/network/mesh.py:72
+msgid "Mesh Network"
+msgstr "మెష్ వల"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#: ../src/view/home/MeshBox.py:214
+#: ../src/view/devices/network/wireless.py:119
+#: ../src/view/devices/network/mesh.py:89
+msgid "Disconnect..."
+msgstr "వేరుచేయి . . ."
+
+#: ../src/view/home/MeshBox.py:302
+#: ../src/view/palettes.py:61
+msgid "Resume"
+msgstr "పునరారంభించు"
+
+#: ../src/view/home/MeshBox.py:307
+#: ../src/view/frame/activitiestray.py:205
+msgid "Join"
+msgstr "కలువు"
+
+#: ../src/view/devices/battery.py:45
+msgid "My Battery"
+msgstr "నా బేటరీ"
+
+#: ../src/view/devices/battery.py:114
+msgid "Charging"
+msgstr "చార్జి అవుతుంది"
+
+#: ../src/view/devices/battery.py:117
+msgid "Very little power remaining"
+msgstr "చాలా తక్కువ చార్జీ మిగిలిఉంది"
+
+#: ../src/view/devices/battery.py:123
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr "%(hour)d:%(min).2d మిగిలిఉంది"
+
+#: ../src/view/devices/battery.py:127
+msgid "Charged"
+msgstr "చార్జీ అయింది"
+
+#: ../src/view/devices/speaker.py:44
+msgid "My Speakers"
+msgstr "నా స్పీకరులు"
+
+#: ../src/view/devices/speaker.py:119
+msgid "Unmute"
+msgstr "పలికించు"
+
+#: ../src/view/devices/speaker.py:122
+msgid "Mute"
+msgstr "నిశ్శబ్దంగా ఉంచు"
+
+#: ../src/view/devices/network/wireless.py:67
+msgid "Disconnected"
+msgstr "వేరుచేయబడింది"
+
+#: ../src/view/devices/network/wireless.py:137
+msgid "Channel"
+msgstr "ప్రసారమార్గం"
+
+#: ../src/view/frame/zoomtoolbar.py:34
+msgid "Neighborhood"
+msgstr "చుట్టుపక్కలవారు"
+
+#: ../src/view/frame/zoomtoolbar.py:36
+msgid "Group"
+msgstr "గుంపు"
+
+#: ../src/view/frame/zoomtoolbar.py:38
+msgid "Home"
+msgstr "ఇల్లు"
+
+#: ../src/view/frame/zoomtoolbar.py:40
+msgid "Activity"
+msgstr "వ్యాపకం"
+
+#: ../src/controlpanel/cmd.py:26
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+"షుగర్-కంట్రోల్-పేనల్: గమనిక, అదేపేరుతో ఒకటికన్నా ఎక్కువ అవకాశాలు ఉన్నవి: %s "
+"module: %r"
+
+#: ../src/controlpanel/cmd.py:28
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr "షుగర్-కంట్రోల్-పేనల్: key=%s అవకాశం లేదు"
+
+#: ../src/controlpanel/cmd.py:29
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr "షుగర్-కంట్రోల్-పేనల్: %s"
+
+#: ../src/controlpanel/cmd.py:33
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" "
+msgstr "షుగర్-కంట్రోల్-పేనల్:"
+
+#: ../src/controlpanel/cmd.py:45
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr "మీ మార్పులు పనిచేయడానికి సుగరును తిరిగి మొదలు పెట్టండి.\n"
+
+#: ../src/controlpanel/toolbar.py:115
+msgid "Cancel"
+msgstr "రద్దు చేయి"
+
+#: ../src/controlpanel/toolbar.py:121
+#: ../src/view/home/favoritesview.py:294
+msgid "Ok"
+msgstr "సరి"
+
+#: ../src/controlpanel/sectionview.py:34
+#: ../src/controlpanel/gui.py:260
+msgid "Changes require restart"
+msgstr "మార్పులు తిరిగి మొదలుపెట్తినతర్వాతే పనిచేస్తాయి"
+
+#: ../src/controlpanel/gui.py:259
+msgid "Warning"
+msgstr "గమనిక"
+
+#: ../src/controlpanel/gui.py:263
+msgid "Cancel changes"
+msgstr "మార్పులు రద్దుచేయి"
+
+#: ../src/controlpanel/gui.py:267
+msgid "Later"
+msgstr "తరవాత"
+
+#: ../src/controlpanel/gui.py:271
+msgid "Restart now"
+msgstr "ఇప్పుడు మరలామొదలుపెట్టు"
+
+#: ../src/controlpanel/model/aboutme.py:44
+msgid "You must enter a name."
+msgstr "మీరు పేరు నింపవలెను"
+
+#: ../src/controlpanel/model/aboutme.py:69
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr "కుంచె: color=%s hue=%s"
+
+#: ../src/controlpanel/model/aboutme.py:72
+#, python-format
+msgid "stroke: %s"
+msgstr "కుంచె: %s"
+
+#: ../src/controlpanel/model/aboutme.py:74
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr "నింపు: color=%s hue=%s"
+
+#: ../src/controlpanel/model/aboutme.py:76
+#, python-format
+msgid "fill: %s"
+msgstr "నింపు: %s"
+
+#: ../src/controlpanel/model/aboutme.py:87
+msgid "Error in specified color modifiers."
+msgstr "నిర్ధేశించి చెప్పిన రంగులు మార్చడంలో పొరబాటు జరిగింది"
+
+#: ../src/controlpanel/model/aboutme.py:90
+msgid "Error in specified colors."
+msgstr "నిర్ధేశించి చెప్పిన రంగులలో పొరబాటు జరిగింది"
+
+#: ../src/controlpanel/model/aboutxo.py:24
+msgid "Not available"
+msgstr "అలభ్యం"
+
+#: ../src/controlpanel/model/datetime.py:85
+msgid "Error timezone does not exist."
+msgstr "పొరబాటు. కాలమండలమే లేదు."
+
+#: ../src/controlpanel/model/frame.py:38
+#: ../src/controlpanel/model/frame.py:60
+#, fuzzy
+msgid "Value must be an integer."
+msgstr "అంకెలు ఉపయోగించండి"
+
+#: ../src/controlpanel/model/language.py:28
+#, fuzzy
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr "%s ప్రవేశము కుదరలేదు. ప్రమాణ మైన సెట్టింగ్ శ్రుష్తించనా."
+
+#: ../src/controlpanel/model/language.py:104
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr "కోడ్=%s కు భాష తెలియ లేదు."
+
+#: ../src/controlpanel/model/language.py:121
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr "మన్నించాలి నేను '%s'మాట్లాడలేను"
+
+#: ../src/controlpanel/model/network.py:48
+#, fuzzy
+msgid "You must enter a server."
+msgstr "మీరు పేరు నింపవలెను"
+
+#: ../src/controlpanel/model/network.py:63
+msgid "State is unknown."
+msgstr "ఉనికి తెలియదు"
+
+#: ../src/controlpanel/model/network.py:83
+msgid "Error in specified radio argument use on/off."
+msgstr "రేడియొ నిర్దేసించడంలో పొరబాటు. ఉంది/లేదు వాడనా ."
+
+#: ../src/controlpanel/model/power.py:57
+msgid "Error in automatic pm argument, use on/off."
+msgstr "దానంతట అదేజరిగే విద్యుత్తు నిర్వహణలో తప్పుజరింది, ఉంది/లేదు వాడు"
+
+#: ../src/controlpanel/model/power.py:86
+msgid "Error in extreme pm argument, use on/off."
+msgstr "అమోఘమైన విద్యుత్తు నిర్వహణలో తప్పుజరింది, ఉంది/లేదు వాడు"
+
+#: ../src/controlpanel/view/aboutme.py:32
+msgid "About Me"
+msgstr "నాగురించి"
+
+#: ../src/controlpanel/view/aboutme.py:134
+#, fuzzy
+msgid "Click to change your color:"
+msgstr "రంగు మార్చడానికి నొక్కు"
+
+#: ../src/controlpanel/view/aboutxo.py:26
+msgid "About my XO"
+msgstr "నా ఎక్స్ ఒ గురించి"
+
+#: ../src/controlpanel/view/aboutxo.py:47
+msgid "Identity"
+msgstr "ఉనికి"
+
+#: ../src/controlpanel/view/aboutxo.py:56
+msgid "Serial Number:"
+msgstr "వరుస సంఖ్య:"
+
+#: ../src/controlpanel/view/aboutxo.py:79
+msgid "Software"
+msgstr "సాఫ్ట్​వేర్"
+
+#: ../src/controlpanel/view/aboutxo.py:88
+msgid "Build:"
+msgstr "బిల్డ్:"
+
+#: ../src/controlpanel/view/aboutxo.py:103
+msgid "Firmware:"
+msgstr "ఫర్మ్​వేర్:"
+
+#: ../src/controlpanel/view/datetime.py:29
+msgid "Date & Time"
+msgstr "తేదీ & కాలము"
+
+#: ../src/controlpanel/view/datetime.py:72
+msgid "Timezone"
+msgstr "కాలమానం"
+
+#: ../src/controlpanel/view/frame.py:28
+msgid "Frame"
+msgstr "ఫ్రేమ్"
+
+#: ../src/controlpanel/view/frame.py:30
+msgid "never"
+msgstr "ఎప్పుడూకాదు"
+
+#: ../src/controlpanel/view/frame.py:31
+msgid "instantaneous"
+msgstr "వెంటనే"
+
+#: ../src/controlpanel/view/frame.py:32
+#, python-format
+msgid "%s seconds"
+msgstr "%s సెకనులు"
+
+#: ../src/controlpanel/view/frame.py:56
+msgid "Activation Delay"
+msgstr "మొదలుపెట్టడంలో ఆలస్యమైంది"
+
+#: ../src/controlpanel/view/frame.py:80
+msgid "Corner"
+msgstr "మూల"
+
+#: ../src/controlpanel/view/frame.py:115
+msgid "Edge"
+msgstr "అంచు"
+
+#: ../src/controlpanel/view/language.py:29
+#: ../src/controlpanel/view/language.py:74
+msgid "Language"
+msgstr "భాష"
+
+#: ../src/controlpanel/view/network.py:28
+msgid "Network"
+msgstr "వల"
+
+#: ../src/controlpanel/view/network.py:53
+msgid "Wireless"
+msgstr "నిస్తం​త్రి"
+
+#: ../src/controlpanel/view/network.py:61
+msgid "Radio:"
+msgstr "రేడియే"
+
+#: ../src/controlpanel/view/network.py:94
+msgid "Mesh"
+msgstr "మెష్ వల "
+
+#: ../src/controlpanel/view/network.py:103
+msgid "Server:"
+msgstr "శెర్వరు:"
+
+#: ../src/controlpanel/view/power.py:27
+msgid "Power"
+msgstr "విద్యుత్తు "
+
+#: ../src/controlpanel/view/power.py:51
+msgid "Power management"
+msgstr "విద్యుత్తు నిర్వహణ"
+
+#: ../src/controlpanel/view/power.py:61
+msgid "Automatic power management (increases battery life)"
+msgstr "దానంతట అదే విద్యుత్తు నిర్వహణ(బేటరీ ఆయువు పెంచుతుంది)"
+
+#: ../src/controlpanel/view/power.py:89
+msgid ""
+"Extreme power management (disables wireless radio, increases battery life)"
+msgstr ""
+"అమోఘమైన విద్యుత్తు నిర్వహణ( నిస్తం​త్రిని ఆపుతుంది, బేటరీ ఆయువు పెంచుతుంది)"
+
+#: ../src/view/devices/network/mesh.py:111
+msgid "Connected to a School Mesh Portal"
+msgstr "బడి మెష్ పొర్టల్ తో జతచేయబడింది"
+
+#: ../src/view/devices/network/mesh.py:113
+msgid "Looking for a School Mesh Portal..."
+msgstr "బడి మెష్ పొర్టల్ కోసమై చూస్తున్నా..."
+
+#: ../src/view/devices/network/mesh.py:116
+msgid "Connected to an XO Mesh Portal"
+msgstr "ఎక్స్ఒ మెష్ పొర్టల్ తో జతచేయబడింది"
+
+#: ../src/view/devices/network/mesh.py:118
+msgid "Looking for an XO Mesh Portal..."
+msgstr "ఎక్స్ఒ మెష్ పొర్టల్ కోసమై చూస్తున్నా..."
+
+#: ../src/view/devices/network/mesh.py:121
+msgid "Connected to a Simple Mesh"
+msgstr "సరళమైన మెష్ తో జత కుదిరింది"
+
+#: ../src/view/devices/network/mesh.py:123
+msgid "Starting a Simple Mesh"
+msgstr "సరళమైన మెష్ మొదలవుతుంది"
+
+#: ../src/view/devices/network/mesh.py:130
+msgid "Unknown Mesh"
+msgstr "తెలియని మెష్"
+
+#: ../src/view/frame/activitiestray.py:210
+msgid "Decline"
+msgstr "నిరస్కరించు"
+
+#: ../src/view/home/favoritesview.py:285
+msgid "Registration Failed"
+msgstr "రిజిస్టరుచేయడం విఫలమైంది"
+
+#: ../src/view/home/favoritesview.py:286
+#, python-format
+msgid "%s"
+msgstr "%s"
+
+#: ../src/view/home/favoritesview.py:288
+msgid "Registration Successful"
+msgstr "రిజిస్టరుచేయడం విజయవంతమైంది"
+
+#: ../src/view/home/favoritesview.py:289
+msgid "You are now registered with your school server."
+msgstr "ఇప్పుడు మీరు బడి సెర్వరుతో రిజిస్టరుచేయబడిఉన్నారు"
+
+#: ../src/view/home/favoritesview.py:405
+msgid "Control Panel"
+msgstr "కంట్రొల్ పేనల్"
+
+#: ../src/view/home/favoritesview.py:416
+msgid "Restart"
+msgstr "తిరిగి మొదలుపెట్టు"
+
+#: ../src/view/home/favoritesview.py:421
+msgid "Shutdown"
+msgstr "ఫూర్తిగా ఆపు"
+
+#: ../src/view/home/favoritesview.py:427
+msgid "Register"
+msgstr "దాఖలు చేయి"
+
+#: ../src/view/palettes.py:42
+msgid "Starting..."
+msgstr "మొదలవుతుంది..."
+
+#: ../src/view/palettes.py:72
+msgid "Stop"
+msgstr "ఆపు"
+
+#: ../src/view/palettes.py:104
+msgid "Start"
+msgstr "మొదలుపెట్టు"
+
+#: ../src/view/palettes.py:132
+msgid "Remove favorite"
+msgstr "ఇష్టమైనదానిని తొలగించు"
+
+#: ../src/view/palettes.py:136
+msgid "Make favorite"
+msgstr "ఇష్టమైనదానిగా తయారుచేయి"
+
+#: ../src/view/palettes.py:185
+msgid "Show contents"
+msgstr "విషయాలను చూపు"
+
+#: ../src/view/palettes.py:209
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr "%(free_space)d MB ఖాళీ"
+
+#~ msgid "<Ctrl>R"
+#~ msgstr "<Ctrl>R"
+
+#~ msgid "Ring view"
+#~ msgstr "ఉంగరంగా చూడు"
+
+#~ msgid "Add to ring"
+#~ msgstr "ఉంగరమునకు కలుపు"
+
+#~ msgid "off"
+#~ msgstr "లేదు"
+
+#~ msgid "on"
+#~ msgstr "ఉంది"
+
+#~ msgid "Permission denied. You need to be root to run this method."
+#~ msgstr "అనుమతి లేదు. మీరు రూట్ గా మారి ప్రయత్నించాలి"
+
+#~ msgid "Error in reading timezone"
+#~ msgstr "కాలమండలం చదవడంలో పొరబాటు జరిగింది"
+
+#, python-format
+#~ msgid "Error copying timezone (from %s): %s"
+#~ msgstr "కాలమండలం నకలు చేయడంలో పొరబాటు జరిగింది (%s నుంచి ): %s"
+
+#, python-format
+#~ msgid "Changing permission of timezone: %s"
+#~ msgstr "కాలమండలం అనుమతి మారుతుంది:%s"
+
+#~ msgid "About this XO"
+#~ msgstr "ఈ ఎక్స్ఒ గురించి"
+
+#~ msgid "Add to journal"
+#~ msgstr "పద్దుకి కలుపు"
+
+#~ msgid "Reboot"
+#~ msgstr "తిరిగి బూట్ చేయి"
+
+#~ msgid "My Battery life"
+#~ msgstr "నా బేటరీ ఆయువు"
+
+#~ msgid "Battery charging"
+#~ msgstr "బేటరీ చార్జి అవుతుంది"
+
+#~ msgid "Battery discharging"
+#~ msgstr "బేటరీ డిచ్చార్జి అవుతుంది"
+
+#~ msgid "Battery fully charged"
+#~ msgstr "బేటరీ పుర్తిగా చార్జి అయింది"
+
+#~ msgid "Share with:"
+#~ msgstr "తో పంచుకో:"
+
+#~ msgid "Private"
+#~ msgstr "సొంతం"
+
+#~ msgid "My Neighborhood"
+#~ msgstr "నా చుట్టుపక్కలవారు"
+
+#~ msgid "Undo"
+#~ msgstr "ఆఖరుది రద్దు చేయి"
+
+#~ msgid "Redo"
+#~ msgstr "తిరిగి చేయి"
+
+#~ msgid "Copy"
+#~ msgstr "నకలు"
+
+#~ msgid "Paste"
+#~ msgstr "అతికించు"
+
+#, python-format
+#~ msgid "%s Activity"
+#~ msgstr "%s వ్యాపకం"
+
+#~ msgid "Keep error"
+#~ msgstr "పొరబాటు జరిగింది"
+
+#~ msgid "Keep error: all changes will be lost"
+#~ msgstr "పొరబాటు జరిగింది : అన్ని మార్పులూ పోతాయి"
+
+#~ msgid "Don't stop"
+#~ msgstr "ఆపవద్దు"
+
+#~ msgid "Stop anyway"
+#~ msgstr "ఎలాగైనా ఆపువేయి"
+
+#~ msgid "Continue"
+#~ msgstr "కొనసాగించు"
+
+#~ msgid "OK"
+#~ msgstr "సరి"
+
+#, python-format
+#~ msgid "%d year"
+#~ msgstr "%d సంవత్సరము"
+
+#, python-format
+#~ msgid "%d years"
+#~ msgstr "%d సంవత్సరాలు"
+
+#, python-format
+#~ msgid "%d month"
+#~ msgstr "%d నెల"
+
+#, python-format
+#~ msgid "%d months"
+#~ msgstr "%d నెలలు"
+
+#, python-format
+#~ msgid "%d week"
+#~ msgstr "%d వారము"
+
+#, python-format
+#~ msgid "%d weeks"
+#~ msgstr "%d వారాలు"
+
+#, python-format
+#~ msgid "%d day"
+#~ msgstr "%d రోజు"
+
+#, python-format
+#~ msgid "%d days"
+#~ msgstr "%d రోజులు"
+
+#, python-format
+#~ msgid "%d hour"
+#~ msgstr "%d గంట"
+
+#, python-format
+#~ msgid "%d hours"
+#~ msgstr "%d గంటలు"
+
+#, python-format
+#~ msgid "%d minute"
+#~ msgstr "%d నిమిషము"
+
+#, python-format
+#~ msgid "%d minutes"
+#~ msgstr "%d నిమిషాలు"
+
+#, python-format
+#~ msgid "%d second"
+#~ msgstr "%d సెకను"
+
+#~ msgid " and "
+#~ msgstr " మరియు "
+
+#, python-format
+#~ msgid ", "
+#~ msgstr ", "
diff --git a/shell/po/th.po b/shell/po/th.po
new file mode 100644
index 0000000..a79e711
--- /dev/null
+++ b/shell/po/th.po
@@ -0,0 +1,420 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-01-18 18:19+0000\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.0.1\n"
+
+#: ../shell/intro/intro.py:67
+msgid "Name:"
+msgstr ""
+
+#: ../shell/intro/intro.py:96
+msgid "Click to change color:"
+msgstr ""
+
+#: ../shell/intro/intro.py:146
+msgid "Back"
+msgstr ""
+
+#: ../shell/intro/intro.py:160
+msgid "Done"
+msgstr ""
+
+#: ../shell/intro/intro.py:163
+msgid "Next"
+msgstr ""
+
+#: ../shell/view/BuddyMenu.py:59
+msgid "Remove friend"
+msgstr ""
+
+#: ../shell/view/BuddyMenu.py:62
+msgid "Make friend"
+msgstr ""
+
+#: ../shell/view/BuddyMenu.py:84
+#, python-format
+msgid "Invite to %s"
+msgstr ""
+
+#: ../shell/view/clipboardmenu.py:58
+msgid "Remove"
+msgstr ""
+
+#: ../shell/view/clipboardmenu.py:63
+msgid "Open"
+msgstr ""
+
+#. self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+#. TODO: Implement stopping downloads
+#. self._stop_item.connect('activate', self._stop_item_activate_cb)
+#. self.append_menu_item(self._stop_item)
+#: ../shell/view/clipboardmenu.py:73
+msgid "Add to journal"
+msgstr ""
+
+#: ../shell/view/clipboardmenu.py:213
+#, python-format
+msgid "Clipboard object: %s."
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:149
+msgid "Key Type:"
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:169
+msgid "Authentication Type:"
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:250
+msgid "Encryption Type:"
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:90
+msgid "Starting..."
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:104 ../shell/view/home/MeshBox.py:295
+msgid "Resume"
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:111
+#: ../lib/sugar/activity/activity.py:132
+msgid "Stop"
+msgstr ""
+
+#: ../shell/view/Shell.py:285
+msgid "Screenshot"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:159
+msgid "Reboot"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:164
+msgid "Shutdown"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:170
+msgid "Register"
+msgstr ""
+
+#. Only show disconnect when there's a mesh device, because mesh takes
+#. priority over the normal wireless device. NM doesn't have a "disconnect"
+#. method for a device either (for various reasons) so this doesn't
+#. have a good mapping
+#: ../shell/view/home/MeshBox.py:90 ../shell/view/home/MeshBox.py:197
+#: ../shell/view/devices/network/wireless.py:113
+#: ../shell/view/devices/network/mesh.py:83
+msgid "Disconnect..."
+msgstr ""
+
+#: ../shell/view/home/MeshBox.py:195 ../shell/view/devices/network/mesh.py:37
+#: ../shell/view/devices/network/mesh.py:62
+#: ../shell/view/devices/network/mesh.py:66
+msgid "Mesh Network"
+msgstr ""
+
+#: ../shell/view/home/MeshBox.py:300
+msgid "Join"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:38
+msgid "My Battery life"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:94
+msgid "Battery charging"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:96
+msgid "Battery discharging"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:98
+msgid "Battery fully charged"
+msgstr ""
+
+#: ../shell/view/devices/network/wireless.py:61
+msgid "Disconnected"
+msgstr ""
+
+#: ../shell/view/devices/network/wireless.py:131
+msgid "Channel"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:42
+msgid "Neighborhood"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:54
+msgid "Group"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:66
+msgid "Home"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:78
+msgid "Activity"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:115
+msgid "Share with:"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:117
+msgid "Private"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:118
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:126
+msgid "Keep"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:245
+msgid "Undo"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:250
+msgid "Redo"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:260
+msgid "Copy"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:265
+msgid "Paste"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:454
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:824
+msgid "Keep error"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:825
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:828
+msgid "Don't stop"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:831
+msgid "Stop anyway"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:164 ../lib/sugar/graphics/alert.py:206
+msgid "Cancel"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:168
+msgid "Ok"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:216
+msgid "Continue"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:244
+msgid "OK"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d year"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d years"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d month"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d months"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d week"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d weeks"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d day"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d days"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hour"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hours"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minute"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minutes"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d second"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d seconds"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:191
+msgid " and "
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:193
+msgid ", "
+msgstr ""
+
+#: ../shell/controlpanel/control.py:213
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:267
+msgid "Error in specified color modifiers."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:270
+msgid "Error in specified colors."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:307
+msgid "off"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:309
+msgid "on"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:310
+msgid "State is unknown."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:332
+msgid "Error in specified radio argument use on/off."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:336
+msgid "Permission denied. You need to be root to run this method."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:366
+msgid "Error in reading timezone"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:397
+#, python-format
+msgid "Error copying timezone (from %s): %s"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:402
+#, python-format
+msgid "Changing permission of timezone: %s"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:412
+msgid "Error timezone does not exist."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:417 ../shell/controlpanel/control.py:436
+#, python-format
+msgid "Could not access %s. Create standard settings."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:463
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:473
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:105
+msgid "Connected to a School Mesh Portal"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:107
+msgid "Looking for a School Mesh Portal..."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:110
+msgid "Connected to an XO Mesh Portal"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:112
+msgid "Looking for an XO Mesh Portal..."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:115
+msgid "Connected to a Simple Mesh"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:117
+msgid "Starting a Simple Mesh"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:124
+msgid "Unknown Mesh"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:175 ../shell/view/home/HomeBox.py:216
+msgid "About this XO"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:222
+msgid "Not available"
+msgstr ""
diff --git a/shell/po/tpi.po b/shell/po/tpi.po
new file mode 100644
index 0000000..e184567
--- /dev/null
+++ b/shell/po/tpi.po
@@ -0,0 +1,517 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-21 00:30-0400\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/intro/intro.py:65 ../src/controlpanel/view/aboutme.py:100
+msgid "Name:"
+msgstr ""
+
+#: ../src/intro/intro.py:94
+msgid "Click to change color:"
+msgstr ""
+
+#: ../src/intro/intro.py:145
+msgid "Back"
+msgstr ""
+
+#: ../src/intro/intro.py:159 ../src/controlpanel/toolbar.py:61
+msgid "Done"
+msgstr ""
+
+#: ../src/intro/intro.py:162
+msgid "Next"
+msgstr ""
+
+#: ../src/view/BuddyMenu.py:58
+msgid "Remove friend"
+msgstr ""
+
+#: ../src/view/BuddyMenu.py:61
+msgid "Make friend"
+msgstr ""
+
+#: ../src/view/BuddyMenu.py:91
+#, python-format
+msgid "Invite to %s"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:48
+msgid "Remove"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:53 ../src/view/clipboardmenu.py:79
+msgid "Open"
+msgstr ""
+
+#. self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+#. TODO: Implement stopping downloads
+#. self._stop_item.connect('activate', self._stop_item_activate_cb)
+#. self.append_menu_item(self._stop_item)
+#: ../src/view/clipboardmenu.py:63
+msgid "Keep"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:84
+msgid "Open with"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:212
+#, python-format
+msgid "Clipboard object: %s."
+msgstr ""
+
+#: ../src/hardware/keydialog.py:150
+msgid "Key Type:"
+msgstr ""
+
+#: ../src/hardware/keydialog.py:170
+msgid "Authentication Type:"
+msgstr ""
+
+#: ../src/hardware/keydialog.py:251
+msgid "Encryption Type:"
+msgstr ""
+
+#: ../src/view/Shell.py:262
+msgid "Screenshot"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:147
+msgid "List view"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:148
+msgid "<Ctrl>L"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:204
+msgid "Favorites view"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:205
+msgid "<Ctrl>R"
+msgstr ""
+
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/view/home/HomeBox.py:211
+msgid "Freeform"
+msgstr ""
+
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/view/home/HomeBox.py:218
+msgid "Ring"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:97
+msgid "Connect"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:106
+msgid "Disconnect"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:118
+msgid "Disconnecting..."
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:152
+msgid "Connecting..."
+msgstr ""
+
+#. TODO: show the channel number
+#: ../src/view/home/MeshBox.py:159
+msgid "Connected"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:211 ../src/view/devices/network/mesh.py:38
+#: ../src/view/devices/network/mesh.py:65
+#: ../src/view/devices/network/mesh.py:69
+msgid "Mesh Network"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:214 ../src/view/devices/network/wireless.py:116
+#: ../src/view/devices/network/mesh.py:86
+msgid "Disconnect..."
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:302 ../src/view/palettes.py:60
+msgid "Resume"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:307 ../src/view/frame/activitiestray.py:219
+msgid "Join"
+msgstr ""
+
+#: ../src/view/devices/battery.py:42
+msgid "My Battery"
+msgstr ""
+
+#: ../src/view/devices/battery.py:111
+msgid "Charging"
+msgstr ""
+
+#: ../src/view/devices/battery.py:114
+msgid "Very little power remaining"
+msgstr ""
+
+#: ../src/view/devices/battery.py:120
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr ""
+
+#: ../src/view/devices/battery.py:124
+msgid "Charged"
+msgstr ""
+
+#: ../src/view/devices/speaker.py:40
+msgid "My Speakers"
+msgstr ""
+
+#: ../src/view/devices/speaker.py:104
+msgid "Unmute"
+msgstr ""
+
+#: ../src/view/devices/speaker.py:107
+msgid "Mute"
+msgstr ""
+
+#: ../src/view/devices/network/wireless.py:64
+msgid "Disconnected"
+msgstr ""
+
+#: ../src/view/devices/network/wireless.py:134
+msgid "Channel"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:34
+msgid "Neighborhood"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:36
+msgid "Group"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:38
+msgid "Home"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:40
+msgid "Activity"
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:26
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:28
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:29
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:33
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" "
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:45
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr ""
+
+#: ../src/controlpanel/toolbar.py:115
+msgid "Cancel"
+msgstr ""
+
+#: ../src/controlpanel/toolbar.py:121
+msgid "Ok"
+msgstr ""
+
+#: ../src/controlpanel/sectionview.py:34 ../src/controlpanel/gui.py:250
+msgid "Changes require restart"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:249
+msgid "Warning"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:253
+msgid "Cancel changes"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:257
+msgid "Later"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:261
+msgid "Restart now"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:44
+msgid "You must enter a name."
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:67
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:70
+#, python-format
+msgid "stroke: %s"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:72
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:74
+#, python-format
+msgid "fill: %s"
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:85
+msgid "Error in specified color modifiers."
+msgstr ""
+
+#: ../src/controlpanel/model/aboutme.py:88
+msgid "Error in specified colors."
+msgstr ""
+
+#: ../src/controlpanel/model/aboutxo.py:24
+msgid "Not available"
+msgstr ""
+
+#: ../src/controlpanel/model/datetime.py:85
+msgid "Error timezone does not exist."
+msgstr ""
+
+#: ../src/controlpanel/model/frame.py:38 ../src/controlpanel/model/frame.py:60
+msgid "Value must be an integer."
+msgstr ""
+
+#: ../src/controlpanel/model/language.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr ""
+
+#: ../src/controlpanel/model/language.py:104
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr ""
+
+#: ../src/controlpanel/model/language.py:121
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr ""
+
+#: ../src/controlpanel/model/network.py:48
+msgid "You must enter a server."
+msgstr ""
+
+#: ../src/controlpanel/model/network.py:63
+msgid "State is unknown."
+msgstr ""
+
+#: ../src/controlpanel/model/network.py:83
+msgid "Error in specified radio argument use on/off."
+msgstr ""
+
+#: ../src/controlpanel/view/aboutme.py:32
+msgid "About Me"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutme.py:134
+msgid "Click to change your color:"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutxo.py:26
+msgid "About my XO"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutxo.py:47
+msgid "Identity"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutxo.py:56
+msgid "Serial Number:"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutxo.py:79
+msgid "Software"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutxo.py:88
+msgid "Build:"
+msgstr ""
+
+#: ../src/controlpanel/view/aboutxo.py:103
+msgid "Firmware:"
+msgstr ""
+
+#: ../src/controlpanel/view/datetime.py:29
+msgid "Date & Time"
+msgstr ""
+
+#: ../src/controlpanel/view/datetime.py:72
+msgid "Timezone"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:28
+msgid "Frame"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:30
+msgid "never"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:31
+msgid "instantaneous"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:32
+#, python-format
+msgid "%s seconds"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:56
+msgid "Activation Delay"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:80
+msgid "Corner"
+msgstr ""
+
+#: ../src/controlpanel/view/frame.py:115
+msgid "Edge"
+msgstr ""
+
+#: ../src/controlpanel/view/language.py:29
+#: ../src/controlpanel/view/language.py:74
+msgid "Language"
+msgstr ""
+
+#: ../src/controlpanel/view/network.py:28
+msgid "Network"
+msgstr ""
+
+#: ../src/controlpanel/view/network.py:53
+msgid "Wireless"
+msgstr ""
+
+#: ../src/controlpanel/view/network.py:61
+msgid "Radio:"
+msgstr ""
+
+#: ../src/controlpanel/view/network.py:94
+msgid "Mesh"
+msgstr ""
+
+#: ../src/controlpanel/view/network.py:103
+msgid "Server:"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:108
+msgid "Connected to a School Mesh Portal"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:110
+msgid "Looking for a School Mesh Portal..."
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:113
+msgid "Connected to an XO Mesh Portal"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:115
+msgid "Looking for an XO Mesh Portal..."
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:118
+msgid "Connected to a Simple Mesh"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:120
+msgid "Starting a Simple Mesh"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:127
+msgid "Unknown Mesh"
+msgstr ""
+
+#: ../src/view/frame/activitiestray.py:224
+msgid "Decline"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:351
+msgid "Control Panel"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:362
+msgid "Restart"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:367
+msgid "Shutdown"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:373
+msgid "Register"
+msgstr ""
+
+#: ../src/view/palettes.py:41
+msgid "Starting..."
+msgstr ""
+
+#: ../src/view/palettes.py:71
+msgid "Stop"
+msgstr ""
+
+#: ../src/view/palettes.py:96
+msgid "Start"
+msgstr ""
+
+#: ../src/view/palettes.py:119
+msgid "Remove favorite"
+msgstr ""
+
+#: ../src/view/palettes.py:123
+msgid "Make favorite"
+msgstr ""
+
+#: ../src/view/palettes.py:169
+msgid "Show contents"
+msgstr ""
+
+#: ../src/view/palettes.py:193
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr ""
diff --git a/shell/po/tr.po b/shell/po/tr.po
new file mode 100644
index 0000000..696a3c0
--- /dev/null
+++ b/shell/po/tr.po
@@ -0,0 +1,839 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-09-25 00:30-0400\n"
+"PO-Revision-Date: 2008-09-26 09:38-0400\n"
+"Last-Translator: abdullah kocabas <abdullah.kocabas@abcdizustu.com>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Pootle 1.1.0rc2\n"
+
+#: ../src/intro/window.py:93 ../src/controlpanel/aboutme/view.py:100
+msgid "Name:"
+msgstr "İsim:"
+
+#: ../src/intro/window.py:125
+msgid "Click to change color:"
+msgstr "Renk değiştirmek için tıkla:"
+
+#: ../src/intro/window.py:175 ../src/journal/detailview.py:119
+msgid "Back"
+msgstr "Geri"
+
+#: ../src/intro/window.py:189 ../src/controlpanel/toolbar.py:61
+msgid "Done"
+msgstr "Kapat"
+
+#: ../src/intro/window.py:192
+msgid "Next"
+msgstr "İleri"
+
+#: ../src/view/BuddyMenu.py:60
+msgid "Remove friend"
+msgstr "Arkadaşı Kaldır"
+
+#: ../src/view/BuddyMenu.py:63
+msgid "Make friend"
+msgstr "Arkadaş Ekle"
+
+#: ../src/view/BuddyMenu.py:92
+#, python-format
+msgid "Invite to %s"
+msgstr "Davet et %s"
+
+#: ../src/view/clipboardmenu.py:51
+msgid "Remove"
+msgstr "Kaldır"
+
+#: ../src/view/clipboardmenu.py:56 ../src/view/clipboardmenu.py:78
+msgid "Open"
+msgstr "Aç"
+
+#: ../src/view/clipboardmenu.py:61 ../src/view/home/HomeBox.py:84
+msgid "Keep"
+msgstr "Kaydet"
+
+#: ../src/view/clipboardmenu.py:83
+msgid "Open with"
+msgstr "Birlikte Aç"
+
+#: ../src/view/clipboardmenu.py:228
+#, python-format
+msgid "%s clipping"
+msgstr "%s Kırp"
+
+#: ../src/hardware/keydialog.py:150
+msgid "Key Type:"
+msgstr "Tuş Çeşidi:"
+
+#: ../src/hardware/keydialog.py:170
+msgid "Authentication Type:"
+msgstr "Kimlik Denetleme Çeşidi:"
+
+#: ../src/hardware/keydialog.py:251
+msgid "Encryption Type:"
+msgstr "Şifreleme Çeşidi:"
+
+#: ../src/hardware/schoolserver.py:17
+msgid "Cannot obtain data needed for registration."
+msgstr "Kayıt İçin Gerekli Bilgileri Sağlayamıyor"
+
+#: ../src/hardware/schoolserver.py:31
+msgid "Cannot connect to the server."
+msgstr "Sunucuya Bağlanamıyor"
+
+#: ../src/hardware/schoolserver.py:36
+msgid "The server could not complete the request."
+msgstr "Sunucu İsteği Tamamlayamıyor"
+
+#: ../src/view/Shell.py:251
+msgid "Screenshot"
+msgstr "Ekran Resmi"
+
+#: ../src/view/home/HomeBox.py:78
+msgid "Confirm erase"
+msgstr "Silmeyi Onayla"
+
+#: ../src/view/home/HomeBox.py:80
+#, python-format
+msgid "Confirm erase: Do you want to permanently erase %s?"
+msgstr "Silme Onayı: %s i tamamen silmek mi istiyorsunuz?"
+
+#: ../src/view/home/HomeBox.py:87 ../src/view/palettes.py:120
+#: ../src/journal/journaltoolbox.py:335 ../src/journal/palettes.py:75
+msgid "Erase"
+msgstr "Silme"
+
+#: ../src/view/home/HomeBox.py:117
+msgid "Software Update"
+msgstr "Yazılım Güncelleme"
+
+#: ../src/view/home/HomeBox.py:118
+msgid "Update your activities to ensure compatibility with your new software"
+msgstr "Aktivitelerinizi yeni yazılıma uygun hale getirmek için güncelleyiniz"
+
+#: ../src/view/home/HomeBox.py:122 ../src/controlpanel/toolbar.py:115
+msgid "Cancel"
+msgstr "İptal"
+
+#: ../src/view/home/HomeBox.py:124 ../src/controlpanel/gui.py:273
+msgid "Later"
+msgstr "Daha Sonra"
+
+#: ../src/view/home/HomeBox.py:127
+msgid "Check now"
+msgstr "Şimdi Kontrol Et"
+
+#: ../src/view/home/HomeBox.py:261
+msgid "List view"
+msgstr "Liste Görünümü"
+
+#: ../src/view/home/HomeBox.py:262
+msgid "<Ctrl>2"
+msgstr "<Ctrl>2"
+
+#: ../src/view/home/HomeBox.py:320
+msgid "Favorites view"
+msgstr "Favoriler Görünümü"
+
+#: ../src/view/home/HomeBox.py:321
+msgid "<Ctrl>1"
+msgstr "<Ctrl>1"
+
+#: ../src/view/home/MeshBox.py:97
+msgid "Connect"
+msgstr "Bağlan"
+
+#: ../src/view/home/MeshBox.py:106
+msgid "Disconnect"
+msgstr "Bağlantıyı kes"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#: ../src/view/home/MeshBox.py:118
+msgid "Disconnecting..."
+msgstr "Bağlantı kesiliyor..."
+
+#: ../src/view/home/MeshBox.py:159
+msgid "Connecting..."
+msgstr "Bağlanıyor..."
+
+# TODO: show the channel number
+#. TODO: show the channel number
+#: ../src/view/home/MeshBox.py:166
+msgid "Connected"
+msgstr "Bağlı"
+
+#: ../src/view/home/MeshBox.py:218 ../src/view/devices/network/mesh.py:41
+#: ../src/view/devices/network/mesh.py:68
+#: ../src/view/devices/network/mesh.py:72
+msgid "Mesh Network"
+msgstr "Mesh Ağı"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#: ../src/view/home/MeshBox.py:221 ../src/view/devices/network/wireless.py:125
+#: ../src/view/devices/network/mesh.py:89
+msgid "Disconnect..."
+msgstr "Bağlantıyı kes..."
+
+#. TRANS: Action label for resuming an activity.
+#: ../src/view/home/MeshBox.py:309 ../src/view/palettes.py:61
+#: ../src/journal/journaltoolbox.py:399 ../src/journal/palettes.py:57
+msgid "Resume"
+msgstr "Devam Et"
+
+#: ../src/view/home/MeshBox.py:314 ../src/view/frame/activitiestray.py:206
+msgid "Join"
+msgstr "Katıl"
+
+#: ../src/view/devices/battery.py:45
+msgid "My Battery"
+msgstr "Pil Durumu"
+
+#: ../src/view/devices/battery.py:114
+msgid "Charging"
+msgstr "Şarj"
+
+#: ../src/view/devices/battery.py:117
+msgid "Very little power remaining"
+msgstr "Çok az şarj kaldı"
+
+#: ../src/view/devices/battery.py:123
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr "%(hour)d:%(min).2d kaldı"
+
+#: ../src/view/devices/battery.py:127
+msgid "Charged"
+msgstr "Şarj edildi"
+
+#: ../src/view/devices/speaker.py:44
+msgid "My Speakers"
+msgstr "Hoparlör"
+
+#: ../src/view/devices/speaker.py:125
+msgid "Unmute"
+msgstr "Sesi açık"
+
+#: ../src/view/devices/speaker.py:128
+msgid "Mute"
+msgstr "Sessiz"
+
+#: ../src/view/devices/network/wireless.py:67
+msgid "Disconnected"
+msgstr "Bağlantı kesildi"
+
+#: ../src/view/devices/network/wireless.py:143
+msgid "Channel"
+msgstr "Kanal"
+
+#: ../src/view/frame/zoomtoolbar.py:37
+msgid "Neighborhood"
+msgstr "Komşular"
+
+#: ../src/view/frame/zoomtoolbar.py:39
+msgid "Group"
+msgstr "Grup"
+
+#: ../src/view/frame/zoomtoolbar.py:41
+msgid "Home"
+msgstr "Ana ekran"
+
+#: ../src/view/frame/zoomtoolbar.py:43
+msgid "Activity"
+msgstr "Aktivite"
+
+#: ../src/controlpanel/cmd.py:26
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+"sugar-kontrol-paneli: UYARI, aynı isimle birden fazla seçenek buldu: %s "
+"birim: %r"
+
+#: ../src/controlpanel/cmd.py:28
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr "sugar-kontrol-panel: anahtar=%s geçerli bir seçenek değil"
+
+#: ../src/controlpanel/cmd.py:29
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr "sugar-kontrol-panel: %s"
+
+#. TRANS: Translators, there's a empty line at the end of this string,
+#. which must appear in the translated string (msgstr) as well.
+#: ../src/controlpanel/cmd.py:35
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" -c key clear the current value for the key \n"
+" "
+msgstr ""
+"Kullanım: sugar-kontrol-paneli [ seçenek ] anahtar [ args ... ] \n"
+"Sugar ortamı için kontrol \n"
+"Seçenekler:\n"
+"-h Bu yardım mesajını göster ve çık \n"
+"-l Tüm uygun seçenekleri listele \n"
+"-h anahtarı Bu anahtar hakkındaki bilgileri göster \n"
+"-g anahtarı Bu anahtarın son değerini al \n"
+"-s anahtarı Bu anahtarın son değerini kur \n"
+"\t"
+
+#: ../src/controlpanel/cmd.py:48
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr "Değişikliklerinizin etkinleşmesi için bilgisayarı tekrar başlatmanız "
+"gerekiyor\n"
+
+#: ../src/controlpanel/toolbar.py:121 ../src/view/home/favoritesview.py:305
+msgid "Ok"
+msgstr "Tamam"
+
+#: ../src/controlpanel/sectionview.py:42 ../src/controlpanel/gui.py:265
+msgid "Changes require restart"
+msgstr "Değişiklikler bilgisayarın tekrar başlatılmasını gerektiriyor"
+
+#: ../src/controlpanel/gui.py:264
+msgid "Warning"
+msgstr "Uyarı"
+
+#: ../src/controlpanel/gui.py:268
+msgid "Cancel changes"
+msgstr "Değişiklikleri İptal Et"
+
+#: ../src/controlpanel/gui.py:277
+msgid "Restart now"
+msgstr "Şimdi Tekrar Başlat"
+
+#: ../src/controlpanel/aboutme/model.py:44
+msgid "You must enter a name."
+msgstr "Bir isim girmeniz gerekiyor"
+
+#: ../src/controlpanel/aboutme/model.py:69
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr "fırça darbesi:_ renk=%s renk=%s"
+
+#: ../src/controlpanel/aboutme/model.py:72
+#, python-format
+msgid "stroke: %s"
+msgstr "fırça darbesi:_ %s"
+
+#: ../src/controlpanel/aboutme/model.py:74
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr "doldurma:_ renk=%s renk=%s"
+
+#: ../src/controlpanel/aboutme/model.py:76
+#, python-format
+msgid "fill: %s"
+msgstr "doldurma:_ %s"
+
+#: ../src/controlpanel/aboutme/model.py:87
+msgid "Error in specified color modifiers."
+msgstr "Belirtilen renk değiştiricilerinde hata"
+
+#: ../src/controlpanel/aboutme/model.py:90
+msgid "Error in specified colors."
+msgstr "Belirtilen renklerde hata"
+
+#: ../src/controlpanel/aboutme/view.py:32
+#: ../src/controlpanel/aboutme/__init__.py:22
+msgid "About Me"
+msgstr "Benim Hakkımda"
+
+#: ../src/controlpanel/aboutme/view.py:134
+msgid "Click to change your color:"
+msgstr "Renk değiştirmek için tıklayınız:"
+
+#: ../src/controlpanel/aboutxo/model.py:24
+msgid "Not available"
+msgstr "Mevcut değil"
+
+#: ../src/controlpanel/aboutxo/view.py:55
+msgid "Identity"
+msgstr "Kimlik"
+
+#: ../src/controlpanel/aboutxo/view.py:64
+msgid "Serial Number:"
+msgstr "Seri Numarası:"
+
+#: ../src/controlpanel/aboutxo/view.py:87
+msgid "Software"
+msgstr "Yazılım"
+
+#: ../src/controlpanel/aboutxo/view.py:96
+msgid "Build:"
+msgstr "Yapı:"
+
+#: ../src/controlpanel/aboutxo/view.py:111
+msgid "Sugar:"
+msgstr "Sugar:"
+
+#: ../src/controlpanel/aboutxo/view.py:126
+msgid "Firmware:"
+msgstr "Aygıt Yazılımı:"
+
+#: ../src/controlpanel/aboutxo/view.py:148
+msgid "Copyright and License"
+msgstr "Telif Hakkı ve Lisans"
+
+#: ../src/controlpanel/aboutxo/view.py:156
+msgid ""
+"© 2008 One Laptop per Child Association Inc; Red Hat Inc; and Contributors."
+msgstr ""
+"© 2008 One Laptop per Child Association Inc; Red Hat Inc; ve Destekçiler"
+
+#: ../src/controlpanel/aboutxo/view.py:163
+msgid ""
+"Sugar is the graphical user interface that you are looking at. Sugar is free "
+"software, covered by the GNU General Public License, and you are welcome to "
+"change it and/or distribute copies of it under certain conditions described "
+"therein."
+msgstr ""
+"Sugar gördüğünüz grafiksel kullanıcı arayüzüdür. Sugar, GNU General Public "
+"Lisansı'na sahip, ücretsiz bir yazılımdır. İstediğiniz takdirde yazılımı "
+"değiştirebilir ve/veya burada açıklanan belli koşullar altında kopyasını "
+"dağıtabilirsiniz."
+
+#: ../src/controlpanel/aboutxo/view.py:175
+msgid "Full license:"
+msgstr "Tam Lisans:"
+
+#: ../src/controlpanel/aboutxo/__init__.py:21
+msgid "About my XO"
+msgstr "XO Bilgisayarım Hakkında"
+
+#: ../src/controlpanel/datetime/model.py:89
+msgid "Error timezone does not exist."
+msgstr "Hatalı zaman dilimi yok"
+
+#: ../src/controlpanel/datetime/view.py:68
+msgid "Timezone"
+msgstr "Saat Dilimi"
+
+#: ../src/controlpanel/datetime/__init__.py:21
+msgid "Date & Time"
+msgstr "Tarih ve Zaman"
+
+#: ../src/controlpanel/frame/model.py:38 ../src/controlpanel/frame/model.py:60
+msgid "Value must be an integer."
+msgstr "Değer bir tamsayı olmalı"
+
+#: ../src/controlpanel/frame/view.py:26
+msgid "never"
+msgstr "asla"
+
+#: ../src/controlpanel/frame/view.py:27
+msgid "instantaneous"
+msgstr "anlık"
+
+#: ../src/controlpanel/frame/view.py:28
+#, python-format
+msgid "%s seconds"
+msgstr "%s saniyeler"
+
+#: ../src/controlpanel/frame/view.py:52
+msgid "Activation Delay"
+msgstr "Aktivasyon Gecikmesi"
+
+#: ../src/controlpanel/frame/view.py:76
+msgid "Corner"
+msgstr "Köşe"
+
+#: ../src/controlpanel/frame/view.py:111
+msgid "Edge"
+msgstr "Kenar"
+
+#: ../src/controlpanel/frame/__init__.py:21
+msgid "Frame"
+msgstr "Çerçeve"
+
+#: ../src/controlpanel/language/model.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr "~/.i18n e girilemiyor. Standart ayarları oluşturunuz"
+
+#: ../src/controlpanel/language/model.py:114
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr "kod=%s için dil tanımlanamadı"
+
+#: ../src/controlpanel/language/model.py:131
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr "Üzgünüm, %s konuşmuyorum"
+
+#: ../src/controlpanel/language/view.py:70
+#: ../src/controlpanel/language/__init__.py:21
+msgid "Language"
+msgstr "Dil"
+
+#: ../src/controlpanel/network/model.py:62
+msgid "State is unknown."
+msgstr "Durum bilinmiyor"
+
+#: ../src/controlpanel/network/model.py:82
+msgid "Error in specified radio argument use on/off."
+msgstr "Belirtilen bağlantı noktasında hata vardır, açma kapama yapınız"
+
+#: ../src/controlpanel/network/view.py:28
+#: ../src/controlpanel/network/__init__.py:21
+msgid "Network"
+msgstr "Ağ"
+
+#: ../src/controlpanel/network/view.py:54
+msgid "Wireless"
+msgstr "Kablosuz Bağlantı"
+
+#: ../src/controlpanel/network/view.py:62
+msgid "Turn of the wireless radio to save battery life"
+msgstr "Pil ömrünü arttırmak için kablosuz bağlantı özelliğini kapatınız."
+
+#: ../src/controlpanel/network/view.py:75
+msgid "Radio"
+msgstr "Bağlantı "
+
+#: ../src/controlpanel/network/view.py:91
+msgid "Discard network history if you have trouble connecting to the network"
+msgstr "Ağa bağlanmada sorun yaşıyorsanız ağ geçmişini silebilirsiniz."
+
+#: ../src/controlpanel/network/view.py:100
+msgid "Discard network history"
+msgstr "Ağ geçmişini siliniz."
+
+#: ../src/controlpanel/network/view.py:113
+msgid "Mesh"
+msgstr "Mesh"
+
+#: ../src/controlpanel/network/view.py:122
+msgid "Server:"
+msgstr "Sunucu:"
+
+#: ../src/controlpanel/power/model.py:55
+msgid "Error in automatic pm argument, use on/off."
+msgstr "Otomatik pm değişkeninde hata vardır, açma kapama yapınız"
+
+#: ../src/controlpanel/power/model.py:84
+msgid "Error in extreme pm argument, use on/off."
+msgstr "En uçtaki pm değişkeninde hata vardır, açma kapama yapınız"
+
+#: ../src/controlpanel/power/view.py:47
+msgid "Power management"
+msgstr "Güç Kontrolü"
+
+#: ../src/controlpanel/power/view.py:57
+msgid "Automatic power management (increases battery life)"
+msgstr "Otomatik güç kontrolü (pil ömrünü uzatır)"
+
+#: ../src/controlpanel/power/view.py:85
+msgid ""
+"Extreme power management (disableswireless radio, increases battery life)"
+msgstr ""
+"Aşırı güç kontrolü (kablosuz bağlantı özelliğini etkisiz kılar, şarj ömrünü "
+"uzatır)"
+
+#: ../src/controlpanel/power/__init__.py:21
+msgid "Power"
+msgstr "Güç"
+
+#: ../src/view/devices/network/mesh.py:111
+msgid "Connected to a School Mesh Portal"
+msgstr "Okul Mesh Ağı Portalı'na bağlandı."
+
+#: ../src/view/devices/network/mesh.py:113
+msgid "Looking for a School Mesh Portal..."
+msgstr "Okul Mesh Ağı Portalı arıyor..."
+
+#: ../src/view/devices/network/mesh.py:116
+msgid "Connected to an XO Mesh Portal"
+msgstr "XO Mesh Ağı Portalı'na bağlandı."
+
+#: ../src/view/devices/network/mesh.py:118
+msgid "Looking for an XO Mesh Portal..."
+msgstr "XO Mesh Ağ Portalı arıyor..."
+
+#: ../src/view/devices/network/mesh.py:121
+msgid "Connected to a Simple Mesh"
+msgstr "Sadece Mesh Ağına bağlandı."
+
+#: ../src/view/devices/network/mesh.py:123
+msgid "Starting a Simple Mesh"
+msgstr "Sadece Mesh Ağı başlatıyor."
+
+#: ../src/view/devices/network/mesh.py:130
+msgid "Unknown Mesh"
+msgstr "Bilinmeyen Mesh Ağı"
+
+#: ../src/view/frame/activitiestray.py:211
+msgid "Decline"
+msgstr "Azalmak"
+
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/view/home/favoriteslayout.py:107
+msgid "Freeform"
+msgstr "dağınık görünüm"
+
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/view/home/favoriteslayout.py:189
+msgid "Ring"
+msgstr "çember görünümü"
+
+#. TRANS: label for the spiral layout in the favorites view
+#: ../src/view/home/favoriteslayout.py:334
+msgid "Spiral"
+msgstr "sarmal"
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/view/home/favoriteslayout.py:401
+msgid "Box"
+msgstr "kutu"
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/view/home/favoriteslayout.py:442
+msgid "Triangle"
+msgstr "üçgen"
+
+#: ../src/view/home/favoritesview.py:295
+msgid "Registration Failed"
+msgstr "Kayıt Başarısız"
+
+#: ../src/view/home/favoritesview.py:296
+#, python-format
+msgid "%s"
+msgstr "%s"
+
+#: ../src/view/home/favoritesview.py:298
+msgid "Registration Successful"
+msgstr "Kayıt Başarılı"
+
+#: ../src/view/home/favoritesview.py:299
+msgid "You are now registered with your school server."
+msgstr "Şu an okul sunucunuza kayıt oldunuz"
+
+#: ../src/view/home/favoritesview.py:420
+msgid "Settings"
+msgstr "Ayarlar"
+
+#: ../src/view/home/favoritesview.py:425
+msgid "Restart"
+msgstr "Yeniden Başlat"
+
+#: ../src/view/home/favoritesview.py:430
+msgid "Shutdown"
+msgstr "Kapat"
+
+#: ../src/view/home/favoritesview.py:436
+msgid "Register"
+msgstr "Kaydol"
+
+#: ../src/view/palettes.py:42
+msgid "Starting..."
+msgstr "Başlıyor..."
+
+#: ../src/view/palettes.py:72
+msgid "Stop"
+msgstr "Durdur"
+
+#. TRANS: Action label for starting an entry.
+#: ../src/view/palettes.py:104 ../src/journal/journaltoolbox.py:402
+#: ../src/journal/palettes.py:59
+msgid "Start"
+msgstr "Başlat"
+
+#: ../src/view/palettes.py:138
+msgid "Remove favorite"
+msgstr "Favorilerimden kaldır"
+
+#: ../src/view/palettes.py:142
+msgid "Make favorite"
+msgstr "Favorilerime ekle"
+
+#: ../src/view/palettes.py:191
+msgid "Show contents"
+msgstr "İçeriklerini göster"
+
+#: ../src/view/palettes.py:215
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr "%(free_space)d MB boş"
+
+#: ../src/journal/journaltoolbox.py:62
+msgid "Search"
+msgstr "Arama"
+
+#: ../src/journal/journaltoolbox.py:119
+msgid "Anytime"
+msgstr "Herhangi bir zaman"
+
+#: ../src/journal/journaltoolbox.py:121
+msgid "Today"
+msgstr "Bugün"
+
+#: ../src/journal/journaltoolbox.py:123
+msgid "Since yesterday"
+msgstr "Dünden itibaren"
+
+#. TRANS: Filter entries modified during the last 7 days.
+#: ../src/journal/journaltoolbox.py:125
+msgid "Past week"
+msgstr "Geçen hafta"
+
+#. TRANS: Filter entries modified during the last 30 days.
+#: ../src/journal/journaltoolbox.py:127
+msgid "Past month"
+msgstr "Geçen ay"
+
+#. TRANS: Filter entries modified during the last 356 days.
+#: ../src/journal/journaltoolbox.py:129
+msgid "Past year"
+msgstr "Geçen yıl"
+
+#: ../src/journal/journaltoolbox.py:136
+msgid "Anyone"
+msgstr "Herhangi biri"
+
+#: ../src/journal/journaltoolbox.py:138
+msgid "My friends"
+msgstr "Arkadaşlarım"
+
+#: ../src/journal/journaltoolbox.py:139
+msgid "My class"
+msgstr "Sınıfım"
+
+#. TRANS: Item in a combo box that filters by entry type.
+#: ../src/journal/journaltoolbox.py:255
+msgid "Anything"
+msgstr "Herhangi bir şey"
+
+#. TODO: Add "Start with" menu item
+#: ../src/journal/journaltoolbox.py:325 ../src/journal/palettes.py:67
+msgid "Copy"
+msgstr "Kopyala"
+
+#: ../src/journal/collapsedentry.py:248 ../src/journal/expandedentry.py:176
+#: ../src/journal/palettes.py:51
+msgid "Untitled"
+msgstr "İsimsiz"
+
+#: ../src/journal/journalactivity.py:119 ../src/journal/volumesmanager.py:57
+msgid "Journal"
+msgstr "Günlük"
+
+#: ../src/journal/expandedentry.py:222
+msgid "No preview"
+msgstr "Ön izleme yok"
+
+#: ../src/journal/expandedentry.py:241
+msgid "Participants:"
+msgstr "Katılımcılar:"
+
+#: ../src/journal/expandedentry.py:266
+msgid "Description:"
+msgstr "Tanım:"
+
+#: ../src/journal/expandedentry.py:292
+msgid "Tags:"
+msgstr "Etiketler:"
+
+#: ../src/journal/objectchooser.py:134
+msgid "Choose an object"
+msgstr "Bir nesne seçiniz"
+
+#: ../src/journal/objectchooser.py:139
+msgid "Close"
+msgstr "Kapat"
+
+#: ../src/journal/volumestoolbar.py:93
+msgid "Unmount"
+msgstr "Bağlantıyı kaldır"
+
+#: ../src/journal/misc.py:95
+msgid "No date"
+msgstr "Tarih yok"
+
+#: ../src/journal/listview.py:39
+msgid "Your Journal is empty"
+msgstr "Günlüğünüz boş"
+
+#: ../src/journal/listview.py:40
+msgid "No matching entries "
+msgstr "Eşleşen bir giriş yok_"
+
+#: ../src/journal/modalalert.py:59
+msgid "Your Journal is full"
+msgstr "Günlüğünüz dolu"
+
+#: ../src/journal/modalalert.py:63
+msgid "Please delete some old Journal entries to make space for new ones."
+msgstr ""
+"Yeni girişler yapabilmek için lütfen günlükteki eski kayıtlardan bir kısmını "
+"siliniz"
+
+#: ../src/journal/modalalert.py:75
+msgid "Show Journal"
+msgstr "Günlüğü göster"
+
+#~ msgid "Add to journal"
+#~ msgstr "Günlüğe ekleyiniz."
+
+#, python-format
+#~ msgid "Clipboard object: %s."
+#~ msgstr "Pano nesnesi: %s"
+
+#~ msgid "Reboot"
+#~ msgstr "Yeniden başlatınız."
+
+#~ msgid "About this XO"
+#~ msgstr "Bu XO hakkında"
+
+#~ msgid "My Battery life"
+#~ msgstr "Pil ömrüm"
+
+#~ msgid "Battery charging"
+#~ msgstr "Pil doluyor."
+
+#~ msgid "Battery discharging"
+#~ msgstr "Pil boşalıyor."
+
+#~ msgid "Battery fully charged"
+#~ msgstr "Pil tam dolu"
+
+#~ msgid "off"
+#~ msgstr "kapalı"
+
+#~ msgid "on"
+#~ msgstr "açık"
+
+#~ msgid "Permission denied. You need to be root to run this method."
+#~ msgstr ""
+#~ "İzin reddedildi. Bu metodu çalıştırmak için merkezden olmanız gerekmektedir."
+
+#~ msgid "Error in reading timezone"
+#~ msgstr "Saat diliminde hata vardır."
+
+#, python-format
+#~ msgid "Error copying timezone (from %s): %s"
+#~ msgstr "Saat dilimini %s'ten kopyalarken hata oluşmuştur: %s"
+
+#, python-format
+#~ msgid "Changing permission of timezone: %s"
+#~ msgstr "Saat dilimini değiştirme izni: %s"
diff --git a/shell/po/tvl.po b/shell/po/tvl.po
new file mode 100644
index 0000000..eccea53
--- /dev/null
+++ b/shell/po/tvl.po
@@ -0,0 +1,1188 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-09-16 00:31-0400\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.3.0\n"
+
+#: ../extensions/cpsection/aboutme/__init__.py:24
+msgid "About Me"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:43
+msgid "You must enter a name."
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:68
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:71
+#, python-format
+msgid "stroke: %s"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:73
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:75
+#, python-format
+msgid "fill: %s"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:86
+msgid "Error in specified color modifiers."
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:89
+msgid "Error in specified colors."
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/view.py:94 ../src/jarabe/intro/window.py:92
+msgid "Name:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/view.py:128
+msgid "Click to change your color:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/__init__.py:21
+msgid "About my Computer"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/model.py:28
+msgid "Not available"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:60
+msgid "Identity"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:69
+msgid "Serial Number:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:91
+msgid "Software"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:100
+msgid "Build:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:115
+msgid "Sugar:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:131
+msgid "Firmware:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:146
+msgid "Wireless Firmware:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:169
+msgid "Copyright and License"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:184
+msgid ""
+"Sugar is the graphical user interface that you are looking at. Sugar is free "
+"software, covered by the GNU General Public License, and you are welcome to "
+"change it and/or distribute copies of it under certain conditions described "
+"therein."
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:196
+msgid "Full license:"
+msgstr ""
+
+#: ../extensions/cpsection/datetime/__init__.py:21
+msgid "Date & Time"
+msgstr ""
+
+#: ../extensions/cpsection/datetime/model.py:87
+msgid "Error timezone does not exist."
+msgstr ""
+
+#: ../extensions/cpsection/datetime/view.py:68 ../data/sugar.schemas.in.h:27
+msgid "Timezone"
+msgstr ""
+
+#: ../extensions/cpsection/frame/__init__.py:21
+msgid "Frame"
+msgstr ""
+
+#: ../extensions/cpsection/frame/model.py:38
+#: ../extensions/cpsection/frame/model.py:60
+msgid "Value must be an integer."
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:26
+msgid "never"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:27
+msgid "instantaneous"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:28
+#, python-format
+msgid "%s seconds"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:52
+msgid "Activation Delay"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:76
+msgid "Corner"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:111
+msgid "Edge"
+msgstr ""
+
+#: ../extensions/cpsection/keyboard/__init__.py:21
+#: ../extensions/cpsection/keyboard/view.py:31
+msgid "Keyboard"
+msgstr ""
+
+#: ../extensions/cpsection/keyboard/view.py:187
+msgid "Keyboard Model"
+msgstr ""
+
+#: ../extensions/cpsection/keyboard/view.py:243
+msgid "Key(s) to change layout"
+msgstr ""
+
+#: ../extensions/cpsection/keyboard/view.py:311
+msgid "Keyboard Layout(s)"
+msgstr ""
+
+#: ../extensions/cpsection/language/__init__.py:21
+#: ../extensions/cpsection/language/view.py:33
+msgid "Language"
+msgstr ""
+
+#: ../extensions/cpsection/language/model.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr ""
+
+#: ../extensions/cpsection/language/model.py:124
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr ""
+
+#: ../extensions/cpsection/language/model.py:144
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr ""
+
+#: ../extensions/cpsection/language/view.py:56
+msgid ""
+"Add languages in the order you prefer. If a translation is not available, "
+"the next in the list will be used."
+msgstr ""
+
+#: ../extensions/cpsection/network/__init__.py:21
+#: ../extensions/cpsection/network/view.py:28
+msgid "Network"
+msgstr ""
+
+#: ../extensions/cpsection/network/model.py:79
+msgid "State is unknown."
+msgstr ""
+
+#: ../extensions/cpsection/network/model.py:105
+msgid "Error in specified radio argument use on/off."
+msgstr ""
+
+#: ../extensions/cpsection/network/model.py:137
+msgid "Error in specified argument use 0/1."
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:59
+msgid "Wireless"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:67
+msgid "Turn off the wireless radio to save battery life"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:80
+msgid "Radio"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:96
+msgid "Discard network history if you have trouble connecting to the network"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:105
+msgid "Discard network history"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:118
+msgid "Collaboration"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:126
+msgid ""
+"The server is the equivalent of what room you are in; people on the same "
+"server will be able to see each other, even when they aren't on the same "
+"network."
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:136
+msgid "Server:"
+msgstr ""
+
+#: ../extensions/cpsection/power/__init__.py:21
+msgid "Power"
+msgstr ""
+
+#: ../extensions/cpsection/power/model.py:54
+msgid "Error in automatic pm argument, use on/off."
+msgstr ""
+
+#: ../extensions/cpsection/power/model.py:81
+msgid "Error in extreme pm argument, use on/off."
+msgstr ""
+
+#: ../extensions/cpsection/power/view.py:47
+msgid "Power management"
+msgstr ""
+
+#: ../extensions/cpsection/power/view.py:57
+msgid "Automatic power management (increases battery life)"
+msgstr ""
+
+#: ../extensions/cpsection/power/view.py:85
+msgid ""
+"Extreme power management (disableswireless radio, increases battery life)"
+msgstr ""
+
+#: ../extensions/cpsection/updater/__init__.py:21
+msgid "Software update"
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:62
+msgid ""
+"Software updates correct errors, eliminate security vulnerabilities, and "
+"provide new features."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:122
+#, python-format
+msgid "Checking %s..."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:124
+#, python-format
+msgid "Downloading %s..."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:126
+#, python-format
+msgid "Updating %s..."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:135
+msgid "Your software is up-to-date"
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:137
+#, python-format
+msgid "You can install %s update"
+msgid_plural "You can install %s updates"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../extensions/cpsection/updater/view.py:155
+msgid "Checking for updates..."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:160
+msgid "Installing updates..."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:165
+#, python-format
+msgid "%s update was installed"
+msgid_plural "%s updates were installed"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../extensions/cpsection/updater/view.py:244
+msgid "Install selected"
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:265
+#, python-format
+msgid "Download size: %s"
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:353
+#, python-format
+msgid "From version %(current)d to %(new)s (Size: %(size)s)"
+msgstr ""
+
+#. TRANS: download size is 0
+#: ../extensions/cpsection/updater/view.py:373
+msgid "None"
+msgstr ""
+
+#. TRANS: download size of very small updates
+#: ../extensions/cpsection/updater/view.py:376
+msgid "1 KB"
+msgstr ""
+
+#. TRANS: download size of small updates, e.g. '250 KB'
+#: ../extensions/cpsection/updater/view.py:379
+#, python-format
+msgid "%.0f KB"
+msgstr ""
+
+#. TRANS: download size of updates, e.g. '2.3 MB'
+#: ../extensions/cpsection/updater/view.py:382
+#, python-format
+msgid "%.1f MB"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:58
+msgid "My Battery"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:137
+msgid "Removed"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:140
+msgid "Charging"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:143
+msgid "Very little power remaining"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:149
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:152
+msgid "Charged"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:44
+#, python-format
+msgid "IP address: %s"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:110
+msgid "Disconnect..."
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:114
+msgid "Create new wireless network"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:120
+#: ../src/jarabe/desktop/meshbox.py:264
+msgid "Connecting..."
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:124
+#: ../extensions/deviceicon/network.py:186
+#: ../src/jarabe/desktop/meshbox.py:270
+msgid "Connected"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:146
+msgid "Channel"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:161
+msgid "Wired Network"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:189
+msgid "Speed"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:415
+#, python-format
+msgid "%s's network %s"
+msgstr ""
+
+#: ../extensions/deviceicon/speaker.py:59
+msgid "My Speakers"
+msgstr ""
+
+#: ../extensions/deviceicon/speaker.py:133
+msgid "Unmute"
+msgstr ""
+
+#: ../extensions/deviceicon/speaker.py:136
+msgid "Mute"
+msgstr ""
+
+#: ../extensions/globalkey/screenshot.py:56
+msgid "Mesh"
+msgstr ""
+
+#: ../extensions/globalkey/screenshot.py:58
+#: ../src/jarabe/frame/zoomtoolbar.py:39
+msgid "Group"
+msgstr ""
+
+#: ../extensions/globalkey/screenshot.py:60
+#: ../src/jarabe/frame/zoomtoolbar.py:41
+msgid "Home"
+msgstr ""
+
+#: ../extensions/globalkey/screenshot.py:66
+#: ../src/jarabe/frame/zoomtoolbar.py:43
+msgid "Activity"
+msgstr ""
+
+#: ../extensions/globalkey/screenshot.py:69
+msgid "Screenshot"
+msgstr ""
+
+#: ../extensions/globalkey/screenshot.py:71
+#, python-format
+msgid "Screenshot of \"%s\""
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:1
+msgid "Backup URL"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:2
+msgid ""
+"Color for the XO icon that is used throughout the desktop. The string is "
+"composed of the stroke color and fill color, format is that of rbg colors. "
+"Example: #AC32FF,#9A5200"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:3
+msgid "Corner Delay"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:4
+msgid "Delay for the activation of the frame using the corners."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:5
+msgid "Delay for the activation of the frame using the edges."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:6
+msgid "Edge Delay"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:7
+msgid "Favorites Layout"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:8
+msgid "Favorites resume mode"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:9
+msgid ""
+"If TRUE, Sugar will make us searchable for the other users of the Jabber "
+"server."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:10
+msgid "If TRUE, Sugar will show a \"Log out\" option."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:11
+msgid "Jabber Server"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:12
+msgid "Keyboard layouts"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:13
+msgid "Keyboard model"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:14
+msgid "Keyboard options"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:15
+msgid "Layout of the favorites view."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:16
+msgid ""
+"List of keyboard layouts. Each entry should be in the form layout(variant)"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:17
+msgid "List of keyboard options."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:18
+msgid "Power Automatic"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:19
+msgid "Power Automatic."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:20
+msgid "Power Extreme"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:21
+msgid "Power Extreme."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:22
+msgid "Publish to Gadget"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:23
+msgid "Setting for muting the sound device."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:24
+msgid "Show Log out"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:25
+msgid "Sound Muted"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:26
+msgid "The keyboard model to be used"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:28
+msgid "Timezone setting for the system."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:29
+msgid "Url of the jabber server to use."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:30
+msgid "Url where the backup is saved to."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:31
+msgid "User Color"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:32
+msgid "User Name"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:33
+msgid "User name that is used throughout the desktop."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:34
+msgid "Volume Level"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:35
+msgid "Volume level for the sound device."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:36
+msgid ""
+"When in resume mode, clicking on a favorite icon will cause the last entry "
+"for that activity to be resumed."
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:28
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:30
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:31
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr ""
+
+#. TRANS: Translators, there's a empty line at the end of this string,
+#. which must appear in the translated string (msgstr) as well.
+#: ../src/jarabe/controlpanel/cmd.py:37
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" -c key clear the current value for the key \n"
+" "
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:50
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:280
+msgid "Warning"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:281
+#: ../src/jarabe/controlpanel/sectionview.py:42
+msgid "Changes require restart"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:284
+msgid "Cancel changes"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:289 ../src/jarabe/desktop/homebox.py:70
+msgid "Later"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:293
+msgid "Restart now"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/toolbar.py:61 ../src/jarabe/intro/window.py:188
+msgid "Done"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/toolbar.py:115
+#: ../src/jarabe/desktop/homebox.py:68
+#: ../src/jarabe/frame/activitiestray.py:726
+#: ../src/jarabe/frame/activitiestray.py:822
+#: ../src/jarabe/frame/activitiestray.py:850
+msgid "Cancel"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/toolbar.py:121
+#: ../src/jarabe/desktop/favoritesview.py:334
+msgid "Ok"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:236
+#, python-format
+msgid "Version %s"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:357
+msgid "Confirm erase"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:359
+#, python-format
+msgid "Confirm erase: Do you want to permanently erase %s?"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:363
+#: ../src/jarabe/frame/clipboardmenu.py:62
+#: ../src/jarabe/view/viewsource.py:218
+msgid "Keep"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:366
+#: ../src/jarabe/desktop/activitieslist.py:409
+#: ../src/jarabe/journal/journaltoolbox.py:360
+#: ../src/jarabe/journal/palettes.py:112
+msgid "Erase"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:430
+msgid "Remove favorite"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:434
+msgid "Make favorite"
+msgstr ""
+
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:116
+msgid "Freeform"
+msgstr ""
+
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:198
+msgid "Ring"
+msgstr ""
+
+#. TRANS: label for the spiral layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:337
+msgid "Spiral"
+msgstr ""
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:404
+msgid "Box"
+msgstr ""
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:445
+msgid "Triangle"
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:325
+msgid "Registration Failed"
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:326
+#, python-format
+msgid "%s"
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:328
+msgid "Registration Successful"
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:329
+msgid "You are now registered with your school server."
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:673
+msgid "Register"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:63
+msgid "Software Update"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:64
+msgid "Update your activities to ensure compatibility with your new software"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:73
+msgid "Check now"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:192
+msgid "List view"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:193
+msgid "<Ctrl>2"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:255
+msgid "Favorites view"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:256
+msgid "<Ctrl>1"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:131
+msgid "Key Type:"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:151
+msgid "Authentication Type:"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:215
+msgid "WPA & WPA2 Personal"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:224
+msgid "Wireless Security:"
+msgstr ""
+
+#: ../src/jarabe/desktop/meshbox.py:136
+msgid "Connect"
+msgstr ""
+
+#: ../src/jarabe/desktop/meshbox.py:140
+msgid "Disconnect"
+msgstr ""
+
+#. TRANS: Action label for resuming an activity.
+#: ../src/jarabe/desktop/meshbox.py:466
+#: ../src/jarabe/frame/activitiestray.py:761
+#: ../src/jarabe/journal/journaltoolbox.py:428
+#: ../src/jarabe/journal/palettes.py:72 ../src/jarabe/view/palettes.py:67
+msgid "Resume"
+msgstr ""
+
+#: ../src/jarabe/desktop/meshbox.py:471
+#: ../src/jarabe/frame/activitiestray.py:235
+msgid "Join"
+msgstr ""
+
+#: ../src/jarabe/desktop/schoolserver.py:103
+msgid "Cannot connect to the server."
+msgstr ""
+
+#: ../src/jarabe/desktop/schoolserver.py:108
+msgid "The server could not complete the request."
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:240
+#: ../src/jarabe/frame/activitiestray.py:698
+msgid "Decline"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:650
+#, python-format
+msgid "%dB"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:652
+#, python-format
+msgid "%dKB"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:654
+#, python-format
+msgid "%dMB"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:671
+#, python-format
+msgid "%s of %s"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:683
+#, python-format
+msgid "Transfer from %r"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:693
+msgid "Accept"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:716
+#: ../src/jarabe/frame/activitiestray.py:840
+#, python-format
+msgid "%s (%s)"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:750
+#: ../src/jarabe/frame/activitiestray.py:875
+msgid "Dismiss"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:810
+#, python-format
+msgid "Transfer to %r"
+msgstr ""
+
+#: ../src/jarabe/frame/clipboardmenu.py:52 ../src/jarabe/view/palettes.py:221
+msgid "Remove"
+msgstr ""
+
+#: ../src/jarabe/frame/clipboardmenu.py:57
+#: ../src/jarabe/frame/clipboardmenu.py:80
+msgid "Open"
+msgstr ""
+
+#: ../src/jarabe/frame/clipboardmenu.py:85
+msgid "Open with"
+msgstr ""
+
+#: ../src/jarabe/frame/clipboardobject.py:49
+#, python-format
+msgid "%s clipping"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:37
+msgid "Neighborhood"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:37
+msgid "F1"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:39
+msgid "F2"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:41
+msgid "F3"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:43
+msgid "F4"
+msgstr ""
+
+#: ../src/jarabe/intro/window.py:124
+msgid "Click to change color:"
+msgstr ""
+
+#: ../src/jarabe/intro/window.py:174 ../src/jarabe/journal/detailview.py:103
+msgid "Back"
+msgstr ""
+
+#: ../src/jarabe/intro/window.py:191
+msgid "Next"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:152
+#: ../src/jarabe/journal/palettes.py:66
+msgid "Untitled"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:245
+msgid "No preview"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:264
+#, python-format
+msgid "Kind: %s"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:264
+msgid "Unknown"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:265
+#, python-format
+msgid "Date: %s"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:266
+#, python-format
+msgid "Size: %s"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:288 ../src/jarabe/journal/misc.py:89
+msgid "No date"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:295
+msgid "Participants:"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:318
+msgid "Description:"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:343
+msgid "Tags:"
+msgstr ""
+
+#: ../src/jarabe/journal/journalactivity.py:108
+#: ../src/jarabe/journal/volumestoolbar.py:47
+msgid "Journal"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:67
+msgid "Search"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:126
+msgid "Anytime"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:128
+msgid "Today"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:130
+msgid "Since yesterday"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 7 days.
+#: ../src/jarabe/journal/journaltoolbox.py:132
+msgid "Past week"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 30 days.
+#: ../src/jarabe/journal/journaltoolbox.py:134
+msgid "Past month"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 356 days.
+#: ../src/jarabe/journal/journaltoolbox.py:136
+msgid "Past year"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:143
+msgid "Anyone"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:145
+msgid "My friends"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:146
+msgid "My class"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:274
+msgid "Anything"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:350
+#: ../src/jarabe/journal/palettes.py:90
+msgid "Copy"
+msgstr ""
+
+#. TRANS: Action label for starting an entry.
+#: ../src/jarabe/journal/journaltoolbox.py:431
+#: ../src/jarabe/journal/palettes.py:75
+msgid "Start"
+msgstr ""
+
+#: ../src/jarabe/journal/listview.py:352
+msgid "Your Journal is empty"
+msgstr ""
+
+#: ../src/jarabe/journal/listview.py:354
+msgid "No matching entries"
+msgstr ""
+
+#: ../src/jarabe/journal/listview.py:365
+msgid "Clear search"
+msgstr ""
+
+#: ../src/jarabe/journal/modalalert.py:63
+msgid "Your Journal is full"
+msgstr ""
+
+#: ../src/jarabe/journal/modalalert.py:67
+msgid "Please delete some old Journal entries to make space for new ones."
+msgstr ""
+
+#: ../src/jarabe/journal/modalalert.py:79
+msgid "Show Journal"
+msgstr ""
+
+#: ../src/jarabe/journal/objectchooser.py:146
+msgid "Choose an object"
+msgstr ""
+
+#: ../src/jarabe/journal/objectchooser.py:151
+#: ../src/jarabe/view/viewsource.py:308
+msgid "Close"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:73
+msgid "Resume with"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:76
+msgid "Start with"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:98
+msgid "Send to"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:107
+msgid "View Details"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:185
+msgid "No friends present"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:190
+msgid "No valid connection found"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:218
+msgid "No activity to resume entry"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:220
+msgid "No activity to start entry"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:62
+msgid "Remove friend"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:65
+msgid "Make friend"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:82
+msgid "Shutdown"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:90
+msgid "Logout"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:95
+msgid "My Settings"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:130
+#, python-format
+msgid "Invite to %s"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:45
+msgid "Starting..."
+msgstr ""
+
+#. TODO: share-with, keep
+#: ../src/jarabe/view/palettes.py:74
+msgid "View Source"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:85
+msgid "Stop"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:125
+msgid "Start new"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:174
+msgid "Show contents"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:196 ../src/jarabe/view/palettes.py:246
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr ""
+
+#: ../src/jarabe/view/viewsource.py:208
+msgid "Instance Source"
+msgstr ""
+
+#: ../src/jarabe/view/viewsource.py:233
+msgid "Source"
+msgstr ""
+
+#: ../src/jarabe/view/viewsource.py:292
+msgid "Activity Bundle Source"
+msgstr ""
+
+#: ../src/jarabe/view/viewsource.py:299
+#, python-format
+msgid "View source: %r"
+msgstr ""
diff --git a/shell/po/tzo.po b/shell/po/tzo.po
new file mode 100644
index 0000000..afc8693
--- /dev/null
+++ b/shell/po/tzo.po
@@ -0,0 +1,1210 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-08-26 00:31-0400\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.3.0\n"
+
+#: ../extensions/cpsection/aboutme/__init__.py:24
+msgid "About Me"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:43
+msgid "You must enter a name."
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:68
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:71
+#, python-format
+msgid "stroke: %s"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:73
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:75
+#, python-format
+msgid "fill: %s"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:86
+msgid "Error in specified color modifiers."
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:89
+msgid "Error in specified colors."
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/view.py:94 ../src/jarabe/intro/window.py:92
+msgid "Name:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/view.py:128
+msgid "Click to change your color:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/__init__.py:21
+msgid "About my Computer"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/model.py:28
+msgid "Not available"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:60
+msgid "Identity"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:69
+msgid "Serial Number:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:91
+msgid "Software"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:100
+msgid "Build:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:115
+msgid "Sugar:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:131
+msgid "Firmware:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:146
+msgid "Wireless Firmware:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:169
+msgid "Copyright and License"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:184
+msgid ""
+"Sugar is the graphical user interface that you are looking at. Sugar is free "
+"software, covered by the GNU General Public License, and you are welcome to "
+"change it and/or distribute copies of it under certain conditions described "
+"therein."
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:196
+msgid "Full license:"
+msgstr ""
+
+#: ../extensions/cpsection/datetime/__init__.py:21
+msgid "Date & Time"
+msgstr ""
+
+#: ../extensions/cpsection/datetime/model.py:87
+msgid "Error timezone does not exist."
+msgstr ""
+
+#: ../extensions/cpsection/datetime/view.py:68 ../data/sugar.schemas.in.h:27
+msgid "Timezone"
+msgstr ""
+
+#: ../extensions/cpsection/frame/__init__.py:21
+msgid "Frame"
+msgstr ""
+
+#: ../extensions/cpsection/frame/model.py:38
+#: ../extensions/cpsection/frame/model.py:60
+msgid "Value must be an integer."
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:26
+msgid "never"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:27
+msgid "instantaneous"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:28
+#, python-format
+msgid "%s seconds"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:52
+msgid "Activation Delay"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:76
+msgid "Corner"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:111
+msgid "Edge"
+msgstr ""
+
+#: ../extensions/cpsection/keyboard/__init__.py:21
+#: ../extensions/cpsection/keyboard/view.py:31
+msgid "Keyboard"
+msgstr ""
+
+#: ../extensions/cpsection/keyboard/view.py:187
+msgid "Keyboard Model"
+msgstr ""
+
+#: ../extensions/cpsection/keyboard/view.py:243
+msgid "Key(s) to change layout"
+msgstr ""
+
+#: ../extensions/cpsection/keyboard/view.py:311
+msgid "Keyboard Layout(s)"
+msgstr ""
+
+#: ../extensions/cpsection/language/__init__.py:21
+#: ../extensions/cpsection/language/view.py:33
+msgid "Language"
+msgstr ""
+
+#: ../extensions/cpsection/language/model.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr ""
+
+#: ../extensions/cpsection/language/model.py:124
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr ""
+
+#: ../extensions/cpsection/language/model.py:144
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr ""
+
+#: ../extensions/cpsection/language/view.py:56
+msgid ""
+"Add languages in the order you prefer. If a translation is not available, "
+"the next in the list will be used."
+msgstr ""
+
+#: ../extensions/cpsection/network/__init__.py:21
+#: ../extensions/cpsection/network/view.py:28
+msgid "Network"
+msgstr ""
+
+#: ../extensions/cpsection/network/model.py:79
+msgid "State is unknown."
+msgstr ""
+
+#: ../extensions/cpsection/network/model.py:105
+msgid "Error in specified radio argument use on/off."
+msgstr ""
+
+#: ../extensions/cpsection/network/model.py:137
+msgid "Error in specified argument use 0/1."
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:59
+msgid "Wireless"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:67
+msgid "Turn off the wireless radio to save battery life"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:80
+msgid "Radio"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:96
+msgid "Discard network history if you have trouble connecting to the network"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:105
+msgid "Discard network history"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:118
+msgid "Collaboration"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:126
+msgid ""
+"The server is the equivalent of what room you are in; people on the same "
+"server will be able to see each other, even when they aren't on the same "
+"network."
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:136
+msgid "Server:"
+msgstr ""
+
+#: ../extensions/cpsection/power/__init__.py:21
+msgid "Power"
+msgstr ""
+
+#: ../extensions/cpsection/power/model.py:54
+msgid "Error in automatic pm argument, use on/off."
+msgstr ""
+
+#: ../extensions/cpsection/power/model.py:81
+msgid "Error in extreme pm argument, use on/off."
+msgstr ""
+
+#: ../extensions/cpsection/power/view.py:47
+msgid "Power management"
+msgstr ""
+
+#: ../extensions/cpsection/power/view.py:57
+msgid "Automatic power management (increases battery life)"
+msgstr ""
+
+#: ../extensions/cpsection/power/view.py:85
+msgid ""
+"Extreme power management (disableswireless radio, increases battery life)"
+msgstr ""
+
+#: ../extensions/cpsection/updater/__init__.py:21
+msgid "Software update"
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:62
+msgid ""
+"Software updates correct errors, eliminate security vulnerabilities, and "
+"provide new features."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:122
+#, python-format
+msgid "Checking %s..."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:124
+#, python-format
+msgid "Downloading %s..."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:126
+#, python-format
+msgid "Updating %s..."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:135
+msgid "Your software is up-to-date"
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:137
+#, python-format
+msgid "You can install %s update"
+msgid_plural "You can install %s updates"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../extensions/cpsection/updater/view.py:155
+msgid "Checking for updates..."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:160
+msgid "Installing updates..."
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:165
+#, python-format
+msgid "%s update was installed"
+msgid_plural "%s updates were installed"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../extensions/cpsection/updater/view.py:244
+msgid "Install selected"
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:265
+#, python-format
+msgid "Download size: %s"
+msgstr ""
+
+#: ../extensions/cpsection/updater/view.py:353
+#, python-format
+msgid "From version %(current)d to %(new)s (Size: %(size)s)"
+msgstr ""
+
+#. TRANS: download size is 0
+#: ../extensions/cpsection/updater/view.py:373
+msgid "None"
+msgstr ""
+
+#. TRANS: download size of very small updates
+#: ../extensions/cpsection/updater/view.py:376
+msgid "1 KB"
+msgstr ""
+
+#. TRANS: download size of small updates, e.g. '250 KB'
+#: ../extensions/cpsection/updater/view.py:379
+#, python-format
+msgid "%.0f KB"
+msgstr ""
+
+#. TRANS: download size of updates, e.g. '2.3 MB'
+#: ../extensions/cpsection/updater/view.py:382
+#, python-format
+msgid "%.1f MB"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:58
+msgid "My Battery"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:137
+msgid "Removed"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:140
+msgid "Charging"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:143
+msgid "Very little power remaining"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:149
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:152
+msgid "Charged"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:44
+#, python-format
+msgid "IP address: %s"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:110
+msgid "Disconnect..."
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:114
+msgid "Create new wireless network"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:120
+#: ../src/jarabe/desktop/meshbox.py:261
+msgid "Connecting..."
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:124
+#: ../extensions/deviceicon/network.py:186
+#: ../src/jarabe/desktop/meshbox.py:267
+msgid "Connected"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:146
+msgid "Channel"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:161
+msgid "Wired Network"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:189
+msgid "Speed"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:415
+#, python-format
+msgid "%s's network %s"
+msgstr ""
+
+#: ../extensions/deviceicon/speaker.py:59
+msgid "My Speakers"
+msgstr ""
+
+#: ../extensions/deviceicon/speaker.py:133
+msgid "Unmute"
+msgstr ""
+
+#: ../extensions/deviceicon/speaker.py:136
+msgid "Mute"
+msgstr ""
+
+#: ../extensions/globalkey/screenshot.py:56
+msgid "Mesh"
+msgstr ""
+
+#: ../extensions/globalkey/screenshot.py:58
+#: ../src/jarabe/frame/zoomtoolbar.py:39
+msgid "Group"
+msgstr ""
+
+#: ../extensions/globalkey/screenshot.py:60
+#: ../src/jarabe/frame/zoomtoolbar.py:41
+msgid "Home"
+msgstr ""
+
+#: ../extensions/globalkey/screenshot.py:66
+#: ../src/jarabe/frame/zoomtoolbar.py:43
+msgid "Activity"
+msgstr ""
+
+#: ../extensions/globalkey/screenshot.py:69
+msgid "Screenshot"
+msgstr ""
+
+#: ../extensions/globalkey/screenshot.py:71
+#, python-format
+msgid "Screenshot of \"%s\""
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:1
+msgid "Backup URL"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:2
+msgid ""
+"Color for the XO icon that is used throughout the desktop. The string is "
+"composed of the stroke color and fill color, format is that of rbg colors. "
+"Example: #AC32FF,#9A5200"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:3
+msgid "Corner Delay"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:4
+msgid "Delay for the activation of the frame using the corners."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:5
+msgid "Delay for the activation of the frame using the edges."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:6
+msgid "Edge Delay"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:7
+msgid "Favorites Layout"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:8
+msgid "Favorites resume mode"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:9
+msgid ""
+"If TRUE, Sugar will make us searchable for the other users of the Jabber "
+"server."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:10
+msgid "If TRUE, Sugar will show a \"Log out\" option."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:11
+msgid "Jabber Server"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:12
+msgid "Keyboard layouts"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:13
+msgid "Keyboard model"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:14
+msgid "Keyboard options"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:15
+msgid "Layout of the favorites view."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:16
+msgid ""
+"List of keyboard layouts. Each entry should be in the form layout(variant)"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:17
+msgid "List of keyboard options."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:18
+msgid "Power Automatic"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:19
+msgid "Power Automatic."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:20
+msgid "Power Extreme"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:21
+msgid "Power Extreme."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:22
+msgid "Publish to Gadget"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:23
+msgid "Setting for muting the sound device."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:24
+msgid "Show Log out"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:25
+msgid "Sound Muted"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:26
+msgid "The keyboard model to be used"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:28
+msgid "Timezone setting for the system."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:29
+msgid "Url of the jabber server to use."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:30
+msgid "Url where the backup is saved to."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:31
+msgid "User Color"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:32
+msgid "User Name"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:33
+msgid "User name that is used throughout the desktop."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:34
+msgid "Volume Level"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:35
+msgid "Volume level for the sound device."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:36
+msgid ""
+"When in resume mode, clicking on a favorite icon will cause the last entry "
+"for that activity to be resumed."
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:28
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:30
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:31
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr ""
+
+#. TRANS: Translators, there's a empty line at the end of this string,
+#. which must appear in the translated string (msgstr) as well.
+#: ../src/jarabe/controlpanel/cmd.py:37
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" -c key clear the current value for the key \n"
+" "
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:50
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:280
+msgid "Warning"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:281
+#: ../src/jarabe/controlpanel/sectionview.py:42
+msgid "Changes require restart"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:284
+msgid "Cancel changes"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:289 ../src/jarabe/desktop/homebox.py:70
+msgid "Later"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:293
+msgid "Restart now"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/toolbar.py:61 ../src/jarabe/intro/window.py:188
+msgid "Done"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/toolbar.py:115
+#: ../src/jarabe/desktop/homebox.py:68
+#: ../src/jarabe/frame/activitiestray.py:726
+#: ../src/jarabe/frame/activitiestray.py:822
+#: ../src/jarabe/frame/activitiestray.py:850
+msgid "Cancel"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/toolbar.py:121
+#: ../src/jarabe/desktop/favoritesview.py:332
+msgid "Ok"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:80
+#: ../src/jarabe/journal/listview.py:147
+msgid "Title"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:91
+msgid "Version"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:105
+#: ../src/jarabe/journal/listview.py:178
+msgid "Date"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:234
+#, python-format
+msgid "Version %s"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:355
+msgid "Confirm erase"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:357
+#, python-format
+msgid "Confirm erase: Do you want to permanently erase %s?"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:361
+#: ../src/jarabe/frame/clipboardmenu.py:62
+#: ../src/jarabe/view/viewsource.py:218
+msgid "Keep"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:364
+#: ../src/jarabe/desktop/activitieslist.py:406
+#: ../src/jarabe/journal/journaltoolbox.py:360
+#: ../src/jarabe/journal/palettes.py:112
+msgid "Erase"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:427
+msgid "Remove favorite"
+msgstr ""
+
+#: ../src/jarabe/desktop/activitieslist.py:431
+msgid "Make favorite"
+msgstr ""
+
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:116
+msgid "Freeform"
+msgstr ""
+
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:198
+msgid "Ring"
+msgstr ""
+
+#. TRANS: label for the spiral layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:334
+msgid "Spiral"
+msgstr ""
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:401
+msgid "Box"
+msgstr ""
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:442
+msgid "Triangle"
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:323
+msgid "Registration Failed"
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:324
+#, python-format
+msgid "%s"
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:326
+msgid "Registration Successful"
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:327
+msgid "You are now registered with your school server."
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:671
+msgid "Register"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:63
+msgid "Software Update"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:64
+msgid "Update your activities to ensure compatibility with your new software"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:73
+msgid "Check now"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:192
+msgid "List view"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:193
+msgid "<Ctrl>2"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:255
+msgid "Favorites view"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:256
+msgid "<Ctrl>1"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:131
+msgid "Key Type:"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:151
+msgid "Authentication Type:"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:215
+msgid "WPA & WPA2 Personal"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:224
+msgid "Wireless Security:"
+msgstr ""
+
+#: ../src/jarabe/desktop/meshbox.py:136
+msgid "Connect"
+msgstr ""
+
+#: ../src/jarabe/desktop/meshbox.py:140
+msgid "Disconnect"
+msgstr ""
+
+#. TRANS: Action label for resuming an activity.
+#: ../src/jarabe/desktop/meshbox.py:463
+#: ../src/jarabe/frame/activitiestray.py:761
+#: ../src/jarabe/journal/journaltoolbox.py:428
+#: ../src/jarabe/journal/palettes.py:72 ../src/jarabe/view/palettes.py:64
+msgid "Resume"
+msgstr ""
+
+#: ../src/jarabe/desktop/meshbox.py:468
+#: ../src/jarabe/frame/activitiestray.py:235
+msgid "Join"
+msgstr ""
+
+#: ../src/jarabe/desktop/schoolserver.py:34
+msgid "Cannot obtain data needed for registration."
+msgstr ""
+
+#: ../src/jarabe/desktop/schoolserver.py:51
+msgid "Cannot connect to the server."
+msgstr ""
+
+#: ../src/jarabe/desktop/schoolserver.py:56
+msgid "The server could not complete the request."
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:240
+#: ../src/jarabe/frame/activitiestray.py:698
+msgid "Decline"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:650
+#, python-format
+msgid "%dB"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:652
+#, python-format
+msgid "%dKB"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:654
+#, python-format
+msgid "%dMB"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:671
+#, python-format
+msgid "%s of %s"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:683
+#, python-format
+msgid "Transfer from %r"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:693
+msgid "Accept"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:716
+#: ../src/jarabe/frame/activitiestray.py:840
+#, python-format
+msgid "%s (%s)"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:750
+#: ../src/jarabe/frame/activitiestray.py:875
+msgid "Dismiss"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:810
+#, python-format
+msgid "Transfer to %r"
+msgstr ""
+
+#: ../src/jarabe/frame/clipboardmenu.py:52
+msgid "Remove"
+msgstr ""
+
+#: ../src/jarabe/frame/clipboardmenu.py:57
+#: ../src/jarabe/frame/clipboardmenu.py:80
+msgid "Open"
+msgstr ""
+
+#: ../src/jarabe/frame/clipboardmenu.py:85
+msgid "Open with"
+msgstr ""
+
+#: ../src/jarabe/frame/clipboardobject.py:49
+#, python-format
+msgid "%s clipping"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:37
+msgid "Neighborhood"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:37
+msgid "F1"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:39
+msgid "F2"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:41
+msgid "F3"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:43
+msgid "F4"
+msgstr ""
+
+#: ../src/jarabe/intro/window.py:124
+msgid "Click to change color:"
+msgstr ""
+
+#: ../src/jarabe/intro/window.py:174 ../src/jarabe/journal/detailview.py:103
+msgid "Back"
+msgstr ""
+
+#: ../src/jarabe/intro/window.py:191
+msgid "Next"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:164
+#: ../src/jarabe/journal/palettes.py:66
+msgid "Untitled"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:210
+msgid "No preview"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:229
+#, python-format
+msgid "Kind: %s"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:229
+msgid "Unknown"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:230
+#, python-format
+msgid "Date: %s"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:231
+#, python-format
+msgid "Size: %s"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:253 ../src/jarabe/journal/misc.py:92
+msgid "No date"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:260
+msgid "Participants:"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:283
+msgid "Description:"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:309
+msgid "Tags:"
+msgstr ""
+
+#: ../src/jarabe/journal/journalactivity.py:108
+#: ../src/jarabe/journal/volumestoolbar.py:47
+msgid "Journal"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:67
+msgid "Search"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:126
+msgid "Anytime"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:128
+msgid "Today"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:130
+msgid "Since yesterday"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 7 days.
+#: ../src/jarabe/journal/journaltoolbox.py:132
+msgid "Past week"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 30 days.
+#: ../src/jarabe/journal/journaltoolbox.py:134
+msgid "Past month"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 356 days.
+#: ../src/jarabe/journal/journaltoolbox.py:136
+msgid "Past year"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:143
+msgid "Anyone"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:145
+msgid "My friends"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:146
+msgid "My class"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:274
+msgid "Anything"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:350
+#: ../src/jarabe/journal/palettes.py:90
+msgid "Copy"
+msgstr ""
+
+#. TRANS: Action label for starting an entry.
+#: ../src/jarabe/journal/journaltoolbox.py:431
+#: ../src/jarabe/journal/palettes.py:75
+msgid "Start"
+msgstr ""
+
+#: ../src/jarabe/journal/listview.py:361
+msgid "Your Journal is empty"
+msgstr ""
+
+#: ../src/jarabe/journal/listview.py:363
+msgid "No matching entries"
+msgstr ""
+
+#: ../src/jarabe/journal/listview.py:374
+msgid "Clear search"
+msgstr ""
+
+#: ../src/jarabe/journal/modalalert.py:63
+msgid "Your Journal is full"
+msgstr ""
+
+#: ../src/jarabe/journal/modalalert.py:67
+msgid "Please delete some old Journal entries to make space for new ones."
+msgstr ""
+
+#: ../src/jarabe/journal/modalalert.py:79
+msgid "Show Journal"
+msgstr ""
+
+#: ../src/jarabe/journal/objectchooser.py:146
+msgid "Choose an object"
+msgstr ""
+
+#: ../src/jarabe/journal/objectchooser.py:151
+#: ../src/jarabe/view/viewsource.py:308
+msgid "Close"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:73
+msgid "Resume with"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:76
+msgid "Start with"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:98
+msgid "Send to"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:107
+msgid "View Details"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:185
+msgid "No friends present"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:190
+msgid "No valid connection found"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:218
+msgid "No activity to resume entry"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:220
+msgid "No activity to start entry"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:62
+msgid "Remove friend"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:65
+msgid "Make friend"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:82
+msgid "Shutdown"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:90
+msgid "Logout"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:95
+msgid "My Settings"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:130
+#, python-format
+msgid "Invite to %s"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:45
+msgid "Starting..."
+msgstr ""
+
+#. TODO: share-with, keep
+#: ../src/jarabe/view/palettes.py:71
+msgid "View Source"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:82
+msgid "Stop"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:122
+msgid "Start new"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:171
+msgid "Show contents"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:193 ../src/jarabe/view/palettes.py:243
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:218
+msgid "Unmount"
+msgstr ""
+
+#: ../src/jarabe/view/viewsource.py:208
+msgid "Instance Source"
+msgstr ""
+
+#: ../src/jarabe/view/viewsource.py:233
+msgid "Source"
+msgstr ""
+
+#: ../src/jarabe/view/viewsource.py:292
+msgid "Activity Bundle Source"
+msgstr ""
+
+#: ../src/jarabe/view/viewsource.py:299
+#, python-format
+msgid "View source: %r"
+msgstr ""
diff --git a/shell/po/ug.po b/shell/po/ug.po
new file mode 100644
index 0000000..a45bff2
--- /dev/null
+++ b/shell/po/ug.po
@@ -0,0 +1,979 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-01-27 13:34-0500\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../extensions/cpsection/aboutme/__init__.py:24
+msgid "About Me"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:43
+msgid "You must enter a name."
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:68
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:71
+#, python-format
+msgid "stroke: %s"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:73
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:75
+#, python-format
+msgid "fill: %s"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:86
+msgid "Error in specified color modifiers."
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/model.py:89
+msgid "Error in specified colors."
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/view.py:94 ../src/jarabe/intro/window.py:92
+msgid "Name:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutme/view.py:128
+msgid "Click to change your color:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/__init__.py:21
+msgid "About my Computer"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/model.py:26
+msgid "Not available"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:59
+msgid "Identity"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:68
+msgid "Serial Number:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:90
+msgid "Software"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:99
+msgid "Build:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:114
+msgid "Sugar:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:130
+msgid "Firmware:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:145
+msgid "Wireless Firmware:"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:168
+msgid "Copyright and License"
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:176
+msgid ""
+"© 2008 One Laptop per Child Association Inc; Red Hat Inc; and Contributors."
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:183
+msgid ""
+"Sugar is the graphical user interface that you are looking at. Sugar is free "
+"software, covered by the GNU General Public License, and you are welcome to "
+"change it and/or distribute copies of it under certain conditions described "
+"therein."
+msgstr ""
+
+#: ../extensions/cpsection/aboutcomputer/view.py:195
+msgid "Full license:"
+msgstr ""
+
+#: ../extensions/cpsection/datetime/__init__.py:21
+msgid "Date & Time"
+msgstr ""
+
+#: ../extensions/cpsection/datetime/model.py:87
+msgid "Error timezone does not exist."
+msgstr ""
+
+#: ../extensions/cpsection/datetime/view.py:68 ../data/sugar.schemas.in.h:19
+msgid "Timezone"
+msgstr ""
+
+#: ../extensions/cpsection/frame/__init__.py:21
+msgid "Frame"
+msgstr ""
+
+#: ../extensions/cpsection/frame/model.py:38
+#: ../extensions/cpsection/frame/model.py:60
+msgid "Value must be an integer."
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:26
+msgid "never"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:27
+msgid "instantaneous"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:28
+#, python-format
+msgid "%s seconds"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:52
+msgid "Activation Delay"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:76
+msgid "Corner"
+msgstr ""
+
+#: ../extensions/cpsection/frame/view.py:111
+msgid "Edge"
+msgstr ""
+
+#: ../extensions/cpsection/language/__init__.py:21
+#: ../extensions/cpsection/language/view.py:32
+msgid "Language"
+msgstr ""
+
+#: ../extensions/cpsection/language/model.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr ""
+
+#: ../extensions/cpsection/language/model.py:124
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr ""
+
+#: ../extensions/cpsection/language/model.py:144
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr ""
+
+#: ../extensions/cpsection/network/__init__.py:21
+#: ../extensions/cpsection/network/view.py:28
+msgid "Network"
+msgstr ""
+
+#: ../extensions/cpsection/network/model.py:79
+msgid "State is unknown."
+msgstr ""
+
+#: ../extensions/cpsection/network/model.py:105
+msgid "Error in specified radio argument use on/off."
+msgstr ""
+
+#: ../extensions/cpsection/network/model.py:137
+msgid "Error in specified argument use 0/1."
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:56
+msgid "Wireless"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:64
+msgid "Turn off the wireless radio to save battery life"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:77
+msgid "Radio"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:93
+msgid "Discard network history if you have trouble connecting to the network"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:102
+msgid "Discard network history"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:115
+msgid "Collaboration"
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:123
+msgid ""
+"The server is the equivalent of what room you are in; people on the same "
+"server will be able to see each other, even when they aren't on the same "
+"network."
+msgstr ""
+
+#: ../extensions/cpsection/network/view.py:133
+msgid "Server:"
+msgstr ""
+
+#: ../extensions/cpsection/power/__init__.py:21
+msgid "Power"
+msgstr ""
+
+#: ../extensions/cpsection/power/model.py:54
+msgid "Error in automatic pm argument, use on/off."
+msgstr ""
+
+#: ../extensions/cpsection/power/model.py:81
+msgid "Error in extreme pm argument, use on/off."
+msgstr ""
+
+#: ../extensions/cpsection/power/view.py:47
+msgid "Power management"
+msgstr ""
+
+#: ../extensions/cpsection/power/view.py:57
+msgid "Automatic power management (increases battery life)"
+msgstr ""
+
+#: ../extensions/cpsection/power/view.py:85
+msgid ""
+"Extreme power management (disableswireless radio, increases battery life)"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:56
+msgid "My Battery"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:137
+msgid "Removed"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:140
+msgid "Charging"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:143
+msgid "Very little power remaining"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:149
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr ""
+
+#: ../extensions/deviceicon/battery.py:153
+msgid "Charged"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:40
+#, python-format
+msgid "IP address: %s"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:104
+msgid "Disconnect..."
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:109
+#: ../src/jarabe/desktop/meshbox.py:246
+msgid "Connecting..."
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:113
+#: ../extensions/deviceicon/network.py:166
+#: ../src/jarabe/desktop/meshbox.py:252
+msgid "Connected"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:126
+msgid "Channel"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:141
+msgid "Wired Network"
+msgstr ""
+
+#: ../extensions/deviceicon/network.py:169
+msgid "Speed"
+msgstr ""
+
+#: ../extensions/deviceicon/speaker.py:46
+msgid "My Speakers"
+msgstr ""
+
+#: ../extensions/deviceicon/speaker.py:128
+msgid "Unmute"
+msgstr ""
+
+#: ../extensions/deviceicon/speaker.py:131
+msgid "Mute"
+msgstr ""
+
+#: ../extensions/globalkey/screenshot.py:50
+msgid "Screenshot"
+msgstr ""
+
+#: ../extensions/globalkey/viewsource.py:196
+#, python-format
+msgid "View source: %r"
+msgstr ""
+
+#: ../extensions/globalkey/viewsource.py:205
+#: ../src/jarabe/frame/zoomtoolbar.py:42
+msgid "Activity"
+msgstr ""
+
+#: ../extensions/globalkey/viewsource.py:212
+msgid "Document"
+msgstr ""
+
+#: ../extensions/globalkey/viewsource.py:226
+#: ../src/jarabe/journal/objectchooser.py:141
+msgid "Close"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:1
+msgid "Backup URL"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:2
+msgid ""
+"Color for the XO icon that is used throughout the desktop. The string is "
+"composed of the stroke color and fill color, format is that of rbg colors. "
+"Example: #AC32FF,#9A5200"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:3
+msgid "Corner Delay"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:4
+msgid "Delay for the activation of the frame using the corners."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:5
+msgid "Delay for the activation of the frame using the edges."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:6
+msgid "Edge Delay"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:7
+msgid "Favorites Layout"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:8
+msgid "Favorites resume mode"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:9
+msgid ""
+"If TRUE, Sugar will make us searchable for the other users of the Jabber "
+"server."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:10
+msgid "Jabber Server"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:11
+msgid "Layout of the favorites view."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:12
+msgid "Power Automatic"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:13
+msgid "Power Automatic."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:14
+msgid "Power Extreme"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:15
+msgid "Power Extreme."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:16
+msgid "Publish to Gadget"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:17
+msgid "Setting for muting the sound device."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:18
+msgid "Sound Muted"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:20
+msgid "Timezone setting for the system."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:21
+msgid "Url of the jabber server to use."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:22
+msgid "Url where the backup is saved to."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:23
+msgid "User Color"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:24
+msgid "User Name"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:25
+msgid "User name that is used throughout the desktop."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:26
+msgid "Volume Level"
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:27
+msgid "Volume level for the sound device."
+msgstr ""
+
+#: ../data/sugar.schemas.in.h:28
+msgid ""
+"When in resume mode, clicking on a favorite icon will cause the last entry "
+"for that activity to be resumed."
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:28
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:30
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:31
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr ""
+
+#. TRANS: Translators, there's a empty line at the end of this string,
+#. which must appear in the translated string (msgstr) as well.
+#: ../src/jarabe/controlpanel/cmd.py:37
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" -c key clear the current value for the key \n"
+" "
+msgstr ""
+
+#: ../src/jarabe/controlpanel/cmd.py:50
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:272
+msgid "Warning"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:273
+#: ../src/jarabe/controlpanel/sectionview.py:42
+msgid "Changes require restart"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:276
+msgid "Cancel changes"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:281 ../src/jarabe/desktop/homebox.py:113
+msgid "Later"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/gui.py:285
+msgid "Restart now"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/toolbar.py:61 ../src/jarabe/intro/window.py:188
+msgid "Done"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/toolbar.py:115
+#: ../src/jarabe/desktop/homebox.py:111
+#: ../src/jarabe/frame/activitiestray.py:683
+#: ../src/jarabe/frame/activitiestray.py:762
+#: ../src/jarabe/frame/activitiestray.py:790
+msgid "Cancel"
+msgstr ""
+
+#: ../src/jarabe/controlpanel/toolbar.py:121
+#: ../src/jarabe/desktop/favoritesview.py:334
+msgid "Ok"
+msgstr ""
+
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:114
+msgid "Freeform"
+msgstr ""
+
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:196
+msgid "Ring"
+msgstr ""
+
+#. TRANS: label for the spiral layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:341
+msgid "Spiral"
+msgstr ""
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:408
+msgid "Box"
+msgstr ""
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:449
+msgid "Triangle"
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:325
+msgid "Registration Failed"
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:326
+#, python-format
+msgid "%s"
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:328
+msgid "Registration Successful"
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:329
+msgid "You are now registered with your school server."
+msgstr ""
+
+#: ../src/jarabe/desktop/favoritesview.py:666
+msgid "Register"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:67
+msgid "Confirm erase"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:69
+#, python-format
+msgid "Confirm erase: Do you want to permanently erase %s?"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:73 ../src/jarabe/frame/clipboardmenu.py:62
+msgid "Keep"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:76
+#: ../src/jarabe/journal/journaltoolbox.py:357
+#: ../src/jarabe/journal/palettes.py:97 ../src/jarabe/view/palettes.py:127
+msgid "Erase"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:106
+msgid "Software Update"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:107
+msgid "Update your activities to ensure compatibility with your new software"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:116
+msgid "Check now"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:233
+msgid "List view"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:234
+msgid "<Ctrl>2"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:296
+msgid "Favorites view"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:297
+msgid "<Ctrl>1"
+msgstr ""
+
+#: ../src/jarabe/desktop/homebox.py:304
+msgid "Resume by default"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:131
+msgid "Key Type:"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:151
+msgid "Authentication Type:"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:215
+msgid "WPA & WPA2 Personal"
+msgstr ""
+
+#: ../src/jarabe/desktop/keydialog.py:224
+msgid "Wireless Security:"
+msgstr ""
+
+#: ../src/jarabe/desktop/meshbox.py:130
+msgid "Connect"
+msgstr ""
+
+#: ../src/jarabe/desktop/meshbox.py:134
+msgid "Disconnect"
+msgstr ""
+
+#. TRANS: Action label for resuming an activity.
+#: ../src/jarabe/desktop/meshbox.py:440
+#: ../src/jarabe/frame/activitiestray.py:707
+#: ../src/jarabe/journal/journaltoolbox.py:425
+#: ../src/jarabe/journal/palettes.py:63 ../src/jarabe/view/palettes.py:62
+msgid "Resume"
+msgstr ""
+
+#: ../src/jarabe/desktop/meshbox.py:445
+#: ../src/jarabe/frame/activitiestray.py:221
+msgid "Join"
+msgstr ""
+
+#: ../src/jarabe/desktop/schoolserver.py:18
+msgid "Cannot obtain data needed for registration."
+msgstr ""
+
+#: ../src/jarabe/desktop/schoolserver.py:35
+msgid "Cannot connect to the server."
+msgstr ""
+
+#: ../src/jarabe/desktop/schoolserver.py:40
+msgid "The server could not complete the request."
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:226
+#: ../src/jarabe/frame/activitiestray.py:655
+msgid "Decline"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:608
+#, python-format
+msgid "%dB"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:610
+#, python-format
+msgid "%dKB"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:612
+#, python-format
+msgid "%dMB"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:629
+#, python-format
+msgid "%s of %s"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:640
+#, python-format
+msgid "Transfer from %r"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:650
+msgid "Accept"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:673
+#: ../src/jarabe/frame/activitiestray.py:780
+#, python-format
+msgid "%s (%s)"
+msgstr ""
+
+#: ../src/jarabe/frame/activitiestray.py:751
+#, python-format
+msgid "Transfer to %r"
+msgstr ""
+
+#: ../src/jarabe/frame/clipboardmenu.py:52
+msgid "Remove"
+msgstr ""
+
+#: ../src/jarabe/frame/clipboardmenu.py:57
+#: ../src/jarabe/frame/clipboardmenu.py:80
+msgid "Open"
+msgstr ""
+
+#: ../src/jarabe/frame/clipboardmenu.py:85
+msgid "Open with"
+msgstr ""
+
+#: ../src/jarabe/frame/clipboardobject.py:47
+#, python-format
+msgid "%s clipping"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:36
+msgid "Neighborhood"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:38
+msgid "Group"
+msgstr ""
+
+#: ../src/jarabe/frame/zoomtoolbar.py:40
+msgid "Home"
+msgstr ""
+
+#: ../src/jarabe/intro/window.py:124
+msgid "Click to change color:"
+msgstr ""
+
+#: ../src/jarabe/intro/window.py:174 ../src/jarabe/journal/detailview.py:103
+msgid "Back"
+msgstr ""
+
+#: ../src/jarabe/intro/window.py:191
+msgid "Next"
+msgstr ""
+
+#: ../src/jarabe/journal/collapsedentry.py:243
+#: ../src/jarabe/journal/expandedentry.py:159
+#: ../src/jarabe/journal/palettes.py:57
+msgid "Untitled"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:205
+msgid "No preview"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:224
+msgid "Participants:"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:247
+msgid "Description:"
+msgstr ""
+
+#: ../src/jarabe/journal/expandedentry.py:273
+msgid "Tags:"
+msgstr ""
+
+#: ../src/jarabe/journal/journalactivity.py:108
+#: ../src/jarabe/journal/volumestoolbar.py:47
+msgid "Journal"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:65
+msgid "Search"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:124
+msgid "Anytime"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:126
+msgid "Today"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:128
+msgid "Since yesterday"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 7 days.
+#: ../src/jarabe/journal/journaltoolbox.py:130
+msgid "Past week"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 30 days.
+#: ../src/jarabe/journal/journaltoolbox.py:132
+msgid "Past month"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 356 days.
+#: ../src/jarabe/journal/journaltoolbox.py:134
+msgid "Past year"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:141
+msgid "Anyone"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:143
+msgid "My friends"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:144
+msgid "My class"
+msgstr ""
+
+#. TRANS: Item in a combo box that filters by entry type.
+#: ../src/jarabe/journal/journaltoolbox.py:271
+msgid "Anything"
+msgstr ""
+
+#: ../src/jarabe/journal/journaltoolbox.py:347
+#: ../src/jarabe/journal/palettes.py:81
+msgid "Copy"
+msgstr ""
+
+#. TRANS: Action label for starting an entry.
+#: ../src/jarabe/journal/journaltoolbox.py:428
+#: ../src/jarabe/journal/palettes.py:66 ../src/jarabe/view/palettes.py:111
+msgid "Start"
+msgstr ""
+
+#: ../src/jarabe/journal/listview.py:40
+msgid "Your Journal is empty"
+msgstr ""
+
+#: ../src/jarabe/journal/listview.py:41
+msgid "No matching entries "
+msgstr ""
+
+#: ../src/jarabe/journal/listview.py:369
+msgid "Clear search"
+msgstr ""
+
+#: ../src/jarabe/journal/misc.py:92
+msgid "No date"
+msgstr ""
+
+#: ../src/jarabe/journal/modalalert.py:63
+msgid "Your Journal is full"
+msgstr ""
+
+#: ../src/jarabe/journal/modalalert.py:67
+msgid "Please delete some old Journal entries to make space for new ones."
+msgstr ""
+
+#: ../src/jarabe/journal/modalalert.py:79
+msgid "Show Journal"
+msgstr ""
+
+#: ../src/jarabe/journal/objectchooser.py:136
+msgid "Choose an object"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:64
+msgid "Resume with"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:67
+msgid "Start with"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:89
+msgid "Send to"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:167
+msgid "No friends present"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:172
+msgid "No valid connection found"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:200
+msgid "No activity to resume entry"
+msgstr ""
+
+#: ../src/jarabe/journal/palettes.py:202
+msgid "No activity to start entry"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:61
+msgid "Remove friend"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:64
+msgid "Make friend"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:81
+msgid "My Settings"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:86
+msgid "Logout"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:91
+msgid "Restart"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:96
+msgid "Shutdown"
+msgstr ""
+
+#: ../src/jarabe/view/buddymenu.py:131
+#, python-format
+msgid "Invite to %s"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:43
+msgid "Starting..."
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:73
+msgid "Stop"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:145
+msgid "Remove favorite"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:149
+msgid "Make favorite"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:201
+msgid "Show contents"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:223 ../src/jarabe/view/palettes.py:272
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr ""
+
+#: ../src/jarabe/view/palettes.py:248
+msgid "Unmount"
+msgstr ""
diff --git a/shell/po/ur.po b/shell/po/ur.po
new file mode 100644
index 0000000..1bc0263
--- /dev/null
+++ b/shell/po/ur.po
@@ -0,0 +1,743 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-07-23 07:44-0400\n"
+"PO-Revision-Date: 2008-08-05 23:34-0400\n"
+"Last-Translator: Huda Sarfraz <huda.sarfraz@nu.edu.pk>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Pootle 1.1.0rc2\n"
+
+#: ../src/intro/intro.py:65 ../src/controlpanel/view/aboutme.py:100
+msgid "Name:"
+msgstr "نام:"
+
+#: ../src/intro/intro.py:94
+msgid "Click to change color:"
+msgstr "رنگ تبديل کرنے کے ليے کلک کريں:"
+
+#: ../src/intro/intro.py:145
+msgid "Back"
+msgstr "واپس"
+
+#: ../src/intro/intro.py:159 ../src/controlpanel/toolbar.py:61
+msgid "Done"
+msgstr "مکمل"
+
+#: ../src/intro/intro.py:162
+msgid "Next"
+msgstr "آگے"
+
+#: ../src/view/BuddyMenu.py:58
+msgid "Remove friend"
+msgstr "دوست کو ہٹائيں"
+
+#: ../src/view/BuddyMenu.py:61
+msgid "Make friend"
+msgstr "دوست بنائيں"
+
+#: ../src/view/BuddyMenu.py:91
+#, python-format
+msgid "Invite to %s"
+msgstr "%s کی دعوت دیں"
+
+#: ../src/view/clipboardmenu.py:48
+msgid "Remove"
+msgstr "ہٹائيں"
+
+#: ../src/view/clipboardmenu.py:53 ../src/view/clipboardmenu.py:79
+msgid "Open"
+msgstr "کھوليں"
+
+# self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+# TODO: Implement stopping downloads
+# self._stop_item.connect('activate', self._stop_item_activate_cb)
+# self.append_menu_item(self._stop_item)
+#. self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+#. TODO: Implement stopping downloads
+#. self._stop_item.connect('activate', self._stop_item_activate_cb)
+#. self.append_menu_item(self._stop_item)
+#: ../src/view/clipboardmenu.py:63 ../src/view/home/HomeBox.py:86
+msgid "Keep"
+msgstr "رکھيں"
+
+#: ../src/view/clipboardmenu.py:84
+msgid "Open with"
+msgstr "اس کے ساتھہ کھولیں"
+
+#: ../src/view/clipboardmenu.py:216
+#, python-format
+msgid "Clipboard object: %s."
+msgstr "کلپ بورڈ آبجیکٹ: %s."
+
+#: ../src/hardware/keydialog.py:150
+msgid "Key Type:"
+msgstr "اصل قسم:"
+
+#: ../src/hardware/keydialog.py:170
+msgid "Authentication Type:"
+msgstr "اجاذت دينے کی قسم:"
+
+#: ../src/hardware/keydialog.py:251
+msgid "Encryption Type:"
+msgstr "خفیہ کاری قسم:"
+
+#: ../src/view/Shell.py:240
+msgid "Screenshot"
+msgstr "سکرين شاٹ"
+
+#: ../src/view/home/HomeBox.py:80
+msgid "Confirm erase"
+msgstr "مٹانا کنفرم کريں"
+
+#: ../src/view/home/HomeBox.py:82
+#, python-format
+msgid "Confirm erase: Do you want to permanently erase %s?"
+msgstr "مٹانا تصدیق کريں: کيا آپ مستقل طور پر اس کو مٹانا چاھتے ہيں %s؟"
+
+#: ../src/view/home/HomeBox.py:89 ../src/view/palettes.py:120
+msgid "Erase"
+msgstr "مٹائيں"
+
+#: ../src/view/home/HomeBox.py:215
+msgid "List view"
+msgstr "فہرست نظارہ"
+
+#: ../src/view/home/HomeBox.py:216
+msgid "<Ctrl>2"
+msgstr "<Ctrl>2"
+
+#: ../src/view/home/HomeBox.py:273
+msgid "Favorites view"
+msgstr "پسندیدگان نظارہ"
+
+#: ../src/view/home/HomeBox.py:274
+msgid "<Ctrl>1"
+msgstr "<Ctrl>1"
+
+# TRANS: label for the freeform layout in the favorites view
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/view/home/HomeBox.py:282
+msgid "Freeform"
+msgstr "فری فارم"
+
+# TRANS: label for the ring layout in the favorites view
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/view/home/HomeBox.py:289
+msgid "Ring"
+msgstr "پليٹ"
+
+#: ../src/view/home/MeshBox.py:97
+msgid "Connect"
+msgstr "کنيکٹ"
+
+#: ../src/view/home/MeshBox.py:106
+msgid "Disconnect"
+msgstr "منقطع کریں"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#: ../src/view/home/MeshBox.py:118
+msgid "Disconnecting..."
+msgstr "رابطہ کٹ رہا ہے۔۔۔"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#: ../src/view/home/MeshBox.py:152
+msgid "Connecting..."
+msgstr "کنيکشن مل رہا ہے۔۔۔"
+
+#. TODO: show the channel number
+#: ../src/view/home/MeshBox.py:159
+msgid "Connected"
+msgstr "کنيکشن ہو گيا ہے"
+
+#: ../src/view/home/MeshBox.py:211 ../src/view/devices/network/mesh.py:41
+#: ../src/view/devices/network/mesh.py:68
+#: ../src/view/devices/network/mesh.py:72
+msgid "Mesh Network"
+msgstr "ميش نيٹ ورک"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#: ../src/view/home/MeshBox.py:214 ../src/view/devices/network/wireless.py:119
+#: ../src/view/devices/network/mesh.py:89
+msgid "Disconnect..."
+msgstr "کٹ کريں۔۔۔"
+
+#: ../src/view/home/MeshBox.py:302 ../src/view/palettes.py:61
+msgid "Resume"
+msgstr "پھر شروع کريں"
+
+#: ../src/view/home/MeshBox.py:307 ../src/view/frame/activitiestray.py:205
+msgid "Join"
+msgstr "شرکت کريں"
+
+#: ../src/view/devices/battery.py:45
+msgid "My Battery"
+msgstr "ميری بيٹری"
+
+#: ../src/view/devices/battery.py:114
+msgid "Charging"
+msgstr "چارج کر رہا ہے"
+
+#: ../src/view/devices/battery.py:117
+msgid "Very little power remaining"
+msgstr "پاور بہت کم رہ گئی ہے"
+
+#: ../src/view/devices/battery.py:123
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr "%(hour)d:%(min).2d رہ گئے ہيں"
+
+#: ../src/view/devices/battery.py:127
+msgid "Charged"
+msgstr "چارج ہو گئی ہے"
+
+#: ../src/view/devices/speaker.py:44
+msgid "My Speakers"
+msgstr "ميرے سپيکرز"
+
+#: ../src/view/devices/speaker.py:119
+msgid "Unmute"
+msgstr "ان ميوٹ"
+
+#: ../src/view/devices/speaker.py:122
+msgid "Mute"
+msgstr "ميوٹ"
+
+#: ../src/view/devices/network/wireless.py:67
+msgid "Disconnected"
+msgstr "منقطع ہو گیا"
+
+#: ../src/view/devices/network/wireless.py:137
+msgid "Channel"
+msgstr "چينل"
+
+#: ../src/view/frame/zoomtoolbar.py:34
+msgid "Neighborhood"
+msgstr "ميرے پڑوسی"
+
+#: ../src/view/frame/zoomtoolbar.py:36
+msgid "Group"
+msgstr "گروپ"
+
+#: ../src/view/frame/zoomtoolbar.py:38
+msgid "Home"
+msgstr "گھر"
+
+#: ../src/view/frame/zoomtoolbar.py:40
+msgid "Activity"
+msgstr "سرگرمی"
+
+#: ../src/controlpanel/cmd.py:26
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr "شوگر-کنٹرول-پينل:ايک ہی نام کے ساتھہ کافی آپشن ملی ہيں: %s موڈيول: %r"
+
+#: ../src/controlpanel/cmd.py:28
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr "شوگر-کنٹرول-پينل: کی=%s يہ آپشن موجود نہيں ہے"
+
+#: ../src/controlpanel/cmd.py:29
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr "شوگر-کنٹرول-پينل:%s "
+
+#: ../src/controlpanel/cmd.py:33
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" "
+msgstr ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+"Control for the sugar environment. \n"
+"Options: \n"
+"-h show this help message and exit \n"
+"-l list all the available options \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"
+" "
+
+#: ../src/controlpanel/cmd.py:45
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr "نئی تبديلیاں عمل میں لانے کے لیے آپ کو شوگر پھر شروع کرنا ہو گا۔\n"
+
+#: ../src/controlpanel/toolbar.py:115
+msgid "Cancel"
+msgstr "منسوخ کريں"
+
+#: ../src/controlpanel/toolbar.py:121 ../src/view/home/favoritesview.py:294
+msgid "Ok"
+msgstr "ٹھیک ہے"
+
+#: ../src/controlpanel/sectionview.py:34 ../src/controlpanel/gui.py:260
+msgid "Changes require restart"
+msgstr "دوبارہ چلا کر تبديلياں عمل ميں لائيں"
+
+#: ../src/controlpanel/gui.py:259
+msgid "Warning"
+msgstr "وارننگ"
+
+#: ../src/controlpanel/gui.py:263
+msgid "Cancel changes"
+msgstr "تبديلياں ختم کريں"
+
+#: ../src/controlpanel/gui.py:267
+msgid "Later"
+msgstr "بعد ميں"
+
+#: ../src/controlpanel/gui.py:271
+msgid "Restart now"
+msgstr "ابھی دوبارہ چلائيں"
+
+#: ../src/controlpanel/model/aboutme.py:44
+msgid "You must enter a name."
+msgstr "آپ کو ضرور ايک نام دينا ہے"
+
+#: ../src/controlpanel/model/aboutme.py:69
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr "سٹروک: رنگ=%s ہيو=%s"
+
+#: ../src/controlpanel/model/aboutme.py:72
+#, python-format
+msgid "stroke: %s"
+msgstr "سٹروک: %s"
+
+#: ../src/controlpanel/model/aboutme.py:74
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr "بھر ديں: رنگ=%s ہيو=%s"
+
+#: ../src/controlpanel/model/aboutme.py:76
+#, python-format
+msgid "fill: %s"
+msgstr "بھر ديں: %s"
+
+#: ../src/controlpanel/model/aboutme.py:87
+msgid "Error in specified color modifiers."
+msgstr "اختصاص کردہ رنگ ترمیم کاروں میں نقص۔"
+
+#: ../src/controlpanel/model/aboutme.py:90
+msgid "Error in specified colors."
+msgstr "اختصاص کردہ رنگوں میں نقص۔"
+
+#: ../src/controlpanel/model/aboutxo.py:24
+msgid "Not available"
+msgstr "دستیاب نہيں"
+
+#: ../src/controlpanel/model/datetime.py:85
+msgid "Error timezone does not exist."
+msgstr "نقص، ٹائیم زون موجود نہیں ہے۔"
+
+#: ../src/controlpanel/model/frame.py:38 ../src/controlpanel/model/frame.py:60
+msgid "Value must be an integer."
+msgstr "انٹيجر ويليو ہونی چاھيے۔"
+
+#: ../src/controlpanel/model/language.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr "رسائی ممکن نہيں~/.n18. سٹينڈرڈ بنائيں."
+
+#: ../src/controlpanel/model/language.py:104
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr "کوڈ=%s کی زبان تعین نہیں کی جا سکی۔"
+
+#: ../src/controlpanel/model/language.py:121
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr "معاف کيجيے ميں '%s' نہيں بولتا/بولتی۔"
+
+#: ../src/controlpanel/model/network.py:48
+msgid "You must enter a server."
+msgstr "ايک سکول سرور ضرور لکھیں۔"
+
+#: ../src/controlpanel/model/network.py:63
+msgid "State is unknown."
+msgstr "نامعلوم حالت"
+
+#: ../src/controlpanel/model/network.py:83
+msgid "Error in specified radio argument use on/off."
+msgstr "اختصاص کردہ ریڈیو آرگیومنٹ میں نقص، آن/آف استعمال کریں۔"
+
+#: ../src/controlpanel/model/power.py:57
+msgid "Error in automatic pm argument, use on/off."
+msgstr "خودکار pm برہان میں نقص، آن/آف استعمال کریں۔"
+
+#: ../src/controlpanel/model/power.py:86
+msgid "Error in extreme pm argument, use on/off."
+msgstr "ایکسٹریم pm برہان میں نقص، آن/آف استعمال کریں۔"
+
+#: ../src/controlpanel/view/aboutme.py:32
+msgid "About Me"
+msgstr "ميرے بارے ميں"
+
+#: ../src/controlpanel/view/aboutme.py:134
+msgid "Click to change your color:"
+msgstr "رنگ تبديل کرنے کے ليے کلک کريں:"
+
+#: ../src/controlpanel/view/aboutxo.py:26
+msgid "About my XO"
+msgstr "ميرے XO کے بارے ميں"
+
+#: ../src/controlpanel/view/aboutxo.py:47
+msgid "Identity"
+msgstr "پہچان"
+
+#: ../src/controlpanel/view/aboutxo.py:56
+msgid "Serial Number:"
+msgstr "سيريل نمبر:"
+
+#: ../src/controlpanel/view/aboutxo.py:79
+msgid "Software"
+msgstr "Software"
+
+#: ../src/controlpanel/view/aboutxo.py:88
+msgid "Build:"
+msgstr "سسٹم:"
+
+#: ../src/controlpanel/view/aboutxo.py:103
+msgid "Firmware:"
+msgstr "فرم ویئر"
+
+#: ../src/controlpanel/view/datetime.py:29
+msgid "Date & Time"
+msgstr "تاريخ & وقت"
+
+#: ../src/controlpanel/view/datetime.py:72
+msgid "Timezone"
+msgstr "ٹائم زون"
+
+#: ../src/controlpanel/view/frame.py:28
+msgid "Frame"
+msgstr "فريم"
+
+#: ../src/controlpanel/view/frame.py:30
+msgid "never"
+msgstr "کبھی نہیں"
+
+#: ../src/controlpanel/view/frame.py:31
+msgid "instantaneous"
+msgstr "اچانک"
+
+#: ../src/controlpanel/view/frame.py:32
+#, python-format
+msgid "%s seconds"
+msgstr "%s سيکنڈ"
+
+#: ../src/controlpanel/view/frame.py:56
+msgid "Activation Delay"
+msgstr "ايکٹيويشن وقفہ"
+
+#: ../src/controlpanel/view/frame.py:80
+msgid "Corner"
+msgstr "کونہ"
+
+#: ../src/controlpanel/view/frame.py:115
+msgid "Edge"
+msgstr "کنارہ"
+
+#: ../src/controlpanel/view/language.py:29
+#: ../src/controlpanel/view/language.py:74
+msgid "Language"
+msgstr "زبان"
+
+#: ../src/controlpanel/view/network.py:28
+msgid "Network"
+msgstr "نيٹ ورک"
+
+#: ../src/controlpanel/view/network.py:53
+msgid "Wireless"
+msgstr "وائيرليس"
+
+#: ../src/controlpanel/view/network.py:61
+msgid "Radio:"
+msgstr "ریڈيو:"
+
+#: ../src/controlpanel/view/network.py:94
+msgid "Mesh"
+msgstr "ميش"
+
+#: ../src/controlpanel/view/network.py:103
+msgid "Server:"
+msgstr "سکول سرور:"
+
+#: ../src/controlpanel/view/power.py:27
+msgid "Power"
+msgstr "پاور"
+
+#: ../src/controlpanel/view/power.py:51
+msgid "Power management"
+msgstr "پاور کنٹرول"
+
+#: ../src/controlpanel/view/power.py:61
+msgid "Automatic power management (increases battery life)"
+msgstr "آٹو ميٹک پاور کنٹرول(بیٹری کا دورانيہ ذيادہ کرنے کے ليے)"
+
+#: ../src/controlpanel/view/power.py:89
+msgid ""
+"Extreme power management (disables wireless radio, increases battery life)"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:111
+msgid "Connected to a School Mesh Portal"
+msgstr "سکول کے ميش پورٹل کے ساتھہ ملا ہوا ہے"
+
+#: ../src/view/devices/network/mesh.py:113
+msgid "Looking for a School Mesh Portal..."
+msgstr " سکول ميش پورٹل ڈھونڈ رہا ہے ..."
+
+#: ../src/view/devices/network/mesh.py:116
+msgid "Connected to an XO Mesh Portal"
+msgstr "‌XO ميش پورٹل کے ساتھہ جڑا ہوا ہے"
+
+#: ../src/view/devices/network/mesh.py:118
+msgid "Looking for an XO Mesh Portal..."
+msgstr "XO ميش پورٹل ڈھونڈ رہا ہے ..."
+
+#: ../src/view/devices/network/mesh.py:121
+msgid "Connected to a Simple Mesh"
+msgstr "سادہ ميش کے ساتھ جڑا ہوا ہے"
+
+#: ../src/view/devices/network/mesh.py:123
+msgid "Starting a Simple Mesh"
+msgstr "سادہ ميش شروع کر رہا ہے"
+
+#: ../src/view/devices/network/mesh.py:130
+msgid "Unknown Mesh"
+msgstr "نامعلوم ميش"
+
+#: ../src/view/frame/activitiestray.py:210
+msgid "Decline"
+msgstr "منظور نہیں ہوا"
+
+#: ../src/view/home/favoritesview.py:285
+msgid "Registration Failed"
+msgstr "رجسٹريشن نہيں ہوئی"
+
+#: ../src/view/home/favoritesview.py:286
+#, python-format
+msgid "%s"
+msgstr "%s"
+
+#: ../src/view/home/favoritesview.py:288
+msgid "Registration Successful"
+msgstr "رجسٹريشن ہو گئی"
+
+#: ../src/view/home/favoritesview.py:289
+msgid "You are now registered with your school server."
+msgstr "اب آپ اپنے سکول سرور کے ساتھہ رجسٹرڈ ہيں۔"
+
+#: ../src/view/home/favoritesview.py:405
+msgid "Control Panel"
+msgstr "کنٹرول پينل"
+
+#: ../src/view/home/favoritesview.py:416
+msgid "Restart"
+msgstr "دوبارہ چلائيں"
+
+#: ../src/view/home/favoritesview.py:421
+msgid "Shutdown"
+msgstr "بند کريں"
+
+#: ../src/view/home/favoritesview.py:427
+msgid "Register"
+msgstr "رجسٹر کريں"
+
+#: ../src/view/palettes.py:42
+msgid "Starting..."
+msgstr "شروع ہو رہا ہے ..."
+
+#: ../src/view/palettes.py:72
+msgid "Stop"
+msgstr "روکیں"
+
+#: ../src/view/palettes.py:104
+msgid "Start"
+msgstr "شروع کريں"
+
+#: ../src/view/palettes.py:132
+msgid "Remove favorite"
+msgstr "پسنديدہ ختم کريں"
+
+#: ../src/view/palettes.py:136
+msgid "Make favorite"
+msgstr "پسندیدہ بنائيں"
+
+#: ../src/view/palettes.py:185
+msgid "Show contents"
+msgstr "مشمولات دکھائيں"
+
+#: ../src/view/palettes.py:209
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr "%(free_space)d MB dخالی"
+
+#~ msgid "<Ctrl>R"
+#~ msgstr "<Ctrl>R"
+
+#~ msgid "off"
+#~ msgstr "آف"
+
+#~ msgid "on"
+#~ msgstr "آن"
+
+#~ msgid "Permission denied. You need to be root to run this method."
+#~ msgstr "اجازت سے انکار۔ یہ میتھڈ چلانے کے لیے آپ کو بھر بوٹ کرنا ہو گا۔"
+
+#~ msgid "Error in reading timezone"
+#~ msgstr "ٹائیم زون پڑھنے میں نقص"
+
+#, python-format
+#~ msgid "Error copying timezone (from %s): %s"
+#~ msgstr "ٹائیم زون نقل کرنے میں (%s سے) نقص: %s"
+
+#, python-format
+#~ msgid "Changing permission of timezone: %s"
+#~ msgstr "ٹائیم زون کی اجازت بدل رہا ہے: %s"
+
+#~ msgid "About this XO"
+#~ msgstr "اس XO کے بارے ميں"
+
+#~ msgid "Add to journal"
+#~ msgstr "جریدے ميں شامل کريں"
+
+#~ msgid "Reboot"
+#~ msgstr "پھر بوٹ کریں"
+
+#~ msgid "My Battery life"
+#~ msgstr "ميری بيٹری کی زندگی"
+
+#~ msgid "Battery charging"
+#~ msgstr "بيٹری چارج ہو رہی ہے"
+
+#~ msgid "Battery discharging"
+#~ msgstr "بیٹری ڈسچارج ہو رہی ہے"
+
+#~ msgid "Battery fully charged"
+#~ msgstr "ببيٹری پوری چارج ہے"
+
+#~ msgid "Share with:"
+#~ msgstr "حصہ داری کریں از:"
+
+#~ msgid "Private"
+#~ msgstr "ذاتی"
+
+#~ msgid "My Neighborhood"
+#~ msgstr "ميرا گرد و نواح"
+
+#~ msgid "Undo"
+#~ msgstr "کالعدم کريں"
+
+#~ msgid "Redo"
+#~ msgstr "اعادہ کريں"
+
+#~ msgid "Copy"
+#~ msgstr "نفل کريں"
+
+#~ msgid "Paste"
+#~ msgstr "جوڑيں"
+
+#, python-format
+#~ msgid "%s Activity"
+#~ msgstr "%s سرگرمی"
+
+#~ msgid "Keep error"
+#~ msgstr "رکھنے میں نقص"
+
+#~ msgid "Keep error: all changes will be lost"
+#~ msgstr "رکھنے میں نقص: تمام تبديلياں ضائع ہو جائیں گی"
+
+#~ msgid "Don't stop"
+#~ msgstr "نہیں روکیں"
+
+#~ msgid "Stop anyway"
+#~ msgstr "پھر بھی روکيں"
+
+#~ msgid "Continue"
+#~ msgstr "جاری رکھیں"
+
+#~ msgid "OK"
+#~ msgstr "ٹھیک ہے"
+
+#, python-format
+#~ msgid "%d year"
+#~ msgstr "%d سال"
+
+#, python-format
+#~ msgid "%d years"
+#~ msgstr "%d سال"
+
+#, python-format
+#~ msgid "%d month"
+#~ msgstr "%d مہينہ"
+
+#, python-format
+#~ msgid "%d months"
+#~ msgstr "%d مہينے"
+
+#, python-format
+#~ msgid "%d week"
+#~ msgstr "%d ہفتہ"
+
+#, python-format
+#~ msgid "%d weeks"
+#~ msgstr "%d ہفتے"
+
+#, python-format
+#~ msgid "%d day"
+#~ msgstr "%d دن"
+
+#, python-format
+#~ msgid "%d days"
+#~ msgstr "%d دن"
+
+#, python-format
+#~ msgid "%d hour"
+#~ msgstr "%d گھنٹہ"
+
+#, python-format
+#~ msgid "%d hours"
+#~ msgstr "%d گھنٹے"
+
+#, python-format
+#~ msgid "%d minute"
+#~ msgstr "%d منٹ"
+
+#, python-format
+#~ msgid "%d minutes"
+#~ msgstr "%d منٹ"
+
+#, python-format
+#~ msgid "%d second"
+#~ msgstr "%d سيکن"
+
+#~ msgid " and "
+#~ msgstr "_اور_"
+
+#~ msgid ", "
+#~ msgstr "،_"
diff --git a/shell/po/vi.po b/shell/po/vi.po
new file mode 100644
index 0000000..84268c2
--- /dev/null
+++ b/shell/po/vi.po
@@ -0,0 +1,1415 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-02-11 00:32-0500\n"
+"PO-Revision-Date: 2010-03-15 05:42+0200\n"
+"Last-Translator: Chris <cjl@laptop.org>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: vi\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Pootle 2.0.3\n"
+
+#: ../extensions/cpsection/aboutme/__init__.py:24
+msgid "About Me"
+msgstr "Giới thiệu mình"
+
+#: ../extensions/cpsection/aboutme/model.py:43
+msgid "You must enter a name."
+msgstr "Bạn cần phải nhập một tên."
+
+#: ../extensions/cpsection/aboutme/model.py:68
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr "nét vẽ: màu=%s sắc=%s"
+
+#: ../extensions/cpsection/aboutme/model.py:71
+#, python-format
+msgid "stroke: %s"
+msgstr "nét vẽ: %s"
+
+#: ../extensions/cpsection/aboutme/model.py:73
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr "tô màu : màu=%s sắc=%s"
+
+#: ../extensions/cpsection/aboutme/model.py:75
+#, python-format
+msgid "fill: %s"
+msgstr "tô màu : %s"
+
+#: ../extensions/cpsection/aboutme/model.py:86
+msgid "Error in specified color modifiers."
+msgstr "Sai xác định sự sửa đổi màu."
+
+#: ../extensions/cpsection/aboutme/model.py:89
+msgid "Error in specified colors."
+msgstr "Sai xác định màu."
+
+#: ../extensions/cpsection/aboutme/view.py:94 ../src/jarabe/intro/window.py:93
+msgid "Name:"
+msgstr "Tên:"
+
+#: ../extensions/cpsection/aboutme/view.py:128
+msgid "Click to change your color:"
+msgstr "Nhấn chuột để thay đổi màu :"
+
+#: ../extensions/cpsection/aboutcomputer/__init__.py:21
+msgid "About my Computer"
+msgstr "Giới thiệu máy tính này"
+
+#: ../extensions/cpsection/aboutcomputer/model.py:28
+msgid "Not available"
+msgstr "Không sẵn sàng"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:60
+msgid "Identity"
+msgstr "Cá tính"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:69
+msgid "Serial Number:"
+msgstr "Số sản xuất:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:91
+msgid "Software"
+msgstr "Phần mềm"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:100
+msgid "Build:"
+msgstr "Bản xây dựng:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:115
+msgid "Sugar:"
+msgstr "Sugar:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:131
+msgid "Firmware:"
+msgstr "Phần vững:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:146
+msgid "Wireless Firmware:"
+msgstr "Phần vững không dây:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:169
+msgid "Copyright and License"
+msgstr "Tác quyền và Giấy phép"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:184
+msgid ""
+"Sugar is the graphical user interface that you are looking at. Sugar is free "
+"software, covered by the GNU General Public License, and you are welcome to "
+"change it and/or distribute copies of it under certain conditions described "
+"therein."
+msgstr ""
+"Sugar là giao diện người dùng đồ họa thích hợp với bạn. Sugar là phần mềm tự "
+"do, được bao quát bởi Giấy Phép Công Cộng GNU (GPL), thì bạn có quyền sửa "
+"đổi và/hay phân phối lại bản sao nó với các điều kiện GPL."
+
+#: ../extensions/cpsection/aboutcomputer/view.py:196
+msgid "Full license:"
+msgstr "Giấy phép đầy đủ :"
+
+#: ../extensions/cpsection/datetime/__init__.py:21
+msgid "Date & Time"
+msgstr "Ngày và Giờ"
+
+#: ../extensions/cpsection/datetime/model.py:87
+msgid "Error timezone does not exist."
+msgstr "Lỗi: múi giờ không tồn tại."
+
+#: ../extensions/cpsection/datetime/view.py:68 ../data/sugar.schemas.in.h:33
+msgid "Timezone"
+msgstr "Múi giờ"
+
+#: ../extensions/cpsection/frame/__init__.py:21
+msgid "Frame"
+msgstr "Khung"
+
+#: ../extensions/cpsection/frame/model.py:38
+#: ../extensions/cpsection/frame/model.py:60
+msgid "Value must be an integer."
+msgstr "Giá trị phải là một số nguyên."
+
+#: ../extensions/cpsection/frame/view.py:26
+msgid "never"
+msgstr "không bao giờ"
+
+#: ../extensions/cpsection/frame/view.py:27
+msgid "instantaneous"
+msgstr "ngay lập tức"
+
+#: ../extensions/cpsection/frame/view.py:28
+#, python-format
+msgid "%s seconds"
+msgstr "%s giây"
+
+#: ../extensions/cpsection/frame/view.py:52
+msgid "Activation Delay"
+msgstr "Khoảng đợi kích hoạt"
+
+#: ../extensions/cpsection/frame/view.py:76
+msgid "Corner"
+msgstr "Góc"
+
+#: ../extensions/cpsection/frame/view.py:111
+msgid "Edge"
+msgstr "Cạnh"
+
+#: ../extensions/cpsection/keyboard/__init__.py:21
+#: ../extensions/cpsection/keyboard/view.py:31
+msgid "Keyboard"
+msgstr "Bàn phím"
+
+#: ../extensions/cpsection/keyboard/view.py:189
+msgid "Keyboard Model"
+msgstr "Mô hình bàn phím"
+
+#: ../extensions/cpsection/keyboard/view.py:248
+msgid "Key(s) to change layout"
+msgstr "(Tổ hợp) phím để chuyển đổi bố trí"
+
+#: ../extensions/cpsection/keyboard/view.py:318
+msgid "Keyboard Layout(s)"
+msgstr "Bố trí Bàn phím"
+
+#: ../extensions/cpsection/language/__init__.py:21
+#: ../extensions/cpsection/language/view.py:33
+msgid "Language"
+msgstr "Ngôn ngữ"
+
+#: ../extensions/cpsection/language/model.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr ""
+"Không thể truy cập đến thư mục « ~/.i18n ». Hãy tạo thiết lập tiêu chuẩn."
+
+#: ../extensions/cpsection/language/model.py:124
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr "Không thể quyết định ngôn ngữ cho mã=%s."
+
+#: ../extensions/cpsection/language/model.py:144
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr "Tiếc là chương trình này không hiểu « %s »."
+
+#: ../extensions/cpsection/language/view.py:56
+msgid ""
+"Add languages in the order you prefer. If a translation is not available, "
+"the next in the list will be used."
+msgstr ""
+"Thêm các ngôn ngữ theo thứ tự đã thích. Nếu không có bản dịch thì dùng ngôn "
+"ngữ kế tiếp trong danh sách này."
+
+#: ../extensions/cpsection/modemconfiguration/__init__.py:21
+msgid "Modem Configuration"
+msgstr "Cấu hình mô-đêm"
+
+#: ../extensions/cpsection/modemconfiguration/view.py:90
+msgid "Username:"
+msgstr "Tên người dùng:"
+
+#: ../extensions/cpsection/modemconfiguration/view.py:101
+msgid "Password:"
+msgstr "Mật khẩu :"
+
+#: ../extensions/cpsection/modemconfiguration/view.py:112
+msgid "Number:"
+msgstr "Số :"
+
+#: ../extensions/cpsection/modemconfiguration/view.py:123
+msgid "APN:"
+msgstr "APN:"
+
+#: ../extensions/cpsection/network/__init__.py:21
+#: ../extensions/cpsection/network/view.py:28
+msgid "Network"
+msgstr "Mạng"
+
+#: ../extensions/cpsection/network/model.py:79
+msgid "State is unknown."
+msgstr "Không rõ tình trạng."
+
+#: ../extensions/cpsection/network/model.py:105
+msgid "Error in specified radio argument use on/off."
+msgstr "Lỗi trong đối số chọn một đã ghi rõ bật/tắt."
+
+#: ../extensions/cpsection/network/model.py:137
+msgid "Error in specified argument use 0/1."
+msgstr "Lỗi trong đối số chỉ định: dùng 0/1."
+
+#: ../extensions/cpsection/network/view.py:59
+msgid "Wireless"
+msgstr "Không dây"
+
+#: ../extensions/cpsection/network/view.py:67
+msgid "Turn off the wireless radio to save battery life"
+msgstr "Tắt máy thu thanh không dây để tiết kiệm thời gian chạy bằng pin"
+
+#: ../extensions/cpsection/network/view.py:80
+msgid "Radio"
+msgstr "Thu thanh"
+
+#: ../extensions/cpsection/network/view.py:96
+msgid "Discard network history if you have trouble connecting to the network"
+msgstr "Hủy lịch sử mạng nếu bạn gặp khó khăn trong việc kết nối tới mạng"
+
+#: ../extensions/cpsection/network/view.py:105
+msgid "Discard network history"
+msgstr "Hủy lịch sử mạng"
+
+#: ../extensions/cpsection/network/view.py:118
+msgid "Collaboration"
+msgstr "Hợp tác"
+
+#: ../extensions/cpsection/network/view.py:126
+msgid ""
+"The server is the equivalent of what room you are in; people on the same "
+"server will be able to see each other, even when they aren't on the same "
+"network."
+msgstr ""
+"Máy phục vụ giống như một phòng chứa vài người khác nhau : những người cùng "
+"phòng (trên cùng một máy phục vụ) thì có thể liên lạc với nhau, ngay cả khi "
+"không phải làm cùng một việc (trên cùng một mạng)."
+
+#: ../extensions/cpsection/network/view.py:136
+msgid "Server:"
+msgstr "Máy phục vụ :"
+
+#: ../extensions/cpsection/power/__init__.py:21
+msgid "Power"
+msgstr "Năng lượng"
+
+#: ../extensions/cpsection/power/model.py:54
+msgid "Error in automatic pm argument, use on/off."
+msgstr "Lỗi trong đối số tự động pm: dùng on/off."
+
+#: ../extensions/cpsection/power/model.py:81
+msgid "Error in extreme pm argument, use on/off."
+msgstr "Lỗi trong đối số extreme pm: dùng on/off."
+
+#: ../extensions/cpsection/power/view.py:47
+msgid "Power management"
+msgstr "Quản lý năng lượng"
+
+#: ../extensions/cpsection/power/view.py:57
+msgid "Automatic power management (increases battery life)"
+msgstr "Tự động quản lý năng lượng (tăng thời gian chạy bằng pin)"
+
+#: ../extensions/cpsection/power/view.py:85
+msgid ""
+"Extreme power management (disableswireless radio, increases battery life)"
+msgstr ""
+"Rất quản lý năng lượng (tắt máy thu thanh không dây, tăng thời gian chạy "
+"bằng pin)"
+
+#: ../extensions/cpsection/updater/__init__.py:21
+msgid "Software update"
+msgstr "Cập nhật phần mềm"
+
+#: ../extensions/cpsection/updater/view.py:63
+msgid ""
+"Software updates correct errors, eliminate security vulnerabilities, and "
+"provide new features."
+msgstr ""
+"Bản cập nhật phần mềm thì sửa chữa lỗi, loại trừ trường hợp có thể bị lỗ "
+"hổng bảo mật, và cung cấp tính năng mới."
+
+#: ../extensions/cpsection/updater/view.py:125
+#, python-format
+msgid "Checking %s..."
+msgstr "Đang kiểm tra %s..."
+
+#: ../extensions/cpsection/updater/view.py:127
+#, python-format
+msgid "Downloading %s..."
+msgstr "Đang tải về %s..."
+
+#: ../extensions/cpsection/updater/view.py:129
+#, python-format
+msgid "Updating %s..."
+msgstr "Đang cập nhật %s..."
+
+#: ../extensions/cpsection/updater/view.py:139
+msgid "Your software is up-to-date"
+msgstr "Phần mềm vẫn còn hiện thời"
+
+#: ../extensions/cpsection/updater/view.py:141
+#, python-format
+msgid "You can install %s update"
+msgid_plural "You can install %s updates"
+msgstr[0] "Bạn có dịp cài đặt %s bản cập nhật"
+
+#: ../extensions/cpsection/updater/view.py:159
+msgid "Checking for updates..."
+msgstr "Đang kiểm tra có bản cập nhật chưa..."
+
+#: ../extensions/cpsection/updater/view.py:164
+msgid "Installing updates..."
+msgstr "Đang cài đặt bản cập nhật..."
+
+#: ../extensions/cpsection/updater/view.py:172
+#, python-format
+msgid "%s update was installed"
+msgid_plural "%s updates were installed"
+msgstr[0] "%s bản cập nhật đã được cài đặt"
+
+#: ../extensions/cpsection/updater/view.py:253
+msgid "Install selected"
+msgstr "Cài đặt mục đã chọn"
+
+#: ../extensions/cpsection/updater/view.py:274
+#, python-format
+msgid "Download size: %s"
+msgstr "Kích cỡ tải về: %s"
+
+#: ../extensions/cpsection/updater/view.py:362
+#, python-format
+msgid "From version %(current)d to %(new)s (Size: %(size)s)"
+msgstr "Từ phiên bản %(current)d lên %(new)s (Kích cỡ : %(size)s)"
+
+#. TRANS: download size is 0
+#: ../extensions/cpsection/updater/view.py:382
+msgid "None"
+msgstr "Không có"
+
+#. TRANS: download size of very small updates
+#: ../extensions/cpsection/updater/view.py:385
+msgid "1 KB"
+msgstr "1 KB"
+
+#. TRANS: download size of small updates, e.g. '250 KB'
+#: ../extensions/cpsection/updater/view.py:388
+#, python-format
+msgid "%.0f KB"
+msgstr "%.0f KB"
+
+#. TRANS: download size of updates, e.g. '2.3 MB'
+#: ../extensions/cpsection/updater/view.py:391
+#, python-format
+msgid "%.1f MB"
+msgstr "%.1f MB"
+
+#: ../extensions/deviceicon/battery.py:58
+msgid "My Battery"
+msgstr "Pin của mình"
+
+#: ../extensions/deviceicon/battery.py:137
+msgid "Removed"
+msgstr "Bị bỏ"
+
+#: ../extensions/deviceicon/battery.py:140
+msgid "Charging"
+msgstr "Đang sạc"
+
+#: ../extensions/deviceicon/battery.py:143
+msgid "Very little power remaining"
+msgstr "Pin yếu tới hạn"
+
+#: ../extensions/deviceicon/battery.py:149
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr "%(hour)d:%(min).2d còn lại"
+
+#: ../extensions/deviceicon/battery.py:152
+msgid "Charged"
+msgstr "Sạc đầy"
+
+#: ../extensions/deviceicon/network.py:49
+#, python-format
+msgid "IP address: %s"
+msgstr "Địa chỉ IP: %s"
+
+#: ../extensions/deviceicon/network.py:111
+msgid "Disconnect..."
+msgstr "Ngắt kết nối..."
+
+#: ../extensions/deviceicon/network.py:116
+msgid "Create new wireless network"
+msgstr "Tạo mạng vô tuyến mới"
+
+#: ../extensions/deviceicon/network.py:122
+#: ../extensions/deviceicon/network.py:284
+#: ../src/jarabe/desktop/meshbox.py:248 ../src/jarabe/desktop/meshbox.py:537
+msgid "Connecting..."
+msgstr "Đang kết nối..."
+
+# TODO: show the channel number
+#: ../extensions/deviceicon/network.py:126
+#: ../extensions/deviceicon/network.py:198
+#: ../extensions/deviceicon/network.py:288
+#: ../src/jarabe/desktop/meshbox.py:254 ../src/jarabe/desktop/meshbox.py:543
+msgid "Connected"
+msgstr "Đã kết nối"
+
+#: ../extensions/deviceicon/network.py:158
+msgid "Channel"
+msgstr "Kênh"
+
+#: ../extensions/deviceicon/network.py:173
+msgid "Wired Network"
+msgstr "Mạng theo dây"
+
+#: ../extensions/deviceicon/network.py:201
+msgid "Speed"
+msgstr "Tốc độ"
+
+#: ../extensions/deviceicon/network.py:228
+msgid "Wireless modem"
+msgstr "Mô-đem vô tuyến"
+
+#: ../extensions/deviceicon/network.py:276
+msgid "Please wait..."
+msgstr "Hãy đợi..."
+
+#: ../extensions/deviceicon/network.py:279
+#: ../src/jarabe/desktop/meshbox.py:164 ../src/jarabe/desktop/meshbox.py:494
+msgid "Connect"
+msgstr "Kết nối"
+
+#: ../extensions/deviceicon/network.py:280
+msgid "Disconnected"
+msgstr "Bị ngắt kết nối"
+
+#: ../extensions/deviceicon/network.py:283
+#: ../src/jarabe/controlpanel/toolbar.py:115
+#: ../src/jarabe/desktop/homebox.py:68
+#: ../src/jarabe/frame/activitiestray.py:700
+#: ../src/jarabe/frame/activitiestray.py:799
+#: ../src/jarabe/frame/activitiestray.py:827
+msgid "Cancel"
+msgstr "Thôi"
+
+#: ../extensions/deviceicon/network.py:287
+#: ../src/jarabe/desktop/meshbox.py:168
+msgid "Disconnect"
+msgstr "Ngắt kết nối"
+
+#: ../extensions/deviceicon/network.py:530
+#, python-format
+msgid "%s's network"
+msgstr "Mạng của %s"
+
+#: ../extensions/deviceicon/network.py:597
+#: ../extensions/deviceicon/network.py:656
+msgid "Mesh Network"
+msgstr "Mạng mắc lưới"
+
+#: ../extensions/deviceicon/network.py:857
+#, python-format
+msgid "Data sent %d kb / received %d kb"
+msgstr "Dữ liệu đã gửi %d kb / đã nhận %d kb"
+
+#: ../extensions/deviceicon/network.py:868
+msgid "Connection time "
+msgstr "Thời gian kết nối "
+
+#: ../extensions/deviceicon/speaker.py:59
+msgid "My Speakers"
+msgstr "Loa của mình"
+
+#: ../extensions/deviceicon/speaker.py:133
+msgid "Unmute"
+msgstr "Bỏ câm"
+
+#: ../extensions/deviceicon/speaker.py:136
+msgid "Mute"
+msgstr "Câm"
+
+#: ../extensions/globalkey/screenshot.py:59
+msgid "Mesh"
+msgstr "Mesh"
+
+#: ../extensions/globalkey/screenshot.py:61
+#: ../src/jarabe/frame/zoomtoolbar.py:39
+msgid "Group"
+msgstr "Nhóm"
+
+#: ../extensions/globalkey/screenshot.py:63
+#: ../src/jarabe/frame/zoomtoolbar.py:41
+msgid "Home"
+msgstr "Nhà"
+
+#: ../extensions/globalkey/screenshot.py:69
+#: ../src/jarabe/frame/zoomtoolbar.py:43
+msgid "Activity"
+msgstr "Hoạt động"
+
+#: ../extensions/globalkey/screenshot.py:72
+msgid "Screenshot"
+msgstr "Ảnh chụp màn hình"
+
+#: ../extensions/globalkey/screenshot.py:74
+#, python-format
+msgid "Screenshot of \"%s\""
+msgstr "Ảnh chụp màn hình « %s »"
+
+#: ../data/sugar.schemas.in.h:1
+msgid ""
+"\"disabled\" to ask nick on initialization; \"system\" to reuse UNIX account "
+"long name."
+msgstr ""
+"« bị tắt » để yêu cầu tên hiệu khi sơ khởi; « hệ thống » để dùng lại tên dài "
+"của tài khoản UNIX."
+
+#: ../data/sugar.schemas.in.h:2
+msgid "Backup URL"
+msgstr "URL sao lưu"
+
+#: ../data/sugar.schemas.in.h:3
+msgid ""
+"Color for the XO icon that is used throughout the desktop. The string is "
+"composed of the stroke color and fill color, format is that of rbg colors. "
+"Example: #AC32FF,#9A5200"
+msgstr ""
+"Màu của biểu tượng XO được dùng trong toàn môi trường. Chuỗi bao gồm màu nét "
+"và màu tô đầy, định dạng là RBG, v.d. #AC32FF,#9A5200"
+
+#: ../data/sugar.schemas.in.h:4
+msgid "Corner Delay"
+msgstr "Trễ góc"
+
+#: ../data/sugar.schemas.in.h:5
+msgid "Default font face"
+msgstr "Mặt chữ mặc định"
+
+#: ../data/sugar.schemas.in.h:6
+msgid "Default font size"
+msgstr "Cỡ phông mặc định"
+
+#: ../data/sugar.schemas.in.h:7
+msgid "Default nick"
+msgstr "Tên hiệu mặc định"
+
+#: ../data/sugar.schemas.in.h:8
+msgid "Delay for the activation of the frame using the corners."
+msgstr "Khoảng đợi khi kích hoạt khung dùng góc."
+
+#: ../data/sugar.schemas.in.h:9
+msgid "Delay for the activation of the frame using the edges."
+msgstr "Khoảng đợi khi kích hoạt khung dùng cạnh."
+
+#: ../data/sugar.schemas.in.h:10
+msgid "Edge Delay"
+msgstr "Trễ cạnh"
+
+#: ../data/sugar.schemas.in.h:11
+msgid "Favorites Layout"
+msgstr "Bố trí Ưa thích"
+
+#: ../data/sugar.schemas.in.h:12
+msgid "Favorites resume mode"
+msgstr "Chế độ tiếp tục lại mục ưa thích"
+
+#: ../data/sugar.schemas.in.h:13
+msgid "Font face that is used throughout the desktop."
+msgstr "Mặt phông chữ được sử dụng trên toàn bộ môi trường."
+
+#: ../data/sugar.schemas.in.h:14
+msgid "Font size that is used throughout the desktop."
+msgstr "Kích cỡ phông chữ được sử dụng trên toàn bộ môi trường."
+
+#: ../data/sugar.schemas.in.h:15
+msgid ""
+"If TRUE, Sugar will make us searchable for the other users of the Jabber "
+"server."
+msgstr ""
+"TRUE (đúng) thì Sugar sẽ cấp các người khác trên máy phục vụ Jabber có quyền "
+"tìm kiếm qua chúng ta."
+
+#: ../data/sugar.schemas.in.h:16
+msgid "If TRUE, Sugar will show a \"Log out\" option."
+msgstr "TRUE (đúng) thì Sugar hiển thị một tùy chọn « Đăng xuất »."
+
+#: ../data/sugar.schemas.in.h:17
+msgid "Jabber Server"
+msgstr "Máy phục vụ Jabber"
+
+#: ../data/sugar.schemas.in.h:18
+msgid "Keyboard layouts"
+msgstr "Bố trí bàn phím"
+
+#: ../data/sugar.schemas.in.h:19
+msgid "Keyboard model"
+msgstr "Mô hình bàn phím"
+
+#: ../data/sugar.schemas.in.h:20
+msgid "Keyboard options"
+msgstr "Tuỳ chọn bàn phím"
+
+#: ../data/sugar.schemas.in.h:21
+msgid "Layout of the favorites view."
+msgstr "Bố trí của ô xem các mục ưa thích."
+
+#: ../data/sugar.schemas.in.h:22
+msgid ""
+"List of keyboard layouts. Each entry should be in the form layout(variant)"
+msgstr "Danh sách các bố trí bàn phím. Mỗi mục nên có dạng « bố_trí(biến_đổi) »"
+
+#: ../data/sugar.schemas.in.h:23
+msgid "List of keyboard options."
+msgstr "Danh sách các tuỳ chọn về bàn phím."
+
+#: ../data/sugar.schemas.in.h:24
+msgid "Power Automatic"
+msgstr "Tự động năng lượng"
+
+#: ../data/sugar.schemas.in.h:25
+msgid "Power Automatic."
+msgstr "Tự động năng lượng."
+
+#: ../data/sugar.schemas.in.h:26
+msgid "Power Extreme"
+msgstr "Năng lượng cực"
+
+#: ../data/sugar.schemas.in.h:27
+msgid "Power Extreme."
+msgstr "Năng lượng cực."
+
+#: ../data/sugar.schemas.in.h:28
+msgid "Publish to Gadget"
+msgstr "Xuất tới Gadget"
+
+#: ../data/sugar.schemas.in.h:29
+msgid "Setting for muting the sound device."
+msgstr "Thiết lập để câm thiết bị âm thanh."
+
+#: ../data/sugar.schemas.in.h:30
+msgid "Show Log out"
+msgstr "Hiện Đăng xuất"
+
+#: ../data/sugar.schemas.in.h:31
+msgid "Sound Muted"
+msgstr "Âm câm"
+
+#: ../data/sugar.schemas.in.h:32
+msgid "The keyboard model to be used"
+msgstr "Mô hình bàn phím cần dùng"
+
+#: ../data/sugar.schemas.in.h:34
+msgid "Timezone setting for the system."
+msgstr "Thiết lập múi giờ cho hệ thống."
+
+#: ../data/sugar.schemas.in.h:35
+msgid "Url of the jabber server to use."
+msgstr "Địa chỉ URL của máy phục vụ Jabber cần dùng."
+
+#: ../data/sugar.schemas.in.h:36
+msgid "Url where the backup is saved to."
+msgstr "Địa chỉ URL vào đó lưu bản sao lưu."
+
+#: ../data/sugar.schemas.in.h:37
+msgid "User Color"
+msgstr "Màu người dùng"
+
+#: ../data/sugar.schemas.in.h:38
+msgid "User Name"
+msgstr "Tên người dùng"
+
+#: ../data/sugar.schemas.in.h:39
+msgid "User name that is used throughout the desktop."
+msgstr "Tên người dùng được sử dụng trên toàn bộ môi trường."
+
+#: ../data/sugar.schemas.in.h:40
+msgid "Volume Level"
+msgstr "Cấp âm"
+
+#: ../data/sugar.schemas.in.h:41
+msgid "Volume level for the sound device."
+msgstr "Cấp âm lượng cho thiết bị âm thanh."
+
+#: ../data/sugar.schemas.in.h:42
+msgid ""
+"When in resume mode, clicking on a favorite icon will cause the last entry "
+"for that activity to be resumed."
+msgstr ""
+"Khi trong chế độ tiếp tục lại, cú nhấn vào một biểu tượng ưa thích sẽ tiếp "
+"tục lại mục nhập cuối cùng cho hoạt động đó."
+
+#: ../src/jarabe/controlpanel/cmd.py:28
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+"sugar-control-panel: CẢNH BÁO : tìm nhiều tùy chọn cùng tên: mô-đun %s: %r"
+
+#: ../src/jarabe/controlpanel/cmd.py:30
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr "sugar-control-panel: khoá=%s không phải là một tùy chọn sẵn sàng"
+
+#: ../src/jarabe/controlpanel/cmd.py:31
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr "sugar-control-panel: %s"
+
+# TRANS: Translators, there's a empty line at the end of this string,
+# which must appear in the translated string (msgstr) as well.
+#. TRANS: Translators, there's a empty line at the end of this string,
+#. which must appear in the translated string (msgstr) as well.
+#: ../src/jarabe/controlpanel/cmd.py:37
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" -c key clear the current value for the key \n"
+" "
+msgstr ""
+"Sử dụng: sugar-control-panel [ tùy_chọn ] khoá [ đối_số ... ]\n"
+" Điều khiển môi trường sugar.\n"
+"\n"
+" Tùy chọn:\n"
+" -h hiển thị trợ giúp này và thoát\n"
+" -l liệt kê tất cả các tùy chọn sẵn sàng\n"
+" -h khoá hiển thị thông tin về khoá này\n"
+" -g khoá lấy giá trị hiện thời của khoá này\n"
+" -s khoá đặt giá trị hiện thời của khoá này\n"
+" -c khoá xoá giá trị hiện thời của khoá\n"
+" "
+
+#: ../src/jarabe/controlpanel/cmd.py:50
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr "Để áp dụng các thay đổi thì cần phải khởi chạy lại pn sugar.\n"
+
+#: ../src/jarabe/controlpanel/gui.py:281
+msgid "Warning"
+msgstr "Cảnh báo"
+
+#: ../src/jarabe/controlpanel/gui.py:282
+#: ../src/jarabe/controlpanel/sectionview.py:42
+msgid "Changes require restart"
+msgstr "Có thay đổi thì cần phải khởi chạy lại"
+
+#: ../src/jarabe/controlpanel/gui.py:285
+msgid "Cancel changes"
+msgstr "Thôi thay đổi"
+
+#: ../src/jarabe/controlpanel/gui.py:290 ../src/jarabe/desktop/homebox.py:70
+msgid "Later"
+msgstr "Về sau"
+
+#: ../src/jarabe/controlpanel/gui.py:294
+msgid "Restart now"
+msgstr "Khởi chạy lại ngay"
+
+#: ../src/jarabe/controlpanel/toolbar.py:61 ../src/jarabe/intro/window.py:206
+msgid "Done"
+msgstr "Hoàn tất"
+
+#: ../src/jarabe/controlpanel/toolbar.py:121
+#: ../src/jarabe/desktop/favoritesview.py:333
+msgid "Ok"
+msgstr "OK"
+
+#: ../src/jarabe/desktop/activitieslist.py:236
+#, python-format
+msgid "Version %s"
+msgstr "Phiên bản %s"
+
+#: ../src/jarabe/desktop/activitieslist.py:357
+msgid "Confirm erase"
+msgstr "Xác nhận việc xoá"
+
+#: ../src/jarabe/desktop/activitieslist.py:359
+#, python-format
+msgid "Confirm erase: Do you want to permanently erase %s?"
+msgstr "Xác nhận việc xoá: bạn có muốn xoá hẳn %s không?"
+
+#: ../src/jarabe/desktop/activitieslist.py:363
+#: ../src/jarabe/frame/clipboardmenu.py:63
+#: ../src/jarabe/view/viewsource.py:218
+msgid "Keep"
+msgstr "Giữ"
+
+#: ../src/jarabe/desktop/activitieslist.py:366
+#: ../src/jarabe/desktop/activitieslist.py:409
+#: ../src/jarabe/journal/journaltoolbox.py:360
+#: ../src/jarabe/journal/palettes.py:105
+msgid "Erase"
+msgstr "Xoá"
+
+#: ../src/jarabe/desktop/activitieslist.py:430
+msgid "Remove favorite"
+msgstr "Bỏ Ưa thích"
+
+#: ../src/jarabe/desktop/activitieslist.py:434
+msgid "Make favorite"
+msgstr "Đặt Ưa thích"
+
+# TRANS: label for the freeform layout in the favorites view
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:116
+msgid "Freeform"
+msgstr "Dạng tự do"
+
+# TRANS: label for the ring layout in the favorites view
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:198
+msgid "Ring"
+msgstr "Vòng"
+
+# TRANS: label for the spiral layout in the favorites view
+#. TRANS: label for the spiral layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:337
+msgid "Spiral"
+msgstr "Xoắn ốc"
+
+# TRANS: label for the box layout in the favorites view
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:404
+msgid "Box"
+msgstr "Hộp"
+
+# TRANS: label for the box layout in the favorites view
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:445
+msgid "Triangle"
+msgstr "Hình giác"
+
+#: ../src/jarabe/desktop/favoritesview.py:324
+msgid "Registration Failed"
+msgstr "Lỗi đăng ký"
+
+#: ../src/jarabe/desktop/favoritesview.py:325
+#, python-format
+msgid "%s"
+msgstr "%s"
+
+#: ../src/jarabe/desktop/favoritesview.py:327
+msgid "Registration Successful"
+msgstr "Đang ký thành công"
+
+#: ../src/jarabe/desktop/favoritesview.py:328
+msgid "You are now registered with your school server."
+msgstr "Bạn đã đăng ký với máy phục vụ trường học."
+
+#: ../src/jarabe/desktop/favoritesview.py:630
+msgid "Register"
+msgstr "Đăng ký"
+
+#: ../src/jarabe/desktop/homebox.py:63
+msgid "Software Update"
+msgstr "Cập nhật Phần mềm"
+
+#: ../src/jarabe/desktop/homebox.py:64
+msgid "Update your activities to ensure compatibility with your new software"
+msgstr "Cập nhật các hoạt động để đảm bảo tính tương thích với phần mềm mới"
+
+#: ../src/jarabe/desktop/homebox.py:73
+msgid "Check now"
+msgstr "Kiểm tra ngay"
+
+#: ../src/jarabe/desktop/homebox.py:192
+msgid "List view"
+msgstr "Xem danh sách"
+
+#: ../src/jarabe/desktop/homebox.py:193
+msgid "<Ctrl>2"
+msgstr "<Ctrl>2"
+
+#: ../src/jarabe/desktop/homebox.py:255
+msgid "Favorites view"
+msgstr "Xem Ưa thích"
+
+#: ../src/jarabe/desktop/homebox.py:256
+msgid "<Ctrl>1"
+msgstr "<Ctrl>1"
+
+#: ../src/jarabe/desktop/keydialog.py:135
+msgid "Key Type:"
+msgstr "Kiểu khoá:"
+
+#: ../src/jarabe/desktop/keydialog.py:155
+msgid "Authentication Type:"
+msgstr "Cách xác thực:"
+
+#: ../src/jarabe/desktop/keydialog.py:220
+msgid "WPA & WPA2 Personal"
+msgstr "WPA và WPA2 Cá nhân"
+
+#: ../src/jarabe/desktop/keydialog.py:229
+msgid "Wireless Security:"
+msgstr "Bảo mật Không dây:"
+
+#: ../src/jarabe/desktop/meshbox.py:492
+#, python-format
+msgid "Mesh Network %d"
+msgstr "Mạng mắc lưới %d"
+
+# TRANS: Action label for resuming an activity.
+#. TRANS: Action label for resuming an activity.
+#: ../src/jarabe/desktop/meshbox.py:629
+#: ../src/jarabe/frame/activitiestray.py:735
+#: ../src/jarabe/journal/journaltoolbox.py:428
+#: ../src/jarabe/journal/palettes.py:65 ../src/jarabe/view/palettes.py:67
+msgid "Resume"
+msgstr "Tiếp tục"
+
+#: ../src/jarabe/desktop/meshbox.py:634
+#: ../src/jarabe/frame/activitiestray.py:233
+msgid "Join"
+msgstr "Vào"
+
+#: ../src/jarabe/desktop/schoolserver.py:103
+msgid "Cannot connect to the server."
+msgstr "Không thể kết nối tới máy phục vụ."
+
+#: ../src/jarabe/desktop/schoolserver.py:108
+msgid "The server could not complete the request."
+msgstr "Máy phục vụ không thể hoàn tất yêu cầu."
+
+#: ../src/jarabe/frame/activitiestray.py:238
+#: ../src/jarabe/frame/activitiestray.py:672
+msgid "Decline"
+msgstr "Từ chối"
+
+#: ../src/jarabe/frame/activitiestray.py:624
+#, python-format
+msgid "%dB"
+msgstr "%dB"
+
+#: ../src/jarabe/frame/activitiestray.py:626
+#, python-format
+msgid "%dKB"
+msgstr "%dKB"
+
+#: ../src/jarabe/frame/activitiestray.py:628
+#, python-format
+msgid "%dMB"
+msgstr "%dMB"
+
+#: ../src/jarabe/frame/activitiestray.py:645
+#, python-format
+msgid "%s of %s"
+msgstr "%s trên %s"
+
+#: ../src/jarabe/frame/activitiestray.py:657
+#, python-format
+msgid "Transfer from %r"
+msgstr "Truyền từ %r"
+
+#: ../src/jarabe/frame/activitiestray.py:667
+msgid "Accept"
+msgstr "Chấp nhận"
+
+#: ../src/jarabe/frame/activitiestray.py:690
+#: ../src/jarabe/frame/activitiestray.py:817
+#, python-format
+msgid "%s (%s)"
+msgstr "%s (%s)"
+
+#: ../src/jarabe/frame/activitiestray.py:724
+#: ../src/jarabe/frame/activitiestray.py:852
+msgid "Dismiss"
+msgstr "Hủy"
+
+#: ../src/jarabe/frame/activitiestray.py:787
+#, python-format
+msgid "Transfer to %r"
+msgstr "Truyền tới %r"
+
+#: ../src/jarabe/frame/clipboardmenu.py:53 ../src/jarabe/view/palettes.py:221
+msgid "Remove"
+msgstr "Bỏ"
+
+#: ../src/jarabe/frame/clipboardmenu.py:58
+#: ../src/jarabe/frame/clipboardmenu.py:81
+msgid "Open"
+msgstr "Mở"
+
+#: ../src/jarabe/frame/clipboardmenu.py:86
+msgid "Open with"
+msgstr "Mở bằng"
+
+#: ../src/jarabe/frame/clipboardobject.py:49
+#, python-format
+msgid "%s clipping"
+msgstr "đoạn trích %s"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:37
+msgid "Neighborhood"
+msgstr "Hàng xóm"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:37
+msgid "F1"
+msgstr "F1"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:39
+msgid "F2"
+msgstr "F2"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:41
+msgid "F3"
+msgstr "F3"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:43
+msgid "F4"
+msgstr "F4"
+
+#: ../src/jarabe/intro/window.py:128
+msgid "Click to change color:"
+msgstr "Nhấn vào để thay đổi mảu:"
+
+#: ../src/jarabe/intro/window.py:192 ../src/jarabe/journal/detailview.py:103
+msgid "Back"
+msgstr "Lùi"
+
+#: ../src/jarabe/intro/window.py:209
+msgid "Next"
+msgstr "Tiếp"
+
+#: ../src/jarabe/journal/expandedentry.py:152
+#: ../src/jarabe/journal/palettes.py:59
+msgid "Untitled"
+msgstr "Không tên"
+
+#: ../src/jarabe/journal/expandedentry.py:243
+msgid "No preview"
+msgstr "Không xem thử"
+
+#: ../src/jarabe/journal/expandedentry.py:262
+#, python-format
+msgid "Kind: %s"
+msgstr "Kiểu : %s"
+
+#: ../src/jarabe/journal/expandedentry.py:262
+msgid "Unknown"
+msgstr "Không rõ"
+
+#: ../src/jarabe/journal/expandedentry.py:263
+#, python-format
+msgid "Date: %s"
+msgstr "Ngày: %s"
+
+#: ../src/jarabe/journal/expandedentry.py:264
+#, python-format
+msgid "Size: %s"
+msgstr "Kích cỡ : %s"
+
+#: ../src/jarabe/journal/expandedentry.py:286 ../src/jarabe/journal/misc.py:93
+msgid "No date"
+msgstr "Không có ngày tháng"
+
+#: ../src/jarabe/journal/expandedentry.py:293
+msgid "Participants:"
+msgstr "Người dự :"
+
+#: ../src/jarabe/journal/expandedentry.py:316
+msgid "Description:"
+msgstr "Mô tả:"
+
+#: ../src/jarabe/journal/expandedentry.py:341
+msgid "Tags:"
+msgstr "Thẻ:"
+
+#: ../src/jarabe/journal/journalactivity.py:108
+#: ../src/jarabe/journal/volumestoolbar.py:47
+msgid "Journal"
+msgstr "Nhật ký"
+
+#: ../src/jarabe/journal/journaltoolbox.py:67
+msgid "Search"
+msgstr "Tìm kiếm"
+
+#: ../src/jarabe/journal/journaltoolbox.py:126
+msgid "Anytime"
+msgstr "Lúc nào"
+
+#: ../src/jarabe/journal/journaltoolbox.py:128
+msgid "Today"
+msgstr "Hôm nay"
+
+#: ../src/jarabe/journal/journaltoolbox.py:130
+msgid "Since yesterday"
+msgstr "Kể từ ngày hôm qua"
+
+# TRANS: Filter entries modified during the last 7 days.
+#. TRANS: Filter entries modified during the last 7 days.
+#: ../src/jarabe/journal/journaltoolbox.py:132
+msgid "Past week"
+msgstr "Tuần qua"
+
+# TRANS: Filter entries modified during the last 30 days.
+#. TRANS: Filter entries modified during the last 30 days.
+#: ../src/jarabe/journal/journaltoolbox.py:134
+msgid "Past month"
+msgstr "Tháng qua"
+
+# TRANS: Filter entries modified during the last 356 days.
+#. TRANS: Filter entries modified during the last 356 days.
+#: ../src/jarabe/journal/journaltoolbox.py:136
+msgid "Past year"
+msgstr "Năm qua"
+
+#: ../src/jarabe/journal/journaltoolbox.py:143
+msgid "Anyone"
+msgstr "Bất cứ ai"
+
+#: ../src/jarabe/journal/journaltoolbox.py:145
+msgid "My friends"
+msgstr "Bạn bè"
+
+#: ../src/jarabe/journal/journaltoolbox.py:146
+msgid "My class"
+msgstr "Cùng lớp"
+
+# TRANS: Item in a combo box that filters by entry type.
+#: ../src/jarabe/journal/journaltoolbox.py:274
+msgid "Anything"
+msgstr "Bất cứ gì"
+
+# TODO: Add "Start with" menu item
+#: ../src/jarabe/journal/journaltoolbox.py:350
+#: ../src/jarabe/journal/palettes.py:83
+msgid "Copy"
+msgstr "Chép"
+
+# TRANS: Action label for starting an entry.
+#. TRANS: Action label for starting an entry.
+#: ../src/jarabe/journal/journaltoolbox.py:431
+#: ../src/jarabe/journal/palettes.py:68
+msgid "Start"
+msgstr "Khởi chạy"
+
+#: ../src/jarabe/journal/listview.py:373
+msgid "Your Journal is empty"
+msgstr "Nhật ký của bạn còn trống"
+
+#: ../src/jarabe/journal/listview.py:375
+msgid "No matching entries"
+msgstr "Không tìm thấy"
+
+#: ../src/jarabe/journal/listview.py:386
+msgid "Clear search"
+msgstr "Xoá trường tìm"
+
+#: ../src/jarabe/journal/modalalert.py:63
+msgid "Your Journal is full"
+msgstr "Nhật ký của bạn còn trống"
+
+#: ../src/jarabe/journal/modalalert.py:67
+msgid "Please delete some old Journal entries to make space for new ones."
+msgstr ""
+"Hãy xoá một số mục nhập Nhật ký cũ để giải phóng thêm sức chứa cho mục nhập "
+"mới."
+
+#: ../src/jarabe/journal/modalalert.py:79
+msgid "Show Journal"
+msgstr "Hiện Nhật ký"
+
+#: ../src/jarabe/journal/objectchooser.py:146
+msgid "Choose an object"
+msgstr "Chọn một đối tượng"
+
+#: ../src/jarabe/journal/objectchooser.py:151
+#: ../src/jarabe/view/viewsource.py:308
+msgid "Close"
+msgstr "Đóng"
+
+#: ../src/jarabe/journal/palettes.py:66
+msgid "Resume with"
+msgstr "Tiếp tục lại với"
+
+#: ../src/jarabe/journal/palettes.py:69
+msgid "Start with"
+msgstr "Bắt đầu với"
+
+#: ../src/jarabe/journal/palettes.py:91
+msgid "Send to"
+msgstr "Gửi cho"
+
+#: ../src/jarabe/journal/palettes.py:100
+msgid "View Details"
+msgstr "Xem chi tiết"
+
+#: ../src/jarabe/journal/palettes.py:178
+msgid "No friends present"
+msgstr "Bạn bè không có mặt"
+
+#: ../src/jarabe/journal/palettes.py:183
+msgid "No valid connection found"
+msgstr "Không tìm thấy kết nối hợp lệ"
+
+#: ../src/jarabe/journal/palettes.py:211
+msgid "No activity to resume entry"
+msgstr "Không có hoạt động để tiếp tục nhập vào"
+
+#: ../src/jarabe/journal/palettes.py:213
+msgid "No activity to start entry"
+msgstr "Không có hoạt động cần lại để bắt đầu nhập vào"
+
+#: ../src/jarabe/view/buddymenu.py:62
+msgid "Remove friend"
+msgstr "Bỏ bạn"
+
+#: ../src/jarabe/view/buddymenu.py:65
+msgid "Make friend"
+msgstr "Kết bạn"
+
+#: ../src/jarabe/view/buddymenu.py:82
+msgid "Shutdown"
+msgstr "Tắt máy"
+
+#: ../src/jarabe/view/buddymenu.py:90
+msgid "Logout"
+msgstr "Đăng xuất"
+
+#: ../src/jarabe/view/buddymenu.py:95
+msgid "My Settings"
+msgstr "Thiết lập của mình"
+
+#: ../src/jarabe/view/buddymenu.py:130
+#, python-format
+msgid "Invite to %s"
+msgstr "Mời vào %s"
+
+#: ../src/jarabe/view/palettes.py:45
+msgid "Starting..."
+msgstr "Đang khởi chạy..."
+
+#. TODO: share-with, keep
+#: ../src/jarabe/view/palettes.py:74
+msgid "View Source"
+msgstr "Xem nguồn"
+
+#: ../src/jarabe/view/palettes.py:85
+msgid "Stop"
+msgstr "Dừng"
+
+#: ../src/jarabe/view/palettes.py:125
+msgid "Start new"
+msgstr "Khởi chạy mới"
+
+#: ../src/jarabe/view/palettes.py:174
+msgid "Show contents"
+msgstr "Hiện nội dung"
+
+#: ../src/jarabe/view/palettes.py:196 ../src/jarabe/view/palettes.py:246
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr "%(free_space)d MB còn rảnh"
+
+#: ../src/jarabe/view/viewsource.py:208
+msgid "Instance Source"
+msgstr "Thể hiện nguồn"
+
+#: ../src/jarabe/view/viewsource.py:233
+msgid "Source"
+msgstr "Nguồn"
+
+#: ../src/jarabe/view/viewsource.py:292
+msgid "Activity Bundle Source"
+msgstr "Nguồn bó hoạt động"
+
+#: ../src/jarabe/view/viewsource.py:299
+#, python-format
+msgid "View source: %r"
+msgstr "Xem nguồn: %r"
+
+#~ msgid "Title"
+#~ msgstr "Tên"
+
+#~ msgid "Version"
+#~ msgstr "Phiên bản"
+
+#~ msgid "Date"
+#~ msgstr "Ngày"
+
+#~ msgid "Cannot obtain data needed for registration."
+#~ msgstr "Không thể lấy dữ liệu cần thiết để đăng ký."
+
+#~ msgid "Unmount"
+#~ msgstr "Bỏ lắp"
+
+#~ msgid "Restart"
+#~ msgstr "Khởi chạy lại"
+
+#~ msgid ""
+#~ "© 2008 One Laptop per Child Association Inc; Red Hat Inc; and Contributors."
+#~ msgstr ""
+#~ "© 2008 One Laptop per Child Association Inc; Red Hat Inc; and Contributors."
+
+#~ msgid "Document"
+#~ msgstr "Tài liệu"
+
+#~ msgid "Resume by default"
+#~ msgstr "Tiếp tục lại theo mặc định"
+
+#~ msgid "Encryption Type:"
+#~ msgstr "Cách mật mã: "
+
+#~ msgid "Disconnecting..."
+#~ msgstr "Đang ngắt kết nối..."
+
+#~ msgid "About my XO"
+#~ msgstr "Giới thiệu XO của mình"
+
+#~ msgid "Connected to a School Mesh Portal"
+#~ msgstr "Có kết nối tới một cổng chính Mesh trường học"
+
+#~ msgid "Looking for a School Mesh Portal..."
+#~ msgstr "Đang tìm một cổng chính Mesh trường học..."
+
+#~ msgid "Connected to an XO Mesh Portal"
+#~ msgstr "Có kết nối tới một cổng chính Mesh XO"
+
+#~ msgid "Looking for an XO Mesh Portal..."
+#~ msgstr "Đang tìm một cổng chính Mesh XO..."
+
+#~ msgid "Connected to a Simple Mesh"
+#~ msgstr "Có kết nối tới một Mesh đơn giản"
+
+#~ msgid "Starting a Simple Mesh"
+#~ msgstr "Đang khởi chạy một Mesh đơn giản"
+
+#~ msgid "Unknown Mesh"
+#~ msgstr "Không rõ Mesh"
+
+#, python-format
+#~ msgid "Clipboard object: %s."
+#~ msgstr "Đối tượng bảng nháp: %s."
+
+#~ msgid "You must enter a server."
+#~ msgstr "Bạn cần phải nhập một máy phục vụ."
+
+#~ msgid "Control Panel"
+#~ msgstr "Bảng điều khiển"
+
+#~ msgid "<Ctrl>R"
+#~ msgstr "<Ctrl>R"
+
+#~ msgid "Ring view"
+#~ msgstr "Xem vòng"
+
+#~ msgid "Remove from ring"
+#~ msgstr "Gỡ ra vòng"
+
+#~ msgid "Add to ring"
+#~ msgstr "Thêm vào vòng"
+
+#~ msgid "Changes require a sugar restart to take effect."
+#~ msgstr "Để thay đổi có tác động, cần phải khởi chạy lại phần mềm sugar."
+
+#~ msgid "Changes require restart to take effect"
+#~ msgstr "Để thay đổi có tác động, cần phải khởi chạy lại phần mềm"
+
+#~ msgid "Delay in milliseconds:"
+#~ msgstr "Khoảng đợi theo mili-giây: "
+
+#~ msgid "Hot Corners"
+#~ msgstr "Góc nóng"
+
+#~ msgid "Warm Edges"
+#~ msgstr "Cạnh ấm"
diff --git a/shell/po/wa.po b/shell/po/wa.po
new file mode 100644
index 0000000..63d4e34
--- /dev/null
+++ b/shell/po/wa.po
@@ -0,0 +1,764 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-09-25 00:30-0400\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/intro/window.py:93 ../src/controlpanel/aboutme/view.py:100
+msgid "Name:"
+msgstr ""
+
+#: ../src/intro/window.py:125
+msgid "Click to change color:"
+msgstr ""
+
+#: ../src/intro/window.py:175 ../src/journal/detailview.py:119
+msgid "Back"
+msgstr ""
+
+#: ../src/intro/window.py:189 ../src/controlpanel/toolbar.py:61
+msgid "Done"
+msgstr ""
+
+#: ../src/intro/window.py:192
+msgid "Next"
+msgstr ""
+
+#: ../src/view/BuddyMenu.py:60
+msgid "Remove friend"
+msgstr ""
+
+#: ../src/view/BuddyMenu.py:63
+msgid "Make friend"
+msgstr ""
+
+#: ../src/view/BuddyMenu.py:92
+#, python-format
+msgid "Invite to %s"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:51
+msgid "Remove"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:56 ../src/view/clipboardmenu.py:78
+msgid "Open"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:61 ../src/view/home/HomeBox.py:84
+msgid "Keep"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:83
+msgid "Open with"
+msgstr ""
+
+#: ../src/view/clipboardmenu.py:228
+#, python-format
+msgid "%s clipping"
+msgstr ""
+
+#: ../src/hardware/keydialog.py:150
+msgid "Key Type:"
+msgstr ""
+
+#: ../src/hardware/keydialog.py:170
+msgid "Authentication Type:"
+msgstr ""
+
+#: ../src/hardware/keydialog.py:251
+msgid "Encryption Type:"
+msgstr ""
+
+#: ../src/hardware/schoolserver.py:17
+msgid "Cannot obtain data needed for registration."
+msgstr ""
+
+#: ../src/hardware/schoolserver.py:31
+msgid "Cannot connect to the server."
+msgstr ""
+
+#: ../src/hardware/schoolserver.py:36
+msgid "The server could not complete the request."
+msgstr ""
+
+#: ../src/view/Shell.py:251
+msgid "Screenshot"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:78
+msgid "Confirm erase"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:80
+#, python-format
+msgid "Confirm erase: Do you want to permanently erase %s?"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:87 ../src/view/palettes.py:120
+#: ../src/journal/journaltoolbox.py:335 ../src/journal/palettes.py:75
+msgid "Erase"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:117
+msgid "Software Update"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:118
+msgid "Update your activities to ensure compatibility with your new software"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:122 ../src/controlpanel/toolbar.py:115
+msgid "Cancel"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:124 ../src/controlpanel/gui.py:273
+msgid "Later"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:127
+msgid "Check now"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:261
+msgid "List view"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:262
+msgid "<Ctrl>2"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:320
+msgid "Favorites view"
+msgstr ""
+
+#: ../src/view/home/HomeBox.py:321
+msgid "<Ctrl>1"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:97
+msgid "Connect"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:106
+msgid "Disconnect"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:118
+msgid "Disconnecting..."
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:159
+msgid "Connecting..."
+msgstr ""
+
+#. TODO: show the channel number
+#: ../src/view/home/MeshBox.py:166
+msgid "Connected"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:218 ../src/view/devices/network/mesh.py:41
+#: ../src/view/devices/network/mesh.py:68
+#: ../src/view/devices/network/mesh.py:72
+msgid "Mesh Network"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:221 ../src/view/devices/network/wireless.py:125
+#: ../src/view/devices/network/mesh.py:89
+msgid "Disconnect..."
+msgstr ""
+
+#. TRANS: Action label for resuming an activity.
+#: ../src/view/home/MeshBox.py:309 ../src/view/palettes.py:61
+#: ../src/journal/journaltoolbox.py:399 ../src/journal/palettes.py:57
+msgid "Resume"
+msgstr ""
+
+#: ../src/view/home/MeshBox.py:314 ../src/view/frame/activitiestray.py:206
+msgid "Join"
+msgstr ""
+
+#: ../src/view/devices/battery.py:45
+msgid "My Battery"
+msgstr ""
+
+#: ../src/view/devices/battery.py:114
+msgid "Charging"
+msgstr ""
+
+#: ../src/view/devices/battery.py:117
+msgid "Very little power remaining"
+msgstr ""
+
+#: ../src/view/devices/battery.py:123
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr ""
+
+#: ../src/view/devices/battery.py:127
+msgid "Charged"
+msgstr ""
+
+#: ../src/view/devices/speaker.py:44
+msgid "My Speakers"
+msgstr ""
+
+#: ../src/view/devices/speaker.py:125
+msgid "Unmute"
+msgstr ""
+
+#: ../src/view/devices/speaker.py:128
+msgid "Mute"
+msgstr ""
+
+#: ../src/view/devices/network/wireless.py:67
+msgid "Disconnected"
+msgstr ""
+
+#: ../src/view/devices/network/wireless.py:143
+msgid "Channel"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:37
+msgid "Neighborhood"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:39
+msgid "Group"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:41
+msgid "Home"
+msgstr ""
+
+#: ../src/view/frame/zoomtoolbar.py:43
+msgid "Activity"
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:26
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:28
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:29
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr ""
+
+#. TRANS: Translators, there's a empty line at the end of this string,
+#. which must appear in the translated string (msgstr) as well.
+#: ../src/controlpanel/cmd.py:35
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" -c key clear the current value for the key \n"
+" "
+msgstr ""
+
+#: ../src/controlpanel/cmd.py:48
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr ""
+
+#: ../src/controlpanel/toolbar.py:121 ../src/view/home/favoritesview.py:305
+msgid "Ok"
+msgstr ""
+
+#: ../src/controlpanel/sectionview.py:42 ../src/controlpanel/gui.py:265
+msgid "Changes require restart"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:264
+msgid "Warning"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:268
+msgid "Cancel changes"
+msgstr ""
+
+#: ../src/controlpanel/gui.py:277
+msgid "Restart now"
+msgstr ""
+
+#: ../src/controlpanel/aboutme/model.py:44
+msgid "You must enter a name."
+msgstr ""
+
+#: ../src/controlpanel/aboutme/model.py:69
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr ""
+
+#: ../src/controlpanel/aboutme/model.py:72
+#, python-format
+msgid "stroke: %s"
+msgstr ""
+
+#: ../src/controlpanel/aboutme/model.py:74
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr ""
+
+#: ../src/controlpanel/aboutme/model.py:76
+#, python-format
+msgid "fill: %s"
+msgstr ""
+
+#: ../src/controlpanel/aboutme/model.py:87
+msgid "Error in specified color modifiers."
+msgstr ""
+
+#: ../src/controlpanel/aboutme/model.py:90
+msgid "Error in specified colors."
+msgstr ""
+
+#: ../src/controlpanel/aboutme/view.py:32
+#: ../src/controlpanel/aboutme/__init__.py:22
+msgid "About Me"
+msgstr ""
+
+#: ../src/controlpanel/aboutme/view.py:134
+msgid "Click to change your color:"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/model.py:24
+msgid "Not available"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:55
+msgid "Identity"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:64
+msgid "Serial Number:"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:87
+msgid "Software"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:96
+msgid "Build:"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:111
+msgid "Sugar:"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:126
+msgid "Firmware:"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:148
+msgid "Copyright and License"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:156
+msgid ""
+"© 2008 One Laptop per Child Association Inc; Red Hat Inc; and Contributors."
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:163
+msgid ""
+"Sugar is the graphical user interface that you are looking at. Sugar is free "
+"software, covered by the GNU General Public License, and you are welcome to "
+"change it and/or distribute copies of it under certain conditions described "
+"therein."
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/view.py:175
+msgid "Full license:"
+msgstr ""
+
+#: ../src/controlpanel/aboutxo/__init__.py:21
+msgid "About my XO"
+msgstr ""
+
+#: ../src/controlpanel/datetime/model.py:89
+msgid "Error timezone does not exist."
+msgstr ""
+
+#: ../src/controlpanel/datetime/view.py:68
+msgid "Timezone"
+msgstr ""
+
+#: ../src/controlpanel/datetime/__init__.py:21
+msgid "Date & Time"
+msgstr ""
+
+#: ../src/controlpanel/frame/model.py:38 ../src/controlpanel/frame/model.py:60
+msgid "Value must be an integer."
+msgstr ""
+
+#: ../src/controlpanel/frame/view.py:26
+msgid "never"
+msgstr ""
+
+#: ../src/controlpanel/frame/view.py:27
+msgid "instantaneous"
+msgstr ""
+
+#: ../src/controlpanel/frame/view.py:28
+#, python-format
+msgid "%s seconds"
+msgstr ""
+
+#: ../src/controlpanel/frame/view.py:52
+msgid "Activation Delay"
+msgstr ""
+
+#: ../src/controlpanel/frame/view.py:76
+msgid "Corner"
+msgstr ""
+
+#: ../src/controlpanel/frame/view.py:111
+msgid "Edge"
+msgstr ""
+
+#: ../src/controlpanel/frame/__init__.py:21
+msgid "Frame"
+msgstr ""
+
+#: ../src/controlpanel/language/model.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr ""
+
+#: ../src/controlpanel/language/model.py:114
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr ""
+
+#: ../src/controlpanel/language/model.py:131
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr ""
+
+#: ../src/controlpanel/language/view.py:70
+#: ../src/controlpanel/language/__init__.py:21
+msgid "Language"
+msgstr ""
+
+#: ../src/controlpanel/network/model.py:62
+msgid "State is unknown."
+msgstr ""
+
+#: ../src/controlpanel/network/model.py:82
+msgid "Error in specified radio argument use on/off."
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:28
+#: ../src/controlpanel/network/__init__.py:21
+msgid "Network"
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:54
+msgid "Wireless"
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:62
+msgid "Turn of the wireless radio to save battery life"
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:75
+msgid "Radio"
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:91
+msgid "Discard network history if you have trouble connecting to the network"
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:100
+msgid "Discard network history"
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:113
+msgid "Mesh"
+msgstr ""
+
+#: ../src/controlpanel/network/view.py:122
+msgid "Server:"
+msgstr ""
+
+#: ../src/controlpanel/power/model.py:55
+msgid "Error in automatic pm argument, use on/off."
+msgstr ""
+
+#: ../src/controlpanel/power/model.py:84
+msgid "Error in extreme pm argument, use on/off."
+msgstr ""
+
+#: ../src/controlpanel/power/view.py:47
+msgid "Power management"
+msgstr ""
+
+#: ../src/controlpanel/power/view.py:57
+msgid "Automatic power management (increases battery life)"
+msgstr ""
+
+#: ../src/controlpanel/power/view.py:85
+msgid ""
+"Extreme power management (disableswireless radio, increases battery life)"
+msgstr ""
+
+#: ../src/controlpanel/power/__init__.py:21
+msgid "Power"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:111
+msgid "Connected to a School Mesh Portal"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:113
+msgid "Looking for a School Mesh Portal..."
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:116
+msgid "Connected to an XO Mesh Portal"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:118
+msgid "Looking for an XO Mesh Portal..."
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:121
+msgid "Connected to a Simple Mesh"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:123
+msgid "Starting a Simple Mesh"
+msgstr ""
+
+#: ../src/view/devices/network/mesh.py:130
+msgid "Unknown Mesh"
+msgstr ""
+
+#: ../src/view/frame/activitiestray.py:211
+msgid "Decline"
+msgstr ""
+
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/view/home/favoriteslayout.py:107
+msgid "Freeform"
+msgstr ""
+
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/view/home/favoriteslayout.py:189
+msgid "Ring"
+msgstr ""
+
+#. TRANS: label for the spiral layout in the favorites view
+#: ../src/view/home/favoriteslayout.py:334
+msgid "Spiral"
+msgstr ""
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/view/home/favoriteslayout.py:401
+msgid "Box"
+msgstr ""
+
+#. TRANS: label for the box layout in the favorites view
+#: ../src/view/home/favoriteslayout.py:442
+msgid "Triangle"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:295
+msgid "Registration Failed"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:296
+#, python-format
+msgid "%s"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:298
+msgid "Registration Successful"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:299
+msgid "You are now registered with your school server."
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:420
+msgid "Settings"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:425
+msgid "Restart"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:430
+msgid "Shutdown"
+msgstr ""
+
+#: ../src/view/home/favoritesview.py:436
+msgid "Register"
+msgstr ""
+
+#: ../src/view/palettes.py:42
+msgid "Starting..."
+msgstr ""
+
+#: ../src/view/palettes.py:72
+msgid "Stop"
+msgstr ""
+
+#. TRANS: Action label for starting an entry.
+#: ../src/view/palettes.py:104 ../src/journal/journaltoolbox.py:402
+#: ../src/journal/palettes.py:59
+msgid "Start"
+msgstr ""
+
+#: ../src/view/palettes.py:138
+msgid "Remove favorite"
+msgstr ""
+
+#: ../src/view/palettes.py:142
+msgid "Make favorite"
+msgstr ""
+
+#: ../src/view/palettes.py:191
+msgid "Show contents"
+msgstr ""
+
+#: ../src/view/palettes.py:215
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr ""
+
+#: ../src/journal/journaltoolbox.py:62
+msgid "Search"
+msgstr ""
+
+#: ../src/journal/journaltoolbox.py:119
+msgid "Anytime"
+msgstr ""
+
+#: ../src/journal/journaltoolbox.py:121
+msgid "Today"
+msgstr ""
+
+#: ../src/journal/journaltoolbox.py:123
+msgid "Since yesterday"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 7 days.
+#: ../src/journal/journaltoolbox.py:125
+msgid "Past week"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 30 days.
+#: ../src/journal/journaltoolbox.py:127
+msgid "Past month"
+msgstr ""
+
+#. TRANS: Filter entries modified during the last 356 days.
+#: ../src/journal/journaltoolbox.py:129
+msgid "Past year"
+msgstr ""
+
+#: ../src/journal/journaltoolbox.py:136
+msgid "Anyone"
+msgstr ""
+
+#: ../src/journal/journaltoolbox.py:138
+msgid "My friends"
+msgstr ""
+
+#: ../src/journal/journaltoolbox.py:139
+msgid "My class"
+msgstr ""
+
+#. TRANS: Item in a combo box that filters by entry type.
+#: ../src/journal/journaltoolbox.py:255
+msgid "Anything"
+msgstr ""
+
+#. TODO: Add "Start with" menu item
+#: ../src/journal/journaltoolbox.py:325 ../src/journal/palettes.py:67
+msgid "Copy"
+msgstr ""
+
+#: ../src/journal/collapsedentry.py:248 ../src/journal/expandedentry.py:176
+#: ../src/journal/palettes.py:51
+msgid "Untitled"
+msgstr ""
+
+#: ../src/journal/journalactivity.py:119 ../src/journal/volumesmanager.py:57
+msgid "Journal"
+msgstr ""
+
+#: ../src/journal/expandedentry.py:222
+msgid "No preview"
+msgstr ""
+
+#: ../src/journal/expandedentry.py:241
+msgid "Participants:"
+msgstr ""
+
+#: ../src/journal/expandedentry.py:266
+msgid "Description:"
+msgstr ""
+
+#: ../src/journal/expandedentry.py:292
+msgid "Tags:"
+msgstr ""
+
+#: ../src/journal/objectchooser.py:134
+msgid "Choose an object"
+msgstr ""
+
+#: ../src/journal/objectchooser.py:139
+msgid "Close"
+msgstr ""
+
+#: ../src/journal/volumestoolbar.py:93
+msgid "Unmount"
+msgstr ""
+
+#: ../src/journal/misc.py:95
+msgid "No date"
+msgstr ""
+
+#: ../src/journal/listview.py:39
+msgid "Your Journal is empty"
+msgstr ""
+
+#: ../src/journal/listview.py:40
+msgid "No matching entries "
+msgstr ""
+
+#: ../src/journal/modalalert.py:59
+msgid "Your Journal is full"
+msgstr ""
+
+#: ../src/journal/modalalert.py:63
+msgid "Please delete some old Journal entries to make space for new ones."
+msgstr ""
+
+#: ../src/journal/modalalert.py:75
+msgid "Show Journal"
+msgstr ""
diff --git a/shell/po/yo.po b/shell/po/yo.po
new file mode 100644
index 0000000..5299db7
--- /dev/null
+++ b/shell/po/yo.po
@@ -0,0 +1,448 @@
+# translation of sugar.po to Yoruba
+# This file is distributed under the same license as the PACKAGE package.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER.
+# Fajuyitan, Sunday <ayo@wazobialinux.com>, 2006.
+# Fajuyitan, Sunday Ayo <ayo@wazobialinux.com>, 2006.
+msgid ""
+msgstr ""
+"Project-Id-Version: sugar\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-01-18 18:19+0000\n"
+"PO-Revision-Date: 2006-07-07 09:48+0100\n"
+"Last-Translator: Fajuyitan, Sunday Ayo <ayo@wazobialinux.com>\n"
+"Language-Team: Yoruba\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: KBabel 1.10\n"
+
+#: ../shell/PresenceWindow.py:62
+msgid "Who's around:"
+msgstr "Ta ló wà níbẹ̀ o o:"
+
+#: ../shell/PresenceWindow.py:104
+msgid "Share"
+msgstr "Ìpín"
+
+#: ../shell/StartPage.py:189
+msgid "Search"
+msgstr "Wádìí"
+
+#: ../shell/intro/intro.py:146
+msgid "Back"
+msgstr "Padà Sẹ́yìn"
+
+#: ../activities/browser/NavigationToolbar.py:23
+msgid "Forward"
+msgstr "Lọ síwájú"
+
+#: ../activities/browser/NavigationToolbar.py:29
+msgid "Reload"
+msgstr "Tun kì"
+
+#: ../shell/shell.py:333
+msgid "Everyone"
+msgstr "Gbogbo èèyàn"
+
+#: ../sugar/chat/ChatEditor.py:43
+msgid "Send"
+msgstr "Fi ránṣẹ́"
+
+#: ../shell/intro/intro.py:67
+msgid "Name:"
+msgstr ""
+
+#: ../shell/intro/intro.py:96
+msgid "Click to change color:"
+msgstr ""
+
+#: ../shell/intro/intro.py:160
+msgid "Done"
+msgstr ""
+
+#: ../shell/intro/intro.py:163
+msgid "Next"
+msgstr ""
+
+#: ../shell/view/BuddyMenu.py:59
+msgid "Remove friend"
+msgstr ""
+
+#: ../shell/view/BuddyMenu.py:62
+msgid "Make friend"
+msgstr ""
+
+#: ../shell/view/BuddyMenu.py:84
+#, python-format
+msgid "Invite to %s"
+msgstr ""
+
+#: ../shell/view/clipboardmenu.py:58
+msgid "Remove"
+msgstr ""
+
+#: ../shell/view/clipboardmenu.py:63
+msgid "Open"
+msgstr ""
+
+#. self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+#. TODO: Implement stopping downloads
+#. self._stop_item.connect('activate', self._stop_item_activate_cb)
+#. self.append_menu_item(self._stop_item)
+#: ../shell/view/clipboardmenu.py:73
+msgid "Add to journal"
+msgstr ""
+
+#: ../shell/view/clipboardmenu.py:213
+#, python-format
+msgid "Clipboard object: %s."
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:149
+msgid "Key Type:"
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:169
+msgid "Authentication Type:"
+msgstr ""
+
+#: ../shell/hardware/keydialog.py:250
+msgid "Encryption Type:"
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:90
+msgid "Starting..."
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:104 ../shell/view/home/MeshBox.py:295
+msgid "Resume"
+msgstr ""
+
+#: ../shell/view/home/activitiesdonut.py:111
+#: ../lib/sugar/activity/activity.py:132
+msgid "Stop"
+msgstr ""
+
+#: ../shell/view/Shell.py:285
+msgid "Screenshot"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:159
+msgid "Reboot"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:164
+msgid "Shutdown"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:170
+msgid "Register"
+msgstr ""
+
+#. Only show disconnect when there's a mesh device, because mesh takes
+#. priority over the normal wireless device. NM doesn't have a "disconnect"
+#. method for a device either (for various reasons) so this doesn't
+#. have a good mapping
+#: ../shell/view/home/MeshBox.py:90 ../shell/view/home/MeshBox.py:197
+#: ../shell/view/devices/network/wireless.py:113
+#: ../shell/view/devices/network/mesh.py:83
+msgid "Disconnect..."
+msgstr ""
+
+#: ../shell/view/home/MeshBox.py:195 ../shell/view/devices/network/mesh.py:37
+#: ../shell/view/devices/network/mesh.py:62
+#: ../shell/view/devices/network/mesh.py:66
+msgid "Mesh Network"
+msgstr ""
+
+#: ../shell/view/home/MeshBox.py:300
+msgid "Join"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:38
+msgid "My Battery life"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:94
+msgid "Battery charging"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:96
+msgid "Battery discharging"
+msgstr ""
+
+#: ../shell/view/devices/battery.py:98
+msgid "Battery fully charged"
+msgstr ""
+
+#: ../shell/view/devices/network/wireless.py:61
+msgid "Disconnected"
+msgstr ""
+
+#: ../shell/view/devices/network/wireless.py:131
+msgid "Channel"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:42
+msgid "Neighborhood"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:54
+msgid "Group"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:66
+msgid "Home"
+msgstr ""
+
+#: ../shell/view/frame/zoomtoolbar.py:78
+msgid "Activity"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:115
+msgid "Share with:"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:117
+msgid "Private"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:118
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:126
+msgid "Keep"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:245
+msgid "Undo"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:250
+msgid "Redo"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:260
+msgid "Copy"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:265
+msgid "Paste"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:454
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:824
+msgid "Keep error"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:825
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:828
+msgid "Don't stop"
+msgstr ""
+
+#: ../lib/sugar/activity/activity.py:831
+msgid "Stop anyway"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:164 ../lib/sugar/graphics/alert.py:206
+msgid "Cancel"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:168
+msgid "Ok"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:216
+msgid "Continue"
+msgstr ""
+
+#: ../lib/sugar/graphics/alert.py:244
+msgid "OK"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d year"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d years"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d month"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d months"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d week"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d weeks"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d day"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d days"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hour"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hours"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minute"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minutes"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d second"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d seconds"
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:191
+msgid " and "
+msgstr ""
+
+#: ../lib/sugar/graphics/objectchooser.py:193
+msgid ", "
+msgstr ""
+
+#: ../shell/controlpanel/control.py:213
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:267
+msgid "Error in specified color modifiers."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:270
+msgid "Error in specified colors."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:307
+msgid "off"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:309
+msgid "on"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:310
+msgid "State is unknown."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:332
+msgid "Error in specified radio argument use on/off."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:336
+msgid "Permission denied. You need to be root to run this method."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:366
+msgid "Error in reading timezone"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:397
+#, python-format
+msgid "Error copying timezone (from %s): %s"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:402
+#, python-format
+msgid "Changing permission of timezone: %s"
+msgstr ""
+
+#: ../shell/controlpanel/control.py:412
+msgid "Error timezone does not exist."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:417 ../shell/controlpanel/control.py:436
+#, python-format
+msgid "Could not access %s. Create standard settings."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:463
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr ""
+
+#: ../shell/controlpanel/control.py:473
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:105
+msgid "Connected to a School Mesh Portal"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:107
+msgid "Looking for a School Mesh Portal..."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:110
+msgid "Connected to an XO Mesh Portal"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:112
+msgid "Looking for an XO Mesh Portal..."
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:115
+msgid "Connected to a Simple Mesh"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:117
+msgid "Starting a Simple Mesh"
+msgstr ""
+
+#: ../shell/view/devices/network/mesh.py:124
+msgid "Unknown Mesh"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:175 ../shell/view/home/HomeBox.py:216
+msgid "About this XO"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:222
+msgid "Not available"
+msgstr ""
diff --git a/shell/po/zh_CN.po b/shell/po/zh_CN.po
new file mode 100644
index 0000000..bd5b1b2
--- /dev/null
+++ b/shell/po/zh_CN.po
@@ -0,0 +1,420 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-01-18 18:19+0000\n"
+"PO-Revision-Date: 2008-01-11 21:14+0000\n"
+"Last-Translator: Yuan Chao <yuanchao@gmail.com>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Pootle 1.0.2\n"
+
+#: ../shell/intro/intro.py:67
+msgid "Name:"
+msgstr "姓名:"
+
+#: ../shell/intro/intro.py:96
+msgid "Click to change color:"
+msgstr "点击来改变颜色:"
+
+#: ../shell/intro/intro.py:146
+msgid "Back"
+msgstr "后退"
+
+#: ../shell/intro/intro.py:160
+msgid "Done"
+msgstr "完成"
+
+#: ../shell/intro/intro.py:163
+msgid "Next"
+msgstr "下一步"
+
+#: ../shell/view/BuddyMenu.py:59
+msgid "Remove friend"
+msgstr "删除好友"
+
+#: ../shell/view/BuddyMenu.py:62
+msgid "Make friend"
+msgstr "结交好友"
+
+#: ../shell/view/BuddyMenu.py:84
+#, python-format
+msgid "Invite to %s"
+msgstr "邀请到%s"
+
+#: ../shell/view/clipboardmenu.py:58
+msgid "Remove"
+msgstr "删除"
+
+#: ../shell/view/clipboardmenu.py:63
+msgid "Open"
+msgstr "打开"
+
+#. self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+#. TODO: Implement stopping downloads
+#. self._stop_item.connect('activate', self._stop_item_activate_cb)
+#. self.append_menu_item(self._stop_item)
+#: ../shell/view/clipboardmenu.py:73
+msgid "Add to journal"
+msgstr "增添到日志中"
+
+#: ../shell/view/clipboardmenu.py:213
+#, python-format
+msgid "Clipboard object: %s."
+msgstr "剪贴板物件: %s."
+
+#: ../shell/hardware/keydialog.py:149
+msgid "Key Type:"
+msgstr "密钥类型:"
+
+#: ../shell/hardware/keydialog.py:169
+msgid "Authentication Type:"
+msgstr "验证类型:"
+
+#: ../shell/hardware/keydialog.py:250
+msgid "Encryption Type:"
+msgstr "加密类型:"
+
+#: ../shell/view/home/activitiesdonut.py:90
+msgid "Starting..."
+msgstr "开始..."
+
+#: ../shell/view/home/activitiesdonut.py:104 ../shell/view/home/MeshBox.py:295
+msgid "Resume"
+msgstr "继续"
+
+#: ../shell/view/home/activitiesdonut.py:111
+#: ../lib/sugar/activity/activity.py:132
+msgid "Stop"
+msgstr "停止"
+
+#: ../shell/view/Shell.py:285
+msgid "Screenshot"
+msgstr "屏幕抓图"
+
+#: ../shell/view/home/HomeBox.py:159
+msgid "Reboot"
+msgstr "重新启动"
+
+#: ../shell/view/home/HomeBox.py:164
+msgid "Shutdown"
+msgstr "关闭系统"
+
+#: ../shell/view/home/HomeBox.py:170
+msgid "Register"
+msgstr "注册"
+
+#. Only show disconnect when there's a mesh device, because mesh takes
+#. priority over the normal wireless device. NM doesn't have a "disconnect"
+#. method for a device either (for various reasons) so this doesn't
+#. have a good mapping
+#: ../shell/view/home/MeshBox.py:90 ../shell/view/home/MeshBox.py:197
+#: ../shell/view/devices/network/wireless.py:113
+#: ../shell/view/devices/network/mesh.py:83
+msgid "Disconnect..."
+msgstr "断开..."
+
+#: ../shell/view/home/MeshBox.py:195 ../shell/view/devices/network/mesh.py:37
+#: ../shell/view/devices/network/mesh.py:62
+#: ../shell/view/devices/network/mesh.py:66
+msgid "Mesh Network"
+msgstr "网状网络"
+
+#: ../shell/view/home/MeshBox.py:300
+msgid "Join"
+msgstr "加入"
+
+#: ../shell/view/devices/battery.py:38
+msgid "My Battery life"
+msgstr "我的电池状态"
+
+#: ../shell/view/devices/battery.py:94
+msgid "Battery charging"
+msgstr "电池充电中"
+
+#: ../shell/view/devices/battery.py:96
+msgid "Battery discharging"
+msgstr "电池放电中"
+
+#: ../shell/view/devices/battery.py:98
+msgid "Battery fully charged"
+msgstr "电池充电完成"
+
+#: ../shell/view/devices/network/wireless.py:61
+msgid "Disconnected"
+msgstr "已切断"
+
+#: ../shell/view/devices/network/wireless.py:131
+msgid "Channel"
+msgstr "频道"
+
+#: ../shell/view/frame/zoomtoolbar.py:42
+msgid "Neighborhood"
+msgstr "邻居"
+
+#: ../shell/view/frame/zoomtoolbar.py:54
+msgid "Group"
+msgstr "组"
+
+#: ../shell/view/frame/zoomtoolbar.py:66
+msgid "Home"
+msgstr "家"
+
+#: ../shell/view/frame/zoomtoolbar.py:78
+msgid "Activity"
+msgstr "活动"
+
+#: ../lib/sugar/activity/activity.py:115
+msgid "Share with:"
+msgstr "共享:"
+
+#: ../lib/sugar/activity/activity.py:117
+msgid "Private"
+msgstr "私人"
+
+#: ../lib/sugar/activity/activity.py:118
+msgid "My Neighborhood"
+msgstr "我的邻居"
+
+#: ../lib/sugar/activity/activity.py:126
+msgid "Keep"
+msgstr "保持"
+
+#: ../lib/sugar/activity/activity.py:245
+msgid "Undo"
+msgstr "撤销"
+
+#: ../lib/sugar/activity/activity.py:250
+msgid "Redo"
+msgstr "重复"
+
+#: ../lib/sugar/activity/activity.py:260
+msgid "Copy"
+msgstr "复制"
+
+#: ../lib/sugar/activity/activity.py:265
+msgid "Paste"
+msgstr "粘贴"
+
+#: ../lib/sugar/activity/activity.py:454
+#, python-format
+msgid "%s Activity"
+msgstr "%s 的活动"
+
+#: ../lib/sugar/activity/activity.py:824
+msgid "Keep error"
+msgstr "保持错误"
+
+#: ../lib/sugar/activity/activity.py:825
+msgid "Keep error: all changes will be lost"
+msgstr "保持错误:所有改动都将撤销"
+
+#: ../lib/sugar/activity/activity.py:828
+msgid "Don't stop"
+msgstr "不停止"
+
+#: ../lib/sugar/activity/activity.py:831
+msgid "Stop anyway"
+msgstr "停止吧"
+
+#: ../lib/sugar/graphics/alert.py:164 ../lib/sugar/graphics/alert.py:206
+msgid "Cancel"
+msgstr "取消"
+
+#: ../lib/sugar/graphics/alert.py:168
+msgid "Ok"
+msgstr "确定"
+
+#: ../lib/sugar/graphics/alert.py:216
+msgid "Continue"
+msgstr "继续"
+
+#: ../lib/sugar/graphics/alert.py:244
+msgid "OK"
+msgstr "确定"
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d year"
+msgstr "%d 年"
+
+#: ../lib/sugar/graphics/objectchooser.py:175
+#, python-format
+msgid "%d years"
+msgstr "%d 年"
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d month"
+msgstr "%d 月"
+
+#: ../lib/sugar/graphics/objectchooser.py:176
+#, python-format
+msgid "%d months"
+msgstr "%d 月"
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d week"
+msgstr "%d 星期"
+
+#: ../lib/sugar/graphics/objectchooser.py:177
+#, python-format
+msgid "%d weeks"
+msgstr "%d 星期"
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d day"
+msgstr "%d 天"
+
+#: ../lib/sugar/graphics/objectchooser.py:178
+#, python-format
+msgid "%d days"
+msgstr "%d 天"
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hour"
+msgstr "%d 小时"
+
+#: ../lib/sugar/graphics/objectchooser.py:179
+#, python-format
+msgid "%d hours"
+msgstr "%d 小时"
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minute"
+msgstr "%d 分钟"
+
+#: ../lib/sugar/graphics/objectchooser.py:180
+#, python-format
+msgid "%d minutes"
+msgstr "%d 分钟"
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d second"
+msgstr "%d 秒"
+
+#: ../lib/sugar/graphics/objectchooser.py:181
+#, python-format
+msgid "%d seconds"
+msgstr "%d 秒"
+
+#: ../lib/sugar/graphics/objectchooser.py:191
+msgid " and "
+msgstr "和"
+
+#: ../lib/sugar/graphics/objectchooser.py:193
+msgid ", "
+msgstr ","
+
+# 如何翻译sugar?
+#: ../shell/controlpanel/control.py:213
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr "你需要重新启动系统, 让改变生效.\n"
+
+#: ../shell/controlpanel/control.py:267
+msgid "Error in specified color modifiers."
+msgstr "给定变化色时发生错误"
+
+#: ../shell/controlpanel/control.py:270
+msgid "Error in specified colors."
+msgstr "给定颜色时发生错误"
+
+#: ../shell/controlpanel/control.py:307
+msgid "off"
+msgstr "关闭"
+
+#: ../shell/controlpanel/control.py:309
+msgid "on"
+msgstr "开启"
+
+#: ../shell/controlpanel/control.py:310
+msgid "State is unknown."
+msgstr "未知状态"
+
+#: ../shell/controlpanel/control.py:332
+msgid "Error in specified radio argument use on/off."
+msgstr "给定无线信号开/关时发生错误"
+
+#: ../shell/controlpanel/control.py:336
+msgid "Permission denied. You need to be root to run this method."
+msgstr "权限不足. 你需要管理员权限来执行."
+
+#: ../shell/controlpanel/control.py:366
+msgid "Error in reading timezone"
+msgstr "阅读时区资料时发生错误"
+
+#: ../shell/controlpanel/control.py:397
+#, python-format
+msgid "Error copying timezone (from %s): %s"
+msgstr "从 %s 复制时区资料时发生错误: %s"
+
+#: ../shell/controlpanel/control.py:402
+#, python-format
+msgid "Changing permission of timezone: %s"
+msgstr "改变时区设置的权限: %s"
+
+#: ../shell/controlpanel/control.py:412
+msgid "Error timezone does not exist."
+msgstr "错误, 时区资料不存在."
+
+#: ../shell/controlpanel/control.py:417 ../shell/controlpanel/control.py:436
+#, python-format
+msgid "Could not access %s. Create standard settings."
+msgstr "不能访问%s. 创建标准设置."
+
+#: ../shell/controlpanel/control.py:463
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr "无法确定代码为 %s 的语言"
+
+#: ../shell/controlpanel/control.py:473
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr "对不起,我不会说 '%s'"
+
+#: ../shell/view/devices/network/mesh.py:105
+msgid "Connected to a School Mesh Portal"
+msgstr "已连接到一个学校网状网络门户"
+
+#: ../shell/view/devices/network/mesh.py:107
+msgid "Looking for a School Mesh Portal..."
+msgstr "寻找学校网状网络门户中..."
+
+#: ../shell/view/devices/network/mesh.py:110
+msgid "Connected to an XO Mesh Portal"
+msgstr "已连接到一个XO网状网络门户"
+
+#: ../shell/view/devices/network/mesh.py:112
+msgid "Looking for an XO Mesh Portal..."
+msgstr "寻找XO网状网络门户中..."
+
+#: ../shell/view/devices/network/mesh.py:115
+msgid "Connected to a Simple Mesh"
+msgstr "已连接到一个简单网状网络"
+
+#: ../shell/view/devices/network/mesh.py:117
+msgid "Starting a Simple Mesh"
+msgstr "创建一个简单网状网络"
+
+#: ../shell/view/devices/network/mesh.py:124
+msgid "Unknown Mesh"
+msgstr "未知网状网络"
+
+#: ../shell/view/home/HomeBox.py:175 ../shell/view/home/HomeBox.py:216
+msgid "About this XO"
+msgstr ""
+
+#: ../shell/view/home/HomeBox.py:222
+msgid "Not available"
+msgstr ""
diff --git a/shell/po/zh_TW.po b/shell/po/zh_TW.po
new file mode 100644
index 0000000..1a2b113
--- /dev/null
+++ b/shell/po/zh_TW.po
@@ -0,0 +1,1543 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: Sugar\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-02-11 00:32-0500\n"
+"PO-Revision-Date: 2010-03-28 16:09+0200\n"
+"Last-Translator: Yuan Chao <yuanchao@gmail.com>\n"
+"Language-Team: Yuan CHAO <yuanchao@gmail.com>\n"
+"Language: zh_TW\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Pootle 2.0.3\n"
+
+#: ../extensions/cpsection/aboutme/__init__.py:24
+msgid "About Me"
+msgstr "關於我"
+
+#: ../extensions/cpsection/aboutme/model.py:43
+msgid "You must enter a name."
+msgstr "請輸入姓名"
+
+#: ../extensions/cpsection/aboutme/model.py:68
+#, python-format
+msgid "stroke: color=%s hue=%s"
+msgstr "畫筆: 顏色=%s 色澤明暗=%s"
+
+#: ../extensions/cpsection/aboutme/model.py:71
+#, python-format
+msgid "stroke: %s"
+msgstr "畫筆: %s"
+
+#: ../extensions/cpsection/aboutme/model.py:73
+#, python-format
+msgid "fill: color=%s hue=%s"
+msgstr "填滿: 顏色=%s 色澤明暗=%s"
+
+#: ../extensions/cpsection/aboutme/model.py:75
+#, python-format
+msgid "fill: %s"
+msgstr "填滿: %s"
+
+#: ../extensions/cpsection/aboutme/model.py:86
+msgid "Error in specified color modifiers."
+msgstr "指定增修顏色時發生錯誤"
+
+#: ../extensions/cpsection/aboutme/model.py:89
+msgid "Error in specified colors."
+msgstr "指定顏色時發生錯誤"
+
+#: ../extensions/cpsection/aboutme/view.py:94 ../src/jarabe/intro/window.py:93
+msgid "Name:"
+msgstr "姓名:"
+
+#: ../extensions/cpsection/aboutme/view.py:128
+msgid "Click to change your color:"
+msgstr "點選改變顏色"
+
+#: ../extensions/cpsection/aboutcomputer/__init__.py:21
+msgid "About my Computer"
+msgstr "我的電腦"
+
+#: ../extensions/cpsection/aboutcomputer/model.py:28
+msgid "Not available"
+msgstr "不存在"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:60
+msgid "Identity"
+msgstr "身份識別"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:69
+msgid "Serial Number:"
+msgstr "序號"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:91
+msgid "Software"
+msgstr "軟體"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:100
+msgid "Build:"
+msgstr "建立:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:115
+msgid "Sugar:"
+msgstr "Sugar:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:131
+msgid "Firmware:"
+msgstr "韌體:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:146
+msgid "Wireless Firmware:"
+msgstr "無線網路韌體:"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:169
+msgid "Copyright and License"
+msgstr "版權與授權資料"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:184
+msgid ""
+"Sugar is the graphical user interface that you are looking at. Sugar is free "
+"software, covered by the GNU General Public License, and you are welcome to "
+"change it and/or distribute copies of it under certain conditions described "
+"therein."
+msgstr "Sugar 是您目前所看到的使用者界面,Sugar 是一個自由軟體,以 GNU 的 GPL 授權。歡迎您依據授權條例修改與散佈本軟體。"
+
+#: ../extensions/cpsection/aboutcomputer/view.py:196
+msgid "Full license:"
+msgstr "完整授權條例:"
+
+#: ../extensions/cpsection/datetime/__init__.py:21
+msgid "Date & Time"
+msgstr "日期與時間"
+
+#: ../extensions/cpsection/datetime/model.py:87
+msgid "Error timezone does not exist."
+msgstr "錯誤,時區資料不存在."
+
+#: ../extensions/cpsection/datetime/view.py:68 ../data/sugar.schemas.in.h:33
+msgid "Timezone"
+msgstr "時區"
+
+#: ../extensions/cpsection/frame/__init__.py:21
+msgid "Frame"
+msgstr "畫面"
+
+#: ../extensions/cpsection/frame/model.py:38
+#: ../extensions/cpsection/frame/model.py:60
+msgid "Value must be an integer."
+msgstr "指定數值必需為整數"
+
+#: ../extensions/cpsection/frame/view.py:26
+msgid "never"
+msgstr "永不"
+
+#: ../extensions/cpsection/frame/view.py:27
+msgid "instantaneous"
+msgstr "即時"
+
+#: ../extensions/cpsection/frame/view.py:28
+#, python-format
+msgid "%s seconds"
+msgstr "%s秒"
+
+#: ../extensions/cpsection/frame/view.py:52
+msgid "Activation Delay"
+msgstr "啟用延遲"
+
+#: ../extensions/cpsection/frame/view.py:76
+msgid "Corner"
+msgstr "角落"
+
+#: ../extensions/cpsection/frame/view.py:111
+msgid "Edge"
+msgstr "邊緣"
+
+#: ../extensions/cpsection/keyboard/__init__.py:21
+#: ../extensions/cpsection/keyboard/view.py:31
+msgid "Keyboard"
+msgstr "鍵盤"
+
+#: ../extensions/cpsection/keyboard/view.py:189
+msgid "Keyboard Model"
+msgstr "鍵盤型式"
+
+#: ../extensions/cpsection/keyboard/view.py:248
+msgid "Key(s) to change layout"
+msgstr "按鍵改變樣式"
+
+#: ../extensions/cpsection/keyboard/view.py:318
+msgid "Keyboard Layout(s)"
+msgstr "鍵盤樣式"
+
+#: ../extensions/cpsection/language/__init__.py:21
+#: ../extensions/cpsection/language/view.py:33
+msgid "Language"
+msgstr "語言"
+
+#: ../extensions/cpsection/language/model.py:28
+msgid "Could not access ~/.i18n. Create standard settings."
+msgstr "無法讀取 ~/.i18n。 建立標準設定。"
+
+#: ../extensions/cpsection/language/model.py:124
+#, python-format
+msgid "Language for code=%s could not be determined."
+msgstr "語系編碼 %s 無法判定"
+
+#: ../extensions/cpsection/language/model.py:144
+#, python-format
+msgid "Sorry I do not speak '%s'."
+msgstr "抱歉我不會說'%s'."
+
+#: ../extensions/cpsection/language/view.py:56
+msgid ""
+"Add languages in the order you prefer. If a translation is not available, "
+"the next in the list will be used."
+msgstr "請依序加入你使用的語言。如果優先選定的語言缺少翻譯,則使用次優先的語言翻譯。"
+
+#: ../extensions/cpsection/modemconfiguration/__init__.py:21
+msgid "Modem Configuration"
+msgstr "數據機設定"
+
+#: ../extensions/cpsection/modemconfiguration/view.py:90
+msgid "Username:"
+msgstr "使用者帳號:"
+
+#: ../extensions/cpsection/modemconfiguration/view.py:101
+msgid "Password:"
+msgstr "密碼:"
+
+#: ../extensions/cpsection/modemconfiguration/view.py:112
+msgid "Number:"
+msgstr "撥接號碼:"
+
+#: ../extensions/cpsection/modemconfiguration/view.py:123
+msgid "APN:"
+msgstr "接取點名稱(APN):"
+
+#: ../extensions/cpsection/network/__init__.py:21
+#: ../extensions/cpsection/network/view.py:28
+msgid "Network"
+msgstr "網路"
+
+#: ../extensions/cpsection/network/model.py:79
+msgid "State is unknown."
+msgstr "未知狀態"
+
+#: ../extensions/cpsection/network/model.py:105
+msgid "Error in specified radio argument use on/off."
+msgstr "錯誤的無線網路信號開關設定,請指定on/off。"
+
+#: ../extensions/cpsection/network/model.py:137
+msgid "Error in specified argument use 0/1."
+msgstr "無線網路信號開關只能設為 0/1。"
+
+#: ../extensions/cpsection/network/view.py:59
+msgid "Wireless"
+msgstr "無線網路"
+
+#: ../extensions/cpsection/network/view.py:67
+msgid "Turn off the wireless radio to save battery life"
+msgstr "關閉無線網路信號來增加電池使用時間"
+
+#: ../extensions/cpsection/network/view.py:80
+msgid "Radio"
+msgstr "無線網路信號"
+
+#: ../extensions/cpsection/network/view.py:96
+msgid "Discard network history if you have trouble connecting to the network"
+msgstr "如果您遇到連線上的問題,請嘗試清除舊的網路歷史資料"
+
+#: ../extensions/cpsection/network/view.py:105
+msgid "Discard network history"
+msgstr "清除舊的網路歷史資料"
+
+#: ../extensions/cpsection/network/view.py:118
+msgid "Collaboration"
+msgstr "協同合作"
+
+#: ../extensions/cpsection/network/view.py:126
+msgid ""
+"The server is the equivalent of what room you are in; people on the same "
+"server will be able to see each other, even when they aren't on the same "
+"network."
+msgstr "伺服器就等同於你所在的房間,在同一個伺服器上的使用者可以互相看見彼此,即使是實際上他們不在同一個網路中。"
+
+#: ../extensions/cpsection/network/view.py:136
+msgid "Server:"
+msgstr "伺服器"
+
+#: ../extensions/cpsection/power/__init__.py:21
+msgid "Power"
+msgstr "電源"
+
+#: ../extensions/cpsection/power/model.py:54
+msgid "Error in automatic pm argument, use on/off."
+msgstr "自動省電管理開關發生錯誤"
+
+#: ../extensions/cpsection/power/model.py:81
+msgid "Error in extreme pm argument, use on/off."
+msgstr "極度省電管理開關發生錯誤"
+
+#: ../extensions/cpsection/power/view.py:47
+msgid "Power management"
+msgstr "電源管理"
+
+#: ../extensions/cpsection/power/view.py:57
+msgid "Automatic power management (increases battery life)"
+msgstr "自動電源管理模式 (增加電池使用時間)"
+
+#: ../extensions/cpsection/power/view.py:85
+msgid ""
+"Extreme power management (disableswireless radio, increases battery life)"
+msgstr "積極電源管理模式 (關閉無線網路,增加電池使用時間)"
+
+#: ../extensions/cpsection/updater/__init__.py:21
+msgid "Software update"
+msgstr "軟體更新"
+
+#: ../extensions/cpsection/updater/view.py:63
+msgid ""
+"Software updates correct errors, eliminate security vulnerabilities, and "
+"provide new features."
+msgstr "軟體更新會修正錯誤,消除安全上的弱點,以及增加新的功能。"
+
+#: ../extensions/cpsection/updater/view.py:125
+#, python-format
+msgid "Checking %s..."
+msgstr "檢查 %s 中…"
+
+#: ../extensions/cpsection/updater/view.py:127
+#, python-format
+msgid "Downloading %s..."
+msgstr "下載 %s 中…"
+
+#: ../extensions/cpsection/updater/view.py:129
+#, python-format
+msgid "Updating %s..."
+msgstr "更新 %s 中…"
+
+#: ../extensions/cpsection/updater/view.py:139
+msgid "Your software is up-to-date"
+msgstr "您的軟體已經是最新版本"
+
+#: ../extensions/cpsection/updater/view.py:141
+#, python-format
+msgid "You can install %s update"
+msgid_plural "You can install %s updates"
+msgstr[0] "您可以安裝 %s 個更新"
+
+#: ../extensions/cpsection/updater/view.py:159
+msgid "Checking for updates..."
+msgstr "檢查更新中…"
+
+#: ../extensions/cpsection/updater/view.py:164
+msgid "Installing updates..."
+msgstr "安裝更新中…"
+
+#: ../extensions/cpsection/updater/view.py:172
+#, python-format
+msgid "%s update was installed"
+msgid_plural "%s updates were installed"
+msgstr[0] "已安裝 %s 個更新"
+
+#: ../extensions/cpsection/updater/view.py:253
+msgid "Install selected"
+msgstr "安裝選用的"
+
+#: ../extensions/cpsection/updater/view.py:274
+#, python-format
+msgid "Download size: %s"
+msgstr "下載檔案大小:%s"
+
+#: ../extensions/cpsection/updater/view.py:362
+#, python-format
+msgid "From version %(current)d to %(new)s (Size: %(size)s)"
+msgstr "由版本 %(current)d 更新至 %(new)s (大小: %(size)s)"
+
+#. TRANS: download size is 0
+#: ../extensions/cpsection/updater/view.py:382
+msgid "None"
+msgstr "無"
+
+#. TRANS: download size of very small updates
+#: ../extensions/cpsection/updater/view.py:385
+msgid "1 KB"
+msgstr "1 KB"
+
+#. TRANS: download size of small updates, e.g. '250 KB'
+#: ../extensions/cpsection/updater/view.py:388
+#, python-format
+msgid "%.0f KB"
+msgstr "%.0f KB"
+
+#. TRANS: download size of updates, e.g. '2.3 MB'
+#: ../extensions/cpsection/updater/view.py:391
+#, python-format
+msgid "%.1f MB"
+msgstr "%.1f MB"
+
+#: ../extensions/deviceicon/battery.py:58
+msgid "My Battery"
+msgstr "我的電池狀態"
+
+#: ../extensions/deviceicon/battery.py:137
+msgid "Removed"
+msgstr "移除"
+
+#: ../extensions/deviceicon/battery.py:140
+msgid "Charging"
+msgstr "充電中"
+
+#: ../extensions/deviceicon/battery.py:143
+msgid "Very little power remaining"
+msgstr "電池電源即將用完"
+
+#: ../extensions/deviceicon/battery.py:149
+#, python-format
+msgid "%(hour)d:%(min).2d remaining"
+msgstr "剩餘 %(hour)d:%(min).2d"
+
+#: ../extensions/deviceicon/battery.py:152
+msgid "Charged"
+msgstr "充電完成"
+
+#: ../extensions/deviceicon/network.py:49
+#, python-format
+msgid "IP address: %s"
+msgstr "網路地址: %s"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#: ../extensions/deviceicon/network.py:111
+msgid "Disconnect..."
+msgstr "切斷連線…"
+
+#: ../extensions/deviceicon/network.py:116
+msgid "Create new wireless network"
+msgstr "新增無線網路"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#: ../extensions/deviceicon/network.py:122
+#: ../extensions/deviceicon/network.py:284
+#: ../src/jarabe/desktop/meshbox.py:248 ../src/jarabe/desktop/meshbox.py:537
+msgid "Connecting..."
+msgstr "連線中…"
+
+# TODO: show the channel number
+#: ../extensions/deviceicon/network.py:126
+#: ../extensions/deviceicon/network.py:198
+#: ../extensions/deviceicon/network.py:288
+#: ../src/jarabe/desktop/meshbox.py:254 ../src/jarabe/desktop/meshbox.py:543
+msgid "Connected"
+msgstr "已連線"
+
+#: ../extensions/deviceicon/network.py:158
+msgid "Channel"
+msgstr "頻道"
+
+#: ../extensions/deviceicon/network.py:173
+msgid "Wired Network"
+msgstr "有線網路"
+
+#: ../extensions/deviceicon/network.py:201
+msgid "Speed"
+msgstr "速度"
+
+#: ../extensions/deviceicon/network.py:228
+msgid "Wireless modem"
+msgstr "無線網路數據機"
+
+#: ../extensions/deviceicon/network.py:276
+msgid "Please wait..."
+msgstr "請稍待…"
+
+#: ../extensions/deviceicon/network.py:279
+#: ../src/jarabe/desktop/meshbox.py:164 ../src/jarabe/desktop/meshbox.py:494
+msgid "Connect"
+msgstr "連線"
+
+#: ../extensions/deviceicon/network.py:280
+msgid "Disconnected"
+msgstr "已切斷"
+
+#: ../extensions/deviceicon/network.py:283
+#: ../src/jarabe/controlpanel/toolbar.py:115
+#: ../src/jarabe/desktop/homebox.py:68
+#: ../src/jarabe/frame/activitiestray.py:700
+#: ../src/jarabe/frame/activitiestray.py:799
+#: ../src/jarabe/frame/activitiestray.py:827
+msgid "Cancel"
+msgstr "取消"
+
+#: ../extensions/deviceicon/network.py:287
+#: ../src/jarabe/desktop/meshbox.py:168
+msgid "Disconnect"
+msgstr "切斷連線"
+
+#: ../extensions/deviceicon/network.py:530
+#, python-format
+msgid "%s's network"
+msgstr "%s 的網路"
+
+#: ../extensions/deviceicon/network.py:597
+#: ../extensions/deviceicon/network.py:656
+msgid "Mesh Network"
+msgstr "網狀網路"
+
+#: ../extensions/deviceicon/network.py:857
+#, python-format
+msgid "Data sent %d kb / received %d kb"
+msgstr "資料發送 %d kb / 接收 %d kb"
+
+#: ../extensions/deviceicon/network.py:868
+msgid "Connection time "
+msgstr "連線時間 "
+
+#: ../extensions/deviceicon/speaker.py:59
+msgid "My Speakers"
+msgstr "我的喇叭"
+
+#: ../extensions/deviceicon/speaker.py:133
+msgid "Unmute"
+msgstr "取消靜音"
+
+#: ../extensions/deviceicon/speaker.py:136
+msgid "Mute"
+msgstr "喇叭靜音"
+
+#: ../extensions/globalkey/screenshot.py:59
+msgid "Mesh"
+msgstr "網狀網路"
+
+#: ../extensions/globalkey/screenshot.py:61
+#: ../src/jarabe/frame/zoomtoolbar.py:39
+msgid "Group"
+msgstr "我的群組"
+
+#: ../extensions/globalkey/screenshot.py:63
+#: ../src/jarabe/frame/zoomtoolbar.py:41
+msgid "Home"
+msgstr "我的家"
+
+#: ../extensions/globalkey/screenshot.py:69
+#: ../src/jarabe/frame/zoomtoolbar.py:43
+msgid "Activity"
+msgstr "活動"
+
+#: ../extensions/globalkey/screenshot.py:72
+msgid "Screenshot"
+msgstr "畫面抓圖"
+
+#: ../extensions/globalkey/screenshot.py:74
+#, python-format
+msgid "Screenshot of \"%s\""
+msgstr "%s 的畫面抓圖"
+
+#: ../data/sugar.schemas.in.h:1
+msgid ""
+"\"disabled\" to ask nick on initialization; \"system\" to reuse UNIX account "
+"long name."
+msgstr "點選「停用」將於啟動時詢問代號,點選「系統」則恢復使用 UNIX 名稱。"
+
+#: ../data/sugar.schemas.in.h:2
+msgid "Backup URL"
+msgstr "備份網址"
+
+#: ../data/sugar.schemas.in.h:3
+msgid ""
+"Color for the XO icon that is used throughout the desktop. The string is "
+"composed of the stroke color and fill color, format is that of rbg colors. "
+"Example: #AC32FF,#9A5200"
+msgstr "您選取的顏色將使用在整個桌面當中。顏色指定字串包含了筆畫顏色與填充顏色,格式為紅藍綠三原色的強度。例如: #AC32FF,#9A5200"
+
+#: ../data/sugar.schemas.in.h:4
+msgid "Corner Delay"
+msgstr "角落觸發延遲"
+
+#: ../data/sugar.schemas.in.h:5
+msgid "Default font face"
+msgstr "預設字型"
+
+#: ../data/sugar.schemas.in.h:6
+msgid "Default font size"
+msgstr "預設字體大小"
+
+#: ../data/sugar.schemas.in.h:7
+msgid "Default nick"
+msgstr "預設代號"
+
+#: ../data/sugar.schemas.in.h:8
+msgid "Delay for the activation of the frame using the corners."
+msgstr "當游標移到畫面四角時浮現邊框的延遲時間"
+
+#: ../data/sugar.schemas.in.h:9
+msgid "Delay for the activation of the frame using the edges."
+msgstr "當游標移到畫面邊緣時浮現邊框的延遲時間"
+
+#: ../data/sugar.schemas.in.h:10
+msgid "Edge Delay"
+msgstr "邊緣觸發延遲"
+
+#: ../data/sugar.schemas.in.h:11
+msgid "Favorites Layout"
+msgstr "偏好的顯示方式"
+
+#: ../data/sugar.schemas.in.h:12
+msgid "Favorites resume mode"
+msgstr "偏好的回復模式"
+
+#: ../data/sugar.schemas.in.h:13
+msgid "Font face that is used throughout the desktop."
+msgstr "預設字型將套用於整個桌面環境"
+
+#: ../data/sugar.schemas.in.h:14
+msgid "Font size that is used throughout the desktop."
+msgstr "預設字體大小將套用於整個桌面環境"
+
+#: ../data/sugar.schemas.in.h:15
+msgid ""
+"If TRUE, Sugar will make us searchable for the other users of the Jabber "
+"server."
+msgstr "選擇「是」的話,Sugar 將會讓其他 Jabber 伺服器的使用者可以搜尋到我們"
+
+#: ../data/sugar.schemas.in.h:16
+msgid "If TRUE, Sugar will show a \"Log out\" option."
+msgstr "選擇「是」的話,Sugar 將會顯示『登出』的選項"
+
+#: ../data/sugar.schemas.in.h:17
+msgid "Jabber Server"
+msgstr "Jabber 伺服器"
+
+#: ../data/sugar.schemas.in.h:18
+msgid "Keyboard layouts"
+msgstr "鍵盤排列"
+
+#: ../data/sugar.schemas.in.h:19
+msgid "Keyboard model"
+msgstr "鍵盤型號"
+
+#: ../data/sugar.schemas.in.h:20
+msgid "Keyboard options"
+msgstr "鍵盤選項"
+
+#: ../data/sugar.schemas.in.h:21
+msgid "Layout of the favorites view."
+msgstr "我的最愛顯示方式"
+
+#: ../data/sugar.schemas.in.h:22
+msgid ""
+"List of keyboard layouts. Each entry should be in the form layout(variant)"
+msgstr "鍵盤排列列表,每個項目應該在排列列表中"
+
+#: ../data/sugar.schemas.in.h:23
+msgid "List of keyboard options."
+msgstr "鍵盤排列列表"
+
+#: ../data/sugar.schemas.in.h:24
+msgid "Power Automatic"
+msgstr "自動省電模式"
+
+#: ../data/sugar.schemas.in.h:25
+msgid "Power Automatic."
+msgstr "自動省電模式。"
+
+#: ../data/sugar.schemas.in.h:26
+msgid "Power Extreme"
+msgstr "極度省電模式"
+
+#: ../data/sugar.schemas.in.h:27
+msgid "Power Extreme."
+msgstr "極度省電模式。"
+
+#: ../data/sugar.schemas.in.h:28
+msgid "Publish to Gadget"
+msgstr "發佈到小工具"
+
+#: ../data/sugar.schemas.in.h:29
+msgid "Setting for muting the sound device."
+msgstr "音效設備靜音設定。"
+
+#: ../data/sugar.schemas.in.h:30
+msgid "Show Log out"
+msgstr "顯示登出"
+
+#: ../data/sugar.schemas.in.h:31
+msgid "Sound Muted"
+msgstr "靜音"
+
+#: ../data/sugar.schemas.in.h:32
+msgid "The keyboard model to be used"
+msgstr "選用的鍵盤型式"
+
+#: ../data/sugar.schemas.in.h:34
+msgid "Timezone setting for the system."
+msgstr "系統的時區設定"
+
+#: ../data/sugar.schemas.in.h:35
+msgid "Url of the jabber server to use."
+msgstr "Jabber 伺服器的網址。"
+
+#: ../data/sugar.schemas.in.h:36
+msgid "Url where the backup is saved to."
+msgstr "備份主機的網址。"
+
+#: ../data/sugar.schemas.in.h:37
+msgid "User Color"
+msgstr "使用者顏色"
+
+#: ../data/sugar.schemas.in.h:38
+msgid "User Name"
+msgstr "使用者名稱"
+
+#: ../data/sugar.schemas.in.h:39
+msgid "User name that is used throughout the desktop."
+msgstr "使用者名稱將用於整個桌面上"
+
+#: ../data/sugar.schemas.in.h:40
+msgid "Volume Level"
+msgstr "音量"
+
+#: ../data/sugar.schemas.in.h:41
+msgid "Volume level for the sound device."
+msgstr "音效設備的音量"
+
+#: ../data/sugar.schemas.in.h:42
+msgid ""
+"When in resume mode, clicking on a favorite icon will cause the last entry "
+"for that activity to be resumed."
+msgstr "在回復模式下,點選偏好的圖示將回復該活動前一次的使用狀態"
+
+#: ../src/jarabe/controlpanel/cmd.py:28
+#, python-format
+msgid ""
+"sugar-control-panel: WARNING, found more than one option with the same name: "
+"%s module: %r"
+msgstr "Sugar控制台:警告,於模組 %r 中發現重複的選項名稱:%s"
+
+#: ../src/jarabe/controlpanel/cmd.py:30
+#, python-format
+msgid "sugar-control-panel: key=%s not an available option"
+msgstr "Sugar控制台: 鍵值=%s 並非可用的選項"
+
+#: ../src/jarabe/controlpanel/cmd.py:31
+#, python-format
+msgid "sugar-control-panel: %s"
+msgstr "Sugar控制台: %s"
+
+# TRANS: Translators, there's a empty line at the end of this string,
+# which must appear in the translated string (msgstr) as well.
+#. TRANS: Translators, there's a empty line at the end of this string,
+#. which must appear in the translated string (msgstr) as well.
+#: ../src/jarabe/controlpanel/cmd.py:37
+msgid ""
+"Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+" Control for the sugar environment. \n"
+" Options: \n"
+" -h show this help message and exit \n"
+" -l list all the available options \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"
+" -c key clear the current value for the key \n"
+" "
+msgstr ""
+"用法: sugar-control-panel [ 選項 ] 鍵值 [ 參數 ... ] \n"
+" Sugar 環境設定工具. \n"
+" 選項: \n"
+" -h 顯示本說明文字並離開 \n"
+" -l 列出所有的選項 \n"
+" -h key 顯示本鍵值的說明 \n"
+" -g key 取得本件值的資料 \n"
+" -s key 設定本鍵值的資料 \n"
+" -c key 清除本鍵值的資料 \n"
+" "
+
+#: ../src/jarabe/controlpanel/cmd.py:50
+msgid "To apply your changes you have to restart sugar.\n"
+msgstr "需要重新啟動系統使變更生效.\n"
+
+#: ../src/jarabe/controlpanel/gui.py:281
+msgid "Warning"
+msgstr "警告"
+
+#: ../src/jarabe/controlpanel/gui.py:282
+#: ../src/jarabe/controlpanel/sectionview.py:42
+msgid "Changes require restart"
+msgstr "改變須要重新啟動才能生效"
+
+#: ../src/jarabe/controlpanel/gui.py:285
+msgid "Cancel changes"
+msgstr "取消改變"
+
+#: ../src/jarabe/controlpanel/gui.py:290 ../src/jarabe/desktop/homebox.py:70
+msgid "Later"
+msgstr "稍候"
+
+#: ../src/jarabe/controlpanel/gui.py:294
+msgid "Restart now"
+msgstr "馬上重新啟動"
+
+#: ../src/jarabe/controlpanel/toolbar.py:61 ../src/jarabe/intro/window.py:206
+msgid "Done"
+msgstr "完成"
+
+#: ../src/jarabe/controlpanel/toolbar.py:121
+#: ../src/jarabe/desktop/favoritesview.py:333
+msgid "Ok"
+msgstr "確定"
+
+#: ../src/jarabe/desktop/activitieslist.py:236
+#, python-format
+msgid "Version %s"
+msgstr "版本 %s"
+
+#: ../src/jarabe/desktop/activitieslist.py:357
+msgid "Confirm erase"
+msgstr "確認刪除"
+
+#: ../src/jarabe/desktop/activitieslist.py:359
+#, python-format
+msgid "Confirm erase: Do you want to permanently erase %s?"
+msgstr "確認刪除:您確定要永久地刪除 %s 嗎?"
+
+# self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+# TODO: Implement stopping downloads
+# self._stop_item.connect('activate', self._stop_item_activate_cb)
+# self.append_menu_item(self._stop_item)
+#: ../src/jarabe/desktop/activitieslist.py:363
+#: ../src/jarabe/frame/clipboardmenu.py:63
+#: ../src/jarabe/view/viewsource.py:218
+msgid "Keep"
+msgstr "保存"
+
+#: ../src/jarabe/desktop/activitieslist.py:366
+#: ../src/jarabe/desktop/activitieslist.py:409
+#: ../src/jarabe/journal/journaltoolbox.py:360
+#: ../src/jarabe/journal/palettes.py:105
+msgid "Erase"
+msgstr "刪除"
+
+#: ../src/jarabe/desktop/activitieslist.py:430
+msgid "Remove favorite"
+msgstr "移除偏好活動"
+
+#: ../src/jarabe/desktop/activitieslist.py:434
+msgid "Make favorite"
+msgstr "指定偏好活動"
+
+# TRANS: label for the freeform layout in the favorites view
+#. TRANS: label for the freeform layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:116
+msgid "Freeform"
+msgstr "自由格式"
+
+# TRANS: label for the ring layout in the favorites view
+#. TRANS: label for the ring layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:198
+msgid "Ring"
+msgstr "活動環"
+
+# TRANS: label for the spiral layout in the favorites view
+#. TRANS: label for the spiral layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:337
+msgid "Spiral"
+msgstr "螺旋"
+
+# TRANS: label for the box layout in the favorites view
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:404
+msgid "Box"
+msgstr "方型"
+
+# TRANS: label for the box layout in the favorites view
+#. TRANS: label for the box layout in the favorites view
+#: ../src/jarabe/desktop/favoriteslayout.py:445
+msgid "Triangle"
+msgstr "三角形"
+
+#: ../src/jarabe/desktop/favoritesview.py:324
+msgid "Registration Failed"
+msgstr "註冊失敗"
+
+#: ../src/jarabe/desktop/favoritesview.py:325
+#, python-format
+msgid "%s"
+msgstr "%s"
+
+#: ../src/jarabe/desktop/favoritesview.py:327
+msgid "Registration Successful"
+msgstr "註冊成功"
+
+#: ../src/jarabe/desktop/favoritesview.py:328
+msgid "You are now registered with your school server."
+msgstr "您已經成功註冊到校園主機"
+
+#: ../src/jarabe/desktop/favoritesview.py:630
+msgid "Register"
+msgstr "註冊"
+
+#: ../src/jarabe/desktop/homebox.py:63
+msgid "Software Update"
+msgstr "軟體更新"
+
+#: ../src/jarabe/desktop/homebox.py:64
+msgid "Update your activities to ensure compatibility with your new software"
+msgstr "更新您的活動來確保新軟體的相容性"
+
+#: ../src/jarabe/desktop/homebox.py:73
+msgid "Check now"
+msgstr "現在就檢查"
+
+#: ../src/jarabe/desktop/homebox.py:192
+msgid "List view"
+msgstr "檢視清單"
+
+#: ../src/jarabe/desktop/homebox.py:193
+msgid "<Ctrl>2"
+msgstr "<Ctrl>2"
+
+#: ../src/jarabe/desktop/homebox.py:255
+msgid "Favorites view"
+msgstr "偏好檢視"
+
+#: ../src/jarabe/desktop/homebox.py:256
+msgid "<Ctrl>1"
+msgstr "<Ctrl>1"
+
+#: ../src/jarabe/desktop/keydialog.py:135
+msgid "Key Type:"
+msgstr "金鑰類型:"
+
+#: ../src/jarabe/desktop/keydialog.py:155
+msgid "Authentication Type:"
+msgstr "認證類型:"
+
+#: ../src/jarabe/desktop/keydialog.py:220
+msgid "WPA & WPA2 Personal"
+msgstr "個人的 WPA & WPA2 加密"
+
+#: ../src/jarabe/desktop/keydialog.py:229
+msgid "Wireless Security:"
+msgstr "無線網路加密:"
+
+#: ../src/jarabe/desktop/meshbox.py:492
+#, python-format
+msgid "Mesh Network %d"
+msgstr "網狀網路 %d"
+
+# TRANS: Action label for resuming an activity.
+#. TRANS: Action label for resuming an activity.
+#: ../src/jarabe/desktop/meshbox.py:629
+#: ../src/jarabe/frame/activitiestray.py:735
+#: ../src/jarabe/journal/journaltoolbox.py:428
+#: ../src/jarabe/journal/palettes.py:65 ../src/jarabe/view/palettes.py:67
+msgid "Resume"
+msgstr "回復"
+
+#: ../src/jarabe/desktop/meshbox.py:634
+#: ../src/jarabe/frame/activitiestray.py:233
+msgid "Join"
+msgstr "加入"
+
+#: ../src/jarabe/desktop/schoolserver.py:103
+msgid "Cannot connect to the server."
+msgstr "無法連線到伺服器"
+
+#: ../src/jarabe/desktop/schoolserver.py:108
+msgid "The server could not complete the request."
+msgstr "伺服器無法完成查尋動作"
+
+#: ../src/jarabe/frame/activitiestray.py:238
+#: ../src/jarabe/frame/activitiestray.py:672
+msgid "Decline"
+msgstr "拒絕"
+
+#: ../src/jarabe/frame/activitiestray.py:624
+#, python-format
+msgid "%dB"
+msgstr "%dB"
+
+#: ../src/jarabe/frame/activitiestray.py:626
+#, python-format
+msgid "%dKB"
+msgstr "%dKB"
+
+#: ../src/jarabe/frame/activitiestray.py:628
+#, python-format
+msgid "%dMB"
+msgstr "%dMB"
+
+#: ../src/jarabe/frame/activitiestray.py:645
+#, python-format
+msgid "%s of %s"
+msgstr "%s 之 %s"
+
+#: ../src/jarabe/frame/activitiestray.py:657
+#, python-format
+msgid "Transfer from %r"
+msgstr "傳送自 %r"
+
+#: ../src/jarabe/frame/activitiestray.py:667
+msgid "Accept"
+msgstr "接受"
+
+#: ../src/jarabe/frame/activitiestray.py:690
+#: ../src/jarabe/frame/activitiestray.py:817
+#, python-format
+msgid "%s (%s)"
+msgstr "%s (%s)"
+
+#: ../src/jarabe/frame/activitiestray.py:724
+#: ../src/jarabe/frame/activitiestray.py:852
+msgid "Dismiss"
+msgstr "拒絕"
+
+#: ../src/jarabe/frame/activitiestray.py:787
+#, python-format
+msgid "Transfer to %r"
+msgstr "傳送給 %r"
+
+#: ../src/jarabe/frame/clipboardmenu.py:53 ../src/jarabe/view/palettes.py:221
+msgid "Remove"
+msgstr "移除"
+
+#: ../src/jarabe/frame/clipboardmenu.py:58
+#: ../src/jarabe/frame/clipboardmenu.py:81
+msgid "Open"
+msgstr "開啟"
+
+#: ../src/jarabe/frame/clipboardmenu.py:86
+msgid "Open with"
+msgstr "開啟使用"
+
+#: ../src/jarabe/frame/clipboardobject.py:49
+#, python-format
+msgid "%s clipping"
+msgstr "%s 裁剪"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:37
+msgid "Neighborhood"
+msgstr "我的鄰居"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:37
+msgid "F1"
+msgstr "F1"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:39
+msgid "F2"
+msgstr "F2"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:41
+msgid "F3"
+msgstr "F3"
+
+#: ../src/jarabe/frame/zoomtoolbar.py:43
+msgid "F4"
+msgstr "F4"
+
+#: ../src/jarabe/intro/window.py:128
+msgid "Click to change color:"
+msgstr "點選改變顏色:"
+
+#: ../src/jarabe/intro/window.py:192 ../src/jarabe/journal/detailview.py:103
+msgid "Back"
+msgstr "上一步"
+
+#: ../src/jarabe/intro/window.py:209
+msgid "Next"
+msgstr "下一步"
+
+#: ../src/jarabe/journal/expandedentry.py:152
+#: ../src/jarabe/journal/palettes.py:59
+msgid "Untitled"
+msgstr "未命名"
+
+#: ../src/jarabe/journal/expandedentry.py:243
+msgid "No preview"
+msgstr "沒有預覽"
+
+#: ../src/jarabe/journal/expandedentry.py:262
+#, python-format
+msgid "Kind: %s"
+msgstr "類別: %s"
+
+#: ../src/jarabe/journal/expandedentry.py:262
+msgid "Unknown"
+msgstr "未知"
+
+#: ../src/jarabe/journal/expandedentry.py:263
+#, python-format
+msgid "Date: %s"
+msgstr "日期: %s"
+
+#: ../src/jarabe/journal/expandedentry.py:264
+#, python-format
+msgid "Size: %s"
+msgstr "大小: %s"
+
+#: ../src/jarabe/journal/expandedentry.py:286 ../src/jarabe/journal/misc.py:93
+msgid "No date"
+msgstr "無日期"
+
+#: ../src/jarabe/journal/expandedentry.py:293
+msgid "Participants:"
+msgstr "參與者"
+
+#: ../src/jarabe/journal/expandedentry.py:316
+msgid "Description:"
+msgstr "說明:"
+
+#: ../src/jarabe/journal/expandedentry.py:341
+msgid "Tags:"
+msgstr "標籤:"
+
+#: ../src/jarabe/journal/journalactivity.py:108
+#: ../src/jarabe/journal/volumestoolbar.py:47
+msgid "Journal"
+msgstr "日誌"
+
+#: ../src/jarabe/journal/journaltoolbox.py:67
+msgid "Search"
+msgstr "搜尋"
+
+#: ../src/jarabe/journal/journaltoolbox.py:126
+msgid "Anytime"
+msgstr "任何時間"
+
+#: ../src/jarabe/journal/journaltoolbox.py:128
+msgid "Today"
+msgstr "今天"
+
+#: ../src/jarabe/journal/journaltoolbox.py:130
+msgid "Since yesterday"
+msgstr "一天內"
+
+# TRANS: Filter entries modified during the last 7 days.
+#. TRANS: Filter entries modified during the last 7 days.
+#: ../src/jarabe/journal/journaltoolbox.py:132
+msgid "Past week"
+msgstr "一週內"
+
+# TRANS: Filter entries modified during the last 30 days.
+#. TRANS: Filter entries modified during the last 30 days.
+#: ../src/jarabe/journal/journaltoolbox.py:134
+msgid "Past month"
+msgstr "一個月內"
+
+# TRANS: Filter entries modified during the last 356 days.
+#. TRANS: Filter entries modified during the last 356 days.
+#: ../src/jarabe/journal/journaltoolbox.py:136
+msgid "Past year"
+msgstr "一年內"
+
+#: ../src/jarabe/journal/journaltoolbox.py:143
+msgid "Anyone"
+msgstr "任何人"
+
+#: ../src/jarabe/journal/journaltoolbox.py:145
+msgid "My friends"
+msgstr "我的好友"
+
+#: ../src/jarabe/journal/journaltoolbox.py:146
+msgid "My class"
+msgstr "我的類別"
+
+# TRANS: Item in a combo box that filters by entry type.
+#: ../src/jarabe/journal/journaltoolbox.py:274
+msgid "Anything"
+msgstr "所有類別"
+
+# TODO: Add "Start with" menu item
+#: ../src/jarabe/journal/journaltoolbox.py:350
+#: ../src/jarabe/journal/palettes.py:83
+msgid "Copy"
+msgstr "複製"
+
+# TRANS: Action label for starting an entry.
+#. TRANS: Action label for starting an entry.
+#: ../src/jarabe/journal/journaltoolbox.py:431
+#: ../src/jarabe/journal/palettes.py:68
+msgid "Start"
+msgstr "啟動"
+
+#: ../src/jarabe/journal/listview.py:373
+msgid "Your Journal is empty"
+msgstr "空白的日誌內容"
+
+#: ../src/jarabe/journal/listview.py:375
+msgid "No matching entries"
+msgstr "沒有相符的紀錄"
+
+#: ../src/jarabe/journal/listview.py:386
+msgid "Clear search"
+msgstr "清除搜尋項目"
+
+#: ../src/jarabe/journal/modalalert.py:63
+msgid "Your Journal is full"
+msgstr "日誌空間已滿"
+
+#: ../src/jarabe/journal/modalalert.py:67
+msgid "Please delete some old Journal entries to make space for new ones."
+msgstr "請刪除一些舊的日誌內容來騰出空間給新的日誌"
+
+#: ../src/jarabe/journal/modalalert.py:79
+msgid "Show Journal"
+msgstr "顯示日誌"
+
+#: ../src/jarabe/journal/objectchooser.py:146
+msgid "Choose an object"
+msgstr "選擇物件"
+
+#: ../src/jarabe/journal/objectchooser.py:151
+#: ../src/jarabe/view/viewsource.py:308
+msgid "Close"
+msgstr "關閉"
+
+#: ../src/jarabe/journal/palettes.py:66
+msgid "Resume with"
+msgstr "回復活動"
+
+#: ../src/jarabe/journal/palettes.py:69
+msgid "Start with"
+msgstr "開始活動"
+
+#: ../src/jarabe/journal/palettes.py:91
+msgid "Send to"
+msgstr "傳送給"
+
+#: ../src/jarabe/journal/palettes.py:100
+msgid "View Details"
+msgstr "檢視詳細內容"
+
+#: ../src/jarabe/journal/palettes.py:178
+msgid "No friends present"
+msgstr "沒有好友上線"
+
+#: ../src/jarabe/journal/palettes.py:183
+msgid "No valid connection found"
+msgstr "找不到有效的網路連線"
+
+#: ../src/jarabe/journal/palettes.py:211
+msgid "No activity to resume entry"
+msgstr "找不到可以回復的活動"
+
+#: ../src/jarabe/journal/palettes.py:213
+msgid "No activity to start entry"
+msgstr "找不到可以開始的活動"
+
+#: ../src/jarabe/view/buddymenu.py:62
+msgid "Remove friend"
+msgstr "移除好友"
+
+#: ../src/jarabe/view/buddymenu.py:65
+msgid "Make friend"
+msgstr "結交好友"
+
+#: ../src/jarabe/view/buddymenu.py:82
+msgid "Shutdown"
+msgstr "關機"
+
+#: ../src/jarabe/view/buddymenu.py:90
+msgid "Logout"
+msgstr "顯示方式"
+
+#: ../src/jarabe/view/buddymenu.py:95
+msgid "My Settings"
+msgstr "我的設定"
+
+#: ../src/jarabe/view/buddymenu.py:130
+#, python-format
+msgid "Invite to %s"
+msgstr "邀請 %s"
+
+#: ../src/jarabe/view/palettes.py:45
+msgid "Starting..."
+msgstr "啟動中…"
+
+#. TODO: share-with, keep
+#: ../src/jarabe/view/palettes.py:74
+msgid "View Source"
+msgstr "檢視原始碼"
+
+#: ../src/jarabe/view/palettes.py:85
+msgid "Stop"
+msgstr "停止"
+
+#: ../src/jarabe/view/palettes.py:125
+msgid "Start new"
+msgstr "建立新的"
+
+#: ../src/jarabe/view/palettes.py:174
+msgid "Show contents"
+msgstr "顯示內容"
+
+#: ../src/jarabe/view/palettes.py:196 ../src/jarabe/view/palettes.py:246
+#, python-format
+msgid "%(free_space)d MB Free"
+msgstr "剩餘 %(free_space)d MB 空間"
+
+#: ../src/jarabe/view/viewsource.py:208
+msgid "Instance Source"
+msgstr "範例原始碼"
+
+#: ../src/jarabe/view/viewsource.py:233
+msgid "Source"
+msgstr "原始碼"
+
+#: ../src/jarabe/view/viewsource.py:292
+msgid "Activity Bundle Source"
+msgstr "預裝活動原始碼"
+
+#: ../src/jarabe/view/viewsource.py:299
+#, python-format
+msgid "View source: %r"
+msgstr "查看原始碼: %r"
+
+#~ msgid "Title"
+#~ msgstr "標題"
+
+#~ msgid "Version"
+#~ msgstr "版本"
+
+#~ msgid "Date"
+#~ msgstr "日期"
+
+#~ msgid "Cannot obtain data needed for registration."
+#~ msgstr "無法取得註冊所需的資料"
+
+#~ msgid "Unmount"
+#~ msgstr "退出磁碟"
+
+#~ msgid "Restart"
+#~ msgstr "重新啟動"
+
+#~ msgid ""
+#~ "© 2008 One Laptop per Child Association Inc; Red Hat Inc; and Contributors."
+#~ msgstr "© 2008 每童一機 公司; 紅帽 公司; 以及其他的貢獻者"
+
+#~ msgid "Document"
+#~ msgstr "文件"
+
+#~ msgid "Resume by default"
+#~ msgstr "預設回復"
+
+#~ msgid "Encryption Type:"
+#~ msgstr "加密類型:"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#~ msgid "Disconnecting..."
+#~ msgstr "切斷連線中…"
+
+#~ msgid "About my XO"
+#~ msgstr "關於我的XO"
+
+#~ msgid "Connected to a School Mesh Portal"
+#~ msgstr "已連接到校園網狀網入口"
+
+#~ msgid "Looking for a School Mesh Portal..."
+#~ msgstr "搜尋校園網狀網路中..."
+
+#~ msgid "Connected to an XO Mesh Portal"
+#~ msgstr "已連接到XO網狀網入口"
+
+#~ msgid "Looking for an XO Mesh Portal..."
+#~ msgstr "搜尋XO網狀網路中..."
+
+#~ msgid "Connected to a Simple Mesh"
+#~ msgstr "已連接上簡易網狀網路"
+
+#~ msgid "Starting a Simple Mesh"
+#~ msgstr "建立簡易網狀網路"
+
+#~ msgid "Unknown Mesh"
+#~ msgstr "未知網狀網路"
+
+#~ msgid "Settings"
+#~ msgstr "設定"
+
+#, python-format
+#~ msgid "Clipboard object: %s."
+#~ msgstr "剪貼簿物件: %s."
+
+#~ msgid "You must enter a server."
+#~ msgstr "請指定伺服器"
+
+#~ msgid "Control Panel"
+#~ msgstr "控制台"
+
+#~ msgid "<Ctrl>R"
+#~ msgstr "<Ctrl>R"
+
+#~ msgid "off"
+#~ msgstr "關閉"
+
+#~ msgid "on"
+#~ msgstr "開啟"
+
+#~ msgid "Permission denied. You need to be root to run this method."
+#~ msgstr "權限不足, 需要有系統管理員權限才可以執行此功能。"
+
+#~ msgid "Error in reading timezone"
+#~ msgstr "讀取時區資料時發生錯誤"
+
+#, python-format
+#~ msgid "Error copying timezone (from %s): %s"
+#~ msgstr "複製(%s)時區資料時發生錯誤: %s"
+
+#, python-format
+#~ msgid "Changing permission of timezone: %s"
+#~ msgstr "改變時區資料權限: %s"
+
+#~ msgid "About this XO"
+#~ msgstr "關於XO"
+
+#
+#~ msgid "Add to journal"
+#~ msgstr "增加到日誌"
+
+#
+#~ msgid "Reboot"
+#~ msgstr "重新啟動"
+
+#
+#~ msgid "My Battery life"
+#~ msgstr "我的電池狀態"
+
+#
+#~ msgid "Battery charging"
+#~ msgstr "電池充電中"
+
+#
+#~ msgid "Battery discharging"
+#~ msgstr "電池放電中"
+
+#
+#~ msgid "Battery fully charged"
+#~ msgstr "電池已充電完成"
+
+#
+#~ msgid "Share with:"
+#~ msgstr "分享給:"
+
+#
+#~ msgid "Private"
+#~ msgstr "私人"
+
+#
+#~ msgid "My Neighborhood"
+#~ msgstr "我的鄰居"
+
+#
+#~ msgid "Undo"
+#~ msgstr "復原"
+
+#
+#~ msgid "Redo"
+#~ msgstr "取消復原"
+
+#
+#~ msgid "Paste"
+#~ msgstr "貼上"
+
+#, python-format
+#~ msgid "%s Activity"
+#~ msgstr "%s活動"
+
+#~ msgid "Keep error"
+#~ msgstr "保存時發生錯誤"
+
+#~ msgid "Keep error: all changes will be lost"
+#~ msgstr "保存時發生錯誤: 所有的改變將被取消"
+
+#~ msgid "Don't stop"
+#~ msgstr "取消關閉"
+
+#~ msgid "Stop anyway"
+#~ msgstr "直接關閉"
+
+#~ msgid "Continue"
+#~ msgstr "繼續活動"
+
+#
+#~ msgid "OK"
+#~ msgstr "確定"
+
+#, python-format
+#~ msgid "%d year"
+#~ msgstr "%d年"
+
+#, python-format
+#~ msgid "%d years"
+#~ msgstr "%d年"
+
+#, python-format
+#~ msgid "%d month"
+#~ msgstr "%d月"
+
+#, python-format
+#~ msgid "%d months"
+#~ msgstr "%d月"
+
+#, python-format
+#~ msgid "%d week"
+#~ msgstr "%d週"
+
+#, python-format
+#~ msgid "%d weeks"
+#~ msgstr "%d週"
+
+#, python-format
+#~ msgid "%d day"
+#~ msgstr "%d日"
+
+#, python-format
+#~ msgid "%d days"
+#~ msgstr "%d日"
+
+#, python-format
+#~ msgid "%d hour"
+#~ msgstr "%d時"
+
+#, python-format
+#~ msgid "%d hours"
+#~ msgstr "%d時"
+
+#, python-format
+#~ msgid "%d minute"
+#~ msgstr "%d分"
+
+#, python-format
+#~ msgid "%d minutes"
+#~ msgstr "%d分"
+
+#, python-format
+#~ msgid "%d second"
+#~ msgstr "%d秒"
+
+#
+#~ msgid " and "
+#~ msgstr "又"
+
+#
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#, python-format
+#~ msgid ", "
+#~ msgstr ","
diff --git a/shell/src/Makefile.am b/shell/src/Makefile.am
new file mode 100644
index 0000000..83571a4
--- /dev/null
+++ b/shell/src/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = jarabe
diff --git a/shell/src/jarabe/.gitignore b/shell/src/jarabe/.gitignore
new file mode 100644
index 0000000..4acd06b
--- /dev/null
+++ b/shell/src/jarabe/.gitignore
@@ -0,0 +1 @@
+config.py
diff --git a/shell/src/jarabe/Makefile.am b/shell/src/jarabe/Makefile.am
new file mode 100644
index 0000000..84bb213
--- /dev/null
+++ b/shell/src/jarabe/Makefile.am
@@ -0,0 +1,16 @@
+SUBDIRS = \
+ controlpanel \
+ desktop \
+ frame \
+ journal \
+ model \
+ view \
+ intro \
+ util
+
+sugardir = $(pythondir)/jarabe
+sugar_PYTHON = \
+ __init__.py
+
+nodist_sugar_PYTHON = config.py
+
diff --git a/shell/src/jarabe/__init__.py b/shell/src/jarabe/__init__.py
new file mode 100644
index 0000000..41b4b1c
--- /dev/null
+++ b/shell/src/jarabe/__init__.py
@@ -0,0 +1,26 @@
+"""OLPC Sugar Graphical "Shell" Interface
+
+Provides the shell-level operations for managing
+the OLPC laptop computers. It interacts heavily
+with (and depends upon) the Sugar UI libraries.
+
+This is a "graphical" shell, the name does not
+refer to a command-line "shell" interface.
+"""
+
+# Copyright (C) 2006-2007, Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
diff --git a/shell/src/jarabe/config.py.in b/shell/src/jarabe/config.py.in
new file mode 100644
index 0000000..6c418e9
--- /dev/null
+++ b/shell/src/jarabe/config.py.in
@@ -0,0 +1,26 @@
+# Copyright (C) 2008 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+# pylint: disable-msg=C0301
+
+prefix = '@prefix@'
+data_path = '@prefix@/share/sugar/data'
+shell_path = '@prefix@/share/sugar/shell'
+locale_path = '@prefix@/share/locale'
+ext_path = '@prefix@/share/sugar/extensions'
+activities_path = "@prefix@/share/sugar/activities"
+version = '@SUCROSE_VERSION@'
+
diff --git a/shell/src/jarabe/controlpanel/Makefile.am b/shell/src/jarabe/controlpanel/Makefile.am
new file mode 100644
index 0000000..1de2961
--- /dev/null
+++ b/shell/src/jarabe/controlpanel/Makefile.am
@@ -0,0 +1,10 @@
+sugardir = $(pythondir)/jarabe/controlpanel
+sugar_PYTHON = \
+ __init__.py \
+ cmd.py \
+ gui.py \
+ inlinealert.py \
+ sectionview.py \
+ toolbar.py
+
+
diff --git a/shell/src/jarabe/controlpanel/__init__.py b/shell/src/jarabe/controlpanel/__init__.py
new file mode 100644
index 0000000..a9dd95a
--- /dev/null
+++ b/shell/src/jarabe/controlpanel/__init__.py
@@ -0,0 +1,16 @@
+# Copyright (C) 2006-2007, Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
diff --git a/shell/src/jarabe/controlpanel/cmd.py b/shell/src/jarabe/controlpanel/cmd.py
new file mode 100644
index 0000000..7144b33
--- /dev/null
+++ b/shell/src/jarabe/controlpanel/cmd.py
@@ -0,0 +1,158 @@
+# 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 sys
+import getopt
+import os
+from gettext import gettext as _
+import traceback
+import logging
+
+from jarabe import config
+
+_RESTART = 1
+
+_same_option_warning = _("sugar-control-panel: WARNING, found more than"
+ " one option with the same name: %s module: %r")
+_no_option_error = _("sugar-control-panel: key=%s not an available option")
+_general_error = _("sugar-control-panel: %s")
+
+def cmd_help():
+ '''Print the help to the screen'''
+ # TRANS: Translators, there's a empty line at the end of this string,
+ # which must appear in the translated string (msgstr) as well.
+ print _('Usage: sugar-control-panel [ option ] key [ args ... ] \n\
+ Control for the sugar environment. \n\
+ Options: \n\
+ -h show this help message and exit \n\
+ -l list all the available options \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\
+ -c key clear the current value for the key \n\
+ ')
+
+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():
+ '''Build a list of pointers to available modules and import them.
+ '''
+ modules = []
+
+ path = os.path.join(config.ext_path, 'cpsection')
+ folder = os.listdir(path)
+
+ for item in folder:
+ if os.path.isdir(os.path.join(path, item)) and \
+ os.path.exists(os.path.join(path, item, 'model.py')):
+ try:
+ module = __import__('.'.join(('cpsection', item, 'model')),
+ globals(), locals(), ['model'])
+ except Exception:
+ logging.error('Exception while loading extension:\n' + \
+ ''.join(traceback.format_exception(*sys.exc_info())))
+ else:
+ modules.append(module)
+
+ return modules
+
+def main():
+ try:
+ options, args = getopt.getopt(sys.argv[1:], "h:s:g:c:l", [])
+ except getopt.GetoptError:
+ cmd_help()
+ sys.exit(2)
+
+ if not options:
+ cmd_help()
+ sys.exit(2)
+
+ modules = load_modules()
+
+ for option, key in options:
+ found = 0
+ if option in ("-h"):
+ for module in modules:
+ method = getattr(module, 'set_' + key, None)
+ if method:
+ found += 1
+ if found == 1:
+ print method.__doc__
+ else:
+ print _(_same_option_warning % (key, module))
+ if found == 0:
+ print _(_no_option_error % key)
+ if option 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:]
+ elif method.startswith('clear_'):
+ print " %s (use the -c argument with this option)" \
+ % method[6:]
+ if option in ("-g"):
+ for module in modules:
+ method = getattr(module, 'print_' + key, None)
+ if method:
+ found += 1
+ if found == 1:
+ try:
+ method()
+ except Exception, detail:
+ print _(_general_error % detail)
+ else:
+ print _(_same_option_warning % (key, module))
+ if found == 0:
+ print _(_no_option_error % key)
+ if option in ("-s"):
+ for module in modules:
+ method = getattr(module, 'set_' + key, None)
+ if method:
+ note = 0
+ found += 1
+ if found == 1:
+ try:
+ note = method(*args)
+ except Exception, detail:
+ print _(_general_error % detail)
+ if note == _RESTART:
+ note_restart()
+ else:
+ print _(_same_option_warning % (key, module))
+ if found == 0:
+ print _(_no_option_error % key)
+ if option in ("-c"):
+ for module in modules:
+ method = getattr(module, 'clear_' + key, None)
+ if method:
+ note = 0
+ found += 1
+ if found == 1:
+ try:
+ note = method(*args)
+ except Exception, detail:
+ print _(_general_error % detail)
+ if note == _RESTART:
+ note_restart()
+ else:
+ print _(_same_option_warning % (key, module))
+ if found == 0:
+ print _(_no_option_error % key)
diff --git a/shell/src/jarabe/controlpanel/gui.py b/shell/src/jarabe/controlpanel/gui.py
new file mode 100644
index 0000000..51d9820
--- /dev/null
+++ b/shell/src/jarabe/controlpanel/gui.py
@@ -0,0 +1,431 @@
+# Copyright (C) 2008 One Laptop Per Child
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import logging
+from gettext import gettext as _
+import sys
+import traceback
+
+import gobject
+import gtk
+
+from sugar.graphics.icon import Icon
+from sugar.graphics import style
+from sugar.graphics.alert import Alert
+
+from jarabe.model.session import get_session_manager
+from jarabe.controlpanel.toolbar import MainToolbar
+from jarabe.controlpanel.toolbar import SectionToolbar
+from jarabe import config
+
+_logger = logging.getLogger('ControlPanel')
+_MAX_COLUMNS = 5
+
+class ControlPanel(gtk.Window):
+ __gtype_name__ = 'SugarControlPanel'
+
+ def __init__(self):
+ gtk.Window.__init__(self)
+
+ 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._scrolledwindow = None
+ self._separator = None
+ self._section_view = None
+ self._section_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._get_options()
+ self._current_option = None
+ self._setup_main()
+ self._setup_section()
+ self._show_main_view()
+
+ def __realize_cb(self, widget):
+ self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
+ self.window.set_accept_focus(True)
+
+ 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_col_spacings(style.GRID_CELL_SIZE)
+ self._table.set_border_width(style.GRID_CELL_SIZE)
+
+ self._scrolledwindow = gtk.ScrolledWindow()
+ self._scrolledwindow.set_policy(gtk.POLICY_AUTOMATIC,
+ gtk.POLICY_AUTOMATIC)
+ self._scrolledwindow.add_with_viewport(self._table)
+ child = self._scrolledwindow.get_child()
+ child.modify_bg(gtk.STATE_NORMAL, style.COLOR_BLACK.get_gdk_color())
+
+ 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):
+ if not os.path.exists('/ofw'):
+ del self._options['power']
+
+ try:
+ import xklavier
+ except ImportError:
+ del self._options['keyboard']
+
+ row = 0
+ column = 2
+ options = self._options.keys()
+ options.sort()
+
+ for option in options:
+ sectionicon = _SectionIcon(icon_name=self._options[option]['icon'],
+ title=self._options[option]['title'],
+ xo_color=self._options[option]['color'],
+ pixel_size=style.GRID_CELL_SIZE)
+ sectionicon.connect('button_press_event',
+ self.__select_option_cb, option)
+ sectionicon.show()
+
+ if option == 'aboutme':
+ self._table.attach(sectionicon, 0, 1, 0, 1)
+ elif option == 'aboutcomputer':
+ self._table.attach(sectionicon, 1, 2, 0, 1)
+ else:
+ self._table.attach(sectionicon,
+ column, column + 1,
+ row, row + 1)
+ column += 1
+ if column == _MAX_COLUMNS:
+ column = 0
+ row += 1
+
+ self._options[option]['button'] = sectionicon
+
+ def _show_main_view(self):
+ self._set_toolbar(self._main_toolbar)
+ self._main_toolbar.show()
+ self._set_canvas(self._scrolledwindow)
+ self._main_view.modify_bg(gtk.STATE_NORMAL,
+ style.COLOR_BLACK.get_gdk_color())
+ self._table.show()
+ self._scrolledwindow.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.lower() in key.lower():
+ self._options[option]['button'].set_sensitive(True)
+ found = True
+ break
+ if not found:
+ self._options[option]['button'].set_sensitive(False)
+
+ def _setup_section(self):
+ self._section_toolbar = SectionToolbar()
+ self._section_toolbar.connect('cancel-clicked',
+ self.__cancel_clicked_cb)
+ self._section_toolbar.connect('accept-clicked',
+ self.__accept_clicked_cb)
+
+ def show_section_view(self, option):
+ self._set_toolbar(self._section_toolbar)
+
+ icon = self._section_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._section_toolbar.get_title()
+ title.set_text(self._options[option]['title'])
+ self._section_toolbar.show()
+
+ self._current_option = option
+
+ mod = __import__('.'.join(('cpsection', option, 'view')),
+ globals(), locals(), ['view'])
+ view_class = getattr(mod, self._options[option]['view'], None)
+
+ mod = __import__('.'.join(('cpsection', option, 'model')),
+ globals(), locals(), ['model'])
+ model = ModelWrapper(mod)
+
+ self._section_view = view_class(model,
+ self._options[option]['alerts'])
+
+ self._set_canvas(self._section_view)
+ self._section_view.show()
+ self._section_view.connect('notify::is-valid',
+ self.__valid_section_cb)
+ self._section_view.connect('request-close',
+ self.__close_request_cb)
+ self._main_view.modify_bg(gtk.STATE_NORMAL,
+ style.COLOR_WHITE.get_gdk_color())
+
+ def set_section_view_auto_close(self):
+ '''Automatically close the control panel if there is "nothing to do"
+ '''
+ self._section_view.auto_close = True
+
+ def _get_options(self):
+ '''Get the available option information from the extensions
+ '''
+ options = {}
+
+ path = os.path.join(config.ext_path, 'cpsection')
+ folder = os.listdir(path)
+
+ for item in folder:
+ if os.path.isdir(os.path.join(path, item)) and \
+ os.path.exists(os.path.join(path, item, '__init__.py')):
+ try:
+ mod = __import__('.'.join(('cpsection', item)),
+ globals(), locals(), [item])
+ view_class = getattr(mod, 'CLASS', None)
+ if view_class is not None:
+ options[item] = {}
+ options[item]['alerts'] = []
+ options[item]['view'] = view_class
+ options[item]['icon'] = getattr(mod, 'ICON', item)
+ options[item]['title'] = getattr(mod, 'TITLE', item)
+ options[item]['color'] = getattr(mod, 'COLOR', None)
+ keywords = getattr(mod, 'KEYWORDS', [])
+ keywords.append(options[item]['title'].lower())
+ if item not in keywords:
+ keywords.append(item)
+ options[item]['keywords'] = keywords
+ else:
+ _logger.error('There is no CLASS constant specifieds ' \
+ 'in the view file \'%s\'.' % item)
+ except Exception:
+ logging.error('Exception while loading extension:\n' + \
+ ''.join(traceback.format_exception(*sys.exc_info())))
+
+ return options
+
+ def __cancel_clicked_cb(self, widget):
+ self._section_view.undo()
+ self._options[self._current_option]['alerts'] = []
+ self._section_toolbar.accept_button.set_sensitive(True)
+ self._show_main_view()
+
+ def __accept_clicked_cb(self, widget):
+ if self._section_view.needs_restart:
+ self._section_toolbar.accept_button.set_sensitive(False)
+ self._section_toolbar.cancel_button.set_sensitive(False)
+ alert = Alert()
+ alert.props.title = _('Warning')
+ alert.props.msg = _('Changes require restart')
+
+ icon = Icon(icon_name='dialog-cancel')
+ alert.add_button(gtk.RESPONSE_CANCEL, _('Cancel changes'), icon)
+ icon.show()
+
+ if self._current_option != 'aboutme':
+ icon = Icon(icon_name='dialog-ok')
+ alert.add_button(gtk.RESPONSE_ACCEPT, _('Later'), icon)
+ icon.show()
+
+ icon = Icon(icon_name='system-restart')
+ alert.add_button(gtk.RESPONSE_APPLY, _('Restart now'), icon)
+ icon.show()
+
+ 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)
+ self._section_toolbar.accept_button.set_sensitive(True)
+ self._section_toolbar.cancel_button.set_sensitive(True)
+ if response_id is gtk.RESPONSE_CANCEL:
+ self._section_view.undo()
+ self._section_view.setup()
+ self._options[self._current_option]['alerts'] = []
+ elif response_id is gtk.RESPONSE_ACCEPT:
+ self._options[self._current_option]['alerts'] = \
+ self._section_view.restart_alerts
+ self._show_main_view()
+ elif response_id is gtk.RESPONSE_APPLY:
+ session_manager = get_session_manager()
+ session_manager.logout()
+
+ def __select_option_cb(self, button, event, option):
+ self.show_section_view(option)
+
+ def __search_changed_cb(self, maintoolbar, query):
+ self._update(query)
+
+ def __stop_clicked_cb(self, widget):
+ self.destroy()
+
+ def __close_request_cb(self, widget, event=None):
+ self.destroy()
+
+ def __valid_section_cb(self, section_view, pspec):
+ section_is_valid = section_view.props.is_valid
+ self._section_toolbar.accept_button.set_sensitive(section_is_valid)
+
+class ModelWrapper(object):
+ def __init__(self, module):
+ self._module = module
+ self._options = {}
+ self._setup()
+
+ def _setup(self):
+ methods = dir(self._module)
+ for method in methods:
+ if method.startswith('get_') and method[4:] != 'color':
+ try:
+ self._options[method[4:]] = getattr(self._module, method)()
+ except Exception:
+ self._options[method[4:]] = None
+
+ def __getattr__(self, name):
+ return getattr(self._module, name)
+
+ def undo(self):
+ for key in self._options.keys():
+ method = getattr(self._module, 'set_' + key, None)
+ if method and self._options[key] is not None:
+ try:
+ method(self._options[key])
+ except Exception, detail:
+ _logger.debug('Error undo option: %s', detail)
+
+class _SectionIcon(gtk.EventBox):
+ __gtype_name__ = "SugarSectionIcon"
+
+ __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/shell/src/jarabe/controlpanel/inlinealert.py b/shell/src/jarabe/controlpanel/inlinealert.py
new file mode 100644
index 0000000..b1880da
--- /dev/null
+++ b/shell/src/jarabe/controlpanel/inlinealert.py
@@ -0,0 +1,83 @@
+# Copyright (C) 2008, OLPC
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import gtk
+import gobject
+import pango
+
+from sugar.graphics import style
+from sugar.graphics.icon import Icon
+
+class InlineAlert(gtk.HBox):
+ """UI interface for Inline alerts
+
+ Inline alerts are different from the other alerts beause they are
+ no dialogs, they only inform about a current event.
+
+ Properties:
+ 'msg': 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),
+ 'icon' : (object, None, None,
+ gobject.PARAM_WRITABLE)
+ }
+
+ def __init__(self, **kwargs):
+
+ self._msg = None
+ self._msg_color = None
+ self._icon = Icon(icon_name='emblem-warning',
+ fill_color=style.COLOR_SELECTION_GREY.get_svg(),
+ stroke_color=style.COLOR_WHITE.get_svg())
+
+ 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())
+
+ gobject.GObject.__init__(self, **kwargs)
+
+ self.set_spacing(style.DEFAULT_SPACING)
+ self.modify_bg(gtk.STATE_NORMAL,
+ style.COLOR_WHITE.get_gdk_color())
+
+ self.pack_start(self._icon, False)
+ self.pack_start(self._msg_label, False)
+ self._msg_label.show()
+ self._icon.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
+
+ def do_get_property(self, pspec):
+ if pspec.name == 'msg':
+ return self._msg
+
diff --git a/shell/src/jarabe/controlpanel/sectionview.py b/shell/src/jarabe/controlpanel/sectionview.py
new file mode 100644
index 0000000..4de27a2
--- /dev/null
+++ b/shell/src/jarabe/controlpanel/sectionview.py
@@ -0,0 +1,55 @@
+# 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 gobject
+import gtk
+from gettext import gettext as _
+
+class SectionView(gtk.VBox):
+ __gtype_name__ = 'SugarSectionView'
+
+ __gsignals__ = {
+ 'request-close': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([]))
+ }
+
+ __gproperties__ = {
+ 'is_valid' : (bool, None, None, True,
+ gobject.PARAM_READWRITE)
+ }
+
+ _APPLY_TIMEOUT = 1000
+
+ def __init__(self):
+ gtk.VBox.__init__(self)
+ self._is_valid = True
+ self.auto_close = False
+ self.needs_restart = False
+ self.restart_alerts = []
+ self.restart_msg = _('Changes require restart')
+
+ def do_set_property(self, pspec, value):
+ if pspec.name == 'is-valid':
+ if self._is_valid != value:
+ self._is_valid = value
+
+ def do_get_property(self, pspec):
+ if pspec.name == 'is-valid':
+ return self._is_valid
+
+ def undo(self):
+ '''Undo here the changes that have been made in this section.'''
+ pass
diff --git a/shell/src/jarabe/controlpanel/toolbar.py b/shell/src/jarabe/controlpanel/toolbar.py
new file mode 100644
index 0000000..320a8eb
--- /dev/null
+++ b/shell/src/jarabe/controlpanel/toolbar.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 toolbar of the control panel
+ """
+ __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 SectionToolbar(gtk.Toolbar):
+ """ Toolbar of the sections of the control panel
+ """
+ __gtype_name__ = 'SectionToolbar'
+
+ __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)
+
+ self.cancel_button = ToolButton('dialog-cancel')
+ self.cancel_button.set_tooltip(_('Cancel'))
+ self.cancel_button.connect('clicked', self.__cancel_button_clicked_cb)
+ self.insert(self.cancel_button, -1)
+ self.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/shell/src/jarabe/desktop/Makefile.am b/shell/src/jarabe/desktop/Makefile.am
new file mode 100644
index 0000000..25fb0b4
--- /dev/null
+++ b/shell/src/jarabe/desktop/Makefile.am
@@ -0,0 +1,18 @@
+sugardir = $(pythondir)/jarabe/desktop
+sugar_PYTHON = \
+ __init__.py \
+ activitieslist.py \
+ favoritesview.py \
+ favoriteslayout.py \
+ friendview.py \
+ grid.py \
+ groupbox.py \
+ homebox.py \
+ homewindow.py \
+ keydialog.py \
+ meshbox.py \
+ networkviews.py \
+ schoolserver.py \
+ snowflakelayout.py \
+ spreadlayout.py \
+ transitionbox.py
diff --git a/shell/src/jarabe/desktop/__init__.py b/shell/src/jarabe/desktop/__init__.py
new file mode 100644
index 0000000..a9dd95a
--- /dev/null
+++ b/shell/src/jarabe/desktop/__init__.py
@@ -0,0 +1,16 @@
+# Copyright (C) 2006-2007, Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
diff --git a/shell/src/jarabe/desktop/activitieslist.py b/shell/src/jarabe/desktop/activitieslist.py
new file mode 100644
index 0000000..e14d0f7
--- /dev/null
+++ b/shell/src/jarabe/desktop/activitieslist.py
@@ -0,0 +1,451 @@
+# Copyright (C) 2008 One Laptop Per Child
+# Copyright (C) 2009 Tomeu Vizoso
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import logging
+from gettext import gettext as _
+
+import gobject
+import pango
+import gconf
+import gtk
+
+from sugar import util
+from sugar.graphics import style
+from sugar.graphics.icon import Icon, CellRendererIcon
+from sugar.graphics.xocolor import XoColor
+from sugar.graphics.menuitem import MenuItem
+from sugar.graphics.alert import Alert
+from sugar.activity import activityfactory
+from sugar.activity.activityhandle import ActivityHandle
+
+from jarabe.model import bundleregistry
+from jarabe.view.palettes import ActivityPalette
+from jarabe.view import launcher
+from jarabe.journal import misc
+
+class ActivitiesTreeView(gtk.TreeView):
+ __gtype_name__ = 'SugarActivitiesTreeView'
+
+ __gsignals__ = {
+ 'erase-activated' : (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([str]))
+ }
+
+ def __init__(self):
+ gobject.GObject.__init__(self)
+
+ self._query = ''
+
+ self.modify_base(gtk.STATE_NORMAL, style.COLOR_WHITE.get_gdk_color())
+ self.set_headers_visible(False)
+ selection = self.get_selection()
+ selection.set_mode(gtk.SELECTION_NONE)
+
+ model = ListModel()
+ model.set_visible_func(self.__model_visible_cb)
+ self.set_model(model)
+
+ cell_favorite = CellRendererFavorite(self)
+ cell_favorite.connect('clicked', self.__favorite_clicked_cb)
+
+ column = gtk.TreeViewColumn()
+ column.pack_start(cell_favorite)
+ column.set_cell_data_func(cell_favorite, self.__favorite_set_data_cb)
+ self.append_column(column)
+
+ cell_icon = CellRendererActivityIcon(self)
+ cell_icon.connect('erase-activated', self.__erase_activated_cb)
+ cell_icon.connect('clicked', self.__icon_clicked_cb)
+
+ column = gtk.TreeViewColumn()
+ column.pack_start(cell_icon)
+ column.add_attribute(cell_icon, 'file-name', ListModel.COLUMN_ICON)
+ self.append_column(column)
+
+ cell_text = gtk.CellRendererText()
+ cell_text.props.ellipsize = pango.ELLIPSIZE_MIDDLE
+ cell_text.props.ellipsize_set = True
+
+ column = gtk.TreeViewColumn()
+ column.props.sizing = gtk.TREE_VIEW_COLUMN_GROW_ONLY
+ column.props.expand = True
+ column.set_sort_column_id(ListModel.COLUMN_TITLE)
+ column.pack_start(cell_text)
+ column.add_attribute(cell_text, 'markup', ListModel.COLUMN_TITLE)
+ self.append_column(column)
+
+ cell_text = gtk.CellRendererText()
+ cell_text.props.xalign = 1
+
+ column = gtk.TreeViewColumn()
+ column.set_alignment(1)
+ column.props.sizing = gtk.TREE_VIEW_COLUMN_GROW_ONLY
+ column.props.resizable = True
+ column.props.reorderable = True
+ column.props.expand = True
+ column.set_sort_column_id(ListModel.COLUMN_VERSION)
+ column.pack_start(cell_text)
+ column.add_attribute(cell_text, 'text', ListModel.COLUMN_VERSION_TEXT)
+ self.append_column(column)
+
+ cell_text = gtk.CellRendererText()
+ cell_text.props.xalign = 1
+
+ column = gtk.TreeViewColumn()
+ column.set_alignment(1)
+ column.props.sizing = gtk.TREE_VIEW_COLUMN_GROW_ONLY
+ column.props.resizable = True
+ column.props.reorderable = True
+ column.props.expand = True
+ column.set_sort_column_id(ListModel.COLUMN_DATE)
+ column.pack_start(cell_text)
+ column.add_attribute(cell_text, 'text', ListModel.COLUMN_DATE_TEXT)
+ self.append_column(column)
+
+ self.set_search_column(ListModel.COLUMN_TITLE)
+
+ def __erase_activated_cb(self, cell_renderer, bundle_id):
+ self.emit('erase-activated', bundle_id)
+
+ def __favorite_set_data_cb(self, column, cell, model, tree_iter):
+ favorite = model[tree_iter][ListModel.COLUMN_FAVORITE]
+ if favorite:
+ client = gconf.client_get_default()
+ color = XoColor(client.get_string('/desktop/sugar/user/color'))
+ cell.props.xo_color = color
+ else:
+ cell.props.xo_color = None
+
+ def __favorite_clicked_cb(self, cell, path):
+ row = self.get_model()[path]
+ registry = bundleregistry.get_registry()
+ registry.set_bundle_favorite(row[ListModel.COLUMN_BUNDLE_ID],
+ row[ListModel.COLUMN_VERSION],
+ not row[ListModel.COLUMN_FAVORITE])
+
+ def __icon_clicked_cb(self, cell, path):
+ row = self.get_model()[path]
+
+ registry = bundleregistry.get_registry()
+ bundle = registry.get_bundle(row[ListModel.COLUMN_BUNDLE_ID])
+
+ misc.launch(bundle)
+
+ def set_filter(self, query):
+ self._query = query.lower()
+ self.get_model().refilter()
+
+ def __model_visible_cb(self, model, tree_iter):
+ title = model[tree_iter][ListModel.COLUMN_TITLE]
+ return title is not None and title.lower().find(self._query) > -1
+
+class ListModel(gtk.TreeModelSort):
+ __gtype_name__ = 'SugarListModel'
+
+ COLUMN_BUNDLE_ID = 0
+ COLUMN_FAVORITE = 1
+ COLUMN_ICON = 2
+ COLUMN_TITLE = 3
+ COLUMN_VERSION = 4
+ COLUMN_VERSION_TEXT = 5
+ COLUMN_DATE = 6
+ COLUMN_DATE_TEXT = 7
+
+ def __init__(self):
+ self._model = gtk.ListStore(str, bool, str, str, int, str, int, str)
+ self._model_filter = self._model.filter_new()
+ gtk.TreeModelSort.__init__(self, self._model_filter)
+
+ gobject.idle_add(self.__connect_to_bundle_registry_cb)
+
+ def __connect_to_bundle_registry_cb(self):
+ registry = bundleregistry.get_registry()
+ for info in registry:
+ self._add_activity(info)
+ registry.connect('bundle-added', self.__activity_added_cb)
+ registry.connect('bundle-changed', self.__activity_changed_cb)
+ registry.connect('bundle-removed', self.__activity_removed_cb)
+
+ def __activity_added_cb(self, activity_registry, activity_info):
+ self._add_activity(activity_info)
+
+ def __activity_changed_cb(self, activity_registry, activity_info):
+ bundle_id = activity_info.get_bundle_id()
+ version = activity_info.get_activity_version()
+ favorite = activity_registry.is_bundle_favorite(bundle_id, version)
+ for row in self._model:
+ if row[ListModel.COLUMN_BUNDLE_ID] == bundle_id and \
+ row[ListModel.COLUMN_VERSION] == version:
+ row[ListModel.COLUMN_FAVORITE] = favorite
+ return
+
+ def __activity_removed_cb(self, activity_registry, activity_info):
+ bundle_id = activity_info.get_bundle_id()
+ version = activity_info.get_activity_version()
+ for row in self._model:
+ if row[ListModel.COLUMN_BUNDLE_ID] == bundle_id and \
+ row[ListModel.COLUMN_VERSION] == version:
+ self._model.remove(row.iter)
+ return
+
+ def _add_activity(self, activity_info):
+ if activity_info.get_bundle_id() == 'org.laptop.JournalActivity':
+ return
+
+ timestamp = activity_info.get_installation_time()
+ version = activity_info.get_activity_version()
+
+ registry = bundleregistry.get_registry()
+ favorite = registry.is_bundle_favorite(activity_info.get_bundle_id(),
+ version)
+
+ tag_list = activity_info.get_tags()
+ if tag_list is None or not tag_list:
+ title = '<b>%s</b>' % activity_info.get_name()
+ else:
+ tags = ', '.join(tag_list)
+ title = '<b>%s</b>\n' \
+ '<span style="italic" weight="light">%s</span>' % \
+ (activity_info.get_name(), tags)
+
+ self._model.append([activity_info.get_bundle_id(),
+ favorite,
+ activity_info.get_icon(),
+ title,
+ version,
+ _('Version %s') % version,
+ timestamp,
+ util.timestamp_to_elapsed_string(timestamp)])
+
+ def set_visible_func(self, func):
+ self._model_filter.set_visible_func(func)
+
+ def refilter(self):
+ self._model_filter.refilter()
+
+class CellRendererFavorite(CellRendererIcon):
+ __gtype_name__ = 'SugarCellRendererFavorite'
+
+ def __init__(self, tree_view):
+ CellRendererIcon.__init__(self, tree_view)
+
+ self.props.width = style.GRID_CELL_SIZE
+ self.props.height = style.GRID_CELL_SIZE
+ self.props.size = style.SMALL_ICON_SIZE
+ self.props.icon_name = 'emblem-favorite'
+ self.props.mode = gtk.CELL_RENDERER_MODE_ACTIVATABLE
+ self.props.prelit_stroke_color = style.COLOR_BUTTON_GREY.get_svg()
+ self.props.prelit_fill_color = style.COLOR_BUTTON_GREY.get_svg()
+
+class CellRendererActivityIcon(CellRendererIcon):
+ __gtype_name__ = 'SugarCellRendererActivityIcon'
+
+ __gsignals__ = {
+ 'erase-activated' : (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([str]))
+ }
+
+ def __init__(self, tree_view):
+ CellRendererIcon.__init__(self, tree_view)
+
+ self.props.width = style.GRID_CELL_SIZE
+ self.props.height = style.GRID_CELL_SIZE
+ self.props.size = style.STANDARD_ICON_SIZE
+ self.props.stroke_color = style.COLOR_BUTTON_GREY.get_svg()
+ self.props.fill_color = style.COLOR_TRANSPARENT.get_svg()
+ self.props.mode = gtk.CELL_RENDERER_MODE_ACTIVATABLE
+
+ client = gconf.client_get_default()
+ prelit_color = XoColor(client.get_string('/desktop/sugar/user/color'))
+ self.props.prelit_stroke_color = prelit_color.get_stroke_color()
+ self.props.prelit_fill_color = prelit_color.get_fill_color()
+
+ self._tree_view = tree_view
+
+ def create_palette(self):
+ model = self._tree_view.get_model()
+ row = model[self.props.palette_invoker.path]
+ bundle_id = row[ListModel.COLUMN_BUNDLE_ID]
+
+ registry = bundleregistry.get_registry()
+ palette = ActivityListPalette(registry.get_bundle(bundle_id))
+ palette.connect('erase-activated', self.__erase_activated_cb)
+ return palette
+
+ def __erase_activated_cb(self, palette, bundle_id):
+ self.emit('erase-activated', bundle_id)
+
+class ActivitiesList(gtk.VBox):
+ __gtype_name__ = 'SugarActivitiesList'
+
+ def __init__(self):
+ logging.debug('STARTUP: Loading the activities list')
+
+ gobject.GObject.__init__(self)
+
+ scrolled_window = gtk.ScrolledWindow()
+ scrolled_window.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+ scrolled_window.set_shadow_type(gtk.SHADOW_NONE)
+ scrolled_window.connect('key-press-event', self.__key_press_event_cb)
+ self.pack_start(scrolled_window)
+ scrolled_window.show()
+
+ self._tree_view = ActivitiesTreeView()
+ self._tree_view.connect('erase-activated', self.__erase_activated_cb)
+ scrolled_window.add(self._tree_view)
+ self._tree_view.show()
+
+ self._alert = None
+
+ def set_filter(self, query):
+ self._tree_view.set_filter(query)
+
+ def __key_press_event_cb(self, scrolled_window, event):
+ keyname = gtk.gdk.keyval_name(event.keyval)
+
+ vadjustment = scrolled_window.props.vadjustment
+ if keyname == 'Up':
+ if vadjustment.props.value > vadjustment.props.lower:
+ vadjustment.props.value -= vadjustment.props.step_increment
+ elif keyname == 'Down':
+ max_value = vadjustment.props.upper - vadjustment.props.page_size
+ if vadjustment.props.value < max_value:
+ vadjustment.props.value = min(
+ vadjustment.props.value + vadjustment.props.step_increment,
+ max_value)
+ else:
+ return False
+
+ return True
+
+ def add_alert(self, alert):
+ if self._alert is not None:
+ self.remove_alert()
+ self._alert = alert
+ self.pack_start(alert, False)
+ self.reorder_child(alert, 0)
+
+ def remove_alert(self):
+ self.remove(self._alert)
+ self._alert = None
+
+ def __erase_activated_cb(self, tree_view, bundle_id):
+ registry = bundleregistry.get_registry()
+ activity_info = registry.get_bundle(bundle_id)
+
+ alert = Alert()
+ alert.props.title = _('Confirm erase')
+ alert.props.msg = \
+ _('Confirm erase: Do you want to permanently erase %s?') \
+ % activity_info.get_name()
+
+ cancel_icon = Icon(icon_name='dialog-cancel')
+ alert.add_button(gtk.RESPONSE_CANCEL, _('Keep'), cancel_icon)
+
+ erase_icon = Icon(icon_name='dialog-ok')
+ alert.add_button(gtk.RESPONSE_OK, _('Erase'), erase_icon)
+
+ alert.connect('response', self.__erase_confirmation_dialog_response_cb,
+ bundle_id)
+
+ self.add_alert(alert)
+
+ def __erase_confirmation_dialog_response_cb(self, alert, response_id,
+ bundle_id):
+ self.remove_alert()
+ if response_id == gtk.RESPONSE_OK:
+ registry = bundleregistry.get_registry()
+ bundle = registry.get_bundle(bundle_id)
+ registry.uninstall(bundle)
+
+class ActivityListPalette(ActivityPalette):
+ __gtype_name__ = 'SugarActivityListPalette'
+
+ __gsignals__ = {
+ 'erase-activated' : (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([str]))
+ }
+
+ def __init__(self, activity_info):
+ ActivityPalette.__init__(self, activity_info)
+
+ self._bundle_id = activity_info.get_bundle_id()
+ self._version = activity_info.get_activity_version()
+
+ registry = bundleregistry.get_registry()
+ self._favorite = registry.is_bundle_favorite(self._bundle_id,
+ self._version)
+
+ self._favorite_item = MenuItem('')
+ self._favorite_icon = Icon(icon_name='emblem-favorite',
+ icon_size=gtk.ICON_SIZE_MENU)
+ self._favorite_item.set_image(self._favorite_icon)
+ self._favorite_item.connect('activate',
+ self.__change_favorite_activate_cb)
+ self.menu.append(self._favorite_item)
+ self._favorite_item.show()
+
+ if activity_info.is_user_activity():
+ menu_item = MenuItem(_('Erase'), 'list-remove')
+ menu_item.connect('activate', self.__erase_activate_cb)
+ self.menu.append(menu_item)
+ menu_item.show()
+
+ if not os.access(activity_info.get_path(), os.W_OK):
+ menu_item.props.sensitive = False
+
+ registry = bundleregistry.get_registry()
+ self._activity_changed_sid = registry.connect('bundle_changed',
+ self.__activity_changed_cb)
+ self._update_favorite_item()
+
+ self.connect('destroy', self.__destroy_cb)
+
+ def __destroy_cb(self, palette):
+ self.disconnect(self._activity_changed_sid)
+
+ def _update_favorite_item(self):
+ label = self._favorite_item.child
+ if self._favorite:
+ label.set_text(_('Remove favorite'))
+ xo_color = XoColor('%s,%s' % (style.COLOR_WHITE.get_svg(),
+ style.COLOR_TRANSPARENT.get_svg()))
+ else:
+ label.set_text(_('Make favorite'))
+ client = gconf.client_get_default()
+ xo_color = XoColor(client.get_string("/desktop/sugar/user/color"))
+
+ self._favorite_icon.props.xo_color = xo_color
+
+ def __change_favorite_activate_cb(self, menu_item):
+ registry = bundleregistry.get_registry()
+ registry.set_bundle_favorite(self._bundle_id,
+ self._version,
+ not self._favorite)
+
+ def __activity_changed_cb(self, activity_registry, activity_info):
+ if activity_info.get_bundle_id() == self._bundle_id and \
+ activity_info.get_activity_version() == self._version:
+ registry = bundleregistry.get_registry()
+ self._favorite = registry.is_bundle_favorite(self._bundle_id,
+ self._version)
+ self._update_favorite_item()
+
+ def __erase_activate_cb(self, menu_item):
+ self.emit('erase-activated', self._bundle_id)
+
diff --git a/shell/src/jarabe/desktop/favoriteslayout.py b/shell/src/jarabe/desktop/favoriteslayout.py
new file mode 100644
index 0000000..85e1b59
--- /dev/null
+++ b/shell/src/jarabe/desktop/favoriteslayout.py
@@ -0,0 +1,488 @@
+# Copyright (C) 2008 One Laptop Per Child
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import logging
+import math
+import hashlib
+from gettext import gettext as _
+
+import gobject
+import gtk
+import hippo
+
+from sugar.graphics import style
+
+from jarabe.model import bundleregistry
+from jarabe.desktop.grid import Grid
+
+_logger = logging.getLogger('FavoritesLayout')
+
+_CELL_SIZE = 4
+_BASE_SCALE = 1000
+
+class FavoritesLayout(gobject.GObject, hippo.CanvasLayout):
+ """Base class of the different layout types."""
+
+ __gtype_name__ = 'FavoritesLayout'
+
+ def __init__(self):
+ gobject.GObject.__init__(self)
+ self.box = None
+ self.fixed_positions = {}
+
+ def do_set_box(self, box):
+ self.box = box
+
+ def do_get_height_request(self, for_width):
+ return 0, gtk.gdk.screen_height() - style.GRID_CELL_SIZE
+
+ def do_get_width_request(self):
+ return 0, gtk.gdk.screen_width()
+
+ def compare_activities(self, icon_a, icon_b):
+ return 0
+
+ def append(self, icon, locked=False):
+ if not hasattr(type(icon), 'fixed_position'):
+ logging.debug('Icon without fixed_position: %r', icon)
+ return
+
+ icon.props.size = max(icon.props.size, style.STANDARD_ICON_SIZE)
+
+ relative_x, relative_y = icon.fixed_position
+ if relative_x < 0 or relative_y < 0:
+ logging.debug('Icon out of bounds: %r', icon)
+ return
+
+ min_width_, width = self.box.get_width_request()
+ min_height_, height = self.box.get_height_request(width)
+ self.fixed_positions[icon] = \
+ (int(relative_x * _BASE_SCALE / float(width)),
+ int(relative_y * _BASE_SCALE / float(height)))
+
+ def remove(self, icon):
+ if icon in self.fixed_positions:
+ del self.fixed_positions[icon]
+
+ def move_icon(self, icon, x, y, locked=False):
+ if icon not in self.box.get_children():
+ raise ValueError('Child not in box.')
+
+ if not(hasattr(icon, 'get_bundle_id') and hasattr(icon, 'get_version')):
+ logging.debug('Not an activity icon %r', icon)
+ return
+
+ min_width_, width = self.box.get_width_request()
+ min_height_, height = self.box.get_height_request(width)
+ registry = bundleregistry.get_registry()
+ registry.set_bundle_position(
+ icon.get_bundle_id(), icon.get_version(),
+ x * width / float(_BASE_SCALE),
+ y * height / float(_BASE_SCALE))
+ self.fixed_positions[icon] = (x, y)
+
+ def do_allocate(self, x, y, width, height, req_width, req_height,
+ origin_changed):
+ raise NotImplementedError()
+
+ def allow_dnd(self):
+ return False
+
+class RandomLayout(FavoritesLayout):
+ """Lay out icons randomly; try to nudge them around to resolve overlaps."""
+
+ __gtype_name__ = 'RandomLayout'
+
+ icon_name = 'view-freeform'
+ """Name of icon used in home view dropdown palette."""
+
+ key = 'random-layout'
+ """String used in profile to represent this view."""
+
+ # TRANS: label for the freeform layout in the favorites view
+ palette_name = _('Freeform')
+ """String used to identify this layout in home view dropdown palette."""
+
+ def __init__(self):
+ FavoritesLayout.__init__(self)
+
+ min_width_, width = self.do_get_width_request()
+ min_height_, height = self.do_get_height_request(width)
+
+ self._grid = Grid(width / _CELL_SIZE, height / _CELL_SIZE)
+ self._grid.connect('child-changed', self.__grid_child_changed_cb)
+
+ def __grid_child_changed_cb(self, grid, child):
+ child.emit_request_changed()
+
+ def append(self, icon, locked=False):
+ FavoritesLayout.append(self, icon, locked)
+
+ min_width_, child_width = icon.get_width_request()
+ min_height_, child_height = icon.get_height_request(child_width)
+ min_width_, width = self.box.get_width_request()
+ min_height_, height = self.box.get_height_request(width)
+
+ if icon in self.fixed_positions:
+ x, y = self.fixed_positions[icon]
+ x = min(x, width - child_width)
+ y = min(y, height - child_height)
+ elif hasattr(icon, 'get_bundle_id'):
+ name_hash = hashlib.md5(icon.get_bundle_id())
+ x = int(name_hash.hexdigest()[:5], 16) % (width - child_width)
+ y = int(name_hash.hexdigest()[-5:], 16) % (height - child_height)
+ else:
+ x = None
+ y = None
+
+ if x is None or y is None:
+ self._grid.add(icon,
+ child_width / _CELL_SIZE, child_height / _CELL_SIZE)
+ else:
+ self._grid.add(icon,
+ child_width / _CELL_SIZE, child_height / _CELL_SIZE,
+ x / _CELL_SIZE, y / _CELL_SIZE)
+
+ def remove(self, icon):
+ self._grid.remove(icon)
+ FavoritesLayout.remove(self, icon)
+
+ def move_icon(self, icon, x, y, locked=False):
+ self._grid.move(icon, x / _CELL_SIZE, y / _CELL_SIZE, locked)
+ FavoritesLayout.move_icon(self, icon, x, y, locked)
+
+ def do_allocate(self, x, y, width, height, req_width, req_height,
+ origin_changed):
+ for child in self.box.get_layout_children():
+ # We need to always get requests to not confuse hippo
+ min_w_, child_width = child.get_width_request()
+ min_h_, child_height = child.get_height_request(child_width)
+
+ rect = self._grid.get_child_rect(child.item)
+ child.allocate(rect.x * _CELL_SIZE,
+ rect.y * _CELL_SIZE,
+ child_width,
+ child_height,
+ origin_changed)
+
+ def allow_dnd(self):
+ return True
+
+_MINIMUM_RADIUS = style.XLARGE_ICON_SIZE / 2 + style.DEFAULT_SPACING + \
+ style.STANDARD_ICON_SIZE * 2
+_MAXIMUM_RADIUS = (gtk.gdk.screen_height() - style.GRID_CELL_SIZE) / 2 - \
+ style.STANDARD_ICON_SIZE - style.DEFAULT_SPACING
+
+class RingLayout(FavoritesLayout):
+ """Lay out icons in a ring around the XO man."""
+
+ __gtype_name__ = 'RingLayout'
+ icon_name = 'view-radial'
+ """Name of icon used in home view dropdown palette."""
+ key = 'ring-layout'
+ """String used in profile to represent this view."""
+ # TRANS: label for the ring layout in the favorites view
+ palette_name = _('Ring')
+ """String used to identify this layout in home view dropdown palette."""
+
+ def __init__(self):
+ FavoritesLayout.__init__(self)
+ self._locked_children = {}
+
+ def append(self, icon, locked=False):
+ FavoritesLayout.append(self, icon, locked)
+ if locked:
+ child = self.box.find_box_child(icon)
+ self._locked_children[child] = (0, 0)
+
+ def remove(self, icon):
+ child = self.box.find_box_child(icon)
+ if child in self._locked_children:
+ del self._locked_children[child]
+ FavoritesLayout.remove(self, icon)
+
+ def move_icon(self, icon, x, y, locked=False):
+ FavoritesLayout.move_icon(self, icon, x, y, locked)
+ if locked:
+ child = self.box.find_box_child(icon)
+ self._locked_children[child] = (x, y)
+
+ def _calculate_radius_and_icon_size(self, children_count):
+ # what's the radius required without downscaling?
+ distance = style.STANDARD_ICON_SIZE + style.DEFAULT_SPACING
+ icon_size = style.STANDARD_ICON_SIZE
+ # circumference is 2*pi*r; we want this to be at least
+ # 'children_count * distance'
+ radius = children_count * distance / (2 * math.pi)
+ # limit computed radius to reasonable bounds.
+ radius = max(radius, _MINIMUM_RADIUS)
+ radius = min(radius, _MAXIMUM_RADIUS)
+ # recompute icon size from limited radius
+ if children_count > 0:
+ icon_size = (2 * math.pi * radius / children_count) \
+ - style.DEFAULT_SPACING
+ # limit adjusted icon size.
+ icon_size = max(icon_size, style.SMALL_ICON_SIZE)
+ icon_size = min(icon_size, style.MEDIUM_ICON_SIZE)
+ return radius, icon_size
+
+ def _calculate_position(self, radius, icon_size, index, children_count,
+ sin=math.sin, cos=math.cos):
+ width, height = self.box.get_allocation()
+ angle = index * (2 * math.pi / children_count) - math.pi / 2
+ x = radius * cos(angle) + (width - icon_size) / 2
+ y = radius * sin(angle) + (height - icon_size -
+ (style.GRID_CELL_SIZE/2) ) / 2
+ return x, y
+
+ def _get_children_in_ring(self):
+ children_in_ring = [child for child in self.box.get_layout_children() \
+ if child not in self._locked_children]
+ return children_in_ring
+
+ def do_allocate(self, x, y, width, height, req_width, req_height,
+ origin_changed):
+ children_in_ring = self._get_children_in_ring()
+ if children_in_ring:
+ radius, icon_size = \
+ self._calculate_radius_and_icon_size(len(children_in_ring))
+
+ for n in range(len(children_in_ring)):
+ child = children_in_ring[n]
+
+ x, y = self._calculate_position(radius, icon_size, n,
+ len(children_in_ring))
+
+ # We need to always get requests to not confuse hippo
+ min_w_, child_width = child.get_width_request()
+ min_h_, child_height = child.get_height_request(child_width)
+
+ child.allocate(int(x), int(y), child_width, child_height,
+ origin_changed)
+ child.item.props.size = icon_size
+
+ for child in self._locked_children.keys():
+ x, y = self._locked_children[child]
+
+ # We need to always get requests to not confuse hippo
+ min_w_, child_width = child.get_width_request()
+ min_h_, child_height = child.get_height_request(child_width)
+
+ if child_width <= 0 or child_height <= 0:
+ return
+
+ child.allocate(int(x), int(y), child_width, child_height,
+ origin_changed)
+
+ def compare_activities(self, icon_a, icon_b):
+ if hasattr(icon_a, 'installation_time') and \
+ hasattr(icon_b, 'installation_time'):
+ return icon_b.installation_time - icon_a.installation_time
+ else:
+ return 0
+
+_SUNFLOWER_CONSTANT = style.STANDARD_ICON_SIZE * .75
+"""Chose a constant such that STANDARD_ICON_SIZE icons are nicely spaced."""
+
+_SUNFLOWER_OFFSET = \
+ math.pow((style.XLARGE_ICON_SIZE / 2 + style.STANDARD_ICON_SIZE) /
+ _SUNFLOWER_CONSTANT, 2)
+"""
+Compute a starting index for the `SunflowerLayout` which leaves space for
+the XO man in the center. Since r = _SUNFLOWER_CONSTANT * sqrt(n),
+solve for n when r is (XLARGE_ICON_SIZE + STANDARD_ICON_SIZE)/2.
+"""
+
+_GOLDEN_RATIO = 1.6180339887498949
+"""
+Golden ratio: http://en.wikipedia.org/wiki/Golden_ratio
+Calculation: (math.sqrt(5) + 1) / 2
+"""
+
+_SUNFLOWER_ANGLE = 2.3999632297286531
+"""
+The sunflower angle is approximately 137.5 degrees.
+This is the golden angle: http://en.wikipedia.org/wiki/Golden_angle
+Calculation: math.radians(360) / ( _GOLDEN_RATIO * _GOLDEN_RATIO )
+"""
+
+class SunflowerLayout(RingLayout):
+ """Spiral layout based on Fibonacci ratio in phyllotaxis.
+
+ See http://algorithmicbotany.org/papers/abop/abop-ch4.pdf
+ for details of Vogel's model of florets in a sunflower head."""
+
+ __gtype_name__ = 'SunflowerLayout'
+
+ icon_name = 'view-spiral'
+ """Name of icon used in home view dropdown palette."""
+
+ key = 'spiral-layout'
+ """String used in profile to represent this view."""
+
+ # TRANS: label for the spiral layout in the favorites view
+ palette_name = _('Spiral')
+ """String used to identify this layout in home view dropdown palette."""
+
+ def __init__(self):
+ RingLayout.__init__(self)
+ self.skipped_indices = []
+
+ def _calculate_radius_and_icon_size(self, children_count):
+ """Stub out this method; not used in `SunflowerLayout`."""
+ return None, style.STANDARD_ICON_SIZE
+
+ def adjust_index(self, i):
+ """Skip floret indices which end up outside the desired bounding box."""
+ for idx in self.skipped_indices:
+ if i < idx:
+ break
+ i += 1
+ return i
+
+ def _calculate_position(self, radius, icon_size, oindex, children_count,
+ sin=math.sin, cos=math.cos):
+ """Calculate the position of sunflower floret number 'oindex'.
+ If the result is outside the bounding box, use the next index which
+ is inside the bounding box."""
+
+ width, height = self.box.get_allocation()
+
+ while True:
+
+ index = self.adjust_index(oindex)
+
+ # tweak phi to get a nice gap lined up where the "active activity"
+ # icon is, below the central XO man.
+ phi = index * _SUNFLOWER_ANGLE + math.radians(-130)
+
+ # we offset index when computing r to make space for the XO man.
+ r = _SUNFLOWER_CONSTANT * math.sqrt(index + _SUNFLOWER_OFFSET)
+
+ # x,y are the top-left corner of the icon, so remove icon_size
+ # from width/height to compensate. y has an extra GRID_CELL_SIZE/2
+ # removed to make room for the "active activity" icon.
+ x = r * cos(phi) + (width - icon_size) / 2
+ y = r * sin(phi) + (height - icon_size - \
+ (style.GRID_CELL_SIZE / 2) ) / 2
+
+ # skip allocations outside the allocation box.
+ # give up once we can't fit
+ if r < math.hypot(width / 2, height / 2):
+ if y < 0 or y > (height - icon_size) or \
+ x < 0 or x > (width - icon_size):
+ self.skipped_indices.append(index)
+ continue # try again
+
+ return x, y
+
+class BoxLayout(RingLayout):
+ """Lay out icons in a square around the XO man."""
+
+ __gtype_name__ = 'BoxLayout'
+
+ icon_name = 'view-box'
+ """Name of icon used in home view dropdown palette."""
+
+ key = 'box-layout'
+ """String used in profile to represent this view."""
+
+ # TRANS: label for the box layout in the favorites view
+ palette_name = _('Box')
+ """String used to identify this layout in home view dropdown palette."""
+
+ def __init__(self):
+ RingLayout.__init__(self)
+
+ def _calculate_position(self, radius, icon_size, index, children_count,
+ sin=None, cos=None):
+
+ # use "orthogonal" versions of cos and sin in order to square the
+ # circle and turn the 'ring view' into a 'box view'
+ def cos_d(d):
+ while d < 0:
+ d += 360
+ if d < 45:
+ return 1
+ if d < 135:
+ return (90 - d) / 45.
+ if d < 225:
+ return -1
+ return cos_d(360 - d) # mirror around 180
+
+ cos = lambda r: cos_d(math.degrees(r))
+ sin = lambda r: cos_d(math.degrees(r) - 90)
+
+ return RingLayout._calculate_position\
+ (self, radius, icon_size, index, children_count,
+ sin=sin, cos=cos)
+
+class TriangleLayout(RingLayout):
+ """Lay out icons in a triangle around the XO man."""
+
+ __gtype_name__ = 'TriangleLayout'
+
+ icon_name = 'view-triangle'
+ """Name of icon used in home view dropdown palette."""
+
+ key = 'triangle-layout'
+ """String used in profile to represent this view."""
+
+ # TRANS: label for the box layout in the favorites view
+ palette_name = _('Triangle')
+ """String used to identify this layout in home view dropdown palette."""
+
+ def __init__(self):
+ RingLayout.__init__(self)
+
+ def _calculate_radius_and_icon_size(self, children_count):
+ # use slightly larger minimum radius than parent, because sides
+ # of triangle come awful close to the center.
+ radius, icon_size = \
+ RingLayout._calculate_radius_and_icon_size(self, children_count)
+ return max(radius, _MINIMUM_RADIUS + style.MEDIUM_ICON_SIZE), icon_size
+
+ def _calculate_position(self, radius, icon_size, index, children_count,
+ sin=math.sin, cos=math.cos):
+ # tweak cos and sin in order to make the 'ring' into an equilateral
+ # triangle.
+
+ def cos_d(d):
+ while d < -90:
+ d += 360
+ if d <= 30:
+ return (d + 90) / 120.
+ if d <= 90:
+ return (90 - d) / 60.
+ return -cos_d(180 - d) # mirror around 90
+
+ sqrt_3 = math.sqrt(3)
+
+ def sin_d(d):
+ while d < -90:
+ d += 360
+ if d <= 30:
+ return ((d + 90) / 120.) * sqrt_3 - 1
+ if d <= 90:
+ return sqrt_3 - 1
+ return sin_d(180 - d) # mirror around 90
+
+ cos = lambda r: cos_d(math.degrees(r))
+ sin = lambda r: sin_d(math.degrees(r))
+
+ return RingLayout._calculate_position\
+ (self, radius, icon_size, index, children_count,
+ sin=sin, cos=cos)
diff --git a/shell/src/jarabe/desktop/favoritesview.py b/shell/src/jarabe/desktop/favoritesview.py
new file mode 100644
index 0000000..bb85024
--- /dev/null
+++ b/shell/src/jarabe/desktop/favoritesview.py
@@ -0,0 +1,670 @@
+# Copyright (C) 2006-2007 Red Hat, Inc.
+# Copyright (C) 2008 One Laptop Per Child
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import logging
+from gettext import gettext as _
+import math
+
+import gobject
+import gconf
+import gtk
+import hippo
+
+from sugar.graphics import style
+from sugar.graphics.icon import Icon, CanvasIcon
+from sugar.graphics.menuitem import MenuItem
+from sugar.graphics.alert import Alert
+from sugar.graphics.xocolor import XoColor
+from sugar.activity import activityfactory
+from sugar.activity.activityhandle import ActivityHandle
+from sugar.presence import presenceservice
+from sugar import dispatch
+from sugar.datastore import datastore
+
+from jarabe.view.palettes import JournalPalette
+from jarabe.view.palettes import CurrentActivityPalette, ActivityPalette
+from jarabe.view.buddyicon import BuddyIcon
+from jarabe.view.buddymenu import BuddyMenu
+from jarabe.view import launcher
+from jarabe.model.buddy import BuddyModel, get_owner_instance
+from jarabe.model import shell
+from jarabe.model import bundleregistry
+from jarabe.journal import misc
+
+from jarabe.desktop import schoolserver
+from jarabe.desktop.schoolserver import RegisterError
+from jarabe.desktop import favoriteslayout
+
+_logger = logging.getLogger('FavoritesView')
+
+_ICON_DND_TARGET = ('activity-icon', gtk.TARGET_SAME_WIDGET, 0)
+
+LAYOUT_MAP = {favoriteslayout.RingLayout.key: favoriteslayout.RingLayout,
+ #favoriteslayout.BoxLayout.key: favoriteslayout.BoxLayout,
+ #favoriteslayout.TriangleLayout.key: favoriteslayout.TriangleLayout,
+ #favoriteslayout.SunflowerLayout.key: favoriteslayout.SunflowerLayout,
+ favoriteslayout.RandomLayout.key: favoriteslayout.RandomLayout}
+"""Map numeric layout identifiers to uninstantiated subclasses of
+`FavoritesLayout` which implement the layouts. Additional information
+about the layout can be accessed with fields of the class."""
+
+class FavoritesView(hippo.Canvas):
+ __gtype_name__ = 'SugarFavoritesView'
+
+ def __init__(self, **kwargs):
+ logging.debug('STARTUP: Loading the favorites view')
+
+ gobject.GObject.__init__(self, **kwargs)
+
+ # DND stuff
+ self._pressed_button = None
+ self._press_start_x = None
+ self._press_start_y = None
+ self._hot_x = None
+ self._hot_y = None
+ self._last_clicked_icon = None
+
+ self._box = hippo.CanvasBox()
+ self._box.props.background_color = style.COLOR_WHITE.get_int()
+ self.set_root(self._box)
+
+ self._my_icon = OwnerIcon(style.XLARGE_ICON_SIZE)
+ self._my_icon.connect('register-activate', self.__register_activate_cb)
+ self._box.append(self._my_icon)
+
+ self._current_activity = CurrentActivityIcon()
+ self._box.append(self._current_activity)
+
+ self._layout = None
+ self._alert = None
+ self._resume_mode = True
+
+ # More DND stuff
+ self.add_events(gtk.gdk.BUTTON_PRESS_MASK |
+ gtk.gdk.POINTER_MOTION_HINT_MASK)
+ self.connect('motion-notify-event', self.__motion_notify_event_cb)
+ self.connect('button-press-event', self.__button_press_event_cb)
+ self.connect('drag-begin', self.__drag_begin_cb)
+ self.connect('drag-motion', self.__drag_motion_cb)
+ self.connect('drag-drop', self.__drag_drop_cb)
+ self.connect('drag-data-received', self.__drag_data_received_cb)
+
+ gobject.idle_add(self.__connect_to_bundle_registry_cb)
+
+ favorites_settings = get_settings()
+ favorites_settings.changed.connect(self.__settings_changed_cb)
+ self._set_layout(favorites_settings.layout)
+
+ def __settings_changed_cb(self, **kwargs):
+ favorites_settings = get_settings()
+ self._set_layout(favorites_settings.layout)
+
+ def __connect_to_bundle_registry_cb(self):
+ registry = bundleregistry.get_registry()
+
+ for info in registry:
+ if registry.is_bundle_favorite(info.get_bundle_id(),
+ info.get_activity_version()):
+ self._add_activity(info)
+
+ registry.connect('bundle-added', self.__activity_added_cb)
+ registry.connect('bundle-removed', self.__activity_removed_cb)
+ registry.connect('bundle-changed', self.__activity_changed_cb)
+
+ def _add_activity(self, activity_info):
+ if activity_info.get_bundle_id() == 'org.laptop.JournalActivity':
+ return
+ icon = ActivityIcon(activity_info)
+ icon.props.size = style.STANDARD_ICON_SIZE
+ icon.set_resume_mode(self._resume_mode)
+ self._box.insert_sorted(icon, 0, self._layout.compare_activities)
+ self._layout.append(icon)
+
+ def __activity_added_cb(self, activity_registry, activity_info):
+ registry = bundleregistry.get_registry()
+ if registry.is_bundle_favorite(activity_info.get_bundle_id(),
+ activity_info.get_activity_version()):
+ self._add_activity(activity_info)
+
+ def _find_activity_icon(self, bundle_id, version):
+ for icon in self._box.get_children():
+ if isinstance(icon, ActivityIcon) and \
+ icon.bundle_id == bundle_id and icon.version == version:
+ return icon
+ return None
+
+ def __activity_removed_cb(self, activity_registry, activity_info):
+ icon = self._find_activity_icon(activity_info.get_bundle_id(),
+ activity_info.get_activity_version())
+ if icon is not None:
+ self._layout.remove(icon)
+ self._box.remove(icon)
+
+ def __activity_changed_cb(self, activity_registry, activity_info):
+ if activity_info.get_bundle_id() == 'org.laptop.JournalActivity':
+ return
+ icon = self._find_activity_icon(activity_info.get_bundle_id(),
+ activity_info.get_activity_version())
+ if icon is not None:
+ self._box.remove(icon)
+
+ registry = bundleregistry.get_registry()
+ if registry.is_bundle_favorite(activity_info.get_bundle_id(),
+ activity_info.get_activity_version()):
+ self._add_activity(activity_info)
+
+ def do_size_allocate(self, allocation):
+ width = allocation.width
+ height = allocation.height
+
+ min_w_, my_icon_width = self._my_icon.get_width_request()
+ min_h_, my_icon_height = self._my_icon.get_height_request(my_icon_width)
+ x = (width - my_icon_width) / 2
+ y = (height - my_icon_height - style.GRID_CELL_SIZE) / 2
+ self._layout.move_icon(self._my_icon, x, y, locked=True)
+
+ min_w_, icon_width = self._current_activity.get_width_request()
+ min_h_, icon_height = \
+ self._current_activity.get_height_request(icon_width)
+ x = (width - icon_width) / 2
+ y = (height - my_icon_height - style.GRID_CELL_SIZE) / 2 + \
+ my_icon_height + style.DEFAULT_PADDING
+ self._layout.move_icon(self._current_activity, x, y, locked=True)
+
+ hippo.Canvas.do_size_allocate(self, allocation)
+
+ # TODO: Dnd methods. This should be merged somehow inside hippo-canvas.
+ def __button_press_event_cb(self, widget, event):
+ if event.button == 1 and event.type == gtk.gdk.BUTTON_PRESS:
+ self._last_clicked_icon = self._get_icon_at_coords(event.x, event.y)
+ if self._last_clicked_icon is not None:
+ self._pressed_button = event.button
+ self._press_start_x = event.x
+ self._press_start_y = event.y
+
+ return False
+
+ def _get_icon_at_coords(self, x, y):
+ for icon in self._box.get_children():
+ icon_x, icon_y = icon.get_context().translate_to_widget(icon)
+ icon_width, icon_height = icon.get_allocation()
+
+ if (x >= icon_x ) and (x <= icon_x + icon_width) and \
+ (y >= icon_y ) and (y <= icon_y + icon_height) and \
+ isinstance(icon, ActivityIcon):
+ return icon
+ return None
+
+ def __motion_notify_event_cb(self, widget, event):
+ if not self._pressed_button:
+ return False
+
+ # if the mouse button is not pressed, no drag should occurr
+ if not event.state & gtk.gdk.BUTTON1_MASK:
+ self._pressed_button = None
+ return False
+
+ if event.is_hint:
+ x, y, state_ = event.window.get_pointer()
+ else:
+ x = event.x
+ y = event.y
+
+ if widget.drag_check_threshold(int(self._press_start_x),
+ int(self._press_start_y),
+ int(x),
+ int(y)):
+ context_ = widget.drag_begin([_ICON_DND_TARGET],
+ gtk.gdk.ACTION_MOVE,
+ 1,
+ event)
+ return False
+
+ def __drag_begin_cb(self, widget, context):
+ icon_file_name = self._last_clicked_icon.props.file_name
+ # TODO: we should get the pixbuf from the widget, so it has colors, etc
+ pixbuf = gtk.gdk.pixbuf_new_from_file(icon_file_name)
+
+ self._hot_x = pixbuf.props.width / 2
+ self._hot_y = pixbuf.props.height / 2
+ context.set_icon_pixbuf(pixbuf, self._hot_x, self._hot_y)
+
+ def __drag_motion_cb(self, widget, context, x, y, time):
+ if self._last_clicked_icon is not None:
+ context.drag_status(context.suggested_action, time)
+ return True
+ else:
+ return False
+
+ def __drag_drop_cb(self, widget, context, x, y, time):
+ if self._last_clicked_icon is not None:
+ self.drag_get_data(context, _ICON_DND_TARGET[0])
+
+ self._layout.move_icon(self._last_clicked_icon,
+ x - self._hot_x, y - self._hot_y)
+
+ self._pressed_button = None
+ self._press_start_x = None
+ self._press_start_y = None
+ self._hot_x = None
+ self._hot_y = None
+ self._last_clicked_icon = None
+
+ return True
+ else:
+ return False
+
+ def __drag_data_received_cb(self, widget, context, x, y, selection_data,
+ info, time):
+ context.drop_finish(success=True, time=time)
+
+ def _set_layout(self, layout):
+ if layout not in LAYOUT_MAP:
+ logging.warn('Unknown favorites layout: %r' % layout)
+ layout = favoriteslayout.RingLayout.key
+ assert layout in LAYOUT_MAP
+
+ if type(self._layout) == LAYOUT_MAP[layout]:
+ return
+
+ self._layout = LAYOUT_MAP[layout]()
+ self._box.set_layout(self._layout)
+
+ #TODO: compatibility hack while sort() gets added to the hippo python
+ # bindings
+ if hasattr(self._box, 'sort'):
+ self._box.sort(self._layout.compare_activities)
+
+ for icon in self._box.get_children():
+ if icon not in [self._my_icon, self._current_activity]:
+ self._layout.append(icon)
+
+ self._layout.append(self._my_icon, locked=True)
+ self._layout.append(self._current_activity, locked=True)
+
+ if self._layout.allow_dnd():
+ self.drag_source_set(0, [], 0)
+ self.drag_dest_set(0, [], 0)
+ else:
+ self.drag_source_unset()
+ self.drag_dest_unset()
+
+ layout = property(None, _set_layout)
+
+ def add_alert(self, alert):
+ if self._alert is not None:
+ self.remove_alert()
+ alert.set_size_request(gtk.gdk.screen_width(), -1)
+ self._alert = hippo.CanvasWidget(widget=alert)
+ self._box.append(self._alert, hippo.PACK_FIXED)
+
+ def remove_alert(self):
+ self._box.remove(self._alert)
+ self._alert = None
+
+ def __register_activate_cb(self, icon):
+ alert = Alert()
+ try:
+ schoolserver.register_laptop()
+ except RegisterError, e:
+ alert.props.title = _('Registration Failed')
+ alert.props.msg = _('%s') % e
+ else:
+ alert.props.title = _('Registration Successful')
+ alert.props.msg = _('You are now registered ' \
+ 'with your school server.')
+ self._my_icon.remove_register_menu()
+
+ ok_icon = Icon(icon_name='dialog-ok')
+ alert.add_button(gtk.RESPONSE_OK, _('Ok'), ok_icon)
+
+ self.add_alert(alert)
+ alert.connect('response', self.__register_alert_response_cb)
+
+ def __register_alert_response_cb(self, alert, response_id):
+ self.remove_alert()
+
+ def set_resume_mode(self, resume_mode):
+ self._resume_mode = resume_mode
+ for icon in self._box.get_children():
+ if hasattr(icon, 'set_resume_mode'):
+ icon.set_resume_mode(self._resume_mode)
+
+
+class ActivityIcon(CanvasIcon):
+ __gtype_name__ = 'SugarFavoriteActivityIcon'
+
+ _BORDER_WIDTH = style.zoom(3)
+ _MAX_RESUME_ENTRIES = 5
+
+ def __init__(self, activity_info):
+ CanvasIcon.__init__(self, cache=True,
+ file_name=activity_info.get_icon())
+
+ self._activity_info = activity_info
+ self._journal_entries = []
+ self._hovering = False
+ self._resume_mode = True
+
+ self.connect('hovering-changed', self.__hovering_changed_event_cb)
+ self.connect('button-release-event', self.__button_release_event_cb)
+
+ datastore.updated.connect(self.__datastore_listener_updated_cb)
+ datastore.deleted.connect(self.__datastore_listener_deleted_cb)
+
+ self._refresh()
+ self._update()
+
+ def _refresh(self):
+ bundle_id = self._activity_info.get_bundle_id()
+ properties = ['uid', 'title', 'icon-color', 'activity', 'activity_id',
+ 'mime_type', 'mountpoint']
+ self._get_last_activity_async(bundle_id, properties)
+
+ def __datastore_listener_updated_cb(self, **kwargs):
+ bundle_id = self._activity_info.get_bundle_id()
+ if kwargs['metadata'].get('activity', '') == bundle_id:
+ self._refresh()
+
+ def __datastore_listener_deleted_cb(self, **kwargs):
+ for entry in self._journal_entries:
+ if entry['uid'] == kwargs['object_id']:
+ self._refresh()
+ break
+
+ def _get_last_activity_async(self, bundle_id, properties):
+ query = {'activity': bundle_id}
+ datastore.find(query, sorting=['+timestamp'],
+ limit=self._MAX_RESUME_ENTRIES,
+ properties=properties,
+ reply_handler=self.__get_last_activity_reply_handler_cb,
+ error_handler=self.__get_last_activity_error_handler_cb)
+
+ def __get_last_activity_reply_handler_cb(self, entries, total_count):
+ # If there's a problem with the DS index, we may get entries not related
+ # to this activity.
+ checked_entries = []
+ for entry in entries:
+ if entry['activity'] == self.bundle_id:
+ checked_entries.append(entry)
+
+ self._journal_entries = checked_entries
+ self._update()
+
+ def __get_last_activity_error_handler_cb(self, error):
+ logging.error('Error retrieving most recent activities: %r', error)
+
+ def _update(self):
+ self.palette = None
+ if not self._resume_mode or not self._journal_entries:
+ xo_color = XoColor('%s,%s' % (style.COLOR_BUTTON_GREY.get_svg(),
+ style.COLOR_TRANSPARENT.get_svg()))
+ else:
+ xo_color = misc.get_icon_color(self._journal_entries[0])
+ self.props.xo_color = xo_color
+
+ def create_palette(self):
+ palette = FavoritePalette(self._activity_info, self._journal_entries)
+ palette.connect('activate', self.__palette_activate_cb)
+ palette.connect('entry-activate', self.__palette_entry_activate_cb)
+ return palette
+
+ def __palette_activate_cb(self, palette):
+ self._activate()
+
+ def __palette_entry_activate_cb(self, palette, metadata):
+ self._resume(metadata)
+
+ def __hovering_changed_event_cb(self, icon, hovering):
+ self._hovering = hovering
+ self.emit_paint_needed(0, 0, -1, -1)
+
+ def do_paint_above_children(self, cr, damaged_box):
+ if not self._hovering:
+ return
+
+ width, height = self.get_allocation()
+
+ x = ActivityIcon._BORDER_WIDTH / 2.0
+ y = ActivityIcon._BORDER_WIDTH / 2.0
+ width -= ActivityIcon._BORDER_WIDTH
+ height -= ActivityIcon._BORDER_WIDTH
+ radius = width / 10.0
+
+ cr.move_to(x + radius, y)
+ cr.arc(x + width - radius, y + radius, radius, math.pi * 1.5,
+ math.pi * 2.0)
+ cr.arc(x + width - radius, x + height - radius, radius, 0,
+ math.pi * 0.5)
+ cr.arc(x + radius, y + height - radius, radius, math.pi * 0.5, math.pi)
+ cr.arc(x + radius, y + radius, radius, math.pi, math.pi * 1.5)
+
+ color = style.COLOR_SELECTION_GREY.get_int()
+ hippo.cairo_set_source_rgba32(cr, color)
+ cr.set_line_width(ActivityIcon._BORDER_WIDTH)
+ cr.stroke()
+
+ def do_get_content_height_request(self, for_width):
+ height, height = CanvasIcon.do_get_content_height_request(self,
+ for_width)
+ height += ActivityIcon._BORDER_WIDTH * 2
+ return height, height
+
+ def do_get_content_width_request(self):
+ width, width = CanvasIcon.do_get_content_width_request(self)
+ width += ActivityIcon._BORDER_WIDTH * 2
+ return width, width
+
+ def __button_release_event_cb(self, icon, event):
+ self._activate()
+
+ def _resume(self, journal_entry):
+ if not journal_entry['activity_id']:
+ journal_entry['activity_id'] = activityfactory.create_activity_id()
+ misc.resume(journal_entry, self._activity_info.get_bundle_id())
+
+ def _activate(self):
+ if self.palette is not None:
+ self.palette.popdown(immediate=True)
+
+ if self._resume_mode and self._journal_entries:
+ self._resume(self._journal_entries[0])
+ else:
+ misc.launch(self._activity_info)
+
+ def get_bundle_id(self):
+ return self._activity_info.get_bundle_id()
+ bundle_id = property(get_bundle_id, None)
+
+ def get_version(self):
+ return self._activity_info.get_activity_version()
+ version = property(get_version, None)
+
+ def _get_installation_time(self):
+ return self._activity_info.get_installation_time()
+ installation_time = property(_get_installation_time, None)
+
+ def _get_fixed_position(self):
+ registry = bundleregistry.get_registry()
+ return registry.get_bundle_position(self.bundle_id, self.version)
+ fixed_position = property(_get_fixed_position, None)
+
+ def set_resume_mode(self, resume_mode):
+ self._resume_mode = resume_mode
+ self._update()
+
+class FavoritePalette(ActivityPalette):
+ __gtype_name__ = 'SugarFavoritePalette'
+
+ __gsignals__ = {
+ 'entry-activate': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([object])),
+ }
+
+ def __init__(self, activity_info, journal_entries):
+ ActivityPalette.__init__(self, activity_info)
+
+ if not journal_entries:
+ xo_color = XoColor('%s,%s' % (style.COLOR_BUTTON_GREY.get_svg(),
+ style.COLOR_TRANSPARENT.get_svg()))
+ else:
+ xo_color = misc.get_icon_color(journal_entries[0])
+
+ self.props.icon = Icon(file=activity_info.get_icon(),
+ xo_color=xo_color,
+ icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR)
+
+ if journal_entries:
+ self.props.secondary_text = journal_entries[0]['title']
+
+ menu_items = []
+ for entry in journal_entries:
+ icon_file_name = misc.get_icon_name(entry)
+ color = misc.get_icon_color(entry)
+
+ menu_item = MenuItem(text_label=entry['title'],
+ file_name=icon_file_name,
+ xo_color=color)
+ menu_item.connect('activate', self.__resume_entry_cb, entry)
+ menu_items.append(menu_item)
+ menu_item.show()
+
+ if journal_entries:
+ separator = gtk.SeparatorMenuItem()
+ menu_items.append(separator)
+ separator.show()
+
+ for i in range(0, len(menu_items)):
+ self.menu.insert(menu_items[i], i)
+
+ def __resume_entry_cb(self, menu_item, entry):
+ if entry is not None:
+ self.emit('entry-activate', entry)
+
+class CurrentActivityIcon(CanvasIcon, hippo.CanvasItem):
+ def __init__(self):
+ CanvasIcon.__init__(self, cache=True)
+ self._home_model = shell.get_model()
+ self._home_activity = self._home_model.get_active_activity()
+
+ if self._home_activity is not None:
+ self._update()
+
+ self._home_model.connect('active-activity-changed',
+ self.__active_activity_changed_cb)
+
+ self.connect('button-release-event', self.__button_release_event_cb)
+
+ def __button_release_event_cb(self, icon, event):
+ window = self._home_model.get_active_activity().get_window()
+ window.activate(gtk.get_current_event_time())
+
+ def _update(self):
+ self.props.file_name = self._home_activity.get_icon_path()
+ self.props.xo_color = self._home_activity.get_icon_color()
+ self.props.size = style.STANDARD_ICON_SIZE
+
+ if self.palette is not None:
+ self.palette.destroy()
+ self.palette = None
+
+ def create_palette(self):
+ if self._home_activity.is_journal():
+ palette = JournalPalette(self._home_activity)
+ else:
+ palette = CurrentActivityPalette(self._home_activity)
+ return palette
+
+ def __active_activity_changed_cb(self, home_model, home_activity):
+ self._home_activity = home_activity
+ self._update()
+
+class OwnerIcon(BuddyIcon):
+ __gtype_name__ = 'SugarFavoritesOwnerIcon'
+
+ __gsignals__ = {
+ 'register-activate' : (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([]))
+ }
+ def __init__(self, size):
+ BuddyIcon.__init__(self, buddy=get_owner_instance(), size=size)
+
+ self._palette_enabled = False
+ self._register_menu = None
+
+ def create_palette(self):
+ if not self._palette_enabled:
+ self._palette_enabled = True
+ return
+
+ presence_service = presenceservice.get_instance()
+ palette = BuddyMenu(self.buddy)
+
+ client = gconf.client_get_default()
+ backup_url = client.get_string('/desktop/sugar/backup_url')
+ if not backup_url:
+ self._register_menu = MenuItem(_('Register'), 'media-record')
+ self._register_menu.connect('activate', self.__register_activate_cb)
+ palette.menu.append(self._register_menu)
+ self._register_menu.show()
+
+ return palette
+
+ def get_toplevel(self):
+ return hippo.get_canvas_for_item(self).get_toplevel()
+
+ def __register_activate_cb(self, menuitem):
+ self.emit('register-activate')
+
+ def remove_register_menu(self):
+ self.palette.menu.remove(self._register_menu)
+
+class FavoritesSetting(object):
+
+ _FAVORITES_KEY = "/desktop/sugar/desktop/favorites_layout"
+
+ def __init__(self):
+ client = gconf.client_get_default()
+ self._layout = client.get_string(self._FAVORITES_KEY)
+ logging.debug('FavoritesSetting layout %r', self._layout)
+
+ self._mode = None
+
+ self.changed = dispatch.Signal()
+
+ def get_layout(self):
+ return self._layout
+
+ def set_layout(self, layout):
+ logging.debug('set_layout %r %r', layout, self._layout)
+ if layout != self._layout:
+ self._layout = layout
+
+ client = gconf.client_get_default()
+ client.set_string(self._FAVORITES_KEY, layout)
+
+ self.changed.send(self)
+
+ layout = property(get_layout, set_layout)
+
+_favorites_settings = None
+
+def get_settings():
+ global _favorites_settings
+ if _favorites_settings is None:
+ _favorites_settings = FavoritesSetting()
+ return _favorites_settings
diff --git a/shell/src/jarabe/desktop/friendview.py b/shell/src/jarabe/desktop/friendview.py
new file mode 100644
index 0000000..c3faef8
--- /dev/null
+++ b/shell/src/jarabe/desktop/friendview.py
@@ -0,0 +1,87 @@
+# Copyright (C) 2006-2007 Red Hat, Inc.
+# Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# 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 hippo
+
+from sugar.graphics.icon import CanvasIcon
+from sugar.graphics import style
+from sugar.presence import presenceservice
+
+from jarabe.view.buddyicon import BuddyIcon
+from jarabe.model import bundleregistry
+
+class FriendView(hippo.CanvasBox):
+ def __init__(self, buddy, **kwargs):
+ hippo.CanvasBox.__init__(self, **kwargs)
+
+ self._pservice = presenceservice.get_instance()
+
+ self._buddy = buddy
+ self._buddy_icon = BuddyIcon(buddy)
+ self._buddy_icon.props.size = style.LARGE_ICON_SIZE
+ self.append(self._buddy_icon)
+
+ self._activity_icon = CanvasIcon(size=style.LARGE_ICON_SIZE)
+ self._activity_icon_visible = False
+
+ self._update_activity()
+
+ self._buddy.connect('notify::current-activity',
+ self.__buddy_notify_current_activity_cb)
+ self._buddy.connect('notify::present', self.__buddy_notify_present_cb)
+ self._buddy.connect('notify::color', self.__buddy_notify_color_cb)
+
+ def _get_new_icon_name(self, ps_activity):
+ registry = bundleregistry.get_registry()
+ activity_info = registry.get_bundle(ps_activity.props.type)
+ if activity_info:
+ return activity_info.get_icon()
+ return None
+
+ def _remove_activity_icon(self):
+ if self._activity_icon_visible:
+ self.remove(self._activity_icon)
+ self._activity_icon_visible = False
+
+ def __buddy_notify_current_activity_cb(self, buddy, pspec):
+ self._update_activity()
+
+ def _update_activity(self):
+ if not self._buddy.props.present or \
+ not self._buddy.props.current_activity:
+ self._remove_activity_icon()
+ return
+
+ # FIXME: use some sort of "unknown activity" icon rather
+ # than hiding the icon?
+ name = self._get_new_icon_name(self._buddy.current_activity)
+ if name:
+ self._activity_icon.props.file_name = name
+ self._activity_icon.props.xo_color = self._buddy.props.color
+ if not self._activity_icon_visible:
+ self.append(self._activity_icon, hippo.PACK_EXPAND)
+ self._activity_icon_visible = True
+ else:
+ self._remove_activity_icon()
+
+ def __buddy_notify_present_cb(self, buddy, pspec):
+ self._update_activity()
+
+ def __buddy_notify_color_cb(self, buddy, pspec):
+ # TODO: shouldn't this change self._buddy_icon instead?
+ self._activity_icon.props.xo_color = buddy.props.color
+
diff --git a/shell/src/jarabe/desktop/grid.py b/shell/src/jarabe/desktop/grid.py
new file mode 100644
index 0000000..f3412c9
--- /dev/null
+++ b/shell/src/jarabe/desktop/grid.py
@@ -0,0 +1,201 @@
+# Copyright (C) 2007 Red Hat, Inc.
+# 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 random
+
+import gobject
+import gtk
+
+from sugar import _sugarext
+
+_PLACE_TRIALS = 20
+_MAX_WEIGHT = 255
+_REFRESH_RATE = 200
+_MAX_COLLISIONS_PER_REFRESH = 20
+
+class Grid(_sugarext.Grid):
+ __gsignals__ = {
+ 'child-changed' : (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT]))
+ }
+ def __init__(self, width, height):
+ gobject.GObject.__init__(self)
+
+ self.width = width
+ self.height = height
+ self._children = []
+ self._child_rects = {}
+ self._locked_children = set()
+ self._collisions = []
+ self._collisions_sid = 0
+
+ self.setup(self.width, self.height)
+
+ def add(self, child, width, height, x=None, y=None, locked=False):
+ if x is not None and y is not None:
+ rect = gtk.gdk.Rectangle(x, y, width, height)
+ weight = self.compute_weight(rect)
+ else:
+ trials = _PLACE_TRIALS
+ weight = _MAX_WEIGHT
+ while trials > 0 and weight:
+ x = int(random.random() * (self.width - width))
+ y = int(random.random() * (self.height - height))
+
+ rect = gtk.gdk.Rectangle(x, y, width, height)
+ new_weight = self.compute_weight(rect)
+ if weight > new_weight:
+ weight = new_weight
+
+ trials -= 1
+
+ self._child_rects[child] = rect
+ self._children.append(child)
+ self.add_weight(self._child_rects[child])
+ if locked:
+ self._locked_children.add(child)
+
+ if weight > 0:
+ self._detect_collisions(child)
+
+ def remove(self, child):
+ self._children.remove(child)
+ self.remove_weight(self._child_rects[child])
+ self._locked_children.discard(child)
+ del self._child_rects[child]
+
+ if child in self._collisions:
+ self._collisions.remove(child)
+
+ def move(self, child, x, y, locked=False):
+ self.remove_weight(self._child_rects[child])
+
+ rect = self._child_rects[child]
+ rect.x = x
+ rect.y = y
+
+ weight = self.compute_weight(rect)
+ self.add_weight(self._child_rects[child])
+
+ if locked:
+ self._locked_children.add(child)
+ else:
+ self._locked_children.discard(child)
+
+ if weight > 0:
+ self._detect_collisions(child)
+
+ def _shift_child(self, child, weight):
+ rect = self._child_rects[child]
+
+ new_rects = []
+
+ # Get rects right, left, bottom and top
+ if (rect.x + rect.width < self.width - 1):
+ new_rects.append(gtk.gdk.Rectangle(rect.x + 1, rect.y,
+ rect.width, rect.height))
+
+ if (rect.x - 1 > 0):
+ new_rects.append(gtk.gdk.Rectangle(rect.x - 1, rect.y,
+ rect.width, rect.height))
+
+ if (rect.y + rect.height < self.height - 1):
+ new_rects.append(gtk.gdk.Rectangle(rect.x, rect.y + 1,
+ rect.width, rect.height))
+
+ if (rect.y - 1 > 0):
+ new_rects.append(gtk.gdk.Rectangle(rect.x, rect.y - 1,
+ rect.width, rect.height))
+
+ # Get diagonal rects
+ if rect.x + rect.width < self.width - 1 and \
+ rect.y + rect.height < self.height - 1:
+ new_rects.append(gtk.gdk.Rectangle(rect.x + 1, rect.y + 1,
+ rect.width, rect.height))
+
+ if rect.x - 1 > 0 and rect.y + rect.height < self.height - 1:
+ new_rects.append(gtk.gdk.Rectangle(rect.x - 1, rect.y + 1,
+ rect.width, rect.height))
+
+ if rect.x + rect.width < self.width - 1 and rect.y - 1 > 0:
+ new_rects.append(gtk.gdk.Rectangle(rect.x + 1, rect.y - 1,
+ rect.width, rect.height))
+
+ if rect.x - 1 > 0 and rect.y - 1 > 0:
+ new_rects.append(gtk.gdk.Rectangle(rect.x - 1, rect.y - 1,
+ rect.width, rect.height))
+
+ random.shuffle(new_rects)
+
+ best_rect = None
+ for new_rect in new_rects:
+ new_weight = self.compute_weight(new_rect)
+ if new_weight < weight:
+ best_rect = new_rect
+ weight = new_weight
+
+ if best_rect:
+ self._child_rects[child] = best_rect
+ weight = self._shift_child(child, weight)
+
+ return weight
+
+ def __solve_collisions_cb(self):
+ for i_ in range(_MAX_COLLISIONS_PER_REFRESH):
+ collision = self._collisions.pop(0)
+
+ old_rect = self._child_rects[collision]
+ self.remove_weight(old_rect)
+ weight = self.compute_weight(old_rect)
+ weight = self._shift_child(collision, weight)
+ self.add_weight(self._child_rects[collision])
+
+ # TODO: we shouldn't give up the first time we failed to find a
+ # better position.
+ if old_rect != self._child_rects[collision]:
+ self._detect_collisions(collision)
+ self.emit('child-changed', collision)
+ if weight > 0:
+ self._collisions.append(collision)
+
+ if not self._collisions:
+ self._collisions_sid = 0
+ return False
+
+ return True
+
+ def _detect_collisions(self, child):
+ collision_found = False
+ child_rect = self._child_rects[child]
+ for c in self._children:
+ intersection = child_rect.intersect(self._child_rects[c])
+ if c != child and intersection.width > 0:
+ if c not in self._locked_children and c not in self._collisions:
+ collision_found = True
+ self._collisions.append(c)
+
+ if collision_found:
+ if child not in self._collisions:
+ self._collisions.append(child)
+
+ if self._collisions and not self._collisions_sid:
+ self._collisions_sid = gobject.timeout_add(_REFRESH_RATE,
+ self.__solve_collisions_cb, priority=gobject.PRIORITY_LOW)
+
+ def get_child_rect(self, child):
+ return self._child_rects[child]
diff --git a/shell/src/jarabe/desktop/groupbox.py b/shell/src/jarabe/desktop/groupbox.py
new file mode 100644
index 0000000..89043fe
--- /dev/null
+++ b/shell/src/jarabe/desktop/groupbox.py
@@ -0,0 +1,92 @@
+# Copyright (C) 2006-2007 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import logging
+
+import gobject
+import hippo
+import gconf
+
+from sugar.graphics import style
+from sugar.graphics.icon import CanvasIcon
+from sugar.graphics.xocolor import XoColor
+
+from jarabe.view.buddymenu import BuddyMenu
+from jarabe.model.buddy import get_owner_instance
+from jarabe.model import friends
+from jarabe.desktop.friendview import FriendView
+from jarabe.desktop.spreadlayout import SpreadLayout
+
+class GroupBox(hippo.Canvas):
+ __gtype_name__ = 'SugarGroupBox'
+ def __init__(self):
+ logging.debug("STARTUP: Loading the group view")
+
+ gobject.GObject.__init__(self)
+
+ self._box = hippo.CanvasBox()
+ self._box.props.background_color = style.COLOR_WHITE.get_int()
+ self.set_root(self._box)
+
+ self._friends = {}
+
+ self._layout = SpreadLayout()
+ self._box.set_layout(self._layout)
+
+ client = gconf.client_get_default()
+ color = XoColor(client.get_string("/desktop/sugar/user/color"))
+
+ self._owner_icon = CanvasIcon(icon_name='computer-xo', cache=True,
+ xo_color=color)
+ self._owner_icon.props.size = style.LARGE_ICON_SIZE
+
+ self._owner_icon.set_palette(BuddyMenu(get_owner_instance()))
+ self._layout.add(self._owner_icon)
+
+ friends_model = friends.get_model()
+
+ for friend in friends_model:
+ self.add_friend(friend)
+
+ friends_model.connect('friend-added', self._friend_added_cb)
+ friends_model.connect('friend-removed', self._friend_removed_cb)
+
+ def add_friend(self, buddy_info):
+ icon = FriendView(buddy_info)
+ self._layout.add(icon)
+
+ self._friends[buddy_info.get_key()] = icon
+
+ def _friend_added_cb(self, data_model, buddy_info):
+ self.add_friend(buddy_info)
+
+ def _friend_removed_cb(self, data_model, key):
+ icon = self._friends[key]
+ self._layout.remove(icon)
+ del self._friends[key]
+ icon.destroy()
+
+ def do_size_allocate(self, allocation):
+ width = allocation.width
+ height = allocation.height
+
+ min_w_, icon_width = self._owner_icon.get_width_request()
+ min_h_, icon_height = self._owner_icon.get_height_request(icon_width)
+ x = (width - icon_width) / 2
+ y = (height - icon_height) / 2
+ self._layout.move(self._owner_icon, x, y)
+
+ hippo.Canvas.do_size_allocate(self, allocation)
diff --git a/shell/src/jarabe/desktop/homebox.py b/shell/src/jarabe/desktop/homebox.py
new file mode 100644
index 0000000..85279ff
--- /dev/null
+++ b/shell/src/jarabe/desktop/homebox.py
@@ -0,0 +1,298 @@
+# Copyright (C) 2008 One Laptop Per Child
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from gettext import gettext as _
+import logging
+import os
+
+import gobject
+import gtk
+
+from sugar.graphics import style
+from sugar.graphics import iconentry
+from sugar.graphics.radiotoolbutton import RadioToolButton
+from sugar.graphics.alert import Alert
+from sugar.graphics.icon import Icon
+
+from jarabe.desktop import favoritesview
+from jarabe.desktop.activitieslist import ActivitiesList
+
+_FAVORITES_VIEW = 0
+_LIST_VIEW = 1
+
+_AUTOSEARCH_TIMEOUT = 1000
+
+class HomeBox(gtk.VBox):
+ __gtype_name__ = 'SugarHomeBox'
+
+ def __init__(self):
+ logging.debug("STARTUP: Loading the home view")
+
+ gobject.GObject.__init__(self)
+
+ self._favorites_view = favoritesview.FavoritesView()
+ self._list_view = ActivitiesList()
+
+ self._toolbar = HomeToolbar()
+ self._toolbar.connect('query-changed', self.__toolbar_query_changed_cb)
+ self._toolbar.connect('view-changed', self.__toolbar_view_changed_cb)
+ self.pack_start(self._toolbar, expand=False)
+ self._toolbar.show()
+
+ self._set_view(_FAVORITES_VIEW)
+
+ def show_software_updates_alert(self):
+ alert = Alert()
+ updater_icon = Icon(icon_name='module-updater',
+ pixel_size = style.STANDARD_ICON_SIZE)
+ alert.props.icon = updater_icon
+ updater_icon.show()
+ alert.props.title = _('Software Update')
+ alert.props.msg = _('Update your activities to ensure'
+ ' compatibility with your new software')
+
+ cancel_icon = Icon(icon_name='dialog-cancel')
+ alert.add_button(gtk.RESPONSE_CANCEL, _('Cancel'), cancel_icon)
+
+ alert.add_button(gtk.RESPONSE_REJECT, _('Later'))
+
+ erase_icon = Icon(icon_name='dialog-ok')
+ alert.add_button(gtk.RESPONSE_OK, _('Check now'), erase_icon)
+
+ if self._list_view in self.get_children():
+ self._list_view.add_alert(alert)
+ else:
+ self._favorites_view.add_alert(alert)
+ alert.connect('response', self.__software_update_response_cb)
+
+ def __software_update_response_cb(self, alert, response_id):
+ if self._list_view in self.get_children():
+ self._list_view.remove_alert()
+ else:
+ self._favorites_view.remove_alert()
+
+ if response_id != gtk.RESPONSE_REJECT:
+ update_trigger_file = os.path.expanduser('~/.sugar-update')
+ try:
+ os.unlink(update_trigger_file)
+ except OSError:
+ logging.error('Software-update: Can not remove file %s',
+ update_trigger_file)
+
+ if response_id == gtk.RESPONSE_OK:
+ from jarabe.controlpanel.gui import ControlPanel
+ panel = ControlPanel()
+ panel.set_transient_for(self.get_toplevel())
+ panel.show()
+ panel.show_section_view('updater')
+ panel.set_section_view_auto_close()
+
+ def __toolbar_query_changed_cb(self, toolbar, query):
+ query = query.lower()
+ self._list_view.set_filter(query)
+
+ def __toolbar_view_changed_cb(self, toolbar, view):
+ self._set_view(view)
+
+ def _set_view(self, view):
+ if view == _FAVORITES_VIEW:
+ if self._list_view in self.get_children():
+ self.remove(self._list_view)
+
+ if self._favorites_view not in self.get_children():
+ self.add(self._favorites_view)
+ self._favorites_view.show()
+ elif view == _LIST_VIEW:
+ if self._favorites_view in self.get_children():
+ self.remove(self._favorites_view)
+
+ if self._list_view not in self.get_children():
+ self.add(self._list_view)
+ self._list_view.show()
+ else:
+ raise ValueError('Invalid view: %r' % view)
+
+ _REDRAW_TIMEOUT = 5 * 60 * 1000 # 5 minutes
+
+ def resume(self):
+ pass
+
+ def suspend(self):
+ pass
+
+ def has_activities(self):
+ # TODO: Do we need this?
+ #return self._donut.has_activities()
+ return False
+
+ def focus_search_entry(self):
+ self._toolbar.search_entry.grab_focus()
+
+ def set_resume_mode(self, resume_mode):
+ self._favorites_view.set_resume_mode(resume_mode)
+
+class HomeToolbar(gtk.Toolbar):
+ __gtype_name__ = 'SugarHomeToolbar'
+
+ __gsignals__ = {
+ 'query-changed': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([str])),
+ 'view-changed': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([object]))
+ }
+
+ def __init__(self):
+ gtk.Toolbar.__init__(self)
+
+ self._query = None
+ self._autosearch_timer = None
+
+ 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('activate', self.__entry_activated_cb)
+ self.search_entry.connect('changed', self.__entry_changed_cb)
+ tool_item.add(self.search_entry)
+ self.search_entry.set_sensitive(False)
+ self.search_entry.show()
+
+ self._add_separator(expand=True)
+
+ favorites_button = FavoritesButton()
+ favorites_button.connect('toggled', self.__view_button_toggled_cb,
+ _FAVORITES_VIEW)
+ self.insert(favorites_button, -1)
+ favorites_button.show()
+
+ self._list_button = RadioToolButton(named_icon='view-list')
+ self._list_button.props.group = favorites_button
+ self._list_button.props.tooltip = _('List view')
+ self._list_button.props.accelerator = _('<Ctrl>2')
+ self._list_button.connect('toggled', self.__view_button_toggled_cb,
+ _LIST_VIEW)
+ self.insert(self._list_button, -1)
+ self._list_button.show()
+
+ self._add_separator()
+
+ def __view_button_toggled_cb(self, button, view):
+ if button.props.active:
+ if view == _FAVORITES_VIEW:
+ self.search_entry.set_text('')
+ self.search_entry.set_sensitive(False)
+ self.emit('view-changed', view)
+ else:
+ self.search_entry.set_sensitive(True)
+ self.search_entry.grab_focus()
+ self.emit('view-changed', view)
+
+ 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.GRID_CELL_SIZE,
+ style.GRID_CELL_SIZE)
+ self.insert(separator, -1)
+ separator.show()
+
+ def __entry_activated_cb(self, entry):
+ if self._autosearch_timer:
+ gobject.source_remove(self._autosearch_timer)
+ new_query = entry.props.text
+ if self._query != new_query:
+ self._query = new_query
+
+ if self._query is not '':
+ self._list_button.props.active = True
+ self.emit('query-changed', self._query)
+
+ def __entry_changed_cb(self, entry):
+ if not entry.props.text:
+ entry.activate()
+ return
+
+ if self._autosearch_timer:
+ gobject.source_remove(self._autosearch_timer)
+ self._autosearch_timer = gobject.timeout_add(_AUTOSEARCH_TIMEOUT,
+ self.__autosearch_timer_cb)
+
+ def __autosearch_timer_cb(self):
+ self._autosearch_timer = None
+ self.search_entry.activate()
+ return False
+
+class FavoritesButton(RadioToolButton):
+ __gtype_name__ = 'SugarFavoritesButton'
+
+ def __init__(self):
+ RadioToolButton.__init__(self)
+
+ self.props.tooltip = _('Favorites view')
+ self.props.accelerator = _('<Ctrl>1')
+ self.props.group = None
+
+ favorites_settings = favoritesview.get_settings()
+ self._layout = favorites_settings.layout
+ self._update_icon()
+
+ # someday, this will be a gtk.Table()
+ layouts_grid = gtk.HBox()
+ layout_item = None
+ for layoutid, layoutclass in sorted(favoritesview.LAYOUT_MAP.items()):
+ layout_item = RadioToolButton(icon_name=layoutclass.icon_name,
+ group=layout_item, active=False)
+ if layoutid == self._layout:
+ layout_item.set_active(True)
+ layouts_grid.pack_start(layout_item, fill=False)
+ layout_item.connect('toggled', self.__layout_activate_cb,
+ layoutid)
+ layouts_grid.show_all()
+ self.props.palette.set_content(layouts_grid)
+
+ def __layout_activate_cb(self, menu_item, layout):
+ if not menu_item.get_active():
+ return
+ if self._layout == layout and self.props.active:
+ return
+
+ if self._layout != layout:
+ self._layout = layout
+ self._update_icon()
+
+ favorites_settings = favoritesview.get_settings()
+ favorites_settings.layout = layout
+
+ if not self.props.active:
+ self.props.active = True
+ else:
+ self.emit('toggled')
+
+ def _update_icon(self):
+ self.props.named_icon = favoritesview.LAYOUT_MAP[self._layout]\
+ .icon_name
+
diff --git a/shell/src/jarabe/desktop/homewindow.py b/shell/src/jarabe/desktop/homewindow.py
new file mode 100644
index 0000000..fec4289
--- /dev/null
+++ b/shell/src/jarabe/desktop/homewindow.py
@@ -0,0 +1,193 @@
+# Copyright (C) 2006-2007 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import logging
+
+import gtk
+
+from sugar.graphics import style
+from sugar.graphics import palettegroup
+
+from jarabe.desktop.meshbox import MeshBox
+from jarabe.desktop.homebox import HomeBox
+from jarabe.desktop.groupbox import GroupBox
+from jarabe.desktop.transitionbox import TransitionBox
+from jarabe.model.shell import ShellModel
+from jarabe.model import shell
+
+_HOME_PAGE = 0
+_GROUP_PAGE = 1
+_MESH_PAGE = 2
+_TRANSITION_PAGE = 3
+
+class HomeWindow(gtk.Window):
+ def __init__(self):
+ logging.debug('STARTUP: Loading the desktop window')
+ gtk.Window.__init__(self)
+
+ accel_group = gtk.AccelGroup()
+ self.set_data('sugar-accel-group', accel_group)
+ self.add_accel_group(accel_group)
+
+ self._active = False
+ self._fully_obscured = True
+
+ screen = self.get_screen()
+ screen.connect('size-changed', self.__screen_size_change_cb)
+ self.set_default_size(screen.get_width(),
+ screen.get_height())
+
+ self.realize()
+ self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DESKTOP)
+
+ self.add_events(gtk.gdk.VISIBILITY_NOTIFY_MASK)
+ self.connect('visibility-notify-event',
+ self._visibility_notify_event_cb)
+ self.connect('map-event', self.__map_event_cb)
+ self.connect('key-press-event', self.__key_press_event_cb)
+ self.connect('key-release-event', self.__key_release_event_cb)
+
+ self._home_box = HomeBox()
+ self._group_box = GroupBox()
+ self._mesh_box = MeshBox()
+ self._transition_box = TransitionBox()
+
+ self.add(self._home_box)
+ self._home_box.show()
+
+ self._transition_box.connect('completed',
+ self._transition_completed_cb)
+
+ shell.get_model().zoom_level_changed.connect(
+ self.__zoom_level_changed_cb)
+
+ def _deactivate_view(self, level):
+ group = palettegroup.get_group("default")
+ group.popdown()
+ if level == ShellModel.ZOOM_HOME:
+ self._home_box.suspend()
+ elif level == ShellModel.ZOOM_MESH:
+ self._mesh_box.suspend()
+
+ def __screen_size_change_cb(self, screen):
+ self.resize(screen.get_width(), screen.get_height())
+
+ def _activate_view(self, level):
+ if level == ShellModel.ZOOM_HOME:
+ self._home_box.resume()
+ elif level == ShellModel.ZOOM_MESH:
+ self._mesh_box.resume()
+
+ def _visibility_notify_event_cb(self, window, event):
+ fully_obscured = (event.state == gtk.gdk.VISIBILITY_FULLY_OBSCURED)
+ if self._fully_obscured == fully_obscured:
+ return
+ self._fully_obscured = fully_obscured
+
+ if fully_obscured:
+ self._deactivate_view(shell.get_model().zoom_level)
+ else:
+ display = gtk.gdk.display_get_default()
+ screen_, x_, y_, modmask = display.get_pointer()
+ if modmask & gtk.gdk.MOD1_MASK:
+ self._home_box.set_resume_mode(False)
+ else:
+ self._home_box.set_resume_mode(True)
+
+ self._activate_view(shell.get_model().zoom_level)
+
+ def __key_press_event_cb(self, window, event):
+ if event.keyval in [gtk.keysyms.Alt_L, gtk.keysyms.Alt_R]:
+ self._home_box.set_resume_mode(False)
+ return False
+
+ def __key_release_event_cb(self, window, event):
+ if event.keyval in [gtk.keysyms.Alt_L, gtk.keysyms.Alt_R]:
+ self._home_box.set_resume_mode(True)
+ return False
+
+ def __map_event_cb(self, window, event):
+ # have to make the desktop window active
+ # since metacity doesn't make it on startup
+ timestamp = event.get_time()
+ if not timestamp:
+ timestamp = gtk.gdk.x11_get_server_time(self.window)
+ self.window.focus(timestamp)
+
+ def __zoom_level_changed_cb(self, **kwargs):
+ old_level = kwargs['old_level']
+ new_level = kwargs['new_level']
+
+ self._deactivate_view(old_level)
+ self._activate_view(new_level)
+
+ if old_level != ShellModel.ZOOM_ACTIVITY and \
+ new_level != ShellModel.ZOOM_ACTIVITY:
+ self.remove(self.get_child())
+ self.add(self._transition_box)
+ self._transition_box.show()
+
+ if new_level == ShellModel.ZOOM_HOME:
+ end_size = style.XLARGE_ICON_SIZE
+ elif new_level == ShellModel.ZOOM_GROUP:
+ end_size = style.LARGE_ICON_SIZE
+ elif new_level == ShellModel.ZOOM_MESH:
+ end_size = style.STANDARD_ICON_SIZE
+
+ if old_level == ShellModel.ZOOM_HOME:
+ start_size = style.XLARGE_ICON_SIZE
+ elif old_level == ShellModel.ZOOM_GROUP:
+ start_size = style.LARGE_ICON_SIZE
+ elif old_level == ShellModel.ZOOM_MESH:
+ start_size = style.STANDARD_ICON_SIZE
+
+ self._transition_box.start_transition(start_size, end_size)
+ else:
+ self._update_view(new_level)
+
+ def _transition_completed_cb(self, transition_box):
+ self._update_view(shell.get_model().zoom_level)
+
+ def _update_view(self, level):
+ if level == ShellModel.ZOOM_ACTIVITY:
+ return
+
+ current_child = self.get_child()
+ self.remove(current_child)
+
+ if level == ShellModel.ZOOM_HOME:
+ self.add(self._home_box)
+ self._home_box.show()
+ self._home_box.focus_search_entry()
+ elif level == ShellModel.ZOOM_GROUP:
+ self.add(self._group_box)
+ self._group_box.show()
+ elif level == ShellModel.ZOOM_MESH:
+ self.add(self._mesh_box)
+ self._mesh_box.show()
+ self._mesh_box.focus_search_entry()
+
+ def get_home_box(self):
+ return self._home_box
+
+_instance = None
+
+def get_instance():
+ global _instance
+ if not _instance:
+ _instance = HomeWindow()
+ return _instance
+
diff --git a/shell/src/jarabe/desktop/keydialog.py b/shell/src/jarabe/desktop/keydialog.py
new file mode 100644
index 0000000..1e6d17a
--- /dev/null
+++ b/shell/src/jarabe/desktop/keydialog.py
@@ -0,0 +1,317 @@
+# Copyright (C) 2006-2007 Red Hat, Inc.
+# Copyright (C) 2009 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 hashlib
+from gettext import gettext as _
+
+import gtk
+import dbus
+
+from jarabe.model import network
+from jarabe.model.network import Secrets
+
+IW_AUTH_ALG_OPEN_SYSTEM = 'open'
+IW_AUTH_ALG_SHARED_KEY = 'shared'
+
+def string_is_hex(key):
+ is_hex = True
+ for c in key:
+ if not 'a' <= c.lower() <= 'f' and not '0' <= c <= '9':
+ is_hex = False
+ return is_hex
+
+def string_is_ascii(string):
+ try:
+ string.encode('ascii')
+ return True
+ except UnicodeEncodeError:
+ return False
+
+def string_to_hex(passphrase):
+ key = ''
+ for c in passphrase:
+ key += '%02x' % ord(c)
+ return key
+
+def hash_passphrase(passphrase):
+ # passphrase must have a length of 64
+ if len(passphrase) > 64:
+ passphrase = passphrase[:64]
+ elif len(passphrase) < 64:
+ while len(passphrase) < 64:
+ passphrase += passphrase[:64 - len(passphrase)]
+ passphrase = hashlib.md5(passphrase).digest()
+ return string_to_hex(passphrase)[:26]
+
+class CanceledKeyRequestError(dbus.DBusException):
+ def __init__(self):
+ dbus.DBusException.__init__(self)
+ self._dbus_error_name = network.NM_SETTINGS_IFACE + '.CanceledError'
+
+class KeyDialog(gtk.Dialog):
+ def __init__(self, ssid, flags, wpa_flags, rsn_flags, dev_caps, settings,
+ response):
+ gtk.Dialog.__init__(self, flags=gtk.DIALOG_MODAL)
+ self.set_title("Wireless Key Required")
+
+ self._settings = settings
+ self._response = response
+ self._entry = None
+ self._ssid = ssid
+ self._flags = flags
+ self._wpa_flags = wpa_flags
+ self._rsn_flags = rsn_flags
+ self._dev_caps = dev_caps
+
+ self.set_has_separator(False)
+
+ label = gtk.Label("A wireless encryption key is required for\n" \
+ " the wireless network '%s'." % self._ssid)
+ self.vbox.pack_start(label)
+
+ self.add_buttons(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
+ gtk.STOCK_OK, gtk.RESPONSE_OK)
+ self.set_default_response(gtk.RESPONSE_OK)
+ self.set_has_separator(True)
+
+ def add_key_entry(self):
+ self._entry = gtk.Entry()
+ self._entry.connect('changed', self._update_response_sensitivity)
+ self._entry.connect('activate', self._entry_activate_cb)
+ self.vbox.pack_start(self._entry)
+ self.vbox.set_spacing(6)
+ self.vbox.show_all()
+
+ self._update_response_sensitivity()
+ self._entry.grab_focus()
+
+ def _entry_activate_cb(self, entry):
+ self.response(gtk.RESPONSE_OK)
+
+ def create_security(self):
+ raise NotImplementedError
+
+ def get_response_object(self):
+ return self._response
+
+WEP_PASSPHRASE = 1
+WEP_HEX = 2
+WEP_ASCII = 3
+
+class WEPKeyDialog(KeyDialog):
+ def __init__(self, ssid, flags, wpa_flags, rsn_flags, dev_caps, settings,
+ response):
+ KeyDialog.__init__(self, ssid, flags, wpa_flags, rsn_flags,
+ dev_caps, settings, response)
+
+ # WEP key type
+ self.key_store = gtk.ListStore(str, int)
+ self.key_store.append(["Passphrase (128-bit)", WEP_PASSPHRASE])
+ self.key_store.append(["Hex (40/128-bit)", WEP_HEX])
+ self.key_store.append(["ASCII (40/128-bit)", WEP_ASCII])
+
+ self.key_combo = gtk.ComboBox(self.key_store)
+ cell = gtk.CellRendererText()
+ self.key_combo.pack_start(cell, True)
+ self.key_combo.add_attribute(cell, 'text', 0)
+ self.key_combo.set_active(0)
+ self.key_combo.connect('changed', self._key_combo_changed_cb)
+
+ hbox = gtk.HBox()
+ hbox.pack_start(gtk.Label(_("Key Type:")))
+ hbox.pack_start(self.key_combo)
+ hbox.show_all()
+ self.vbox.pack_start(hbox)
+
+ # Key entry field
+ self.add_key_entry()
+
+ # WEP authentication mode
+ self.auth_store = gtk.ListStore(str, str)
+ self.auth_store.append(["Open System", IW_AUTH_ALG_OPEN_SYSTEM])
+ self.auth_store.append(["Shared Key", IW_AUTH_ALG_SHARED_KEY])
+
+ self.auth_combo = gtk.ComboBox(self.auth_store)
+ cell = gtk.CellRendererText()
+ self.auth_combo.pack_start(cell, True)
+ self.auth_combo.add_attribute(cell, 'text', 0)
+ self.auth_combo.set_active(0)
+
+ hbox = gtk.HBox()
+ hbox.pack_start(gtk.Label(_("Authentication Type:")))
+ hbox.pack_start(self.auth_combo)
+ hbox.show_all()
+
+ self.vbox.pack_start(hbox)
+
+ def _key_combo_changed_cb(self, widget):
+ self._update_response_sensitivity()
+
+ def _get_security(self):
+ key = self._entry.get_text()
+
+ it = self.key_combo.get_active_iter()
+ (key_type, ) = self.key_store.get(it, 1)
+
+ if key_type == WEP_PASSPHRASE:
+ key = hash_passphrase(key)
+ elif key_type == WEP_ASCII:
+ key = string_to_hex(key)
+
+ it = self.auth_combo.get_active_iter()
+ (auth_alg, ) = self.auth_store.get(it, 1)
+
+ return (key, auth_alg)
+
+ def print_security(self):
+ (key, auth_alg) = self._get_security()
+ print "Key: %s" % key
+ print "Auth: %d" % auth_alg
+
+ def create_security(self):
+ (key, auth_alg) = self._get_security()
+ secrets = Secrets(self._settings)
+ secrets.wep_key = key
+ secrets.auth_alg = auth_alg
+ return secrets
+
+ def _update_response_sensitivity(self, ignored=None):
+ key = self._entry.get_text()
+ it = self.key_combo.get_active_iter()
+ (key_type, ) = self.key_store.get(it, 1)
+
+ valid = False
+ if key_type == WEP_PASSPHRASE:
+ # As the md5 passphrase can be of any length and has no indicator,
+ # we cannot check for the validity of the input.
+ if len(key) > 0:
+ valid = True
+ elif key_type == WEP_ASCII:
+ if len(key) == 5 or len(key) == 13:
+ valid = string_is_ascii(key)
+ elif key_type == WEP_HEX:
+ if len(key) == 10 or len(key) == 26:
+ valid = string_is_hex(key)
+
+ self.set_response_sensitive(gtk.RESPONSE_OK, valid)
+
+class WPAKeyDialog(KeyDialog):
+ def __init__(self, ssid, flags, wpa_flags, rsn_flags, dev_caps, settings,
+ response):
+ KeyDialog.__init__(self, ssid, flags, wpa_flags, rsn_flags,
+ dev_caps, settings, response)
+ self.add_key_entry()
+
+ self.store = gtk.ListStore(str)
+ self.store.append([_("WPA & WPA2 Personal")])
+
+ self.combo = gtk.ComboBox(self.store)
+ cell = gtk.CellRendererText()
+ self.combo.pack_start(cell, True)
+ self.combo.add_attribute(cell, 'text', 0)
+ self.combo.set_active(0)
+
+ self.hbox = gtk.HBox()
+ self.hbox.pack_start(gtk.Label(_("Wireless Security:")))
+ self.hbox.pack_start(self.combo)
+ self.hbox.show_all()
+
+ self.vbox.pack_start(self.hbox)
+
+ def _get_security(self):
+ ssid = self._ssid
+ key = self._entry.get_text()
+ is_hex = string_is_hex(key)
+
+ real_key = None
+ if len(key) == 64 and is_hex:
+ # Hex key
+ real_key = key
+ elif len(key) >= 8 and len(key) <= 63:
+ # passphrase
+ from subprocess import Popen, PIPE
+ p = Popen(['wpa_passphrase', ssid, key], stdout=PIPE)
+ for line in p.stdout:
+ if line.strip().startswith("psk="):
+ real_key = line.strip()[4:]
+ if p.wait() != 0:
+ raise RuntimeError("Error hashing passphrase")
+ if real_key and len(real_key) != 64:
+ real_key = None
+
+ if not real_key:
+ raise RuntimeError("Invalid key")
+
+ return real_key
+
+ def print_security(self):
+ key = self._get_security()
+ print "Key: %s" % key
+
+ def create_security(self):
+ secrets = Secrets(self._settings)
+ secrets.psk = self._get_security()
+ return secrets
+
+ def _update_response_sensitivity(self, ignored=None):
+ key = self._entry.get_text()
+ is_hex = string_is_hex(key)
+
+ valid = False
+ if len(key) == 64 and is_hex:
+ # hex key
+ valid = True
+ elif len(key) >= 8 and len(key) <= 63:
+ # passphrase
+ valid = True
+ self.set_response_sensitive(gtk.RESPONSE_OK, valid)
+ return False
+
+def create(ssid, flags, wpa_flags, rsn_flags, dev_caps, settings, response):
+ if wpa_flags == network.NM_802_11_AP_SEC_NONE and \
+ rsn_flags == network.NM_802_11_AP_SEC_NONE:
+ key_dialog = WEPKeyDialog(ssid, flags, wpa_flags, rsn_flags,
+ dev_caps, settings, response)
+ else:
+ key_dialog = WPAKeyDialog(ssid, flags, wpa_flags, rsn_flags,
+ dev_caps, settings, response)
+
+ key_dialog.connect("response", _key_dialog_response_cb)
+ key_dialog.connect("destroy", _key_dialog_destroy_cb)
+ key_dialog.show_all()
+
+def _key_dialog_destroy_cb(key_dialog, data=None):
+ _key_dialog_response_cb(key_dialog, gtk.RESPONSE_CANCEL)
+
+def _key_dialog_response_cb(key_dialog, response_id):
+ response = key_dialog.get_response_object()
+ secrets = None
+ if response_id == gtk.RESPONSE_OK:
+ secrets = key_dialog.create_security()
+
+ if response_id in [gtk.RESPONSE_CANCEL, gtk.RESPONSE_NONE]:
+ # key dialog dialog was canceled; send the error back to NM
+ response.set_error(CanceledKeyRequestError())
+ elif response_id == gtk.RESPONSE_OK:
+ if not secrets:
+ raise RuntimeError("Invalid security arguments.")
+ response.set_secrets(secrets)
+ else:
+ raise RuntimeError("Unhandled key dialog response %d" % response_id)
+
+ key_dialog.destroy()
+
diff --git a/shell/src/jarabe/desktop/meshbox.py b/shell/src/jarabe/desktop/meshbox.py
new file mode 100644
index 0000000..cf72053
--- /dev/null
+++ b/shell/src/jarabe/desktop/meshbox.py
@@ -0,0 +1,670 @@
+# Copyright (C) 2006-2007 Red Hat, Inc.
+# Copyright (C) 2009 Tomeu Vizoso, Simon Schampijer
+# Copyright (C) 2009-2010 One Laptop per Child
+# Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from gettext import gettext as _
+import logging
+
+import dbus
+import hippo
+import glib
+import gobject
+import gtk
+import gconf
+
+from sugar.graphics.icon import CanvasIcon, Icon
+from sugar.graphics import style
+from sugar.graphics import palette
+from sugar.graphics import iconentry
+from sugar.graphics.menuitem import MenuItem
+
+from jarabe.model import neighborhood
+from jarabe.model.buddy import get_owner_instance
+from jarabe.view.buddyicon import BuddyIcon
+from jarabe.desktop.snowflakelayout import SnowflakeLayout
+from jarabe.desktop.spreadlayout import SpreadLayout
+from jarabe.desktop.networkviews import WirelessNetworkView
+from jarabe.desktop.networkviews import OlpcMeshView
+from jarabe.desktop.networkviews import SugarAdhocView
+from jarabe.model import network
+from jarabe.model.network import AccessPoint
+from jarabe.model.olpcmesh import OlpcMeshManager
+from jarabe.model.adhoc import get_adhoc_manager_instance
+from jarabe.journal import misc
+
+_NM_SERVICE = 'org.freedesktop.NetworkManager'
+_NM_IFACE = 'org.freedesktop.NetworkManager'
+_NM_PATH = '/org/freedesktop/NetworkManager'
+_NM_DEVICE_IFACE = 'org.freedesktop.NetworkManager.Device'
+_NM_WIRELESS_IFACE = 'org.freedesktop.NetworkManager.Device.Wireless'
+_NM_OLPC_MESH_IFACE = 'org.freedesktop.NetworkManager.Device.OlpcMesh'
+_NM_ACCESSPOINT_IFACE = 'org.freedesktop.NetworkManager.AccessPoint'
+_NM_ACTIVE_CONN_IFACE = 'org.freedesktop.NetworkManager.Connection.Active'
+
+_AP_ICON_NAME = 'network-wireless'
+_OLPC_MESH_ICON_NAME = 'network-mesh'
+
+class ActivityView(hippo.CanvasBox):
+ def __init__(self, model):
+ hippo.CanvasBox.__init__(self)
+
+ self._model = model
+ self._model.connect('current-buddy-added', self.__buddy_added_cb)
+ self._model.connect('current-buddy-removed', self.__buddy_removed_cb)
+
+ self._icons = {}
+ self._palette = None
+
+ self._layout = SnowflakeLayout()
+ self.set_layout(self._layout)
+
+ self._icon = self._create_icon()
+ self._layout.add(self._icon, center=True)
+
+ self._palette = self._create_palette()
+ self._icon.set_palette(self._palette)
+
+ for buddy in self._model.props.current_buddies:
+ self._add_buddy(buddy)
+
+ def _create_icon(self):
+ icon = CanvasIcon(file_name=self._model.bundle.get_icon(),
+ xo_color=self._model.get_color(), cache=True,
+ size=style.STANDARD_ICON_SIZE)
+ icon.connect('activated', self._clicked_cb)
+ return icon
+
+ def _create_palette(self):
+ p_text = glib.markup_escape_text(self._model.bundle.get_name())
+ p_icon = Icon(file=self._model.bundle.get_icon(),
+ xo_color=self._model.get_color())
+ p_icon.props.icon_size = gtk.ICON_SIZE_LARGE_TOOLBAR
+ p = palette.Palette(None,
+ primary_text=p_text,
+ icon=p_icon)
+
+ private = self._model.props.private
+ joined = get_owner_instance() in self._model.props.buddies
+
+ if joined:
+ item = MenuItem(_('Resume'), 'activity-start')
+ item.connect('activate', self._clicked_cb)
+ item.show()
+ p.menu.append(item)
+ elif not private:
+ item = MenuItem(_('Join'), 'activity-start')
+ item.connect('activate', self._clicked_cb)
+ item.show()
+ p.menu.append(item)
+
+ return p
+
+ def has_buddy_icon(self, key):
+ return self._icons.has_key(key)
+
+ def __buddy_added_cb(self, activity, buddy):
+ self._add_buddy(buddy)
+
+ def _add_buddy(self, buddy):
+ icon = BuddyIcon(buddy, style.STANDARD_ICON_SIZE)
+ self._icons[buddy.props.key] = icon
+ self._layout.add(icon)
+
+ def __buddy_removed_cb(self, activity, buddy):
+ icon = self._icons[buddy.props.key]
+ del self._icons[buddy.props.key]
+ icon.destroy()
+
+ def _clicked_cb(self, item):
+ bundle = self._model.get_bundle()
+ misc.launch(bundle, activity_id=self._model.activity_id,
+ color=self._model.get_color())
+
+ def set_filter(self, query):
+ text_to_check = self._model.bundle.get_name().lower() + \
+ self._model.bundle.get_bundle_id().lower()
+ if text_to_check.find(query) == -1:
+ self._icon.props.stroke_color = '#D5D5D5'
+ self._icon.props.fill_color = style.COLOR_TRANSPARENT.get_svg()
+ else:
+ self._icon.props.xo_color = self._model.get_color()
+
+ for icon in self._icons.itervalues():
+ if hasattr(icon, 'set_filter'):
+ icon.set_filter(query)
+
+_AUTOSEARCH_TIMEOUT = 1000
+
+
+class MeshToolbar(gtk.Toolbar):
+ __gtype_name__ = 'MeshToolbar'
+
+ __gsignals__ = {
+ 'query-changed': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([str]))
+ }
+
+ def __init__(self):
+ gtk.Toolbar.__init__(self)
+
+ self._query = None
+ self._autosearch_timer = None
+
+ 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('activate', self._entry_activated_cb)
+ self.search_entry.connect('changed', self._entry_changed_cb)
+ tool_item.add(self.search_entry)
+ self.search_entry.show()
+
+ self._add_separator(expand=True)
+
+ 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.GRID_CELL_SIZE,
+ style.GRID_CELL_SIZE)
+ self.insert(separator, -1)
+ separator.show()
+
+ def _entry_activated_cb(self, entry):
+ if self._autosearch_timer:
+ gobject.source_remove(self._autosearch_timer)
+ new_query = entry.props.text
+ if self._query != new_query:
+ self._query = new_query
+ self.emit('query-changed', self._query)
+
+ def _entry_changed_cb(self, entry):
+ if not entry.props.text:
+ entry.activate()
+ return
+
+ if self._autosearch_timer:
+ gobject.source_remove(self._autosearch_timer)
+ self._autosearch_timer = gobject.timeout_add(_AUTOSEARCH_TIMEOUT,
+ self._autosearch_timer_cb)
+
+ def _autosearch_timer_cb(self):
+ logging.debug('_autosearch_timer_cb')
+ self._autosearch_timer = None
+ self.search_entry.activate()
+ return False
+
+
+class DeviceObserver(gobject.GObject):
+ __gsignals__ = {
+ 'access-point-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ 'access-point-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT]))
+ }
+ def __init__(self, device):
+ gobject.GObject.__init__(self)
+ self._bus = dbus.SystemBus()
+ self.device = device
+
+ wireless = dbus.Interface(device, _NM_WIRELESS_IFACE)
+ wireless.GetAccessPoints(reply_handler=self._get_access_points_reply_cb,
+ error_handler=self._get_access_points_error_cb)
+
+ self._bus.add_signal_receiver(self.__access_point_added_cb,
+ signal_name='AccessPointAdded',
+ path=device.object_path,
+ dbus_interface=_NM_WIRELESS_IFACE)
+ self._bus.add_signal_receiver(self.__access_point_removed_cb,
+ signal_name='AccessPointRemoved',
+ path=device.object_path,
+ dbus_interface=_NM_WIRELESS_IFACE)
+
+ def _get_access_points_reply_cb(self, access_points_o):
+ for ap_o in access_points_o:
+ ap = self._bus.get_object(_NM_SERVICE, ap_o)
+ self.emit('access-point-added', ap)
+
+ def _get_access_points_error_cb(self, err):
+ logging.error('Failed to get access points: %s', err)
+
+ def __access_point_added_cb(self, access_point_o):
+ ap = self._bus.get_object(_NM_SERVICE, access_point_o)
+ self.emit('access-point-added', ap)
+
+ def __access_point_removed_cb(self, access_point_o):
+ self.emit('access-point-removed', access_point_o)
+
+ def disconnect(self):
+ self._bus.remove_signal_receiver(self.__access_point_added_cb,
+ signal_name='AccessPointAdded',
+ path=self.device.object_path,
+ dbus_interface=_NM_WIRELESS_IFACE)
+ self._bus.remove_signal_receiver(self.__access_point_removed_cb,
+ signal_name='AccessPointRemoved',
+ path=self.device.object_path,
+ dbus_interface=_NM_WIRELESS_IFACE)
+
+
+class NetworkManagerObserver(object):
+
+ _SHOW_ADHOC_GCONF_KEY = '/desktop/sugar/network/adhoc'
+
+ def __init__(self, box):
+ self._box = box
+ self._bus = None
+ self._devices = {}
+ self._netmgr = None
+ self._olpc_mesh_device_o = None
+
+ client = gconf.client_get_default()
+ self._have_adhoc_networks = client.get_bool(self._SHOW_ADHOC_GCONF_KEY)
+
+ def listen(self):
+ try:
+ self._bus = dbus.SystemBus()
+ obj = self._bus.get_object(_NM_SERVICE, _NM_PATH)
+ self._netmgr = dbus.Interface(obj, _NM_IFACE)
+ except dbus.DBusException:
+ logging.debug('%s service not available', _NM_SERVICE)
+ return
+
+ self._netmgr.GetDevices(reply_handler=self.__get_devices_reply_cb,
+ error_handler=self.__get_devices_error_cb)
+
+ self._bus.add_signal_receiver(self.__device_added_cb,
+ signal_name='DeviceAdded',
+ dbus_interface=_NM_IFACE)
+ self._bus.add_signal_receiver(self.__device_removed_cb,
+ signal_name='DeviceRemoved',
+ dbus_interface=_NM_IFACE)
+ self._bus.add_signal_receiver(self.__properties_changed_cb,
+ signal_name='PropertiesChanged',
+ dbus_interface=_NM_IFACE)
+
+ settings = network.get_settings()
+ if settings is not None:
+ settings.secrets_request.connect(self.__secrets_request_cb)
+
+ def __secrets_request_cb(self, **kwargs):
+ # FIXME It would be better to do all of this async, but I cannot think
+ # of a good way to. NM could really use some love here.
+
+ netmgr_props = dbus.Interface(
+ self._netmgr, 'org.freedesktop.DBus.Properties')
+ active_connections_o = netmgr_props.Get(_NM_IFACE, 'ActiveConnections')
+
+ for conn_o in active_connections_o:
+ obj = self._bus.get_object(_NM_IFACE, conn_o)
+ props = dbus.Interface(obj, 'org.freedesktop.DBus.Properties')
+ state = props.Get(_NM_ACTIVE_CONN_IFACE, 'State')
+ if state == network.NM_ACTIVE_CONNECTION_STATE_ACTIVATING:
+ ap_o = props.Get(_NM_ACTIVE_CONN_IFACE, 'SpecificObject')
+ found = False
+ if ap_o != '/':
+ for net in self._box.wireless_networks.values():
+ if net.find_ap(ap_o) is not None:
+ found = True
+ settings = kwargs['connection'].get_settings()
+ net.create_keydialog(settings, kwargs['response'])
+ if not found:
+ logging.error('Could not determine AP for'
+ ' specific object %s' % conn_o)
+
+ def __get_devices_reply_cb(self, devices_o):
+ for dev_o in devices_o:
+ self._check_device(dev_o)
+
+ def __get_devices_error_cb(self, err):
+ logging.error('Failed to get devices: %s', err)
+
+ def _check_device(self, device_o):
+ device = self._bus.get_object(_NM_SERVICE, device_o)
+ props = dbus.Interface(device, 'org.freedesktop.DBus.Properties')
+
+ device_type = props.Get(_NM_DEVICE_IFACE, 'DeviceType')
+ if device_type == network.DEVICE_TYPE_802_11_WIRELESS:
+ self._devices[device_o] = DeviceObserver(device)
+ self._devices[device_o].connect('access-point-added',
+ self.__ap_added_cb)
+ self._devices[device_o].connect('access-point-removed',
+ self.__ap_removed_cb)
+ if self._have_adhoc_networks:
+ self._box.add_adhoc_networks(device)
+ elif device_type == network.DEVICE_TYPE_802_11_OLPC_MESH:
+ self._olpc_mesh_device_o = device_o
+ self._box.enable_olpc_mesh(device)
+
+ def _get_device_path_error_cb(self, err):
+ logging.error('Failed to get device type: %s', err)
+
+ def __device_added_cb(self, device_o):
+ self._check_device(device_o)
+
+ def __device_removed_cb(self, device_o):
+ if device_o in self._devices:
+ observer = self._devices[device_o]
+ observer.disconnect()
+ del self._devices[device_o]
+ if self._have_adhoc_networks:
+ self._box.remove_adhoc_networks()
+ return
+
+ if self._olpc_mesh_device_o == device_o:
+ self._box.disable_olpc_mesh(device_o)
+
+ def __ap_added_cb(self, device_observer, access_point):
+ self._box.add_access_point(device_observer.device, access_point)
+
+ def __ap_removed_cb(self, device_observer, access_point_o):
+ self._box.remove_access_point(access_point_o)
+
+ def __properties_changed_cb(self, properties):
+ if 'WirelessHardwareEnabled' in properties:
+ if properties['WirelessHardwareEnabled']:
+ if not self._have_adhoc_networks:
+ self._box.remove_adhoc_networks()
+ elif properties['WirelessHardwareEnabled']:
+ for device in self._devices:
+ if self._have_adhoc_networks:
+ self._box.add_adhoc_networks(device)
+
+
+class MeshBox(gtk.VBox):
+ __gtype_name__ = 'SugarMeshBox'
+
+ def __init__(self):
+ logging.debug("STARTUP: Loading the mesh view")
+
+ gobject.GObject.__init__(self)
+
+ self.wireless_networks = {}
+ self._adhoc_manager = None
+ self._adhoc_networks = []
+
+ self._model = neighborhood.get_model()
+ self._buddies = {}
+ self._activities = {}
+ self._mesh = []
+ self._buddy_to_activity = {}
+ self._suspended = True
+ self._query = ''
+ self._owner_icon = None
+
+ self._toolbar = MeshToolbar()
+ self._toolbar.connect('query-changed', self._toolbar_query_changed_cb)
+ self.pack_start(self._toolbar, expand=False)
+ self._toolbar.show()
+
+ canvas = hippo.Canvas()
+ self.add(canvas)
+ canvas.show()
+
+ self._layout_box = hippo.CanvasBox( \
+ background_color=style.COLOR_WHITE.get_int())
+ canvas.set_root(self._layout_box)
+
+ self._layout = SpreadLayout()
+ self._layout_box.set_layout(self._layout)
+
+ for buddy_model in self._model.get_buddies():
+ self._add_buddy(buddy_model)
+
+ self._model.connect('buddy-added', self._buddy_added_cb)
+ self._model.connect('buddy-removed', self._buddy_removed_cb)
+
+ for activity_model in self._model.get_activities():
+ self._add_activity(activity_model)
+
+ self._model.connect('activity-added', self._activity_added_cb)
+ self._model.connect('activity-removed', self._activity_removed_cb)
+
+ netmgr_observer = NetworkManagerObserver(self)
+ netmgr_observer.listen()
+
+ def do_size_allocate(self, allocation):
+ width = allocation.width
+ height = allocation.height
+
+ min_w_, icon_width = self._owner_icon.get_width_request()
+ min_h_, icon_height = self._owner_icon.get_height_request(icon_width)
+ x = (width - icon_width) / 2
+ y = (height - icon_height) / 2 - style.GRID_CELL_SIZE
+ self._layout.move(self._owner_icon, x, y)
+
+ gtk.VBox.do_size_allocate(self, allocation)
+
+ def _buddy_added_cb(self, model, buddy_model):
+ self._add_buddy(buddy_model)
+
+ def _buddy_removed_cb(self, model, buddy_model):
+ self._remove_buddy(buddy_model)
+
+ def _activity_added_cb(self, model, activity_model):
+ self._add_activity(activity_model)
+
+ def _activity_removed_cb(self, model, activity_model):
+ self._remove_activity(activity_model)
+
+ def _add_buddy(self, buddy_model):
+ logging.debug('MeshBox._add_buddy %r', buddy_model.props.key)
+ buddy_model.connect('notify::current-activity',
+ self.__buddy_notify_current_activity_cb)
+ if buddy_model.props.current_activity is not None:
+ return
+ icon = BuddyIcon(buddy_model)
+ if buddy_model.is_owner():
+ self._owner_icon = icon
+ self._layout.add(icon)
+
+ if hasattr(icon, 'set_filter'):
+ icon.set_filter(self._query)
+
+ self._buddies[buddy_model.props.key] = icon
+
+ def _remove_buddy(self, buddy_model):
+ logging.debug('MeshBox._remove_buddy')
+ icon = self._buddies[buddy_model.props.key]
+ self._layout.remove(icon)
+ del self._buddies[buddy_model.props.key]
+ icon.destroy()
+
+ def __buddy_notify_current_activity_cb(self, buddy_model, pspec):
+ logging.debug('MeshBox.__buddy_notify_current_activity_cb %s',
+ buddy_model.props.current_activity)
+ if buddy_model.props.current_activity is None:
+ if not buddy_model.props.key in self._buddies:
+ self._add_buddy(buddy_model)
+ elif buddy_model.props.key in self._buddies:
+ self._remove_buddy(buddy_model)
+
+ def _add_activity(self, activity_model):
+ icon = ActivityView(activity_model)
+ self._layout.add(icon)
+
+ if hasattr(icon, 'set_filter'):
+ icon.set_filter(self._query)
+
+ self._activities[activity_model.activity_id] = icon
+
+ def _remove_activity(self, activity_model):
+ icon = self._activities[activity_model.activity_id]
+ self._layout.remove(icon)
+ del self._activities[activity_model.activity_id]
+ icon.destroy()
+
+ # add AP to its corresponding network icon on the desktop,
+ # creating one if it doesn't already exist
+ def _add_ap_to_network(self, ap):
+ hash = ap.network_hash()
+ if hash in self.wireless_networks:
+ self.wireless_networks[hash].add_ap(ap)
+ else:
+ # this is a new network
+ icon = WirelessNetworkView(ap)
+ self.wireless_networks[hash] = icon
+ self._layout.add(icon)
+ if hasattr(icon, 'set_filter'):
+ icon.set_filter(self._query)
+
+ def _remove_net_if_empty(self, net, hash):
+ # remove a network if it has no APs left
+ if net.num_aps() == 0:
+ net.disconnect()
+ self._layout.remove(net)
+ del self.wireless_networks[hash]
+
+ def _ap_props_changed_cb(self, ap, old_hash):
+ # if we have mesh hardware, ignore OLPC mesh networks that appear as
+ # normal wifi networks
+ if len(self._mesh) > 0 and ap.mode == network.NM_802_11_MODE_ADHOC \
+ and ap.name == "olpc-mesh":
+ logging.debug("ignoring OLPC mesh IBSS")
+ ap.disconnect()
+ return
+
+ if self._adhoc_manager is not None and \
+ network.is_sugar_adhoc_network(ap.name) and \
+ ap.mode == network.NM_802_11_MODE_ADHOC:
+ if old_hash is None: # new Ad-hoc network finished initializing
+ self._adhoc_manager.add_access_point(ap)
+ # we are called as well in other cases but we do not need to
+ # act here as we don't display signal strength for Ad-hoc networks
+ return
+
+ if old_hash is None: # new AP finished initializing
+ self._add_ap_to_network(ap)
+ return
+
+ hash = ap.network_hash()
+ if old_hash == hash:
+ # no change in network identity, so just update signal strengths
+ self.wireless_networks[hash].update_strength()
+ return
+
+ # properties change includes a change of the identity of the network
+ # that it is on. so create this as a new network.
+ self.wireless_networks[old_hash].remove_ap(ap)
+ self._remove_net_if_empty(self.wireless_networks[old_hash], old_hash)
+ self._add_ap_to_network(ap)
+
+ def add_access_point(self, device, ap_o):
+ ap = AccessPoint(device, ap_o)
+ ap.connect('props-changed', self._ap_props_changed_cb)
+ ap.initialize()
+
+ def remove_access_point(self, ap_o):
+ if self._adhoc_manager is not None:
+ if self._adhoc_manager.is_sugar_adhoc_access_point(ap_o):
+ self._adhoc_manager.remove_access_point(ap_o)
+ return
+
+ # we don't keep an index of ap object path to network, but since
+ # we'll only ever have a handful of networks, just try them all...
+ for net in self.wireless_networks.values():
+ ap = net.find_ap(ap_o)
+ if not ap:
+ continue
+
+ ap.disconnect()
+ net.remove_ap(ap)
+ self._remove_net_if_empty(net, ap.network_hash())
+ return
+
+ # it's not an error if the AP isn't found, since we might have ignored
+ # it (e.g. olpc-mesh adhoc network)
+ logging.debug('Can not remove access point %s' % ap_o)
+
+ def add_adhoc_networks(self, device):
+ if self._adhoc_manager is None:
+ self._adhoc_manager = get_adhoc_manager_instance()
+ self._adhoc_manager.start_listening(device)
+ self._add_adhoc_network_icon(1)
+ self._add_adhoc_network_icon(6)
+ self._add_adhoc_network_icon(11)
+ self._adhoc_manager.autoconnect()
+
+ def remove_adhoc_networks(self):
+ for icon in self._adhoc_networks:
+ self._layout.remove(icon)
+ self._adhoc_networks = []
+
+ def _add_adhoc_network_icon(self, channel):
+ icon = SugarAdhocView(channel)
+ self._layout.add(icon)
+ self._adhoc_networks.append(icon)
+
+ def _add_olpc_mesh_icon(self, mesh_mgr, channel):
+ icon = OlpcMeshView(mesh_mgr, channel)
+ self._layout.add(icon)
+ self._mesh.append(icon)
+
+ def enable_olpc_mesh(self, mesh_device):
+ mesh_mgr = OlpcMeshManager(mesh_device)
+ self._add_olpc_mesh_icon(mesh_mgr, 1)
+ self._add_olpc_mesh_icon(mesh_mgr, 6)
+ self._add_olpc_mesh_icon(mesh_mgr, 11)
+
+ # the OLPC mesh can be recognised as a "normal" wifi network. remove
+ # any such normal networks if they have been created
+ for hash, net in self.wireless_networks.iteritems():
+ if not net.is_olpc_mesh():
+ continue
+
+ logging.debug("removing OLPC mesh IBSS")
+ net.remove_all_aps()
+ net.disconnect()
+ self._layout.remove(net)
+ del self.wireless_networks[hash]
+
+ def disable_olpc_mesh(self, mesh_device):
+ for icon in self._mesh:
+ icon.disconnect()
+ self._layout.remove(icon)
+ self._mesh = []
+
+ def suspend(self):
+ if not self._suspended:
+ self._suspended = True
+ for net in self.wireless_networks.values() + self._mesh:
+ net.props.paused = True
+
+ def resume(self):
+ if self._suspended:
+ self._suspended = False
+ for net in self.wireless_networks.values() + self._mesh:
+ net.props.paused = False
+
+ def _toolbar_query_changed_cb(self, toolbar, query):
+ self._query = query.lower()
+ for icon in self._layout_box.get_children():
+ if hasattr(icon, 'set_filter'):
+ icon.set_filter(self._query)
+
+ def focus_search_entry(self):
+ self._toolbar.search_entry.grab_focus()
diff --git a/shell/src/jarabe/desktop/networkviews.py b/shell/src/jarabe/desktop/networkviews.py
new file mode 100644
index 0000000..121c817
--- /dev/null
+++ b/shell/src/jarabe/desktop/networkviews.py
@@ -0,0 +1,716 @@
+# Copyright (C) 2006-2007 Red Hat, Inc.
+# Copyright (C) 2009 Tomeu Vizoso, Simon Schampijer
+# Copyright (C) 2009-2010 One Laptop per Child
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from gettext import gettext as _
+import logging
+import hashlib
+
+import dbus
+import glib
+
+from sugar.graphics.icon import Icon
+from sugar.graphics.xocolor import XoColor
+from sugar.graphics import xocolor
+from sugar.graphics import style
+from sugar.graphics.icon import get_icon_state
+from sugar.graphics import palette
+from sugar.graphics.menuitem import MenuItem
+from sugar.util import unique_id
+from sugar import profile
+
+from jarabe.view.pulsingicon import CanvasPulsingIcon
+from jarabe.desktop import keydialog
+from jarabe.model import network
+from jarabe.model.network import Settings
+from jarabe.model.network import IP4Config
+from jarabe.model.network import WirelessSecurity
+from jarabe.model.adhoc import get_adhoc_manager_instance
+
+_NM_SERVICE = 'org.freedesktop.NetworkManager'
+_NM_IFACE = 'org.freedesktop.NetworkManager'
+_NM_PATH = '/org/freedesktop/NetworkManager'
+_NM_DEVICE_IFACE = 'org.freedesktop.NetworkManager.Device'
+_NM_WIRELESS_IFACE = 'org.freedesktop.NetworkManager.Device.Wireless'
+_NM_OLPC_MESH_IFACE = 'org.freedesktop.NetworkManager.Device.OlpcMesh'
+_NM_ACCESSPOINT_IFACE = 'org.freedesktop.NetworkManager.AccessPoint'
+_NM_ACTIVE_CONN_IFACE = 'org.freedesktop.NetworkManager.Connection.Active'
+
+_AP_ICON_NAME = 'network-wireless'
+_OLPC_MESH_ICON_NAME = 'network-mesh'
+
+
+class WirelessNetworkView(CanvasPulsingIcon):
+ def __init__(self, initial_ap):
+ CanvasPulsingIcon.__init__(self, size=style.STANDARD_ICON_SIZE,
+ cache=True)
+ self._bus = dbus.SystemBus()
+ self._access_points = {initial_ap.model.object_path: initial_ap}
+ self._active_ap = None
+ self._device = initial_ap.device
+ self._palette_icon = None
+ self._disconnect_item = None
+ self._connect_item = None
+ self._greyed_out = False
+ self._name = initial_ap.name
+ self._mode = initial_ap.mode
+ self._strength = initial_ap.strength
+ self._flags = initial_ap.flags
+ self._wpa_flags = initial_ap.wpa_flags
+ self._rsn_flags = initial_ap.rsn_flags
+ self._device_caps = 0
+ self._device_state = None
+ self._connection = None
+ self._color = None
+
+ if self._mode == network.NM_802_11_MODE_ADHOC and \
+ network.is_sugar_adhoc_network(self._name):
+ self._color = profile.get_color()
+ else:
+ sha_hash = hashlib.sha1()
+ data = self._name + hex(self._flags)
+ sha_hash.update(data)
+ digest = hash(sha_hash.digest())
+ index = digest % len(xocolor.colors)
+
+ self._color = xocolor.XoColor('%s,%s' %
+ (xocolor.colors[index][0],
+ xocolor.colors[index][1]))
+
+ self.connect('button-release-event', self.__button_release_event_cb)
+
+ pulse_color = XoColor('%s,%s' % (style.COLOR_BUTTON_GREY.get_svg(),
+ style.COLOR_TRANSPARENT.get_svg()))
+ self.props.pulse_color = pulse_color
+
+ self._palette = self._create_palette()
+ self.set_palette(self._palette)
+ self._palette_icon.props.xo_color = self._color
+
+ if self._mode != network.NM_802_11_MODE_ADHOC:
+ if network.find_connection_by_ssid(self._name) is not None:
+ self.props.badge_name = "emblem-favorite"
+ self._palette_icon.props.badge_name = "emblem-favorite"
+ elif self._flags == network.NM_802_11_AP_FLAGS_PRIVACY:
+ self.props.badge_name = "emblem-locked"
+ self._palette_icon.props.badge_name = "emblem-locked"
+ else:
+ self.props.badge_name = None
+ self._palette_icon.props.badge_name = None
+ else:
+ self.props.badge_name = None
+ self._palette_icon.props.badge_name = None
+
+ interface_props = dbus.Interface(self._device,
+ 'org.freedesktop.DBus.Properties')
+ interface_props.Get(_NM_DEVICE_IFACE, 'State',
+ reply_handler=self.__get_device_state_reply_cb,
+ error_handler=self.__get_device_state_error_cb)
+ interface_props.Get(_NM_WIRELESS_IFACE, 'WirelessCapabilities',
+ reply_handler=self.__get_device_caps_reply_cb,
+ error_handler=self.__get_device_caps_error_cb)
+ interface_props.Get(_NM_WIRELESS_IFACE, 'ActiveAccessPoint',
+ reply_handler=self.__get_active_ap_reply_cb,
+ error_handler=self.__get_active_ap_error_cb)
+
+ self._bus.add_signal_receiver(self.__device_state_changed_cb,
+ signal_name='StateChanged',
+ path=self._device.object_path,
+ dbus_interface=_NM_DEVICE_IFACE)
+ self._bus.add_signal_receiver(self.__wireless_properties_changed_cb,
+ signal_name='PropertiesChanged',
+ path=self._device.object_path,
+ dbus_interface=_NM_WIRELESS_IFACE)
+
+ def _create_palette(self):
+ icon_name = get_icon_state(_AP_ICON_NAME, self._strength)
+ self._palette_icon = Icon(icon_name=icon_name,
+ icon_size=style.STANDARD_ICON_SIZE,
+ badge_name=self.props.badge_name)
+
+ p = palette.Palette(primary_text=glib.markup_escape_text(self._name),
+ icon=self._palette_icon)
+
+ self._connect_item = MenuItem(_('Connect'), 'dialog-ok')
+ self._connect_item.connect('activate', self.__connect_activate_cb)
+ p.menu.append(self._connect_item)
+
+ self._disconnect_item = MenuItem(_('Disconnect'), 'media-eject')
+ self._disconnect_item.connect('activate',
+ self._disconnect_activate_cb)
+ p.menu.append(self._disconnect_item)
+
+ return p
+
+ def __device_state_changed_cb(self, new_state, old_state, reason):
+ self._device_state = new_state
+ self._update_state()
+
+ def __update_active_ap(self, ap_path):
+ if ap_path in self._access_points:
+ # save reference to active AP, so that we always display the
+ # strength of that one
+ self._active_ap = self._access_points[ap_path]
+ self.update_strength()
+ self._update_state()
+ elif self._active_ap is not None:
+ # revert to showing state of strongest AP again
+ self._active_ap = None
+ self.update_strength()
+ self._update_state()
+
+ def __wireless_properties_changed_cb(self, properties):
+ if 'ActiveAccessPoint' in properties:
+ self.__update_active_ap(properties['ActiveAccessPoint'])
+
+ def __get_active_ap_reply_cb(self, ap_path):
+ self.__update_active_ap(ap_path)
+
+ def __get_active_ap_error_cb(self, err):
+ logging.error('Error getting the active access point: %s', err)
+
+ def __get_device_caps_reply_cb(self, caps):
+ self._device_caps = caps
+
+ def __get_device_caps_error_cb(self, err):
+ logging.error('Error getting the wireless device properties: %s', err)
+
+ def __get_device_state_reply_cb(self, state):
+ self._device_state = state
+ self._update()
+
+ def __get_device_state_error_cb(self, err):
+ logging.error('Error getting the device state: %s', err)
+
+ def _update(self):
+ self._update_state()
+ self._update_color()
+
+ def _update_state(self):
+ if self._active_ap is not None:
+ state = self._device_state
+ else:
+ state = network.DEVICE_STATE_UNKNOWN
+
+ if self._mode == network.NM_802_11_MODE_ADHOC and \
+ network.is_sugar_adhoc_network(self._name):
+ channel = max([1] + [ap.channel for ap in
+ self._access_points.values()])
+ if state == network.DEVICE_STATE_ACTIVATED:
+ icon_name = 'network-adhoc-%s-connected' % channel
+ else:
+ icon_name = 'network-adhoc-%s' % channel
+ self.props.icon_name = icon_name
+ icon = self._palette.props.icon
+ icon.props.icon_name = icon_name
+ else:
+ if state == network.DEVICE_STATE_ACTIVATED:
+ connection = network.find_connection_by_ssid(self._name)
+ if connection is not None:
+ if self._mode == network.NM_802_11_MODE_INFRA:
+ connection.set_connected()
+ icon_name = '%s-connected' % _AP_ICON_NAME
+ else:
+ icon_name = _AP_ICON_NAME
+
+ icon_name = get_icon_state(icon_name, self._strength)
+ if icon_name:
+ self.props.icon_name = icon_name
+ icon = self._palette.props.icon
+ icon.props.icon_name = icon_name
+
+ if state == network.DEVICE_STATE_PREPARE or \
+ state == network.DEVICE_STATE_CONFIG or \
+ state == network.DEVICE_STATE_NEED_AUTH or \
+ state == network.DEVICE_STATE_IP_CONFIG:
+ if self._disconnect_item:
+ self._disconnect_item.show()
+ self._connect_item.hide()
+ self._palette.props.secondary_text = _('Connecting...')
+ self.props.pulsing = True
+ elif state == network.DEVICE_STATE_ACTIVATED:
+ if self._disconnect_item:
+ self._disconnect_item.show()
+ self._connect_item.hide()
+ self._palette.props.secondary_text = _('Connected')
+ self.props.pulsing = False
+ else:
+ if self._disconnect_item:
+ self._disconnect_item.hide()
+ self._connect_item.show()
+ self._palette.props.secondary_text = None
+ self.props.pulsing = False
+
+ def _update_color(self):
+ if self._greyed_out:
+ self.props.pulsing = False
+ self.props.base_color = XoColor('#D5D5D5,#D5D5D5')
+ else:
+ self.props.base_color = self._color
+
+ def _disconnect_activate_cb(self, item):
+ pass
+
+ def _add_ciphers_from_flags(self, flags, pairwise):
+ ciphers = []
+ if pairwise:
+ if flags & network.NM_802_11_AP_SEC_PAIR_TKIP:
+ ciphers.append("tkip")
+ if flags & network.NM_802_11_AP_SEC_PAIR_CCMP:
+ ciphers.append("ccmp")
+ else:
+ if flags & network.NM_802_11_AP_SEC_GROUP_WEP40:
+ ciphers.append("wep40")
+ if flags & network.NM_802_11_AP_SEC_GROUP_WEP104:
+ ciphers.append("wep104")
+ if flags & network.NM_802_11_AP_SEC_GROUP_TKIP:
+ ciphers.append("tkip")
+ if flags & network.NM_802_11_AP_SEC_GROUP_CCMP:
+ ciphers.append("ccmp")
+ return ciphers
+
+ def _get_security(self):
+ if not (self._flags & network.NM_802_11_AP_FLAGS_PRIVACY) and \
+ (self._wpa_flags == network.NM_802_11_AP_SEC_NONE) and \
+ (self._rsn_flags == network.NM_802_11_AP_SEC_NONE):
+ # No security
+ return None
+
+ if (self._flags & network.NM_802_11_AP_FLAGS_PRIVACY) and \
+ (self._wpa_flags == network.NM_802_11_AP_SEC_NONE) and \
+ (self._rsn_flags == network.NM_802_11_AP_SEC_NONE):
+ # Static WEP, Dynamic WEP, or LEAP
+ wireless_security = WirelessSecurity()
+ wireless_security.key_mgmt = 'none'
+ return wireless_security
+
+ if (self._mode != network.NM_802_11_MODE_INFRA):
+ # Stuff after this point requires infrastructure
+ logging.error('The infrastructure mode is not supoorted'
+ ' by your wireless device.')
+ return None
+
+ if (self._rsn_flags & network.NM_802_11_AP_SEC_KEY_MGMT_PSK) and \
+ (self._device_caps & network.NM_802_11_DEVICE_CAP_RSN):
+ # WPA2 PSK first
+ pairwise = self._add_ciphers_from_flags(self._rsn_flags, True)
+ group = self._add_ciphers_from_flags(self._rsn_flags, False)
+ wireless_security = WirelessSecurity()
+ wireless_security.key_mgmt = 'wpa-psk'
+ wireless_security.proto = 'rsn'
+ wireless_security.pairwise = pairwise
+ wireless_security.group = group
+ return wireless_security
+
+ if (self._wpa_flags & network.NM_802_11_AP_SEC_KEY_MGMT_PSK) and \
+ (self._device_caps & network.NM_802_11_DEVICE_CAP_WPA):
+ # WPA PSK
+ pairwise = self._add_ciphers_from_flags(self._wpa_flags, True)
+ group = self._add_ciphers_from_flags(self._wpa_flags, False)
+ wireless_security = WirelessSecurity()
+ wireless_security.key_mgmt = 'wpa-psk'
+ wireless_security.proto = 'wpa'
+ wireless_security.pairwise = pairwise
+ wireless_security.group = group
+ return wireless_security
+
+ def __connect_activate_cb(self, icon):
+ self._connect()
+
+ def __button_release_event_cb(self, icon, event):
+ self._connect()
+
+ def _connect(self):
+ connection = network.find_connection_by_ssid(self._name)
+ if connection is None:
+ settings = Settings()
+ settings.connection.id = 'Auto ' + self._name
+ uuid = settings.connection.uuid = unique_id()
+ settings.connection.type = '802-11-wireless'
+ settings.wireless.ssid = self._name
+
+ if self._mode == network.NM_802_11_MODE_INFRA:
+ settings.wireless.mode = 'infrastructure'
+ elif self._mode == network.NM_802_11_MODE_ADHOC:
+ settings.wireless.mode = 'adhoc'
+ settings.wireless.band = 'bg'
+ settings.ip4_config = IP4Config()
+ settings.ip4_config.method = 'link-local'
+
+ wireless_security = self._get_security()
+ settings.wireless_security = wireless_security
+
+ if wireless_security is not None:
+ settings.wireless.security = '802-11-wireless-security'
+
+ connection = network.add_connection(uuid, settings)
+
+ obj = self._bus.get_object(_NM_SERVICE, _NM_PATH)
+ netmgr = dbus.Interface(obj, _NM_IFACE)
+
+ netmgr.ActivateConnection(network.SETTINGS_SERVICE, connection.path,
+ self._device.object_path,
+ "/",
+ reply_handler=self.__activate_reply_cb,
+ error_handler=self.__activate_error_cb)
+
+ def __activate_reply_cb(self, connection):
+ logging.debug('Connection activated: %s', connection)
+
+ def __activate_error_cb(self, err):
+ logging.error('Failed to activate connection: %s', err)
+
+ def set_filter(self, query):
+ self._greyed_out = self._name.lower().find(query) == -1
+ self._update_state()
+ self._update_color()
+
+ def create_keydialog(self, settings, response):
+ keydialog.create(self._name, self._flags, self._wpa_flags,
+ self._rsn_flags, self._device_caps, settings, response)
+
+ def update_strength(self):
+ if self._active_ap is not None:
+ # display strength of AP that we are connected to
+ new_strength = self._active_ap.strength
+ else:
+ # display the strength of the strongest AP that makes up this
+ # network, also considering that there may be no APs
+ new_strength = max([0] + [ap.strength for ap in
+ self._access_points.values()])
+
+ if new_strength != self._strength:
+ self._strength = new_strength
+ self._update_state()
+
+ def add_ap(self, ap):
+ self._access_points[ap.model.object_path] = ap
+ self.update_strength()
+
+ def remove_ap(self, ap):
+ path = ap.model.object_path
+ if path not in self._access_points:
+ return
+ del self._access_points[path]
+ if self._active_ap == ap:
+ self._active_ap = None
+ self.update_strength()
+
+ def num_aps(self):
+ return len(self._access_points)
+
+ def find_ap(self, ap_path):
+ if ap_path not in self._access_points:
+ return None
+ return self._access_points[ap_path]
+
+ def is_olpc_mesh(self):
+ return self._mode == network.NM_802_11_MODE_ADHOC \
+ and self.name == "olpc-mesh"
+
+ def remove_all_aps(self):
+ for ap in self._access_points.values():
+ ap.disconnect()
+ self._access_points = {}
+ self._active_ap = None
+ self.update_strength()
+
+ def disconnect(self):
+ self._bus.remove_signal_receiver(self.__device_state_changed_cb,
+ signal_name='StateChanged',
+ path=self._device.object_path,
+ dbus_interface=_NM_DEVICE_IFACE)
+ self._bus.remove_signal_receiver(self.__wireless_properties_changed_cb,
+ signal_name='PropertiesChanged',
+ path=self._device.object_path,
+ dbus_interface=_NM_WIRELESS_IFACE)
+
+class SugarAdhocView(CanvasPulsingIcon):
+ """To mimic the mesh behavior on devices where mesh hardware is
+ not available we support the creation of an Ad-hoc network on
+ three channels 1, 6, 11. This is the class for an icon
+ representing a channel in the neighborhood view.
+
+ """
+
+ _ICON_NAME = 'network-adhoc-'
+ _NAME = 'Ad-hoc Network '
+
+ def __init__(self, channel):
+ CanvasPulsingIcon.__init__(self,
+ icon_name=self._ICON_NAME + str(channel),
+ size=style.STANDARD_ICON_SIZE, cache=True)
+ self._bus = dbus.SystemBus()
+ self._channel = channel
+ self._disconnect_item = None
+ self._connect_item = None
+ self._palette_icon = None
+ self._greyed_out = False
+
+ get_adhoc_manager_instance().connect('members-changed',
+ self.__members_changed_cb)
+ get_adhoc_manager_instance().connect('state-changed',
+ self.__state_changed_cb)
+
+ self.connect('button-release-event', self.__button_release_event_cb)
+
+ pulse_color = XoColor('%s,%s' % (style.COLOR_BUTTON_GREY.get_svg(),
+ style.COLOR_TRANSPARENT.get_svg()))
+ self.props.pulse_color = pulse_color
+ self._state_color = XoColor('%s,%s' % \
+ (profile.get_color().get_stroke_color(),
+ style.COLOR_TRANSPARENT.get_svg()))
+ self.props.base_color = self._state_color
+ self._palette = self._create_palette()
+ self.set_palette(self._palette)
+ self._palette_icon.props.xo_color = self._state_color
+
+ def _create_palette(self):
+ self._palette_icon = Icon( \
+ icon_name=self._ICON_NAME + str(self._channel),
+ icon_size=style.STANDARD_ICON_SIZE)
+
+ palette_ = palette.Palette(_("Ad-hoc Network %d") % self._channel,
+ icon=self._palette_icon)
+
+ self._connect_item = MenuItem(_('Connect'), 'dialog-ok')
+ self._connect_item.connect('activate', self.__connect_activate_cb)
+ palette_.menu.append(self._connect_item)
+
+ self._disconnect_item = MenuItem(_('Disconnect'), 'media-eject')
+ self._disconnect_item.connect('activate',
+ self.__disconnect_activate_cb)
+ palette_.menu.append(self._disconnect_item)
+
+ return palette_
+
+ def __button_release_event_cb(self, icon, event):
+ get_adhoc_manager_instance().activate_channel(self._channel)
+
+ def __connect_activate_cb(self, icon):
+ get_adhoc_manager_instance().activate_channel(self._channel)
+
+ def __disconnect_activate_cb(self, icon):
+ get_adhoc_manager_instance().deactivate_active_channel()
+
+ def __state_changed_cb(self, adhoc_manager, channel, device_state):
+ if self._channel == channel:
+ state = device_state
+ else:
+ state = network.DEVICE_STATE_UNKNOWN
+
+ if state == network.DEVICE_STATE_ACTIVATED:
+ icon_name = '%s-connected' % (self._ICON_NAME + str(self._channel))
+ else:
+ icon_name = self._ICON_NAME + str(self._channel)
+
+ self.props.base_color = self._state_color
+ self._palette_icon.props.xo_color = self._state_color
+
+ if icon_name is not None:
+ self.props.icon_name = icon_name
+ icon = self._palette.props.icon
+ icon.props.icon_name = icon_name
+
+ if state in [network.DEVICE_STATE_PREPARE,
+ network.DEVICE_STATE_CONFIG,
+ network.DEVICE_STATE_NEED_AUTH,
+ network.DEVICE_STATE_IP_CONFIG]:
+ if self._disconnect_item:
+ self._disconnect_item.show()
+ self._connect_item.hide()
+ self._palette.props.secondary_text = _('Connecting...')
+ self.props.pulsing = True
+ elif state == network.DEVICE_STATE_ACTIVATED:
+ if self._disconnect_item:
+ self._disconnect_item.show()
+ self._connect_item.hide()
+ self._palette.props.secondary_text = _('Connected')
+ self.props.pulsing = False
+ else:
+ if self._disconnect_item:
+ self._disconnect_item.hide()
+ self._connect_item.show()
+ self._palette.props.secondary_text = None
+ self.props.pulsing = False
+
+ def _update_color(self):
+ if self._greyed_out:
+ self.props.base_color = XoColor('#D5D5D5,#D5D5D5')
+ else:
+ self.props.base_color = self._state_color
+
+ def __members_changed_cb(self, adhoc_manager, channel, has_members):
+ if channel == self._channel:
+ if has_members == True:
+ self._state_color = profile.get_color()
+ self.props.base_color = self._state_color
+ self._palette_icon.props.xo_color = self._state_color
+ else:
+ color = '%s,%s' % (profile.get_color().get_stroke_color(),
+ style.COLOR_TRANSPARENT.get_svg())
+ self._state_color = XoColor(color)
+ self.props.base_color = self._state_color
+ self._palette_icon.props.xo_color = self._state_color
+
+ def set_filter(self, query):
+ name = self._NAME + str(self._channel)
+ self._greyed_out = name.lower().find(query) == -1
+ self._update_color()
+
+
+class OlpcMeshView(CanvasPulsingIcon):
+ def __init__(self, mesh_mgr, channel):
+ CanvasPulsingIcon.__init__(self, icon_name=_OLPC_MESH_ICON_NAME,
+ size=style.STANDARD_ICON_SIZE, cache=True)
+ self._bus = dbus.SystemBus()
+ self._channel = channel
+ self._mesh_mgr = mesh_mgr
+ self._disconnect_item = None
+ self._connect_item = None
+ self._greyed_out = False
+ self._name = ''
+ self._device_state = None
+ self._connection = None
+ self._active = False
+ device = mesh_mgr.mesh_device
+
+ self.connect('button-release-event', self.__button_release_event_cb)
+
+ interface_props = dbus.Interface(device,
+ 'org.freedesktop.DBus.Properties')
+ interface_props.Get(_NM_DEVICE_IFACE, 'State',
+ reply_handler=self.__get_device_state_reply_cb,
+ error_handler=self.__get_device_state_error_cb)
+ interface_props.Get(_NM_OLPC_MESH_IFACE, 'ActiveChannel',
+ reply_handler=self.__get_active_channel_reply_cb,
+ error_handler=self.__get_active_channel_error_cb)
+
+ self._bus.add_signal_receiver(self.__device_state_changed_cb,
+ signal_name='StateChanged',
+ path=device.object_path,
+ dbus_interface=_NM_DEVICE_IFACE)
+ self._bus.add_signal_receiver(self.__wireless_properties_changed_cb,
+ signal_name='PropertiesChanged',
+ path=device.object_path,
+ dbus_interface=_NM_OLPC_MESH_IFACE)
+
+ pulse_color = XoColor('%s,%s' % (style.COLOR_BUTTON_GREY.get_svg(),
+ style.COLOR_TRANSPARENT.get_svg()))
+ self.props.pulse_color = pulse_color
+ self.props.base_color = profile.get_color()
+ self._palette = self._create_palette()
+ self.set_palette(self._palette)
+
+ def _create_palette(self):
+ _palette = palette.Palette(_("Mesh Network %d") % self._channel)
+
+ self._connect_item = MenuItem(_('Connect'), 'dialog-ok')
+ self._connect_item.connect('activate', self.__connect_activate_cb)
+ _palette.menu.append(self._connect_item)
+
+ return _palette
+
+ def __get_device_state_reply_cb(self, state):
+ self._device_state = state
+ self._update()
+
+ def __get_device_state_error_cb(self, err):
+ logging.error('Error getting the device state: %s', err)
+
+ def __device_state_changed_cb(self, new_state, old_state, reason):
+ self._device_state = new_state
+ self._update()
+
+ def __get_active_channel_reply_cb(self, channel):
+ self._active = (channel == self._channel)
+ self._update()
+
+ def __get_active_channel_error_cb(self, err):
+ logging.error('Error getting the active channel: %s', err)
+
+ def __wireless_properties_changed_cb(self, properties):
+ if 'ActiveChannel' in properties:
+ channel = properties['ActiveChannel']
+ self._active = (channel == self._channel)
+ self._update()
+
+ def _update(self):
+ if self._active:
+ state = self._device_state
+ else:
+ state = network.DEVICE_STATE_UNKNOWN
+
+ if state in [network.DEVICE_STATE_PREPARE,
+ network.DEVICE_STATE_CONFIG,
+ network.DEVICE_STATE_NEED_AUTH,
+ network.DEVICE_STATE_IP_CONFIG]:
+ if self._disconnect_item:
+ self._disconnect_item.show()
+ self._connect_item.hide()
+ self._palette.props.secondary_text = _('Connecting...')
+ self.props.pulsing = True
+ elif state == network.DEVICE_STATE_ACTIVATED:
+ if self._disconnect_item:
+ self._disconnect_item.show()
+ self._connect_item.hide()
+ self._palette.props.secondary_text = _('Connected')
+ self.props.pulsing = False
+ else:
+ if self._disconnect_item:
+ self._disconnect_item.hide()
+ self._connect_item.show()
+ self._palette.props.secondary_text = None
+ self.props.pulsing = False
+
+ def _update_color(self):
+ if self._greyed_out:
+ self.props.base_color = XoColor('#D5D5D5,#D5D5D5')
+ else:
+ self.props.base_color = profile.get_color()
+
+ def __connect_activate_cb(self, icon):
+ self._connect()
+
+ def __button_release_event_cb(self, icon, event):
+ self._connect()
+
+ def _connect(self):
+ self._mesh_mgr.user_activate_channel(self._channel)
+
+ def __activate_reply_cb(self, connection):
+ logging.debug('Connection activated: %s', connection)
+
+ def __activate_error_cb(self, err):
+ logging.error('Failed to activate connection: %s', err)
+
+ def set_filter(self, query):
+ self._greyed_out = (query != '')
+ self._update_color()
+
+ def disconnect(self):
+ device_object_path = self._mesh_mgr.mesh_device.object_path
+
+ self._bus.remove_signal_receiver(self.__device_state_changed_cb,
+ signal_name='StateChanged',
+ path=device_object_path,
+ dbus_interface=_NM_DEVICE_IFACE)
+ self._bus.remove_signal_receiver(self.__wireless_properties_changed_cb,
+ signal_name='PropertiesChanged',
+ path=device_object_path,
+ dbus_interface=_NM_OLPC_MESH_IFACE)
+
diff --git a/shell/src/jarabe/desktop/schoolserver.py b/shell/src/jarabe/desktop/schoolserver.py
new file mode 100644
index 0000000..a05f56c
--- /dev/null
+++ b/shell/src/jarabe/desktop/schoolserver.py
@@ -0,0 +1,127 @@
+# 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 logging
+from gettext import gettext as _
+from xmlrpclib import ServerProxy, Error
+import socket
+import os
+from string import ascii_uppercase
+import random
+import time
+import uuid
+
+import gconf
+
+from sugar import env
+from sugar.profile import get_profile
+
+REGISTER_URL = 'http://schoolserver:8080/'
+
+def generate_serial_number():
+ """ Generates a serial number based on 3 random uppercase letters
+ and the last 8 digits of the current unix seconds. """
+
+ serial_part1 = []
+
+ for y_ in range(3) :
+ serial_part1.append(random.choice(ascii_uppercase))
+
+ serial_part1 = ''.join(serial_part1)
+ serial_part2 = str(int(time.time()))[-8:]
+ serial = serial_part1 + serial_part2
+
+ return serial
+
+def store_identifiers(serial_number, uuid, backup_url):
+ """ Stores the serial number, uuid and backup_url
+ in the identifier folder inside the profile directory
+ so that these identifiers can be used for backup. """
+
+ identifier_path = os.path.join(env.get_profile_path(), 'identifiers')
+ if not os.path.exists(identifier_path):
+ os.mkdir(identifier_path)
+
+ if os.path.exists(os.path.join(identifier_path, 'sn')):
+ os.remove(os.path.join(identifier_path, 'sn'))
+ serial_file = open(os.path.join(identifier_path, 'sn'), 'w')
+ serial_file.write(serial_number)
+ serial_file.close()
+
+ if os.path.exists(os.path.join(identifier_path, 'uuid')):
+ os.remove(os.path.join(identifier_path, 'uuid'))
+ uuid_file = open(os.path.join(identifier_path, 'uuid'), 'w')
+ uuid_file.write(uuid)
+ uuid_file.close()
+
+ if os.path.exists(os.path.join(identifier_path, 'backup_url')):
+ os.remove(os.path.join(identifier_path, 'backup_url'))
+ backup_url_file = open(os.path.join(identifier_path, 'backup_url'), 'w')
+ backup_url_file.write(backup_url)
+ backup_url_file.close()
+
+class RegisterError(Exception):
+ pass
+
+def register_laptop(url=REGISTER_URL):
+
+ profile = get_profile()
+ client = gconf.client_get_default()
+
+ if have_ofw_tree():
+ sn = read_ofw('mfg-data/SN')
+ uuid_ = read_ofw('mfg-data/U#')
+ sn = sn or 'SHF00000000'
+ uuid_ = uuid_ or '00000000-0000-0000-0000-000000000000'
+ else:
+ sn = generate_serial_number()
+ uuid_ = str(uuid.uuid1())
+ setting_name = '/desktop/sugar/collaboration/jabber_server'
+ jabber_server = client.get_string(setting_name)
+ store_identifiers(sn, uuid_, jabber_server)
+ url = 'http://' + jabber_server + ':8080/'
+
+ nick = client.get_string('/desktop/sugar/user/nick')
+
+ server = ServerProxy(url)
+ try:
+ data = server.register(sn, nick, uuid_, profile.pubkey)
+ except (Error, TypeError, socket.error):
+ logging.exception('Registration: cannot connect to server')
+ raise RegisterError(_('Cannot connect to the server.'))
+
+ if data['success'] != 'OK':
+ logging.error('Registration: server could not complete request: %s',
+ data['error'])
+ raise RegisterError(_('The server could not complete the request.'))
+
+ client.set_string('/desktop/sugar/collaboration/jabber_server',
+ data['jabberserver'])
+ client.set_string('/desktop/sugar/backup_url', data['backupurl'])
+
+ return True
+
+def have_ofw_tree():
+ return os.path.exists('/ofw')
+
+def read_ofw(path):
+ path = os.path.join('/ofw', path)
+ if not os.path.exists(path):
+ return None
+ fh = open(path, 'r')
+ data = fh.read().rstrip('\0\n')
+ fh.close()
+ return data
diff --git a/shell/src/jarabe/desktop/snowflakelayout.py b/shell/src/jarabe/desktop/snowflakelayout.py
new file mode 100644
index 0000000..5782cff
--- /dev/null
+++ b/shell/src/jarabe/desktop/snowflakelayout.py
@@ -0,0 +1,108 @@
+# Copyright (C) 2006-2007 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import math
+
+import gobject
+import hippo
+
+from sugar.graphics import style
+
+_BASE_DISTANCE = style.zoom(25)
+_CHILDREN_FACTOR = style.zoom(3)
+
+class SnowflakeLayout(gobject.GObject, hippo.CanvasLayout):
+ __gtype_name__ = 'SugarSnowflakeLayout'
+ def __init__(self):
+ gobject.GObject.__init__(self)
+ self._nflakes = 0
+ self._box = None
+
+ def add(self, child, center=False):
+ if not center:
+ self._nflakes += 1
+
+ self._box.append(child)
+
+ box_child = self._box.find_box_child(child)
+ box_child.is_center = center
+
+ def remove(self, child):
+ box_child = self._box.find_box_child(child)
+ if not box_child.is_center:
+ self._nflakes -= 1
+
+ self._box.remove(child)
+
+ def do_set_box(self, box):
+ self._box = box
+
+ def do_get_height_request(self, for_width):
+ size = self._calculate_size()
+ return (size, size)
+
+ def do_get_width_request(self):
+ size = self._calculate_size()
+ return (size, size)
+
+ def do_allocate(self, x, y, width, height,
+ req_width, req_height, origin_changed):
+ r = self._get_radius()
+ index = 0
+
+ for child in self._box.get_layout_children():
+ min_width, child_width = child.get_width_request()
+ min_height, child_height = child.get_height_request(child_width)
+
+ if child.is_center:
+ child.allocate(x + (width - child_width) / 2,
+ y + (height - child_height) / 2,
+ child_width, child_height, origin_changed)
+ else:
+ angle = 2 * math.pi * index / self._nflakes
+
+ if self._nflakes != 2:
+ angle -= math.pi / 2
+
+ dx = math.cos(angle) * r
+ dy = math.sin(angle) * r
+
+ child_x = int(x + (width - child_width) / 2 + dx)
+ child_y = int(y + (height - child_height) / 2 + dy)
+
+ child.allocate(child_x, child_y, child_width,
+ child_height, origin_changed)
+
+ index += 1
+
+ def _get_radius(self):
+ radius = int(_BASE_DISTANCE + _CHILDREN_FACTOR * self._nflakes)
+ for child in self._box.get_layout_children():
+ if child.is_center:
+ [min_w, child_w] = child.get_width_request()
+ [min_h, child_h] = child.get_height_request(child_w)
+ radius += max(child_w, child_h) / 2
+
+ return radius
+
+ def _calculate_size(self):
+ thickness = 0
+ for child in self._box.get_layout_children():
+ [min_width, child_width] = child.get_width_request()
+ [min_height, child_height] = child.get_height_request(child_width)
+ thickness = max(thickness, max(child_width, child_height))
+
+ return self._get_radius() * 2 + thickness
diff --git a/shell/src/jarabe/desktop/spreadlayout.py b/shell/src/jarabe/desktop/spreadlayout.py
new file mode 100644
index 0000000..ffc5bc7
--- /dev/null
+++ b/shell/src/jarabe/desktop/spreadlayout.py
@@ -0,0 +1,83 @@
+# Copyright (C) 2007 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import hippo
+import gobject
+import gtk
+
+from sugar.graphics import style
+
+from jarabe.desktop.grid import Grid
+
+_CELL_SIZE = 4.0
+
+class SpreadLayout(gobject.GObject, hippo.CanvasLayout):
+ __gtype_name__ = 'SugarSpreadLayout'
+ def __init__(self):
+ gobject.GObject.__init__(self)
+ self._box = None
+
+ min_width, width = self.do_get_width_request()
+ min_height, height = self.do_get_height_request(width)
+
+ self._grid = Grid(int(width / _CELL_SIZE), int(height / _CELL_SIZE))
+ self._grid.connect('child-changed', self._grid_child_changed_cb)
+
+ def add(self, child):
+ self._box.append(child)
+
+ width, height = self._get_child_grid_size(child)
+ self._grid.add(child, width, height)
+
+ def remove(self, child):
+ self._grid.remove(child)
+ self._box.remove(child)
+
+ def move(self, child, x, y):
+ self._grid.move(child, x / _CELL_SIZE, y / _CELL_SIZE, locked=True)
+
+ def do_set_box(self, box):
+ self._box = box
+
+ def do_get_height_request(self, for_width):
+ return 0, gtk.gdk.screen_height() - style.GRID_CELL_SIZE
+
+ def do_get_width_request(self):
+ return 0, gtk.gdk.screen_width()
+
+ def do_allocate(self, x, y, width, height,
+ req_width, req_height, origin_changed):
+ for child in self._box.get_layout_children():
+ # We need to always get requests to not confuse hippo
+ min_w, child_width = child.get_width_request()
+ min_h, child_height = child.get_height_request(child_width)
+
+ rect = self._grid.get_child_rect(child.item)
+ child.allocate(int(round(rect.x * _CELL_SIZE)),
+ int(round(rect.y * _CELL_SIZE)),
+ child_width,
+ child_height,
+ origin_changed)
+
+ def _get_child_grid_size(self, child):
+ min_width, width = child.get_width_request()
+ min_height, height = child.get_height_request(width)
+
+ return int(width / _CELL_SIZE), int(height / _CELL_SIZE)
+
+ def _grid_child_changed_cb(self, grid, child):
+ child.emit_request_changed()
+
diff --git a/shell/src/jarabe/desktop/transitionbox.py b/shell/src/jarabe/desktop/transitionbox.py
new file mode 100644
index 0000000..cf9e0d6
--- /dev/null
+++ b/shell/src/jarabe/desktop/transitionbox.py
@@ -0,0 +1,96 @@
+# Copyright (C) 2007, Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import hippo
+import gobject
+
+from sugar.graphics import style
+from sugar.graphics import animator
+
+from jarabe.model.buddy import get_owner_instance
+from jarabe.view.buddyicon import BuddyIcon
+
+class _Animation(animator.Animation):
+ def __init__(self, icon, start_size, end_size):
+ animator.Animation.__init__(self, 0.0, 1.0)
+
+ self._icon = icon
+ self.start_size = start_size
+ self.end_size = end_size
+
+ def next_frame(self, current):
+ d = (self.end_size - self.start_size) * current
+ self._icon.props.size = self.start_size + d
+
+class _Layout(gobject.GObject, hippo.CanvasLayout):
+ __gtype_name__ = 'SugarTransitionBoxLayout'
+ def __init__(self):
+ gobject.GObject.__init__(self)
+ self._box = None
+
+ def do_set_box(self, box):
+ self._box = box
+
+ def do_get_height_request(self, for_width):
+ return 0, 0
+
+ def do_get_width_request(self):
+ return 0, 0
+
+ def do_allocate(self, x, y, width, height,
+ req_width, req_height, origin_changed):
+ for child in self._box.get_layout_children():
+ min_width, child_width = child.get_width_request()
+ min_height, child_height = child.get_height_request(child_width)
+
+ child.allocate(x + (width - child_width) / 2,
+ y + (height - child_height) / 2,
+ child_width, child_height, origin_changed)
+
+class TransitionBox(hippo.Canvas):
+ __gtype_name__ = 'SugarTransitionBox'
+
+ __gsignals__ = {
+ 'completed': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([]))
+ }
+
+ def __init__(self):
+ gobject.GObject.__init__(self)
+
+ self._box = hippo.CanvasBox()
+ self._box.props.background_color = style.COLOR_WHITE.get_int()
+ self.set_root(self._box)
+
+ self._layout = _Layout()
+ self._box.set_layout(self._layout)
+
+ self._my_icon = BuddyIcon(buddy=get_owner_instance(),
+ size=style.XLARGE_ICON_SIZE)
+ self._box.append(self._my_icon)
+
+ self._animator = animator.Animator(0.3)
+ self._animator.connect('completed', self._animation_completed_cb)
+
+ def _animation_completed_cb(self, anim):
+ self.emit('completed')
+
+ def start_transition(self, start_size, end_size):
+ self._my_icon.props.size = start_size
+
+ self._animator.remove_all()
+ self._animator.add(_Animation(self._my_icon, start_size, end_size))
+ self._animator.start()
diff --git a/shell/src/jarabe/frame/Makefile.am b/shell/src/jarabe/frame/Makefile.am
new file mode 100644
index 0000000..e5c445f
--- /dev/null
+++ b/shell/src/jarabe/frame/Makefile.am
@@ -0,0 +1,18 @@
+sugardir = $(pythondir)/jarabe/frame
+sugar_PYTHON = \
+ __init__.py \
+ activitiestray.py \
+ clipboard.py \
+ clipboardicon.py \
+ clipboardmenu.py \
+ clipboardobject.py \
+ clipboardpanelwindow.py \
+ clipboardtray.py \
+ devicestray.py \
+ frameinvoker.py \
+ friendstray.py \
+ eventarea.py \
+ frame.py \
+ notification.py \
+ framewindow.py \
+ zoomtoolbar.py
diff --git a/shell/src/jarabe/frame/__init__.py b/shell/src/jarabe/frame/__init__.py
new file mode 100644
index 0000000..d7aec3d
--- /dev/null
+++ b/shell/src/jarabe/frame/__init__.py
@@ -0,0 +1,25 @@
+# Copyright (C) 2006-2007, Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from jarabe.frame.frame import Frame
+
+_view = None
+
+def get_view():
+ global _view
+ if not _view:
+ _view = Frame()
+ return _view
diff --git a/shell/src/jarabe/frame/activitiestray.py b/shell/src/jarabe/frame/activitiestray.py
new file mode 100644
index 0000000..9a1a9da
--- /dev/null
+++ b/shell/src/jarabe/frame/activitiestray.py
@@ -0,0 +1,745 @@
+# Copyright (C) 2006-2007 Red Hat, Inc.
+# Copyright (C) 2008 One Laptop Per Child
+# Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import logging
+from gettext import gettext as _
+import tempfile
+import os
+
+import gobject
+import gconf
+import gio
+import gtk
+
+from sugar.graphics import style
+from sugar.graphics.tray import HTray
+from sugar.graphics.xocolor import XoColor
+from sugar.graphics.radiotoolbutton import RadioToolButton
+from sugar.graphics.toolbutton import ToolButton
+from sugar.graphics.icon import Icon, get_icon_file_name
+from sugar.graphics.palette import Palette, WidgetInvoker
+from sugar.graphics.menuitem import MenuItem
+from sugar.datastore import datastore
+from sugar import mime
+from sugar import env
+
+from jarabe.model import shell
+from jarabe.model import invites
+from jarabe.model import bundleregistry
+from jarabe.model import filetransfer
+from jarabe.view.palettes import JournalPalette, CurrentActivityPalette
+from jarabe.view.pulsingicon import PulsingIcon
+from jarabe.frame.frameinvoker import FrameWidgetInvoker
+from jarabe.frame.notification import NotificationIcon
+import jarabe.frame
+
+
+class ActivityButton(RadioToolButton):
+ def __init__(self, home_activity, group):
+ RadioToolButton.__init__(self, group=group)
+
+ self.set_palette_invoker(FrameWidgetInvoker(self))
+
+ self._home_activity = home_activity
+ self._notify_launch_hid = None
+
+ self._icon = PulsingIcon()
+ self._icon.props.base_color = home_activity.get_icon_color()
+ self._icon.props.pulse_color = \
+ XoColor('%s,%s' % (style.COLOR_BUTTON_GREY.get_svg(),
+ style.COLOR_TOOLBAR_GREY.get_svg()))
+ if home_activity.get_icon_path():
+ self._icon.props.file = home_activity.get_icon_path()
+ else:
+ self._icon.props.icon_name = 'image-missing'
+ self.set_icon_widget(self._icon)
+ self._icon.show()
+
+ if home_activity.props.launch_status == shell.Activity.LAUNCHING:
+ self._icon.props.pulsing = True
+ self._notify_launch_hid = home_activity.connect( \
+ 'notify::launch-status', self.__notify_launch_status_cb)
+ elif home_activity.props.launch_status == shell.Activity.LAUNCH_FAILED:
+ self._on_failed_launch()
+
+ def create_palette(self):
+ if self._home_activity.is_journal():
+ palette = JournalPalette(self._home_activity)
+ else:
+ palette = CurrentActivityPalette(self._home_activity)
+ palette.set_group_id('frame')
+ self.set_palette(palette)
+
+ def _on_failed_launch(self):
+ # TODO http://bugs.sugarlabs.org/ticket/2007
+ pass
+
+ def __notify_launch_status_cb(self, home_activity, pspec):
+ home_activity.disconnect(self._notify_launch_hid)
+ self._notify_launch_hid = None
+ if home_activity.props.launch_status == shell.Activity.LAUNCH_FAILED:
+ self._on_failed_launch()
+ else:
+ self._icon.props.pulsing = False
+
+
+
+class InviteButton(ToolButton):
+ """Invite to shared activity"""
+ def __init__(self, invite):
+ ToolButton.__init__(self)
+
+ self._invite = invite
+
+ self.connect('clicked', self.__clicked_cb)
+ self.connect('destroy', self.__destroy_cb)
+
+ bundle_registry = bundleregistry.get_registry()
+ bundle = bundle_registry.get_bundle(invite.get_bundle_id())
+
+ self._icon = Icon()
+ self._icon.props.xo_color = invite.get_color()
+ if bundle is not None:
+ self._icon.props.file = bundle.get_icon()
+ else:
+ self._icon.props.icon_name = 'image-missing'
+ self.set_icon_widget(self._icon)
+ self._icon.show()
+
+ palette = InvitePalette(invite)
+ palette.props.invoker = FrameWidgetInvoker(self)
+ palette.set_group_id('frame')
+ self.set_palette(palette)
+
+ self._notif_icon = NotificationIcon()
+ self._notif_icon.connect('button-release-event',
+ self.__button_release_event_cb)
+
+ self._notif_icon.props.xo_color = invite.get_color()
+ if bundle is not None:
+ self._notif_icon.props.icon_filename = bundle.get_icon()
+ else:
+ self._notif_icon.props.icon_name = 'image-missing'
+
+ palette = InvitePalette(invite)
+ palette.props.invoker = WidgetInvoker(self._notif_icon)
+ palette.set_group_id('frame')
+ self._notif_icon.palette = palette
+
+ frame = jarabe.frame.get_view()
+ frame.add_notification(self._notif_icon, gtk.CORNER_TOP_LEFT)
+
+ def __button_release_event_cb(self, icon, event):
+ self.emit('clicked')
+
+ def __clicked_cb(self, button):
+ if self._notif_icon is not None:
+ frame = jarabe.frame.get_view()
+ frame.remove_notification(self._notif_icon)
+ self._notif_icon = None
+ self._launch()
+
+ def __destroy_cb(self, button):
+ frame = jarabe.frame.get_view()
+ frame.remove_notification(self._notif_icon)
+
+ def _launch(self):
+ """Join the activity in the invite."""
+ self._invite.join()
+
+
+class InvitePalette(Palette):
+ """Palette for frame or notification icon for invites."""
+
+ def __init__(self, invite):
+ Palette.__init__(self, '')
+
+ self._invite = invite
+
+ menu_item = MenuItem(_('Join'), icon_name='dialog-ok')
+ menu_item.connect('activate', self.__join_activate_cb)
+ self.menu.append(menu_item)
+ menu_item.show()
+
+ menu_item = MenuItem(_('Decline'), icon_name='dialog-cancel')
+ menu_item.connect('activate', self.__decline_activate_cb)
+ self.menu.append(menu_item)
+ menu_item.show()
+
+ bundle_id = invite.get_bundle_id()
+
+ registry = bundleregistry.get_registry()
+ self._bundle = registry.get_bundle(bundle_id)
+ if self._bundle:
+ self.set_primary_text(self._bundle.get_name())
+ else:
+ self.set_primary_text(bundle_id)
+
+ def __join_activate_cb(self, menu_item):
+ self._invite.join()
+
+ def __decline_activate_cb(self, menu_item):
+ invites_model = invites.get_instance()
+ activity_id = self._activity_model.get_id()
+ invites_model.remove_activity(activity_id)
+
+
+class ActivitiesTray(HTray):
+ def __init__(self):
+ HTray.__init__(self)
+
+ self._buttons = {}
+ self._invite_to_item = {}
+ self._freeze_button_clicks = False
+
+ self._home_model = shell.get_model()
+ self._home_model.connect('activity-added', self.__activity_added_cb)
+ self._home_model.connect('activity-removed', self.__activity_removed_cb)
+ self._home_model.connect('active-activity-changed',
+ self.__activity_changed_cb)
+ self._home_model.connect('tabbing-activity-changed',
+ self.__tabbing_activity_changed_cb)
+
+ self._invites = invites.get_instance()
+ for invite in self._invites:
+ self._add_invite(invite)
+ self._invites.connect('invite-added', self.__invite_added_cb)
+ self._invites.connect('invite-removed', self.__invite_removed_cb)
+
+ filetransfer.new_file_transfer.connect(self.__new_file_transfer_cb)
+
+ def __activity_added_cb(self, home_model, home_activity):
+ logging.debug('__activity_added_cb: %r', home_activity)
+ if self.get_children():
+ group = self.get_children()[0]
+ else:
+ group = None
+
+ button = ActivityButton(home_activity, group)
+ self.add_item(button)
+ self._buttons[home_activity] = button
+ button.connect('clicked', self.__activity_clicked_cb, home_activity)
+ button.show()
+
+ def __activity_removed_cb(self, home_model, home_activity):
+ logging.debug('__activity_removed_cb: %r', home_activity)
+ button = self._buttons[home_activity]
+ self.remove_item(button)
+ del self._buttons[home_activity]
+
+ def _activate_activity(self, home_activity):
+ button = self._buttons[home_activity]
+ self._freeze_button_clicks = True
+ button.props.active = True
+ self._freeze_button_clicks = False
+
+ self.scroll_to_item(button)
+ # Redraw immediately.
+ # The widget may not be realized yet, and then there is no window.
+ if self.window:
+ self.window.process_updates(True)
+
+ def __activity_changed_cb(self, home_model, home_activity):
+ logging.debug('__activity_changed_cb: %r', home_activity)
+
+ # Only select the new activity, if there is no tabbing activity.
+ if home_model.get_tabbing_activity() is None:
+ self._activate_activity(home_activity)
+
+ def __tabbing_activity_changed_cb(self, home_model, home_activity):
+ logging.debug('__tabbing_activity_changed_cb: %r', home_activity)
+ # If the tabbing_activity is set to None just do nothing.
+ # The active activity will be updated a bit later (and it will
+ # be set to the activity that is currently selected).
+ if home_activity is None:
+ return
+
+ self._activate_activity(home_activity)
+
+ def __activity_clicked_cb(self, button, home_activity):
+ if not self._freeze_button_clicks and button.props.active:
+ logging.debug('ActivitiesTray.__activity_clicked_cb')
+ window = home_activity.get_window()
+ if window:
+ window.activate(gtk.get_current_event_time())
+
+ def __invite_clicked_cb(self, icon, invite):
+ self._invites.remove_invite(invite)
+
+ def __invite_added_cb(self, invites_model, invite):
+ self._add_invite(invite)
+
+ def __invite_removed_cb(self, invites_model, invite):
+ self._remove_invite(invite)
+
+ def _add_invite(self, invite):
+ """Add an invite"""
+ item = InviteButton(invite)
+ item.connect('clicked', self.__invite_clicked_cb, invite)
+ self.add_item(item)
+ item.show()
+
+ self._invite_to_item[invite] = item
+
+ def _remove_invite(self, invite):
+ self.remove_item(self._invite_to_item[invite])
+ self._invite_to_item[invite].destroy()
+ del self._invite_to_item[invite]
+
+ def __new_file_transfer_cb(self, **kwargs):
+ file_transfer = kwargs['file_transfer']
+ logging.debug('__new_file_transfer_cb %r', file_transfer)
+
+ if isinstance(file_transfer, filetransfer.IncomingFileTransfer):
+ button = IncomingTransferButton(file_transfer)
+ elif isinstance(file_transfer, filetransfer.OutgoingFileTransfer):
+ button = OutgoingTransferButton(file_transfer)
+
+ self.add_item(button)
+ button.show()
+
+class BaseTransferButton(ToolButton):
+ """Button with a notification attached
+ """
+ def __init__(self, file_transfer):
+ ToolButton.__init__(self)
+
+ self.file_transfer = file_transfer
+ file_transfer.connect('notify::state', self.__notify_state_cb)
+
+ icon = Icon()
+ self.props.icon_widget = icon
+ icon.show()
+
+ self.notif_icon = NotificationIcon()
+ self.notif_icon.connect('button-release-event',
+ self.__button_release_event_cb)
+
+ def __button_release_event_cb(self, icon, event):
+ if self.notif_icon is not None:
+ frame = jarabe.frame.get_view()
+ frame.remove_notification(self.notif_icon)
+ self.notif_icon = None
+
+ def remove(self):
+ frame = jarabe.frame.get_view()
+ frame.remove_notification(self.notif_icon)
+ self.props.parent.remove(self)
+
+ def __notify_state_cb(self, file_transfer, pspec):
+ logging.debug('_update state: %r %r', file_transfer.props.state,
+ file_transfer.reason_last_change)
+ if file_transfer.props.state == filetransfer.FT_STATE_CANCELLED:
+ if file_transfer.reason_last_change == \
+ filetransfer.FT_REASON_LOCAL_STOPPED:
+ self.remove()
+
+class IncomingTransferButton(BaseTransferButton):
+ """UI element representing an ongoing incoming file transfer
+ """
+ def __init__(self, file_transfer):
+ BaseTransferButton.__init__(self, file_transfer)
+
+ self._ds_object = datastore.create()
+
+ file_transfer.connect('notify::state', self.__notify_state_cb)
+ file_transfer.connect('notify::transferred-bytes',
+ self.__notify_transferred_bytes_cb)
+
+ icons = gio.content_type_get_icon(file_transfer.mime_type).props.names
+ icons.append('application-octet-stream')
+ for icon_name in icons:
+ icon_name = 'transfer-from-%s' % icon_name
+ file_name = get_icon_file_name(icon_name)
+ if file_name is not None:
+ self.props.icon_widget.props.icon_name = icon_name
+ self.notif_icon.props.icon_name = icon_name
+ break
+
+ icon_color = XoColor(file_transfer.buddy.props.color)
+ self.props.icon_widget.props.xo_color = icon_color
+ self.notif_icon.props.xo_color = icon_color
+
+ frame = jarabe.frame.get_view()
+ frame.add_notification(self.notif_icon,
+ gtk.CORNER_TOP_LEFT)
+
+ def create_palette(self):
+ palette = IncomingTransferPalette(self.file_transfer)
+ palette.connect('dismiss-clicked', self.__dismiss_clicked_cb)
+ palette.props.invoker = FrameWidgetInvoker(self)
+ palette.set_group_id('frame')
+ return palette
+
+ def __notify_state_cb(self, file_transfer, pspec):
+ if file_transfer.props.state == filetransfer.FT_STATE_OPEN:
+ logging.debug('__notify_state_cb OPEN')
+ self._ds_object.metadata['title'] = file_transfer.title
+ self._ds_object.metadata['description'] = file_transfer.description
+ self._ds_object.metadata['progress'] = '0'
+ self._ds_object.metadata['keep'] = '0'
+ self._ds_object.metadata['buddies'] = ''
+ self._ds_object.metadata['preview'] = ''
+ self._ds_object.metadata['icon-color'] = \
+ file_transfer.buddy.props.color
+ self._ds_object.metadata['mime_type'] = file_transfer.mime_type
+ elif file_transfer.props.state == filetransfer.FT_STATE_COMPLETED:
+ logging.debug('__notify_state_cb COMPLETED')
+ self._ds_object.metadata['progress'] = '100'
+ self._ds_object.file_path = file_transfer.destination_path
+ datastore.write(self._ds_object, transfer_ownership=True,
+ reply_handler=self.__reply_handler_cb,
+ error_handler=self.__error_handler_cb)
+ elif file_transfer.props.state == filetransfer.FT_STATE_CANCELLED:
+ logging.debug('__notify_state_cb CANCELLED')
+ object_id = self._ds_object.object_id
+ if object_id is not None:
+ self._ds_object.destroy()
+ datastore.delete(object_id)
+ self._ds_object = None
+
+ def __notify_transferred_bytes_cb(self, file_transfer, pspec):
+ progress = file_transfer.props.transferred_bytes / \
+ file_transfer.file_size
+ self._ds_object.metadata['progress'] = str(progress * 100)
+ datastore.write(self._ds_object, update_mtime=False)
+
+ def __reply_handler_cb(self):
+ logging.debug('__reply_handler_cb %r', self._ds_object.object_id)
+
+ def __error_handler_cb(self, error):
+ logging.debug('__error_handler_cb %r %s', self._ds_object.object_id,
+ error)
+
+ def __dismiss_clicked_cb(self, palette):
+ self.remove()
+
+class OutgoingTransferButton(BaseTransferButton):
+ """UI element representing an ongoing outgoing file transfer
+ """
+ def __init__(self, file_transfer):
+ BaseTransferButton.__init__(self, file_transfer)
+
+ icons = gio.content_type_get_icon(file_transfer.mime_type).props.names
+ icons.append('application-octet-stream')
+ for icon_name in icons:
+ icon_name = 'transfer-to-%s' % icon_name
+ file_name = get_icon_file_name(icon_name)
+ if file_name is not None:
+ self.props.icon_widget.props.icon_name = icon_name
+ self.notif_icon.props.icon_name = icon_name
+ break
+
+ client = gconf.client_get_default()
+ icon_color = XoColor(client.get_string("/desktop/sugar/user/color"))
+ self.props.icon_widget.props.xo_color = icon_color
+ self.notif_icon.props.xo_color = icon_color
+
+ frame = jarabe.frame.get_view()
+ frame.add_notification(self.notif_icon,
+ gtk.CORNER_TOP_LEFT)
+
+ def create_palette(self):
+ palette = OutgoingTransferPalette(self.file_transfer)
+ palette.connect('dismiss-clicked', self.__dismiss_clicked_cb)
+ palette.props.invoker = FrameWidgetInvoker(self)
+ palette.set_group_id('frame')
+ return palette
+
+ def __dismiss_clicked_cb(self, palette):
+ self.remove()
+
+class BaseTransferPalette(Palette):
+ """Base palette class for frame or notification icon for file transfers
+ """
+ __gtype_name__ = "SugarBaseTransferPalette"
+
+ __gsignals__ = {
+ 'dismiss-clicked': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([])),
+ }
+
+ def __init__(self, file_transfer):
+ Palette.__init__(self, file_transfer.title)
+
+ self.file_transfer = file_transfer
+
+ self.progress_bar = None
+ self.progress_label = None
+ self._notify_transferred_bytes_handler = None
+
+ self.connect('popup', self.__popup_cb)
+ self.connect('popdown', self.__popdown_cb)
+
+ def __popup_cb(self, palette):
+ self.update_progress()
+ self._notify_transferred_bytes_handler = \
+ self.file_transfer.connect('notify::transferred_bytes',
+ self.__notify_transferred_bytes_cb)
+
+ def __popdown_cb(self, palette):
+ if self._notify_transferred_bytes_handler is not None:
+ self.file_transfer.disconnect(
+ self._notify_transferred_bytes_handler)
+ self._notify_transferred_bytes_handler = None
+
+ def __notify_transferred_bytes_cb(self, file_transfer, pspec):
+ self.update_progress()
+
+ def _format_size(self, size):
+ if size < 1024:
+ return _('%dB') % size
+ elif size < 1048576:
+ return _('%dKB') % (size / 1024)
+ else:
+ return _('%dMB') % (size / 1048576)
+
+ def update_progress(self):
+ logging.debug('update_progress: %r',
+ self.file_transfer.props.transferred_bytes)
+
+ if self.progress_bar is None:
+ return
+
+ self.progress_bar.props.fraction = \
+ self.file_transfer.props.transferred_bytes / \
+ float(self.file_transfer.file_size)
+ logging.debug('update_progress: %r', self.progress_bar.props.fraction)
+
+ transferred = self._format_size(
+ self.file_transfer.props.transferred_bytes)
+ total = self._format_size(self.file_transfer.file_size)
+ self.progress_label.props.label = _('%s of %s') % (transferred, total)
+
+class IncomingTransferPalette(BaseTransferPalette):
+ """Palette for frame or notification icon for incoming file transfers
+ """
+ __gtype_name__ = "SugarIncomingTransferPalette"
+ def __init__(self, file_transfer):
+ BaseTransferPalette.__init__(self, file_transfer)
+
+ self.file_transfer.connect('notify::state', self.__notify_state_cb)
+
+ nick = self.file_transfer.buddy.props.nick
+ self.props.secondary_text = _('Transfer from %r') % nick
+
+ self._update()
+
+ def __notify_state_cb(self, file_transfer, pspec):
+ self._update()
+
+ def _update(self):
+ logging.debug('_update state: %r', self.file_transfer.props.state)
+ if self.file_transfer.props.state == filetransfer.FT_STATE_PENDING:
+ menu_item = MenuItem(_('Accept'), icon_name='dialog-ok')
+ menu_item.connect('activate', self.__accept_activate_cb)
+ self.menu.append(menu_item)
+ menu_item.show()
+
+ menu_item = MenuItem(_('Decline'), icon_name='dialog-cancel')
+ menu_item.connect('activate', self.__decline_activate_cb)
+ self.menu.append(menu_item)
+ menu_item.show()
+
+ vbox = gtk.VBox()
+ self.set_content(vbox)
+ vbox.show()
+
+ if self.file_transfer.description:
+ label = gtk.Label(self.file_transfer.description)
+ vbox.add(label)
+ label.show()
+
+ mime_type = self.file_transfer.mime_type
+ type_description = mime.get_mime_description(mime_type)
+
+ size = self._format_size(self.file_transfer.file_size)
+ label = gtk.Label(_('%s (%s)') % (size, type_description))
+ vbox.add(label)
+ label.show()
+
+ elif self.file_transfer.props.state in \
+ [filetransfer.FT_STATE_ACCEPTED, filetransfer.FT_STATE_OPEN]:
+
+ for item in self.menu.get_children():
+ self.menu.remove(item)
+
+ menu_item = MenuItem(_('Cancel'), icon_name='dialog-cancel')
+ menu_item.connect('activate', self.__cancel_activate_cb)
+ self.menu.append(menu_item)
+ menu_item.show()
+
+ vbox = gtk.VBox()
+ self.set_content(vbox)
+ vbox.show()
+
+ self.progress_bar = gtk.ProgressBar()
+ vbox.add(self.progress_bar)
+ self.progress_bar.show()
+
+ self.progress_label = gtk.Label('')
+ vbox.add(self.progress_label)
+ self.progress_label.show()
+
+ self.update_progress()
+
+ elif self.file_transfer.props.state == filetransfer.FT_STATE_COMPLETED:
+
+ for item in self.menu.get_children():
+ self.menu.remove(item)
+
+ menu_item = MenuItem(_('Dismiss'), icon_name='dialog-cancel')
+ menu_item.connect('activate', self.__dismiss_activate_cb)
+ self.menu.append(menu_item)
+ menu_item.show()
+
+ self.update_progress()
+ elif self.file_transfer.props.state == filetransfer.FT_STATE_CANCELLED:
+
+ for item in self.menu.get_children():
+ self.menu.remove(item)
+
+ menu_item = MenuItem(_('Resume'), icon_name='dialog-cancel')
+ menu_item.connect('activate', self.__resume_activate_cb)
+ self.menu.append(menu_item)
+ menu_item.show()
+
+ self.update_progress()
+
+ def __accept_activate_cb(self, menu_item):
+ #TODO: figure out the best place to get rid of that temp file
+ extension = mime.get_primary_extension(self.file_transfer.mime_type)
+ if extension is None:
+ extension = '.bin'
+ fd, file_path = tempfile.mkstemp(suffix=extension,
+ prefix=self._sanitize(self.file_transfer.title),
+ dir=os.path.join(env.get_profile_path(), 'data'))
+ os.close(fd)
+ os.unlink(file_path)
+
+ self.file_transfer.accept(file_path)
+
+ def _sanitize(self, file_name):
+ file_name = file_name.replace('/', '_')
+ file_name = file_name.replace('.', '_')
+ file_name = file_name.replace('?', '_')
+ return file_name
+
+ def __decline_activate_cb(self, menu_item):
+ self.file_transfer.cancel()
+
+ def __cancel_activate_cb(self, menu_item):
+ self.file_transfer.cancel()
+
+ def __resume_activate_cb(self, menu_item):
+ self.file_transfer.resume()
+
+ def __dismiss_activate_cb(self, menu_item):
+ self.emit('dismiss-clicked')
+
+class OutgoingTransferPalette(BaseTransferPalette):
+ """Palette for frame or notification icon for outgoing file transfers
+ """
+ __gtype_name__ = "SugarOutgoingTransferPalette"
+
+ def __init__(self, file_transfer):
+ BaseTransferPalette.__init__(self, file_transfer)
+
+ self.progress_bar = None
+ self.progress_label = None
+
+ self.file_transfer.connect('notify::state', self.__notify_state_cb)
+
+ nick = file_transfer.buddy.props.nick
+ self.props.secondary_text = _('Transfer to %r') % nick
+
+ self._update()
+
+ def __notify_state_cb(self, file_transfer, pspec):
+ self._update()
+
+ def _update(self):
+ new_state = self.file_transfer.props.state
+ logging.debug('_update state: %r', new_state)
+ if new_state == filetransfer.FT_STATE_PENDING:
+
+ menu_item = MenuItem(_('Cancel'), icon_name='dialog-cancel')
+ menu_item.connect('activate', self.__cancel_activate_cb)
+ self.menu.append(menu_item)
+ menu_item.show()
+
+ vbox = gtk.VBox()
+ self.set_content(vbox)
+ vbox.show()
+
+ if self.file_transfer.description:
+ label = gtk.Label(self.file_transfer.description)
+ vbox.add(label)
+ label.show()
+
+ mime_type = self.file_transfer.mime_type
+ type_description = mime.get_mime_description(mime_type)
+
+ size = self._format_size(self.file_transfer.file_size)
+ label = gtk.Label(_('%s (%s)') % (size, type_description))
+ vbox.add(label)
+ label.show()
+
+ elif new_state in [filetransfer.FT_STATE_ACCEPTED,
+ filetransfer.FT_STATE_OPEN]:
+
+ for item in self.menu.get_children():
+ self.menu.remove(item)
+
+ menu_item = MenuItem(_('Cancel'), icon_name='dialog-cancel')
+ menu_item.connect('activate', self.__cancel_activate_cb)
+ self.menu.append(menu_item)
+ menu_item.show()
+
+ vbox = gtk.VBox()
+ self.set_content(vbox)
+ vbox.show()
+
+ self.progress_bar = gtk.ProgressBar()
+ vbox.add(self.progress_bar)
+ self.progress_bar.show()
+
+ self.progress_label = gtk.Label('')
+ vbox.add(self.progress_label)
+ self.progress_label.show()
+
+ self.update_progress()
+
+ elif new_state in [filetransfer.FT_STATE_COMPLETED,
+ filetransfer.FT_STATE_CANCELLED]:
+
+ for item in self.menu.get_children():
+ self.menu.remove(item)
+
+ menu_item = MenuItem(_('Dismiss'), icon_name='dialog-cancel')
+ menu_item.connect('activate', self.__dismiss_activate_cb)
+ self.menu.append(menu_item)
+ menu_item.show()
+
+ self.update_progress()
+
+ def __cancel_activate_cb(self, menu_item):
+ self.file_transfer.cancel()
+
+ def __dismiss_activate_cb(self, menu_item):
+ self.emit('dismiss-clicked')
diff --git a/shell/src/jarabe/frame/clipboard.py b/shell/src/jarabe/frame/clipboard.py
new file mode 100644
index 0000000..3b9f745
--- /dev/null
+++ b/shell/src/jarabe/frame/clipboard.py
@@ -0,0 +1,149 @@
+# Copyright (C) 2006, Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import logging
+import os
+import shutil
+import urlparse
+import tempfile
+
+import gobject
+
+from sugar import mime
+
+from jarabe.frame.clipboardobject import ClipboardObject, Format
+
+class Clipboard(gobject.GObject):
+
+ __gsignals__ = {
+ 'object-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([object])),
+ 'object-deleted': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([int])),
+ 'object-state-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([object]))
+ }
+
+ def __init__(self):
+ gobject.GObject.__init__(self)
+
+ self._objects = {}
+ self._next_id = 0
+
+ def _get_next_object_id(self):
+ self._next_id += 1
+ return self._next_id
+
+ def add_object(self, name):
+ logging.debug('Clipboard.add_object')
+ object_id = self._get_next_object_id()
+ self._objects[object_id] = ClipboardObject(object_id, name)
+ self.emit('object-added', self._objects[object_id])
+ return object_id
+
+ def add_object_format(self, object_id, format_type, data, on_disk):
+ logging.debug('Clipboard.add_object_format')
+ cb_object = self._objects[object_id]
+
+ if format_type == 'XdndDirectSave0':
+ format_ = Format('text/uri-list', data + '\r\n', on_disk)
+ format_.owns_disk_data = True
+ cb_object.add_format(format_)
+ elif on_disk and cb_object.get_percent() == 100:
+ new_uri = self._copy_file(data)
+ cb_object.add_format(Format(format_type, new_uri, on_disk))
+ logging.debug('Added format of type ' + format_type
+ + ' with path at ' + new_uri)
+ else:
+ cb_object.add_format(Format(format_type, data, on_disk))
+ logging.debug('Added in-memory format of type ' + format_type + '.')
+
+ self.emit('object-state-changed', cb_object)
+
+ def delete_object(self, object_id):
+ cb_object = self._objects.pop(object_id)
+ cb_object.destroy()
+ self.emit('object-deleted', object_id)
+ logging.debug('Deleted object with object_id %r', object_id)
+
+ def set_object_percent(self, object_id, percent):
+ cb_object = self._objects[object_id]
+ if percent < 0 or percent > 100:
+ raise ValueError("invalid percentage")
+ if cb_object.get_percent() > percent:
+ raise ValueError("invalid percentage; less than current percent")
+ if cb_object.get_percent() == percent:
+ # ignore setting same percentage
+ return
+
+ cb_object.set_percent(percent)
+
+ if percent == 100:
+ self._process_object(cb_object)
+
+ self.emit('object-state-changed', cb_object)
+
+ def _process_object(self, cb_object):
+ formats = cb_object.get_formats()
+ for format_name, format_ in formats.iteritems():
+ if format_.is_on_disk() and not format_.owns_disk_data:
+ new_uri = self._copy_file(format_.get_data())
+ format_.set_data(new_uri)
+
+ # Add a text/plain format to objects that are text but lack it
+ if 'text/plain' not in formats.keys():
+ if 'UTF8_STRING' in formats.keys():
+ self.add_object_format(
+ cb_object.get_id(), 'text/plain',
+ data=formats['UTF8_STRING'].get_data(), on_disk=False)
+ elif 'text/unicode' in formats.keys():
+ self.add_object_format(
+ cb_object.get_id(), 'text/plain',
+ data=formats['UTF8_STRING'].get_data(), on_disk=False)
+
+ def get_object(self, object_id):
+ logging.debug('Clipboard.get_object')
+ return self._objects[object_id]
+
+ def get_object_data(self, object_id, format_type):
+ logging.debug('Clipboard.get_object_data')
+ cb_object = self._objects[object_id]
+ format_ = cb_object.get_formats()[format_type]
+ return format_
+
+ def _copy_file(self, original_uri):
+ uri = urlparse.urlparse(original_uri)
+ path_, file_name = os.path.split(uri.path)
+
+ root, ext = os.path.splitext(file_name)
+ if not ext or ext == '.':
+ mime_type = mime.get_for_file(uri.path)
+ ext = '.' + mime.get_primary_extension(mime_type)
+
+ f_, new_file_path = tempfile.mkstemp(ext, root)
+ del f_
+ shutil.copyfile(uri.path, new_file_path)
+ os.chmod(new_file_path, 0644)
+
+ return 'file://' + new_file_path
+
+_instance = None
+
+def get_instance():
+ global _instance
+ if not _instance:
+ _instance = Clipboard()
+ return _instance
diff --git a/shell/src/jarabe/frame/clipboardicon.py b/shell/src/jarabe/frame/clipboardicon.py
new file mode 100644
index 0000000..279db08
--- /dev/null
+++ b/shell/src/jarabe/frame/clipboardicon.py
@@ -0,0 +1,158 @@
+# Copyright (C) 2007, Red Hat, Inc.
+# Copyright (C) 2007, One Laptop Per Child
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import logging
+import gconf
+
+import gtk
+
+from sugar.graphics.radiotoolbutton import RadioToolButton
+from sugar.graphics.icon import Icon
+from sugar.graphics.xocolor import XoColor
+from sugar.graphics import style
+
+from jarabe.frame import clipboard
+from jarabe.frame.clipboardmenu import ClipboardMenu
+from jarabe.frame.frameinvoker import FrameWidgetInvoker
+from jarabe.frame.notification import NotificationIcon
+import jarabe.frame
+
+class ClipboardIcon(RadioToolButton):
+ __gtype_name__ = 'SugarClipboardIcon'
+
+ def __init__(self, cb_object, group):
+ RadioToolButton.__init__(self, group=group)
+
+ self.props.palette_invoker = FrameWidgetInvoker(self)
+
+ self._cb_object = cb_object
+ self.owns_clipboard = False
+ self.props.sensitive = False
+ self.props.active = False
+ self._notif_icon = None
+ self._current_percent = None
+
+ self._icon = Icon()
+ client = gconf.client_get_default()
+ color = XoColor(client.get_string('/desktop/sugar/user/color'))
+ self._icon.props.xo_color = color
+ self.set_icon_widget(self._icon)
+ self._icon.show()
+
+ cb_service = clipboard.get_instance()
+ cb_service.connect('object-state-changed',
+ self._object_state_changed_cb)
+
+ child = self.get_child()
+ child.connect('drag_data_get', self._drag_data_get_cb)
+ self.connect('notify::active', self._notify_active_cb)
+
+ def create_palette(self):
+ palette = ClipboardMenu(self._cb_object)
+ palette.set_group_id('frame')
+ return palette
+
+ def get_object_id(self):
+ return self._cb_object.get_id()
+
+ def _drag_data_get_cb(self, widget, context, selection, target_type,
+ event_time):
+ logging.debug('_drag_data_get_cb: requested target ' + selection.target)
+ data = self._cb_object.get_formats()[selection.target].get_data()
+ selection.set(selection.target, 8, data)
+
+ def _put_in_clipboard(self):
+ logging.debug('ClipboardIcon._put_in_clipboard')
+
+ if self._cb_object.get_percent() < 100:
+ raise ValueError('Object is not complete,' \
+ ' cannot be put into the clipboard.')
+
+ targets = self._get_targets()
+ if targets:
+ x_clipboard = gtk.Clipboard()
+ if not x_clipboard.set_with_data(targets,
+ self._clipboard_data_get_cb,
+ self._clipboard_clear_cb,
+ targets):
+ logging.error('GtkClipboard.set_with_data failed!')
+ else:
+ self.owns_clipboard = True
+
+ def _clipboard_data_get_cb(self, x_clipboard, selection, info, targets):
+ if not selection.target in [target[0] for target in targets]:
+ logging.warning('ClipboardIcon._clipboard_data_get_cb: asked %s' \
+ ' but only have %r.', selection.target, targets)
+ return
+ data = self._cb_object.get_formats()[selection.target].get_data()
+ selection.set(selection.target, 8, data)
+
+ def _clipboard_clear_cb(self, x_clipboard, targets):
+ logging.debug('ClipboardIcon._clipboard_clear_cb')
+ self.owns_clipboard = False
+
+ def _object_state_changed_cb(self, cb_service, cb_object):
+ if cb_object != self._cb_object:
+ return
+
+ if cb_object.get_icon():
+ self._icon.props.icon_name = cb_object.get_icon()
+ else:
+ self._icon.props.icon_name = 'application-octet-stream'
+
+ child = self.get_child()
+ child.connect('drag-begin', self._drag_begin_cb)
+ child.drag_source_set(gtk.gdk.BUTTON1_MASK,
+ self._get_targets(),
+ gtk.gdk.ACTION_COPY)
+
+ if cb_object.get_percent() == 100:
+ self.props.sensitive = True
+
+ # Clipboard object became complete. Make it the active one.
+ if self._current_percent < 100 and cb_object.get_percent() == 100:
+ self.props.active = True
+
+ self._notif_icon = NotificationIcon()
+ self._notif_icon.props.icon_name = self._icon.props.icon_name
+ self._notif_icon.props.xo_color = \
+ XoColor('%s,%s' % (self._icon.props.stroke_color,
+ self._icon.props.fill_color))
+ frame = jarabe.frame.get_view()
+ frame.add_notification(self._notif_icon,
+ gtk.CORNER_BOTTOM_LEFT)
+ self._current_percent = cb_object.get_percent()
+
+ def _drag_begin_cb(self, widget, context):
+ # TODO: We should get the pixbuf from the icon, with colors, etc.
+ icon_theme = gtk.icon_theme_get_default()
+ pixbuf = icon_theme.load_icon(self._icon.props.icon_name,
+ style.STANDARD_ICON_SIZE, 0)
+ context.set_icon_pixbuf(pixbuf, hot_x=pixbuf.props.width / 2,
+ hot_y=pixbuf.props.height / 2)
+
+ def _notify_active_cb(self, widget, pspec):
+ if self.props.active:
+ self._put_in_clipboard()
+ else:
+ self.owns_clipboard = False
+
+ def _get_targets(self):
+ targets = []
+ for format_type in self._cb_object.get_formats().keys():
+ targets.append((format_type, 0, 0))
+ return targets
diff --git a/shell/src/jarabe/frame/clipboardmenu.py b/shell/src/jarabe/frame/clipboardmenu.py
new file mode 100644
index 0000000..b998110
--- /dev/null
+++ b/shell/src/jarabe/frame/clipboardmenu.py
@@ -0,0 +1,249 @@
+# Copyright (C) 2007, One Laptop Per Child
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from gettext import gettext as _
+import tempfile
+import urlparse
+import os
+import logging
+import gconf
+
+import gtk
+
+from sugar.graphics.palette import Palette
+from sugar.graphics.menuitem import MenuItem
+from sugar.graphics.icon import Icon
+from sugar.graphics.xocolor import XoColor
+from sugar.datastore import datastore
+from sugar import mime
+from sugar import env
+
+from jarabe.frame import clipboard
+from jarabe.journal import misc
+from jarabe.model import bundleregistry
+
+class ClipboardMenu(Palette):
+
+ def __init__(self, cb_object):
+ Palette.__init__(self, text_maxlen=100)
+
+ self._cb_object = cb_object
+
+ self.set_group_id('frame')
+
+ cb_service = clipboard.get_instance()
+ cb_service.connect('object-state-changed',
+ self._object_state_changed_cb)
+
+ self._progress_bar = None
+
+ self._remove_item = MenuItem(_('Remove'), 'list-remove')
+ self._remove_item.connect('activate', self._remove_item_activate_cb)
+ self.menu.append(self._remove_item)
+ self._remove_item.show()
+
+ self._open_item = MenuItem(_('Open'), 'zoom-activity')
+ self._open_item.connect('activate', self._open_item_activate_cb)
+ self.menu.append(self._open_item)
+ self._open_item.show()
+
+ self._journal_item = MenuItem(_('Keep'))
+ client = gconf.client_get_default()
+ color = XoColor(client.get_string('/desktop/sugar/user/color'))
+ icon = Icon(icon_name='document-save', icon_size=gtk.ICON_SIZE_MENU,
+ xo_color=color)
+ self._journal_item.set_image(icon)
+
+ self._journal_item.connect('activate', self._journal_item_activate_cb)
+ self.menu.append(self._journal_item)
+ self._journal_item.show()
+
+ self._update()
+
+ def _update_open_submenu(self):
+ activities = self._get_activities()
+ logging.debug('_update_open_submenu: %r', activities)
+ child = self._open_item.get_child()
+ if activities is None or len(activities) <= 1:
+ child.set_text(_('Open'))
+ if self._open_item.get_submenu() is not None:
+ self._open_item.remove_submenu()
+ return
+
+ child.set_text(_('Open with'))
+ submenu = self._open_item.get_submenu()
+ if submenu is None:
+ submenu = gtk.Menu()
+ self._open_item.set_submenu(submenu)
+ submenu.show()
+ else:
+ for item in submenu.get_children():
+ submenu.remove(item)
+
+ for service_name in activities:
+ registry = bundleregistry.get_registry()
+ activity_info = registry.get_bundle(service_name)
+
+ if not activity_info:
+ logging.warning('Activity %s is unknown.', service_name)
+
+ item = gtk.MenuItem(activity_info.get_name())
+ item.connect('activate', self._open_submenu_item_activate_cb,
+ service_name)
+ submenu.append(item)
+ item.show()
+
+ def _update_items_visibility(self):
+ activities = self._get_activities()
+ installable = self._cb_object.is_bundle()
+ percent = self._cb_object.get_percent()
+
+ if percent == 100 and (activities or installable):
+ self._remove_item.props.sensitive = True
+ self._open_item.props.sensitive = True
+ self._journal_item.props.sensitive = True
+ elif percent == 100 and (not activities and not installable):
+ self._remove_item.props.sensitive = True
+ self._open_item.props.sensitive = False
+ self._journal_item.props.sensitive = True
+ else:
+ self._remove_item.props.sensitive = True
+ self._open_item.props.sensitive = False
+ self._journal_item.props.sensitive = False
+
+ self._update_progress_bar()
+
+ def _get_activities(self):
+ mime_type = self._cb_object.get_mime_type()
+ if not mime_type:
+ return ''
+
+ registry = bundleregistry.get_registry()
+ activities = registry.get_activities_for_type(mime_type)
+ if activities:
+ return [info.get_bundle_id() for info in activities]
+ else:
+ return ''
+
+ def _update_progress_bar(self):
+ percent = self._cb_object.get_percent()
+ if percent == 100.0:
+ if self._progress_bar:
+ self._progress_bar = None
+ self.set_content(None)
+ else:
+ if self._progress_bar is None:
+ self._progress_bar = gtk.ProgressBar()
+ self._progress_bar.show()
+ self.set_content(self._progress_bar)
+
+ self._progress_bar.props.fraction = percent / 100.0
+ self._progress_bar.props.text = '%.2f %%' % percent
+
+ def _object_state_changed_cb(self, cb_service, cb_object):
+ if cb_object != self._cb_object:
+ return
+ self._update()
+
+ def _update(self):
+ self.props.primary_text = self._cb_object.get_name()
+ preview = self._cb_object.get_preview()
+ if preview:
+ self.props.secondary_text = preview
+ self._update_progress_bar()
+ self._update_items_visibility()
+ self._update_open_submenu()
+
+ def _open_item_activate_cb(self, menu_item):
+ logging.debug('_open_item_activate_cb')
+ percent = self._cb_object.get_percent()
+ if percent < 100 or menu_item.get_submenu() is not None:
+ return
+ jobject = self._copy_to_journal()
+ misc.resume(jobject.metadata, self._get_activities()[0])
+ jobject.destroy()
+
+ def _open_submenu_item_activate_cb(self, menu_item, service_name):
+ logging.debug('_open_submenu_item_activate_cb')
+ percent = self._cb_object.get_percent()
+ if percent < 100:
+ return
+ jobject = self._copy_to_journal()
+ misc.resume(jobject.metadata, service_name)
+ jobject.destroy()
+
+ def _remove_item_activate_cb(self, menu_item):
+ cb_service = clipboard.get_instance()
+ cb_service.delete_object(self._cb_object.get_id())
+
+ def _journal_item_activate_cb(self, menu_item):
+ logging.debug('_journal_item_activate_cb')
+ jobject = self._copy_to_journal()
+ jobject.destroy()
+
+ def _write_to_temp_file(self, data):
+ tmp_dir = os.path.join(env.get_profile_path(), 'data')
+ f, file_path = tempfile.mkstemp(dir=tmp_dir)
+ try:
+ os.write(f, data)
+ finally:
+ os.close(f)
+ return file_path
+
+ def _copy_to_journal(self):
+ formats = self._cb_object.get_formats().keys()
+ most_significant_mime_type = mime.choose_most_significant(formats)
+ format_ = self._cb_object.get_formats()[most_significant_mime_type]
+
+ transfer_ownership = False
+ if most_significant_mime_type == 'text/uri-list':
+ uris = mime.split_uri_list(format_.get_data())
+ if len(uris) == 1 and uris[0].startswith('file://'):
+ file_path = urlparse.urlparse(uris[0]).path
+ transfer_ownership = False
+ mime_type = mime.get_for_file(file_path)
+ else:
+ file_path = self._write_to_temp_file(format_.get_data())
+ transfer_ownership = True
+ mime_type = 'text/uri-list'
+ else:
+ if format_.is_on_disk():
+ file_path = urlparse.urlparse(format_.get_data()).path
+ transfer_ownership = False
+ mime_type = mime.get_for_file(file_path)
+ else:
+ file_path = self._write_to_temp_file(format_.get_data())
+ transfer_ownership = True
+ sniffed_mime_type = mime.get_for_file(file_path)
+ if sniffed_mime_type == 'application/octet-stream':
+ mime_type = most_significant_mime_type
+ else:
+ mime_type = sniffed_mime_type
+
+ jobject = datastore.create()
+ jobject.metadata['title'] = self._cb_object.get_name()
+ jobject.metadata['keep'] = '0'
+ jobject.metadata['buddies'] = ''
+ jobject.metadata['preview'] = ''
+ client = gconf.client_get_default()
+ color = client.get_string('/desktop/sugar/user/color')
+ jobject.metadata['icon-color'] = color
+ jobject.metadata['mime_type'] = mime_type
+ jobject.file_path = file_path
+
+ datastore.write(jobject, transfer_ownership=transfer_ownership)
+
+ return jobject
diff --git a/shell/src/jarabe/frame/clipboardobject.py b/shell/src/jarabe/frame/clipboardobject.py
new file mode 100644
index 0000000..e9403f9
--- /dev/null
+++ b/shell/src/jarabe/frame/clipboardobject.py
@@ -0,0 +1,142 @@
+# Copyright (C) 2007, One Laptop Per Child
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import logging
+import urlparse
+import gio
+import gtk
+
+from gettext import gettext as _
+from sugar import mime
+from sugar.bundle.activitybundle import ActivityBundle
+
+class ClipboardObject(object):
+
+ def __init__(self, object_path, name):
+ self._id = object_path
+ self._name = name
+ self._percent = 0
+ self._formats = {}
+
+ def destroy(self):
+ for format_ in self._formats.itervalues():
+ format_.destroy()
+
+ def get_id(self):
+ return self._id
+
+ def get_name(self):
+ name = self._name
+ if not name:
+ mime_type = mime.get_mime_description(self.get_mime_type())
+
+ if not mime_type:
+ mime_type = 'Data'
+ name = _('%s clipping') % mime_type
+
+ return name
+
+ def get_icon(self):
+ mime_type = self.get_mime_type()
+
+ generic_types = mime.get_all_generic_types()
+ for generic_type in generic_types:
+ if mime_type in generic_type.mime_types:
+ return generic_type.icon
+
+ icons = gio.content_type_get_icon(mime_type)
+ icon_name = None
+ if icons is not None:
+ icon_theme = gtk.icon_theme_get_default()
+ for icon_name in icons.props.names:
+ icon_info = icon_theme.lookup_icon(icon_name,
+ gtk.ICON_SIZE_LARGE_TOOLBAR, 0)
+ if icon_info is not None:
+ icon_info.free()
+ return icon_name
+
+ return 'application-octet-stream'
+
+ def get_preview(self):
+ for mime_type in ['text/plain']:
+ if mime_type in self._formats:
+ return self._formats[mime_type].get_data()
+ return ''
+
+ def is_bundle(self):
+ # A bundle will have only one format.
+ if not self._formats:
+ return False
+ else:
+ return self._formats.keys()[0] in [ActivityBundle.MIME_TYPE,
+ ActivityBundle.DEPRECATED_MIME_TYPE]
+
+ def get_percent(self):
+ return self._percent
+
+ def set_percent(self, percent):
+ self._percent = percent
+
+ def add_format(self, format_):
+ self._formats[format_.get_type()] = format_
+
+ def get_formats(self):
+ return self._formats
+
+ def get_mime_type(self):
+ if not self._formats:
+ return ''
+
+ format_ = mime.choose_most_significant(self._formats.keys())
+ if format_ == 'text/uri-list':
+ data = self._formats['text/uri-list'].get_data()
+ uri = urlparse.urlparse(mime.split_uri_list(data)[0], 'file')
+ if uri.scheme == 'file':
+ if os.path.exists(uri.path):
+ format_ = mime.get_for_file(uri.path)
+ else:
+ format_ = mime.get_from_file_name(uri.path)
+ logging.debug('Chose %r!', format_)
+
+ return format_
+
+class Format(object):
+
+ def __init__(self, mime_type, data, on_disk):
+ self.owns_disk_data = False
+
+ self._type = mime_type
+ self._data = data
+ self._on_disk = on_disk
+
+ def destroy(self):
+ if self._on_disk:
+ uri = urlparse.urlparse(self._data)
+ if os.path.isfile(uri.path):
+ os.remove(uri.path)
+
+ def get_type(self):
+ return self._type
+
+ def get_data(self):
+ return self._data
+
+ def set_data(self, data):
+ self._data = data
+
+ def is_on_disk(self):
+ return self._on_disk
diff --git a/shell/src/jarabe/frame/clipboardpanelwindow.py b/shell/src/jarabe/frame/clipboardpanelwindow.py
new file mode 100644
index 0000000..ac324f4
--- /dev/null
+++ b/shell/src/jarabe/frame/clipboardpanelwindow.py
@@ -0,0 +1,103 @@
+# Copyright (C) 2007, One Laptop Per Child
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import logging
+from urlparse import urlparse
+
+import gtk
+import hippo
+
+from jarabe.frame.framewindow import FrameWindow
+from jarabe.frame.clipboardtray import ClipboardTray
+
+from jarabe.frame import clipboard
+
+class ClipboardPanelWindow(FrameWindow):
+ def __init__(self, frame, orientation):
+ FrameWindow.__init__(self, orientation)
+
+ self._frame = frame
+
+ # Listening for new clipboard objects
+ # NOTE: we need to keep a reference to gtk.Clipboard in order to keep
+ # listening to it.
+ self._clipboard = gtk.Clipboard()
+ self._clipboard.connect("owner-change", self._owner_change_cb)
+
+ self._clipboard_tray = ClipboardTray()
+ canvas_widget = hippo.CanvasWidget(widget=self._clipboard_tray)
+ self.append(canvas_widget, hippo.PACK_EXPAND)
+
+ # Receiving dnd drops
+ self.drag_dest_set(0, [], 0)
+ self.connect("drag_motion", self._clipboard_tray.drag_motion_cb)
+ self.connect("drag_leave", self._clipboard_tray.drag_leave_cb)
+ self.connect("drag_drop", self._clipboard_tray.drag_drop_cb)
+ self.connect("drag_data_received",
+ self._clipboard_tray.drag_data_received_cb)
+
+ def _owner_change_cb(self, x_clipboard, event):
+ logging.debug("owner_change_cb")
+
+ if self._clipboard_tray.owns_clipboard():
+ return
+
+ cb_service = clipboard.get_instance()
+ key = cb_service.add_object(name="")
+ cb_service.set_object_percent(key, percent=0)
+
+ targets = x_clipboard.wait_for_targets()
+ for target in targets:
+ if target not in ('TIMESTAMP', 'TARGETS',
+ 'MULTIPLE', 'SAVE_TARGETS'):
+ logging.debug('Asking for target %s.', target)
+ selection = x_clipboard.wait_for_contents(target)
+ if not selection:
+ logging.warning('no data for selection target %s.', target)
+ continue
+ self._add_selection(key, selection)
+
+ cb_service.set_object_percent(key, percent=100)
+
+ def _add_selection(self, key, selection):
+ if not selection.data:
+ logging.warning('no data for selection target %s.', selection.type)
+ return
+
+ logging.debug('adding type ' + selection.type + '.')
+
+ cb_service = clipboard.get_instance()
+ if selection.type == 'text/uri-list':
+ uris = selection.get_uris()
+
+ if len(uris) > 1:
+ raise NotImplementedError('Multiple uris in text/uri-list' \
+ ' still not supported.')
+ uri = uris[0]
+ scheme, netloc_, path_, parameters_, query_, fragment_ = \
+ urlparse(uri)
+ on_disk = (scheme == 'file')
+
+ cb_service.add_object_format(key,
+ selection.type,
+ uri,
+ on_disk)
+ else:
+ cb_service.add_object_format(key,
+ selection.type,
+ selection.data,
+ on_disk=False)
+
diff --git a/shell/src/jarabe/frame/clipboardtray.py b/shell/src/jarabe/frame/clipboardtray.py
new file mode 100644
index 0000000..8beb6a8
--- /dev/null
+++ b/shell/src/jarabe/frame/clipboardtray.py
@@ -0,0 +1,216 @@
+# Copyright (C) 2007, One Laptop Per Child
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import logging
+import tempfile
+
+import gtk
+
+from sugar import util
+from sugar.graphics import tray
+from sugar.graphics import style
+
+from jarabe.frame import clipboard
+from jarabe.frame.clipboardicon import ClipboardIcon
+
+class _ContextMap(object):
+ """Maps a drag context to the clipboard object involved in the dragging."""
+ def __init__(self):
+ self._context_map = {}
+
+ def add_context(self, context, object_id, data_types):
+ """Establishes the mapping. data_types will serve us for reference-
+ counting this mapping.
+ """
+ self._context_map[context] = [object_id, data_types]
+
+ def get_object_id(self, context):
+ """Retrieves the object_id associated with context.
+ Will release the association when this function was called as many times
+ as the number of data_types that this clipboard object contains.
+ """
+ [object_id, data_types_left] = self._context_map[context]
+
+ data_types_left = data_types_left - 1
+ if data_types_left == 0:
+ del self._context_map[context]
+ else:
+ self._context_map[context] = [object_id, data_types_left]
+
+ return object_id
+
+ def has_context(self, context):
+ return context in self._context_map
+
+class ClipboardTray(tray.VTray):
+
+ MAX_ITEMS = gtk.gdk.screen_height() / style.GRID_CELL_SIZE - 2
+
+ def __init__(self):
+ tray.VTray.__init__(self, align=tray.ALIGN_TO_END)
+ self._icons = {}
+ self._context_map = _ContextMap()
+
+ cb_service = clipboard.get_instance()
+ cb_service.connect('object-added', self._object_added_cb)
+ cb_service.connect('object-deleted', self._object_deleted_cb)
+
+ def owns_clipboard(self):
+ for icon in self._icons.values():
+ if icon.owns_clipboard:
+ return True
+ return False
+
+ def _add_selection(self, object_id, selection):
+ if not selection.data:
+ return
+
+ logging.debug('ClipboardTray: adding type %r', selection.type)
+
+ cb_service = clipboard.get_instance()
+ if selection.type == 'text/uri-list':
+ uris = selection.data.split('\n')
+ if len(uris) > 1:
+ raise NotImplementedError('Multiple uris in text/uri-list' \
+ ' still not supported.')
+
+ cb_service.add_object_format(object_id,
+ selection.type,
+ uris[0],
+ on_disk=True)
+ else:
+ cb_service.add_object_format(object_id,
+ selection.type,
+ selection.data,
+ on_disk=False)
+
+ def _object_added_cb(self, cb_service, cb_object):
+ if self._icons:
+ group = self._icons.values()[0]
+ else:
+ group = None
+
+ icon = ClipboardIcon(cb_object, group)
+ self.add_item(icon)
+ icon.show()
+ self._icons[cb_object.get_id()] = icon
+
+ objects_to_delete = self.get_children()[:-self.MAX_ITEMS]
+ for icon in objects_to_delete:
+ logging.debug('ClipboardTray: deleting surplus object')
+ cb_service = clipboard.get_instance()
+ cb_service.delete_object(icon.get_object_id())
+
+ logging.debug('ClipboardTray: %r was added', cb_object.get_id())
+
+ def _object_deleted_cb(self, cb_service, object_id):
+ icon = self._icons[object_id]
+ self.remove_item(icon)
+ del self._icons[object_id]
+ logging.debug('ClipboardTray: %r was deleted', object_id)
+
+ def drag_motion_cb(self, widget, context, x, y, time):
+ logging.debug('ClipboardTray._drag_motion_cb')
+
+ if self._internal_drag(context):
+ context.drag_status(gtk.gdk.ACTION_MOVE, time)
+ else:
+ context.drag_status(gtk.gdk.ACTION_COPY, time)
+ self.props.drag_active = True
+
+ return True
+
+ def drag_leave_cb(self, widget, context, time):
+ self.props.drag_active = False
+
+ def drag_drop_cb(self, widget, context, x, y, time):
+ logging.debug('ClipboardTray._drag_drop_cb')
+
+ if self._internal_drag(context):
+ # TODO: We should move the object within the clipboard here
+ if not self._context_map.has_context(context):
+ context.drop_finish(False, gtk.get_current_event_time())
+ return False
+
+ cb_service = clipboard.get_instance()
+ object_id = cb_service.add_object(name="")
+
+ self._context_map.add_context(context, object_id, len(context.targets))
+
+ if 'XdndDirectSave0' in context.targets:
+ window = context.source_window
+ prop_type, format_, filename = \
+ window.property_get('XdndDirectSave0','text/plain')
+
+ # FIXME query the clipboard service for a filename?
+ base_dir = tempfile.gettempdir()
+ dest_filename = util.unique_id()
+
+ name_, dot, extension = filename.rpartition('.')
+ dest_filename += dot + extension
+
+ dest_uri = 'file://' + os.path.join(base_dir, dest_filename)
+
+ window.property_change('XdndDirectSave0', prop_type, format_,
+ gtk.gdk.PROP_MODE_REPLACE, dest_uri)
+
+ widget.drag_get_data(context, 'XdndDirectSave0', time)
+ else:
+ for target in context.targets:
+ if str(target) not in ('TIMESTAMP', 'TARGETS', 'MULTIPLE'):
+ widget.drag_get_data(context, target, time)
+
+ cb_service.set_object_percent(object_id, percent=100)
+
+ return True
+
+ def drag_data_received_cb(self, widget, context, x, y, selection,
+ targetType, time):
+ logging.debug('ClipboardTray: got data for target %r',
+ selection.target)
+
+ object_id = self._context_map.get_object_id(context)
+ try:
+ if selection is None:
+ logging.warn('ClipboardTray: empty selection for target %s',
+ selection.target)
+ elif selection.target == 'XdndDirectSave0':
+ if selection.data == 'S':
+ window = context.source_window
+
+ prop_type, format_, dest = \
+ window.property_get('XdndDirectSave0', 'text/plain')
+
+ clipboardservice = clipboard.get_instance()
+ clipboardservice.add_object_format( \
+ object_id, 'XdndDirectSave0', dest, on_disk=True)
+ else:
+ self._add_selection(object_id, selection)
+
+ finally:
+ # If it's the last target to be processed, finish
+ # the dnd transaction
+ if not self._context_map.has_context(context):
+ context.drop_finish(True, gtk.get_current_event_time())
+
+ def _internal_drag(self, context):
+ view_ancestor = context.get_source_widget().get_ancestor(gtk.Viewport)
+ if view_ancestor is self._viewport:
+ return True
+ else:
+ return False
+
diff --git a/shell/src/jarabe/frame/devicestray.py b/shell/src/jarabe/frame/devicestray.py
new file mode 100644
index 0000000..72affe3
--- /dev/null
+++ b/shell/src/jarabe/frame/devicestray.py
@@ -0,0 +1,54 @@
+# Copyright (C) 2008 One Laptop Per Child
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import sys
+import traceback
+import logging
+
+from sugar.graphics import tray
+
+from jarabe import config
+
+class DevicesTray(tray.HTray):
+ def __init__(self):
+ tray.HTray.__init__(self, align=tray.ALIGN_TO_END)
+
+ for f in os.listdir(os.path.join(config.ext_path, 'deviceicon')):
+ if f.endswith('.py') and not f.startswith('__'):
+ module_name = f[:-3]
+ try:
+ mod = __import__('deviceicon.' + module_name, globals(),
+ locals(), [module_name])
+ mod.setup(self)
+ except Exception:
+ logging.error('Exception while loading extension:\n' + \
+ ''.join(traceback.format_exception(*sys.exc_info())))
+
+ def add_device(self, view):
+ index = 0
+ relative_index = getattr(view, "FRAME_POSITION_RELATIVE", -1)
+ for item in self.get_children():
+ current_relative_index = getattr(item, "FRAME_POSITION_RELATIVE", 0)
+ if current_relative_index >= relative_index:
+ index += 1
+ else:
+ break
+ self.add_item(view, index=index)
+ view.show()
+
+ def remove_device(self, view):
+ self.remove_item(view)
diff --git a/shell/src/jarabe/frame/eventarea.py b/shell/src/jarabe/frame/eventarea.py
new file mode 100644
index 0000000..166aaf5
--- /dev/null
+++ b/shell/src/jarabe/frame/eventarea.py
@@ -0,0 +1,151 @@
+# Copyright (C) 2007, Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import gtk
+import gobject
+import wnck
+import gconf
+
+_MAX_DELAY = 1000
+
+class EventArea(gobject.GObject):
+ __gsignals__ = {
+ 'enter': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([])),
+ 'leave': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([]))
+ }
+
+ def __init__(self):
+ gobject.GObject.__init__(self)
+
+ self._windows = []
+ self._hover = False
+ self._sids = {}
+ client = gconf.client_get_default()
+ self._edge_delay = client.get_int('/desktop/sugar/frame/edge_delay')
+ self._corner_delay = client.get_int('/desktop/sugar/frame/corner_delay')
+
+ right = gtk.gdk.screen_width() - 1
+ bottom = gtk.gdk.screen_height() -1
+ width = gtk.gdk.screen_width() - 2
+ height = gtk.gdk.screen_height() - 2
+
+ if self._edge_delay != _MAX_DELAY:
+ invisible = self._create_invisible(1, 0, width, 1,
+ self._edge_delay)
+ self._windows.append(invisible)
+
+ invisible = self._create_invisible(1, bottom, width, 1,
+ self._edge_delay)
+ self._windows.append(invisible)
+
+ invisible = self._create_invisible(0, 1, 1, height,
+ self._edge_delay)
+ self._windows.append(invisible)
+
+ invisible = self._create_invisible(right, 1, 1, height,
+ self._edge_delay)
+ self._windows.append(invisible)
+
+ if self._corner_delay != _MAX_DELAY:
+ invisible = self._create_invisible(0, 0, 1, 1,
+ self._corner_delay)
+ self._windows.append(invisible)
+
+ invisible = self._create_invisible(right, 0, 1, 1,
+ self._corner_delay)
+ self._windows.append(invisible)
+
+ invisible = self._create_invisible(0, bottom, 1, 1,
+ self._corner_delay)
+ self._windows.append(invisible)
+
+ invisible = self._create_invisible(right, bottom, 1, 1,
+ self._corner_delay)
+ self._windows.append(invisible)
+
+ screen = wnck.screen_get_default()
+ screen.connect('window-stacking-changed',
+ self._window_stacking_changed_cb)
+
+ def _create_invisible(self, x, y, width, height, delay):
+ invisible = gtk.Invisible()
+ if delay >= 0:
+ invisible.connect('enter-notify-event', self._enter_notify_cb,
+ delay)
+ invisible.connect('leave-notify-event', self._leave_notify_cb)
+
+ invisible.drag_dest_set(0, [], 0)
+ invisible.connect('drag_motion', self._drag_motion_cb)
+ invisible.connect('drag_leave', self._drag_leave_cb)
+
+ invisible.realize()
+ invisible.window.set_events(gtk.gdk.POINTER_MOTION_MASK |
+ gtk.gdk.ENTER_NOTIFY_MASK |
+ gtk.gdk.LEAVE_NOTIFY_MASK)
+ invisible.window.move_resize(x, y, width, height)
+
+ return invisible
+
+ def _notify_enter(self):
+ if not self._hover:
+ self._hover = True
+ self.emit('enter')
+
+ def _notify_leave(self):
+ if self._hover:
+ self._hover = False
+ self.emit('leave')
+
+ def _enter_notify_cb(self, widget, event, delay):
+ if widget in self._sids:
+ gobject.source_remove(self._sids[widget])
+ self._sids[widget] = gobject.timeout_add(delay,
+ self.__delay_cb,
+ widget)
+
+ def __delay_cb(self, widget):
+ del self._sids[widget]
+ self._notify_enter()
+ return False
+
+ def _leave_notify_cb(self, widget, event):
+ if widget in self._sids:
+ gobject.source_remove(self._sids[widget])
+ del self._sids[widget]
+ self._notify_leave()
+
+ def _drag_motion_cb(self, widget, drag_context, x, y, timestamp):
+ drag_context.drag_status(0, timestamp)
+ self._notify_enter()
+ return True
+
+ def _drag_leave_cb(self, widget, drag_context, timestamp):
+ self._notify_leave()
+ return True
+
+ def show(self):
+ for window in self._windows:
+ window.show()
+
+ def hide(self):
+ for window in self._windows:
+ window.hide()
+
+ def _window_stacking_changed_cb(self, screen):
+ for window in self._windows:
+ window.window.raise_()
diff --git a/shell/src/jarabe/frame/frame.py b/shell/src/jarabe/frame/frame.py
new file mode 100644
index 0000000..7dde55b
--- /dev/null
+++ b/shell/src/jarabe/frame/frame.py
@@ -0,0 +1,351 @@
+# Copyright (C) 2006-2007 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import logging
+
+import gtk
+import gobject
+import hippo
+
+from sugar.graphics import animator
+from sugar.graphics import style
+from sugar.graphics import palettegroup
+from sugar import profile
+
+from jarabe.frame.eventarea import EventArea
+from jarabe.frame.activitiestray import ActivitiesTray
+from jarabe.frame.zoomtoolbar import ZoomToolbar
+from jarabe.frame.friendstray import FriendsTray
+from jarabe.frame.devicestray import DevicesTray
+from jarabe.frame.framewindow import FrameWindow
+from jarabe.frame.clipboardpanelwindow import ClipboardPanelWindow
+from jarabe.frame.notification import NotificationIcon, NotificationWindow
+from jarabe.model import notifications
+
+TOP_RIGHT = 0
+TOP_LEFT = 1
+BOTTOM_RIGHT = 2
+BOTTOM_LEFT = 3
+
+_FRAME_HIDING_DELAY = 500
+_NOTIFICATION_DURATION = 5000
+
+class _Animation(animator.Animation):
+ def __init__(self, frame, end):
+ start = frame.current_position
+ animator.Animation.__init__(self, start, end)
+ self._frame = frame
+
+ def next_frame(self, current):
+ self._frame.move(current)
+
+class _MouseListener(object):
+ def __init__(self, frame):
+ self._frame = frame
+ self._hide_sid = 0
+
+ def mouse_enter(self):
+ self._show_frame()
+
+ def mouse_leave(self):
+ if self._frame.mode == Frame.MODE_MOUSE:
+ self._hide_frame()
+
+ def _show_frame(self):
+ if self._hide_sid != 0:
+ gobject.source_remove(self._hide_sid)
+ self._frame.show(Frame.MODE_MOUSE)
+
+ def _hide_frame_timeout_cb(self):
+ self._frame.hide()
+ return False
+
+ def _hide_frame(self):
+ if self._hide_sid != 0:
+ gobject.source_remove(self._hide_sid)
+ self._hide_sid = gobject.timeout_add(
+ _FRAME_HIDING_DELAY, self._hide_frame_timeout_cb)
+
+class _KeyListener(object):
+ def __init__(self, frame):
+ self._frame = frame
+
+ def key_press(self):
+ if self._frame.visible:
+ if self._frame.mode == Frame.MODE_KEYBOARD:
+ self._frame.hide()
+ else:
+ self._frame.show(Frame.MODE_KEYBOARD)
+
+class Frame(object):
+ MODE_MOUSE = 0
+ MODE_KEYBOARD = 1
+ MODE_NON_INTERACTIVE = 2
+
+ def __init__(self):
+ logging.debug("STARTUP: Loading the frame")
+ self.mode = None
+
+ self._palette_group = palettegroup.get_group('frame')
+ self._palette_group.connect('popdown', self._palette_group_popdown_cb)
+
+ self._left_panel = None
+ self._right_panel = None
+ self._top_panel = None
+ self._bottom_panel = None
+
+ self.current_position = 0.0
+ self._animator = None
+
+ self._event_area = EventArea()
+ self._event_area.connect('enter', self._enter_corner_cb)
+ self._event_area.show()
+
+ self._top_panel = self._create_top_panel()
+ self._bottom_panel = self._create_bottom_panel()
+ self._left_panel = self._create_left_panel()
+ self._right_panel = self._create_right_panel()
+
+ screen = gtk.gdk.screen_get_default()
+ screen.connect('size-changed', self._size_changed_cb)
+
+ self._key_listener = _KeyListener(self)
+ self._mouse_listener = _MouseListener(self)
+
+ self._notif_by_icon = {}
+
+ notification_service = notifications.get_service()
+ notification_service.notification_received.connect(
+ self.__notification_received_cb)
+ notification_service.notification_cancelled.connect(
+ self.__notification_cancelled_cb)
+
+ def is_visible(self):
+ return self.current_position != 0.0
+
+ visible = property(is_visible, None)
+
+ def hide(self):
+ if self._animator:
+ self._animator.stop()
+
+ self._animator = animator.Animator(0.5)
+ self._animator.add(_Animation(self, 0.0))
+ self._animator.start()
+
+ self.mode = None
+
+ def show(self, mode):
+ if self.visible:
+ return
+ if self._animator:
+ self._animator.stop()
+
+ self.mode = mode
+
+ self._animator = animator.Animator(0.5)
+ self._animator.add(_Animation(self, 1.0))
+ self._animator.start()
+
+ def move(self, pos):
+ self.current_position = pos
+ self._update_position()
+
+ def _is_hover(self):
+ return (self._top_panel.hover or \
+ self._bottom_panel.hover or \
+ self._left_panel.hover or \
+ self._right_panel.hover)
+
+ def _create_top_panel(self):
+ panel = self._create_panel(gtk.POS_TOP)
+
+ # TODO: setting box_width and hippo.PACK_EXPAND looks like a hack to me.
+ # Why hippo isn't respecting the request size of these controls?
+
+ zoom_toolbar = ZoomToolbar()
+ panel.append(hippo.CanvasWidget(widget=zoom_toolbar,
+ box_width=4*style.GRID_CELL_SIZE))
+ zoom_toolbar.show()
+
+ activities_tray = ActivitiesTray()
+ panel.append(hippo.CanvasWidget(widget=activities_tray),
+ hippo.PACK_EXPAND)
+ activities_tray.show()
+
+ return panel
+
+ def _create_bottom_panel(self):
+ panel = self._create_panel(gtk.POS_BOTTOM)
+
+ # TODO: same issue as in _create_top_panel()
+ devices_tray = DevicesTray()
+ panel.append(hippo.CanvasWidget(widget=devices_tray), hippo.PACK_EXPAND)
+ devices_tray.show()
+
+ return panel
+
+ def _create_right_panel(self):
+ panel = self._create_panel(gtk.POS_RIGHT)
+
+ tray = FriendsTray()
+ panel.append(hippo.CanvasWidget(widget=tray), hippo.PACK_EXPAND)
+ tray.show()
+
+ return panel
+
+ def _create_left_panel(self):
+ panel = ClipboardPanelWindow(self, gtk.POS_LEFT)
+
+ self._connect_to_panel(panel)
+ panel.connect('drag-motion', self._drag_motion_cb)
+ panel.connect('drag-leave', self._drag_leave_cb)
+
+ return panel
+
+ def _create_panel(self, orientation):
+ panel = FrameWindow(orientation)
+ self._connect_to_panel(panel)
+
+ return panel
+
+ def _move_panel(self, panel, pos, x1, y1, x2, y2):
+ x = (x2 - x1) * pos + x1
+ y = (y2 - y1) * pos + y1
+
+ panel.move(int(x), int(y))
+
+ # FIXME we should hide and show as necessary to free memory
+ if not panel.props.visible:
+ panel.show()
+
+ def _connect_to_panel(self, panel):
+ panel.connect('enter-notify-event', self._enter_notify_cb)
+ panel.connect('leave-notify-event', self._leave_notify_cb)
+
+ def _update_position(self):
+ screen_h = gtk.gdk.screen_height()
+ screen_w = gtk.gdk.screen_width()
+
+ self._move_panel(self._top_panel, self.current_position,
+ 0, - self._top_panel.size, 0, 0)
+
+ self._move_panel(self._bottom_panel, self.current_position,
+ 0, screen_h, 0, screen_h - self._bottom_panel.size)
+
+ self._move_panel(self._left_panel, self.current_position,
+ - self._left_panel.size, 0, 0, 0)
+
+ self._move_panel(self._right_panel, self.current_position,
+ screen_w, 0, screen_w - self._right_panel.size, 0)
+
+ def _size_changed_cb(self, screen):
+ self._update_position()
+
+ def _enter_notify_cb(self, window, event):
+ if event.detail != gtk.gdk.NOTIFY_INFERIOR:
+ self._mouse_listener.mouse_enter()
+
+ def _leave_notify_cb(self, window, event):
+ if event.detail == gtk.gdk.NOTIFY_INFERIOR:
+ return
+
+ if not self._is_hover() and not self._palette_group.is_up():
+ self._mouse_listener.mouse_leave()
+
+ def _palette_group_popdown_cb(self, group):
+ if not self._is_hover():
+ self._mouse_listener.mouse_leave()
+
+ def _drag_motion_cb(self, window, context, x, y, time):
+ self._mouse_listener.mouse_enter()
+
+ def _drag_leave_cb(self, window, drag_context, timestamp):
+ self._mouse_listener.mouse_leave()
+
+ def _enter_corner_cb(self, event_area):
+ self._mouse_listener.mouse_enter()
+
+ def notify_key_press(self):
+ self._key_listener.key_press()
+
+ def add_notification(self, icon, corner=gtk.CORNER_TOP_LEFT,
+ duration=_NOTIFICATION_DURATION):
+
+ if not isinstance(icon, NotificationIcon):
+ raise TypeError('icon must be a NotificationIcon.')
+
+ window = NotificationWindow()
+
+ screen = gtk.gdk.screen_get_default()
+ if corner == gtk.CORNER_TOP_LEFT:
+ window.move(0, 0)
+ elif corner == gtk.CORNER_TOP_RIGHT:
+ window.move(screen.get_width() - style.GRID_CELL_SIZE, 0)
+ elif corner == gtk.CORNER_BOTTOM_LEFT:
+ window.move(0, screen.get_height() - style.GRID_CELL_SIZE)
+ elif corner == gtk.CORNER_BOTTOM_RIGHT:
+ window.move(screen.get_width() - style.GRID_CELL_SIZE,
+ screen.get_height() - style.GRID_CELL_SIZE)
+ else:
+ raise ValueError('Inalid corner: %r' % corner)
+
+ window.add(icon)
+ icon.show()
+ window.show()
+
+ self._notif_by_icon[icon] = window
+
+ gobject.timeout_add(duration,
+ lambda: self.remove_notification(icon))
+
+ def remove_notification(self, icon):
+ if icon not in self._notif_by_icon:
+ logging.debug('icon %r not in list of notifications.', icon)
+ return
+
+ window = self._notif_by_icon[icon]
+ window.destroy()
+ del self._notif_by_icon[icon]
+
+ def __notification_received_cb(self, **kwargs):
+ logging.debug('__notification_received_cb')
+ icon = NotificationIcon()
+
+ hints = kwargs['hints']
+
+ icon_file_name = hints.get('x-sugar-icon-file-name', '')
+ if icon_file_name:
+ icon.props.icon_filename = icon_file_name
+ else:
+ icon.props.icon_name = 'application-octet-stream'
+
+ icon_colors = hints.get('x-sugar-icon-colors', '')
+ if not icon_colors:
+ icon_colors = profile.get_color()
+ icon.props.xo_color = icon_colors
+
+ duration = kwargs.get('expire_timeout', -1)
+ if duration == -1:
+ duration = _NOTIFICATION_DURATION
+
+ self.add_notification(icon, gtk.CORNER_TOP_RIGHT, duration)
+
+ def __notification_cancelled_cb(self, **kwargs):
+ # Do nothing for now. Our notification UI is so simple, there's no
+ # point yet.
+ pass
+
diff --git a/shell/src/jarabe/frame/frameinvoker.py b/shell/src/jarabe/frame/frameinvoker.py
new file mode 100644
index 0000000..e4a13e1
--- /dev/null
+++ b/shell/src/jarabe/frame/frameinvoker.py
@@ -0,0 +1,36 @@
+# Copyright (C) 2007, Eduardo Silva <edsiper@gmail.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import gtk
+
+from sugar.graphics import style
+from sugar.graphics.palette import WidgetInvoker
+
+def _get_screen_area():
+ frame_thickness = style.GRID_CELL_SIZE
+
+ x = y = frame_thickness
+ width = gtk.gdk.screen_width() - frame_thickness
+ height = gtk.gdk.screen_height() - frame_thickness
+
+ return gtk.gdk.Rectangle(x, y, width, height)
+
+class FrameWidgetInvoker(WidgetInvoker):
+ def __init__(self, widget):
+ WidgetInvoker.__init__(self, widget, widget.child)
+
+ self._position_hint = self.ANCHORED
+ self._screen_area = _get_screen_area()
diff --git a/shell/src/jarabe/frame/framewindow.py b/shell/src/jarabe/frame/framewindow.py
new file mode 100644
index 0000000..a7d8fe7
--- /dev/null
+++ b/shell/src/jarabe/frame/framewindow.py
@@ -0,0 +1,117 @@
+# Copyright (C) 2006-2007 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import gtk
+import hippo
+
+from sugar.graphics import style
+
+class FrameWindow(gtk.Window):
+ __gtype_name__ = 'SugarFrameWindow'
+
+ def __init__(self, position):
+ gtk.Window.__init__(self)
+ self.hover = False
+ self.size = style.GRID_CELL_SIZE + style.LINE_WIDTH
+
+ accel_group = gtk.AccelGroup()
+ self.set_data('sugar-accel-group', accel_group)
+ self.add_accel_group(accel_group)
+
+ self._position = position
+
+ self.set_decorated(False)
+ self.connect('realize', self._realize_cb)
+ self.connect('enter-notify-event', self._enter_notify_cb)
+ self.connect('leave-notify-event', self._leave_notify_cb)
+
+ self._canvas = hippo.Canvas()
+ self.add(self._canvas)
+ self._canvas.show()
+
+ box = hippo.CanvasBox()
+ self._canvas.set_root(box)
+
+ bg_box = hippo.CanvasBox(
+ border_color=style.COLOR_BUTTON_GREY.get_int())
+ box.append(bg_box, hippo.PACK_EXPAND)
+
+ self._bg = hippo.CanvasBox()
+ bg_box.append(self._bg, hippo.PACK_EXPAND)
+
+ padding = style.GRID_CELL_SIZE
+ border = style.LINE_WIDTH
+
+ if position == gtk.POS_TOP or position == gtk.POS_BOTTOM:
+ box.props.orientation = hippo.ORIENTATION_HORIZONTAL
+ box.props.padding_left = padding
+ box.props.padding_right = padding
+ box.props.padding_top = 0
+ box.props.padding_bottom = 0
+ self._bg.props.orientation = hippo.ORIENTATION_HORIZONTAL
+ self._bg.props.padding_left = border * 2
+ self._bg.props.padding_right = border * 2
+ else:
+ box.props.orientation = hippo.ORIENTATION_VERTICAL
+ box.props.padding_left = 0
+ box.props.padding_right = 0
+ box.props.padding_top = padding
+ box.props.padding_bottom = padding
+ self._bg.props.orientation = hippo.ORIENTATION_VERTICAL
+ self._bg.props.padding_top = border * 2
+ self._bg.props.padding_bottom = border * 2
+
+ if position == gtk.POS_TOP:
+ bg_box.props.orientation = hippo.ORIENTATION_HORIZONTAL
+ bg_box.props.border_bottom = border
+ elif position == gtk.POS_BOTTOM:
+ bg_box.props.orientation = hippo.ORIENTATION_HORIZONTAL
+ bg_box.props.border_top = border
+ elif position == gtk.POS_LEFT:
+ bg_box.props.orientation = hippo.ORIENTATION_VERTICAL
+ bg_box.props.border_right = border
+ elif position == gtk.POS_RIGHT:
+ bg_box.props.orientation = hippo.ORIENTATION_VERTICAL
+ bg_box.props.border_left = border
+
+ self._update_size()
+
+ screen = gtk.gdk.screen_get_default()
+ screen.connect('size-changed', self._size_changed_cb)
+
+ def append(self, child, flags=0):
+ self._bg.append(child, flags)
+
+ def _update_size(self):
+ if self._position == gtk.POS_TOP or self._position == gtk.POS_BOTTOM:
+ self.resize(gtk.gdk.screen_width(), self.size)
+ else:
+ self.resize(self.size, gtk.gdk.screen_height())
+
+ def _realize_cb(self, widget):
+ self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DOCK)
+ self.window.set_accept_focus(False)
+
+ def _enter_notify_cb(self, window, event):
+ if event.detail != gtk.gdk.NOTIFY_INFERIOR:
+ self.hover = True
+
+ def _leave_notify_cb(self, window, event):
+ if event.detail != gtk.gdk.NOTIFY_INFERIOR:
+ self.hover = False
+
+ def _size_changed_cb(self, screen):
+ self._update_size()
diff --git a/shell/src/jarabe/frame/friendstray.py b/shell/src/jarabe/frame/friendstray.py
new file mode 100644
index 0000000..141505b
--- /dev/null
+++ b/shell/src/jarabe/frame/friendstray.py
@@ -0,0 +1,118 @@
+# Copyright (C) 2006-2007 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import logging
+
+from sugar.graphics.tray import VTray, TrayIcon
+
+from jarabe.view.buddymenu import BuddyMenu
+from jarabe.frame.frameinvoker import FrameWidgetInvoker
+from jarabe.model import shell
+from jarabe.model.buddy import get_owner_instance
+from jarabe.model import neighborhood
+
+class FriendIcon(TrayIcon):
+ def __init__(self, buddy):
+ TrayIcon.__init__(self, icon_name='computer-xo',
+ xo_color=buddy.get_color())
+
+ self.set_palette_invoker(FrameWidgetInvoker(self))
+ self.palette = BuddyMenu(buddy)
+ self.palette.props.icon_visible = False
+ self.palette.set_group_id('frame')
+
+class FriendsTray(VTray):
+ def __init__(self):
+ VTray.__init__(self)
+
+ self._shared_activity = None
+ self._buddies = {}
+
+ shell.get_model().connect('active-activity-changed',
+ self.__active_activity_changed_cb)
+
+ neighborhood.get_model().connect('activity-added',
+ self.__neighborhood_activity_added_cb)
+
+ def add_buddy(self, buddy):
+ if self._buddies.has_key(buddy.props.key):
+ return
+
+ icon = FriendIcon(buddy)
+ self.add_item(icon)
+ icon.show()
+
+ self._buddies[buddy.props.key] = icon
+
+ def remove_buddy(self, buddy):
+ if not self._buddies.has_key(buddy.props.key):
+ return
+
+ self.remove_item(self._buddies[buddy.props.key])
+ del self._buddies[buddy.props.key]
+
+ def clear(self):
+ for item in self.get_children():
+ self.remove_item(item)
+ item.destroy()
+ self._buddies = {}
+
+ def __neighborhood_activity_added_cb(self, neighborhood_model,
+ shared_activity):
+ logging.debug('FriendsTray.__neighborhood_activity_added_cb')
+ self.clear()
+
+ # always display ourselves
+ self.add_buddy(get_owner_instance())
+
+ self._set_current_activity(shared_activity.activity_id)
+
+ def __active_activity_changed_cb(self, home_model, home_activity):
+ logging.debug('FriendsTray.__active_activity_changed_cb')
+ self.clear()
+
+ # always display ourselves
+ self.add_buddy(get_owner_instance())
+
+ if home_activity is None:
+ return
+
+ activity_id = home_activity.get_activity_id()
+ if activity_id is None:
+ return
+
+ self._set_current_activity(activity_id)
+
+ def _set_current_activity(self, activity_id):
+ logging.debug('FriendsTray._set_current_activity')
+ neighborhood_model = neighborhood.get_model()
+ self._shared_activity = neighborhood_model.get_activity(activity_id)
+ if self._shared_activity is None:
+ return
+
+ for buddy in self._shared_activity.get_buddies():
+ self.add_buddy(buddy)
+
+ self._shared_activity.connect('buddy-added', self.__buddy_added_cb)
+ self._shared_activity.connect('buddy-removed', self.__buddy_removed_cb)
+
+ def __buddy_added_cb(self, activity, buddy):
+ logging.debug('FriendsTray.__buddy_added_cb')
+ self.add_buddy(buddy)
+
+ def __buddy_removed_cb(self, activity, buddy):
+ logging.debug('FriendsTray.__buddy_removed_cb')
+ self.remove_buddy(buddy)
diff --git a/shell/src/jarabe/frame/notification.py b/shell/src/jarabe/frame/notification.py
new file mode 100644
index 0000000..83dc27e
--- /dev/null
+++ b/shell/src/jarabe/frame/notification.py
@@ -0,0 +1,100 @@
+# 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 gobject
+import gtk
+
+from sugar.graphics import style
+from sugar.graphics.xocolor import XoColor
+
+from jarabe.view.pulsingicon import PulsingIcon
+
+class NotificationIcon(gtk.EventBox):
+ __gtype_name__ = 'SugarNotificationIcon'
+
+ __gproperties__ = {
+ 'xo-color' : (object, None, None, gobject.PARAM_READWRITE),
+ 'icon-name' : (str, None, None, None, gobject.PARAM_READWRITE),
+ 'icon-filename' : (str, None, None, None, gobject.PARAM_READWRITE)
+ }
+
+ _PULSE_TIMEOUT = 3
+
+ def __init__(self, **kwargs):
+ self._icon = PulsingIcon(pixel_size=style.STANDARD_ICON_SIZE)
+ gobject.GObject.__init__(self, **kwargs)
+ self.props.visible_window = False
+
+ self._icon.props.pulse_color = \
+ XoColor('%s,%s' % (style.COLOR_BUTTON_GREY.get_svg(),
+ style.COLOR_TRANSPARENT.get_svg()))
+ self._icon.props.pulsing = True
+ self.add(self._icon)
+ self._icon.show()
+
+ gobject.timeout_add_seconds(self._PULSE_TIMEOUT, self.__stop_pulsing_cb)
+
+ self.set_size_request(style.GRID_CELL_SIZE, style.GRID_CELL_SIZE)
+
+ def __stop_pulsing_cb(self):
+ self._icon.props.pulsing = False
+ return False
+
+ def do_set_property(self, pspec, value):
+ if pspec.name == 'xo-color':
+ if self._icon.props.base_color != value:
+ self._icon.props.base_color = value
+ elif pspec.name == 'icon-name':
+ if self._icon.props.icon_name != value:
+ self._icon.props.icon_name = value
+ elif pspec.name == 'icon-filename':
+ if self._icon.props.file != value:
+ self._icon.props.file = value
+
+ def do_get_property(self, pspec):
+ if pspec.name == 'xo-color':
+ return self._icon.props.base_color
+ elif pspec.name == 'icon-name':
+ return self._icon.props.icon_name
+ elif pspec.name == 'icon-filename':
+ return self._icon.props.file
+
+ def _set_palette(self, palette):
+ self._icon.palette = palette
+
+ def _get_palette(self):
+ return self._icon.palette
+
+ palette = property(_get_palette, _set_palette)
+
+class NotificationWindow(gtk.Window):
+ __gtype_name__ = 'SugarNotificationWindow'
+
+ def __init__(self, **kwargs):
+
+ gtk.Window.__init__(self, **kwargs)
+
+ self.set_decorated(False)
+ self.set_resizable(False)
+ self.connect('realize', self._realize_cb)
+
+ def _realize_cb(self, widget):
+ self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
+ self.window.set_accept_focus(False)
+
+ color = gtk.gdk.color_parse(style.COLOR_TOOLBAR_GREY.get_html())
+ self.modify_bg(gtk.STATE_NORMAL, color)
+
diff --git a/shell/src/jarabe/frame/zoomtoolbar.py b/shell/src/jarabe/frame/zoomtoolbar.py
new file mode 100644
index 0000000..2ed3c54
--- /dev/null
+++ b/shell/src/jarabe/frame/zoomtoolbar.py
@@ -0,0 +1,89 @@
+# Copyright (C) 2006-2007 Red Hat, Inc.
+# Copyright (C) 2009 Simon Schampijer
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from gettext import gettext as _
+import logging
+
+import gtk
+
+from sugar.graphics.palette import Palette
+from sugar.graphics.radiotoolbutton import RadioToolButton
+
+from jarabe.frame.frameinvoker import FrameWidgetInvoker
+from jarabe.model import shell
+
+class ZoomToolbar(gtk.Toolbar):
+ def __init__(self):
+ gtk.Toolbar.__init__(self)
+
+ # we shouldn't be mirrored in RTL locales
+ self.set_direction(gtk.TEXT_DIR_LTR)
+
+ self._mesh_button = self._add_button('zoom-neighborhood',
+ _('Neighborhood'), _('F1'), shell.ShellModel.ZOOM_MESH)
+ self._groups_button = self._add_button('zoom-groups',
+ _('Group'), _('F2'), shell.ShellModel.ZOOM_GROUP)
+ self._home_button = self._add_button('zoom-home',
+ _('Home'), _('F3'), shell.ShellModel.ZOOM_HOME)
+ self._activity_button = self._add_button('zoom-activity',
+ _('Activity'), _('F4'), shell.ShellModel.ZOOM_ACTIVITY)
+
+ shell_model = shell.get_model()
+ self._set_zoom_level(shell_model.zoom_level)
+ shell_model.zoom_level_changed.connect(self.__zoom_level_changed_cb)
+
+ def _add_button(self, icon_name, label, accelerator, zoom_level):
+ if self.get_children():
+ group = self.get_children()[0]
+ else:
+ group = None
+
+ button = RadioToolButton(named_icon=icon_name, group=group,
+ accelerator=accelerator)
+ button.connect('clicked', self.__level_clicked_cb, zoom_level)
+ self.add(button)
+ button.show()
+
+ palette = Palette(label)
+ palette.props.invoker = FrameWidgetInvoker(button)
+ palette.set_group_id('frame')
+ button.set_palette(palette)
+
+ return button
+
+ def __level_clicked_cb(self, button, level):
+ if not button.get_active():
+ return
+
+ shell.get_model().set_zoom_level(level)
+
+ def __zoom_level_changed_cb(self, **kwargs):
+ self._set_zoom_level(kwargs['new_level'])
+
+ def _set_zoom_level(self, new_level):
+ logging.debug('new zoom level: %r', new_level)
+ if new_level == shell.ShellModel.ZOOM_MESH:
+ self._mesh_button.props.active = True
+ elif new_level == shell.ShellModel.ZOOM_GROUP:
+ self._groups_button.props.active = True
+ elif new_level == shell.ShellModel.ZOOM_HOME:
+ self._home_button.props.active = True
+ elif new_level == shell.ShellModel.ZOOM_ACTIVITY:
+ self._activity_button.props.active = True
+ else:
+ raise ValueError('Invalid zoom level: %r' % (new_level))
+
diff --git a/shell/src/jarabe/intro/Makefile.am b/shell/src/jarabe/intro/Makefile.am
new file mode 100644
index 0000000..a9fb96b
--- /dev/null
+++ b/shell/src/jarabe/intro/Makefile.am
@@ -0,0 +1,9 @@
+imagedir = $(pythondir)/jarabe/intro
+image_DATA = default-picture.png
+
+EXTRA_DIST = $(conf_DATA) $(image_DATA)
+sugardir = $(pythondir)/jarabe/intro
+sugar_PYTHON = \
+ __init__.py \
+ colorpicker.py \
+ window.py
diff --git a/shell/src/jarabe/intro/__init__.py b/shell/src/jarabe/intro/__init__.py
new file mode 100644
index 0000000..ca4f64d
--- /dev/null
+++ b/shell/src/jarabe/intro/__init__.py
@@ -0,0 +1,25 @@
+import os
+
+import gtk
+
+from sugar import env
+from sugar.profile import get_profile
+
+from jarabe.intro.window import IntroWindow
+from jarabe.intro.window import create_profile
+
+def check_profile():
+ profile = get_profile()
+
+ path = os.path.join(os.path.expanduser('~/.sugar'), 'debug')
+ if not os.path.exists(path):
+ profile.create_debug_file()
+
+ path = os.path.join(env.get_profile_path(), 'config')
+ if os.path.exists(path):
+ profile.convert_profile()
+
+ if not profile.is_valid():
+ win = IntroWindow()
+ win.show_all()
+ gtk.main()
diff --git a/shell/src/jarabe/intro/colorpicker.py b/shell/src/jarabe/intro/colorpicker.py
new file mode 100644
index 0000000..a939857
--- /dev/null
+++ b/shell/src/jarabe/intro/colorpicker.py
@@ -0,0 +1,43 @@
+# Copyright (C) 2007, Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import hippo
+
+from sugar.graphics.icon import CanvasIcon
+from sugar.graphics import style
+from sugar.graphics.xocolor import XoColor
+
+class ColorPicker(hippo.CanvasBox, hippo.CanvasItem):
+ def __init__(self, **kwargs):
+ hippo.CanvasBox.__init__(self, **kwargs)
+ self.props.orientation = hippo.ORIENTATION_HORIZONTAL
+ self._xo_color = None
+
+ self._xo = CanvasIcon(size=style.XLARGE_ICON_SIZE,
+ icon_name='computer-xo')
+ self._set_random_colors()
+ self._xo.connect('activated', self._xo_activated_cb)
+ self.append(self._xo)
+
+ def _xo_activated_cb(self, item):
+ self._set_random_colors()
+
+ def get_color(self):
+ return self._xo_color
+
+ def _set_random_colors(self):
+ self._xo_color = XoColor()
+ self._xo.props.xo_color = self._xo_color
diff --git a/shell/src/jarabe/intro/default-picture.png b/shell/src/jarabe/intro/default-picture.png
new file mode 100644
index 0000000..e26b9b0
--- /dev/null
+++ b/shell/src/jarabe/intro/default-picture.png
Binary files differ
diff --git a/shell/src/jarabe/intro/window.py b/shell/src/jarabe/intro/window.py
new file mode 100644
index 0000000..35c0cda
--- /dev/null
+++ b/shell/src/jarabe/intro/window.py
@@ -0,0 +1,298 @@
+# Copyright (C) 2007, Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import logging
+from gettext import gettext as _
+import gconf
+import pwd
+
+import gtk
+import gobject
+import hippo
+
+from sugar import env
+from sugar.graphics import style
+from sugar.graphics.icon import Icon
+from sugar.graphics.entry import CanvasEntry
+from sugar.graphics.xocolor import XoColor
+
+from jarabe.intro import colorpicker
+
+_BACKGROUND_COLOR = style.COLOR_WHITE
+
+def create_profile(name, color=None, pixbuf=None):
+ if not pixbuf:
+ path = os.path.join(os.path.dirname(__file__), 'default-picture.png')
+ pixbuf = gtk.gdk.pixbuf_new_from_file(path)
+
+ if not color:
+ color = XoColor()
+
+ icon_path = os.path.join(env.get_profile_path(), "buddy-icon.jpg")
+ pixbuf.save(icon_path, "jpeg", {"quality":"85"})
+
+ client = gconf.client_get_default()
+ client.set_string("/desktop/sugar/user/nick", name)
+ client.set_string("/desktop/sugar/user/color", color.to_string())
+
+ # Generate keypair
+ import commands
+ keypath = os.path.join(env.get_profile_path(), "owner.key")
+ if not os.path.isfile(keypath):
+ cmd = "ssh-keygen -q -t dsa -f %s -C '' -N ''" % keypath
+ (s, o) = commands.getstatusoutput(cmd)
+ if s != 0:
+ logging.error("Could not generate key pair: %d %s", s, o)
+ else:
+ logging.error("Keypair exists, skip generation.")
+
+class _Page(hippo.CanvasBox):
+ __gproperties__ = {
+ 'valid' : (bool, None, None, False,
+ gobject.PARAM_READABLE)
+ }
+
+ def __init__(self, **kwargs):
+ hippo.CanvasBox.__init__(self, **kwargs)
+ self.valid = False
+
+ def set_valid(self, valid):
+ self.valid = valid
+ self.notify('valid')
+
+ def do_get_property(self, pspec):
+ if pspec.name == 'valid':
+ return self.valid
+
+ def activate(self):
+ pass
+
+class _NamePage(_Page):
+ def __init__(self, intro):
+ _Page.__init__(self, xalign=hippo.ALIGNMENT_CENTER,
+ background_color=_BACKGROUND_COLOR.get_int(),
+ spacing=style.DEFAULT_SPACING,
+ orientation=hippo.ORIENTATION_HORIZONTAL,)
+
+ self._intro = intro
+
+ label = hippo.CanvasText(text=_("Name:"))
+ self.append(label)
+
+ self._entry = CanvasEntry(box_width=style.zoom(300))
+ self._entry.set_background(_BACKGROUND_COLOR.get_html())
+ self._entry.connect('notify::text', self._text_changed_cb)
+
+ widget = self._entry.props.widget
+ widget.set_max_length(45)
+
+ self.append(self._entry)
+
+ if gtk.widget_get_default_direction() == gtk.TEXT_DIR_RTL:
+ self.reverse()
+
+ def _text_changed_cb(self, entry, pspec):
+ valid = len(entry.props.text.strip()) > 0
+ self.set_valid(valid)
+
+ def get_name(self):
+ return self._entry.props.text
+
+ def set_name(self, new_name):
+ self._entry.props.text = new_name
+
+ def activate(self):
+ self._entry.props.widget.grab_focus()
+
+class _ColorPage(_Page):
+ def __init__(self, **kwargs):
+ _Page.__init__(self, xalign=hippo.ALIGNMENT_CENTER,
+ background_color=_BACKGROUND_COLOR.get_int(),
+ spacing=style.DEFAULT_SPACING,
+ yalign=hippo.ALIGNMENT_CENTER, **kwargs)
+
+ self._label = hippo.CanvasText(text=_("Click to change color:"),
+ xalign=hippo.ALIGNMENT_CENTER)
+ self.append(self._label)
+
+ self._cp = colorpicker.ColorPicker(xalign=hippo.ALIGNMENT_CENTER)
+ self.append(self._cp)
+
+ self._color = self._cp.get_color()
+ self.set_valid(True)
+
+ def get_color(self):
+ return self._cp.get_color()
+
+class _IntroBox(hippo.CanvasBox):
+ __gsignals__ = {
+ 'done': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT]))
+ }
+
+ PAGE_NAME = 0
+ PAGE_COLOR = 1
+
+ PAGE_FIRST = PAGE_NAME
+ PAGE_LAST = PAGE_COLOR
+
+ def __init__(self):
+ hippo.CanvasBox.__init__(self, padding=style.zoom(30),
+ background_color=_BACKGROUND_COLOR.get_int())
+
+ self._page = self.PAGE_NAME
+ self._name_page = _NamePage(self)
+ self._color_page = _ColorPage()
+ self._current_page = None
+ self._next_button = None
+
+ client = gconf.client_get_default()
+ default_nick = client.get_string('/desktop/sugar/user/default_nick')
+ if default_nick != 'disabled':
+ self._page = self.PAGE_COLOR
+ if default_nick == 'system':
+ pwd_entry = pwd.getpwuid(os.getuid())
+ if pwd_entry.pw_gecos:
+ nick = pwd_entry.pw_gecos.split(',')[0]
+ self._name_page.set_name(nick)
+ else:
+ self._name_page.set_name(pwd_entry.pw_name)
+ else:
+ self._name_page.set_name(default_nick)
+
+ self._setup_page()
+
+ def _setup_page(self):
+ self.remove_all()
+
+ if self._page == self.PAGE_NAME:
+ self._current_page = self._name_page
+ elif self._page == self.PAGE_COLOR:
+ self._current_page = self._color_page
+
+ self.append(self._current_page, hippo.PACK_EXPAND)
+
+ button_box = hippo.CanvasBox(orientation=hippo.ORIENTATION_HORIZONTAL)
+
+ if self._page != self.PAGE_FIRST:
+ back_button = hippo.CanvasButton(text=_('Back'))
+ image = Icon(icon_name='go-left')
+ back_button.props.widget.set_image(image)
+ back_button.connect('activated', self._back_activated_cb)
+ button_box.append(back_button)
+
+ spacer = hippo.CanvasBox()
+ button_box.append(spacer, hippo.PACK_EXPAND)
+
+ self._next_button = hippo.CanvasButton()
+ image = Icon(icon_name='go-right')
+ self._next_button.props.widget.set_image(image)
+
+ if self._page == self.PAGE_LAST:
+ self._next_button.props.text = _('Done')
+ self._next_button.connect('activated', self._done_activated_cb)
+ else:
+ self._next_button.props.text = _('Next')
+ self._next_button.connect('activated', self._next_activated_cb)
+
+ self._current_page.activate()
+
+ self._update_next_button()
+ button_box.append(self._next_button)
+
+ self._current_page.connect('notify::valid',
+ self._page_valid_changed_cb)
+ self.append(button_box)
+
+ if gtk.widget_get_default_direction() == gtk.TEXT_DIR_RTL:
+ button_box.reverse()
+
+ def _update_next_button(self):
+ widget = self._next_button.props.widget
+ widget.props.sensitive = self._current_page.props.valid
+
+ def _page_valid_changed_cb(self, page, pspec):
+ self._update_next_button()
+
+ def _back_activated_cb(self, item):
+ self.back()
+
+ def back(self):
+ if self._page != self.PAGE_FIRST:
+ self._page -= 1
+ self._setup_page()
+
+ def _next_activated_cb(self, item):
+ self.next()
+
+ def next(self):
+ if self._page == self.PAGE_LAST:
+ self.done()
+ if self._current_page.props.valid:
+ self._page += 1
+ self._setup_page()
+
+ def _done_activated_cb(self, item):
+ self.done()
+
+ def done(self):
+ name = self._name_page.get_name()
+ color = self._color_page.get_color()
+
+ self.emit('done', name, color)
+
+class IntroWindow(gtk.Window):
+ def __init__(self):
+ gtk.Window.__init__(self)
+
+ self.props.decorated = False
+ self.maximize()
+
+ self._canvas = hippo.Canvas()
+ self._intro_box = _IntroBox()
+ self._intro_box.connect('done', self._done_cb)
+ self._canvas.set_root(self._intro_box)
+
+ self.add(self._canvas)
+ self._canvas.show()
+ self.connect('key-press-event', self.__key_press_cb)
+
+ def _done_cb(self, box, name, color):
+ self.hide()
+ gobject.idle_add(self._create_profile_cb, name, color)
+
+ def _create_profile_cb(self, name, color):
+ create_profile(name, color)
+ gtk.main_quit()
+
+ return False
+
+ def __key_press_cb(self, widget, event):
+ if gtk.gdk.keyval_name(event.keyval) == "Return":
+ self._intro_box.next()
+ return True
+ elif gtk.gdk.keyval_name(event.keyval) == "Escape":
+ self._intro_box.back()
+ return True
+ return False
+
+
+if __name__ == "__main__":
+ w = IntroWindow()
+ w.show()
+ w.connect('destroy', gtk.main_quit)
+ gtk.main()
diff --git a/shell/src/jarabe/journal/Makefile.am b/shell/src/jarabe/journal/Makefile.am
new file mode 100644
index 0000000..f4bf273
--- /dev/null
+++ b/shell/src/jarabe/journal/Makefile.am
@@ -0,0 +1,17 @@
+sugardir = $(pythondir)/jarabe/journal
+sugar_PYTHON = \
+ __init__.py \
+ detailview.py \
+ expandedentry.py \
+ journalactivity.py \
+ journalentrybundle.py \
+ journaltoolbox.py \
+ keepicon.py \
+ listmodel.py \
+ listview.py \
+ misc.py \
+ modalalert.py \
+ model.py \
+ objectchooser.py \
+ palettes.py \
+ volumestoolbar.py
diff --git a/shell/src/jarabe/journal/__init__.py b/shell/src/jarabe/journal/__init__.py
new file mode 100644
index 0000000..6373228
--- /dev/null
+++ b/shell/src/jarabe/journal/__init__.py
@@ -0,0 +1,15 @@
+# Copyright (C) 2007, 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/shell/src/jarabe/journal/detailview.py b/shell/src/jarabe/journal/detailview.py
new file mode 100644
index 0000000..b4a2339
--- /dev/null
+++ b/shell/src/jarabe/journal/detailview.py
@@ -0,0 +1,117 @@
+# Copyright (C) 2007, One Laptop Per Child
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import logging
+from gettext import gettext as _
+
+import gobject
+import gtk
+import hippo
+
+from sugar.graphics import style
+from sugar.graphics.icon import CanvasIcon
+
+from jarabe.journal.expandedentry import ExpandedEntry
+from jarabe.journal import model
+
+class DetailView(gtk.VBox):
+ __gtype_name__ = 'DetailView'
+
+ __gsignals__ = {
+ 'go-back-clicked': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([]))
+ }
+
+ def __init__(self, **kwargs):
+ self._metadata = None
+ self._expanded_entry = None
+
+ canvas = hippo.Canvas()
+
+ self._root = hippo.CanvasBox()
+ self._root.props.background_color = style.COLOR_PANEL_GREY.get_int()
+ canvas.set_root(self._root)
+
+ back_bar = BackBar()
+ back_bar.connect('button-release-event',
+ self.__back_bar_release_event_cb)
+ self._root.append(back_bar)
+
+ gobject.GObject.__init__(self, **kwargs)
+
+ self.pack_start(canvas)
+ canvas.show()
+
+ def _fav_icon_activated_cb(self, fav_icon):
+ keep = not self._expanded_entry.get_keep()
+ self._expanded_entry.set_keep(keep)
+ fav_icon.props.keep = keep
+
+ def __back_bar_release_event_cb(self, back_bar, event):
+ self.emit('go-back-clicked')
+ return False
+
+ def _update_view(self):
+ if self._expanded_entry is None:
+ self._expanded_entry = ExpandedEntry()
+ self._root.append(self._expanded_entry, hippo.PACK_EXPAND)
+ self._expanded_entry.set_metadata(self._metadata)
+
+ def refresh(self):
+ logging.debug('DetailView.refresh')
+ self._metadata = model.get(self._metadata['uid'])
+ self._update_view()
+
+ def get_metadata(self):
+ return self._metadata
+
+ def set_metadata(self, metadata):
+ self._metadata = metadata
+ self._update_view()
+
+ metadata = gobject.property(
+ type=object, getter=get_metadata, setter=set_metadata)
+
+class BackBar(hippo.CanvasBox):
+ def __init__(self):
+ hippo.CanvasBox.__init__(self,
+ orientation=hippo.ORIENTATION_HORIZONTAL,
+ border=style.LINE_WIDTH,
+ background_color=style.COLOR_PANEL_GREY.get_int(),
+ border_color=style.COLOR_SELECTION_GREY.get_int(),
+ padding=style.DEFAULT_PADDING,
+ padding_left=style.DEFAULT_SPACING,
+ spacing=style.DEFAULT_SPACING)
+
+ icon = CanvasIcon(icon_name='go-previous',
+ size=style.SMALL_ICON_SIZE,
+ fill_color=style.COLOR_TOOLBAR_GREY.get_svg())
+ self.append(icon)
+
+ label = hippo.CanvasText(text=_('Back'),
+ font_desc=style.FONT_NORMAL.get_pango_desc())
+ self.append(label)
+
+ if gtk.widget_get_default_direction() == gtk.TEXT_DIR_RTL:
+ self.reverse()
+
+ self.connect('motion-notify-event', self.__motion_notify_event_cb)
+
+ def __motion_notify_event_cb(self, box, event):
+ if event.detail == hippo.MOTION_DETAIL_ENTER:
+ box.props.background_color = style.COLOR_SELECTION_GREY.get_int()
+ elif event.detail == hippo.MOTION_DETAIL_LEAVE:
+ box.props.background_color = style.COLOR_PANEL_GREY.get_int()
+ return False
diff --git a/shell/src/jarabe/journal/expandedentry.py b/shell/src/jarabe/journal/expandedentry.py
new file mode 100644
index 0000000..c8e40c1
--- /dev/null
+++ b/shell/src/jarabe/journal/expandedentry.py
@@ -0,0 +1,429 @@
+# Copyright (C) 2007, One Laptop Per Child
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import logging
+from gettext import gettext as _
+import StringIO
+import time
+
+import hippo
+import cairo
+import gobject
+import gtk
+import simplejson
+
+from sugar.graphics import style
+from sugar.graphics.icon import CanvasIcon
+from sugar.graphics.xocolor import XoColor
+from sugar.graphics.canvastextview import CanvasTextView
+from sugar.util import format_size
+
+from jarabe.journal.keepicon import KeepIcon
+from jarabe.journal.palettes import ObjectPalette, BuddyPalette
+from jarabe.journal import misc
+from jarabe.journal import model
+
+class Separator(hippo.CanvasBox, hippo.CanvasItem):
+ def __init__(self, orientation):
+ hippo.CanvasBox.__init__(self,
+ background_color=style.COLOR_PANEL_GREY.get_int())
+
+ if orientation == hippo.ORIENTATION_VERTICAL:
+ self.props.box_width = style.LINE_WIDTH
+ else:
+ self.props.box_height = style.LINE_WIDTH
+
+class BuddyList(hippo.CanvasBox):
+ def __init__(self, buddies):
+ hippo.CanvasBox.__init__(self, xalign=hippo.ALIGNMENT_START,
+ orientation=hippo.ORIENTATION_HORIZONTAL)
+
+ for buddy in buddies:
+ nick_, color = buddy
+ hbox = hippo.CanvasBox(orientation=hippo.ORIENTATION_HORIZONTAL)
+ icon = CanvasIcon(icon_name='computer-xo',
+ xo_color=XoColor(color),
+ size=style.STANDARD_ICON_SIZE)
+ icon.set_palette(BuddyPalette(buddy))
+ hbox.append(icon)
+ self.append(hbox)
+
+class ExpandedEntry(hippo.CanvasBox):
+ def __init__(self):
+ hippo.CanvasBox.__init__(self)
+ self.props.orientation = hippo.ORIENTATION_VERTICAL
+ self.props.background_color = style.COLOR_WHITE.get_int()
+ self.props.padding_top = style.DEFAULT_SPACING * 3
+
+ self._metadata = None
+ self._update_title_sid = None
+
+ # Create header
+ header = hippo.CanvasBox(orientation=hippo.ORIENTATION_HORIZONTAL,
+ padding=style.DEFAULT_PADDING,
+ padding_right=style.GRID_CELL_SIZE,
+ spacing=style.DEFAULT_SPACING)
+ self.append(header)
+
+ # Create two column body
+
+ body = hippo.CanvasBox(orientation=hippo.ORIENTATION_HORIZONTAL,
+ spacing=style.DEFAULT_SPACING * 3,
+ padding_left=style.GRID_CELL_SIZE,
+ padding_right=style.GRID_CELL_SIZE,
+ padding_top=style.DEFAULT_SPACING * 3)
+
+ self.append(body, hippo.PACK_EXPAND)
+
+ first_column = hippo.CanvasBox(orientation=hippo.ORIENTATION_VERTICAL,
+ spacing=style.DEFAULT_SPACING)
+ body.append(first_column)
+
+ second_column = hippo.CanvasBox(orientation=hippo.ORIENTATION_VERTICAL,
+ spacing=style.DEFAULT_SPACING)
+ body.append(second_column, hippo.PACK_EXPAND)
+
+ # Header
+
+ self._keep_icon = self._create_keep_icon()
+ header.append(self._keep_icon)
+
+ self._icon = None
+ self._icon_box = hippo.CanvasBox()
+ header.append(self._icon_box)
+
+ self._title = self._create_title()
+ header.append(self._title, hippo.PACK_EXPAND)
+
+ # TODO: create a version list popup instead of a date label
+ self._date = self._create_date()
+ header.append(self._date)
+
+ if gtk.widget_get_default_direction() == gtk.TEXT_DIR_RTL:
+ header.reverse()
+
+ # First column
+
+ self._preview_box = hippo.CanvasBox()
+ first_column.append(self._preview_box)
+
+ self._technical_box = hippo.CanvasBox()
+ first_column.append(self._technical_box)
+
+ # Second column
+
+ description_box, self._description = self._create_description()
+ second_column.append(description_box)
+
+ tags_box, self._tags = self._create_tags()
+ second_column.append(tags_box)
+
+ self._buddy_list = hippo.CanvasBox()
+ second_column.append(self._buddy_list)
+
+ def set_metadata(self, metadata):
+ if self._metadata == metadata:
+ return
+ self._metadata = metadata
+
+ self._keep_icon.keep = (int(metadata.get('keep', 0)) == 1)
+
+ self._icon = self._create_icon()
+ self._icon_box.clear()
+ self._icon_box.append(self._icon)
+
+ self._date.props.text = misc.get_date(metadata)
+
+ title = self._title.props.widget
+ title.props.text = metadata.get('title', _('Untitled'))
+ title.props.editable = model.is_editable(metadata)
+
+ self._preview_box.clear()
+ self._preview_box.append(self._create_preview())
+
+ self._technical_box.clear()
+ self._technical_box.append(self._create_technical())
+
+ self._buddy_list.clear()
+ self._buddy_list.append(self._create_buddy_list())
+
+ description = self._description.text_view_widget
+ description.props.buffer.props.text = metadata.get('description', '')
+ description.props.editable = model.is_editable(metadata)
+
+ tags = self._tags.text_view_widget
+ tags.props.buffer.props.text = metadata.get('tags', '')
+ tags.props.editable = model.is_editable(metadata)
+
+ def _create_keep_icon(self):
+ keep_icon = KeepIcon(False)
+ keep_icon.connect('activated', self._keep_icon_activated_cb)
+ return keep_icon
+
+ def _create_icon(self):
+ icon = CanvasIcon(file_name=misc.get_icon_name(self._metadata))
+ icon.connect_after('button-release-event',
+ self._icon_button_release_event_cb)
+
+ if misc.is_activity_bundle(self._metadata):
+ xo_color = XoColor('%s,%s' % (style.COLOR_BUTTON_GREY.get_svg(),
+ style.COLOR_TRANSPARENT.get_svg()))
+ else:
+ xo_color = misc.get_icon_color(self._metadata)
+ icon.props.xo_color = xo_color
+
+ icon.set_palette(ObjectPalette(self._metadata))
+
+ return icon
+
+ def _create_title(self):
+ entry = gtk.Entry()
+ entry.connect('focus-out-event', self._title_focus_out_event_cb)
+
+ bg_color = style.COLOR_WHITE.get_gdk_color()
+ entry.modify_bg(gtk.STATE_INSENSITIVE, bg_color)
+ entry.modify_base(gtk.STATE_INSENSITIVE, bg_color)
+
+ return hippo.CanvasWidget(widget=entry)
+
+ def _create_date(self):
+ date = hippo.CanvasText(xalign=hippo.ALIGNMENT_START,
+ font_desc=style.FONT_NORMAL.get_pango_desc())
+ return date
+
+ def _create_preview(self):
+ width = style.zoom(320)
+ height = style.zoom(240)
+ box = hippo.CanvasBox()
+
+ if self._metadata.has_key('preview') and \
+ len(self._metadata['preview']) > 4:
+
+ if self._metadata['preview'][1:4] == 'PNG':
+ preview_data = self._metadata['preview']
+ else:
+ # TODO: We are close to be able to drop this.
+ import base64
+ preview_data = base64.b64decode(
+ self._metadata['preview'])
+
+ png_file = StringIO.StringIO(preview_data)
+ try:
+ surface = cairo.ImageSurface.create_from_png(png_file)
+ has_preview = True
+ except Exception:
+ logging.exception('Error while loading the preview')
+ has_preview = False
+ else:
+ has_preview = False
+
+ if has_preview:
+ preview_box = hippo.CanvasImage(image=surface,
+ border=style.LINE_WIDTH,
+ border_color=style.COLOR_BUTTON_GREY.get_int(),
+ xalign=hippo.ALIGNMENT_CENTER,
+ yalign=hippo.ALIGNMENT_CENTER,
+ scale_width=width,
+ scale_height=height)
+ else:
+ preview_box = hippo.CanvasText(text=_('No preview'),
+ font_desc=style.FONT_NORMAL.get_pango_desc(),
+ xalign=hippo.ALIGNMENT_CENTER,
+ yalign=hippo.ALIGNMENT_CENTER,
+ border=style.LINE_WIDTH,
+ border_color=style.COLOR_BUTTON_GREY.get_int(),
+ color=style.COLOR_BUTTON_GREY.get_int(),
+ box_width=width,
+ box_height=height)
+ preview_box.connect_after('button-release-event',
+ self._preview_box_button_release_event_cb)
+ box.append(preview_box)
+ return box
+
+ def _create_technical(self):
+ vbox = hippo.CanvasBox()
+ vbox.props.spacing = style.DEFAULT_SPACING
+
+ lines = [
+ _('Kind: %s') % (self._metadata.get('mime_type') or _('Unknown'),),
+ _('Date: %s') % (self._format_date(),),
+ _('Size: %s') % (format_size(model.get_file_size(
+ self._metadata['uid'])),)]
+
+ for line in lines:
+ text = hippo.CanvasText(text=line,
+ font_desc=style.FONT_NORMAL.get_pango_desc())
+ text.props.color = style.COLOR_BUTTON_GREY.get_int()
+
+ if gtk.widget_get_default_direction() == gtk.TEXT_DIR_RTL:
+ text.props.xalign = hippo.ALIGNMENT_END
+ else:
+ text.props.xalign = hippo.ALIGNMENT_START
+
+ vbox.append(text)
+
+ return vbox
+
+ def _format_date(self):
+ if 'timestamp' in self._metadata:
+ timestamp = float(self._metadata['timestamp'])
+ return time.strftime('%x', time.localtime(timestamp))
+ else:
+ return _('No date')
+
+ def _create_buddy_list(self):
+
+ vbox = hippo.CanvasBox()
+ vbox.props.spacing = style.DEFAULT_SPACING
+
+ text = hippo.CanvasText(text=_('Participants:'),
+ font_desc=style.FONT_NORMAL.get_pango_desc())
+ text.props.color = style.COLOR_BUTTON_GREY.get_int()
+
+ if gtk.widget_get_default_direction() == gtk.TEXT_DIR_RTL:
+ text.props.xalign = hippo.ALIGNMENT_END
+ else:
+ text.props.xalign = hippo.ALIGNMENT_START
+
+ vbox.append(text)
+
+ if self._metadata.has_key('buddies') and \
+ self._metadata['buddies']:
+ buddies = simplejson.loads(self._metadata['buddies']).values()
+ vbox.append(BuddyList(buddies))
+ return vbox
+ else:
+ return vbox
+
+ def _create_description(self):
+ vbox = hippo.CanvasBox()
+ vbox.props.spacing = style.DEFAULT_SPACING
+
+ text = hippo.CanvasText(text=_('Description:'),
+ font_desc=style.FONT_NORMAL.get_pango_desc())
+ text.props.color = style.COLOR_BUTTON_GREY.get_int()
+
+ if gtk.widget_get_default_direction() == gtk.TEXT_DIR_RTL:
+ text.props.xalign = hippo.ALIGNMENT_END
+ else:
+ text.props.xalign = hippo.ALIGNMENT_START
+
+ vbox.append(text)
+
+ text_view = CanvasTextView('',
+ box_height=style.GRID_CELL_SIZE * 2)
+ vbox.append(text_view, hippo.PACK_EXPAND)
+
+ text_view.text_view_widget.props.accepts_tab = False
+ text_view.text_view_widget.connect('focus-out-event',
+ self._description_focus_out_event_cb)
+
+ return vbox, text_view
+
+ def _create_tags(self):
+ vbox = hippo.CanvasBox()
+ vbox.props.spacing = style.DEFAULT_SPACING
+
+ text = hippo.CanvasText(text=_('Tags:'),
+ font_desc=style.FONT_NORMAL.get_pango_desc())
+ text.props.color = style.COLOR_BUTTON_GREY.get_int()
+
+ if gtk.widget_get_default_direction() == gtk.TEXT_DIR_RTL:
+ text.props.xalign = hippo.ALIGNMENT_END
+ else:
+ text.props.xalign = hippo.ALIGNMENT_START
+
+ vbox.append(text)
+
+ text_view = CanvasTextView('',
+ box_height=style.GRID_CELL_SIZE * 2)
+ vbox.append(text_view, hippo.PACK_EXPAND)
+
+ text_view.text_view_widget.props.accepts_tab = False
+ text_view.text_view_widget.connect('focus-out-event',
+ self._tags_focus_out_event_cb)
+
+ return vbox, text_view
+
+ def _title_notify_text_cb(self, entry, pspec):
+ if not self._update_title_sid:
+ self._update_title_sid = gobject.timeout_add_seconds(1,
+ self._update_title_cb)
+
+ def _title_focus_out_event_cb(self, entry, event):
+ self._update_entry()
+
+ def _description_focus_out_event_cb(self, text_view, event):
+ self._update_entry()
+
+ def _tags_focus_out_event_cb(self, text_view, event):
+ self._update_entry()
+
+ def _update_entry(self):
+ if not model.is_editable(self._metadata):
+ return
+
+ needs_update = False
+
+ old_title = self._metadata.get('title', None)
+ new_title = self._title.props.widget.props.text
+ if old_title != new_title:
+ self._icon.palette.props.primary_text = new_title
+ self._metadata['title'] = new_title
+ self._metadata['title_set_by_user'] = '1'
+ needs_update = True
+
+ old_tags = self._metadata.get('tags', None)
+ new_tags = self._tags.text_view_widget.props.buffer.props.text
+ if old_tags != new_tags:
+ self._metadata['tags'] = new_tags
+ needs_update = True
+
+ old_description = self._metadata.get('description', None)
+ new_description = \
+ self._description.text_view_widget.props.buffer.props.text
+ if old_description != new_description:
+ self._metadata['description'] = new_description
+ needs_update = True
+
+ if needs_update:
+ model.write(self._metadata, update_mtime=False)
+
+ self._update_title_sid = None
+
+ def get_keep(self):
+ return int(self._metadata.get('keep', 0)) == 1
+
+ def _keep_icon_activated_cb(self, keep_icon):
+ if not model.is_editable(self._metadata):
+ return
+ if self.get_keep():
+ self._metadata['keep'] = 0
+ else:
+ self._metadata['keep'] = 1
+ model.write(self._metadata, update_mtime=False)
+
+ keep_icon.props.keep = self.get_keep()
+
+ def _icon_button_release_event_cb(self, button, event):
+ logging.debug('_icon_button_release_event_cb')
+ misc.resume(self._metadata)
+ return True
+
+ def _preview_box_button_release_event_cb(self, button, event):
+ logging.debug('_preview_box_button_release_event_cb')
+ misc.resume(self._metadata)
+ return True
diff --git a/shell/src/jarabe/journal/journalactivity.py b/shell/src/jarabe/journal/journalactivity.py
new file mode 100644
index 0000000..e278420
--- /dev/null
+++ b/shell/src/jarabe/journal/journalactivity.py
@@ -0,0 +1,371 @@
+# Copyright (C) 2006, Red Hat, Inc.
+# Copyright (C) 2007, One Laptop Per Child
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import logging
+from gettext import gettext as _
+import sys
+import traceback
+import uuid
+
+import gtk
+import dbus
+import statvfs
+import os
+
+from sugar.graphics.window import Window
+from sugar.graphics.alert import ErrorAlert
+
+from sugar.bundle.bundle import ZipExtractException, RegistrationException
+from sugar import env
+from sugar.activity import activityfactory
+from sugar import wm
+
+from jarabe.model import bundleregistry
+from jarabe.journal.journaltoolbox import MainToolbox, DetailToolbox
+from jarabe.journal.listview import ListView
+from jarabe.journal.detailview import DetailView
+from jarabe.journal.volumestoolbar import VolumesToolbar
+from jarabe.journal import misc
+from jarabe.journal.journalentrybundle import JournalEntryBundle
+from jarabe.journal.objectchooser import ObjectChooser
+from jarabe.journal.modalalert import ModalAlert
+from jarabe.journal import model
+
+J_DBUS_SERVICE = 'org.laptop.Journal'
+J_DBUS_INTERFACE = 'org.laptop.Journal'
+J_DBUS_PATH = '/org/laptop/Journal'
+
+_SPACE_TRESHOLD = 52428800
+_BUNDLE_ID = 'org.laptop.JournalActivity'
+
+class JournalActivityDBusService(dbus.service.Object):
+ def __init__(self, parent):
+ self._parent = parent
+ session_bus = dbus.SessionBus()
+ bus_name = dbus.service.BusName(J_DBUS_SERVICE,
+ bus=session_bus, replace_existing=False, allow_replacement=False)
+ logging.debug('bus_name: %r', bus_name)
+ dbus.service.Object.__init__(self, bus_name, J_DBUS_PATH)
+
+ @dbus.service.method(J_DBUS_INTERFACE,
+ in_signature='s', out_signature='')
+ def ShowObject(self, object_id):
+ """Pop-up journal and show object with object_id"""
+
+ logging.debug('Trying to show object %s', object_id)
+
+ if self._parent.show_object(object_id):
+ self._parent.reveal()
+
+ def _chooser_response_cb(self, chooser, response_id, chooser_id):
+ logging.debug('JournalActivityDBusService._chooser_response_cb')
+ if response_id == gtk.RESPONSE_ACCEPT:
+ object_id = chooser.get_selected_object_id()
+ self.ObjectChooserResponse(chooser_id, object_id)
+ else:
+ self.ObjectChooserCancelled(chooser_id)
+ chooser.destroy()
+ del chooser
+
+ @dbus.service.method(J_DBUS_INTERFACE, in_signature='is', out_signature='s')
+ def ChooseObject(self, parent_xid, what_filter=''):
+ chooser_id = uuid.uuid4().hex
+ if parent_xid > 0:
+ parent = gtk.gdk.window_foreign_new(parent_xid)
+ else:
+ parent = None
+ chooser = ObjectChooser(parent, what_filter)
+ chooser.connect('response', self._chooser_response_cb, chooser_id)
+ chooser.show()
+
+ return chooser_id
+
+ @dbus.service.signal(J_DBUS_INTERFACE, signature="ss")
+ def ObjectChooserResponse(self, chooser_id, object_id):
+ pass
+
+ @dbus.service.signal(J_DBUS_INTERFACE, signature="s")
+ def ObjectChooserCancelled(self, chooser_id):
+ pass
+
+class JournalActivity(Window):
+ def __init__(self):
+ logging.debug("STARTUP: Loading the journal")
+ Window.__init__(self)
+
+ self.set_title(_('Journal'))
+
+ self._main_view = None
+ self._secondary_view = None
+ self._list_view = None
+ self._detail_view = None
+ self._main_toolbox = None
+ self._detail_toolbox = None
+ self._volumes_toolbar = None
+
+ self._setup_main_view()
+ self._setup_secondary_view()
+
+ self.add_events(gtk.gdk.ALL_EVENTS_MASK |
+ gtk.gdk.VISIBILITY_NOTIFY_MASK)
+ self._realized_sid = self.connect('realize', self.__realize_cb)
+ self.connect('visibility-notify-event',
+ self.__visibility_notify_event_cb)
+ self.connect('window-state-event', self.__window_state_event_cb)
+ self.connect('key-press-event', self._key_press_event_cb)
+ self.connect('focus-in-event', self._focus_in_event_cb)
+
+ model.created.connect(self.__model_created_cb)
+ model.updated.connect(self.__model_updated_cb)
+ model.deleted.connect(self.__model_deleted_cb)
+
+ self._dbus_service = JournalActivityDBusService(self)
+
+ self.iconify()
+
+ self._critical_space_alert = None
+ self._check_available_space()
+
+ def __alert_notify_cb(self, gobject, strerror, severity):
+ alert = ErrorAlert(title=severity, msg=strerror)
+ alert.connect('response', self.__alert_response_cb)
+ self.add_alert(alert)
+ alert.show()
+
+ def __alert_response_cb(self, alert, response_id):
+ self.remove_alert(alert)
+
+ def __realize_cb(self, window):
+ wm.set_bundle_id(window.window, _BUNDLE_ID)
+ activity_id = activityfactory.create_activity_id()
+ wm.set_activity_id(window.window, str(activity_id))
+ self.disconnect(self._realized_sid)
+ self._realized_sid = None
+
+ def can_close(self):
+ return False
+
+ def _setup_main_view(self):
+ self._main_toolbox = MainToolbox()
+ self._main_view = gtk.VBox()
+
+ self._list_view = ListView()
+ self._list_view.connect('detail-clicked', self.__detail_clicked_cb)
+ self._list_view.connect('clear-clicked', self.__clear_clicked_cb)
+ self._main_view.pack_start(self._list_view)
+ self._list_view.show()
+
+ self._volumes_toolbar = VolumesToolbar()
+ self._volumes_toolbar.connect('volume-changed',
+ self.__volume_changed_cb)
+ self._volumes_toolbar.connect('volume-error', self.__alert_notify_cb)
+ self._main_view.pack_start(self._volumes_toolbar, expand=False)
+
+ search_toolbar = self._main_toolbox.search_toolbar
+ search_toolbar.connect('query-changed', self._query_changed_cb)
+ search_toolbar.set_mount_point('/')
+
+ def _setup_secondary_view(self):
+ self._secondary_view = gtk.VBox()
+
+ self._detail_toolbox = DetailToolbox()
+ entry_toolbar = self._detail_toolbox.entry_toolbar
+
+ self._detail_view = DetailView()
+ self._detail_view.connect('go-back-clicked', self.__go_back_clicked_cb)
+ self._secondary_view.pack_end(self._detail_view)
+ self._detail_view.show()
+
+ def _key_press_event_cb(self, widget, event):
+ keyname = gtk.gdk.keyval_name(event.keyval)
+ if keyname == 'Escape':
+ self.show_main_view()
+
+ def __detail_clicked_cb(self, list_view, object_id):
+ self._show_secondary_view(object_id)
+
+ def __clear_clicked_cb(self, list_view):
+ self._main_toolbox.search_toolbar.clear_query()
+
+ def __go_back_clicked_cb(self, detail_view):
+ self.show_main_view()
+
+ def _query_changed_cb(self, toolbar, query):
+ self._list_view.update_with_query(query)
+ self.show_main_view()
+
+ def show_main_view(self):
+ if self.toolbar_box != self._main_toolbox:
+ self.set_toolbar_box(self._main_toolbox)
+ self._main_toolbox.show()
+
+ if self.canvas != self._main_view:
+ self.set_canvas(self._main_view)
+ self._main_view.show()
+
+ def _show_secondary_view(self, object_id):
+ metadata = model.get(object_id)
+ try:
+ self._detail_toolbox.entry_toolbar.set_metadata(metadata)
+ except Exception:
+ logging.error('Exception while displaying entry:\n' + \
+ ''.join(traceback.format_exception(*sys.exc_info())))
+
+ self.set_toolbar_box(self._detail_toolbox)
+ self._detail_toolbox.show()
+
+ try:
+ self._detail_view.props.metadata = metadata
+ except Exception:
+ logging.error('Exception while displaying entry:\n' + \
+ ''.join(traceback.format_exception(*sys.exc_info())))
+
+ self.set_canvas(self._secondary_view)
+ self._secondary_view.show()
+
+ def show_object(self, object_id):
+ metadata = model.get(object_id)
+ if metadata is None:
+ return False
+ else:
+ self._show_secondary_view(object_id)
+ return True
+
+ def __volume_changed_cb(self, volume_toolbar, mount_point):
+ logging.debug('Selected volume: %r.', mount_point)
+ self._main_toolbox.search_toolbar.set_mount_point(mount_point)
+ self._main_toolbox.set_current_toolbar(0)
+
+ def __model_created_cb(self, sender, **kwargs):
+ self._check_for_bundle(kwargs['object_id'])
+ self._main_toolbox.search_toolbar.refresh_filters()
+ self._check_available_space()
+
+ def __model_updated_cb(self, sender, **kwargs):
+ self._check_for_bundle(kwargs['object_id'])
+
+ if self.canvas == self._secondary_view and \
+ kwargs['object_id'] == self._detail_view.props.metadata['uid']:
+ self._detail_view.refresh()
+
+ self._check_available_space()
+
+ def __model_deleted_cb(self, sender, **kwargs):
+ if self.canvas == self._secondary_view and \
+ kwargs['object_id'] == self._detail_view.props.metadata['uid']:
+ self.show_main_view()
+
+ def _focus_in_event_cb(self, window, event):
+ self.search_grab_focus()
+ self._list_view.update_dates()
+
+ def _check_for_bundle(self, object_id):
+ registry = bundleregistry.get_registry()
+
+ metadata = model.get(object_id)
+ if metadata.get('progress', '').isdigit():
+ if int(metadata['progress']) < 100:
+ return
+
+ bundle = misc.get_bundle(metadata)
+ if bundle is None:
+ return
+
+ if registry.is_installed(bundle):
+ logging.debug('_check_for_bundle bundle already installed')
+ return
+
+ if metadata['mime_type'] == JournalEntryBundle.MIME_TYPE:
+ # JournalEntryBundle code takes over the datastore entry and
+ # transforms it into the journal entry from the bundle -- we have
+ # nothing more to do.
+ try:
+ registry.install(bundle, metadata['uid'])
+ except (ZipExtractException, RegistrationException):
+ logging.exception('Could not install bundle %s',
+ bundle.get_path())
+ return
+
+ try:
+ registry.install(bundle)
+ except (ZipExtractException, RegistrationException):
+ logging.exception('Could not install bundle %s', bundle.get_path())
+ return
+
+ metadata['bundle_id'] = bundle.get_bundle_id()
+ model.write(metadata)
+
+ def search_grab_focus(self):
+ search_toolbar = self._main_toolbox.search_toolbar
+ search_toolbar.give_entry_focus()
+
+ def __window_state_event_cb(self, window, event):
+ logging.debug('window_state_event_cb %r', self)
+ if event.changed_mask & gtk.gdk.WINDOW_STATE_ICONIFIED:
+ state = event.new_window_state
+ visible = not state & gtk.gdk.WINDOW_STATE_ICONIFIED
+ self._list_view.set_is_visible(visible)
+
+ def __visibility_notify_event_cb(self, window, event):
+ logging.debug('visibility_notify_event_cb %r', self)
+ visible = event.state != gtk.gdk.VISIBILITY_FULLY_OBSCURED
+ self._list_view.set_is_visible(visible)
+
+ def _check_available_space(self):
+ ''' Check available space on device
+
+ If the available space is below 50MB an alert will be
+ shown which encourages to delete old journal entries.
+ '''
+
+ if self._critical_space_alert:
+ return
+ stat = os.statvfs(env.get_profile_path())
+ free_space = stat[statvfs.F_BSIZE] * stat[statvfs.F_BAVAIL]
+ if free_space < _SPACE_TRESHOLD:
+ self._critical_space_alert = ModalAlert()
+ self._critical_space_alert.connect('destroy',
+ self.__alert_closed_cb)
+ self._critical_space_alert.show()
+
+ def __alert_closed_cb(self, data):
+ self.show_main_view()
+ self.reveal()
+ self._critical_space_alert = None
+
+ def set_active_volume(self, mount):
+ self._volumes_toolbar.set_active_volume(mount)
+
+ def focus_search(self):
+ """Become visible and give focus to the search entry
+ """
+ self.reveal()
+ self.show_main_view()
+ self.search_grab_focus()
+
+_journal = None
+
+def get_journal():
+ global _journal
+ if _journal is None:
+ _journal = JournalActivity()
+ _journal.show()
+ return _journal
+
+def start():
+ get_journal()
+
diff --git a/shell/src/jarabe/journal/journalentrybundle.py b/shell/src/jarabe/journal/journalentrybundle.py
new file mode 100644
index 0000000..41777c7
--- /dev/null
+++ b/shell/src/jarabe/journal/journalentrybundle.py
@@ -0,0 +1,94 @@
+# Copyright (C) 2007, One Laptop Per Child
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import tempfile
+import shutil
+
+import simplejson
+import dbus
+
+from sugar.bundle.bundle import Bundle, MalformedBundleException
+
+from jarabe.journal import model
+
+class JournalEntryBundle(Bundle):
+ """A Journal entry bundle
+
+ See http://wiki.laptop.org/go/Journal_entry_bundles for details
+ """
+
+ MIME_TYPE = 'application/vnd.olpc-journal-entry'
+
+ _zipped_extension = '.xoj'
+ _unzipped_extension = None
+ _infodir = None
+
+ def __init__(self, path):
+ Bundle.__init__(self, path)
+
+ def install(self, uid=''):
+ if os.environ.has_key('SUGAR_ACTIVITY_ROOT'):
+ install_dir = os.path.join(os.environ['SUGAR_ACTIVITY_ROOT'],
+ 'data')
+ else:
+ install_dir = tempfile.gettempdir()
+ bundle_dir = os.path.join(install_dir, self._zip_root_dir)
+ temp_uid = self._zip_root_dir
+ self._unzip(install_dir)
+ try:
+ metadata = self._read_metadata(bundle_dir)
+ metadata['uid'] = uid
+
+ preview = self._read_preview(temp_uid, bundle_dir)
+ if preview is not None:
+ metadata['preview'] = dbus.ByteArray(preview)
+
+ file_path = os.path.join(bundle_dir, temp_uid)
+ model.write(metadata, file_path)
+ finally:
+ shutil.rmtree(bundle_dir, ignore_errors=True)
+
+ def get_bundle_id(self):
+ return None
+
+ def _read_metadata(self, bundle_dir):
+ metadata_path = os.path.join(bundle_dir, '_metadata.json')
+ if not os.path.exists(metadata_path):
+ raise MalformedBundleException(
+ 'Bundle must contain the file "_metadata.json"')
+ f = open(metadata_path, 'r')
+ try:
+ json_data = f.read()
+ finally:
+ f.close()
+ return simplejson.loads(json_data)
+
+ def _read_preview(self, uid, bundle_dir):
+ preview_path = os.path.join(bundle_dir, 'preview', uid)
+ if not os.path.exists(preview_path):
+ return ''
+ f = open(preview_path, 'r')
+ try:
+ preview_data = f.read()
+ finally:
+ f.close()
+ return preview_data
+
+ def is_installed(self):
+ # These bundles can be reinstalled as many times as desired.
+ return False
+
diff --git a/shell/src/jarabe/journal/journaltoolbox.py b/shell/src/jarabe/journal/journaltoolbox.py
new file mode 100644
index 0000000..61671bc
--- /dev/null
+++ b/shell/src/jarabe/journal/journaltoolbox.py
@@ -0,0 +1,458 @@
+# Copyright (C) 2007, One Laptop Per Child
+# Copyright (C) 2009, Walter Bender
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from gettext import gettext as _
+import logging
+from datetime import datetime, timedelta
+import os
+import gconf
+import time
+
+import gobject
+import gio
+import gtk
+
+from sugar.graphics.toolbox import Toolbox
+from sugar.graphics.toolcombobox import ToolComboBox
+from sugar.graphics.toolbutton import ToolButton
+from sugar.graphics.toggletoolbutton import ToggleToolButton
+from sugar.graphics.combobox import ComboBox
+from sugar.graphics.menuitem import MenuItem
+from sugar.graphics.icon import Icon
+from sugar.graphics.xocolor import XoColor
+from sugar.graphics import iconentry
+from sugar.graphics import style
+from sugar import mime
+from sugar import profile
+
+from jarabe.model import bundleregistry
+from jarabe.journal import misc
+from jarabe.journal import model
+
+_AUTOSEARCH_TIMEOUT = 1000
+
+_ACTION_ANYTIME = 0
+_ACTION_TODAY = 1
+_ACTION_SINCE_YESTERDAY = 2
+_ACTION_PAST_WEEK = 3
+_ACTION_PAST_MONTH = 4
+_ACTION_PAST_YEAR = 5
+
+_ACTION_ANYTHING = 0
+
+_ACTION_EVERYBODY = 0
+_ACTION_MY_FRIENDS = 1
+_ACTION_MY_CLASS = 2
+
+
+class MainToolbox(Toolbox):
+ def __init__(self):
+ Toolbox.__init__(self)
+
+ self.search_toolbar = SearchToolbar()
+ self.search_toolbar.set_size_request(-1, style.GRID_CELL_SIZE)
+ self.add_toolbar(_('Search'), self.search_toolbar)
+ self.search_toolbar.show()
+
+class SearchToolbar(gtk.Toolbar):
+ __gtype_name__ = 'SearchToolbar'
+
+ __gsignals__ = {
+ 'query-changed': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([object]))
+ }
+
+ def __init__(self):
+ gtk.Toolbar.__init__(self)
+
+ self._mount_point = None
+
+ self._search_entry = iconentry.IconEntry()
+ self._search_entry.set_icon_from_name(iconentry.ICON_ENTRY_PRIMARY,
+ 'system-search')
+ self._search_entry.connect('activate', self._search_entry_activated_cb)
+ self._search_entry.connect('changed', self._search_entry_changed_cb)
+ self._search_entry.add_clear_button()
+ self._autosearch_timer = None
+ self._add_widget(self._search_entry, expand=True)
+
+ self._favorite_button = ToggleToolButton('emblem-favorite')
+ self._favorite_button.connect('toggled',
+ self.__favorite_button_toggled_cb)
+ self.insert(self._favorite_button, -1)
+ self._favorite_button.show()
+
+ self._what_search_combo = ComboBox()
+ self._what_combo_changed_sid = self._what_search_combo.connect(
+ 'changed', self._combo_changed_cb)
+ tool_item = ToolComboBox(self._what_search_combo)
+ self.insert(tool_item, -1)
+ tool_item.show()
+
+ self._when_search_combo = self._get_when_search_combo()
+ tool_item = ToolComboBox(self._when_search_combo)
+ self.insert(tool_item, -1)
+ tool_item.show()
+
+ # TODO: enable it when the DS supports saving the buddies.
+ #self._with_search_combo = self._get_with_search_combo()
+ #tool_item = ToolComboBox(self._with_search_combo)
+ #self.insert(tool_item, -1)
+ #tool_item.show()
+
+ self._query = self._build_query()
+
+ self.refresh_filters()
+
+ def give_entry_focus(self):
+ self._search_entry.grab_focus()
+
+ def _get_when_search_combo(self):
+ when_search = ComboBox()
+ when_search.append_item(_ACTION_ANYTIME, _('Anytime'))
+ when_search.append_separator()
+ when_search.append_item(_ACTION_TODAY, _('Today'))
+ when_search.append_item(_ACTION_SINCE_YESTERDAY,
+ _('Since yesterday'))
+ # TRANS: Filter entries modified during the last 7 days.
+ when_search.append_item(_ACTION_PAST_WEEK, _('Past week'))
+ # TRANS: Filter entries modified during the last 30 days.
+ when_search.append_item(_ACTION_PAST_MONTH, _('Past month'))
+ # TRANS: Filter entries modified during the last 356 days.
+ when_search.append_item(_ACTION_PAST_YEAR, _('Past year'))
+ when_search.set_active(0)
+ when_search.connect('changed', self._combo_changed_cb)
+ return when_search
+
+ def _get_with_search_combo(self):
+ with_search = ComboBox()
+ with_search.append_item(_ACTION_EVERYBODY, _('Anyone'))
+ with_search.append_separator()
+ with_search.append_item(_ACTION_MY_FRIENDS, _('My friends'))
+ with_search.append_item(_ACTION_MY_CLASS, _('My class'))
+ with_search.append_separator()
+
+ # TODO: Ask the model for buddies.
+ with_search.append_item(3, 'Dan', 'theme:xo')
+
+ with_search.set_active(0)
+ with_search.connect('changed', self._combo_changed_cb)
+ return with_search
+
+ 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 _build_query(self):
+ query = {}
+
+ if self._mount_point:
+ query['mountpoints'] = [self._mount_point]
+
+ if self._favorite_button.props.active:
+ query['keep'] = 1
+
+ if self._what_search_combo.props.value:
+ value = self._what_search_combo.props.value
+ generic_type = mime.get_generic_type(value)
+ if generic_type:
+ mime_types = generic_type.mime_types
+ query['mime_type'] = mime_types
+ else:
+ query['activity'] = self._what_search_combo.props.value
+
+ if self._when_search_combo.props.value:
+ date_from, date_to = self._get_date_range()
+ query['timestamp'] = {'start': date_from, 'end': date_to}
+
+ if self._search_entry.props.text:
+ text = self._search_entry.props.text.strip()
+ if text:
+ query['query'] = text
+
+ return query
+
+ def _get_date_range(self):
+ today_start = datetime.today().replace(hour=0, minute=0, second=0)
+ right_now = datetime.today()
+ if self._when_search_combo.props.value == _ACTION_TODAY:
+ date_range = (today_start, right_now)
+ elif self._when_search_combo.props.value == _ACTION_SINCE_YESTERDAY:
+ date_range = (today_start - timedelta(1), right_now)
+ elif self._when_search_combo.props.value == _ACTION_PAST_WEEK:
+ date_range = (today_start - timedelta(7), right_now)
+ elif self._when_search_combo.props.value == _ACTION_PAST_MONTH:
+ date_range = (today_start - timedelta(30), right_now)
+ elif self._when_search_combo.props.value == _ACTION_PAST_YEAR:
+ date_range = (today_start - timedelta(356), right_now)
+
+ return (time.mktime(date_range[0].timetuple()),
+ time.mktime(date_range[1].timetuple()))
+
+ def _combo_changed_cb(self, combo):
+ self._update_if_needed()
+
+ def _update_if_needed(self):
+ new_query = self._build_query()
+ if self._query != new_query:
+ self._query = new_query
+ self.emit('query-changed', self._query)
+
+ def _search_entry_activated_cb(self, search_entry):
+ if self._autosearch_timer:
+ gobject.source_remove(self._autosearch_timer)
+ new_query = self._build_query()
+ if self._query != new_query:
+ self._query = new_query
+ self.emit('query-changed', self._query)
+
+ def _search_entry_changed_cb(self, search_entry):
+ if not search_entry.props.text:
+ search_entry.activate()
+ return
+
+ if self._autosearch_timer:
+ gobject.source_remove(self._autosearch_timer)
+ self._autosearch_timer = gobject.timeout_add(_AUTOSEARCH_TIMEOUT,
+ self._autosearch_timer_cb)
+
+ def _autosearch_timer_cb(self):
+ logging.debug('_autosearch_timer_cb')
+ self._autosearch_timer = None
+ self._search_entry.activate()
+ return False
+
+ def set_mount_point(self, mount_point):
+ self._mount_point = mount_point
+ new_query = self._build_query()
+ if self._query != new_query:
+ self._query = new_query
+ self.emit('query-changed', self._query)
+
+ def set_what_filter(self, what_filter):
+ combo_model = self._what_search_combo.get_model()
+ what_filter_index = -1
+ for i in range(0, len(combo_model) - 1):
+ if combo_model[i][0] == what_filter:
+ what_filter_index = i
+ break
+
+ if what_filter_index == -1:
+ logging.warning('what_filter %r not known', what_filter)
+ else:
+ self._what_search_combo.set_active(what_filter_index)
+
+ def refresh_filters(self):
+ current_value = self._what_search_combo.props.value
+ current_value_index = 0
+
+ self._what_search_combo.handler_block(self._what_combo_changed_sid)
+ try:
+ self._what_search_combo.remove_all()
+ # TRANS: Item in a combo box that filters by entry type.
+ self._what_search_combo.append_item(_ACTION_ANYTHING,
+ _('Anything'))
+
+ registry = bundleregistry.get_registry()
+ appended_separator = False
+
+ types = mime.get_all_generic_types()
+ for generic_type in types:
+ if not appended_separator:
+ self._what_search_combo.append_separator()
+ appended_separator = True
+ self._what_search_combo.append_item(
+ generic_type.type_id, generic_type.name, generic_type.icon)
+ if generic_type.type_id == current_value:
+ current_value_index = \
+ len(self._what_search_combo.get_model()) - 1
+
+ self._what_search_combo.set_active(current_value_index)
+
+ self._what_search_combo.append_separator()
+
+ for service_name in model.get_unique_values('activity'):
+ activity_info = registry.get_bundle(service_name)
+ if not activity_info is None:
+ if os.path.exists(activity_info.get_icon()):
+ self._what_search_combo.append_item(service_name,
+ activity_info.get_name(),
+ file_name=activity_info.get_icon())
+ else:
+ self._what_search_combo.append_item(service_name,
+ activity_info.get_name(),
+ icon_name='application-octet-stream')
+
+ if service_name == current_value:
+ current_value_index = \
+ len(self._what_search_combo.get_model()) - 1
+ finally:
+ self._what_search_combo.handler_unblock(
+ self._what_combo_changed_sid)
+
+ def __favorite_button_toggled_cb(self, favorite_button):
+ self._update_if_needed()
+
+ def clear_query(self):
+ self._search_entry.props.text = ''
+ self._what_search_combo.set_active(0)
+ self._when_search_combo.set_active(0)
+ self._favorite_button.props.active = False
+
+class DetailToolbox(Toolbox):
+ def __init__(self):
+ Toolbox.__init__(self)
+
+ self.entry_toolbar = EntryToolbar()
+ self.add_toolbar('', self.entry_toolbar)
+ self.entry_toolbar.show()
+
+class EntryToolbar(gtk.Toolbar):
+ def __init__(self):
+ gtk.Toolbar.__init__(self)
+
+ self._metadata = None
+ self._temp_file_path = None
+
+ self._resume = ToolButton('activity-start')
+ self._resume.connect('clicked', self._resume_clicked_cb)
+ self.add(self._resume)
+ self._resume.show()
+
+ self._copy = ToolButton()
+
+ client = gconf.client_get_default()
+ color = XoColor(client.get_string('/desktop/sugar/user/color'))
+ icon = Icon(icon_name='edit-copy', xo_color=color)
+ self._copy.set_icon_widget(icon)
+ icon.show()
+
+ self._copy.set_tooltip(_('Copy'))
+ self._copy.connect('clicked', self._copy_clicked_cb)
+ self.add(self._copy)
+ self._copy.show()
+
+ separator = gtk.SeparatorToolItem()
+ self.add(separator)
+ separator.show()
+
+ erase_button = ToolButton('list-remove')
+ erase_button.set_tooltip(_('Erase'))
+ erase_button.connect('clicked', self._erase_button_clicked_cb)
+ self.add(erase_button)
+ erase_button.show()
+
+ def set_metadata(self, metadata):
+ self._metadata = metadata
+ self._refresh_copy_palette()
+ self._refresh_resume_palette()
+
+ def _resume_clicked_cb(self, button):
+ misc.resume(self._metadata)
+
+ def _copy_clicked_cb(self, button):
+ clipboard = gtk.Clipboard()
+ clipboard.set_with_data([('text/uri-list', 0, 0)],
+ self.__clipboard_get_func_cb,
+ self.__clipboard_clear_func_cb)
+
+ def __clipboard_get_func_cb(self, clipboard, selection_data, info, data):
+ # Get hold of a reference so the temp file doesn't get deleted
+ self._temp_file_path = model.get_file(self._metadata['uid'])
+ selection_data.set_uris(['file://' + self._temp_file_path])
+
+ def __clipboard_clear_func_cb(self, clipboard, data):
+ # Release and delete the temp file
+ self._temp_file_path = None
+
+ def _erase_button_clicked_cb(self, button):
+ registry = bundleregistry.get_registry()
+
+ bundle = misc.get_bundle(self._metadata)
+ if bundle is not None and registry.is_installed(bundle):
+ registry.uninstall(bundle)
+ model.delete(self._metadata['uid'])
+
+ def _resume_menu_item_activate_cb(self, menu_item, service_name):
+ misc.resume(self._metadata, service_name)
+
+ def _copy_menu_item_activate_cb(self, menu_item, mount_point):
+ model.copy(self._metadata, mount_point)
+
+ def _refresh_copy_palette(self):
+ palette = self._copy.get_palette()
+
+ for menu_item in palette.menu.get_children():
+ palette.menu.remove(menu_item)
+ menu_item.destroy()
+
+ if self._metadata['mountpoint'] != '/':
+ journal_item = MenuItem(_('Journal'))
+ journal_item.set_image(Icon(
+ icon_name='activity-journal',
+ xo_color=profile.get_color(),
+ icon_size=gtk.ICON_SIZE_MENU))
+ journal_item.connect('activate',
+ self._copy_menu_item_activate_cb, '/')
+ journal_item.show()
+ palette.menu.append(journal_item)
+
+ volume_monitor = gio.volume_monitor_get()
+ for mount in volume_monitor.get_mounts():
+ if self._metadata['mountpoint'] == mount.get_root().get_path():
+ continue
+ menu_item = MenuItem(mount.get_name())
+
+ # TODO: fallback to the more generic icons when needed
+ menu_item.set_image(Icon(icon_name=mount.get_icon().props.names[0],
+ icon_size=gtk.ICON_SIZE_MENU))
+
+ menu_item.connect('activate',
+ self._copy_menu_item_activate_cb,
+ mount.get_root().get_path())
+ palette.menu.append(menu_item)
+ menu_item.show()
+
+ def _refresh_resume_palette(self):
+ if self._metadata.get('activity_id', ''):
+ # TRANS: Action label for resuming an activity.
+ self._resume.set_tooltip(_('Resume'))
+ else:
+ # TRANS: Action label for starting an entry.
+ self._resume.set_tooltip(_('Start'))
+
+ palette = self._resume.get_palette()
+
+ for menu_item in palette.menu.get_children():
+ palette.menu.remove(menu_item)
+ menu_item.destroy()
+
+ for activity_info in misc.get_activities(self._metadata):
+ menu_item = MenuItem(activity_info.get_name())
+ menu_item.set_image(Icon(file=activity_info.get_icon(),
+ icon_size=gtk.ICON_SIZE_MENU))
+ menu_item.connect('activate', self._resume_menu_item_activate_cb,
+ activity_info.get_bundle_id())
+ palette.menu.append(menu_item)
+ menu_item.show()
diff --git a/shell/src/jarabe/journal/keepicon.py b/shell/src/jarabe/journal/keepicon.py
new file mode 100644
index 0000000..2c692c6
--- /dev/null
+++ b/shell/src/jarabe/journal/keepicon.py
@@ -0,0 +1,59 @@
+# Copyright (C) 2006, Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import gobject
+import hippo
+import gconf
+
+from sugar.graphics.icon import CanvasIcon
+from sugar.graphics import style
+from sugar.graphics.xocolor import XoColor
+
+class KeepIcon(CanvasIcon):
+ def __init__(self, keep):
+ CanvasIcon.__init__(self, icon_name='emblem-favorite',
+ box_width=style.GRID_CELL_SIZE * 3 / 5,
+ size=style.SMALL_ICON_SIZE)
+ self.connect('motion-notify-event', self.__motion_notify_event_cb)
+
+ self._keep = None
+ self.set_keep(keep)
+
+ def set_keep(self, keep):
+ if keep == self._keep:
+ return
+
+ self._keep = keep
+ if keep:
+ client = gconf.client_get_default()
+ color = XoColor(client.get_string('/desktop/sugar/user/color'))
+ self.props.xo_color = color
+ else:
+ self.props.stroke_color = style.COLOR_BUTTON_GREY.get_svg()
+ self.props.fill_color = style.COLOR_TRANSPARENT.get_svg()
+
+ def get_keep(self):
+ return self._keep
+
+ keep = gobject.property(type=int, default=0, getter=get_keep,
+ setter=set_keep)
+
+ def __motion_notify_event_cb(self, icon, event):
+ if not self._keep:
+ if event.detail == hippo.MOTION_DETAIL_ENTER:
+ icon.props.fill_color = style.COLOR_BUTTON_GREY.get_svg()
+ elif event.detail == hippo.MOTION_DETAIL_LEAVE:
+ icon.props.fill_color = style.COLOR_TRANSPARENT.get_svg()
diff --git a/shell/src/jarabe/journal/listmodel.py b/shell/src/jarabe/journal/listmodel.py
new file mode 100644
index 0000000..07f8544
--- /dev/null
+++ b/shell/src/jarabe/journal/listmodel.py
@@ -0,0 +1,201 @@
+# Copyright (C) 2009, Tomeu Vizoso
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import logging
+
+import simplejson
+import gobject
+import gtk
+
+from sugar.graphics.xocolor import XoColor
+from sugar.graphics import style
+from sugar import util
+
+from jarabe.journal import model
+from jarabe.journal import misc
+
+DS_DBUS_SERVICE = 'org.laptop.sugar.DataStore'
+DS_DBUS_INTERFACE = 'org.laptop.sugar.DataStore'
+DS_DBUS_PATH = '/org/laptop/sugar/DataStore'
+
+class ListModel(gtk.GenericTreeModel, gtk.TreeDragSource):
+ __gtype_name__ = 'JournalListModel'
+
+ __gsignals__ = {
+ 'ready': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([])),
+ 'progress': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([])),
+ }
+
+ COLUMN_UID = 0
+ COLUMN_FAVORITE = 1
+ COLUMN_ICON = 2
+ COLUMN_ICON_COLOR = 3
+ COLUMN_TITLE = 4
+ COLUMN_DATE = 5
+ COLUMN_PROGRESS = 6
+ COLUMN_BUDDY_1 = 7
+ COLUMN_BUDDY_2 = 8
+ COLUMN_BUDDY_3 = 9
+
+ _COLUMN_TYPES = {COLUMN_UID: str,
+ COLUMN_FAVORITE: bool,
+ COLUMN_ICON: str,
+ COLUMN_ICON_COLOR: object,
+ COLUMN_TITLE: str,
+ COLUMN_DATE: str,
+ COLUMN_PROGRESS: int,
+ COLUMN_BUDDY_1: object,
+ COLUMN_BUDDY_3: object,
+ COLUMN_BUDDY_2: object}
+
+ _PAGE_SIZE = 10
+
+ def __init__(self, query):
+ gobject.GObject.__init__(self)
+
+ self._last_requested_index = None
+ self._cached_row = None
+ self._result_set = model.find(query, ListModel._PAGE_SIZE)
+ self._temp_drag_file_path = None
+
+ # HACK: The view will tell us that it is resizing so the model can
+ # avoid hitting D-Bus and disk.
+ self.view_is_resizing = False
+
+ self._result_set.ready.connect(self.__result_set_ready_cb)
+ self._result_set.progress.connect(self.__result_set_progress_cb)
+
+ def __result_set_ready_cb(self, **kwargs):
+ self.emit('ready')
+
+ def __result_set_progress_cb(self, **kwargs):
+ self.emit('progress')
+
+ def setup(self):
+ self._result_set.setup()
+
+ def stop(self):
+ self._result_set.stop()
+
+ def get_metadata(self, path):
+ return model.get(self[path][ListModel.COLUMN_UID])
+
+ def on_get_n_columns(self):
+ return len(ListModel._COLUMN_TYPES)
+
+ def on_get_column_type(self, index):
+ return ListModel._COLUMN_TYPES[index]
+
+ def on_iter_n_children(self, iterator):
+ if iterator == None:
+ return self._result_set.length
+ else:
+ return 0
+
+ def on_get_value(self, index, column):
+ if self.view_is_resizing:
+ return None
+
+ if index == self._last_requested_index:
+ return self._cached_row[column]
+
+ if index >= self._result_set.length:
+ return None
+
+ self._result_set.seek(index)
+ metadata = self._result_set.read()
+
+ self._last_requested_index = index
+ self._cached_row = []
+ self._cached_row.append(metadata['uid'])
+ self._cached_row.append(metadata.get('keep', '0') == '1')
+ self._cached_row.append(misc.get_icon_name(metadata))
+
+ if misc.is_activity_bundle(metadata):
+ xo_color = XoColor('%s,%s' % (style.COLOR_BUTTON_GREY.get_svg(),
+ style.COLOR_TRANSPARENT.get_svg()))
+ else:
+ xo_color = misc.get_icon_color(metadata)
+ self._cached_row.append(xo_color)
+
+ title = gobject.markup_escape_text(metadata.get('title', None))
+ self._cached_row.append('<b>%s</b>' % title)
+
+ timestamp = int(metadata.get('timestamp', 0))
+ self._cached_row.append(util.timestamp_to_elapsed_string(timestamp))
+
+ self._cached_row.append(int(metadata.get('progress', 100)))
+
+ if metadata.get('buddies', ''):
+ buddies = simplejson.loads(metadata['buddies']).values()
+ else:
+ buddies = []
+
+ for n_ in xrange(0, 3):
+ if buddies:
+ nick, color = buddies.pop(0)
+ self._cached_row.append((nick, XoColor(color)))
+ else:
+ self._cached_row.append(None)
+
+ return self._cached_row[column]
+
+ def on_iter_nth_child(self, iterator, n):
+ return n
+
+ def on_get_path(self, iterator):
+ return (iterator)
+
+ def on_get_iter(self, path):
+ return path[0]
+
+ def on_iter_next(self, iterator):
+ if iterator != None:
+ if iterator >= self._result_set.length - 1:
+ return None
+ return iterator + 1
+ return None
+
+ def on_get_flags(self):
+ return gtk.TREE_MODEL_ITERS_PERSIST | gtk.TREE_MODEL_LIST_ONLY
+
+ def on_iter_children(self, iterator):
+ return None
+
+ def on_iter_has_child(self, iterator):
+ return False
+
+ def on_iter_parent(self, iterator):
+ return None
+
+ def do_drag_data_get(self, path, selection):
+ uid = self[path][ListModel.COLUMN_UID]
+ if selection.target == 'text/uri-list':
+ # Get hold of a reference so the temp file doesn't get deleted
+ self._temp_drag_file_path = model.get_file(uid)
+ logging.debug('putting %r in selection', self._temp_drag_file_path)
+ selection.set(selection.target, 8, self._temp_drag_file_path)
+ return True
+ elif selection.target == 'journal-object-id':
+ selection.set(selection.target, 8, uid)
+ return True
+
+ return False
+
diff --git a/shell/src/jarabe/journal/listview.py b/shell/src/jarabe/journal/listview.py
new file mode 100644
index 0000000..9e19f70
--- /dev/null
+++ b/shell/src/jarabe/journal/listview.py
@@ -0,0 +1,641 @@
+# Copyright (C) 2009, Tomeu Vizoso
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import logging
+from gettext import gettext as _
+import time
+
+import gobject
+import gtk
+import hippo
+import gconf
+import pango
+
+from sugar.graphics import style
+from sugar.graphics.icon import CanvasIcon, Icon, CellRendererIcon
+from sugar.graphics.xocolor import XoColor
+from sugar import util
+
+from jarabe.journal.listmodel import ListModel
+from jarabe.journal.palettes import ObjectPalette, BuddyPalette
+from jarabe.journal import model
+from jarabe.journal import misc
+
+UPDATE_INTERVAL = 300
+
+MESSAGE_EMPTY_JOURNAL = 0
+MESSAGE_NO_MATCH = 1
+
+class TreeView(gtk.TreeView):
+ __gtype_name__ = 'JournalTreeView'
+
+ def __init__(self):
+ gtk.TreeView.__init__(self)
+ self.set_headers_visible(False)
+
+ def do_size_request(self, requisition):
+ # HACK: We tell the model that the view is just resizing so it can avoid
+ # hitting both D-Bus and disk.
+ tree_model = self.get_model()
+ if tree_model is not None:
+ tree_model.view_is_resizing = True
+ try:
+ gtk.TreeView.do_size_request(self, requisition)
+ finally:
+ if tree_model is not None:
+ tree_model.view_is_resizing = False
+
+class BaseListView(gtk.Bin):
+ __gtype_name__ = 'JournalBaseListView'
+
+ __gsignals__ = {
+ 'clear-clicked': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([]))
+ }
+
+ def __init__(self):
+ self._query = {}
+ self._model = None
+ self._progress_bar = None
+ self._last_progress_bar_pulse = None
+ self._scroll_position = 0.
+
+ gobject.GObject.__init__(self)
+
+ self.connect('map', self.__map_cb)
+ self.connect('unrealize', self.__unrealize_cb)
+ self.connect('destroy', self.__destroy_cb)
+
+ self._scrolled_window = gtk.ScrolledWindow()
+ self._scrolled_window.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+ self.add(self._scrolled_window)
+ self._scrolled_window.show()
+
+ self.tree_view = TreeView()
+ selection = self.tree_view.get_selection()
+ selection.set_mode(gtk.SELECTION_NONE)
+ self.tree_view.props.fixed_height_mode = True
+ self.tree_view.modify_base(gtk.STATE_NORMAL,
+ style.COLOR_WHITE.get_gdk_color())
+ self._scrolled_window.add(self.tree_view)
+ self.tree_view.show()
+
+ self.cell_title = None
+ self.cell_icon = None
+ self._title_column = None
+ self.date_column = None
+ self._add_columns()
+
+ self.tree_view.enable_model_drag_source(gtk.gdk.BUTTON1_MASK,
+ [('text/uri-list', 0, 0),
+ ('journal-object-id', 0, 0)],
+ gtk.gdk.ACTION_COPY)
+
+ # Auto-update stuff
+ self._fully_obscured = True
+ self._dirty = False
+ self._refresh_idle_handler = None
+ self._update_dates_timer = None
+
+ model.created.connect(self.__model_created_cb)
+ model.updated.connect(self.__model_updated_cb)
+ model.deleted.connect(self.__model_deleted_cb)
+
+ def __model_created_cb(self, sender, **kwargs):
+ self._set_dirty()
+
+ def __model_updated_cb(self, sender, **kwargs):
+ self._set_dirty()
+
+ def __model_deleted_cb(self, sender, **kwargs):
+ self._set_dirty()
+
+ def _add_columns(self):
+ cell_favorite = CellRendererFavorite(self.tree_view)
+ cell_favorite.connect('clicked', self.__favorite_clicked_cb)
+
+ column = gtk.TreeViewColumn()
+ column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED
+ column.props.fixed_width = cell_favorite.props.width
+ column.pack_start(cell_favorite)
+ column.set_cell_data_func(cell_favorite, self.__favorite_set_data_cb)
+ self.tree_view.append_column(column)
+
+ self.cell_icon = CellRendererActivityIcon(self.tree_view)
+
+ column = gtk.TreeViewColumn()
+ column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED
+ column.props.fixed_width = self.cell_icon.props.width
+ column.pack_start(self.cell_icon)
+ column.add_attribute(self.cell_icon, 'file-name', ListModel.COLUMN_ICON)
+ column.add_attribute(self.cell_icon, 'xo-color',
+ ListModel.COLUMN_ICON_COLOR)
+ self.tree_view.append_column(column)
+
+ self.cell_title = gtk.CellRendererText()
+ self.cell_title.props.ellipsize = pango.ELLIPSIZE_MIDDLE
+ self.cell_title.props.ellipsize_set = True
+
+ self._title_column = gtk.TreeViewColumn()
+ self._title_column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED
+ self._title_column.props.expand = True
+ self._title_column.props.clickable = True
+ self._title_column.pack_start(self.cell_title)
+ self._title_column.add_attribute(self.cell_title, 'markup',
+ ListModel.COLUMN_TITLE)
+ self.tree_view.append_column(self._title_column)
+
+ buddies_column = gtk.TreeViewColumn()
+ buddies_column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED
+ self.tree_view.append_column(buddies_column)
+
+ for column_index in [ListModel.COLUMN_BUDDY_1, ListModel.COLUMN_BUDDY_2,
+ ListModel.COLUMN_BUDDY_3]:
+ cell_icon = CellRendererBuddy(self.tree_view,
+ column_index=column_index)
+ buddies_column.pack_start(cell_icon)
+ buddies_column.props.fixed_width += cell_icon.props.width
+ buddies_column.add_attribute(cell_icon, 'buddy', column_index)
+ buddies_column.set_cell_data_func(cell_icon,
+ self.__buddies_set_data_cb)
+
+ cell_progress = gtk.CellRendererProgress()
+ cell_progress.props.ypad = style.GRID_CELL_SIZE / 4
+ buddies_column.pack_start(cell_progress)
+ buddies_column.add_attribute(cell_progress, 'value',
+ ListModel.COLUMN_PROGRESS)
+ buddies_column.set_cell_data_func(cell_progress,
+ self.__progress_data_cb)
+
+ cell_text = gtk.CellRendererText()
+ cell_text.props.xalign = 1
+
+ # Measure the required width for a date in the form of "10 hours, 10
+ # minutes ago"
+ timestamp = time.time() - 10 * 60 - 10 * 60 * 60
+ date = util.timestamp_to_elapsed_string(timestamp)
+ date_width = self._get_width_for_string(date)
+
+ self.date_column = gtk.TreeViewColumn()
+ self.date_column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED
+ self.date_column.props.fixed_width = date_width
+ self.date_column.set_alignment(1)
+ self.date_column.props.resizable = True
+ self.date_column.props.clickable = True
+ self.date_column.pack_start(cell_text)
+ self.date_column.add_attribute(cell_text, 'text', ListModel.COLUMN_DATE)
+ self.tree_view.append_column(self.date_column)
+
+ def _get_width_for_string(self, text):
+ # Add some extra margin
+ text = text + 'aaaaa'
+
+ widget = gtk.Label('')
+ context = widget.get_pango_context()
+ layout = pango.Layout(context)
+ layout.set_text(text)
+ width, height_ = layout.get_size()
+ return pango.PIXELS(width)
+
+ def do_size_allocate(self, allocation):
+ self.allocation = allocation
+ self.child.size_allocate(allocation)
+
+ def do_size_request(self, requisition):
+ requisition.width, requisition.height = self.child.size_request()
+
+ def __destroy_cb(self, widget):
+ if self._model is not None:
+ self._model.stop()
+
+ def __buddies_set_data_cb(self, column, cell, tree_model, tree_iter):
+ progress = tree_model[tree_iter][ListModel.COLUMN_PROGRESS]
+ cell.props.visible = progress >= 100
+
+ def __progress_data_cb(self, column, cell, tree_model, tree_iter):
+ progress = tree_model[tree_iter][ListModel.COLUMN_PROGRESS]
+ cell.props.visible = progress < 100
+
+ def __favorite_set_data_cb(self, column, cell, tree_model, tree_iter):
+ favorite = tree_model[tree_iter][ListModel.COLUMN_FAVORITE]
+ if favorite:
+ client = gconf.client_get_default()
+ color = XoColor(client.get_string('/desktop/sugar/user/color'))
+ cell.props.xo_color = color
+ else:
+ cell.props.xo_color = None
+
+ def __favorite_clicked_cb(self, cell, path):
+ row = self._model[path]
+ metadata = model.get(row[ListModel.COLUMN_UID])
+ if not model.is_editable(metadata):
+ return
+ if metadata.get('keep', 0) == '1':
+ metadata['keep'] = '0'
+ else:
+ metadata['keep'] = '1'
+ model.write(metadata, update_mtime=False)
+
+ def update_with_query(self, query_dict):
+ logging.debug('ListView.update_with_query')
+ self._query = query_dict
+
+ if 'order_by' not in self._query:
+ self._query['order_by'] = ['+timestamp']
+
+ self.refresh()
+
+ def refresh(self):
+ logging.debug('ListView.refresh query %r', self._query)
+ self._stop_progress_bar()
+
+ if self._model is not None:
+ self._model.stop()
+ self._dirty = False
+
+ self._model = ListModel(self._query)
+ self._model.connect('ready', self.__model_ready_cb)
+ self._model.connect('progress', self.__model_progress_cb)
+ self._model.setup()
+
+ def __model_ready_cb(self, tree_model):
+ self._stop_progress_bar()
+
+ self._scroll_position = self.tree_view.props.vadjustment.props.value
+ logging.debug('ListView.__model_ready_cb %r', self._scroll_position)
+
+ if self.tree_view.window is not None:
+ # prevent glitches while later vadjustment setting, see #1235
+ self.tree_view.get_bin_window().hide()
+
+ # Cannot set it up earlier because will try to access the model
+ # and it needs to be ready.
+ self.tree_view.set_model(self._model)
+
+ self.tree_view.props.vadjustment.props.value = self._scroll_position
+ self.tree_view.props.vadjustment.value_changed()
+
+ if self.tree_view.window is not None:
+ # prevent glitches while later vadjustment setting, see #1235
+ self.tree_view.get_bin_window().show()
+
+ if len(tree_model) == 0:
+ if self._is_query_empty():
+ self._show_message(MESSAGE_EMPTY_JOURNAL)
+ else:
+ self._show_message(MESSAGE_NO_MATCH)
+ else:
+ self._clear_message()
+
+ def __map_cb(self, widget):
+ logging.debug('ListView.__map_cb %r', self._scroll_position)
+ self.tree_view.props.vadjustment.props.value = self._scroll_position
+ self.tree_view.props.vadjustment.value_changed()
+
+ def __unrealize_cb(self, widget):
+ self._scroll_position = self.tree_view.props.vadjustment.props.value
+ logging.debug('ListView.__map_cb %r', self._scroll_position)
+
+ is_editable = self._query.get('mountpoints', '') == '/'
+ self.cell_title.props.editable = is_editable
+
+ def _is_query_empty(self):
+ # FIXME: This is a hack, we shouldn't have to update this every time
+ # a new search term is added.
+ if self._query.get('query', '') or self._query.get('mime_type', '') or \
+ self._query.get('keep', '') or self._query.get('mtime', '') or \
+ self._query.get('activity', ''):
+ return False
+ else:
+ return True
+
+ def __model_progress_cb(self, tree_model):
+ if self._progress_bar is None:
+ self._start_progress_bar()
+
+ if time.time() - self._last_progress_bar_pulse > 0.05:
+ self._progress_bar.pulse()
+ self._last_progress_bar_pulse = time.time()
+
+ def _start_progress_bar(self):
+ alignment = gtk.Alignment(xalign=0.5, yalign=0.5, xscale=0.5)
+ self.remove(self.child)
+ self.add(alignment)
+ alignment.show()
+
+ self._progress_bar = gtk.ProgressBar()
+ self._progress_bar.props.pulse_step = 0.01
+ self._last_progress_bar_pulse = time.time()
+ alignment.add(self._progress_bar)
+ self._progress_bar.show()
+
+ def _stop_progress_bar(self):
+ if self._progress_bar is None:
+ return
+ self.remove(self.child)
+ self.add(self._scrolled_window)
+ self._progress_bar = None
+
+ def _show_message(self, message):
+ canvas = hippo.Canvas()
+ self.remove(self.child)
+ self.add(canvas)
+ canvas.show()
+
+ box = hippo.CanvasBox(orientation=hippo.ORIENTATION_VERTICAL,
+ background_color=style.COLOR_WHITE.get_int(),
+ yalign=hippo.ALIGNMENT_CENTER,
+ spacing=style.DEFAULT_SPACING,
+ padding_bottom=style.GRID_CELL_SIZE)
+ canvas.set_root(box)
+
+ icon = CanvasIcon(size=style.LARGE_ICON_SIZE,
+ icon_name='activity-journal',
+ stroke_color = style.COLOR_BUTTON_GREY.get_svg(),
+ fill_color = style.COLOR_TRANSPARENT.get_svg())
+ box.append(icon)
+
+ if message == MESSAGE_EMPTY_JOURNAL:
+ text = _('Your Journal is empty')
+ elif message == MESSAGE_NO_MATCH:
+ text = _('No matching entries')
+ else:
+ raise ValueError('Invalid message')
+
+ text = hippo.CanvasText(text=text,
+ xalign=hippo.ALIGNMENT_CENTER,
+ font_desc=style.FONT_BOLD.get_pango_desc(),
+ color = style.COLOR_BUTTON_GREY.get_int())
+ box.append(text)
+
+ if message == MESSAGE_NO_MATCH:
+ button = gtk.Button(label=_('Clear search'))
+ button.connect('clicked', self.__clear_button_clicked_cb)
+ button.props.image = Icon(icon_name='dialog-cancel',
+ icon_size=gtk.ICON_SIZE_BUTTON)
+ canvas_button = hippo.CanvasWidget(widget=button,
+ xalign=hippo.ALIGNMENT_CENTER)
+ box.append(canvas_button)
+
+ def __clear_button_clicked_cb(self, button):
+ self.emit('clear-clicked')
+
+ def _clear_message(self):
+ if self.child == self._scrolled_window:
+ return
+ self.remove(self.child)
+ self.add(self._scrolled_window)
+ self._scrolled_window.show()
+
+ def update_dates(self):
+ if not self.tree_view.flags() & gtk.REALIZED:
+ return
+ visible_range = self.tree_view.get_visible_range()
+ if visible_range is None:
+ return
+
+ logging.debug('ListView.update_dates')
+
+ path, end_path = visible_range
+ tree_model = self.tree_view.get_model()
+
+ while True:
+ x, y, width, height = self.tree_view.get_cell_area(path,
+ self.date_column)
+ x, y = self.tree_view.convert_tree_to_widget_coords(x, y)
+ self.tree_view.queue_draw_area(x, y, width, height)
+ if path == end_path:
+ break
+ else:
+ next_iter = tree_model.iter_next(tree_model.get_iter(path))
+ path = tree_model.get_path(next_iter)
+
+ def _set_dirty(self):
+ if self._fully_obscured:
+ self._dirty = True
+ else:
+ self.refresh()
+
+ def set_is_visible(self, visible):
+ if visible != self._fully_obscured:
+ return
+
+ logging.debug('canvas_visibility_notify_event_cb %r', visible)
+ if visible:
+ self._fully_obscured = False
+ if self._dirty:
+ self.refresh()
+ if self._update_dates_timer is None:
+ logging.debug('Adding date updating timer')
+ self._update_dates_timer = \
+ gobject.timeout_add_seconds(UPDATE_INTERVAL,
+ self.__update_dates_timer_cb)
+ else:
+ self._fully_obscured = True
+ if self._update_dates_timer is not None:
+ logging.debug('Remove date updating timer')
+ gobject.source_remove(self._update_dates_timer)
+ self._update_dates_timer = None
+
+ def __update_dates_timer_cb(self):
+ self.update_dates()
+ return True
+
+class ListView(BaseListView):
+ __gtype_name__ = 'JournalListView'
+
+ __gsignals__ = {
+ 'detail-clicked': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([object]))
+ }
+
+ def __init__(self):
+ BaseListView.__init__(self)
+ self._is_dragging = False
+
+ self.tree_view.connect('drag-begin', self.__drag_begin_cb)
+ self.tree_view.connect('button-release-event',
+ self.__button_release_event_cb)
+
+ self.cell_title.connect('edited', self.__cell_title_edited_cb)
+ self.cell_title.connect('editing-canceled', self.__editing_canceled_cb)
+
+ self.cell_icon.connect('clicked', self.__icon_clicked_cb)
+ self.cell_icon.connect('detail-clicked', self.__detail_clicked_cb)
+
+ cell_detail = CellRendererDetail(self.tree_view)
+ cell_detail.connect('clicked', self.__detail_cell_clicked_cb)
+
+ column = gtk.TreeViewColumn()
+ column.props.sizing = gtk.TREE_VIEW_COLUMN_FIXED
+ column.props.fixed_width = cell_detail.props.width
+ column.pack_start(cell_detail)
+ self.tree_view.append_column(column)
+
+ def __drag_begin_cb(self, widget, drag_context):
+ self._is_dragging = True
+
+ def __button_release_event_cb(self, tree_view, event):
+ try:
+ if self._is_dragging:
+ return
+ finally:
+ self._is_dragging = False
+
+ pos = tree_view.get_path_at_pos(int(event.x), int(event.y))
+ if pos is None:
+ return
+
+ path, column, x_, y_ = pos
+ if column != self._title_column:
+ return
+
+ row = self.tree_view.get_model()[path]
+ metadata = model.get(row[ListModel.COLUMN_UID])
+ self.cell_title.props.editable = model.is_editable(metadata)
+
+ tree_view.set_cursor_on_cell(path, column, start_editing=True)
+
+ def __detail_cell_clicked_cb(self, cell, path):
+ row = self.tree_view.get_model()[path]
+ self.emit('detail-clicked', row[ListModel.COLUMN_UID])
+
+ def __detail_clicked_cb(self, cell, uid):
+ self.emit('detail-clicked', uid)
+
+ def __icon_clicked_cb(self, cell, path):
+ row = self.tree_view.get_model()[path]
+ metadata = model.get(row[ListModel.COLUMN_UID])
+ misc.resume(metadata)
+
+ def __cell_title_edited_cb(self, cell, path, new_text):
+ row = self._model[path]
+ metadata = model.get(row[ListModel.COLUMN_UID])
+ metadata['title'] = new_text
+ model.write(metadata, update_mtime=False)
+ self.cell_title.props.editable = False
+
+ def __editing_canceled_cb(self, cell):
+ self.cell_title.props.editable = False
+
+class CellRendererFavorite(CellRendererIcon):
+ __gtype_name__ = 'JournalCellRendererFavorite'
+
+ def __init__(self, tree_view):
+ CellRendererIcon.__init__(self, tree_view)
+
+ self.props.width = style.GRID_CELL_SIZE
+ self.props.height = style.GRID_CELL_SIZE
+ self.props.size = style.SMALL_ICON_SIZE
+ self.props.icon_name = 'emblem-favorite'
+ self.props.mode = gtk.CELL_RENDERER_MODE_ACTIVATABLE
+ self.props.prelit_stroke_color = style.COLOR_BUTTON_GREY.get_svg()
+ self.props.prelit_fill_color = style.COLOR_BUTTON_GREY.get_svg()
+
+class CellRendererDetail(CellRendererIcon):
+ __gtype_name__ = 'JournalCellRendererDetail'
+
+ def __init__(self, tree_view):
+ CellRendererIcon.__init__(self, tree_view)
+
+ self.props.width = style.GRID_CELL_SIZE
+ self.props.height = style.GRID_CELL_SIZE
+ self.props.size = style.SMALL_ICON_SIZE
+ self.props.icon_name = 'go-right'
+ self.props.mode = gtk.CELL_RENDERER_MODE_ACTIVATABLE
+ self.props.stroke_color = style.COLOR_TRANSPARENT.get_svg()
+ self.props.fill_color = style.COLOR_BUTTON_GREY.get_svg()
+ self.props.prelit_stroke_color = style.COLOR_TRANSPARENT.get_svg()
+ self.props.prelit_fill_color = style.COLOR_BLACK.get_svg()
+
+class CellRendererActivityIcon(CellRendererIcon):
+ __gtype_name__ = 'JournalCellRendererActivityIcon'
+
+ __gsignals__ = {
+ 'detail-clicked': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([str])),
+ }
+
+ def __init__(self, tree_view):
+ self._show_palette = True
+
+ CellRendererIcon.__init__(self, tree_view)
+
+ self.props.width = style.GRID_CELL_SIZE
+ self.props.height = style.GRID_CELL_SIZE
+ self.props.size = style.STANDARD_ICON_SIZE
+ self.props.mode = gtk.CELL_RENDERER_MODE_ACTIVATABLE
+
+ self.tree_view = tree_view
+
+ def create_palette(self):
+ if not self._show_palette:
+ return None
+
+ tree_model = self.tree_view.get_model()
+ metadata = tree_model.get_metadata(self.props.palette_invoker.path)
+
+ palette = ObjectPalette(metadata, detail=True)
+ palette.connect('detail-clicked',
+ self.__detail_clicked_cb)
+ return palette
+
+ def __detail_clicked_cb(self, palette, uid):
+ self.emit('detail-clicked', uid)
+
+ def set_show_palette(self, show_palette):
+ self._show_palette = show_palette
+
+ show_palette = gobject.property(type=bool, default=True,
+ setter=set_show_palette)
+
+class CellRendererBuddy(CellRendererIcon):
+ __gtype_name__ = 'JournalCellRendererBuddy'
+
+ def __init__(self, tree_view, column_index):
+ CellRendererIcon.__init__(self, tree_view)
+
+ self.props.width = style.STANDARD_ICON_SIZE
+ self.props.height = style.STANDARD_ICON_SIZE
+ self.props.size = style.STANDARD_ICON_SIZE
+ self.props.mode = gtk.CELL_RENDERER_MODE_ACTIVATABLE
+
+ self.tree_view = tree_view
+ self._model_column_index = column_index
+
+ def create_palette(self):
+ tree_model = self.tree_view.get_model()
+ row = tree_model[self.props.palette_invoker.path]
+
+ if row[self._model_column_index] is not None:
+ nick, xo_color = row[self._model_column_index]
+ return BuddyPalette((nick, xo_color.to_string()))
+ else:
+ return None
+
+ def set_buddy(self, buddy):
+ if buddy is None:
+ self.props.icon_name = None
+ else:
+ nick_, xo_color = buddy
+ self.props.icon_name = 'computer-xo'
+ self.props.xo_color = xo_color
+
+ buddy = gobject.property(type=object, setter=set_buddy)
+
diff --git a/shell/src/jarabe/journal/misc.py b/shell/src/jarabe/journal/misc.py
new file mode 100644
index 0000000..657b60e
--- /dev/null
+++ b/shell/src/jarabe/journal/misc.py
@@ -0,0 +1,262 @@
+# Copyright (C) 2007, One Laptop Per Child
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import logging
+import time
+import os
+from gettext import gettext as _
+
+import gio
+import gconf
+import gtk
+
+from sugar.activity import activityfactory
+from sugar.activity.activityhandle import ActivityHandle
+from sugar.graphics.icon import get_icon_file_name
+from sugar.graphics.xocolor import XoColor
+from sugar import mime
+from sugar.bundle.activitybundle import ActivityBundle
+from sugar.bundle.contentbundle import ContentBundle
+from sugar import util
+
+from jarabe.view import launcher
+from jarabe.model import bundleregistry, shell
+from jarabe.journal.journalentrybundle import JournalEntryBundle
+from jarabe.journal import model
+
+def _get_icon_for_mime(mime_type):
+ generic_types = mime.get_all_generic_types()
+ for generic_type in generic_types:
+ if mime_type in generic_type.mime_types:
+ file_name = get_icon_file_name(generic_type.icon)
+ if file_name is not None:
+ return file_name
+
+ icons = gio.content_type_get_icon(mime_type)
+ logging.debug('icons for this file: %r', icons.props.names)
+ for icon_name in icons.props.names:
+ file_name = get_icon_file_name(icon_name)
+ if file_name is not None:
+ return file_name
+
+def get_icon_name(metadata):
+ file_name = None
+
+ bundle_id = metadata.get('activity', '')
+ if not bundle_id:
+ bundle_id = metadata.get('bundle_id', '')
+
+ if bundle_id:
+ activity_info = bundleregistry.get_registry().get_bundle(bundle_id)
+ if activity_info:
+ file_name = activity_info.get_icon()
+
+ if file_name is None and is_activity_bundle(metadata):
+ file_path = model.get_file(metadata['uid'])
+ if file_path is not None and os.path.exists(file_path):
+ try:
+ bundle = ActivityBundle(file_path)
+ file_name = bundle.get_icon()
+ except Exception:
+ logging.exception('Could not read bundle')
+
+ if file_name is None:
+ file_name = _get_icon_for_mime(metadata.get('mime_type', ''))
+
+ if file_name is None:
+ file_name = get_icon_file_name('application-octet-stream')
+
+ return file_name
+
+def get_date(metadata):
+ """ Convert from a string in iso format to a more human-like format. """
+ if metadata.has_key('timestamp'):
+ timestamp = float(metadata['timestamp'])
+ return util.timestamp_to_elapsed_string(timestamp)
+ elif metadata.has_key('mtime'):
+ ti = time.strptime(metadata['mtime'], "%Y-%m-%dT%H:%M:%S")
+ return util.timestamp_to_elapsed_string(time.mktime(ti))
+ else:
+ return _('No date')
+
+def get_bundle(metadata):
+ try:
+ if is_activity_bundle(metadata):
+ file_path = util.TempFilePath(model.get_file(metadata['uid']))
+ if not os.path.exists(file_path):
+ logging.warning('Invalid path: %r', file_path)
+ return None
+ return ActivityBundle(file_path)
+
+ elif is_content_bundle(metadata):
+ file_path = util.TempFilePath(model.get_file(metadata['uid']))
+ if not os.path.exists(file_path):
+ logging.warning('Invalid path: %r', file_path)
+ return None
+ return ContentBundle(file_path)
+
+ elif is_journal_bundle(metadata):
+ file_path = util.TempFilePath(model.get_file(metadata['uid']))
+ if not os.path.exists(file_path):
+ logging.warning('Invalid path: %r', file_path)
+ return None
+ return JournalEntryBundle(file_path)
+ else:
+ return None
+ except Exception:
+ logging.exception('Incorrect bundle')
+ return None
+
+def _get_activities_for_mime(mime_type):
+ registry = bundleregistry.get_registry()
+ result = registry.get_activities_for_type(mime_type)
+ if not result:
+ for parent_mime in mime.get_mime_parents(mime_type):
+ for activity in registry.get_activities_for_type(parent_mime):
+ if activity not in result:
+ result.append(activity)
+ return result
+
+def get_activities(metadata):
+ activities = []
+
+ bundle_id = metadata.get('activity', '')
+ if bundle_id:
+ activity_info = bundleregistry.get_registry().get_bundle(bundle_id)
+ if activity_info:
+ activities.append(activity_info)
+
+ mime_type = metadata.get('mime_type', '')
+ if mime_type:
+ activities_info = _get_activities_for_mime(mime_type)
+ for activity_info in activities_info:
+ if activity_info not in activities:
+ activities.append(activity_info)
+
+ return activities
+
+def resume(metadata, bundle_id=None):
+ registry = bundleregistry.get_registry()
+
+ if is_activity_bundle(metadata) and bundle_id is None:
+
+ logging.debug('Creating activity bundle')
+
+ file_path = model.get_file(metadata['uid'])
+ bundle = ActivityBundle(file_path)
+ if not registry.is_installed(bundle):
+ logging.debug('Installing activity bundle')
+ registry.install(bundle)
+ else:
+ logging.debug('Upgrading activity bundle')
+ registry.upgrade(bundle)
+
+ logging.debug('activityfactory.creating bundle with id %r',
+ bundle.get_bundle_id())
+ installed_bundle = registry.get_bundle(bundle.get_bundle_id())
+ if installed_bundle:
+ launch(installed_bundle)
+ else:
+ logging.error('Bundle %r is not installed.',
+ bundle.get_bundle_id())
+
+ elif is_content_bundle(metadata) and bundle_id is None:
+
+ logging.debug('Creating content bundle')
+
+ file_path = model.get_file(metadata['uid'])
+ bundle = ContentBundle(file_path)
+ if not bundle.is_installed():
+ logging.debug('Installing content bundle')
+ bundle.install()
+
+ activities = _get_activities_for_mime('text/html')
+ if len(activities) == 0:
+ logging.warning('No activity can open HTML content bundles')
+ return
+
+ uri = bundle.get_start_uri()
+ logging.debug('activityfactory.creating with uri %s', uri)
+
+ activity_bundle = registry.get_bundle(activities[0].get_bundle_id())
+ launch(activity_bundle, uri=uri)
+ else:
+ activity_id = metadata.get('activity_id', '')
+
+ if bundle_id is None:
+ activities = get_activities(metadata)
+ if not activities:
+ logging.warning('No activity can open this object, %s.',
+ metadata.get('mime_type', None))
+ return
+ bundle_id = activities[0].get_bundle_id()
+
+ bundle = registry.get_bundle(bundle_id)
+
+
+ if metadata.get('mountpoint', '/') == '/':
+ object_id = metadata['uid']
+ else:
+ object_id = model.copy(metadata, '/')
+
+ launch(bundle, activity_id=activity_id, object_id=object_id,
+ color=get_icon_color(metadata))
+
+def launch(bundle, activity_id=None, object_id=None, uri=None, color=None,
+ invited=False):
+ if activity_id is None:
+ activity_id = activityfactory.create_activity_id()
+
+ logging.debug('launch bundle_id=%s activity_id=%s object_id=%s uri=%s',
+ bundle.get_bundle_id(), activity_id, object_id, uri)
+
+ shell_model = shell.get_model()
+ activity = shell_model.get_activity_by_id(activity_id)
+ if activity is not None:
+ logging.debug('re-launch %r', activity.get_window())
+ activity.get_window().activate(gtk.get_current_event_time())
+ return
+
+ if color is None:
+ client = gconf.client_get_default()
+ color = XoColor(client.get_string('/desktop/sugar/user/color'))
+
+ launcher.add_launcher(activity_id, bundle.get_icon(), color)
+ activity_handle = ActivityHandle(activity_id=activity_id,
+ object_id=object_id, uri=uri, invited=invited)
+ activityfactory.create(bundle, activity_handle)
+
+def is_activity_bundle(metadata):
+ mime_type = metadata.get('mime_type', '')
+ return mime_type == ActivityBundle.MIME_TYPE or \
+ mime_type == ActivityBundle.DEPRECATED_MIME_TYPE
+
+def is_content_bundle(metadata):
+ return metadata.get('mime_type', '') == ContentBundle.MIME_TYPE
+
+def is_journal_bundle(metadata):
+ return metadata.get('mime_type', '') == JournalEntryBundle.MIME_TYPE
+
+def is_bundle(metadata):
+ return is_activity_bundle(metadata) or is_content_bundle(metadata) or \
+ is_journal_bundle(metadata)
+
+def get_icon_color(metadata):
+ if metadata is None or not 'icon-color' in metadata:
+ client = gconf.client_get_default()
+ return XoColor(client.get_string('/desktop/sugar/user/color'))
+ else:
+ return XoColor(metadata['icon-color'])
diff --git a/shell/src/jarabe/journal/modalalert.py b/shell/src/jarabe/journal/modalalert.py
new file mode 100644
index 0000000..c7c6a0a
--- /dev/null
+++ b/shell/src/jarabe/journal/modalalert.py
@@ -0,0 +1,97 @@
+# 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
+from gettext import gettext as _
+import gconf
+
+from sugar.graphics.icon import Icon
+from sugar.graphics import style
+from sugar.graphics.xocolor import XoColor
+
+class ModalAlert(gtk.Window):
+
+ __gtype_name__ = 'SugarModalAlert'
+
+ def __init__(self):
+ gtk.Window.__init__(self)
+
+ 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._main_view = gtk.EventBox()
+ self._vbox = gtk.VBox()
+ self._vbox.set_spacing(style.DEFAULT_SPACING)
+ self._vbox.set_border_width(style.GRID_CELL_SIZE * 2)
+ self._main_view.modify_bg(gtk.STATE_NORMAL,
+ style.COLOR_BLACK.get_gdk_color())
+ self._main_view.add(self._vbox)
+ self._vbox.show()
+
+ client = gconf.client_get_default()
+ color = XoColor(client.get_string('/desktop/sugar/user/color'))
+
+ icon = Icon(icon_name='activity-journal',
+ pixel_size=style.XLARGE_ICON_SIZE,
+ xo_color=color)
+ self._vbox.pack_start(icon, False)
+ icon.show()
+
+ self._title = gtk.Label()
+ self._title.modify_fg(gtk.STATE_NORMAL,
+ style.COLOR_WHITE.get_gdk_color())
+ self._title.set_markup('<b>%s</b>' % _('Your Journal is full'))
+ self._vbox.pack_start(self._title, False)
+ self._title.show()
+
+ self._message = gtk.Label(_('Please delete some old Journal'
+ ' entries to make space for new ones.'))
+ self._message.modify_fg(gtk.STATE_NORMAL,
+ style.COLOR_WHITE.get_gdk_color())
+ self._vbox.pack_start(self._message, False)
+ self._message.show()
+
+ alignment = gtk.Alignment(xalign=0.5, yalign=0.5)
+ self._vbox.pack_start(alignment, expand=False)
+ alignment.show()
+
+ self._show_journal = gtk.Button()
+ self._show_journal.set_label(_('Show Journal'))
+ alignment.add(self._show_journal)
+ self._show_journal.show()
+ self._show_journal.connect('clicked', self.__show_journal_cb)
+
+ self.add(self._main_view)
+ self._main_view.show()
+
+ self.connect("realize", self.__realize_cb)
+
+ def __realize_cb(self, widget):
+ self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
+ self.window.set_accept_focus(True)
+
+ def __show_journal_cb(self, button):
+ '''The opener will listen on the destroy signal
+ '''
+ self.destroy()
+
diff --git a/shell/src/jarabe/journal/model.py b/shell/src/jarabe/journal/model.py
new file mode 100644
index 0000000..ffc62e0
--- /dev/null
+++ b/shell/src/jarabe/journal/model.py
@@ -0,0 +1,541 @@
+# 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 logging
+import os
+from datetime import datetime
+import time
+import shutil
+from stat import S_IFMT, S_IFDIR, S_IFREG
+import re
+
+import gobject
+import dbus
+import gconf
+import gio
+
+from sugar import dispatch
+from sugar import mime
+from sugar import util
+
+DS_DBUS_SERVICE = 'org.laptop.sugar.DataStore'
+DS_DBUS_INTERFACE = 'org.laptop.sugar.DataStore'
+DS_DBUS_PATH = '/org/laptop/sugar/DataStore'
+
+# Properties the journal cares about.
+PROPERTIES = ['uid', 'title', 'mtime', 'timestamp', 'keep', 'buddies',
+ 'icon-color', 'mime_type', 'progress', 'activity', 'mountpoint',
+ 'activity_id', 'bundle_id']
+
+MIN_PAGES_TO_CACHE = 3
+MAX_PAGES_TO_CACHE = 5
+
+class _Cache(object):
+
+ __gtype_name__ = 'model_Cache'
+
+ def __init__(self, entries=None):
+ self._array = []
+ if entries is not None:
+ self.append_all(entries)
+
+ def prepend_all(self, entries):
+ self._array[0:0] = entries
+
+ def append_all(self, entries):
+ self._array += entries
+
+ def __len__(self):
+ return len(self._array)
+
+ def __getitem__(self, key):
+ return self._array[key]
+
+ def __delitem__(self, key):
+ del self._array[key]
+
+
+class BaseResultSet(object):
+ """Encapsulates the result of a query
+ """
+
+ def __init__(self, query, page_size):
+ self._total_count = -1
+ self._position = -1
+ self._query = query
+ self._page_size = page_size
+
+ self._offset = 0
+ self._cache = _Cache()
+
+ self.ready = dispatch.Signal()
+ self.progress = dispatch.Signal()
+
+ def setup(self):
+ self.ready.send(self)
+
+ def stop(self):
+ pass
+
+ def get_length(self):
+ if self._total_count == -1:
+ query = self._query.copy()
+ query['limit'] = self._page_size * MIN_PAGES_TO_CACHE
+ entries, self._total_count = self.find(query)
+ self._cache.append_all(entries)
+ self._offset = 0
+ return self._total_count
+
+ length = property(get_length)
+
+ def find(self, query):
+ raise NotImplementedError()
+
+ def seek(self, position):
+ self._position = position
+
+ def read(self):
+ logging.debug('ResultSet.read position: %r', self._position)
+
+ if self._position == -1:
+ self.seek(0)
+
+ if self._position < self._offset:
+ remaining_forward_entries = 0
+ else:
+ remaining_forward_entries = self._offset + len(self._cache) - \
+ self._position
+
+ if self._position > self._offset + len(self._cache):
+ remaining_backwards_entries = 0
+ else:
+ remaining_backwards_entries = self._position - self._offset
+
+ last_cached_entry = self._offset + len(self._cache)
+
+ if remaining_forward_entries <= 0 and remaining_backwards_entries <= 0:
+
+ # Total cache miss: remake it
+ limit = self._page_size * MIN_PAGES_TO_CACHE
+ offset = max(0, self._position - limit / 2)
+ logging.debug('remaking cache, offset: %r limit: %r', offset,
+ limit)
+ query = self._query.copy()
+ query['limit'] = limit
+ query['offset'] = offset
+ entries, self._total_count = self.find(query)
+
+ del self._cache[:]
+ self._cache.append_all(entries)
+ self._offset = offset
+
+ elif remaining_forward_entries <= 0 and remaining_backwards_entries > 0:
+
+ # Add one page to the end of cache
+ logging.debug('appending one more page, offset: %r',
+ last_cached_entry)
+ query = self._query.copy()
+ query['limit'] = self._page_size
+ query['offset'] = last_cached_entry
+ entries, self._total_count = self.find(query)
+
+ # update cache
+ self._cache.append_all(entries)
+
+ # apply the cache limit
+ cache_limit = self._page_size * MAX_PAGES_TO_CACHE
+ objects_excess = len(self._cache) - cache_limit
+ if objects_excess > 0:
+ self._offset += objects_excess
+ del self._cache[:objects_excess]
+
+ elif remaining_forward_entries > 0 and \
+ remaining_backwards_entries <= 0 and self._offset > 0:
+
+ # Add one page to the beginning of cache
+ limit = min(self._offset, self._page_size)
+ self._offset = max(0, self._offset - limit)
+
+ logging.debug('prepending one more page, offset: %r limit: %r',
+ self._offset, limit)
+ query = self._query.copy()
+ query['limit'] = limit
+ query['offset'] = self._offset
+ entries, self._total_count = self.find(query)
+
+ # update cache
+ self._cache.prepend_all(entries)
+
+ # apply the cache limit
+ cache_limit = self._page_size * MAX_PAGES_TO_CACHE
+ objects_excess = len(self._cache) - cache_limit
+ if objects_excess > 0:
+ del self._cache[-objects_excess:]
+ else:
+ logging.debug('cache hit and no need to grow the cache')
+
+ return self._cache[self._position - self._offset]
+
+class DatastoreResultSet(BaseResultSet):
+ """Encapsulates the result of a query on the datastore
+ """
+ def __init__(self, query, page_size):
+
+ if query.get('query', '') and not query['query'].startswith('"'):
+ query_text = ''
+ words = query['query'].split(' ')
+ for word in words:
+ if word:
+ if query_text:
+ query_text += ' '
+ query_text += word + '*'
+
+ query['query'] = query_text
+
+ BaseResultSet.__init__(self, query, page_size)
+
+ def find(self, query):
+ entries, total_count = _get_datastore().find(query, PROPERTIES,
+ byte_arrays=True)
+
+ for entry in entries:
+ entry['mountpoint'] = '/'
+
+ return entries, total_count
+
+class InplaceResultSet(BaseResultSet):
+ """Encapsulates the result of a query on a mount point
+ """
+ def __init__(self, query, page_size, mount_point):
+ BaseResultSet.__init__(self, query, page_size)
+ self._mount_point = mount_point
+ self._file_list = None
+ self._pending_directories = 0
+ self._stopped = False
+
+ query_text = query.get('query', '')
+ if query_text.startswith('"') and query_text.endswith('"'):
+ self._regex = re.compile('*%s*' % query_text.strip(['"']))
+ elif query_text:
+ expression = ''
+ for word in query_text.split(' '):
+ expression += '(?=.*%s.*)' % word
+ self._regex = re.compile(expression, re.IGNORECASE)
+ else:
+ self._regex = None
+
+ if query.get('timestamp', ''):
+ self._date_start = int(query['timestamp']['start'])
+ self._date_end = int(query['timestamp']['end'])
+ else:
+ self._date_start = None
+ self._date_end = None
+
+ self._mime_types = query.get('mime_type', [])
+
+ def setup(self):
+ self._file_list = []
+ self._recurse_dir(self._mount_point)
+
+ def stop(self):
+ self._stopped = True
+
+ def setup_ready(self):
+ self._file_list.sort(lambda a, b: b[2] - a[2])
+ self.ready.send(self)
+
+ def find(self, query):
+ if self._file_list is None:
+ raise ValueError('Need to call setup() first')
+
+ if self._stopped:
+ raise ValueError('InplaceResultSet already stopped')
+
+ t = time.time()
+
+ offset = int(query.get('offset', 0))
+ limit = int(query.get('limit', len(self._file_list)))
+ total_count = len(self._file_list)
+
+ files = self._file_list[offset:offset + limit]
+
+ entries = []
+ for file_path, stat, mtime_ in files:
+ metadata = _get_file_metadata(file_path, stat)
+ metadata['mountpoint'] = self._mount_point
+ entries.append(metadata)
+
+ logging.debug('InplaceResultSet.find took %f s.', time.time() - t)
+
+ return entries, total_count
+
+ def _recurse_dir(self, dir_path):
+ self._pending_directories += 1
+ gobject.idle_add(self._idle_recurse_dir, dir_path)
+
+ def _idle_recurse_dir(self, dir_path):
+ try:
+ self._real_recurse_dir(dir_path)
+ finally:
+ self._pending_directories -= 1
+ if self._pending_directories == 0:
+ self.setup_ready()
+
+ def _real_recurse_dir(self, dir_path):
+ if self._stopped:
+ return
+
+ try:
+ dirs = os.listdir(dir_path)
+ except Exception:
+ logging.exception('Error reading directory %r', dir_path)
+ dirs = []
+
+ for entry in dirs:
+ if entry.startswith('.'):
+ continue
+ full_path = dir_path + '/' + entry
+ try:
+ stat = os.stat(full_path)
+ if S_IFMT(stat.st_mode) == S_IFDIR:
+ self._recurse_dir(full_path)
+
+ elif S_IFMT(stat.st_mode) == S_IFREG:
+ add_to_list = True
+
+ if self._regex is not None and \
+ not self._regex.match(full_path):
+ add_to_list = False
+
+ if None not in [self._date_start, self._date_end] and \
+ (stat.st_mtime < self._date_start or
+ stat.st_mtime > self._date_end):
+ add_to_list = False
+
+ if self._mime_types:
+ mime_type = gio.content_type_guess(filename=full_path)
+ if mime_type not in self._mime_types:
+ add_to_list = False
+
+ if add_to_list:
+ file_info = (full_path, stat, int(stat.st_mtime))
+ self._file_list.append(file_info)
+
+ self.progress.send(self)
+
+ except Exception:
+ logging.exception('Error reading file %r', full_path)
+
+def _get_file_metadata(path, stat):
+ client = gconf.client_get_default()
+ return {'uid': path,
+ 'title': os.path.basename(path),
+ 'timestamp': stat.st_mtime,
+ 'mime_type': gio.content_type_guess(filename=path),
+ 'activity': '',
+ 'activity_id': '',
+ 'icon-color': client.get_string('/desktop/sugar/user/color'),
+ 'description': path}
+
+_datastore = None
+def _get_datastore():
+ global _datastore
+ if _datastore is None:
+ bus = dbus.SessionBus()
+ remote_object = bus.get_object(DS_DBUS_SERVICE, DS_DBUS_PATH)
+ _datastore = dbus.Interface(remote_object, DS_DBUS_INTERFACE)
+
+ _datastore.connect_to_signal('Created', _datastore_created_cb)
+ _datastore.connect_to_signal('Updated', _datastore_updated_cb)
+ _datastore.connect_to_signal('Deleted', _datastore_deleted_cb)
+
+ return _datastore
+
+def _datastore_created_cb(object_id):
+ created.send(None, object_id=object_id)
+
+def _datastore_updated_cb(object_id):
+ updated.send(None, object_id=object_id)
+
+def _datastore_deleted_cb(object_id):
+ deleted.send(None, object_id=object_id)
+
+def find(query_, page_size):
+ """Returns a ResultSet
+ """
+ query = query_.copy()
+
+ mount_points = query.pop('mountpoints', ['/'])
+ if mount_points is None or len(mount_points) != 1:
+ raise ValueError('Exactly one mount point must be specified')
+
+ if mount_points[0] == '/':
+ return DatastoreResultSet(query, page_size)
+ else:
+ return InplaceResultSet(query, page_size, mount_points[0])
+
+def _get_mount_point(path):
+ dir_path = os.path.dirname(path)
+ while True:
+ if os.path.ismount(dir_path):
+ return dir_path
+ else:
+ dir_path = dir_path.rsplit(os.sep, 1)[0]
+
+def get(object_id):
+ """Returns the metadata for an object
+ """
+ if os.path.exists(object_id):
+ stat = os.stat(object_id)
+ metadata = _get_file_metadata(object_id, stat)
+ metadata['mountpoint'] = _get_mount_point(object_id)
+ else:
+ metadata = _get_datastore().get_properties(object_id, byte_arrays=True)
+ metadata['mountpoint'] = '/'
+ return metadata
+
+def get_file(object_id):
+ """Returns the file for an object
+ """
+ if os.path.exists(object_id):
+ logging.debug('get_file asked for file with path %r', object_id)
+ return object_id
+ else:
+ logging.debug('get_file asked for entry with id %r', object_id)
+ file_path = _get_datastore().get_filename(object_id)
+ if file_path:
+ return util.TempFilePath(file_path)
+ else:
+ return None
+
+def get_file_size(object_id):
+ """Return the file size for an object
+ """
+ logging.debug('get_file_size %r', object_id)
+ if os.path.exists(object_id):
+ return os.stat(object_id).st_size
+
+ file_path = _get_datastore().get_filename(object_id)
+ if file_path:
+ size = os.stat(file_path).st_size
+ os.remove(file_path)
+ return size
+
+ return 0
+
+def get_unique_values(key):
+ """Returns a list with the different values a property has taken
+ """
+ empty_dict = dbus.Dictionary({}, signature='ss')
+ return _get_datastore().get_uniquevaluesfor(key, empty_dict)
+
+def delete(object_id):
+ """Removes an object from persistent storage
+ """
+ if os.path.exists(object_id):
+ os.unlink(object_id)
+ deleted.send(None, object_id=object_id)
+ else:
+ _get_datastore().delete(object_id)
+
+def copy(metadata, mount_point):
+ """Copies an object to another mount point
+ """
+ metadata = get(metadata['uid'])
+ file_path = get_file(metadata['uid'])
+
+ metadata['mountpoint'] = mount_point
+ del metadata['uid']
+
+ return write(metadata, file_path, transfer_ownership=False)
+
+def write(metadata, file_path='', update_mtime=True, transfer_ownership=True):
+ """Creates or updates an entry for that id
+ """
+ logging.debug('model.write %r %r %r', metadata.get('uid', ''), file_path,
+ update_mtime)
+ if update_mtime:
+ metadata['mtime'] = datetime.now().isoformat()
+ metadata['timestamp'] = int(time.time())
+
+ if metadata.get('mountpoint', '/') == '/':
+ if metadata.get('uid', ''):
+ object_id = _get_datastore().update(metadata['uid'],
+ dbus.Dictionary(metadata),
+ file_path,
+ transfer_ownership)
+ else:
+ object_id = _get_datastore().create(dbus.Dictionary(metadata),
+ file_path,
+ transfer_ownership)
+ else:
+ if not os.path.exists(file_path):
+ raise ValueError('Entries without a file cannot be copied to '
+ 'removable devices')
+
+ file_name = _get_file_name(metadata['title'], metadata['mime_type'])
+ file_name = _get_unique_file_name(metadata['mountpoint'], file_name)
+
+ destination_path = os.path.join(metadata['mountpoint'], file_name)
+ shutil.copy(file_path, destination_path)
+ object_id = destination_path
+ created.send(None, object_id=object_id)
+
+ return object_id
+
+def _get_file_name(title, mime_type):
+ file_name = title
+
+ extension = mime.get_primary_extension(mime_type)
+ if extension is not None and extension:
+ extension = '.' + extension
+ if not file_name.endswith(extension):
+ file_name += extension
+
+ # Invalid characters in VFAT filenames. From
+ # http://en.wikipedia.org/wiki/File_Allocation_Table
+ invalid_chars = ['/', '\\', ':', '*', '?', '"', '<', '>', '|', '\x7F']
+ invalid_chars.extend([chr(x) for x in range(0, 32)])
+ for char in invalid_chars:
+ file_name = file_name.replace(char, '_')
+
+ # FAT limit is 255, leave some space for uniqueness
+ max_len = 250
+ if len(file_name) > max_len:
+ name, extension = os.path.splitext(file_name)
+ file_name = name[0:max_len - len(extension)] + extension
+
+ return file_name
+
+def _get_unique_file_name(mount_point, file_name):
+ if os.path.exists(os.path.join(mount_point, file_name)):
+ i = 1
+ while len(file_name) <= 255:
+ name, extension = os.path.splitext(file_name)
+ file_name = name + '_' + str(i) + extension
+ if not os.path.exists(os.path.join(mount_point, file_name)):
+ break
+ i += 1
+
+ return file_name
+
+def is_editable(metadata):
+ mountpoint = metadata.get('mountpoint', '/')
+ return mountpoint == '/'
+
+created = dispatch.Signal()
+updated = dispatch.Signal()
+deleted = dispatch.Signal()
diff --git a/shell/src/jarabe/journal/objectchooser.py b/shell/src/jarabe/journal/objectchooser.py
new file mode 100644
index 0000000..49af3e6
--- /dev/null
+++ b/shell/src/jarabe/journal/objectchooser.py
@@ -0,0 +1,199 @@
+# Copyright (C) 2007, One Laptop Per Child
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from gettext import gettext as _
+import logging
+
+import gobject
+import gtk
+import wnck
+
+from sugar.graphics import style
+from sugar.graphics.toolbutton import ToolButton
+
+from jarabe.journal.listview import BaseListView
+from jarabe.journal.listmodel import ListModel
+from jarabe.journal.journaltoolbox import SearchToolbar
+from jarabe.journal.volumestoolbar import VolumesToolbar
+
+class ObjectChooser(gtk.Window):
+
+ __gtype_name__ = 'ObjectChooser'
+
+ __gsignals__ = {
+ 'response': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([int]))
+ }
+
+ def __init__(self, parent=None, what_filter=''):
+ gtk.Window.__init__(self)
+ self.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
+ self.set_decorated(False)
+ self.set_position(gtk.WIN_POS_CENTER_ALWAYS)
+ self.set_border_width(style.LINE_WIDTH)
+
+ self._selected_object_id = None
+
+ self.add_events(gtk.gdk.VISIBILITY_NOTIFY_MASK)
+ self.connect('visibility-notify-event',
+ self.__visibility_notify_event_cb)
+ self.connect('delete-event', self.__delete_event_cb)
+ self.connect('key-press-event', self.__key_press_event_cb)
+
+ if parent is None:
+ logging.warning('ObjectChooser: No parent window specified')
+ else:
+ self.connect('realize', self.__realize_cb, parent)
+
+ screen = wnck.screen_get_default()
+ screen.connect('window-closed', self.__window_closed_cb, parent)
+
+ vbox = gtk.VBox()
+ self.add(vbox)
+ vbox.show()
+
+ title_box = TitleBox()
+ title_box.connect('volume-changed', self.__volume_changed_cb)
+ title_box.close_button.connect('clicked',
+ self.__close_button_clicked_cb)
+ title_box.set_size_request(-1, style.GRID_CELL_SIZE)
+ vbox.pack_start(title_box, expand=False)
+ title_box.show()
+
+ separator = gtk.HSeparator()
+ vbox.pack_start(separator, expand=False)
+ separator.show()
+
+ self._toolbar = SearchToolbar()
+ self._toolbar.connect('query-changed', self.__query_changed_cb)
+ self._toolbar.set_size_request(-1, style.GRID_CELL_SIZE)
+ vbox.pack_start(self._toolbar, expand=False)
+ self._toolbar.show()
+
+ self._list_view = ChooserListView()
+ self._list_view.connect('entry-activated', self.__entry_activated_cb)
+ vbox.pack_start(self._list_view)
+ self._list_view.show()
+
+ self._toolbar.set_mount_point('/')
+
+ width = gtk.gdk.screen_width() - style.GRID_CELL_SIZE * 2
+ height = gtk.gdk.screen_height() - style.GRID_CELL_SIZE * 2
+ self.set_size_request(width, height)
+
+ if what_filter:
+ self._toolbar.set_what_filter(what_filter)
+
+ def __realize_cb(self, chooser, parent):
+ self.window.set_transient_for(parent)
+ # TODO: Should we disconnect the signal here?
+
+ def __window_closed_cb(self, screen, window, parent):
+ if window.get_xid() == parent.xid:
+ self.destroy()
+
+ def __entry_activated_cb(self, list_view, uid):
+ self._selected_object_id = uid
+ self.emit('response', gtk.RESPONSE_ACCEPT)
+
+ def __delete_event_cb(self, chooser, event):
+ self.emit('response', gtk.RESPONSE_DELETE_EVENT)
+
+ def __key_press_event_cb(self, widget, event):
+ keyname = gtk.gdk.keyval_name(event.keyval)
+ if keyname == 'Escape':
+ self.emit('response', gtk.RESPONSE_DELETE_EVENT)
+
+ def __close_button_clicked_cb(self, button):
+ self.emit('response', gtk.RESPONSE_DELETE_EVENT)
+
+ def get_selected_object_id(self):
+ return self._selected_object_id
+
+ def __query_changed_cb(self, toolbar, query):
+ self._list_view.update_with_query(query)
+
+ def __volume_changed_cb(self, volume_toolbar, mount_point):
+ logging.debug('Selected volume: %r.', mount_point)
+ self._toolbar.set_mount_point(mount_point)
+
+ def __visibility_notify_event_cb(self, window, event):
+ logging.debug('visibility_notify_event_cb %r', self)
+ visible = event.state == gtk.gdk.VISIBILITY_FULLY_OBSCURED
+ self._list_view.set_is_visible(visible)
+
+class TitleBox(VolumesToolbar):
+ __gtype_name__ = 'TitleBox'
+
+ def __init__(self):
+ VolumesToolbar.__init__(self)
+
+ label = gtk.Label()
+ label.set_markup('<b>%s</b>' % _('Choose an object'))
+ label.set_alignment(0, 0.5)
+ self._add_widget(label, expand=True)
+
+ self.close_button = ToolButton(icon_name='dialog-cancel')
+ self.close_button.set_tooltip(_('Close'))
+ self.insert(self.close_button, -1)
+ self.close_button.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()
+
+class ChooserListView(BaseListView):
+ __gtype_name__ = 'ChooserListView'
+
+ __gsignals__ = {
+ 'entry-activated': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([str])),
+ }
+
+ def __init__(self):
+ BaseListView.__init__(self)
+
+ self.cell_icon.props.show_palette = False
+ self.tree_view.props.hover_selection = True
+
+ self.tree_view.connect('button-release-event',
+ self.__button_release_event_cb)
+
+ def __entry_activated_cb(self, entry):
+ self.emit('entry-activated', entry)
+
+ def __button_release_event_cb(self, tree_view, event):
+ if event.window != tree_view.get_bin_window():
+ return False
+
+ pos = tree_view.get_path_at_pos(event.x, event.y)
+ if pos is None:
+ return False
+
+ path, column_, x_, y_ = pos
+ uid = tree_view.get_model()[path][ListModel.COLUMN_UID]
+ self.emit('entry-activated', uid)
+
+ return False
+
diff --git a/shell/src/jarabe/journal/palettes.py b/shell/src/jarabe/journal/palettes.py
new file mode 100644
index 0000000..7c3e5ff
--- /dev/null
+++ b/shell/src/jarabe/journal/palettes.py
@@ -0,0 +1,235 @@
+# Copyright (C) 2008 One Laptop Per Child
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from gettext import gettext as _
+import logging
+
+import gobject
+import gtk
+import gconf
+
+from sugar.graphics import style
+from sugar.graphics.palette import Palette
+from sugar.graphics.menuitem import MenuItem
+from sugar.graphics.icon import Icon
+from sugar.graphics.xocolor import XoColor
+from sugar import mime
+
+from jarabe.model import bundleregistry
+from jarabe.model import friends
+from jarabe.model import filetransfer
+from jarabe.model import mimeregistry
+from jarabe.journal import misc
+from jarabe.journal import model
+
+class ObjectPalette(Palette):
+
+ __gtype_name__ = 'ObjectPalette'
+
+ __gsignals__ = {
+ 'detail-clicked': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([str])),
+ }
+
+ def __init__(self, metadata, detail=False):
+
+ self._metadata = metadata
+ self._temp_file_path = None
+
+ activity_icon = Icon(icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR)
+ activity_icon.props.file = misc.get_icon_name(metadata)
+ activity_icon.props.xo_color = misc.get_icon_color(metadata)
+
+ if metadata.has_key('title'):
+ title = gobject.markup_escape_text(metadata['title'])
+ else:
+ title = _('Untitled')
+
+ Palette.__init__(self, primary_text=title,
+ icon=activity_icon)
+
+ if metadata.get('activity_id', ''):
+ resume_label = _('Resume')
+ resume_with_label = _('Resume with')
+ else:
+ resume_label = _('Start')
+ resume_with_label = _('Start with')
+ menu_item = MenuItem(resume_label, 'activity-start')
+ menu_item.connect('activate', self.__start_activate_cb)
+ self.menu.append(menu_item)
+ menu_item.show()
+
+ menu_item = MenuItem(resume_with_label, 'activity-start')
+ self.menu.append(menu_item)
+ menu_item.show()
+ start_with_menu = StartWithMenu(self._metadata)
+ menu_item.set_submenu(start_with_menu)
+
+ client = gconf.client_get_default()
+ color = XoColor(client.get_string('/desktop/sugar/user/color'))
+ menu_item = MenuItem(_('Copy'))
+ icon = Icon(icon_name='edit-copy', xo_color=color,
+ icon_size=gtk.ICON_SIZE_MENU)
+ menu_item.set_image(icon)
+ menu_item.connect('activate', self.__copy_activate_cb)
+ self.menu.append(menu_item)
+ menu_item.show()
+
+ menu_item = MenuItem(_('Send to'), 'document-send')
+ self.menu.append(menu_item)
+ menu_item.show()
+
+ friends_menu = FriendsMenu()
+ friends_menu.connect('friend-selected', self.__friend_selected_cb)
+ menu_item.set_submenu(friends_menu)
+
+ if detail == True:
+ menu_item = MenuItem(_('View Details'), 'go-right')
+ menu_item.connect('activate', self.__detail_activate_cb)
+ self.menu.append(menu_item)
+ menu_item.show()
+
+ menu_item = MenuItem(_('Erase'), 'list-remove')
+ menu_item.connect('activate', self.__erase_activate_cb)
+ self.menu.append(menu_item)
+ menu_item.show()
+
+ def __start_activate_cb(self, menu_item):
+ misc.resume(self._metadata)
+
+ def __copy_activate_cb(self, menu_item):
+ clipboard = gtk.Clipboard()
+ clipboard.set_with_data([('text/uri-list', 0, 0)],
+ self.__clipboard_get_func_cb,
+ self.__clipboard_clear_func_cb)
+
+ def __clipboard_get_func_cb(self, clipboard, selection_data, info, data):
+ # Get hold of a reference so the temp file doesn't get deleted
+ self._temp_file_path = model.get_file(self._metadata['uid'])
+ logging.debug('__clipboard_get_func_cb %r', self._temp_file_path)
+ selection_data.set_uris(['file://' + self._temp_file_path])
+
+ def __clipboard_clear_func_cb(self, clipboard, data):
+ # Release and delete the temp file
+ self._temp_file_path = None
+
+ def __erase_activate_cb(self, menu_item):
+ model.delete(self._metadata['uid'])
+
+ def __detail_activate_cb(self, menu_item):
+ self.emit('detail-clicked', self._metadata['uid'])
+
+ def __friend_selected_cb(self, menu_item, buddy):
+ logging.debug('__friend_selected_cb')
+ file_name = model.get_file(self._metadata['uid'])
+
+ title = str(self._metadata['title'])
+ description = str(self._metadata.get('description', ''))
+ mime_type = str(self._metadata['mime_type'])
+
+ if not mime_type:
+ mime_type = mime.get_for_file(file_name)
+
+ filetransfer.start_transfer(buddy, file_name, title, description,
+ mime_type)
+
+class FriendsMenu(gtk.Menu):
+ __gtype_name__ = 'JournalFriendsMenu'
+
+ __gsignals__ = {
+ 'friend-selected' : (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([object])),
+ }
+
+ def __init__(self):
+ gobject.GObject.__init__(self)
+
+ if filetransfer.file_transfer_available():
+ friends_model = friends.get_model()
+ for friend in friends_model:
+ if friend.is_present():
+ menu_item = MenuItem(text_label=friend.get_nick(),
+ icon_name='computer-xo',
+ xo_color=friend.get_color())
+ menu_item.connect('activate', self.__item_activate_cb,
+ friend)
+ self.append(menu_item)
+ menu_item.show()
+
+ if not self.get_children():
+ menu_item = MenuItem(_('No friends present'))
+ menu_item.set_sensitive(False)
+ self.append(menu_item)
+ menu_item.show()
+ else:
+ menu_item = MenuItem(_('No valid connection found'))
+ menu_item.set_sensitive(False)
+ self.append(menu_item)
+ menu_item.show()
+
+ def __item_activate_cb(self, menu_item, friend):
+ self.emit('friend-selected', friend)
+
+
+class StartWithMenu(gtk.Menu):
+ __gtype_name__ = 'JournalStartWithMenu'
+
+ def __init__(self, metadata):
+ gobject.GObject.__init__(self)
+
+ self._metadata = metadata
+
+ for activity_info in misc.get_activities(metadata):
+ menu_item = MenuItem(activity_info.get_name())
+ menu_item.set_image(Icon(file=activity_info.get_icon(),
+ icon_size=gtk.ICON_SIZE_MENU))
+ menu_item.connect('activate', self.__item_activate_cb,
+ activity_info.get_bundle_id())
+ self.append(menu_item)
+ menu_item.show()
+
+ if not self.get_children():
+ if metadata.get('activity_id', ''):
+ resume_label = _('No activity to resume entry')
+ else:
+ resume_label = _('No activity to start entry')
+ menu_item = MenuItem(resume_label)
+ menu_item.set_sensitive(False)
+ self.append(menu_item)
+ menu_item.show()
+
+ def __item_activate_cb(self, menu_item, service_name):
+ mime_type = self._metadata.get('mime_type', '')
+ if mime_type:
+ mime_registry = mimeregistry.get_registry()
+ mime_registry.set_default_activity(mime_type, service_name)
+ misc.resume(self._metadata, service_name)
+
+
+class BuddyPalette(Palette):
+ def __init__(self, buddy):
+ self._buddy = buddy
+
+ nick, colors = buddy
+ buddy_icon = Icon(icon_name='computer-xo',
+ icon_size=style.STANDARD_ICON_SIZE,
+ xo_color=XoColor(colors))
+
+ Palette.__init__(self, primary_text=nick,
+ icon=buddy_icon)
+
+ # TODO: Support actions on buddies, like make friend, invite, etc.
diff --git a/shell/src/jarabe/journal/volumestoolbar.py b/shell/src/jarabe/journal/volumestoolbar.py
new file mode 100644
index 0000000..8b7786f
--- /dev/null
+++ b/shell/src/jarabe/journal/volumestoolbar.py
@@ -0,0 +1,207 @@
+# Copyright (C) 2007, One Laptop Per Child
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import logging
+import os
+from gettext import gettext as _
+
+import gobject
+import gio
+import gtk
+import gconf
+
+from sugar.graphics.radiotoolbutton import RadioToolButton
+from sugar.graphics.palette import Palette
+from sugar.graphics.xocolor import XoColor
+
+from jarabe.journal import model
+from jarabe.view.palettes import VolumePalette
+
+class VolumesToolbar(gtk.Toolbar):
+ __gtype_name__ = 'VolumesToolbar'
+
+ __gsignals__ = {
+ 'volume-changed': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([str])),
+ 'volume-error': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([str, str]))
+ }
+
+ def __init__(self):
+ gtk.Toolbar.__init__(self)
+ self._mount_added_hid = None
+ self._mount_removed_hid = None
+
+ button = JournalButton()
+ button.set_palette(Palette(_('Journal')))
+ button.connect('toggled', self._button_toggled_cb)
+ self.insert(button, 0)
+ button.show()
+ self._volume_buttons = [button]
+
+ self.connect('destroy', self.__destroy_cb)
+
+ gobject.idle_add(self._set_up_volumes)
+
+ def __destroy_cb(self, widget):
+ volume_monitor = gio.volume_monitor_get()
+ volume_monitor.disconnect(self._mount_added_hid)
+ volume_monitor.disconnect(self._mount_removed_hid)
+
+ def _set_up_volumes(self):
+ volume_monitor = gio.volume_monitor_get()
+ self._mount_added_hid = \
+ volume_monitor.connect('mount-added', self.__mount_added_cb)
+ self._mount_removed_hid = \
+ volume_monitor.connect('mount-removed', self.__mount_removed_cb)
+
+ for mount in volume_monitor.get_mounts():
+ self._add_button(mount)
+
+ def __mount_added_cb(self, volume_monitor, mount):
+ self._add_button(mount)
+
+ def __mount_removed_cb(self, volume_monitor, mount):
+ self._remove_button(mount)
+
+ def _add_button(self, mount):
+ logging.debug('VolumeToolbar._add_button: %r', mount.get_name())
+
+ button = VolumeButton(mount)
+ button.props.group = self._volume_buttons[0]
+ button.connect('toggled', self._button_toggled_cb)
+ button.connect('volume-error', self.__volume_error_cb)
+ position = self.get_item_index(self._volume_buttons[-1]) + 1
+ self.insert(button, position)
+ button.show()
+
+ self._volume_buttons.append(button)
+
+ if len(self.get_children()) > 1:
+ self.show()
+
+ def __volume_error_cb(self, button, strerror, severity):
+ self.emit('volume-error', strerror, severity)
+
+ def _button_toggled_cb(self, button):
+ if button.props.active:
+ self.emit('volume-changed', button.mount_point)
+
+ def _unmount_activated_cb(self, menu_item, mount):
+ logging.debug('VolumesToolbar._unmount_activated_cb: %r', mount)
+ mount.unmount(self.__unmount_cb)
+
+ def __unmount_cb(self, source, result):
+ logging.debug('__unmount_cb %r %r', source, result)
+
+ def _get_button_for_mount(self, mount):
+ mount_point = mount.get_root().get_path()
+ for button in self.get_children():
+ if button.mount_point == mount_point:
+ return button
+ logging.error('Couldnt find button with mount_point %r', mount_point)
+ return None
+
+ def _remove_button(self, mount):
+ button = self._get_button_for_mount(mount)
+ self._volume_buttons.remove(button)
+ self.remove(button)
+ self.get_children()[0].props.active = True
+
+ if len(self.get_children()) < 2:
+ self.hide()
+
+ def set_active_volume(self, mount):
+ button = self._get_button_for_mount(mount)
+ button.props.active = True
+
+class BaseButton(RadioToolButton):
+ __gsignals__ = {
+ 'volume-error': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([str, str]))
+ }
+
+ def __init__(self, mount_point):
+ RadioToolButton.__init__(self)
+
+ self.mount_point = mount_point
+
+ self.drag_dest_set(gtk.DEST_DEFAULT_ALL,
+ [('journal-object-id', 0, 0)],
+ gtk.gdk.ACTION_COPY)
+ self.connect('drag-data-received', self._drag_data_received_cb)
+
+ def _drag_data_received_cb(self, widget, drag_context, x, y, selection_data,
+ info, timestamp):
+ object_id = selection_data.data
+ metadata = model.get(object_id)
+ file_path = model.get_file(metadata['uid'])
+ if not file_path or not os.path.exists(file_path):
+ logging.warn('File does not exist')
+ self.emit('volume-error', _('Entries without a file cannot'
+ ' be copied'), _('Warning'))
+ return
+
+ try:
+ model.copy(metadata, self.mount_point)
+ except IOError:
+ logging.exception('BaseButton._drag_data_received_cb: Error'
+ 'while copying')
+ self.emit('volume-error', _('Input/Output error'), _('Error'))
+
+class VolumeButton(BaseButton):
+ def __init__(self, mount):
+ self._mount = mount
+ mount_point = mount.get_root().get_path()
+ BaseButton.__init__(self, mount_point)
+
+ icon_name = None
+ icon_theme = gtk.icon_theme_get_default()
+ for icon_name in mount.get_icon().props.names:
+ icon_info = icon_theme.lookup_icon(icon_name,
+ gtk.ICON_SIZE_LARGE_TOOLBAR, 0)
+ if icon_info is not None:
+ break
+
+ if icon_name is None:
+ icon_name = 'drive'
+
+ self.props.named_icon = icon_name
+
+ # TODO: retrieve the colors from the owner of the device
+ client = gconf.client_get_default()
+ color = XoColor(client.get_string('/desktop/sugar/user/color'))
+ self.props.xo_color = color
+
+ def create_palette(self):
+ palette = VolumePalette(self._mount)
+ #palette.props.invoker = FrameWidgetInvoker(self)
+ #palette.set_group_id('frame')
+ return palette
+
+class JournalButton(BaseButton):
+ def __init__(self):
+ BaseButton.__init__(self, mount_point='/')
+
+ self.props.named_icon = 'activity-journal'
+
+ client = gconf.client_get_default()
+ color = XoColor(client.get_string('/desktop/sugar/user/color'))
+ self.props.xo_color = color
+
diff --git a/shell/src/jarabe/model/Makefile.am b/shell/src/jarabe/model/Makefile.am
new file mode 100644
index 0000000..92e8712
--- /dev/null
+++ b/shell/src/jarabe/model/Makefile.am
@@ -0,0 +1,19 @@
+sugardir = $(pythondir)/jarabe/model
+sugar_PYTHON = \
+ adhoc.py \
+ __init__.py \
+ buddy.py \
+ bundleregistry.py \
+ filetransfer.py \
+ friends.py \
+ invites.py \
+ olpcmesh.py \
+ mimeregistry.py \
+ neighborhood.py \
+ network.py \
+ notifications.py \
+ shell.py \
+ screen.py \
+ session.py \
+ sound.py \
+ telepathyclient.py
diff --git a/shell/src/jarabe/model/__init__.py b/shell/src/jarabe/model/__init__.py
new file mode 100644
index 0000000..a9dd95a
--- /dev/null
+++ b/shell/src/jarabe/model/__init__.py
@@ -0,0 +1,16 @@
+# Copyright (C) 2006-2007, Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
diff --git a/shell/src/jarabe/model/adhoc.py b/shell/src/jarabe/model/adhoc.py
new file mode 100644
index 0000000..ad0c941
--- /dev/null
+++ b/shell/src/jarabe/model/adhoc.py
@@ -0,0 +1,292 @@
+# Copyright (C) 2010 One Laptop per Child
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import logging
+
+import dbus
+import gobject
+
+from jarabe.model import network
+from jarabe.model.network import Settings
+from sugar.util import unique_id
+from jarabe.model.network import IP4Config
+
+_NM_SERVICE = 'org.freedesktop.NetworkManager'
+_NM_IFACE = 'org.freedesktop.NetworkManager'
+_NM_PATH = '/org/freedesktop/NetworkManager'
+_NM_DEVICE_IFACE = 'org.freedesktop.NetworkManager.Device'
+_NM_WIRELESS_IFACE = 'org.freedesktop.NetworkManager.Device.Wireless'
+_NM_ACCESSPOINT_IFACE = 'org.freedesktop.NetworkManager.AccessPoint'
+_NM_ACTIVE_CONN_IFACE = 'org.freedesktop.NetworkManager.Connection.Active'
+
+
+_adhoc_manager_instance = None
+def get_adhoc_manager_instance():
+ global _adhoc_manager_instance
+ if _adhoc_manager_instance is None:
+ _adhoc_manager_instance = AdHocManager()
+ return _adhoc_manager_instance
+
+
+class AdHocManager(gobject.GObject):
+ """To mimic the mesh behavior on devices where mesh hardware is
+ not available we support the creation of an Ad-hoc network on
+ three channels 1, 6, 11. If Sugar sees no "known" network when it
+ starts, it does autoconnect to an Ad-hoc network.
+
+ """
+
+ __gsignals__ = {
+ 'members-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])),
+ 'state-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT]))
+ }
+
+ _AUTOCONNECT_TIMEOUT = 30
+ _CHANNEL_1 = 1
+ _CHANNEL_6 = 6
+ _CHANNEL_11 = 11
+
+ def __init__(self):
+ gobject.GObject.__init__(self)
+
+ self._bus = dbus.SystemBus()
+ self._device = None
+ self._idle_source = 0
+ self._listening_called = 0
+ self._device_state = network.DEVICE_STATE_UNKNOWN
+
+ self._current_channel = None
+ self._networks = {self._CHANNEL_1: None,
+ self._CHANNEL_6: None,
+ self._CHANNEL_11: None}
+
+ def start_listening(self, device):
+ self._listening_called += 1
+ if self._listening_called > 1:
+ raise RuntimeError('The start listening method can' \
+ ' only be called once.')
+
+ self._device = device
+ props = dbus.Interface(device, 'org.freedesktop.DBus.Properties')
+ self._device_state = props.Get(_NM_DEVICE_IFACE, 'State')
+
+ self._bus.add_signal_receiver(self.__device_state_changed_cb,
+ signal_name='StateChanged',
+ path=self._device.object_path,
+ dbus_interface=_NM_DEVICE_IFACE)
+
+ self._bus.add_signal_receiver(self.__wireless_properties_changed_cb,
+ signal_name='PropertiesChanged',
+ path=self._device.object_path,
+ dbus_interface=_NM_WIRELESS_IFACE)
+
+ def stop_listening(self):
+ self._bus.remove_signal_receiver(self.__device_state_changed_cb,
+ signal_name='StateChanged',
+ path=self._device.object_path,
+ dbus_interface=_NM_DEVICE_IFACE)
+ self._bus.remove_signal_receiver(self.__wireless_properties_changed_cb,
+ signal_name='PropertiesChanged',
+ path=self._device.object_path,
+ dbus_interface=_NM_WIRELESS_IFACE)
+
+ def __device_state_changed_cb(self, new_state, old_state, reason):
+ self._device_state = new_state
+ self._update_state()
+
+ def __wireless_properties_changed_cb(self, properties):
+ if 'ActiveAccessPoint' in properties and \
+ properties['ActiveAccessPoint'] != '/':
+ active_ap = self._bus.get_object(_NM_SERVICE,
+ properties['ActiveAccessPoint'])
+ props = dbus.Interface(active_ap, dbus.PROPERTIES_IFACE)
+ props.GetAll(_NM_ACCESSPOINT_IFACE, byte_arrays=True,
+ reply_handler=self.__get_all_ap_props_reply_cb,
+ error_handler=self.__get_all_ap_props_error_cb)
+
+ def __get_all_ap_props_reply_cb(self, properties):
+ if properties['Mode'] == network.NM_802_11_MODE_ADHOC and \
+ 'Frequency' in properties:
+ frequency = properties['Frequency']
+ self._current_channel = network.frequency_to_channel(frequency)
+ else:
+ self._current_channel = None
+ self._update_state()
+
+ def __get_all_ap_props_error_cb(self, err):
+ logging.error('Error getting the access point properties: %s', err)
+
+ def _update_state(self):
+ self.emit('state-changed', self._current_channel, self._device_state)
+
+ def _have_configured_connections(self):
+ return len(network.get_settings().connections) > 0
+
+ def autoconnect(self):
+ """Autoconnect to an Ad-hoc network"""
+ if self._device_state != network.DEVICE_STATE_DISCONNECTED:
+ return
+ elif self._have_configured_connections():
+ self._autoconnect_adhoc_timer()
+ else:
+ self._autoconnect_adhoc()
+
+ def _autoconnect_adhoc_timer(self):
+ """Start a timer which basically looks for 30 seconds of inactivity
+ on the device, then does autoconnect to an Ad-hoc network.
+
+ """
+ if self._idle_source != 0:
+ gobject.source_remove(self._idle_source)
+ self._idle_source = gobject.timeout_add_seconds( \
+ self._AUTOCONNECT_TIMEOUT, self.__idle_check_cb)
+
+ def __idle_check_cb(self):
+ if self._device_state == network.DEVICE_STATE_DISCONNECTED:
+ logging.debug("Connect to Ad-hoc network due to inactivity.")
+ self._autoconnect_adhoc()
+ return False
+
+ def _autoconnect_adhoc(self):
+ """First we try if there is an Ad-hoc network that is used by other
+ learners in the area, if not we default to channel 1.
+
+ """
+ if self._networks[self._CHANNEL_1] is not None:
+ self._connect(self._CHANNEL_1)
+ elif self._networks[self._CHANNEL_6] is not None:
+ self._connect(self._CHANNEL_6)
+ elif self._networks[self._CHANNEL_11] is not None:
+ self._connect(self._CHANNEL_11)
+ else:
+ self._connect(self._CHANNEL_1)
+
+ def activate_channel(self, channel):
+ """Activate a sugar Ad-hoc network.
+
+ Keyword arguments:
+ channel -- Channel to connect to (should be 1, 6, 11)
+
+ """
+ self._connect(channel)
+
+ def _connect(self, channel):
+ name = "Ad-hoc Network %d" % channel
+ connection = network.find_connection_by_ssid(name)
+ if connection is None:
+ settings = Settings()
+ settings.connection.id = name
+ settings.connection.uuid = unique_id()
+ settings.connection.type = '802-11-wireless'
+ settings.wireless.ssid = dbus.ByteArray(name)
+ settings.wireless.band = 'bg'
+ settings.wireless.channel = channel
+ settings.wireless.mode = 'adhoc'
+ settings.ip4_config = IP4Config()
+ settings.ip4_config.method = 'link-local'
+
+ connection = network.add_connection(name, settings)
+
+ obj = self._bus.get_object(_NM_SERVICE, _NM_PATH)
+ netmgr = dbus.Interface(obj, _NM_IFACE)
+
+ netmgr.ActivateConnection(network.SETTINGS_SERVICE,
+ connection.path,
+ self._device.object_path,
+ '/',
+ reply_handler=self.__activate_reply_cb,
+ error_handler=self.__activate_error_cb)
+
+ def deactivate_active_channel(self):
+ """Deactivate the current active channel."""
+ obj = self._bus.get_object(_NM_SERVICE, _NM_PATH)
+ netmgr = dbus.Interface(obj, _NM_IFACE)
+
+ netmgr_props = dbus.Interface(netmgr, dbus.PROPERTIES_IFACE)
+ netmgr_props.Get(_NM_IFACE, 'ActiveConnections', \
+ reply_handler=self.__get_active_connections_reply_cb,
+ error_handler=self.__get_active_connections_error_cb)
+
+ def __get_active_connections_reply_cb(self, active_connections_o):
+ for connection_o in active_connections_o:
+ obj = self._bus.get_object(_NM_IFACE, connection_o)
+ props = dbus.Interface(obj, dbus.PROPERTIES_IFACE)
+ state = props.Get(_NM_ACTIVE_CONN_IFACE, 'State')
+ if state == network.NM_ACTIVE_CONNECTION_STATE_ACTIVATED:
+ access_point_o = props.Get(_NM_ACTIVE_CONN_IFACE,
+ 'SpecificObject')
+ if access_point_o != '/':
+ obj = self._bus.get_object(_NM_SERVICE, _NM_PATH)
+ netmgr = dbus.Interface(obj, _NM_IFACE)
+ netmgr.DeactivateConnection(connection_o)
+
+ def __get_active_connections_error_cb(self, err):
+ logging.error('Error getting the active connections: %s', err)
+
+ def __activate_reply_cb(self, connection):
+ logging.debug('Ad-hoc network created: %s', connection)
+
+ def __activate_error_cb(self, err):
+ logging.error('Failed to create Ad-hoc network: %s', err)
+
+ def add_access_point(self, access_point):
+ """Add an access point to a network and notify the view to idicate
+ the member change.
+
+ Keyword arguments:
+ access_point -- Access Point
+
+ """
+ if access_point.name.endswith(' 1'):
+ self._networks[self._CHANNEL_1] = access_point
+ self.emit('members-changed', self._CHANNEL_1, True)
+ elif access_point.name.endswith(' 6'):
+ self._networks[self._CHANNEL_6] = access_point
+ self.emit('members-changed', self._CHANNEL_6, True)
+ elif access_point.name.endswith('11'):
+ self._networks[self._CHANNEL_11] = access_point
+ self.emit('members-changed', self._CHANNEL_11, True)
+
+ def is_sugar_adhoc_access_point(self, ap_object_path):
+ """Checks whether an access point is part of a sugar Ad-hoc network.
+
+ Keyword arguments:
+ ap_object_path -- Access Point object path
+
+ Return: Boolean
+
+ """
+ for access_point in self._networks.values():
+ if access_point is not None:
+ if access_point.model.object_path == ap_object_path:
+ return True
+ return False
+
+ def remove_access_point(self, ap_object_path):
+ """Remove an access point from a sugar Ad-hoc network.
+
+ Keyword arguments:
+ ap_object_path -- Access Point object path
+
+ """
+ for channel in self._networks:
+ if self._networks[channel] is not None:
+ if self._networks[channel].model.object_path == ap_object_path:
+ self.emit('members-changed', channel, False)
+ self._networks[channel] = None
+ break
diff --git a/shell/src/jarabe/model/buddy.py b/shell/src/jarabe/model/buddy.py
new file mode 100644
index 0000000..6cf7be9
--- /dev/null
+++ b/shell/src/jarabe/model/buddy.py
@@ -0,0 +1,250 @@
+# Copyright (C) 2006-2007 Red Hat, Inc.
+# Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import logging
+
+import gobject
+import gconf
+import dbus
+from telepathy.client import Connection
+from telepathy.interfaces import CONNECTION
+
+from sugar.graphics.xocolor import XoColor
+from sugar.profile import get_profile
+
+from jarabe.util.telepathy import connection_watcher
+
+_NOT_PRESENT_COLOR = "#d5d5d5,#FFFFFF"
+
+CONNECTION_INTERFACE_BUDDY_INFO = 'org.laptop.Telepathy.BuddyInfo'
+
+class BaseBuddyModel(gobject.GObject):
+ __gtype_name__ = 'SugarBaseBuddyModel'
+
+ def __init__(self, **kwargs):
+ self._key = None
+ self._nick = None
+ self._color = None
+ self._tags = None
+ self._present = False
+ self._current_activity = None
+
+ gobject.GObject.__init__(self, **kwargs)
+
+ def is_present(self):
+ return self._present
+
+ def set_present(self, present):
+ self._present = present
+
+ present = gobject.property(type=bool, default=False, getter=is_present,
+ setter=set_present)
+
+ def get_nick(self):
+ return self._nick
+
+ def set_nick(self, nick):
+ self._nick = nick
+
+ nick = gobject.property(type=object, getter=get_nick, setter=set_nick)
+
+ def get_key(self):
+ return self._key
+
+ def set_key(self, key):
+ self._key = key
+
+ key = gobject.property(type=object, getter=get_key, setter=set_key)
+
+ def get_color(self):
+ return self._color
+
+ def set_color(self, color):
+ self._color = color
+
+ color = gobject.property(type=object, getter=get_color, setter=set_color)
+
+ def get_tags(self):
+ return self._tags
+
+ tags = gobject.property(type=object, getter=get_tags)
+
+ def get_current_activity(self):
+ return self._current_activity
+
+ def set_current_activity(self, current_activity):
+ if self._current_activity != current_activity:
+ self._current_activity = current_activity
+ self.notify('current-activity')
+
+ current_activity = gobject.property(type=object,
+ getter=get_current_activity,
+ setter=set_current_activity)
+
+ def is_owner(self):
+ raise NotImplementedError
+
+ def get_buddy(self):
+ raise NotImplementedError
+
+
+class OwnerBuddyModel(BaseBuddyModel):
+ __gtype_name__ = 'SugarOwnerBuddyModel'
+ def __init__(self):
+ BaseBuddyModel.__init__(self)
+ self.props.present = True
+
+ client = gconf.client_get_default()
+ self.props.nick = client.get_string('/desktop/sugar/user/nick')
+ color = client.get_string('/desktop/sugar/user/color')
+ self.props.color = XoColor(color)
+
+ self.props.key = get_profile().pubkey
+
+ self.connect('notify::nick', self.__property_changed_cb)
+ self.connect('notify::color', self.__property_changed_cb)
+ self.connect('notify::current-activity',
+ self.__current_activity_changed_cb)
+
+ bus = dbus.SessionBus()
+ bus.add_signal_receiver(
+ self.__name_owner_changed_cb,
+ signal_name='NameOwnerChanged',
+ dbus_interface='org.freedesktop.DBus')
+
+ bus_object = bus.get_object(dbus.BUS_DAEMON_NAME, dbus.BUS_DAEMON_PATH)
+ for service in bus_object.ListNames(
+ dbus_interface=dbus.BUS_DAEMON_IFACE):
+ if service.startswith('org.freedesktop.Telepathy.Connection.'):
+ path = '/%s' % service.replace('.', '/')
+ Connection(service, path, bus,
+ ready_handler=self.__connection_ready_cb)
+
+ def __connection_ready_cb(self, connection):
+ self._sync_properties_on_connection(connection)
+
+ def __name_owner_changed_cb(self, name, old, new):
+ if name.startswith(CONNECTION) and not old and new:
+ path = '/' + name.replace('.', '/')
+ Connection(name, path, ready_handler=self.__connection_ready_cb)
+
+ def __property_changed_cb(self, pspec):
+ self._sync_properties()
+
+ def __current_activity_changed_cb(self, pspec):
+ conn_watcher = connection_watcher.get_instance()
+ for connection in conn_watcher.get_connections():
+ if self.props.current_activity is not None:
+ activity_id = self.props.current_activity.activity_id
+ room_handle = self.props.current_activity.room_handle
+ else:
+ activity_id = ''
+ room_handle = 0
+
+ connection[CONNECTION_INTERFACE_BUDDY_INFO].SetCurrentActivity(
+ activity_id,
+ room_handle,
+ reply_handler=self.__set_current_activity_cb,
+ error_handler=self.__error_handler_cb)
+
+ def __set_current_activity_cb(self):
+ logging.debug('__set_current_activity_cb')
+
+ def _sync_properties(self):
+ conn_watcher = connection_watcher.get_instance()
+ for connection in conn_watcher.get_connections():
+ self._sync_properties_on_connection(connection)
+
+ def _sync_properties_on_connection(self, connection):
+ if CONNECTION_INTERFACE_BUDDY_INFO in connection:
+ properties = {}
+ if self.props.key is not None:
+ properties['key'] = dbus.ByteArray(self.props.key)
+ if self.props.color is not None:
+ properties['color'] = self.props.color.to_string()
+
+ logging.debug('calling SetProperties with %r', properties)
+ connection[CONNECTION_INTERFACE_BUDDY_INFO].SetProperties(
+ properties,
+ reply_handler=self.__set_properties_cb,
+ error_handler=self.__error_handler_cb)
+
+ def __set_properties_cb(self):
+ logging.debug('__set_properties_cb')
+
+ def __error_handler_cb(self, error):
+ raise RuntimeError(error)
+
+ def __connection_added_cb(self, conn_watcher, connection):
+ self._sync_properties_on_connection(connection)
+
+ def is_owner(self):
+ return True
+
+ def get_buddy(self):
+ raise NotImplementedError
+
+
+_owner_instance = None
+def get_owner_instance():
+ global _owner_instance
+ if _owner_instance is None:
+ _owner_instance = OwnerBuddyModel()
+ return _owner_instance
+
+
+class BuddyModel(BaseBuddyModel):
+ __gtype_name__ = 'SugarBuddyModel'
+ def __init__(self, **kwargs):
+
+ self._account = None
+ self._contact_id = None
+
+ BaseBuddyModel.__init__(self, **kwargs)
+
+ def is_owner(self):
+ return False
+
+ def get_buddy(self):
+ raise NotImplementedError
+
+ def get_account(self):
+ return self._account
+
+ def set_account(self, account):
+ self._account = account
+
+ account = gobject.property(type=object, getter=get_account,
+ setter=set_account)
+
+ def get_contact_id(self):
+ return self._contact_id
+
+ def set_contact_id(self, contact_id):
+ self._contact_id = contact_id
+
+ contact_id = gobject.property(type=object, getter=get_contact_id,
+ setter=set_contact_id)
+
+
+class FriendBuddyModel(BuddyModel):
+ __gtype_name__ = 'SugarFriendBuddyModel'
+ def __init__(self, nick, key):
+ BuddyModel.__init__(self, nick=nick, key=key)
+
+ def get_buddy(self):
+ raise NotImplementedError
diff --git a/shell/src/jarabe/model/bundleregistry.py b/shell/src/jarabe/model/bundleregistry.py
new file mode 100644
index 0000000..858655f
--- /dev/null
+++ b/shell/src/jarabe/model/bundleregistry.py
@@ -0,0 +1,444 @@
+# Copyright (C) 2006-2007 Red Hat, Inc.
+# Copyright (C) 2009 Aleksey Lim
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import logging
+import traceback
+
+import gobject
+import gio
+import simplejson
+
+from sugar.bundle.activitybundle import ActivityBundle
+from sugar.bundle.contentbundle import ContentBundle
+from jarabe.journal.journalentrybundle import JournalEntryBundle
+from sugar.bundle.bundle import MalformedBundleException, \
+ AlreadyInstalledException, RegistrationException
+from sugar import env
+
+from jarabe import config
+from jarabe.model import mimeregistry
+
+class BundleRegistry(gobject.GObject):
+ """Tracks the available activity bundles"""
+
+ __gsignals__ = {
+ 'bundle-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ 'bundle-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ 'bundle-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT]))
+ }
+
+ def __init__(self):
+ logging.debug('STARTUP: Loading the bundle registry')
+ gobject.GObject.__init__(self)
+
+ self._mime_defaults = self._load_mime_defaults()
+
+ self._bundles = []
+ # hold a reference to the monitors so they don't get disposed
+ self._gio_monitors = []
+
+ user_path = env.get_user_activities_path()
+ for activity_dir in [user_path, config.activities_path]:
+ self._scan_directory(activity_dir)
+ directory = gio.File(activity_dir)
+ monitor = directory.monitor_directory()
+ monitor.connect('changed', self.__file_monitor_changed_cb)
+ self._gio_monitors.append(monitor)
+
+ self._last_defaults_mtime = -1
+ self._favorite_bundles = {}
+
+ try:
+ self._load_favorites()
+ except Exception:
+ logging.exception('Error while loading favorite_activities.')
+
+ self._merge_default_favorites()
+
+ def __file_monitor_changed_cb(self, monitor, one_file, other_file,
+ event_type):
+ if not one_file.get_path().endswith('.activity'):
+ return
+ if event_type == gio.FILE_MONITOR_EVENT_CREATED:
+ self.add_bundle(one_file.get_path(), install_mime_type=True)
+ elif event_type == gio.FILE_MONITOR_EVENT_DELETED:
+ self.remove_bundle(one_file.get_path())
+
+ def _load_mime_defaults(self):
+ defaults = {}
+
+ f = open(os.path.join(config.data_path, 'mime.defaults'), 'r')
+ for line in f.readlines():
+ line = line.strip()
+ if line and not line.startswith('#'):
+ mime = line[:line.find(' ')]
+ handler = line[line.rfind(' ') + 1:]
+ defaults[mime] = handler
+ f.close()
+
+ return defaults
+
+ def _get_favorite_key(self, bundle_id, version):
+ """We use a string as a composite key for the favorites dictionary
+ because JSON doesn't support tuples and python won't accept a list
+ as a dictionary key.
+ """
+ if ' ' in bundle_id:
+ raise ValueError('bundle_id cannot contain spaces')
+ return '%s %s' % (bundle_id, version)
+
+ def _load_favorites(self):
+ favorites_path = env.get_profile_path('favorite_activities')
+ if os.path.exists(favorites_path):
+ favorites_data = simplejson.load(open(favorites_path))
+
+ favorite_bundles = favorites_data['favorites']
+ if not isinstance(favorite_bundles, dict):
+ raise ValueError('Invalid format in %s.' % favorites_path)
+ if favorite_bundles:
+ first_key = favorite_bundles.keys()[0]
+ if not isinstance(first_key, basestring):
+ raise ValueError('Invalid format in %s.' % favorites_path)
+
+ first_value = favorite_bundles.values()[0]
+ if first_value is not None and \
+ not isinstance(first_value, dict):
+ raise ValueError('Invalid format in %s.' % favorites_path)
+
+ self._last_defaults_mtime = float(favorites_data['defaults-mtime'])
+ self._favorite_bundles = favorite_bundles
+
+ def _merge_default_favorites(self):
+ default_activities = []
+ defaults_path = os.path.join(config.data_path, 'activities.defaults')
+ if os.path.exists(defaults_path):
+ file_mtime = os.stat(defaults_path).st_mtime
+ if file_mtime > self._last_defaults_mtime:
+ f = open(defaults_path, 'r')
+ for line in f.readlines():
+ line = line.strip()
+ if line and not line.startswith('#'):
+ default_activities.append(line)
+ f.close()
+ self._last_defaults_mtime = file_mtime
+
+ if not default_activities:
+ return
+
+ for bundle_id in default_activities:
+ max_version = -1
+ for bundle in self._bundles:
+ if bundle.get_bundle_id() == bundle_id and \
+ max_version < bundle.get_activity_version():
+ max_version = bundle.get_activity_version()
+
+ key = self._get_favorite_key(bundle_id, max_version)
+ if max_version > -1 and key not in self._favorite_bundles:
+ self._favorite_bundles[key] = None
+
+ logging.debug('After merging: %r', self._favorite_bundles)
+
+ self._write_favorites_file()
+
+ def get_bundle(self, bundle_id):
+ """Returns a bundle given service name or substring,
+ returns None if there is either no match, or more than one
+ match by substring."""
+ result = []
+ key = bundle_id.lower()
+
+ for bundle in self._bundles:
+ name = bundle.get_bundle_id()
+ if name == bundle_id:
+ return bundle
+ if key in name.lower():
+ result.append(bundle)
+ if len(result) == 1:
+ return result[0]
+ return None
+
+ def __iter__(self):
+ return self._bundles.__iter__()
+
+ def __len__(self):
+ return len(self._bundles)
+
+ def _scan_directory(self, path):
+ if not os.path.isdir(path):
+ return
+
+ # Sort by mtime to ensure a stable activity order
+ bundles = {}
+ for f in os.listdir(path):
+ if not f.endswith('.activity'):
+ continue
+ try:
+ bundle_dir = os.path.join(path, f)
+ if os.path.isdir(bundle_dir):
+ bundles[bundle_dir] = os.stat(bundle_dir).st_mtime
+ except Exception:
+ logging.error('Error while processing installed activity ' \
+ 'bundle %s:\n%s' % \
+ (bundle_dir, traceback.format_exc()))
+
+ bundle_dirs = bundles.keys()
+ bundle_dirs.sort(lambda d1, d2: cmp(bundles[d1], bundles[d2]))
+ for folder in bundle_dirs:
+ try:
+ self._add_bundle(folder)
+ except Exception, e:
+ logging.error('Error while processing installed activity ' \
+ 'bundle %s:\n%s' % \
+ (folder, traceback.format_exc()))
+
+ def add_bundle(self, bundle_path, install_mime_type=False):
+ bundle = self._add_bundle(bundle_path, install_mime_type)
+ if bundle is not None:
+ self._set_bundle_favorite(bundle.get_bundle_id(),
+ bundle.get_activity_version(),
+ True)
+ self.emit('bundle-added', bundle)
+ return True
+ else:
+ return False
+
+ def _add_bundle(self, bundle_path, install_mime_type=False):
+ logging.debug('STARTUP: Adding bundle %r', bundle_path)
+ try:
+ bundle = ActivityBundle(bundle_path)
+ if install_mime_type:
+ bundle.install_mime_type(bundle_path)
+ except MalformedBundleException:
+ logging.exception('Error loading bundle %r', bundle_path)
+ return None
+
+ bundle_id = bundle.get_bundle_id()
+ installed = self.get_bundle(bundle_id)
+
+ if installed is not None:
+ if installed.get_activity_version() >= \
+ bundle.get_activity_version():
+ logging.debug('Skip old version for %s', bundle_id)
+ return None
+ else:
+ logging.debug('Upgrade %s', bundle_id)
+ self.remove_bundle(installed.get_path())
+
+ self._bundles.append(bundle)
+ return bundle
+
+ def remove_bundle(self, bundle_path):
+ for bundle in self._bundles:
+ if bundle.get_path() == bundle_path:
+ self._bundles.remove(bundle)
+ self.emit('bundle-removed', bundle)
+ return True
+ return False
+
+ def get_activities_for_type(self, mime_type):
+ result = []
+
+ mime = mimeregistry.get_registry()
+ default_bundle_id = mime.get_default_activity(mime_type)
+ default_bundle = None
+
+ for bundle in self._bundles:
+ if bundle.get_mime_types() and mime_type in bundle.get_mime_types():
+
+ if bundle.get_bundle_id() == default_bundle_id:
+ default_bundle = bundle
+ elif self.get_default_for_type(mime_type) == \
+ bundle.get_bundle_id():
+ result.insert(0, bundle)
+ else:
+ result.append(bundle)
+
+ if default_bundle is not None:
+ result.insert(0, default_bundle)
+
+ return result
+
+ def get_default_for_type(self, mime_type):
+ if self._mime_defaults.has_key(mime_type):
+ return self._mime_defaults[mime_type]
+ else:
+ return None
+
+ def _find_bundle(self, bundle_id, version):
+ for bundle in self._bundles:
+ if bundle.get_bundle_id() == bundle_id and \
+ bundle.get_activity_version() == version:
+ return bundle
+ raise ValueError('No bundle %r with version %r exists.' % \
+ (bundle_id, version))
+
+ def set_bundle_favorite(self, bundle_id, version, favorite):
+ changed = self._set_bundle_favorite(bundle_id, version, favorite)
+ if changed:
+ bundle = self._find_bundle(bundle_id, version)
+ self.emit('bundle-changed', bundle)
+
+ def _set_bundle_favorite(self, bundle_id, version, favorite):
+ key = self._get_favorite_key(bundle_id, version)
+ if favorite and not key in self._favorite_bundles:
+ self._favorite_bundles[key] = None
+ elif not favorite and key in self._favorite_bundles:
+ del self._favorite_bundles[key]
+ else:
+ return False
+
+ self._write_favorites_file()
+ return True
+
+ def is_bundle_favorite(self, bundle_id, version):
+ key = self._get_favorite_key(bundle_id, version)
+ return key in self._favorite_bundles
+
+ def set_bundle_position(self, bundle_id, version, x, y):
+ key = self._get_favorite_key(bundle_id, version)
+ if key not in self._favorite_bundles:
+ raise ValueError('Bundle %s %s not favorite' % (bundle_id, version))
+
+ if self._favorite_bundles[key] is None:
+ self._favorite_bundles[key] = {}
+ if 'position' not in self._favorite_bundles[key] or \
+ [x, y] != self._favorite_bundles[key]['position']:
+ self._favorite_bundles[key]['position'] = [x, y]
+ else:
+ return
+
+ self._write_favorites_file()
+ bundle = self._find_bundle(bundle_id, version)
+ self.emit('bundle-changed', bundle)
+
+ def get_bundle_position(self, bundle_id, version):
+ """Get the coordinates where the user wants the representation of this
+ bundle to be displayed. Coordinates are relative to a 1000x1000 area.
+ """
+ key = self._get_favorite_key(bundle_id, version)
+ if key not in self._favorite_bundles or \
+ self._favorite_bundles[key] is None or \
+ 'position' not in self._favorite_bundles[key]:
+ return (-1, -1)
+ else:
+ return tuple(self._favorite_bundles[key]['position'])
+
+ def _write_favorites_file(self):
+ path = env.get_profile_path('favorite_activities')
+ favorites_data = {'defaults-mtime': self._last_defaults_mtime,
+ 'favorites': self._favorite_bundles}
+ simplejson.dump(favorites_data, open(path, 'w'), indent=1)
+
+ def is_installed(self, bundle):
+ # TODO treat ContentBundle in special way
+ # needs rethinking while fixing ContentBundle support
+ if isinstance(bundle, ContentBundle) or \
+ isinstance(bundle, JournalEntryBundle):
+ return bundle.is_installed()
+
+ for installed_bundle in self._bundles:
+ if bundle.get_bundle_id() == installed_bundle.get_bundle_id() and \
+ bundle.get_activity_version() == \
+ installed_bundle.get_activity_version():
+ return True
+ return False
+
+ def install(self, bundle, uid=None):
+ activities_path = env.get_user_activities_path()
+
+ for installed_bundle in self._bundles:
+ if bundle.get_bundle_id() == installed_bundle.get_bundle_id() and \
+ bundle.get_activity_version() <= \
+ installed_bundle.get_activity_version():
+ raise AlreadyInstalledException
+ elif bundle.get_bundle_id() == installed_bundle.get_bundle_id():
+ self.uninstall(installed_bundle, force=True)
+
+ install_dir = env.get_user_activities_path()
+ if isinstance(bundle, JournalEntryBundle):
+ install_path = bundle.install(uid)
+ else:
+ install_path = bundle.install(install_dir)
+
+ # TODO treat ContentBundle in special way
+ # needs rethinking while fixing ContentBundle support
+ if isinstance(bundle, ContentBundle) or \
+ isinstance(bundle, JournalEntryBundle):
+ pass
+ elif not self.add_bundle(install_path):
+ raise RegistrationException
+
+ def uninstall(self, bundle, force=False):
+ # TODO treat ContentBundle in special way
+ # needs rethinking while fixing ContentBundle support
+ if isinstance(bundle, ContentBundle) or \
+ isinstance(bundle, JournalEntryBundle):
+ if bundle.is_installed():
+ bundle.uninstall()
+ else:
+ logging.warning('Not uninstalling, bundle is not installed')
+ return
+
+ act = self.get_bundle(bundle.get_bundle_id())
+ if not force and \
+ act.get_activity_version() != bundle.get_activity_version():
+ logging.warning('Not uninstalling, different bundle present')
+ return
+
+ if not act.is_user_activity():
+ logging.debug('Do not uninstall system activity')
+ return
+
+ install_path = act.get_path()
+
+ bundle.uninstall(install_path, force)
+
+ if not self.remove_bundle(install_path):
+ raise RegistrationException
+
+ def upgrade(self, bundle):
+ act = self.get_bundle(bundle.get_bundle_id())
+ if act is None:
+ logging.warning('Activity not installed')
+ elif act.get_activity_version() == bundle.get_activity_version():
+ logging.debug('No upgrade needed, same version already installed.')
+ return
+ elif act.is_user_activity():
+ try:
+ self.uninstall(bundle, force=True)
+ except Exception:
+ logging.error('Uninstall failed, still trying to install ' \
+ 'newer bundle:\n' + \
+ traceback.format_exc())
+ else:
+ logging.warning('Unable to uninstall system activity, ' \
+ 'installing upgraded version in user activities')
+
+ self.install(bundle)
+
+_instance = None
+
+def get_registry():
+ global _instance
+ if not _instance:
+ _instance = BundleRegistry()
+ return _instance
+
diff --git a/shell/src/jarabe/model/filetransfer.py b/shell/src/jarabe/model/filetransfer.py
new file mode 100644
index 0000000..e0809bb
--- /dev/null
+++ b/shell/src/jarabe/model/filetransfer.py
@@ -0,0 +1,374 @@
+# Copyright (C) 2008 Tomeu Vizoso
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import logging
+import socket
+
+import gobject
+import gio
+import dbus
+from telepathy.interfaces import CONNECTION_INTERFACE_REQUESTS, CHANNEL
+from telepathy.constants import CONNECTION_HANDLE_TYPE_CONTACT, \
+ SOCKET_ADDRESS_TYPE_UNIX, \
+ SOCKET_ACCESS_CONTROL_LOCALHOST
+from telepathy.client import Connection, Channel
+
+from sugar.presence import presenceservice
+from sugar import dispatch
+
+from jarabe.util.telepathy import connection_watcher
+
+FT_STATE_NONE = 0
+FT_STATE_PENDING = 1
+FT_STATE_ACCEPTED = 2
+FT_STATE_OPEN = 3
+FT_STATE_COMPLETED = 4
+FT_STATE_CANCELLED = 5
+
+FT_REASON_NONE = 0
+FT_REASON_REQUESTED = 1
+FT_REASON_LOCAL_STOPPED = 2
+FT_REASON_REMOTE_STOPPED = 3
+FT_REASON_LOCAL_ERROR = 4
+FT_REASON_LOCAL_ERROR = 5
+FT_REASON_REMOTE_ERROR = 6
+
+# FIXME: use constants from tp-python once the spec is undrafted
+CHANNEL_TYPE_FILE_TRANSFER = \
+ 'org.freedesktop.Telepathy.Channel.Type.FileTransfer'
+
+# TODO Move to use splice_async() in Sugar 0.88
+class StreamSplicer(gobject.GObject):
+ _CHUNK_SIZE = 10240 # 10K
+ __gsignals__ = {
+ 'finished': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([])),
+ }
+ def __init__(self, input_stream, output_stream):
+ gobject.GObject.__init__(self)
+
+ self._input_stream = input_stream
+ self._output_stream = output_stream
+ self._pending_buffers = []
+
+ def start(self):
+ self._input_stream.read_async(self._CHUNK_SIZE, self.__read_async_cb,
+ gobject.PRIORITY_LOW)
+
+ def __read_async_cb(self, input_stream, result):
+ data = input_stream.read_finish(result)
+
+ if not data:
+ logging.debug('closing input stream')
+ self._input_stream.close()
+ else:
+ self._pending_buffers.append(data)
+ self._input_stream.read_async(self._CHUNK_SIZE,
+ self.__read_async_cb,
+ gobject.PRIORITY_LOW)
+ self._write_next_buffer()
+
+ def __write_async_cb(self, output_stream, result, user_data):
+ count_ = output_stream.write_finish(result)
+
+ if not self._pending_buffers and \
+ not self._output_stream.has_pending() and \
+ not self._input_stream.has_pending():
+ logging.debug('closing output stream')
+ output_stream.close()
+ self.emit('finished')
+ else:
+ self._write_next_buffer()
+
+ def _write_next_buffer(self):
+ if self._pending_buffers and not self._output_stream.has_pending():
+ data = self._pending_buffers.pop(0)
+ # TODO: we pass the buffer as user_data because of
+ # http://bugzilla.gnome.org/show_bug.cgi?id=564102
+ self._output_stream.write_async(data, self.__write_async_cb,
+ gobject.PRIORITY_LOW,
+ user_data=data)
+
+class BaseFileTransfer(gobject.GObject):
+
+ def __init__(self, connection):
+ gobject.GObject.__init__(self)
+ self._connection = connection
+ self._state = FT_STATE_NONE
+ self._transferred_bytes = 0
+
+ self.channel = None
+ self.buddy = None
+ self.title = None
+ self.file_size = None
+ self.description = None
+ self.mime_type = None
+ self.initial_offset = 0
+ self.reason_last_change = FT_REASON_NONE
+
+ def set_channel(self, channel):
+ self.channel = channel
+ self.channel[CHANNEL_TYPE_FILE_TRANSFER].connect_to_signal(
+ 'FileTransferStateChanged', self.__state_changed_cb)
+ self.channel[CHANNEL_TYPE_FILE_TRANSFER].connect_to_signal(
+ 'TransferredBytesChanged', self.__transferred_bytes_changed_cb)
+ self.channel[CHANNEL_TYPE_FILE_TRANSFER].connect_to_signal(
+ 'InitialOffsetDefined', self.__initial_offset_defined_cb)
+
+ channel_properties = self.channel[dbus.PROPERTIES_IFACE]
+
+ props = channel_properties.GetAll(CHANNEL_TYPE_FILE_TRANSFER)
+ self._state = props['State']
+ self.title = props['Filename']
+ self.file_size = props['Size']
+ self.description = props['Description']
+ self.mime_type = props['ContentType']
+
+ handle = channel_properties.Get(CHANNEL, 'TargetHandle')
+ presence_service = presenceservice.get_instance()
+ self.buddy = presence_service.get_buddy_by_telepathy_handle(
+ self._connection.service_name,
+ self._connection.object_path,
+ handle)
+
+ def __transferred_bytes_changed_cb(self, transferred_bytes):
+ logging.debug('__transferred_bytes_changed_cb %r', transferred_bytes)
+ self.props.transferred_bytes = transferred_bytes
+
+ def _set_transferred_bytes(self, transferred_bytes):
+ self._transferred_bytes = transferred_bytes
+
+ def _get_transferred_bytes(self):
+ return self._transferred_bytes
+
+ transferred_bytes = gobject.property(type=int, default=0,
+ getter=_get_transferred_bytes, setter=_set_transferred_bytes)
+
+ def __initial_offset_defined_cb(self, offset):
+ logging.debug('__initial_offset_defined_cb %r', offset)
+ self.initial_offset = offset
+
+ def __state_changed_cb(self, state, reason):
+ logging.debug('__state_changed_cb %r %r', state, reason)
+ self.reason_last_change = reason
+ self.props.state = state
+
+ def _set_state(self, state):
+ self._state = state
+
+ def _get_state(self):
+ return self._state
+
+ state = gobject.property(type=int, getter=_get_state, setter=_set_state)
+
+ def cancel(self):
+ self.channel[CHANNEL].Close()
+
+class IncomingFileTransfer(BaseFileTransfer):
+ def __init__(self, connection, object_path, props):
+ BaseFileTransfer.__init__(self, connection)
+
+ channel = Channel(connection.service_name, object_path)
+ self.set_channel(channel)
+
+ self.connect('notify::state', self.__notify_state_cb)
+
+ self.destination_path = None
+ self._socket_address = None
+ self._socket = None
+ self._splicer = None
+
+ def accept(self, destination_path):
+ if os.path.exists(destination_path):
+ raise ValueError('Destination path already exists: %r' % \
+ destination_path)
+
+ self.destination_path = destination_path
+
+ channel_ft = self.channel[CHANNEL_TYPE_FILE_TRANSFER]
+ self._socket_address = channel_ft.AcceptFile(SOCKET_ADDRESS_TYPE_UNIX,
+ SOCKET_ACCESS_CONTROL_LOCALHOST, '', 0, byte_arrays=True)
+
+ def __notify_state_cb(self, file_transfer, pspec):
+ logging.debug('__notify_state_cb %r', self.props.state)
+ if self.props.state == FT_STATE_OPEN:
+ # Need to hold a reference to the socket so that python doesn't
+ # close the fd when it goes out of scope
+ self._socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ self._socket.connect(self._socket_address)
+ input_stream = gio.unix.InputStream(self._socket.fileno(), True)
+
+ destination_file = gio.File(self.destination_path)
+ if self.initial_offset == 0:
+ output_stream = destination_file.create()
+ else:
+ output_stream = destination_file.append_to()
+
+ # TODO: Use splice_async when it gets implemented
+ self._splicer = StreamSplicer(input_stream, output_stream)
+ self._splicer.start()
+
+class OutgoingFileTransfer(BaseFileTransfer):
+ def __init__(self, buddy, file_name, title, description, mime_type):
+
+ presence_service = presenceservice.get_instance()
+ name, path = presence_service.get_preferred_connection()
+ connection = Connection(name, path,
+ ready_handler=self.__connection_ready_cb)
+
+ BaseFileTransfer.__init__(self, connection)
+ self.connect('notify::state', self.__notify_state_cb)
+
+ self._file_name = file_name
+ self._socket_address = None
+ self._socket = None
+ self._splicer = None
+ self._output_stream = None
+
+ self.buddy = buddy.get_buddy()
+ self.title = title
+ self.file_size = os.stat(file_name).st_size
+ self.description = description
+ self.mime_type = mime_type
+
+ def __connection_ready_cb(self, connection):
+ handle = self._get_buddy_handle()
+
+ requests = connection[CONNECTION_INTERFACE_REQUESTS]
+ object_path, properties_ = requests.CreateChannel({
+ CHANNEL + '.ChannelType': CHANNEL_TYPE_FILE_TRANSFER,
+ CHANNEL + '.TargetHandleType': CONNECTION_HANDLE_TYPE_CONTACT,
+ CHANNEL + '.TargetHandle': handle,
+ CHANNEL_TYPE_FILE_TRANSFER + '.ContentType': self.mime_type,
+ CHANNEL_TYPE_FILE_TRANSFER + '.Filename': self.title,
+ CHANNEL_TYPE_FILE_TRANSFER + '.Size': self.file_size,
+ CHANNEL_TYPE_FILE_TRANSFER + '.Description': self.description,
+ CHANNEL_TYPE_FILE_TRANSFER + '.InitialOffset': 0})
+
+ self.set_channel(Channel(connection.service_name, object_path))
+
+ channel_file_transfer = self.channel[CHANNEL_TYPE_FILE_TRANSFER]
+ self._socket_address = channel_file_transfer.ProvideFile(
+ SOCKET_ADDRESS_TYPE_UNIX, SOCKET_ACCESS_CONTROL_LOCALHOST, '',
+ byte_arrays=True)
+
+ def _get_buddy_handle(self):
+ object_path = self.buddy.object_path()
+
+ bus = dbus.SessionBus()
+ remote_object = bus.get_object('org.laptop.Sugar.Presence', object_path)
+ ps_buddy = dbus.Interface(remote_object,
+ 'org.laptop.Sugar.Presence.Buddy')
+
+ handles = ps_buddy.GetTelepathyHandles()
+ logging.debug('_get_buddy_handle %r', handles)
+
+ bus_name, object_path, handle = handles[0]
+
+ return handle
+
+ def __notify_state_cb(self, file_transfer, pspec):
+ logging.debug('__notify_state_cb %r', self.props.state)
+ if self.props.state == FT_STATE_OPEN:
+ # Need to hold a reference to the socket so that python doesn't
+ # closes the fd when it goes out of scope
+ self._socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ self._socket.connect(self._socket_address)
+ output_stream = gio.unix.OutputStream(self._socket.fileno(), True)
+
+ logging.debug('opening %s for reading', self._file_name)
+ input_stream = gio.File(self._file_name).read()
+ if self.initial_offset > 0:
+ input_stream.skip(self.initial_offset)
+
+ # TODO: Use splice_async when it gets implemented
+ self._splicer = StreamSplicer(input_stream, output_stream)
+ self._splicer.start()
+
+ def cancel(self):
+ self.channel[CHANNEL].Close()
+
+def _new_channels_cb(connection, channels):
+ for object_path, props in channels:
+ if props[CHANNEL + '.ChannelType'] == CHANNEL_TYPE_FILE_TRANSFER and \
+ not props[CHANNEL + '.Requested']:
+
+ logging.debug('__new_channels_cb %r', object_path)
+
+ incoming_file_transfer = IncomingFileTransfer(connection,
+ object_path, props)
+ new_file_transfer.send(None, file_transfer=incoming_file_transfer)
+
+def _monitor_connection(connection):
+ logging.debug('connection added %r', connection)
+ connection[CONNECTION_INTERFACE_REQUESTS].connect_to_signal('NewChannels',
+ lambda channels: _new_channels_cb(connection, channels))
+
+def _connection_added_cb(conn_watcher, connection):
+ _monitor_connection(connection)
+
+def _connection_removed_cb(conn_watcher, connection):
+ logging.debug('connection removed %r', connection)
+
+def init():
+ conn_watcher = connection_watcher.get_instance()
+ conn_watcher.connect('connection-added', _connection_added_cb)
+ conn_watcher.connect('connection-removed', _connection_removed_cb)
+
+ for connection in conn_watcher.get_connections():
+ _monitor_connection(connection)
+
+def start_transfer(buddy, file_name, title, description, mime_type):
+ outgoing_file_transfer = OutgoingFileTransfer(buddy, file_name, title,
+ description, mime_type)
+ new_file_transfer.send(None, file_transfer=outgoing_file_transfer)
+
+def file_transfer_available():
+ conn_watcher = connection_watcher.get_instance()
+ for connection in conn_watcher.get_connections():
+
+ properties_iface = connection[dbus.PROPERTIES_IFACE]
+ properties = properties_iface.GetAll(CONNECTION_INTERFACE_REQUESTS)
+ classes = properties['RequestableChannelClasses']
+ for prop, allowed_prop in classes:
+
+ channel_type = prop.get(CHANNEL + '.ChannelType', '')
+ target_handle_type = prop.get(CHANNEL + '.TargetHandleType', '')
+
+ if len(prop) == 2 and \
+ channel_type == CHANNEL_TYPE_FILE_TRANSFER and \
+ target_handle_type == CONNECTION_HANDLE_TYPE_CONTACT:
+ return True
+
+ return False
+
+new_file_transfer = dispatch.Signal()
+
+if __name__ == '__main__':
+ import tempfile
+
+ input_stream = gio.File('/home/tomeu/isos/Soas2-200904031934.iso').read()
+ output_stream = gio.File(tempfile.mkstemp()[1]).append_to()
+
+ # TODO: Use splice_async when it gets implemented
+ splicer = StreamSplicer(input_stream, output_stream)
+ splicer.start()
+
+ loop = gobject.MainLoop()
+ loop.run()
+
diff --git a/shell/src/jarabe/model/friends.py b/shell/src/jarabe/model/friends.py
new file mode 100644
index 0000000..b7bf7f1
--- /dev/null
+++ b/shell/src/jarabe/model/friends.py
@@ -0,0 +1,122 @@
+# Copyright (C) 2006-2007 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import logging
+from ConfigParser import ConfigParser
+
+import gobject
+import dbus
+
+from jarabe.model.buddy import BuddyModel
+from sugar import env
+
+class Friends(gobject.GObject):
+ __gsignals__ = {
+ 'friend-added': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([object])),
+ 'friend-removed': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([str]))
+ }
+
+ def __init__(self):
+ gobject.GObject.__init__(self)
+
+ self._friends = {}
+ self._path = os.path.join(env.get_profile_path(), 'friends')
+
+ self.load()
+
+ def has_buddy(self, buddy):
+ return self._friends.has_key(buddy.get_key())
+
+ def add_friend(self, buddy_info):
+ self._friends[buddy_info.get_key()] = buddy_info
+ self.emit('friend-added', buddy_info)
+
+ def make_friend(self, buddy):
+ if not self.has_buddy(buddy):
+ self.add_friend(buddy)
+ self.save()
+
+ def remove(self, buddy_info):
+ del self._friends[buddy_info.get_key()]
+ self.save()
+ self.emit('friend-removed', buddy_info.get_key())
+
+ def __iter__(self):
+ return self._friends.values().__iter__()
+
+ def load(self):
+ cp = ConfigParser()
+
+ try:
+ success = cp.read([self._path])
+ if success:
+ for key in cp.sections():
+ # HACK: don't screw up on old friends files
+ if len(key) < 20:
+ continue
+ buddy = BuddyModel(key=key, nick=cp.get(key, 'nick'))
+ self.add_friend(buddy)
+ except Exception:
+ logging.exception('Error parsing friends file')
+
+ def save(self):
+ cp = ConfigParser()
+
+ for friend in self:
+ section = friend.get_key()
+ cp.add_section(section)
+ cp.set(section, 'nick', friend.get_nick())
+ cp.set(section, 'color', friend.get_color().to_string())
+
+ fileobject = open(self._path, 'w')
+ cp.write(fileobject)
+ fileobject.close()
+
+ self._sync_friends()
+
+ def _sync_friends(self):
+ # XXX: temporary hack
+ # remove this when the shell service has a D-Bus API for buddies
+
+ def friends_synced():
+ pass
+
+ def friends_synced_error(e):
+ logging.error('Error asking presence service to sync friends: %s',
+ e)
+
+ keys = []
+ for friend in self:
+ keys.append(friend.get_key())
+
+ bus = dbus.SessionBus()
+ ps = bus.get_object('org.laptop.Sugar.Presence',
+ '/org/laptop/Sugar/Presence')
+ psi = dbus.Interface(ps, 'org.laptop.Sugar.Presence')
+ psi.SyncFriends(keys,
+ reply_handler=friends_synced,
+ error_handler=friends_synced_error)
+
+_model = None
+
+def get_model():
+ global _model
+ if _model is None:
+ _model = Friends()
+ return _model
diff --git a/shell/src/jarabe/model/invites.py b/shell/src/jarabe/model/invites.py
new file mode 100644
index 0000000..a386d30
--- /dev/null
+++ b/shell/src/jarabe/model/invites.py
@@ -0,0 +1,239 @@
+# Copyright (C) 2006-2007 Red Hat, Inc.
+# Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import logging
+from functools import partial
+
+import gobject
+import dbus
+from telepathy.interfaces import CHANNEL, \
+ CHANNEL_DISPATCHER, \
+ CHANNEL_DISPATCH_OPERATION, \
+ CHANNEL_TYPE_CONTACT_LIST, \
+ CHANNEL_TYPE_DBUS_TUBE, \
+ CHANNEL_TYPE_STREAMED_MEDIA, \
+ CHANNEL_TYPE_STREAM_TUBE, \
+ CHANNEL_TYPE_TEXT, \
+ CLIENT
+from telepathy.constants import HANDLE_TYPE_ROOM
+
+from sugar.graphics.xocolor import XoColor
+
+from jarabe.model import telepathyclient
+from jarabe.model import bundleregistry
+from jarabe.model import neighborhood
+from jarabe.journal import misc
+
+CONNECTION_INTERFACE_ACTIVITY_PROPERTIES = \
+ 'org.laptop.Telepathy.ActivityProperties'
+
+
+class ActivityInvite(object):
+ """Invitation to a shared activity."""
+ def __init__(self, dispatch_operation_path, handle, handler,
+ activity_properties):
+ self.dispatch_operation_path = dispatch_operation_path
+ self._handle = handle
+ self._handler = handler
+
+ if activity_properties is not None:
+ self._activity_properties = activity_properties
+ else:
+ self._activity_properties = {}
+
+ def get_bundle_id(self):
+ if CLIENT in self._handler:
+ return self._handler[len(CLIENT + '.'):]
+ else:
+ return None
+
+ def get_color(self):
+ color = self._activity_properties.get('color', None)
+ return XoColor(color)
+
+ def join(self):
+ logging.error('ActivityInvite.join handler %r', self._handler)
+
+ registry = bundleregistry.get_registry()
+ bundle_id = self.get_bundle_id()
+ bundle = registry.get_bundle(bundle_id)
+ if bundle is None:
+ self._call_handle_with()
+ else:
+ bus = dbus.SessionBus()
+ bus.add_signal_receiver(self.__name_owner_changed_cb,
+ 'NameOwnerChanged',
+ 'org.freedesktop.DBus',
+ arg0=self._handler)
+
+ model = neighborhood.get_model()
+ activity_id = model.get_activity_by_room(self._handle).activity_id
+ misc.launch(bundle, color=self.get_color(), invited=True,
+ activity_id=activity_id)
+
+ def __name_owner_changed_cb(self, name, old_owner, new_owner):
+ logging.debug('ActivityInvite.__name_owner_changed_cb %r %r %r', name,
+ new_owner, old_owner)
+ if name == self._handler and new_owner and not old_owner:
+ self._call_handle_with()
+
+ def _call_handle_with(self):
+ bus = dbus.Bus()
+ obj = bus.get_object(CHANNEL_DISPATCHER, self.dispatch_operation_path)
+ dispatch_operation = dbus.Interface(obj, CHANNEL_DISPATCH_OPERATION)
+ dispatch_operation.HandleWith(self._handler,
+ reply_handler=self.__handle_with_reply_cb,
+ error_handler=self.__handle_with_reply_cb)
+
+ def __handle_with_reply_cb(self, error=None):
+ if error is not None:
+ raise error
+ else:
+ logging.debug('__handle_with_reply_cb')
+
+class Invites(gobject.GObject):
+ __gsignals__ = {
+ 'invite-added': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([object])),
+ 'invite-removed': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([object]))
+ }
+
+ def __init__(self):
+ gobject.GObject.__init__(self)
+
+ self._dispatch_operations = {}
+
+ client_handler = telepathyclient.get_instance()
+ client_handler.got_dispatch_operation.connect(
+ self.__got_dispatch_operation_cb)
+
+ def __got_dispatch_operation_cb(self, **kwargs):
+ logging.debug('__got_dispatch_operation_cb')
+ dispatch_operation_path = kwargs['dispatch_operation_path']
+ channel_path, channel_properties = kwargs['channels'][0]
+ properties = kwargs['properties']
+ channel_type = channel_properties[CHANNEL + '.ChannelType']
+ handle_type = channel_properties[CHANNEL + '.TargetHandleType']
+ handle = channel_properties[CHANNEL + '.TargetHandle']
+
+ if handle_type == HANDLE_TYPE_ROOM and \
+ channel_type == CHANNEL_TYPE_TEXT:
+ logging.debug('May be an activity, checking its properties')
+ connection_path = properties[CHANNEL_DISPATCH_OPERATION +
+ '.Connection']
+ connection_name = connection_path.replace('/', '.')[1:]
+
+ bus = dbus.Bus()
+ connection = bus.get_object(connection_name, connection_path)
+ connection.GetProperties(
+ channel_properties[CHANNEL + '.TargetHandle'],
+ dbus_interface=CONNECTION_INTERFACE_ACTIVITY_PROPERTIES,
+ reply_handler=partial(self.__get_properties_cb,
+ handle,
+ dispatch_operation_path),
+ error_handler=partial(self.__error_handler_cb,
+ handle,
+ channel_properties,
+ dispatch_operation_path))
+ else:
+ self._dispatch_non_sugar_invitation(channel_path,
+ channel_properties,
+ dispatch_operation_path)
+
+ def __get_properties_cb(self, handle, dispatch_operation_path, properties):
+ logging.debug('__get_properties_cb %r', properties)
+ handler = '%s.%s' % (CLIENT, properties['type'])
+ self._add_invite(dispatch_operation_path, handle, handler, properties)
+
+ def __error_handler_cb(self, handle, channel_properties,
+ dispatch_operation_path, error):
+ logging.debug('__error_handler_cb %r', error)
+ exception_name = 'org.freedesktop.Telepathy.Error.NotAvailable'
+ if error.get_dbus_name() == exception_name:
+ self._dispatch_non_sugar_invitation(handle,
+ channel_properties,
+ dispatch_operation_path)
+ else:
+ raise error
+
+ def _dispatch_non_sugar_invitation(self, handle, channel_properties,
+ dispatch_operation_path):
+ handler = None
+ channel_type = channel_properties[CHANNEL + '.ChannelType']
+ if channel_type == CHANNEL_TYPE_CONTACT_LIST:
+ self._handle_with(dispatch_operation_path, CLIENT + '.Sugar')
+ elif channel_type == CHANNEL_TYPE_TEXT:
+ handler = CLIENT + '.org.laptop.Chat'
+ elif channel_type == CHANNEL_TYPE_STREAMED_MEDIA:
+ handler = CLIENT + '.org.laptop.VideoChat'
+ elif channel_type == CHANNEL_TYPE_DBUS_TUBE:
+ handler = channel_properties[CHANNEL_TYPE_DBUS_TUBE +
+ '.ServiceName']
+ elif channel_type == CHANNEL_TYPE_STREAM_TUBE:
+ handler = channel_properties[CHANNEL_TYPE_STREAM_TUBE + '.Service']
+ else:
+ self._handle_with(dispatch_operation_path, '')
+
+ if handler is not None:
+ logging.debug('Adding an invite from a non-Sugar client')
+ self._add_invite(dispatch_operation_path, handle, handler)
+
+ def _handle_with(self, dispatch_operation_path, handler):
+ logging.debug('_handle_with %r %r', dispatch_operation_path, handler)
+ bus = dbus.Bus()
+ obj = bus.get_object(CHANNEL_DISPATCHER, dispatch_operation_path)
+ dispatch_operation = dbus.Interface(obj, CHANNEL_DISPATCH_OPERATION)
+ dispatch_operation.HandleWith(handler,
+ reply_handler=self.__handle_with_reply_cb,
+ error_handler=self.__handle_with_reply_cb)
+
+ def __handle_with_reply_cb(self, error=None):
+ if error is not None:
+ logging.error('__handle_with_reply_cb %r', error)
+ else:
+ logging.debug('__handle_with_reply_cb')
+
+ def _add_invite(self, dispatch_operation_path, handle, handler,
+ activity_properties=None):
+ logging.debug('_add_invite %r %r %r', dispatch_operation_path, handle,
+ handler)
+ if dispatch_operation_path in self._dispatch_operations:
+ # there is no point to have more than one invite for the same
+ # dispatch operation
+ return
+
+ invite = ActivityInvite(dispatch_operation_path, handle, handler,
+ activity_properties)
+ self._dispatch_operations[dispatch_operation_path] = invite
+ self.emit('invite-added', invite)
+
+ def remove_invite(self, invite):
+ del self._dispatch_operations[invite.dispatch_operation_path]
+ self.emit('invite-removed', invite)
+
+ def __iter__(self):
+ return self._dispatch_operations.values().__iter__()
+
+
+_instance = None
+
+def get_instance():
+ global _instance
+ if not _instance:
+ _instance = Invites()
+ return _instance
diff --git a/shell/src/jarabe/model/mimeregistry.py b/shell/src/jarabe/model/mimeregistry.py
new file mode 100644
index 0000000..537f6f3
--- /dev/null
+++ b/shell/src/jarabe/model/mimeregistry.py
@@ -0,0 +1,49 @@
+# Copyright (C) 2009 Aleksey Lim
+#
+# 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 re
+
+import gconf
+
+
+_DEFAULTS_KEY = '/desktop/sugar/journal/defaults'
+_GCONF_INVALID_CHARS = re.compile('[^a-zA-Z0-9-_/.]')
+_instance = None
+
+
+class MimeRegistry(object):
+
+ def __init__(self):
+ # TODO move here all mime_type related code from jarabe modules
+ self._gconf = gconf.client_get_default()
+
+ def get_default_activity(self, mime_type):
+ return self._gconf.get_string(_key_name(mime_type))
+
+ def set_default_activity(self, mime_type, bundle_id):
+ self._gconf.set_string(_key_name(mime_type), bundle_id)
+
+
+def get_registry():
+ global _instance
+ if _instance is None:
+ _instance = MimeRegistry()
+ return _instance
+
+
+def _key_name(mime_type):
+ mime_type = _GCONF_INVALID_CHARS.sub('_', mime_type)
+ return '%s/%s' % (_DEFAULTS_KEY, mime_type)
diff --git a/shell/src/jarabe/model/neighborhood.py b/shell/src/jarabe/model/neighborhood.py
new file mode 100644
index 0000000..90531a6
--- /dev/null
+++ b/shell/src/jarabe/model/neighborhood.py
@@ -0,0 +1,863 @@
+# Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import logging
+from functools import partial
+
+import gobject
+import gconf
+import dbus
+from dbus import PROPERTIES_IFACE
+from telepathy.interfaces import ACCOUNT, \
+ ACCOUNT_MANAGER, \
+ CHANNEL, \
+ CHANNEL_INTERFACE_GROUP, \
+ CHANNEL_TYPE_CONTACT_LIST, \
+ CONNECTION, \
+ CONNECTION_INTERFACE_ALIASING, \
+ CONNECTION_INTERFACE_CONTACTS, \
+ CONNECTION_INTERFACE_REQUESTS, \
+ CONNECTION_INTERFACE_SIMPLE_PRESENCE
+from telepathy.constants import HANDLE_TYPE_LIST, \
+ CONNECTION_PRESENCE_TYPE_OFFLINE, \
+ CONNECTION_STATUS_CONNECTED, \
+ CONNECTION_STATUS_DISCONNECTED
+from telepathy.client import Connection, Channel
+
+from sugar.graphics.xocolor import XoColor
+from sugar.profile import get_profile
+
+from jarabe.model.buddy import BuddyModel, get_owner_instance
+from jarabe.model import bundleregistry
+
+ACCOUNT_MANAGER_SERVICE = 'org.freedesktop.Telepathy.AccountManager'
+ACCOUNT_MANAGER_PATH = '/org/freedesktop/Telepathy/AccountManager'
+CHANNEL_DISPATCHER_SERVICE = 'org.freedesktop.Telepathy.ChannelDispatcher'
+CHANNEL_DISPATCHER_PATH = '/org/freedesktop/Telepathy/ChannelDispatcher'
+SUGAR_CLIENT_SERVICE = 'org.freedesktop.Telepathy.Client.Sugar'
+SUGAR_CLIENT_PATH = '/org/freedesktop/Telepathy/Client/Sugar'
+
+CONNECTION_INTERFACE_BUDDY_INFO = 'org.laptop.Telepathy.BuddyInfo'
+CONNECTION_INTERFACE_ACTIVITY_PROPERTIES = \
+ 'org.laptop.Telepathy.ActivityProperties'
+
+class ActivityModel(gobject.GObject):
+ __gsignals__ = {
+ 'current-buddy-added': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([object])),
+ 'current-buddy-removed': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([object])),
+ 'buddy-added': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([object])),
+ 'buddy-removed': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([object])),
+ }
+ def __init__(self, activity_id, room_handle):
+ gobject.GObject.__init__(self)
+
+ self.activity_id = activity_id
+ self.room_handle = room_handle
+ self._bundle = None
+ self._color = None
+ self._private = True
+ self._name = None
+ self._current_buddies = []
+ self._buddies = []
+
+ def get_color(self):
+ return self._color
+
+ def set_color(self, color):
+ self._color = color
+
+ color = gobject.property(type=object, getter=get_color, setter=set_color)
+
+ def get_bundle(self):
+ return self._bundle
+
+ def set_bundle(self, bundle):
+ self._bundle = bundle
+
+ bundle = gobject.property(type=object, getter=get_bundle, setter=set_bundle)
+
+ def get_name(self):
+ return self._name
+
+ def set_name(self, name):
+ self._name = name
+
+ name = gobject.property(type=object, getter=get_name, setter=set_name)
+
+ def is_private(self):
+ return self._private
+
+ def set_private(self, private):
+ self._private = private
+
+ private = gobject.property(type=object, getter=is_private,
+ setter=set_private)
+
+ def get_buddies(self):
+ return self._buddies
+
+ def add_buddy(self, buddy):
+ self._buddies.append(buddy)
+ self.notify('buddies')
+ self.emit('buddy-added', buddy)
+
+ def remove_buddy(self, buddy):
+ self._buddies.remove(buddy)
+ self.notify('buddies')
+ self.emit('buddy-removed', buddy)
+
+ buddies = gobject.property(type=object, getter=get_buddies)
+
+ def get_current_buddies(self):
+ return self._current_buddies
+
+ def add_current_buddy(self, buddy):
+ self._current_buddies.append(buddy)
+ self.notify('current-buddies')
+ self.emit('current-buddy-added', buddy)
+
+ def remove_current_buddy(self, buddy):
+ self._current_buddies.remove(buddy)
+ self.notify('current-buddies')
+ self.emit('current-buddy-removed', buddy)
+
+ current_buddies = gobject.property(type=object, getter=get_current_buddies)
+
+class _Account(gobject.GObject):
+ __gsignals__ = {
+ 'activity-added': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([object, object])),
+ 'activity-updated': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([object, object])),
+ 'activity-removed': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([object])),
+ 'buddy-added': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([object, object, object])),
+ 'buddy-updated': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([object, object])),
+ 'buddy-removed': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([object])),
+ 'buddy-joined-activity': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([object, object])),
+ 'buddy-left-activity': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([object, object])),
+ 'current-activity-updated': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([object, object])),
+ 'connected': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([])),
+ 'disconnected': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([])),
+ }
+
+ def __init__(self, account_path):
+ gobject.GObject.__init__(self)
+
+ self.object_path = account_path
+
+ self._connection = None
+ self._buddy_handles = {}
+ self._activity_handles = {}
+ self._self_handle = None
+
+ self._buddies_per_activity = {}
+ self._activities_per_buddy = {}
+
+ self._start_listening()
+
+ def _start_listening(self):
+ bus = dbus.Bus()
+ obj = bus.get_object(ACCOUNT_MANAGER_SERVICE, self.object_path)
+ obj.Get(ACCOUNT, 'Connection',
+ reply_handler=self.__got_connection_cb,
+ error_handler=partial(self.__error_handler_cb,
+ 'Account.GetConnection'))
+ obj.connect_to_signal(
+ 'AccountPropertyChanged', self.__account_property_changed_cb)
+
+ def __error_handler_cb(self, function_name, error):
+ raise RuntimeError('Error when calling %s: %s' % (function_name, error))
+
+ def __got_connection_cb(self, connection_path):
+ logging.debug('_Account.__got_connection_cb %r', connection_path)
+
+ if connection_path == '/':
+ # Account has no connection, wait until it has one.
+ return
+
+ self._prepare_connection(connection_path)
+
+ def __account_property_changed_cb(self, properties):
+ logging.debug('_Account.__account_property_changed_cb %r %r %r',
+ self.object_path, properties.get('Connection', None),
+ self._connection)
+ if 'Connection' not in properties:
+ return
+ if properties['Connection'] == '/':
+ self._connection = None
+ elif self._connection is None:
+ self._prepare_connection(properties['Connection'])
+
+ def _prepare_connection(self, connection_path):
+ connection_name = connection_path.replace('/', '.')[1:]
+
+ self._connection = Connection(connection_name, connection_path,
+ ready_handler=self.__connection_ready_cb)
+
+ def __connection_ready_cb(self, connection):
+ logging.debug('_Account.__connection_ready_cb %r',
+ connection.object_path)
+ connection.connect_to_signal('StatusChanged',
+ self.__status_changed_cb)
+
+ connection[PROPERTIES_IFACE].Get(CONNECTION,
+ 'Status',
+ reply_handler=self.__get_status_cb,
+ error_handler=partial(self.__error_handler_cb,
+ 'Connection.GetStatus'))
+
+ def __get_status_cb(self, status):
+ logging.debug('_Account.__get_status_cb %r %r',
+ self._connection.object_path, status)
+ self._update_status(status)
+
+ def __status_changed_cb(self, status, reason):
+ logging.debug('_Account.__status_changed_cb %r %r', status, reason)
+ self._update_status(status)
+
+ def _update_status(self, status):
+ if status == CONNECTION_STATUS_CONNECTED:
+ self._connection[PROPERTIES_IFACE].Get(CONNECTION,
+ 'SelfHandle',
+ reply_handler=self.__get_self_handle_cb,
+ error_handler=partial(self.__error_handler_cb,
+ 'Connection.GetSelfHandle'))
+ self.emit('connected')
+ else:
+ for contact_handle, contact_id in self._buddy_handles.items():
+ self.emit('buddy-removed', contact_id)
+
+ for room_handle, activity_id in self._activity_handles.items():
+ self.emit('activity-removed', activity_id)
+
+ self._buddy_handles = {}
+ self._activity_handles = {}
+ self._buddies_per_activity = {}
+ self._activities_per_buddy = {}
+
+ self.emit('disconnected')
+
+ if status == CONNECTION_STATUS_DISCONNECTED:
+ self._connection = None
+
+ def __get_self_handle_cb(self, self_handle):
+ self._self_handle = self_handle
+
+ connection = self._connection[CONNECTION_INTERFACE_ALIASING]
+ connection.connect_to_signal('AliasesChanged',
+ self.__aliases_changed_cb)
+
+ connection = self._connection[CONNECTION_INTERFACE_SIMPLE_PRESENCE]
+ connection.connect_to_signal('PresencesChanged',
+ self.__presences_changed_cb)
+
+ if CONNECTION_INTERFACE_BUDDY_INFO in self._connection:
+ connection = self._connection[CONNECTION_INTERFACE_BUDDY_INFO]
+ connection.connect_to_signal('PropertiesChanged',
+ self.__buddy_info_updated_cb,
+ byte_arrays=True)
+
+ connection.connect_to_signal('ActivitiesChanged',
+ self.__buddy_activities_changed_cb)
+
+ connection.connect_to_signal('CurrentActivityChanged',
+ self.__current_activity_changed_cb)
+ else:
+ logging.warning('Connection %s does not support OLPC buddy '
+ 'properties', self._connection.object_path)
+
+ if CONNECTION_INTERFACE_ACTIVITY_PROPERTIES in self._connection:
+ connection = self._connection[
+ CONNECTION_INTERFACE_ACTIVITY_PROPERTIES]
+ connection.connect_to_signal(
+ 'ActivityPropertiesChanged',
+ self.__activity_properties_changed_cb)
+ else:
+ logging.warning('Connection %s does not support OLPC activity '
+ 'properties', self._connection.object_path)
+
+ properties = {
+ CHANNEL + '.ChannelType': CHANNEL_TYPE_CONTACT_LIST,
+ CHANNEL + '.TargetHandleType': HANDLE_TYPE_LIST,
+ CHANNEL + '.TargetID': 'subscribe',
+ }
+ properties = dbus.Dictionary(properties, signature='sv')
+ connection = self._connection[CONNECTION_INTERFACE_REQUESTS]
+ is_ours, channel_path, properties = \
+ connection.EnsureChannel(properties)
+
+ channel = Channel(self._connection.service_name, channel_path)
+ channel[CHANNEL_INTERFACE_GROUP].connect_to_signal(
+ 'MembersChanged', self.__members_changed_cb)
+
+ channel[PROPERTIES_IFACE].Get(CHANNEL_INTERFACE_GROUP,
+ 'Members',
+ reply_handler=self.__get_members_ready_cb,
+ error_handler=partial(self.__error_handler_cb,
+ 'Connection.GetMembers'))
+
+ def __aliases_changed_cb(self, aliases):
+ logging.debug('_Account.__aliases_changed_cb')
+ for handle, alias in aliases:
+ if handle in self._buddy_handles:
+ logging.debug('Got handle %r with nick %r, going to update',
+ handle, alias)
+
+ def __presences_changed_cb(self, presences):
+ logging.debug('_Account.__presences_changed_cb %r', presences)
+ for handle, presence in presences.iteritems():
+ if handle in self._buddy_handles:
+ presence_type, status_, message_ = presence
+ if presence_type == CONNECTION_PRESENCE_TYPE_OFFLINE:
+ del self._buddy_handles[handle]
+ self.emit('buddy-removed', handle)
+
+ def __buddy_info_updated_cb(self, handle, properties):
+ logging.debug('_Account.__buddy_info_updated_cb %r %r', handle,
+ properties)
+
+ def __current_activity_changed_cb(self, contact_handle, activity_id,
+ room_handle):
+ logging.debug('_Account.__current_activity_changed_cb %r %r %r',
+ contact_handle, activity_id, room_handle)
+ if contact_handle in self._buddy_handles:
+ contact_id = self._buddy_handles[contact_handle]
+ if not activity_id and room_handle:
+ activity_id = self._activity_handles.get(room_handle, '')
+ self.emit('current-activity-updated', contact_id, activity_id)
+
+ def __get_current_activity_cb(self, contact_handle, activity_id,
+ room_handle):
+ logging.debug('_Account.__get_current_activity_cb %r %r %r',
+ contact_handle, activity_id, room_handle)
+ contact_id = self._buddy_handles[contact_handle]
+ self.emit('current-activity-updated', contact_id, activity_id)
+
+ def __buddy_activities_changed_cb(self, buddy_handle, activities):
+ logging.debug('_Account.__buddy_activities_changed_cb %r %r',
+ buddy_handle, activities)
+ self._update_buddy_activities(buddy_handle, activities)
+
+ def _update_buddy_activities(self, buddy_handle, activities):
+ logging.debug('_Account._update_buddy_activities')
+ if not buddy_handle in self._buddy_handles:
+ self._buddy_handles[buddy_handle] = None
+
+ if not buddy_handle in self._activities_per_buddy:
+ self._activities_per_buddy[buddy_handle] = set()
+
+ for activity_id, room_handle in activities:
+ if room_handle not in self._activity_handles:
+ self._activity_handles[room_handle] = activity_id
+ self.emit('activity-added', room_handle, activity_id)
+
+ connection = self._connection[
+ CONNECTION_INTERFACE_ACTIVITY_PROPERTIES]
+ connection.GetProperties(room_handle,
+ reply_handler=partial(self.__get_properties_cb,
+ room_handle),
+ error_handler=partial(self.__error_handler_cb,
+ 'ActivityProperties.GetProperties'))
+
+ # Sometimes we'll get CurrentActivityChanged before we get to
+ # know about the activity so we miss the event. In that case,
+ # request again the current activity for this buddy.
+ connection = self._connection[CONNECTION_INTERFACE_BUDDY_INFO]
+ connection.GetCurrentActivity(
+ buddy_handle,
+ reply_handler=partial(self.__get_current_activity_cb,
+ buddy_handle),
+ error_handler=partial(self.__error_handler_cb,
+ 'BuddyInfo.GetCurrentActivity'))
+
+ if not activity_id in self._buddies_per_activity:
+ self._buddies_per_activity[activity_id] = set()
+ self._buddies_per_activity[activity_id].add(buddy_handle)
+ if activity_id not in self._activities_per_buddy[buddy_handle]:
+ self._activities_per_buddy[buddy_handle].add(activity_id)
+ if self._buddy_handles[buddy_handle] is not None:
+ self.emit('buddy-joined-activity',
+ self._buddy_handles[buddy_handle],
+ activity_id)
+
+ current_activity_ids = \
+ [activity_id for activity_id, room_handle in activities]
+ for activity_id in self._activities_per_buddy[buddy_handle].copy():
+ if not activity_id in current_activity_ids:
+ self._remove_buddy_from_activity(buddy_handle, activity_id)
+
+ def __get_properties_cb(self, room_handle, properties):
+ logging.debug('_Account.__get_properties_cb %r %r', room_handle,
+ properties)
+ if properties:
+ self._update_activity(room_handle, properties)
+
+ def _remove_buddy_from_activity(self, buddy_handle, activity_id):
+ if buddy_handle in self._buddies_per_activity[activity_id]:
+ self._buddies_per_activity[activity_id].remove(buddy_handle)
+
+ if activity_id in self._activities_per_buddy[buddy_handle]:
+ self._activities_per_buddy[buddy_handle].remove(activity_id)
+
+ if self._buddy_handles[buddy_handle] is not None:
+ self.emit('buddy-left-activity',
+ self._buddy_handles[buddy_handle],
+ activity_id)
+
+ if not self._buddies_per_activity[activity_id]:
+ del self._buddies_per_activity[activity_id]
+
+ for room_handle in self._activity_handles.copy():
+ if self._activity_handles[room_handle] == activity_id:
+ del self._activity_handles[room_handle]
+ break
+
+ self.emit('activity-removed', activity_id)
+
+ def __activity_properties_changed_cb(self, room_handle, properties):
+ logging.debug('_Account.__activity_properties_changed_cb %r %r',
+ room_handle, properties)
+ self._update_activity(room_handle, properties)
+
+ def _update_activity(self, room_handle, properties):
+ if room_handle in self._activity_handles:
+ self.emit('activity-updated', self._activity_handles[room_handle],
+ properties)
+ else:
+ logging.debug('_Account.__activity_properties_changed_cb unknown '
+ 'activity')
+ # We don't get ActivitiesChanged for the owner of the connection,
+ # so we query for its activities in order to find out.
+ if CONNECTION_INTERFACE_BUDDY_INFO in self._connection:
+ handle = self._self_handle
+ connection = self._connection[CONNECTION_INTERFACE_BUDDY_INFO]
+ connection.GetActivities(
+ handle,
+ reply_handler=partial(self.__got_activities_cb, handle),
+ error_handler=partial(self.__error_handler_cb,
+ 'BuddyInfo.Getactivities'))
+
+ def __members_changed_cb(self, message, added, removed, local_pending,
+ remote_pending, actor, reason):
+ self._add_buddy_handles(added)
+
+ def __get_members_ready_cb(self, handles):
+ logging.debug('_Account.__get_members_ready_cb %r', handles)
+ if not handles:
+ return
+
+ self._add_buddy_handles(handles)
+
+ def _add_buddy_handles(self, handles):
+ logging.debug('_Account._add_buddy_handles %r', handles)
+ interfaces = [CONNECTION, CONNECTION_INTERFACE_ALIASING]
+ self._connection[CONNECTION_INTERFACE_CONTACTS].GetContactAttributes(
+ handles, interfaces, False,
+ reply_handler=self.__get_contact_attributes_cb,
+ error_handler=partial(self.__error_handler_cb,
+ 'Contacts.GetContactAttributes'))
+
+ def __got_buddy_info_cb(self, handle, nick, properties):
+ logging.debug('_Account.__got_buddy_info_cb %r', properties)
+ self.emit('buddy-added', self._buddy_handles[handle], nick,
+ properties.get('key', None))
+ self.emit('buddy-updated', self._buddy_handles[handle], properties)
+
+ def __get_contact_attributes_cb(self, attributes):
+ logging.debug('_Account.__get_contact_attributes_cb %r',
+ attributes.keys())
+
+ for handle in attributes.keys():
+ nick = attributes[handle][CONNECTION_INTERFACE_ALIASING + '/alias']
+
+ if handle in self._buddy_handles and \
+ not self._buddy_handles[handle] is None:
+ logging.debug('Got handle %r with nick %r, going to update',
+ handle, nick)
+ self.emit('buddy-updated', self._buddy_handles[handle],
+ attributes[handle])
+ else:
+ logging.debug('Got handle %r with nick %r, going to add',
+ handle, nick)
+
+ contact_id = attributes[handle][CONNECTION + '/contact-id']
+ self._buddy_handles[handle] = contact_id
+
+ if CONNECTION_INTERFACE_BUDDY_INFO in self._connection:
+ connection = \
+ self._connection[CONNECTION_INTERFACE_BUDDY_INFO]
+
+ connection.GetProperties(
+ handle,
+ reply_handler=partial(self.__got_buddy_info_cb, handle,
+ nick),
+ error_handler=partial(self.__error_handler_cb,
+ 'BuddyInfo.GetProperties'),
+ byte_arrays=True)
+
+ connection.GetActivities(
+ handle,
+ reply_handler=partial(self.__got_activities_cb, handle),
+ error_handler=partial(self.__error_handler_cb,
+ 'BuddyInfo.GetActivities'))
+
+ connection.GetCurrentActivity(
+ handle,
+ reply_handler=partial(self.__get_current_activity_cb,
+ handle),
+ error_handler=partial(self.__error_handler_cb,
+ 'BuddyInfo.GetCurrentActivity'))
+ else:
+ self.emit('buddy-added', contact_id, nick, None)
+
+ def __got_activities_cb(self, buddy_handle, activities):
+ logging.debug('_Account.__got_activities_cb %r %r', buddy_handle,
+ activities)
+ self._update_buddy_activities(buddy_handle, activities)
+
+ def enable(self):
+ logging.debug('_Account.enable %s', self.object_path)
+ self._set_enabled(True)
+
+ def disable(self):
+ logging.debug('_Account.disable %s', self.object_path)
+ self._set_enabled(False)
+ self._connection = None
+
+ def _set_enabled(self, value):
+ bus = dbus.Bus()
+ obj = bus.get_object(ACCOUNT_MANAGER_SERVICE, self.object_path)
+ obj.Set(ACCOUNT, 'Enabled', value,
+ reply_handler=self.__set_enabled_cb,
+ error_handler=partial(self.__error_handler_cb,
+ 'Account.SetEnabled'),
+ dbus_interface='org.freedesktop.DBus.Properties')
+
+ def __set_enabled_cb(self):
+ logging.debug('_Account.__set_enabled_cb success')
+
+class Neighborhood(gobject.GObject):
+ __gsignals__ = {
+ 'activity-added': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([object])),
+ 'activity-removed': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([object])),
+ 'buddy-added': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([object])),
+ 'buddy-removed': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([object]))
+ }
+
+ def __init__(self):
+ gobject.GObject.__init__(self)
+
+ self._buddies = {None: get_owner_instance()}
+ self._activities = {}
+ self._link_local_account = None
+ self._server_account = None
+
+ bus = dbus.Bus()
+ obj = bus.get_object(ACCOUNT_MANAGER_SERVICE, ACCOUNT_MANAGER_PATH)
+ account_manager = dbus.Interface(obj, ACCOUNT_MANAGER)
+ account_manager.Get(ACCOUNT_MANAGER, 'ValidAccounts',
+ dbus_interface=PROPERTIES_IFACE,
+ reply_handler=self.__got_accounts_cb,
+ error_handler=self.__error_handler_cb)
+
+ def __got_accounts_cb(self, account_paths):
+ self._link_local_account = \
+ self._ensure_link_local_account(account_paths)
+ self._connect_to_account(self._link_local_account)
+
+ self._server_account = self._ensure_server_account(account_paths)
+ self._connect_to_account(self._server_account)
+
+ def __error_handler_cb(self, error):
+ raise RuntimeError(error)
+
+ def _connect_to_account(self, account):
+ account.connect('buddy-added', self.__buddy_added_cb)
+ account.connect('buddy-updated', self.__buddy_updated_cb)
+ account.connect('buddy-removed', self.__buddy_removed_cb)
+ account.connect('buddy-joined-activity',
+ self.__buddy_joined_activity_cb)
+ account.connect('buddy-left-activity', self.__buddy_left_activity_cb)
+ account.connect('activity-added', self.__activity_added_cb)
+ account.connect('activity-updated', self.__activity_updated_cb)
+ account.connect('activity-removed', self.__activity_removed_cb)
+ account.connect('current-activity-updated',
+ self.__current_activity_updated_cb)
+ account.connect('connected', self.__account_connected_cb)
+ account.connect('disconnected', self.__account_disconnected_cb)
+
+ def __account_connected_cb(self, account):
+ logging.debug('__account_connected_cb %s', account.object_path)
+ if account == self._server_account:
+ self._link_local_account.disable()
+
+ def __account_disconnected_cb(self, account):
+ logging.debug('__account_disconnected_cb %s', account.object_path)
+ if account == self._server_account:
+ self._link_local_account.enable()
+
+ def _ensure_link_local_account(self, account_paths):
+ for account_path in account_paths:
+ if 'salut' in account_path:
+ logging.debug('Already have a Salut account')
+ account = _Account(account_path)
+ account.enable()
+ return account
+
+ logging.debug('Still dont have a Salut account, creating one')
+
+ client = gconf.client_get_default()
+ nick = client.get_string('/desktop/sugar/user/nick')
+ server = client.get_string('/desktop/sugar/collaboration/jabber_server')
+
+ params = {
+ 'nickname': nick,
+ 'first-name': '',
+ 'last-name': '',
+ 'jid': '%s@%s' % (self._sanitize_nick(nick), server),
+ 'published-name': nick,
+ }
+
+ properties = {
+ 'org.freedesktop.Telepathy.Account.Enabled': True,
+ 'org.freedesktop.Telepathy.Account.Nickname': nick,
+ 'org.freedesktop.Telepathy.Account.ConnectAutomatically': True,
+ }
+
+ bus = dbus.Bus()
+ obj = bus.get_object(ACCOUNT_MANAGER_SERVICE, ACCOUNT_MANAGER_PATH)
+ account_manager = dbus.Interface(obj, ACCOUNT_MANAGER)
+ account_path = account_manager.CreateAccount('salut', 'local-xmpp',
+ 'salut', params,
+ properties)
+ return _Account(account_path)
+
+ def _ensure_server_account(self, account_paths):
+ for account_path in account_paths:
+ if 'gabble' in account_path:
+ logging.debug('Already have a Gabble account')
+ account = _Account(account_path)
+ account.enable()
+ return account
+
+ logging.debug('Still dont have a Gabble account, creating one')
+
+ client = gconf.client_get_default()
+ nick = client.get_string('/desktop/sugar/user/nick')
+ server = client.get_string('/desktop/sugar/collaboration/jabber_server')
+ key_hash = get_profile().privkey_hash
+
+ params = {
+ 'account': '%s@%s' % (self._sanitize_nick(nick), server),
+ 'password': key_hash,
+ 'server': server,
+ 'resource': 'sugar',
+ 'require-encryption': True,
+ 'ignore-ssl-errors': True,
+ 'register': True,
+ 'old-ssl': True,
+ 'port': dbus.UInt32(5223),
+ }
+
+ properties = {
+ 'org.freedesktop.Telepathy.Account.Enabled': True,
+ 'org.freedesktop.Telepathy.Account.Nickname': nick,
+ 'org.freedesktop.Telepathy.Account.ConnectAutomatically': True,
+ }
+
+ bus = dbus.Bus()
+ obj = bus.get_object(ACCOUNT_MANAGER_SERVICE, ACCOUNT_MANAGER_PATH)
+ account_manager = dbus.Interface(obj, ACCOUNT_MANAGER)
+ account_path = account_manager.CreateAccount('gabble', 'jabber',
+ 'jabber', params,
+ properties)
+ return _Account(account_path)
+
+ def _sanitize_nick(self, nick):
+ return nick.replace(' ', '_')
+
+ def __buddy_added_cb(self, account, contact_id, nick, key):
+ logging.debug('__buddy_added_cb %r', contact_id)
+
+ if contact_id in self._buddies:
+ logging.debug('__buddy_added_cb buddy already tracked')
+ return
+
+ buddy = BuddyModel(
+ nick=nick,
+ account=account.object_path,
+ contact_id=contact_id,
+ key=key)
+ self._buddies[contact_id] = buddy
+
+ self.emit('buddy-added', buddy)
+
+ def __buddy_updated_cb(self, account, contact_id, properties):
+ logging.debug('__buddy_updated_cb %r %r', contact_id, properties)
+ if contact_id not in self._buddies:
+ logging.debug('__buddy_updated_cb Unknown buddy with contact_id %r',
+ contact_id)
+ return
+
+ buddy = self._buddies[contact_id]
+ if 'color' in properties:
+ buddy.props.color = XoColor(properties['color'])
+
+ def __buddy_removed_cb(self, account, contact_id):
+ logging.debug('Neighborhood.__buddy_removed_cb %r', contact_id)
+ if contact_id not in self._buddies:
+ logging.debug('Neighborhood.__buddy_removed_cb Unknown buddy with '
+ 'contact_id %r', contact_id)
+ return
+
+ buddy = self._buddies[contact_id]
+ del self._buddies[contact_id]
+ self.emit('buddy-removed', buddy)
+
+ def __activity_added_cb(self, account, room_handle, activity_id):
+ logging.debug('__activity_added_cb %r %r', room_handle, activity_id)
+ if activity_id in self._activities:
+ logging.debug('__activity_added_cb activity already tracked')
+ return
+
+ activity = ActivityModel(activity_id, room_handle)
+ self._activities[activity_id] = activity
+
+ def __activity_updated_cb(self, account, activity_id, properties):
+ logging.debug('__activity_updated_cb %r %r', activity_id, properties)
+ if activity_id not in self._activities:
+ logging.debug('__activity_updated_cb Unknown activity with '
+ 'activity_id %r', activity_id)
+ return
+
+ registry = bundleregistry.get_registry()
+ bundle = registry.get_bundle(properties['type'])
+ if not bundle:
+ logging.warning('Ignoring shared activity we don''t have')
+ return
+
+ activity = self._activities[activity_id]
+
+ is_new = activity.props.bundle is None
+
+ activity.props.color = XoColor(properties['color'])
+ activity.props.bundle = bundle
+ activity.props.name = properties['name']
+ activity.props.private = properties['private']
+
+ if is_new:
+ self.emit('activity-added', activity)
+
+ def __activity_removed_cb(self, account, activity_id):
+ logging.debug('__activity_removed_cb %r', activity_id)
+ if activity_id not in self._activities:
+ logging.debug('Unknown activity with id %s. Already removed?',
+ activity_id)
+ return
+ activity = self._activities[activity_id]
+ del self._activities[activity_id]
+ self.emit('activity-removed', activity)
+
+ def __current_activity_updated_cb(self, account, contact_id, activity_id):
+ logging.debug('__current_activity_updated_cb %r %r', contact_id,
+ activity_id)
+ if contact_id not in self._buddies:
+ logging.debug('__current_activity_updated_cb Unknown buddy with '
+ 'contact_id %r', contact_id)
+ return
+ if activity_id and activity_id not in self._activities:
+ logging.debug('__current_activity_updated_cb Unknown activity with '
+ 'id %s', activity_id)
+ activity_id = ''
+
+ buddy = self._buddies[contact_id]
+ if buddy.props.current_activity is not None:
+ if buddy.props.current_activity.activity_id == activity_id:
+ return
+ buddy.props.current_activity.remove_current_buddy(buddy)
+
+ if activity_id:
+ activity = self._activities[activity_id]
+ buddy.props.current_activity = activity
+ activity.add_current_buddy(buddy)
+ else:
+ buddy.props.current_activity = None
+
+ def __buddy_joined_activity_cb(self, account, contact_id, activity_id):
+ if contact_id not in self._buddies:
+ logging.debug('__buddy_joined_activity_cb Unknown buddy with '
+ 'contact_id %r', contact_id)
+ return
+
+ if activity_id not in self._activities:
+ logging.debug('__buddy_joined_activity_cb Unknown activity with '
+ 'activity_id %r', activity_id)
+ return
+
+ self._activities[activity_id].add_buddy(self._buddies[contact_id])
+
+ def __buddy_left_activity_cb(self, account, contact_id, activity_id):
+ if contact_id not in self._buddies:
+ logging.debug('__buddy_left_activity_cb Unknown buddy with '
+ 'contact_id %r', contact_id)
+ return
+
+ if activity_id not in self._activities:
+ logging.debug('__buddy_left_activity_cb Unknown activity with '
+ 'activity_id %r', activity_id)
+ return
+
+ self._activities[activity_id].remove_buddy(self._buddies[contact_id])
+
+ def get_buddies(self):
+ return self._buddies.values()
+
+ def get_activity(self, activity_id):
+ return self._activities.get(activity_id, None)
+
+ def get_activity_by_room(self, room_handle):
+ for activity in self._activities.values():
+ if activity.room_handle == room_handle:
+ return activity
+ return None
+
+ def get_activities(self):
+ return self._activities.values()
+
+_model = None
+
+def get_model():
+ global _model
+ if _model is None:
+ _model = Neighborhood()
+ return _model
diff --git a/shell/src/jarabe/model/network.py b/shell/src/jarabe/model/network.py
new file mode 100644
index 0000000..cd0d46e
--- /dev/null
+++ b/shell/src/jarabe/model/network.py
@@ -0,0 +1,751 @@
+# Copyright (C) 2008 Red Hat, Inc.
+# Copyright (C) 2009 Tomeu Vizoso, Simon Schampijer
+# Copyright (C) 2009-2010 One Laptop per Child
+# Copyright (C) 2009 Paraguay Educa, Martin Abente
+# Copyright (C) 2010 Plan Ceibal, Daniel Castelo
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import logging
+import os
+import time
+
+import dbus
+import dbus.service
+import gobject
+import ConfigParser
+import gconf
+
+from sugar import dispatch
+from sugar import env
+from sugar.util import unique_id
+
+DEVICE_TYPE_802_3_ETHERNET = 1
+DEVICE_TYPE_802_11_WIRELESS = 2
+DEVICE_TYPE_GSM_MODEM = 3
+DEVICE_TYPE_802_11_OLPC_MESH = 6
+
+DEVICE_STATE_UNKNOWN = 0
+DEVICE_STATE_UNMANAGED = 1
+DEVICE_STATE_UNAVAILABLE = 2
+DEVICE_STATE_DISCONNECTED = 3
+DEVICE_STATE_PREPARE = 4
+DEVICE_STATE_CONFIG = 5
+DEVICE_STATE_NEED_AUTH = 6
+DEVICE_STATE_IP_CONFIG = 7
+DEVICE_STATE_ACTIVATED = 8
+DEVICE_STATE_FAILED = 9
+
+NM_CONNECTION_TYPE_802_11_WIRELESS = '802-11-wireless'
+NM_CONNECTION_TYPE_GSM = 'gsm'
+
+NM_ACTIVE_CONNECTION_STATE_UNKNOWN = 0
+NM_ACTIVE_CONNECTION_STATE_ACTIVATING = 1
+NM_ACTIVE_CONNECTION_STATE_ACTIVATED = 2
+
+NM_802_11_AP_FLAGS_NONE = 0x00000000
+NM_802_11_AP_FLAGS_PRIVACY = 0x00000001
+
+NM_802_11_AP_SEC_NONE = 0x00000000
+NM_802_11_AP_SEC_PAIR_WEP40 = 0x00000001
+NM_802_11_AP_SEC_PAIR_WEP104 = 0x00000002
+NM_802_11_AP_SEC_PAIR_TKIP = 0x00000004
+NM_802_11_AP_SEC_PAIR_CCMP = 0x00000008
+NM_802_11_AP_SEC_GROUP_WEP40 = 0x00000010
+NM_802_11_AP_SEC_GROUP_WEP104 = 0x00000020
+NM_802_11_AP_SEC_GROUP_TKIP = 0x00000040
+NM_802_11_AP_SEC_GROUP_CCMP = 0x00000080
+NM_802_11_AP_SEC_KEY_MGMT_PSK = 0x00000100
+NM_802_11_AP_SEC_KEY_MGMT_802_1X = 0x00000200
+
+NM_802_11_MODE_UNKNOWN = 0
+NM_802_11_MODE_ADHOC = 1
+NM_802_11_MODE_INFRA = 2
+
+NM_802_11_DEVICE_CAP_NONE = 0x00000000
+NM_802_11_DEVICE_CAP_CIPHER_WEP40 = 0x00000001
+NM_802_11_DEVICE_CAP_CIPHER_WEP104 = 0x00000002
+NM_802_11_DEVICE_CAP_CIPHER_TKIP = 0x00000004
+NM_802_11_DEVICE_CAP_CIPHER_CCMP = 0x00000008
+NM_802_11_DEVICE_CAP_WPA = 0x00000010
+NM_802_11_DEVICE_CAP_RSN = 0x00000020
+
+SETTINGS_SERVICE = 'org.freedesktop.NetworkManagerUserSettings'
+
+NM_SETTINGS_PATH = '/org/freedesktop/NetworkManagerSettings'
+NM_SETTINGS_IFACE = 'org.freedesktop.NetworkManagerSettings'
+NM_CONNECTION_IFACE = 'org.freedesktop.NetworkManagerSettings.Connection'
+NM_SECRETS_IFACE = 'org.freedesktop.NetworkManagerSettings.Connection.Secrets'
+NM_ACCESSPOINT_IFACE = 'org.freedesktop.NetworkManager.AccessPoint'
+
+GSM_USERNAME_PATH = '/desktop/sugar/network/gsm/username'
+GSM_PASSWORD_PATH = '/desktop/sugar/network/gsm/password'
+GSM_NUMBER_PATH = '/desktop/sugar/network/gsm/number'
+GSM_APN_PATH = '/desktop/sugar/network/gsm/apn'
+GSM_PIN_PATH = '/desktop/sugar/network/gsm/pin'
+GSM_PUK_PATH = '/desktop/sugar/network/gsm/puk'
+
+_nm_settings = None
+_conn_counter = 0
+
+
+def frequency_to_channel(frequency):
+ """Returns the channel matching a given radio channel frequency. If a
+ frequency is not in the dictionary channel 1 will be returned.
+
+ Keyword arguments:
+ frequency -- The radio channel frequency in MHz.
+
+ Return: Channel
+
+ """
+ ftoc = {2412: 1, 2417: 2, 2422: 3, 2427: 4,
+ 2432: 5, 2437: 6, 2442: 7, 2447: 8,
+ 2452: 9, 2457: 10, 2462: 11, 2467: 12,
+ 2472: 13}
+ if frequency not in ftoc:
+ logging.warning("The frequency %s can not be mapped to a channel, " \
+ "defaulting to channel 1.", frequency)
+ return 1
+ return ftoc[frequency]
+
+def is_sugar_adhoc_network(ssid):
+ """Checks whether an access point is a sugar Ad-hoc network.
+
+ Keyword arguments:
+ ssid -- Ssid of the access point.
+
+ Return: Boolean
+
+ """
+ return ssid.startswith('Ad-hoc Network')
+
+
+class WirelessSecurity(object):
+ def __init__(self):
+ self.key_mgmt = None
+ self.proto = None
+ self.group = None
+ self.pairwise = None
+
+ def get_dict(self):
+ wireless_security = {}
+ if self.key_mgmt is not None:
+ wireless_security['key-mgmt'] = self.key_mgmt
+ if self.proto is not None:
+ wireless_security['proto'] = self.proto
+ if self.pairwise is not None:
+ wireless_security['pairwise'] = self.pairwise
+ if self.group is not None:
+ wireless_security['group'] = self.group
+ return wireless_security
+
+class Wireless(object):
+ nm_name = "802-11-wireless"
+
+ def __init__(self):
+ self.ssid = None
+ self.security = None
+ self.mode = None
+ self.band = None
+ self.channel = None
+
+ def get_dict(self):
+ wireless = {'ssid': self.ssid}
+ if self.security:
+ wireless['security'] = self.security
+ if self.mode:
+ wireless['mode'] = self.mode
+ if self.band:
+ wireless['band'] = self.band
+ if self.channel:
+ wireless['channel'] = self.channel
+ return wireless
+
+
+class OlpcMesh(object):
+ nm_name = "802-11-olpc-mesh"
+
+ def __init__(self, channel, anycast_addr):
+ self.channel = channel
+ self.anycast_addr = anycast_addr
+
+ def get_dict(self):
+ ret = {
+ "ssid": dbus.ByteArray("olpc-mesh"),
+ "channel": self.channel,
+ }
+
+ if self.anycast_addr:
+ ret["dhcp-anycast-address"] = dbus.ByteArray(self.anycast_addr)
+ return ret
+
+
+class Connection(object):
+ def __init__(self):
+ self.id = None
+ self.uuid = None
+ self.type = None
+ self.autoconnect = False
+ self.timestamp = None
+
+ def get_dict(self):
+ connection = {'id': self.id,
+ 'uuid': self.uuid,
+ 'type': self.type,
+ 'autoconnect': self.autoconnect}
+ if self.timestamp:
+ connection['timestamp'] = self.timestamp
+ return connection
+
+class IP4Config(object):
+ def __init__(self):
+ self.method = None
+
+ def get_dict(self):
+ ip4_config = {}
+ if self.method is not None:
+ ip4_config['method'] = self.method
+ return ip4_config
+
+class Serial(object):
+ def __init__(self):
+ self.baud = None
+
+ def get_dict(self):
+ serial = {}
+
+ if self.baud is not None:
+ serial['baud'] = self.baud
+
+ return serial
+
+class Ppp(object):
+ def __init__(self):
+ pass
+
+ def get_dict(self):
+ ppp = {}
+ return ppp
+
+class Gsm(object):
+ def __init__(self):
+ self.apn = None
+ self.number = None
+ self.username = None
+
+ def get_dict(self):
+ gsm = {}
+
+ if self.apn is not None:
+ gsm['apn'] = self.apn
+ if self.number is not None:
+ gsm['number'] = self.number
+ if self.username is not None:
+ gsm['username'] = self.username
+
+ return gsm
+
+class Settings(object):
+ def __init__(self, wireless_cfg=None):
+ self.connection = Connection()
+ self.ip4_config = None
+ self.wireless_security = None
+
+ if wireless_cfg is not None:
+ self.wireless = wireless_cfg
+ else:
+ self.wireless = Wireless()
+
+ def get_dict(self):
+ settings = {}
+ settings['connection'] = self.connection.get_dict()
+ settings[self.wireless.nm_name] = self.wireless.get_dict()
+ if self.wireless_security is not None:
+ settings['802-11-wireless-security'] = \
+ self.wireless_security.get_dict()
+ if self.ip4_config is not None:
+ settings['ipv4'] = self.ip4_config.get_dict()
+ return settings
+
+class Secrets(object):
+ def __init__(self, settings):
+ self.settings = settings
+ self.wep_key = None
+ self.psk = None
+ self.auth_alg = None
+
+ def get_dict(self):
+ # Although we could just return the keys here, we instead return all
+ # of the network settings so that we can apply any late decisions made
+ # by the user (e.g. if they selected shared key authentication). see
+ # http://bugs.sugarlabs.org/ticket/1602
+ settings = self.settings.get_dict()
+ if '802-11-wireless-security' not in settings:
+ settings['802-11-wireless-security'] = {}
+
+ if self.wep_key is not None:
+ settings['802-11-wireless-security']['wep-key0'] = self.wep_key
+ if self.psk is not None:
+ settings['802-11-wireless-security']['psk'] = self.psk
+ if self.auth_alg is not None:
+ settings['802-11-wireless-security']['auth-alg'] = self.auth_alg
+
+ return settings
+
+class SettingsGsm(object):
+ def __init__(self):
+ self.connection = Connection()
+ self.ip4_config = IP4Config()
+ self.serial = Serial()
+ self.ppp = Ppp()
+ self.gsm = Gsm()
+
+ def get_dict(self):
+ settings = {}
+
+ settings['connection'] = self.connection.get_dict()
+ settings['serial'] = self.serial.get_dict()
+ settings['ppp'] = self.ppp.get_dict()
+ settings['gsm'] = self.gsm.get_dict()
+ settings['ipv4'] = self.ip4_config.get_dict()
+
+ return settings
+
+class SecretsGsm(object):
+ def __init__(self):
+ self.password = None
+ self.pin = None
+ self.puk = None
+
+ def get_dict(self):
+ secrets = {}
+ if self.password is not None:
+ secrets['password'] = self.password
+ if self.pin is not None:
+ secrets['pin'] = self.pin
+ if self.puk is not None:
+ secrets['puk'] = self.puk
+ return {'gsm': secrets}
+
+class NMSettings(dbus.service.Object):
+ def __init__(self):
+ bus = dbus.SystemBus()
+ bus_name = dbus.service.BusName(SETTINGS_SERVICE, bus=bus)
+ dbus.service.Object.__init__(self, bus_name, NM_SETTINGS_PATH)
+
+ self.connections = {}
+ self.secrets_request = dispatch.Signal()
+
+ @dbus.service.method(dbus_interface=NM_SETTINGS_IFACE,
+ in_signature='', out_signature='ao')
+ def ListConnections(self):
+ return self.connections.values()
+
+ @dbus.service.signal(NM_SETTINGS_IFACE, signature='o')
+ def NewConnection(self, connection_path):
+ pass
+
+ def add_connection(self, uuid, conn):
+ self.connections[uuid] = conn
+ conn.secrets_request.connect(self.__secrets_request_cb)
+ self.NewConnection(conn.path)
+
+ def __secrets_request_cb(self, sender, **kwargs):
+ self.secrets_request.send(self, connection=sender,
+ response=kwargs['response'])
+
+class SecretsResponse(object):
+ ''' Intermediate object to report the secrets from the dialog
+ back to the connection object and which will inform NM
+ '''
+ def __init__(self, connection, reply_cb, error_cb):
+ self._connection = connection
+ self._reply_cb = reply_cb
+ self._error_cb = error_cb
+
+ def set_secrets(self, secrets):
+ self._connection.set_secrets(secrets)
+ self._reply_cb(secrets.get_dict())
+
+ def set_error(self, error):
+ self._error_cb(error)
+
+class NMSettingsConnection(dbus.service.Object):
+ def __init__(self, path, settings, secrets):
+ bus = dbus.SystemBus()
+ bus_name = dbus.service.BusName(SETTINGS_SERVICE, bus=bus)
+ dbus.service.Object.__init__(self, bus_name, path)
+
+ self.path = path
+ self.secrets_request = dispatch.Signal()
+
+ self._settings = settings
+ self._secrets = secrets
+
+ def set_connected(self):
+ if self._settings.connection.type == NM_CONNECTION_TYPE_GSM:
+ self._settings.connection.timestamp = int(time.time())
+ else:
+ if not self._settings.connection.autoconnect:
+ self._settings.connection.autoconnect = True
+ self._settings.connection.timestamp = int(time.time())
+ if self._settings.connection.type == NM_CONNECTION_TYPE_802_11_WIRELESS:
+ self.save()
+
+ def set_secrets(self, secrets):
+ self._secrets = secrets
+ if self._settings.connection.type == NM_CONNECTION_TYPE_802_11_WIRELESS:
+ self.save()
+
+ def get_settings(self):
+ return self._settings
+
+ def save(self):
+ profile_path = env.get_profile_path()
+ config_path = os.path.join(profile_path, 'nm', 'connections.cfg')
+
+ config = ConfigParser.ConfigParser()
+ try:
+ try:
+ if not config.read(config_path):
+ logging.error('Error reading the nm config file')
+ return
+ except ConfigParser.ParsingError:
+ logging.exception('Error reading the nm config file')
+ return
+ identifier = self._settings.connection.id
+
+ if identifier not in config.sections():
+ config.add_section(identifier)
+ config.set(identifier, 'type', self._settings.connection.type)
+ config.set(identifier, 'ssid', self._settings.wireless.ssid)
+ config.set(identifier, 'uuid', self._settings.connection.uuid)
+ config.set(identifier, 'autoconnect',
+ self._settings.connection.autoconnect)
+ if self._settings.connection.timestamp is not None:
+ config.set(identifier, 'timestamp',
+ self._settings.connection.timestamp)
+ if self._settings.wireless_security is not None:
+ if self._settings.wireless_security.key_mgmt is not None:
+ config.set(identifier, 'key-mgmt',
+ self._settings.wireless_security.key_mgmt)
+ if self._settings.wireless_security.proto is not None:
+ config.set(identifier, 'proto',
+ self._settings.wireless_security.proto)
+ if self._settings.wireless_security.pairwise is not None:
+ config.set(identifier, 'pairwise',
+ self._settings.wireless_security.pairwise)
+ if self._settings.wireless_security.group is not None:
+ config.set(identifier, 'group',
+ self._settings.wireless_security.group)
+ if self._settings.wireless.security is not None:
+ config.set(identifier, 'security',
+ self._settings.wireless.security)
+ if self._secrets is not None:
+ if self._settings.wireless_security.key_mgmt == 'none':
+ config.set(identifier, 'key', self._secrets.wep_key)
+ config.set(identifier, 'auth-alg', self._secrets.auth_alg)
+ elif self._settings.wireless_security.key_mgmt == 'wpa-psk':
+ config.set(identifier, 'key', self._secrets.psk)
+ except ConfigParser.Error, e:
+ logging.exception('Error constructing %s', identifier)
+ else:
+ f = open(config_path, 'w')
+ try:
+ config.write(f)
+ except ConfigParser.Error:
+ logging.exception('Can not write %s', config_path)
+ f.close()
+
+ @dbus.service.method(dbus_interface=NM_CONNECTION_IFACE,
+ in_signature='', out_signature='a{sa{sv}}')
+ def GetSettings(self):
+ return self._settings.get_dict()
+
+ @dbus.service.method(dbus_interface=NM_SECRETS_IFACE,
+ async_callbacks=('reply', 'error'),
+ in_signature='sasb', out_signature='a{sa{sv}}')
+ def GetSecrets(self, setting_name, hints, request_new, reply, error):
+ logging.debug('Secrets requested for connection %s request_new=%s',
+ self.path, request_new)
+ if request_new or self._secrets is None:
+ # request_new is for example the case when the pw on the AP changes
+ response = SecretsResponse(self, reply, error)
+ try:
+ self.secrets_request.send(self, response=response)
+ except Exception:
+ logging.exception('Error requesting the secrets via dialog')
+ else:
+ reply(self._secrets.get_dict())
+
+
+class AccessPoint(gobject.GObject):
+ __gsignals__ = {
+ 'props-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT]))
+ }
+
+ def __init__(self, device, model):
+ self.__gobject_init__()
+ self.device = device
+ self.model = model
+
+ self._initialized = False
+ self._bus = dbus.SystemBus()
+
+ self.name = ''
+ self.strength = 0
+ self.flags = 0
+ self.wpa_flags = 0
+ self.rsn_flags = 0
+ self.mode = 0
+ self.channel = 0
+
+ def initialize(self):
+ model_props = dbus.Interface(self.model,
+ 'org.freedesktop.DBus.Properties')
+ model_props.GetAll(NM_ACCESSPOINT_IFACE, byte_arrays=True,
+ reply_handler=self._ap_properties_changed_cb,
+ error_handler=self._get_all_props_error_cb)
+
+ self._bus.add_signal_receiver(self._ap_properties_changed_cb,
+ signal_name='PropertiesChanged',
+ path=self.model.object_path,
+ dbus_interface=NM_ACCESSPOINT_IFACE,
+ byte_arrays=True)
+
+ def network_hash(self):
+ """
+ This is a hash which uniquely identifies the network that this AP
+ is a bridge to. i.e. its expected for 2 APs with identical SSID and
+ other settings to have the same network hash, because we assume that
+ they are a part of the same underlying network.
+ """
+
+ # based on logic from nm-applet
+ fl = 0
+
+ if self.mode == NM_802_11_MODE_INFRA:
+ fl |= 1 << 0
+ elif self.mode == NM_802_11_MODE_ADHOC:
+ fl |= 1 << 1
+ else:
+ fl |= 1 << 2
+
+ # Separate out no encryption, WEP-only, and WPA-capable */
+ if (not (self.flags & NM_802_11_AP_FLAGS_PRIVACY)) \
+ and self.wpa_flags == NM_802_11_AP_SEC_NONE \
+ and self.rsn_flags == NM_802_11_AP_SEC_NONE:
+ fl |= 1 << 3
+ elif (self.flags & NM_802_11_AP_FLAGS_PRIVACY) \
+ and self.wpa_flags == NM_802_11_AP_SEC_NONE \
+ and self.rsn_flags == NM_802_11_AP_SEC_NONE:
+ fl |= 1 << 4
+ elif (not (self.flags & NM_802_11_AP_FLAGS_PRIVACY)) \
+ and self.wpa_flags != NM_802_11_AP_SEC_NONE \
+ and self.rsn_flags != NM_802_11_AP_SEC_NONE:
+ fl |= 1 << 5
+ else:
+ fl |= 1 << 6
+
+ hashstr = str(fl) + "@" + self.name
+ return hash(hashstr)
+
+ def _update_properties(self, properties):
+ if self._initialized:
+ old_hash = self.network_hash()
+ else:
+ old_hash = None
+
+ if 'Ssid' in properties:
+ self.name = properties['Ssid']
+ if 'Strength' in properties:
+ self.strength = properties['Strength']
+ if 'Flags' in properties:
+ self.flags = properties['Flags']
+ if 'WpaFlags' in properties:
+ self.wpa_flags = properties['WpaFlags']
+ if 'RsnFlags' in properties:
+ self.rsn_flags = properties['RsnFlags']
+ if 'Mode' in properties:
+ self.mode = properties['Mode']
+ if 'Frequency' in properties:
+ self.channel = frequency_to_channel(properties['Frequency'])
+
+ self._initialized = True
+ self.emit('props-changed', old_hash)
+
+ def _get_all_props_error_cb(self, err):
+ logging.error('Error getting the access point properties: %s', err)
+
+ def _ap_properties_changed_cb(self, properties):
+ self._update_properties(properties)
+
+ def disconnect(self):
+ self._bus.remove_signal_receiver(self._ap_properties_changed_cb,
+ signal_name='PropertiesChanged',
+ path=self.model.object_path,
+ dbus_interface=NM_ACCESSPOINT_IFACE)
+
+def get_settings():
+ global _nm_settings
+ if _nm_settings is None:
+ try:
+ _nm_settings = NMSettings()
+ except dbus.DBusException:
+ logging.exception('Cannot create the UserSettings service.')
+ load_connections()
+ return _nm_settings
+
+def find_connection_by_ssid(ssid):
+ connections = get_settings().connections
+
+ for conn_index in connections:
+ connection = connections[conn_index]
+ if connection._settings.connection.type == NM_CONNECTION_TYPE_802_11_WIRELESS:
+ if connection._settings.wireless.ssid == ssid:
+ return connection
+
+ return None
+
+def add_connection(uuid, settings, secrets=None):
+ global _conn_counter
+
+ path = NM_SETTINGS_PATH + '/' + str(_conn_counter)
+ _conn_counter += 1
+
+ conn = NMSettingsConnection(path, settings, secrets)
+ _nm_settings.add_connection(uuid, conn)
+ return conn
+
+def load_wifi_connections():
+ profile_path = env.get_profile_path()
+ config_path = os.path.join(profile_path, 'nm', 'connections.cfg')
+
+ config = ConfigParser.ConfigParser()
+
+ if not os.path.exists(config_path):
+ if not os.path.exists(os.path.dirname(config_path)):
+ os.makedirs(os.path.dirname(config_path), 0755)
+ f = open(config_path, 'w')
+ config.write(f)
+ f.close()
+
+ try:
+ if not config.read(config_path):
+ logging.error('Error reading the nm config file')
+ return
+ except ConfigParser.ParsingError:
+ logging.exception('Error reading the nm config file')
+ return
+
+ for section in config.sections():
+ try:
+ settings = Settings()
+ settings.connection.id = section
+ ssid = config.get(section, 'ssid')
+ settings.wireless.ssid = dbus.ByteArray(ssid)
+ uuid = config.get(section, 'uuid')
+ settings.connection.uuid = uuid
+ nmtype = config.get(section, 'type')
+ settings.connection.type = nmtype
+ autoconnect = bool(config.get(section, 'autoconnect'))
+ settings.connection.autoconnect = autoconnect
+
+ if config.has_option(section, 'timestamp'):
+ timestamp = int(config.get(section, 'timestamp'))
+ settings.connection.timestamp = timestamp
+
+ secrets = None
+ if config.has_option(section, 'key-mgmt'):
+ secrets = Secrets(settings)
+ settings.wireless_security = WirelessSecurity()
+ mgmt = config.get(section, 'key-mgmt')
+ settings.wireless_security.key_mgmt = mgmt
+ security = config.get(section, 'security')
+ settings.wireless.security = security
+ key = config.get(section, 'key')
+ if mgmt == 'none':
+ secrets.wep_key = key
+ auth_alg = config.get(section, 'auth-alg')
+ secrets.auth_alg = auth_alg
+ elif mgmt == 'wpa-psk':
+ secrets.psk = key
+ if config.has_option(section, 'proto'):
+ value = config.get(section, 'proto')
+ settings.wireless_security.proto = value
+ if config.has_option(section, 'group'):
+ value = config.get(section, 'group')
+ settings.wireless_security.group = value
+ if config.has_option(section, 'pairwise'):
+ value = config.get(section, 'pairwise')
+ settings.wireless_security.pairwise = value
+ except ConfigParser.Error:
+ logging.exception('Error reading section')
+ else:
+ add_connection(uuid, settings, secrets)
+
+
+def load_gsm_connection():
+ _BAUD_RATE = 115200
+
+ client = gconf.client_get_default()
+
+ username = client.get_string(GSM_USERNAME_PATH) or ''
+ password = client.get_string(GSM_PASSWORD_PATH) or ''
+ number = client.get_string(GSM_NUMBER_PATH) or ''
+ apn = client.get_string(GSM_APN_PATH) or ''
+ pin = client.get_string(GSM_PIN_PATH) or ''
+ puk = client.get_string(GSM_PUK_PATH) or ''
+
+ if username and number and apn:
+ settings = SettingsGsm()
+ settings.gsm.username = username
+ settings.gsm.number = number
+ settings.gsm.apn = apn
+
+ secrets = SecretsGsm()
+ secrets.pin = pin
+ secrets.puk = puk
+ secrets.password = password
+
+ settings.connection.id = 'gsm'
+ settings.connection.type = NM_CONNECTION_TYPE_GSM
+ uuid = settings.connection.uuid = unique_id()
+ settings.connection.autoconnect = False
+ settings.ip4_config.method = 'auto'
+ settings.serial.baud = _BAUD_RATE
+
+ try:
+ add_connection(uuid, settings, secrets)
+ except Exception:
+ logging.exception('Error adding gsm connection to NMSettings.')
+ else:
+ logging.exception("No gsm connection was set in GConf.")
+
+def load_connections():
+ load_wifi_connections()
+ load_gsm_connection()
+
+def find_gsm_connection():
+ connections = get_settings().connections
+
+ for connection in connections.values():
+ if connection.get_settings().connection.type == NM_CONNECTION_TYPE_GSM:
+ return connection
+
+ logging.debug('There is no gsm connection in the NMSettings.')
+ return None
diff --git a/shell/src/jarabe/model/notifications.py b/shell/src/jarabe/model/notifications.py
new file mode 100644
index 0000000..f2e2d65
--- /dev/null
+++ b/shell/src/jarabe/model/notifications.py
@@ -0,0 +1,95 @@
+# 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 sys
+import logging
+
+import dbus
+
+from sugar import dispatch
+
+from jarabe import config
+
+_DBUS_SERVICE = "org.freedesktop.Notifications"
+_DBUS_IFACE = "org.freedesktop.Notifications"
+_DBUS_PATH = "/org/freedesktop/Notifications"
+
+class NotificationService(dbus.service.Object):
+ def __init__(self):
+ bus = dbus.SessionBus()
+ bus_name = dbus.service.BusName(_DBUS_SERVICE, bus=bus)
+ dbus.service.Object.__init__(self, bus_name, _DBUS_PATH)
+
+ self._notification_counter = 0
+ self.notification_received = dispatch.Signal()
+ self.notification_cancelled = dispatch.Signal()
+
+ @dbus.service.method(_DBUS_IFACE,
+ in_signature='susssava{sv}i', out_signature='u')
+ def Notify(self, app_name, replaces_id, app_icon, summary, body, actions,
+ hints, expire_timeout):
+
+ logging.debug('Received notification: %r', [app_name, replaces_id,
+ '<app_icon>', summary, body, actions, '<hints>', expire_timeout])
+
+ if replaces_id > 0:
+ notification_id = replaces_id
+ else:
+ if self._notification_counter == sys.maxint:
+ self._notification_counter = 1
+ else:
+ self._notification_counter += 1
+ notification_id = self._notification_counter
+
+ self.notification_received.send(self, app_name=app_name,
+ replaces_id=replaces_id, app_icon=app_icon, summary=summary,
+ body=body, actions=actions, hints=hints,
+ expire_timeout=expire_timeout)
+
+ return notification_id
+
+ @dbus.service.method(_DBUS_IFACE, in_signature='u', out_signature='')
+ def CloseNotification(self, notification_id):
+ self.notification_cancelled.send(self, notification_id=notification_id)
+
+ @dbus.service.method(_DBUS_IFACE, in_signature='', out_signature='as')
+ def GetCapabilities(self):
+ return []
+
+ @dbus.service.method(_DBUS_IFACE, in_signature='', out_signature='sss')
+ def GetServerInformation(self, name, vendor, version):
+ return 'Sugar Shell', 'Sugar', config.version
+
+
+ @dbus.service.signal(_DBUS_IFACE, signature="uu")
+ def NotificationClosed(self, notification_id, reason):
+ pass
+
+ @dbus.service.signal(_DBUS_IFACE, signature="us")
+ def ActionInvoked(self, notification_id, action_key):
+ pass
+
+_instance = None
+
+def get_service():
+ global _instance
+ if not _instance:
+ _instance = NotificationService()
+ return _instance
+
+def init():
+ get_service()
+
diff --git a/shell/src/jarabe/model/olpcmesh.py b/shell/src/jarabe/model/olpcmesh.py
new file mode 100644
index 0000000..60f6be4
--- /dev/null
+++ b/shell/src/jarabe/model/olpcmesh.py
@@ -0,0 +1,214 @@
+# Copyright (C) 2009, 2010 One Laptop per Child
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import logging
+
+import dbus
+import gobject
+
+from jarabe.model import network
+from jarabe.model.network import Settings
+from jarabe.model.network import OlpcMesh as OlpcMeshSettings
+from sugar.util import unique_id
+
+_NM_SERVICE = 'org.freedesktop.NetworkManager'
+_NM_IFACE = 'org.freedesktop.NetworkManager'
+_NM_PATH = '/org/freedesktop/NetworkManager'
+_NM_DEVICE_IFACE = 'org.freedesktop.NetworkManager.Device'
+_NM_OLPC_MESH_IFACE = 'org.freedesktop.NetworkManager.Device.OlpcMesh'
+
+_XS_ANYCAST = "\xc0\x27\xc0\x27\xc0\x00"
+
+DEVICE_STATE_UNKNOWN = 0
+DEVICE_STATE_UNMANAGED = 1
+DEVICE_STATE_UNAVAILABLE = 2
+DEVICE_STATE_DISCONNECTED = 3
+DEVICE_STATE_PREPARE = 4
+DEVICE_STATE_CONFIG = 5
+DEVICE_STATE_NEED_AUTH = 6
+DEVICE_STATE_IP_CONFIG = 7
+DEVICE_STATE_ACTIVATED = 8
+DEVICE_STATE_FAILED = 9
+
+class OlpcMeshManager(object):
+ def __init__(self, mesh_device):
+ self._bus = dbus.SystemBus()
+
+ self.mesh_device = mesh_device
+ self.eth_device = self._get_companion_device()
+
+ self._connection_queue = []
+ """Stack of connections that we'll iterate through until we find one
+ that works.
+
+ """
+
+ props = dbus.Interface(self.mesh_device,
+ 'org.freedesktop.DBus.Properties')
+ props.Get(_NM_DEVICE_IFACE, 'State',
+ reply_handler=self.__get_mesh_state_reply_cb,
+ error_handler=self.__get_state_error_cb)
+
+ props = dbus.Interface(self.eth_device,
+ 'org.freedesktop.DBus.Properties')
+ props.Get(_NM_DEVICE_IFACE, 'State',
+ reply_handler=self.__get_eth_state_reply_cb,
+ error_handler=self.__get_state_error_cb)
+
+ self._bus.add_signal_receiver(self.__eth_device_state_changed_cb,
+ signal_name='StateChanged',
+ path=self.eth_device.object_path,
+ dbus_interface=_NM_DEVICE_IFACE)
+
+ self._bus.add_signal_receiver(self.__mshdev_state_changed_cb,
+ signal_name='StateChanged',
+ path=self.mesh_device.object_path,
+ dbus_interface=_NM_DEVICE_IFACE)
+
+ self._idle_source = 0
+ self._mesh_device_state = DEVICE_STATE_UNKNOWN
+ self._eth_device_state = DEVICE_STATE_UNKNOWN
+
+ if self._have_configured_connections():
+ self._start_automesh_timer()
+ else:
+ self._start_automesh()
+
+ def _get_companion_device(self):
+ props = dbus.Interface(self.mesh_device,
+ 'org.freedesktop.DBus.Properties')
+ eth_device_o = props.Get(_NM_OLPC_MESH_IFACE, 'Companion')
+ return self._bus.get_object(_NM_SERVICE, eth_device_o)
+
+ def _have_configured_connections(self):
+ return len(network.get_settings().connections) > 0
+
+ def _start_automesh_timer(self):
+ """Start our timer system which basically looks for 10 seconds of
+ inactivity on both devices, then starts automesh.
+
+ """
+ if self._idle_source != 0:
+ gobject.source_remove(self._idle_source)
+ self._idle_source = gobject.timeout_add_seconds(10, self._idle_check)
+
+ def __get_state_error_cb(self, err):
+ logging.debug('Error getting the device state: %s', err)
+
+ def __get_mesh_state_reply_cb(self, state):
+ self._mesh_device_state = state
+ self._maybe_schedule_idle_check()
+
+ def __get_eth_state_reply_cb(self, state):
+ self._eth_device_state = state
+ self._maybe_schedule_idle_check()
+
+ def __eth_device_state_changed_cb(self, new_state, old_state, reason):
+ """If a connection is activated on the eth device, stop trying our
+ automatic connections.
+
+ """
+ self._eth_device_state = new_state
+ self._maybe_schedule_idle_check()
+
+ if new_state >= DEVICE_STATE_PREPARE \
+ and new_state <= DEVICE_STATE_ACTIVATED \
+ and len(self._connection_queue) > 0:
+ self._connection_queue = []
+
+ def __mshdev_state_changed_cb(self, new_state, old_state, reason):
+ self._mesh_device_state = new_state
+ self._maybe_schedule_idle_check()
+
+ if new_state == DEVICE_STATE_FAILED:
+ self._try_next_connection_from_queue()
+ elif new_state == DEVICE_STATE_ACTIVATED \
+ and len(self._connection_queue) > 0:
+ self._empty_connection_queue()
+
+ def _maybe_schedule_idle_check(self):
+ if self._mesh_device_state == DEVICE_STATE_DISCONNECTED \
+ and self._eth_device_state == DEVICE_STATE_DISCONNECTED:
+ self._start_automesh_timer()
+
+ def _idle_check(self):
+ if self._mesh_device_state == DEVICE_STATE_DISCONNECTED \
+ and self._eth_device_state == DEVICE_STATE_DISCONNECTED:
+ logging.debug("starting automesh due to inactivity")
+ self._start_automesh()
+ return False
+
+ def _make_connection(self, channel, anycast_address=None):
+ wireless_config = OlpcMeshSettings(channel, anycast_address)
+ settings = Settings(wireless_cfg=wireless_config)
+ if not anycast_address:
+ settings.ip4_config = network.IP4Config()
+ settings.ip4_config.method = 'link-local'
+ settings.connection.id = 'olpc-mesh-' + str(channel)
+ settings.connection.uuid = unique_id()
+ settings.connection.type = '802-11-olpc-mesh'
+ connection = network.add_connection(settings.connection.id, settings)
+ return connection
+
+ def __activate_reply_cb(self, connection):
+ logging.debug('Connection activated: %s', connection)
+
+ def __activate_error_cb(self, err):
+ logging.error('Failed to activate connection: %s', err)
+
+ def _activate_connection(self, channel, anycast_address=None):
+ logging.debug("activate channel %d anycast %r",
+ channel, anycast_address)
+ proxy = self._bus.get_object(_NM_SERVICE, _NM_PATH)
+ network_manager = dbus.Interface(proxy, _NM_IFACE)
+ connection = self._make_connection(channel, anycast_address)
+
+ network_manager.ActivateConnection(network.SETTINGS_SERVICE,
+ connection.path,
+ self.mesh_device.object_path,
+ self.mesh_device.object_path,
+ reply_handler=self.__activate_reply_cb,
+ error_handler=self.__activate_error_cb)
+
+ def _try_next_connection_from_queue(self):
+ if len(self._connection_queue) == 0:
+ return
+
+ channel, anycast = self._connection_queue.pop()
+ self._activate_connection(channel, anycast)
+
+ def _empty_connection_queue(self):
+ self._connection_queue = []
+
+ def user_activate_channel(self, channel):
+ """Activate a mesh connection on a user-specified channel.
+ Looks for XS first, then resorts to simple mesh."""
+ self._empty_connection_queue()
+ self._connection_queue.append((channel, None))
+ self._connection_queue.append((channel, _XS_ANYCAST))
+ self._try_next_connection_from_queue()
+
+ def _start_automesh(self):
+ """Start meshing automatically, intended when there are no better
+ networks to connect to. First looks for XS on all channels, then falls
+ back to simple mesh on channel 1."""
+ self._empty_connection_queue()
+ self._connection_queue.append((1, None))
+ self._connection_queue.append((11, _XS_ANYCAST))
+ self._connection_queue.append((6, _XS_ANYCAST))
+ self._connection_queue.append((1, _XS_ANYCAST))
+ self._try_next_connection_from_queue()
+
diff --git a/shell/src/jarabe/model/screen.py b/shell/src/jarabe/model/screen.py
new file mode 100644
index 0000000..4403c1c
--- /dev/null
+++ b/shell/src/jarabe/model/screen.py
@@ -0,0 +1,43 @@
+# Copyright (C) 2006-2008 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import logging
+
+import dbus
+
+_HARDWARE_MANAGER_INTERFACE = 'org.freedesktop.ohm.Keystore'
+_HARDWARE_MANAGER_SERVICE = 'org.freedesktop.ohm'
+_HARDWARE_MANAGER_OBJECT_PATH = '/org/freedesktop/ohm/Keystore'
+
+_ohm_service = None
+
+def _get_ohm():
+ global _ohm_service
+ if _ohm_service is None:
+ bus = dbus.SystemBus()
+ proxy = bus.get_object(_HARDWARE_MANAGER_SERVICE,
+ _HARDWARE_MANAGER_OBJECT_PATH,
+ follow_name_owner_changes=True)
+ _ohm_service = dbus.Interface(proxy, _HARDWARE_MANAGER_INTERFACE)
+
+ return _ohm_service
+
+def set_dcon_freeze(frozen):
+ try:
+ _get_ohm().SetKey("display.dcon_freeze", frozen)
+ except dbus.DBusException:
+ logging.error('Cannot unfreeze the DCON')
+
diff --git a/shell/src/jarabe/model/session.py b/shell/src/jarabe/model/session.py
new file mode 100644
index 0000000..9e0f087
--- /dev/null
+++ b/shell/src/jarabe/model/session.py
@@ -0,0 +1,89 @@
+# Copyright (C) 2008, Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import gtk
+import dbus
+import os
+import signal
+import sys
+import logging
+
+from sugar import session
+from sugar import env
+
+_session_manager = None
+
+class SessionManager(session.SessionManager):
+ MODE_LOGOUT = 0
+ MODE_SHUTDOWN = 1
+ MODE_REBOOT = 2
+
+ def __init__(self):
+ session.SessionManager.__init__(self)
+ self._logout_mode = None
+
+ def logout(self):
+ self._logout_mode = self.MODE_LOGOUT
+ self.initiate_shutdown()
+
+ def shutdown(self):
+ self._logout_mode = self.MODE_SHUTDOWN
+ self.initiate_shutdown()
+
+ def reboot(self):
+ self._logout_mode = self.MODE_REBOOT
+ self.initiate_shutdown()
+
+ def shutdown_completed(self):
+ if env.is_emulator():
+ self._close_emulator()
+ elif self._logout_mode != self.MODE_LOGOUT:
+ try:
+ bus = dbus.SystemBus()
+ proxy = bus.get_object('org.freedesktop.Hal',
+ '/org/freedesktop/Hal/devices/computer')
+ pm = dbus.Interface(proxy,
+ 'org.freedesktop.Hal.Device.SystemPowerManagement')
+
+ if self._logout_mode == self.MODE_SHUTDOWN:
+ pm.Shutdown()
+ elif self._logout_mode == self.MODE_REBOOT:
+ pm.Reboot()
+ except:
+ logging.exception('Can not stop sugar')
+ self.session.cancel_shutdown()
+ return
+
+ session.SessionManager.shutdown_completed(self)
+ gtk.main_quit()
+
+ def _close_emulator(self):
+ gtk.main_quit()
+
+ if os.environ.has_key('SUGAR_EMULATOR_PID'):
+ pid = int(os.environ['SUGAR_EMULATOR_PID'])
+ os.kill(pid, signal.SIGTERM)
+
+ # Need to call this ASAP so the atexit handlers get called before we get
+ # killed by the X (dis)connection
+ sys.exit()
+
+def get_session_manager():
+ global _session_manager
+
+ if _session_manager == None:
+ _session_manager = SessionManager()
+ return _session_manager
diff --git a/shell/src/jarabe/model/shell.py b/shell/src/jarabe/model/shell.py
new file mode 100644
index 0000000..db0e050
--- /dev/null
+++ b/shell/src/jarabe/model/shell.py
@@ -0,0 +1,641 @@
+# Copyright (C) 2006-2007 Owen Williams.
+# Copyright (C) 2006-2008 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import logging
+import time
+
+import gconf
+import wnck
+import gobject
+import gtk
+import dbus
+
+from sugar import wm
+from sugar import dispatch
+from sugar.graphics.xocolor import XoColor
+
+from jarabe.model.bundleregistry import get_registry
+from jarabe.model import neighborhood
+
+_SERVICE_NAME = "org.laptop.Activity"
+_SERVICE_PATH = "/org/laptop/Activity"
+_SERVICE_INTERFACE = "org.laptop.Activity"
+
+
+class Activity(gobject.GObject):
+ """Activity which appears in the "Home View" of the Sugar shell
+
+ This class stores the Sugar Shell's metadata regarding a
+ given activity/application in the system. It interacts with
+ the sugar.activity.* modules extensively in order to
+ accomplish its tasks.
+ """
+
+ __gtype_name__ = 'SugarHomeActivity'
+
+ LAUNCHING = 0
+ LAUNCH_FAILED = 1
+ LAUNCHED = 2
+
+ def __init__(self, activity_info, activity_id, window=None):
+ """Initialise the HomeActivity
+
+ activity_info -- sugar.activity.registry.ActivityInfo instance,
+ provides the information required to actually
+ create the new instance. This is, in effect,
+ the "type" of activity being created.
+ activity_id -- unique identifier for this instance
+ of the activity type
+ window -- Main WnckWindow of the activity
+ """
+ gobject.GObject.__init__(self)
+
+ self._window = None
+ self._service = None
+ self._activity_id = activity_id
+ self._activity_info = activity_info
+ self._launch_time = time.time()
+ self._launch_status = Activity.LAUNCHING
+
+ if window is not None:
+ self.set_window(window)
+
+ self._retrieve_service()
+
+ self._name_owner_changed_handler = None
+ if not self._service:
+ bus = dbus.SessionBus()
+ self._name_owner_changed_handler = bus.add_signal_receiver(
+ self._name_owner_changed_cb,
+ signal_name="NameOwnerChanged",
+ dbus_interface="org.freedesktop.DBus")
+
+ self._launch_completed_hid = get_model().connect('launch-completed',
+ self.__launch_completed_cb)
+ self._launch_failed_hid = get_model().connect('launch-failed',
+ self.__launch_failed_cb)
+
+ def get_launch_status(self):
+ return self._launch_status
+
+ launch_status = gobject.property(getter=get_launch_status)
+
+ def set_window(self, window):
+ """Set the window for the activity
+
+ We allow resetting the window for an activity so that we
+ can replace the launcher once we get its real window.
+ """
+ if not window:
+ raise ValueError("window must be valid")
+ self._window = window
+
+ def get_service(self):
+ """Get the activity service
+
+ Note that non-native Sugar applications will not have
+ such a service, so the return value will be None in
+ those cases.
+ """
+
+ return self._service
+
+ def get_title(self):
+ """Retrieve the application's root window's suggested title"""
+ if self._window:
+ return self._window.get_name()
+ else:
+ return ''
+
+ def get_icon_path(self):
+ """Retrieve the activity's icon (file) name"""
+ if self.is_journal():
+ icon_theme = gtk.icon_theme_get_default()
+ info = icon_theme.lookup_icon('activity-journal',
+ gtk.ICON_SIZE_SMALL_TOOLBAR, 0)
+ if not info:
+ return None
+ fname = info.get_filename()
+ del info
+ return fname
+ elif self._activity_info:
+ return self._activity_info.get_icon()
+ else:
+ return None
+
+ def get_icon_color(self):
+ """Retrieve the appropriate icon colour for this activity
+
+ Uses activity_id to index into the PresenceService's
+ set of activity colours, if the PresenceService does not
+ have an entry (implying that this is not a Sugar-shared application)
+ uses the local user's profile colour for the icon.
+ """
+ # HACK to suppress warning in logs when activity isn't found
+ # (if it's locally launched and not shared yet)
+ activity = None
+ for act in neighborhood.get_model().get_activities():
+ if self._activity_id == act.activity_id:
+ activity = act
+ break
+
+ if activity != None:
+ return activity.props.color
+ else:
+ client = gconf.client_get_default()
+ return XoColor(client.get_string("/desktop/sugar/user/color"))
+
+ def get_activity_id(self):
+ """Retrieve the "activity_id" passed in to our constructor
+
+ This is a "globally likely unique" identifier generated by
+ sugar.util.unique_id
+ """
+ return self._activity_id
+
+ def get_xid(self):
+ """Retrieve the X-windows ID of our root window"""
+ if self._window is not None:
+ return self._window.get_xid()
+ else:
+ return None
+
+ def get_window(self):
+ """Retrieve the X-windows root window of this application
+
+ This was stored by the set_window method, which was
+ called by HomeModel._add_activity, which was called
+ via a callback that looks for all 'window-opened'
+ events.
+
+ HomeModel currently uses a dbus service query on the
+ activity to determine to which HomeActivity the newly
+ launched window belongs.
+ """
+ return self._window
+
+ def get_type(self):
+ """Retrieve the activity bundle id for future reference"""
+ if self._window is None:
+ return None
+ else:
+ return wm.get_bundle_id(self._window)
+
+ def is_journal(self):
+ """Returns boolean if the activity is of type JournalActivity"""
+ return self.get_type() == 'org.laptop.JournalActivity'
+
+ def get_launch_time(self):
+ """Return the time at which the activity was first launched
+
+ Format is floating-point time.time() value
+ (seconds since the epoch)
+ """
+ return self._launch_time
+
+ def get_pid(self):
+ """Returns the activity's PID"""
+ return self._window.get_pid()
+
+ def get_bundle_path(self):
+ """Returns the activity's bundle directory"""
+ if self._activity_info is None:
+ return None
+ else:
+ return self._activity_info.get_path()
+
+ def get_activity_name(self):
+ """Returns the activity's bundle name"""
+ if self._activity_info is None:
+ return None
+ else:
+ return self._activity_info.get_name()
+
+ def equals(self, activity):
+ if self._activity_id and activity.get_activity_id():
+ return self._activity_id == activity.get_activity_id()
+ if self._window.get_xid() and activity.get_xid():
+ return self._window.get_xid() == activity.get_xid()
+ return False
+
+ def _get_service_name(self):
+ if self._activity_id:
+ return _SERVICE_NAME + self._activity_id
+ else:
+ return None
+
+ def _retrieve_service(self):
+ if not self._activity_id:
+ return
+
+ try:
+ bus = dbus.SessionBus()
+ proxy = bus.get_object(self._get_service_name(),
+ _SERVICE_PATH + "/" + self._activity_id)
+ self._service = dbus.Interface(proxy, _SERVICE_INTERFACE)
+ except dbus.DBusException:
+ self._service = None
+
+ def _name_owner_changed_cb(self, name, old, new):
+ if name == self._get_service_name():
+ if old and not new:
+ logging.debug('Activity._name_owner_changed_cb: ' \
+ 'activity %s went away', name)
+ self._name_owner_changed_handler.remove()
+ self._name_owner_changed_handler = None
+ self._service = None
+ elif not old and new:
+ logging.debug('Activity._name_owner_changed_cb: ' \
+ 'activity %s started up', name)
+ self._retrieve_service()
+ self.set_active(True)
+
+ def set_active(self, state):
+ """Propagate the current state to the activity object"""
+ if self._service is not None:
+ self._service.SetActive(state,
+ reply_handler=self._set_active_success,
+ error_handler=self._set_active_error)
+
+ def _set_active_success(self):
+ pass
+
+ def _set_active_error(self, err):
+ logging.error("set_active() failed: %s", err)
+
+ def _set_launch_status(self, value):
+ get_model().disconnect(self._launch_completed_hid)
+ get_model().disconnect(self._launch_failed_hid)
+ self._launch_completed_hid = None
+ self._launch_failed_hid = None
+ self._launch_status = value
+ self.notify('launch_status')
+
+ def __launch_completed_cb(self, model, home_activity):
+ if home_activity is self:
+ self._set_launch_status(Activity.LAUNCHED)
+
+ def __launch_failed_cb(self, model, home_activity):
+ if home_activity is self:
+ self._set_launch_status(Activity.LAUNCH_FAILED)
+
+
+class ShellModel(gobject.GObject):
+ """Model of the shell (activity management)
+
+ The ShellModel is basically the point of registration
+ for all running activities within Sugar. It traps
+ events that tell the system there is a new activity
+ being created (generated by the activity factories),
+ or removed, as well as those which tell us that the
+ currently focussed activity has changed.
+
+ The HomeModel tracks a set of HomeActivity instances,
+ which are tracking the window to activity mappings
+ the activity factories have set up.
+ """
+
+ __gsignals__ = {
+ 'activity-added': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ 'activity-removed': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ 'active-activity-changed': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ 'tabbing-activity-changed': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ 'launch-started': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ 'launch-completed': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ 'launch-failed': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT]))
+ }
+
+ ZOOM_MESH = 0
+ ZOOM_GROUP = 1
+ ZOOM_HOME = 2
+ ZOOM_ACTIVITY = 3
+
+ def __init__(self):
+ gobject.GObject.__init__(self)
+
+ self._screen = wnck.screen_get_default()
+ self._screen.connect('window-opened', self._window_opened_cb)
+ self._screen.connect('window-closed', self._window_closed_cb)
+ self._screen.connect('active-window-changed',
+ self._active_window_changed_cb)
+
+ self.zoom_level_changed = dispatch.Signal()
+
+ self._desktop_level = self.ZOOM_HOME
+ self._zoom_level = self.ZOOM_HOME
+ self._current_activity = None
+ self._activities = []
+ self._active_activity = None
+ self._tabbing_activity = None
+ self._launchers = {}
+
+ self._screen.toggle_showing_desktop(True)
+
+ def get_launcher(self, activity_id):
+ return self._launchers.get(str(activity_id))
+
+ def register_launcher(self, activity_id, launcher):
+ self._launchers[activity_id] = launcher
+
+ def unregister_launcher(self, activity_id):
+ if activity_id in self._launchers:
+ del self._launchers[activity_id]
+
+ def _update_zoom_level(self, window):
+ if window.get_window_type() == wnck.WINDOW_DIALOG:
+ return
+ elif window.get_window_type() == wnck.WINDOW_NORMAL:
+ new_level = self.ZOOM_ACTIVITY
+ else:
+ new_level = self._desktop_level
+
+ if self._zoom_level != new_level:
+ old_level = self._zoom_level
+ self._zoom_level = new_level
+ self.zoom_level_changed.send(self, old_level=old_level,
+ new_level=new_level)
+
+ def set_zoom_level(self, new_level, x_event_time=0):
+ old_level = self.zoom_level
+ if old_level == new_level:
+ return
+
+ if old_level != self.ZOOM_ACTIVITY:
+ screen = gtk.gdk.screen_get_default()
+ active_window_type = screen.get_active_window().get_type_hint()
+ if active_window_type != gtk.gdk.WINDOW_TYPE_HINT_DESKTOP:
+ return
+
+ self._zoom_level = new_level
+ if new_level is not self.ZOOM_ACTIVITY:
+ self._desktop_level = new_level
+
+ self.zoom_level_changed.send(self, old_level=old_level,
+ new_level=new_level)
+
+ show_desktop = new_level is not self.ZOOM_ACTIVITY
+ self._screen.toggle_showing_desktop(show_desktop)
+
+ if new_level is self.ZOOM_ACTIVITY:
+ # activate the window, in case it was iconified
+ # (e.g. during sugar launch, the Journal starts in this state)
+ window = self._active_activity.get_window()
+ if window:
+ window.activate(x_event_time or gtk.get_current_event_time())
+
+ def _get_zoom_level(self):
+ return self._zoom_level
+
+ zoom_level = property(_get_zoom_level)
+
+ def _get_activities_with_window(self):
+ ret = []
+ for i in self._activities:
+ if i.get_window() is not None:
+ ret.append(i)
+ return ret
+
+ def get_previous_activity(self, current=None):
+ if not current:
+ current = self._active_activity
+
+ activities = self._get_activities_with_window()
+ i = activities.index(current)
+ if len(activities) == 0:
+ return None
+ elif i - 1 >= 0:
+ return activities[i - 1]
+ else:
+ return activities[len(activities) - 1]
+
+ def get_next_activity(self, current=None):
+ if not current:
+ current = self._active_activity
+
+ activities = self._get_activities_with_window()
+ i = activities.index(current)
+ if len(activities) == 0:
+ return None
+ elif i + 1 < len(activities):
+ return activities[i + 1]
+ else:
+ return activities[0]
+
+ def get_active_activity(self):
+ """Returns the activity that the user is currently working in"""
+ return self._active_activity
+
+ def get_tabbing_activity(self):
+ """Returns the activity that is currently highlighted during tabbing"""
+ return self._tabbing_activity
+
+ def set_tabbing_activity(self, activity):
+ """Sets the activity that is currently highlighted during tabbing"""
+ self._tabbing_activity = activity
+ self.emit("tabbing-activity-changed", self._tabbing_activity)
+
+ def _set_active_activity(self, home_activity):
+ if self._active_activity == home_activity:
+ return
+
+ if home_activity:
+ home_activity.set_active(True)
+
+ if self._active_activity:
+ self._active_activity.set_active(False)
+
+ self._active_activity = home_activity
+ self.emit('active-activity-changed', self._active_activity)
+
+ def __iter__(self):
+ return iter(self._activities)
+
+ def __len__(self):
+ return len(self._activities)
+
+ def __getitem__(self, i):
+ return self._activities[i]
+
+ def index(self, obj):
+ return self._activities.index(obj)
+
+ def _window_opened_cb(self, screen, window):
+ if window.get_window_type() == wnck.WINDOW_NORMAL:
+ home_activity = None
+
+ activity_id = wm.get_activity_id(window)
+
+ service_name = wm.get_bundle_id(window)
+ if service_name:
+ registry = get_registry()
+ activity_info = registry.get_bundle(service_name)
+ else:
+ activity_info = None
+
+ if activity_id:
+ home_activity = self.get_activity_by_id(activity_id)
+
+ xid = window.get_xid()
+ gdk_window = gtk.gdk.window_foreign_new(xid)
+ gdk_window.set_decorations(0)
+
+ window.maximize()
+
+ if not home_activity:
+ home_activity = Activity(activity_info, activity_id, window)
+ self._add_activity(home_activity)
+ else:
+ home_activity.set_window(window)
+
+ if wm.get_sugar_window_type(window) != 'launcher':
+ self.emit('launch-completed', home_activity)
+
+ startup_time = time.time() - home_activity.get_launch_time()
+ logging.debug('%s launched in %f seconds.',
+ home_activity.get_type(), startup_time)
+
+ if self._active_activity is None:
+ self._set_active_activity(home_activity)
+
+ def _window_closed_cb(self, screen, window):
+ if window.get_window_type() == wnck.WINDOW_NORMAL:
+ if self._get_activity_by_xid(window.get_xid()) is not None:
+ self._remove_activity_by_xid(window.get_xid())
+
+ def _get_activity_by_xid(self, xid):
+ for home_activity in self._activities:
+ if home_activity.get_xid() == xid:
+ return home_activity
+ return None
+
+ def get_activity_by_id(self, activity_id):
+ for home_activity in self._activities:
+ if home_activity.get_activity_id() == activity_id:
+ return home_activity
+ return None
+
+ def _active_window_changed_cb(self, screen, previous_window=None):
+ window = screen.get_active_window()
+ if window is None:
+ return
+
+ if window.get_window_type() != wnck.WINDOW_DIALOG:
+ while window.get_transient() is not None:
+ window = window.get_transient()
+
+ act = self._get_activity_by_xid(window.get_xid())
+ if act is not None:
+ self._set_active_activity(act)
+
+ self._update_zoom_level(window)
+
+ def _add_activity(self, home_activity):
+ self._activities.append(home_activity)
+ self.emit('activity-added', home_activity)
+
+ def _remove_activity(self, home_activity):
+ if home_activity == self._active_activity:
+ windows = wnck.screen_get_default().get_windows_stacked()
+ windows.reverse()
+ for window in windows:
+ new_activity = self._get_activity_by_xid(window.get_xid())
+ if new_activity is not None:
+ self._set_active_activity(new_activity)
+ break
+ else:
+ logging.error('No activities are running')
+ self._set_active_activity(None)
+
+ self.emit('activity-removed', home_activity)
+ self._activities.remove(home_activity)
+
+ def _remove_activity_by_xid(self, xid):
+ home_activity = self._get_activity_by_xid(xid)
+ if home_activity:
+ self._remove_activity(home_activity)
+ else:
+ logging.error('Model for window %d does not exist.', xid)
+
+ def notify_launch(self, activity_id, service_name):
+ registry = get_registry()
+ activity_info = registry.get_bundle(service_name)
+ if not activity_info:
+ raise ValueError("Activity service name '%s'" \
+ " was not found in the bundle registry."
+ % service_name)
+ home_activity = Activity(activity_info, activity_id)
+ self._add_activity(home_activity)
+
+ self._set_active_activity(home_activity)
+
+ self.emit('launch-started', home_activity)
+
+ # FIXME: better learn about finishing processes by receiving a signal.
+ # Now just check whether an activity has a window after ~90sec
+ gobject.timeout_add_seconds(90, self._check_activity_launched,
+ activity_id)
+
+ def notify_launch_failed(self, activity_id):
+ home_activity = self.get_activity_by_id(activity_id)
+ if home_activity:
+ logging.debug("Activity %s (%s) launch failed", activity_id,
+ home_activity.get_type())
+ if self.get_launcher(activity_id) is not None:
+ self.emit('launch-failed', home_activity)
+ else:
+ # activity sent failure notification after closing launcher
+ self._remove_activity(home_activity)
+ else:
+ logging.error('Model for activity id %s does not exist.',
+ activity_id)
+
+ def _check_activity_launched(self, activity_id):
+ home_activity = self.get_activity_by_id(activity_id)
+
+ if not home_activity:
+ logging.debug('Activity %s has been closed already.', activity_id)
+ return False
+
+ if self.get_launcher(activity_id) is not None:
+ logging.debug('Activity %s still launching, assuming it failed.',
+ activity_id)
+ self.notify_launch_failed(activity_id)
+ return False
+
+
+_model = None
+
+def get_model():
+ global _model
+ if _model is None:
+ _model = ShellModel()
+ return _model
+
diff --git a/shell/src/jarabe/model/sound.py b/shell/src/jarabe/model/sound.py
new file mode 100644
index 0000000..65090a4
--- /dev/null
+++ b/shell/src/jarabe/model/sound.py
@@ -0,0 +1,58 @@
+# Copyright (C) 2006-2008 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import gconf
+
+from sugar import env
+from sugar import _sugarext
+from sugar import dispatch
+
+VOLUME_STEP = 10
+
+muted_changed = dispatch.Signal()
+volume_changed = dispatch.Signal()
+
+def get_muted():
+ return _volume.get_mute()
+
+def get_volume():
+ return _volume.get_volume()
+
+def set_volume(new_volume):
+ old_volume = _volume.get_volume()
+ _volume.set_volume(new_volume)
+
+ volume_changed.send(None)
+ save()
+
+def set_muted(new_state):
+ old_state = _volume.get_mute()
+ _volume.set_mute(new_state)
+
+ muted_changed.send(None)
+ save()
+
+def save():
+ if env.is_emulator() is False:
+ client = gconf.client_get_default()
+ client.set_int('/desktop/sugar/sound/volume', get_volume())
+
+def restore():
+ if env.is_emulator() is False:
+ client = gconf.client_get_default()
+ set_volume(client.get_int('/desktop/sugar/sound/volume'))
+
+_volume = _sugarext.VolumeAlsa()
diff --git a/shell/src/jarabe/model/telepathyclient.py b/shell/src/jarabe/model/telepathyclient.py
new file mode 100644
index 0000000..f4eccc3
--- /dev/null
+++ b/shell/src/jarabe/model/telepathyclient.py
@@ -0,0 +1,100 @@
+# Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import logging
+
+import dbus
+from dbus import PROPERTIES_IFACE
+from telepathy.interfaces import CLIENT, \
+ CLIENT_APPROVER, \
+ CLIENT_HANDLER, \
+ CLIENT_INTERFACE_REQUESTS
+from telepathy.server import DBusProperties
+
+from sugar import dispatch
+
+SUGAR_CLIENT_SERVICE = 'org.freedesktop.Telepathy.Client.Sugar'
+SUGAR_CLIENT_PATH = '/org/freedesktop/Telepathy/Client/Sugar'
+
+class TelepathyClient(dbus.service.Object, DBusProperties):
+ def __init__(self):
+ self._interfaces = set([CLIENT, CLIENT_HANDLER,
+ CLIENT_INTERFACE_REQUESTS, PROPERTIES_IFACE,
+ CLIENT_APPROVER])
+
+ bus = dbus.Bus()
+ bus_name = dbus.service.BusName(SUGAR_CLIENT_SERVICE, bus=bus)
+
+ dbus.service.Object.__init__(self, bus_name, SUGAR_CLIENT_PATH)
+ DBusProperties.__init__(self)
+
+ self._implement_property_get(CLIENT, {
+ 'Interfaces': lambda: list(self._interfaces),
+ })
+ self._implement_property_get(CLIENT_HANDLER, {
+ 'HandlerChannelFilter': self.__get_filters_cb,
+ })
+ self._implement_property_get(CLIENT_APPROVER, {
+ 'ApproverChannelFilter': self.__get_filters_cb,
+ })
+
+ self.got_channel = dispatch.Signal()
+ self.got_dispatch_operation = dispatch.Signal()
+
+ def __get_filters_cb(self):
+ logging.debug('__get_filters_cb')
+ filter_dict = dbus.Dictionary({}, signature='sv')
+ return dbus.Array([filter_dict], signature='a{sv}')
+
+ @dbus.service.method(dbus_interface=CLIENT_HANDLER,
+ in_signature='ooa(oa{sv})aota{sv}', out_signature='')
+ def HandleChannels(self, account, connection, channels, requests_satisfied,
+ user_action_time, handler_info):
+ logging.debug('HandleChannels\n%r\n%r\n%r\n%r\n%r\n%r\n', account,
+ connection, channels, requests_satisfied,
+ user_action_time, handler_info)
+ for channel in channels:
+ self.got_channel.send(self, account=account,
+ connection=connection, channel=channel)
+
+ @dbus.service.method(dbus_interface=CLIENT_INTERFACE_REQUESTS,
+ in_signature='oa{sv}', out_signature='')
+ def AddRequest(self, request, properties):
+ logging.debug('AddRequest\n%r\n%r', request, properties)
+
+ @dbus.service.method(dbus_interface=CLIENT_APPROVER,
+ in_signature='a(oa{sv})oa{sv}', out_signature='',
+ async_callbacks=('success_cb', 'error_cb_'))
+ def AddDispatchOperation(self, channels, dispatch_operation_path,
+ properties, success_cb, error_cb_):
+ success_cb()
+ try:
+ logging.debug('AddDispatchOperation\n%r\n%r\n%r', channels,
+ dispatch_operation_path, properties)
+
+ self.got_dispatch_operation.send(self, channels=channels,
+ dispatch_operation_path=dispatch_operation_path,
+ properties=properties)
+ except Exception, e:
+ logging.exception(e)
+
+_instance = None
+
+def get_instance():
+ global _instance
+ if not _instance:
+ _instance = TelepathyClient()
+ return _instance
diff --git a/shell/src/jarabe/util/Makefile.am b/shell/src/jarabe/util/Makefile.am
new file mode 100644
index 0000000..8bda3d6
--- /dev/null
+++ b/shell/src/jarabe/util/Makefile.am
@@ -0,0 +1,7 @@
+SUBDIRS = \
+ telepathy
+
+sugardir = $(pythondir)/jarabe/util
+sugar_PYTHON = \
+ __init__.py \
+ emulator.py
diff --git a/shell/src/jarabe/util/__init__.py b/shell/src/jarabe/util/__init__.py
new file mode 100644
index 0000000..1610dd0
--- /dev/null
+++ b/shell/src/jarabe/util/__init__.py
@@ -0,0 +1,19 @@
+"""OLPC Sugar Jarabe utility modules
+"""
+
+# 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/shell/src/jarabe/util/emulator.py b/shell/src/jarabe/util/emulator.py
new file mode 100644
index 0000000..5a99dbe
--- /dev/null
+++ b/shell/src/jarabe/util/emulator.py
@@ -0,0 +1,177 @@
+# Copyright (C) 2006-2008, Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import signal
+import subprocess
+import sys
+import time
+from optparse import OptionParser
+
+import gtk
+import gobject
+
+from sugar import env
+
+
+ERROR_NO_DISPLAY = 30
+ERROR_NO_SERVER = 31
+
+
+default_dimensions = (800, 600)
+def _run_xephyr(display, dpi, dimensions, fullscreen):
+ cmd = [ 'Xephyr' ]
+ cmd.append(':%d' % display)
+ cmd.append('-ac')
+
+ screen_size = (gtk.gdk.screen_width(), gtk.gdk.screen_height())
+
+ if (not dimensions) and (fullscreen is None) and \
+ (screen_size < default_dimensions) :
+ # no forced settings, screen too small => fit screen
+ fullscreen = True
+ elif (not dimensions) :
+ # screen is big enough or user has en/disabled fullscreen manually
+ # => use default size (will get ignored for fullscreen)
+ dimensions = '%dx%d' % default_dimensions
+
+ if not dpi :
+ dpi = gtk.settings_get_default().get_property('gtk-xft-dpi') / 1024
+
+ if fullscreen :
+ cmd.append('-fullscreen')
+
+ if dimensions :
+ cmd.append('-screen')
+ cmd.append(dimensions)
+
+ if dpi :
+ cmd.append('-dpi')
+ cmd.append('%d' % dpi)
+
+ cmd.append('-noreset')
+
+ try:
+ pipe = subprocess.Popen(cmd)
+
+ except OSError, exc:
+ sys.stderr.write('Error executing server: %s\n' % (exc, ))
+ return None
+
+ return pipe
+
+
+def _check_server(display):
+ result = subprocess.call(['xdpyinfo', '-display', ':%d' % display],
+ stdout=open(os.devnull, "w"),
+ stderr=open(os.devnull, "w"))
+ return result == 0
+
+
+def _kill_pipe(pipe):
+ """Terminate and wait for child process."""
+ try:
+ os.kill(pipe.pid, signal.SIGTERM)
+ except OSError:
+ pass
+
+ pipe.wait()
+
+
+def _start_xephyr(dpi, dimensions, fullscreen):
+ for display in range(30, 40):
+ if not _check_server(display):
+ pipe = _run_xephyr(display, dpi, dimensions, fullscreen)
+ if pipe is None:
+ return None, None
+
+ for i_ in range(10):
+ if _check_server(display):
+ return pipe, display
+
+ time.sleep(0.1)
+
+ _kill_pipe(pipe)
+
+ return None, None
+
+
+def _start_window_manager():
+ cmd = ['metacity']
+
+ cmd.extend(['--no-force-fullscreen'])
+
+ gobject.spawn_async(cmd, flags=gobject.SPAWN_SEARCH_PATH)
+
+def _setup_env(display, scaling, emulator_pid):
+ os.environ['SUGAR_EMULATOR'] = 'yes'
+ os.environ['GABBLE_LOGFILE'] = os.path.join(
+ env.get_profile_path(), 'logs', 'telepathy-gabble.log')
+ os.environ['SALUT_LOGFILE'] = os.path.join(
+ env.get_profile_path(), 'logs', 'telepathy-salut.log')
+ os.environ['STREAM_ENGINE_LOGFILE'] = os.path.join(
+ env.get_profile_path(), 'logs', 'telepathy-stream-engine.log')
+ os.environ['DISPLAY'] = ":%d" % (display)
+ os.environ['SUGAR_EMULATOR_PID'] = emulator_pid
+
+ if scaling:
+ os.environ['SUGAR_SCALING'] = scaling
+
+def main():
+ """Script-level operations"""
+
+ parser = OptionParser()
+ parser.add_option('-d', '--dpi', dest='dpi', type="int",
+ help='Emulator dpi')
+ parser.add_option('-s', '--scaling', dest='scaling',
+ help='Sugar scaling in %')
+ parser.add_option('-i', '--dimensions', dest='dimensions',
+ help='Emulator dimensions (ex. 1200x900)')
+ parser.add_option('-f', '--fullscreen', dest='fullscreen',
+ action='store_true', default=None,
+ help='Run emulator in fullscreen mode')
+ parser.add_option('-F', '--no-fullscreen', dest='fullscreen',
+ action='store_false',
+ help='Do not run emulator in fullscreen mode')
+ (options, args) = parser.parse_args()
+
+ if not os.environ.get('DISPLAY'):
+ sys.stderr.write('DISPLAY not set, cannot connect to host X server.\n')
+ return ERROR_NO_DISPLAY
+
+ server, display = _start_xephyr(options.dpi, options.dimensions,
+ options.fullscreen)
+ if server is None:
+ sys.stderr.write('Failed to start server. Please check output above'
+ ' for any error message.\n')
+ return ERROR_NO_SERVER
+
+ _setup_env(display, options.scaling, str(server.pid))
+
+ command = ['dbus-launch', '--exit-with-session']
+
+ if not args:
+ command.append('sugar')
+ else:
+ _start_window_manager()
+
+ if args[0].endswith('.py'):
+ command.append('python')
+
+ command.append(args[0])
+
+ subprocess.call(command)
+ _kill_pipe(server)
diff --git a/shell/src/jarabe/util/telepathy/Makefile.am b/shell/src/jarabe/util/telepathy/Makefile.am
new file mode 100644
index 0000000..d40349d
--- /dev/null
+++ b/shell/src/jarabe/util/telepathy/Makefile.am
@@ -0,0 +1,4 @@
+sugardir = $(pythondir)/jarabe/util/telepathy
+sugar_PYTHON = \
+ __init__.py \
+ connection_watcher.py
diff --git a/shell/src/jarabe/util/telepathy/__init__.py b/shell/src/jarabe/util/telepathy/__init__.py
new file mode 100644
index 0000000..387d09c
--- /dev/null
+++ b/shell/src/jarabe/util/telepathy/__init__.py
@@ -0,0 +1,19 @@
+"""OLPC Sugar Jarabe utility telepathy modules
+"""
+
+# 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/shell/src/jarabe/util/telepathy/connection_watcher.py b/shell/src/jarabe/util/telepathy/connection_watcher.py
new file mode 100644
index 0000000..391bdd5
--- /dev/null
+++ b/shell/src/jarabe/util/telepathy/connection_watcher.py
@@ -0,0 +1,118 @@
+# This should eventually land in telepathy-python, so has the same license:
+# Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# This program 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.1 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+# FIXME: this sould go upstream, in telepathy-python
+
+import logging
+
+import dbus
+import dbus.mainloop.glib
+import gobject
+
+from telepathy.client import Connection
+from telepathy.interfaces import CONN_INTERFACE
+from telepathy.constants import CONNECTION_STATUS_CONNECTED, \
+ CONNECTION_STATUS_DISCONNECTED
+
+class ConnectionWatcher(gobject.GObject):
+ __gsignals__ = {
+ 'connection-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ 'connection-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT]))
+ }
+
+ def __init__(self, bus=None):
+ gobject.GObject.__init__(self)
+
+ if bus is None:
+ self.bus = dbus.Bus()
+ else:
+ self.bus = bus
+
+ # D-Bus path -> Connection
+ self._connections = {}
+
+ self.bus.add_signal_receiver(self._status_changed_cb,
+ dbus_interface=CONN_INTERFACE, signal_name='StatusChanged',
+ path_keyword='path')
+
+ for conn in Connection.get_connections(bus):
+ conn.call_when_ready(self._conn_ready_cb)
+
+ def _status_changed_cb(self, *args, **kwargs):
+ path = kwargs['path']
+ if not path.startswith('/org/freedesktop/Telepathy/Connection/'):
+ return
+
+ status, reason_ = args
+ service_name = path.replace('/', '.')[1:]
+
+ if status == CONNECTION_STATUS_CONNECTED:
+ self._add_connection(service_name, path)
+ elif status == CONNECTION_STATUS_DISCONNECTED:
+ self._remove_connection(service_name, path)
+
+ def _conn_ready_cb(self, conn):
+ if conn.object_path in self._connections:
+ return
+
+ self._connections[conn.object_path] = conn
+ self.emit('connection-added', conn)
+
+ def _add_connection(self, service_name, path):
+ if path in self._connections:
+ return
+
+ try:
+ Connection(service_name, path, ready_handler=self._conn_ready_cb)
+ except dbus.exceptions.DBusException:
+ logging.debug('%s is propably already gone.', service_name)
+
+ def _remove_connection(self, service_name, path):
+ conn = self._connections.pop(path, None)
+ if conn is None:
+ return
+
+ self.emit('connection-removed', conn)
+
+ def get_connections(self):
+ return self._connections.values()
+
+_instance = None
+
+def get_instance():
+ global _instance
+ if _instance is None:
+ _instance = ConnectionWatcher()
+ return _instance
+
+if __name__ == '__main__':
+ dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+
+ def connection_added_cb(conn_watcher, conn):
+ print "new connection", conn.service_name
+
+ def connection_removed_cb(conn_watcher, conn):
+ print "removed connection", conn.service_name
+
+ watcher = ConnectionWatcher()
+ watcher.connect('connection-added', connection_added_cb)
+ watcher.connect('connection-removed', connection_removed_cb)
+
+ loop = gobject.MainLoop()
+ loop.run()
diff --git a/shell/src/jarabe/view/Makefile.am b/shell/src/jarabe/view/Makefile.am
new file mode 100644
index 0000000..1abea6d
--- /dev/null
+++ b/shell/src/jarabe/view/Makefile.am
@@ -0,0 +1,12 @@
+sugardir = $(pythondir)/jarabe/view
+sugar_PYTHON = \
+ __init__.py \
+ buddyicon.py \
+ buddymenu.py \
+ keyhandler.py \
+ launcher.py \
+ palettes.py \
+ pulsingicon.py \
+ service.py \
+ tabbinghandler.py \
+ viewsource.py
diff --git a/shell/src/jarabe/view/__init__.py b/shell/src/jarabe/view/__init__.py
new file mode 100644
index 0000000..a9dd95a
--- /dev/null
+++ b/shell/src/jarabe/view/__init__.py
@@ -0,0 +1,16 @@
+# Copyright (C) 2006-2007, Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
diff --git a/shell/src/jarabe/view/buddyicon.py b/shell/src/jarabe/view/buddyicon.py
new file mode 100644
index 0000000..37b9167
--- /dev/null
+++ b/shell/src/jarabe/view/buddyicon.py
@@ -0,0 +1,61 @@
+# Copyright (C) 2006-2007 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from sugar.graphics.icon import CanvasIcon
+from sugar.graphics import style
+
+from jarabe.view.buddymenu import BuddyMenu
+
+class BuddyIcon(CanvasIcon):
+ def __init__(self, buddy, size=style.STANDARD_ICON_SIZE):
+ CanvasIcon.__init__(self, icon_name='computer-xo', size=size)
+
+ self._greyed_out = False
+ self._buddy = buddy
+ self._buddy.connect('notify::present', self.__buddy_notify_present_cb)
+ self._buddy.connect('notify::color', self.__buddy_notify_color_cb)
+
+ self._update_color()
+
+ def create_palette(self):
+ return BuddyMenu(self._buddy)
+
+ def __buddy_notify_present_cb(self, buddy, pspec):
+ # Update the icon's color when the buddy comes and goes
+ self._update_color()
+
+ def __buddy_notify_color_cb(self, buddy, pspec):
+ self._update_color()
+
+ def _update_color(self):
+ # keep the icon in the palette in sync with the view
+ palette = self.get_palette()
+ if self._greyed_out:
+ self.props.stroke_color = '#D5D5D5'
+ self.props.fill_color = style.COLOR_TRANSPARENT.get_svg()
+ if palette is not None:
+ palette.props.icon.props.stroke_color = self.props.stroke_color
+ palette.props.icon.props.fill_color = self.props.fill_color
+ else:
+ self.props.xo_color = self._buddy.get_color()
+ if palette is not None:
+ palette.props.icon.props.xo_color = self._buddy.get_color()
+
+ def set_filter(self, query):
+ self._greyed_out = (self._buddy.get_nick().lower().find(query) == -1) \
+ and not self._buddy.is_owner()
+ self._update_color()
+
diff --git a/shell/src/jarabe/view/buddymenu.py b/shell/src/jarabe/view/buddymenu.py
new file mode 100644
index 0000000..0ba6cc1
--- /dev/null
+++ b/shell/src/jarabe/view/buddymenu.py
@@ -0,0 +1,168 @@
+# Copyright (C) 2006-2007 Red Hat, Inc.
+# Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import logging
+from gettext import gettext as _
+
+import gtk
+import gconf
+import dbus
+
+from sugar.graphics.palette import Palette
+from sugar.graphics.menuitem import MenuItem
+from sugar.graphics.icon import Icon
+
+from jarabe.model import shell
+from jarabe.model import friends
+from jarabe.model.session import get_session_manager
+from jarabe.controlpanel.gui import ControlPanel
+
+class BuddyMenu(Palette):
+ def __init__(self, buddy):
+ self._buddy = buddy
+
+ buddy_icon = Icon(icon_name='computer-xo',
+ xo_color=buddy.get_color(),
+ icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR)
+ Palette.__init__(self, None, primary_text=buddy.get_nick(),
+ icon=buddy_icon)
+ self._invite_menu = None
+ self._active_activity_changed_hid = None
+ self.connect('destroy', self.__destroy_cb)
+
+ self._buddy.connect('notify::nick', self.__buddy_notify_nick_cb)
+
+ if buddy.is_owner():
+ self._add_my_items()
+ else:
+ self._add_buddy_items()
+
+ def __destroy_cb(self, menu):
+ if self._active_activity_changed_hid is not None:
+ home_model = shell.get_model()
+ home_model.disconnect(self._active_activity_changed_hid)
+ self._buddy.disconnect_by_func(self.__buddy_notify_nick_cb)
+
+ def _add_buddy_items(self):
+ if friends.get_model().has_buddy(self._buddy):
+ menu_item = MenuItem(_('Remove friend'), 'list-remove')
+ menu_item.connect('activate', self._remove_friend_cb)
+ else:
+ menu_item = MenuItem(_('Make friend'), 'list-add')
+ menu_item.connect('activate', self._make_friend_cb)
+
+ self.menu.append(menu_item)
+ menu_item.show()
+
+ self._invite_menu = MenuItem('')
+ self._invite_menu.connect('activate', self._invite_friend_cb)
+ self.menu.append(self._invite_menu)
+
+ home_model = shell.get_model()
+ self._active_activity_changed_hid = home_model.connect(
+ 'active-activity-changed', self._cur_activity_changed_cb)
+ activity = home_model.get_active_activity()
+ self._update_invite_menu(activity)
+
+ def _add_my_items(self):
+ item = MenuItem(_('Shutdown'), 'system-shutdown')
+ item.connect('activate', self.__shutdown_activate_cb)
+ self.menu.append(item)
+ item.show()
+
+ client = gconf.client_get_default()
+
+ if client.get_bool('/desktop/sugar/show_logout'):
+ item = MenuItem(_('Logout'), 'system-logout')
+ item.connect('activate', self.__logout_activate_cb)
+ self.menu.append(item)
+ item.show()
+
+ item = MenuItem(_('My Settings'), 'preferences-system')
+ item.connect('activate', self.__controlpanel_activate_cb)
+ self.menu.append(item)
+ item.show()
+
+ def __logout_activate_cb(self, menu_item):
+ session_manager = get_session_manager()
+ session_manager.logout()
+
+ def __reboot_activate_cb(self, menu_item):
+ session_manager = get_session_manager()
+ session_manager.reboot()
+
+ def __shutdown_activate_cb(self, menu_item):
+ session_manager = get_session_manager()
+ session_manager.shutdown()
+
+ def __controlpanel_activate_cb(self, menu_item):
+ panel = ControlPanel()
+ panel.set_transient_for(self.get_toplevel())
+ panel.show()
+
+ def _update_invite_menu(self, activity):
+ buddy_activity = self._buddy.props.current_activity
+ if buddy_activity is not None:
+ buddy_activity_id = buddy_activity.activity_id
+ else:
+ buddy_activity_id = None
+
+ if activity is None or activity.is_journal() or \
+ activity.get_activity_id() == buddy_activity_id:
+ self._invite_menu.hide()
+ else:
+ title = activity.get_title()
+ label = self._invite_menu.get_children()[0]
+ label.set_text(_('Invite to %s') % title)
+
+ icon = Icon(file=activity.get_icon_path())
+ icon.props.xo_color = activity.get_icon_color()
+ self._invite_menu.set_image(icon)
+ icon.show()
+
+ self._invite_menu.show()
+
+ def _cur_activity_changed_cb(self, home_model, activity_model):
+ self._update_invite_menu(activity_model)
+
+ def __buddy_notify_nick_cb(self, buddy, pspec):
+ self.set_primary_text(buddy.props.nick)
+
+ def _make_friend_cb(self, menuitem):
+ friends.get_model().make_friend(self._buddy)
+
+ def _remove_friend_cb(self, menuitem):
+ friends.get_model().remove(self._buddy)
+
+ def _invite_friend_cb(self, menuitem):
+ activity = shell.get_model().get_active_activity()
+ service = activity.get_service()
+ if service:
+ try:
+ service.InviteContact(self._buddy.props.account,
+ self._buddy.props.contact_id)
+ except dbus.DBusException, e:
+ expected_exceptions = [
+ 'org.freedesktop.DBus.Error.UnknownMethod',
+ 'org.freedesktop.DBus.Python.NotImplementedError']
+ if e.get_dbus_name() in expected_exceptions:
+ logging.warning('Trying deprecated Activity.Invite')
+ service.Invite(self._buddy.props.key)
+ else:
+ raise
+ else:
+ logging.error('Invite failed, activity service not ')
diff --git a/shell/src/jarabe/view/keyhandler.py b/shell/src/jarabe/view/keyhandler.py
new file mode 100644
index 0000000..8a85ac7
--- /dev/null
+++ b/shell/src/jarabe/view/keyhandler.py
@@ -0,0 +1,242 @@
+# Copyright (C) 2006-2007, Red Hat, Inc.
+# Copyright (C) 2009 Simon Schampijer
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import logging
+import traceback
+
+import dbus
+import gtk
+
+from sugar._sugarext import KeyGrabber
+
+from jarabe.model import sound
+from jarabe.model import shell
+from jarabe.model import session
+from jarabe.view.tabbinghandler import TabbingHandler
+from jarabe.model.shell import ShellModel
+from jarabe import config
+from jarabe.journal import journalactivity
+
+_VOLUME_STEP = sound.VOLUME_STEP
+_VOLUME_MAX = 100
+_TABBING_MODIFIER = gtk.gdk.MOD1_MASK
+
+_actions_table = {
+ 'F1' : 'zoom_mesh',
+ 'F2' : 'zoom_group',
+ 'F3' : 'zoom_home',
+ 'F4' : 'zoom_activity',
+ 'F5' : 'open_search',
+ 'F6' : 'frame',
+ 'XF86AudioMute' : 'volume_mute',
+ 'F11' : 'volume_down',
+ 'XF86AudioLowerVolume' : 'volume_down',
+ 'F12' : 'volume_up',
+ 'XF86AudioRaiseVolume' : 'volume_up',
+ '<alt>F11' : 'volume_min',
+ '<alt>F12' : 'volume_max',
+ '0x93' : 'frame',
+ '<alt>Tab' : 'next_window',
+ '<alt><shift>Tab' : 'previous_window',
+ '<alt>Escape' : 'close_window',
+ '0xDC' : 'open_search',
+# the following are intended for emulator users
+ '<alt><shift>f' : 'frame',
+ '<alt><shift>q' : 'quit_emulator',
+ 'XF86Search' : 'open_search',
+ '<alt><shift>o' : 'open_search',
+ '<alt><shift>s' : 'say_text',
+}
+
+SPEECH_DBUS_SERVICE = 'org.laptop.Speech'
+SPEECH_DBUS_PATH = '/org/laptop/Speech'
+SPEECH_DBUS_INTERFACE = 'org.laptop.Speech'
+
+class KeyHandler(object):
+ def __init__(self, frame):
+ self._frame = frame
+ self._key_pressed = None
+ self._keycode_pressed = 0
+ self._keystate_pressed = 0
+ self._speech_proxy = None
+
+ self._key_grabber = KeyGrabber()
+ self._key_grabber.connect('key-pressed',
+ self._key_pressed_cb)
+ self._key_grabber.connect('key-released',
+ self._key_released_cb)
+
+ self._tabbing_handler = TabbingHandler(self._frame, _TABBING_MODIFIER)
+
+ for f in os.listdir(os.path.join(config.ext_path, 'globalkey')):
+ if f.endswith('.py') and not f.startswith('__'):
+ module_name = f[:-3]
+ try:
+ logging.debug('Loading module %r', module_name)
+ module = __import__('globalkey.' + module_name, globals(),
+ locals(), [module_name])
+ for key in module.BOUND_KEYS:
+ if key in _actions_table:
+ raise ValueError('Key %r is already bound' % key)
+ _actions_table[key] = module
+ except Exception:
+ logging.error('Exception while loading extension:\n' + \
+ traceback.format_exc())
+
+ self._key_grabber.grab_keys(_actions_table.keys())
+
+ def _change_volume(self, step=None, value=None):
+ if step is not None:
+ volume = sound.get_volume() + step
+ elif value is not None:
+ volume = value
+
+ volume = min(max(0, volume), _VOLUME_MAX)
+
+ sound.set_volume(volume)
+ sound.set_muted(volume == 0)
+
+ def _get_speech_proxy(self):
+ if self._speech_proxy is None:
+ bus = dbus.SessionBus()
+ speech_obj = bus.get_object(SPEECH_DBUS_SERVICE, SPEECH_DBUS_PATH,
+ follow_name_owner_changes=True)
+ self._speech_proxy = dbus.Interface(speech_obj,
+ SPEECH_DBUS_INTERFACE)
+ return self._speech_proxy
+
+ def _on_speech_err(self, ex):
+ logging.error('An error occurred with the ESpeak service: %r', ex)
+
+ def _primary_selection_cb(self, clipboard, text, user_data):
+ logging.debug('KeyHandler._primary_selection_cb: %r', text)
+ if text:
+ self._get_speech_proxy().SayText(text, reply_handler=lambda: None, \
+ error_handler=self._on_speech_err)
+
+ def handle_say_text(self, event_time):
+ clipboard = gtk.clipboard_get(selection="PRIMARY")
+ clipboard.request_text(self._primary_selection_cb)
+
+ def handle_previous_window(self, event_time):
+ self._tabbing_handler.previous_activity(event_time)
+
+ def handle_next_window(self, event_time):
+ self._tabbing_handler.next_activity(event_time)
+
+ def handle_close_window(self, event_time):
+ active_activity = shell.get_model().get_active_activity()
+ if active_activity.is_journal():
+ return
+
+ active_activity.get_window().close()
+
+ def handle_zoom_mesh(self, event_time):
+ shell.get_model().set_zoom_level(ShellModel.ZOOM_MESH, event_time)
+
+ def handle_zoom_group(self, event_time):
+ shell.get_model().set_zoom_level(ShellModel.ZOOM_GROUP, event_time)
+
+ def handle_zoom_home(self, event_time):
+ shell.get_model().set_zoom_level(ShellModel.ZOOM_HOME, event_time)
+
+ def handle_zoom_activity(self, event_time):
+ shell.get_model().set_zoom_level(ShellModel.ZOOM_ACTIVITY, event_time)
+
+ def handle_volume_max(self, event_time):
+ self._change_volume(value=_VOLUME_MAX)
+
+ def handle_volume_min(self, event_time):
+ self._change_volume(value=0)
+
+ def handle_volume_mute(self, event_time):
+ if sound.get_muted() is True:
+ sound.set_muted(False)
+ else:
+ sound.set_muted(True)
+
+ def handle_volume_up(self, event_time):
+ self._change_volume(step=_VOLUME_STEP)
+
+ def handle_volume_down(self, event_time):
+ self._change_volume(step=-_VOLUME_STEP)
+
+ def handle_frame(self, event_time):
+ self._frame.notify_key_press()
+
+ def handle_quit_emulator(self, event_time):
+ session.get_session_manager().shutdown()
+
+ def handle_open_search(self, event_time):
+ journalactivity.get_journal().focus_search()
+
+ def _key_pressed_cb(self, grabber, keycode, state, event_time):
+ key = grabber.get_key(keycode, state)
+ logging.debug('_key_pressed_cb: %i %i %s', keycode, state, key)
+ if key is not None:
+ self._key_pressed = key
+ self._keycode_pressed = keycode
+ self._keystate_pressed = state
+
+ action = _actions_table[key]
+ if self._tabbing_handler.is_tabbing():
+ # Only accept window tabbing events, everything else
+ # cancels the tabbing operation.
+ if not action in ["next_window", "previous_window"]:
+ self._tabbing_handler.stop(event_time)
+ return True
+
+ if hasattr(action, 'handle_key_press'):
+ action.handle_key_press(key)
+ elif isinstance(action, basestring):
+ method = getattr(self, 'handle_' + action)
+ method(event_time)
+ else:
+ raise TypeError('Invalid action %r' % action)
+
+ return True
+ else:
+ # If this is not a registered key, then cancel tabbing.
+ if self._tabbing_handler.is_tabbing():
+ if not grabber.is_modifier(keycode):
+ self._tabbing_handler.stop(event_time)
+ return True
+
+ return False
+
+ def _key_released_cb(self, grabber, keycode, state, event_time):
+ logging.debug('_key_released_cb: %i %i' % (keycode, state))
+ if self._tabbing_handler.is_tabbing():
+ # We stop tabbing and switch to the new window as soon as the
+ # modifier key is raised again.
+ if grabber.is_modifier(keycode, mask=_TABBING_MODIFIER):
+ self._tabbing_handler.stop(event_time)
+
+ return True
+ return False
+
+_instance = None
+
+def setup(frame):
+ global _instance
+
+ if _instance:
+ del _instance
+
+ _instance = KeyHandler(frame)
+
diff --git a/shell/src/jarabe/view/launcher.py b/shell/src/jarabe/view/launcher.py
new file mode 100644
index 0000000..89251e5
--- /dev/null
+++ b/shell/src/jarabe/view/launcher.py
@@ -0,0 +1,217 @@
+# Copyright (C) 2008, Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import logging
+from gettext import gettext as _
+
+import gtk
+import hippo
+import gobject
+
+from sugar import wm
+from sugar.graphics import style
+from sugar.graphics import animator
+from sugar.graphics.xocolor import XoColor
+
+from jarabe.model import shell
+from jarabe.view.pulsingicon import CanvasPulsingIcon
+
+
+class LaunchWindow(gtk.Window):
+
+ def __init__(self, activity_id, icon_path, icon_color):
+ gobject.GObject.__init__(self)
+
+ self.props.type_hint = gtk.gdk.WINDOW_TYPE_HINT_NORMAL
+ self.props.decorated = False
+ self.modify_bg(gtk.STATE_NORMAL, style.COLOR_WHITE.get_gdk_color())
+
+ canvas = gtk.VBox()
+ canvas.show()
+ self.add(canvas)
+
+ bar_size = gtk.gdk.screen_height() / 5 * 2
+
+ header = gtk.VBox()
+ header.set_size_request(-1, bar_size)
+ header.show()
+ canvas.pack_start(header, expand=False)
+
+ self._activity_id = activity_id
+ self._box = LaunchBox(activity_id, icon_path, icon_color)
+ box = hippo.Canvas()
+ box.modify_bg(gtk.STATE_NORMAL, style.COLOR_WHITE.get_gdk_color())
+ box.set_root(self._box)
+ box.show()
+ canvas.pack_start(box)
+
+ footer = gtk.VBox(spacing=style.DEFAULT_SPACING)
+ footer.set_size_request(-1, bar_size)
+ footer.show()
+ canvas.pack_end(footer, expand=False)
+
+ self.error_text = gtk.Label()
+ self.error_text.props.use_markup = True
+ footer.pack_start(self.error_text, expand=False)
+
+ button_box = gtk.Alignment(xalign=0.5)
+ button_box.show()
+ footer.pack_start(button_box, expand=False)
+ self.cancel_button = gtk.Button(stock=gtk.STOCK_STOP)
+ button_box.add(self.cancel_button)
+
+ self.connect('realize', self.__realize_cb)
+
+ screen = gtk.gdk.screen_get_default()
+ screen.connect('size-changed', self.__size_changed_cb)
+
+ self._update_size()
+
+ def show(self):
+ self.present()
+ self._box.zoom_in()
+
+ def _update_size(self):
+ self.resize(gtk.gdk.screen_width(), gtk.gdk.screen_height())
+
+ def __realize_cb(self, widget):
+ wm.set_activity_id(widget.window, str(self._activity_id))
+ widget.window.property_change('_SUGAR_WINDOW_TYPE', 'STRING', 8,
+ gtk.gdk.PROP_MODE_REPLACE, 'launcher')
+
+ def __size_changed_cb(self, screen):
+ self._update_size()
+
+
+class LaunchBox(hippo.CanvasBox):
+
+ def __init__(self, activity_id, icon_path, icon_color):
+ gobject.GObject.__init__(self, orientation=hippo.ORIENTATION_VERTICAL)
+
+ self._activity_id = activity_id
+ self._activity_icon = CanvasPulsingIcon(
+ file_name=icon_path,
+ pulse_color=icon_color,
+ background_color=style.COLOR_WHITE.get_gdk_color())
+ self.append(self._activity_icon, hippo.PACK_EXPAND)
+
+ # FIXME support non-xo colors in CanvasPulsingIcon
+ self._activity_icon.props.base_color = \
+ XoColor('%s,%s' % (style.COLOR_BUTTON_GREY.get_svg(),
+ style.COLOR_TRANSPARENT.get_svg()))
+
+ self._animator = animator.Animator(1.0)
+
+ self._home = shell.get_model()
+ self._home.connect('active-activity-changed',
+ self.__active_activity_changed_cb)
+
+ self.connect('destroy', self.__destroy_cb)
+
+ def __destroy_cb(self, box):
+ self._activity_icon.props.pulsing = False
+ self._home.disconnect_by_func(self.__active_activity_changed_cb)
+
+ def zoom_in(self):
+ self._activity_icon.props.size = style.STANDARD_ICON_SIZE
+
+ self._animator.remove_all()
+ self._animator.add(_Animation(self._activity_icon,
+ style.STANDARD_ICON_SIZE,
+ style.XLARGE_ICON_SIZE))
+ self._animator.start()
+ self._activity_icon.props.pulsing = True
+
+ def __active_activity_changed_cb(self, model, activity):
+ if activity.get_activity_id() == self._activity_id:
+ self._activity_icon.props.paused = False
+ else:
+ self._activity_icon.props.paused = True
+
+
+class _Animation(animator.Animation):
+
+ def __init__(self, icon, start_size, end_size):
+ animator.Animation.__init__(self, 0.0, 1.0)
+
+ self._icon = icon
+ self.start_size = start_size
+ self.end_size = end_size
+
+ def next_frame(self, current):
+ d = (self.end_size - self.start_size) * current
+ self._icon.props.size = int(self.start_size + d)
+
+
+def setup():
+ model = shell.get_model()
+ model.connect('launch-started', __launch_started_cb)
+ model.connect('launch-failed', __launch_failed_cb)
+ model.connect('launch-completed', __launch_completed_cb)
+
+
+def add_launcher(activity_id, icon_path, icon_color):
+ model = shell.get_model()
+
+ if model.get_launcher(activity_id) is not None:
+ return
+
+ launch_window = LaunchWindow(activity_id, icon_path, icon_color)
+ launch_window.show()
+
+ model.register_launcher(activity_id, launch_window)
+
+
+def __launch_started_cb(home_model, home_activity):
+ add_launcher(home_activity.get_activity_id(),
+ home_activity.get_icon_path(), home_activity.get_icon_color())
+
+
+def __launch_failed_cb(home_model, home_activity):
+ activity_id = home_activity.get_activity_id()
+ launcher = shell.get_model().get_launcher(activity_id)
+
+ if launcher is None:
+ logging.error('Launcher for %s is missing', activity_id)
+ else:
+ launcher.error_text.props.label = _('<b>%s</b> failed to start.') % \
+ home_activity.get_activity_name()
+ launcher.error_text.show()
+
+ launcher.cancel_button.connect('clicked',
+ __cancel_button_clicked_cb, home_activity)
+ launcher.cancel_button.show()
+
+
+def __cancel_button_clicked_cb(button, home_activity):
+ _destroy_launcher(home_activity)
+
+
+def __launch_completed_cb(home_model, home_activity):
+ _destroy_launcher(home_activity)
+
+
+def _destroy_launcher(home_activity):
+ activity_id = home_activity.get_activity_id()
+
+ launcher = shell.get_model().get_launcher(activity_id)
+ if launcher is None:
+ if not home_activity.is_journal():
+ logging.error('Launcher was not registered for %s', activity_id)
+ return
+
+ shell.get_model().unregister_launcher(activity_id)
+ launcher.destroy()
diff --git a/shell/src/jarabe/view/palettes.py b/shell/src/jarabe/view/palettes.py
new file mode 100644
index 0000000..43612d4
--- /dev/null
+++ b/shell/src/jarabe/view/palettes.py
@@ -0,0 +1,250 @@
+# Copyright (C) 2008 One Laptop Per Child
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import statvfs
+from gettext import gettext as _
+import logging
+
+import gconf
+import gtk
+
+from sugar import env
+from sugar.graphics.palette import Palette
+from sugar.graphics.menuitem import MenuItem
+from sugar.graphics.icon import Icon
+from sugar.graphics import style
+from sugar.graphics.xocolor import XoColor
+from sugar.activity import activityfactory
+from sugar.activity.activityhandle import ActivityHandle
+
+from jarabe.model import shell
+from jarabe.view.viewsource import setup_view_source
+from jarabe.journal import misc
+
+class BasePalette(Palette):
+ def __init__(self, home_activity):
+ Palette.__init__(self)
+
+ self._notify_launch_hid = None
+
+ if home_activity.props.launch_status == shell.Activity.LAUNCHING:
+ self._notify_launch_hid = home_activity.connect( \
+ 'notify::launch-status', self.__notify_launch_status_cb)
+ self.set_primary_text(_('Starting...'))
+ elif home_activity.props.launch_status == shell.Activity.LAUNCH_FAILED:
+ self._on_failed_launch()
+ else:
+ self.setup_palette()
+
+ def setup_palette(self):
+ raise NotImplementedError
+
+ def _on_failed_launch(self):
+ self.set_primary_text(_('Activity failed to start'))
+
+ def __notify_launch_status_cb(self, home_activity, pspec):
+ home_activity.disconnect(self._notify_launch_hid)
+ self._notify_launch_hid = None
+ if home_activity.props.launch_status == shell.Activity.LAUNCH_FAILED:
+ self._on_failed_launch()
+ else:
+ self.setup_palette()
+
+
+class CurrentActivityPalette(BasePalette):
+ def __init__(self, home_activity):
+ self._home_activity = home_activity
+ BasePalette.__init__(self, home_activity)
+
+ def setup_palette(self):
+ self.props.primary_text = self._home_activity.get_activity_name()
+
+ if self._home_activity.get_title() != self.props.primary_text:
+ self.props.secondary_text = self._home_activity.get_title()
+
+ menu_item = MenuItem(_('Resume'), 'activity-start')
+ menu_item.connect('activate', self.__resume_activate_cb)
+ self.menu.append(menu_item)
+ menu_item.show()
+
+ # TODO: share-with, keep
+
+ menu_item = MenuItem(_('View Source'), 'view-source')
+ # TODO Make this accelerator translatable
+ menu_item.props.accelerator = '<Alt><Shift>v'
+ menu_item.connect('activate', self.__view_source__cb)
+ self.menu.append(menu_item)
+ menu_item.show()
+
+ separator = gtk.SeparatorMenuItem()
+ self.menu.append(separator)
+ separator.show()
+
+ menu_item = MenuItem(_('Stop'), 'activity-stop')
+ menu_item.connect('activate', self.__stop_activate_cb)
+ self.menu.append(menu_item)
+ menu_item.show()
+
+ def __resume_activate_cb(self, menu_item):
+ self._home_activity.get_window().activate(gtk.get_current_event_time())
+
+ def __view_source__cb(self, menu_item):
+ setup_view_source(self._home_activity)
+ shell_model = shell.get_model()
+ if self._home_activity is not shell_model.get_active_activity():
+ self._home_activity.get_window().activate( \
+ gtk.get_current_event_time())
+
+ def __active_window_changed_cb(self, screen, previous_window=None):
+ setup_view_source()
+ self._screen.disconnect(self._active_window_changed_sid)
+
+ def __stop_activate_cb(self, menu_item):
+ self._home_activity.get_window().close(1)
+
+
+class ActivityPalette(Palette):
+ __gtype_name__ = 'SugarActivityPalette'
+
+ def __init__(self, activity_info):
+ self._activity_info = activity_info
+
+ client = gconf.client_get_default()
+ color = XoColor(client.get_string("/desktop/sugar/user/color"))
+ activity_icon = Icon(file=activity_info.get_icon(),
+ xo_color=color,
+ icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR)
+
+ Palette.__init__(self, primary_text=activity_info.get_name(),
+ icon=activity_icon)
+
+ xo_color = XoColor('%s,%s' % (style.COLOR_WHITE.get_svg(),
+ style.COLOR_TRANSPARENT.get_svg()))
+ menu_item = MenuItem(text_label=_('Start new'),
+ file_name=activity_info.get_icon(),
+ xo_color=xo_color)
+ menu_item.connect('activate', self.__start_activate_cb)
+ self.menu.append(menu_item)
+ menu_item.show()
+
+ # TODO: start-with
+
+ def __start_activate_cb(self, menu_item):
+ self.popdown(immediate=True)
+ misc.launch(self._activity_info)
+
+class JournalPalette(BasePalette):
+ def __init__(self, home_activity):
+ self._home_activity = home_activity
+ self._progress_bar = None
+ self._free_space_label = None
+
+ BasePalette.__init__(self, home_activity)
+
+ def setup_palette(self):
+ self.set_primary_text(self._home_activity.get_title())
+
+ vbox = gtk.VBox()
+ self.set_content(vbox)
+ vbox.show()
+
+ self._progress_bar = gtk.ProgressBar()
+ vbox.add(self._progress_bar)
+ self._progress_bar.show()
+
+ self._free_space_label = gtk.Label()
+ self._free_space_label.set_alignment(0.5, 0.5)
+ vbox.add(self._free_space_label)
+ self._free_space_label.show()
+
+ self.connect('popup', self.__popup_cb)
+
+ menu_item = MenuItem(_('Show contents'))
+
+ icon = Icon(file=self._home_activity.get_icon_path(),
+ icon_size=gtk.ICON_SIZE_MENU,
+ xo_color=self._home_activity.get_icon_color())
+ menu_item.set_image(icon)
+ icon.show()
+
+ menu_item.connect('activate', self.__open_activate_cb)
+ self.menu.append(menu_item)
+ menu_item.show()
+
+ def __open_activate_cb(self, menu_item):
+ self._home_activity.get_window().activate(gtk.get_current_event_time())
+
+ def __popup_cb(self, palette):
+ stat = os.statvfs(env.get_profile_path())
+ free_space = stat[statvfs.F_BSIZE] * stat[statvfs.F_BAVAIL]
+ total_space = stat[statvfs.F_BSIZE] * stat[statvfs.F_BLOCKS]
+
+ fraction = (total_space - free_space) / float(total_space)
+ self._progress_bar.props.fraction = fraction
+ self._free_space_label.props.label = _('%(free_space)d MB Free') % \
+ {'free_space': free_space / (1024 * 1024)}
+
+class VolumePalette(Palette):
+ def __init__(self, mount):
+ Palette.__init__(self, label=mount.get_name())
+ self._mount = mount
+
+ self.props.secondary_text = mount.get_root().get_path()
+
+ vbox = gtk.VBox()
+ self.set_content(vbox)
+ vbox.show()
+
+ self._progress_bar = gtk.ProgressBar()
+ vbox.add(self._progress_bar)
+ self._progress_bar.show()
+
+ self._free_space_label = gtk.Label()
+ self._free_space_label.set_alignment(0.5, 0.5)
+ vbox.add(self._free_space_label)
+ self._free_space_label.show()
+
+ self.connect('popup', self.__popup_cb)
+
+ menu_item = MenuItem(_('Remove'))
+
+ icon = Icon(icon_name='media-eject', icon_size=gtk.ICON_SIZE_MENU)
+ menu_item.set_image(icon)
+ icon.show()
+
+ menu_item.connect('activate', self.__unmount_activate_cb)
+ self.menu.append(menu_item)
+ menu_item.show()
+
+ def __unmount_activate_cb(self, menu_item):
+ self._mount.unmount(self.__unmount_cb)
+
+ def __unmount_cb(self, mount, result):
+ logging.debug('__unmount_cb %r %r', mount, result)
+ mount.unmount_finish(result)
+
+ def __popup_cb(self, palette):
+ mount_point = self._mount.get_root().get_path()
+ stat = os.statvfs(mount_point)
+ free_space = stat[statvfs.F_BSIZE] * stat[statvfs.F_BAVAIL]
+ total_space = stat[statvfs.F_BSIZE] * stat[statvfs.F_BLOCKS]
+
+ fraction = (total_space - free_space) / float(total_space)
+ self._progress_bar.props.fraction = fraction
+ self._free_space_label.props.label = _('%(free_space)d MB Free') % \
+ {'free_space': free_space / (1024 * 1024)}
+
diff --git a/shell/src/jarabe/view/pulsingicon.py b/shell/src/jarabe/view/pulsingicon.py
new file mode 100644
index 0000000..43ec358
--- /dev/null
+++ b/shell/src/jarabe/view/pulsingicon.py
@@ -0,0 +1,229 @@
+# 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 math
+
+import gtk
+import gobject
+
+from sugar.graphics.icon import Icon, CanvasIcon
+
+_INTERVAL = 100
+_STEP = math.pi / 10 # must be a fraction of pi, for clean caching
+
+class Pulser(object):
+ def __init__(self, icon):
+ self._pulse_hid = None
+ self._icon = icon
+ self._level = 0
+ self._phase = 0
+
+ def start(self, restart=False):
+ if restart:
+ self._phase = 0
+ if self._pulse_hid is None:
+ self._pulse_hid = gobject.timeout_add(_INTERVAL, self.__pulse_cb)
+
+ def stop(self):
+ if self._pulse_hid is not None:
+ gobject.source_remove(self._pulse_hid)
+ self._pulse_hid = None
+ self._icon.xo_color = self._icon.get_base_color()
+
+ def update(self):
+ if self._icon.get_pulsing():
+ base_color = self._icon.get_base_color()
+ pulse_color = self._icon.get_pulse_color()
+
+ base_stroke = self._get_as_rgba(base_color.get_stroke_color())
+ pulse_stroke = self._get_as_rgba(pulse_color.get_stroke_color())
+ base_fill = self._get_as_rgba(base_color.get_fill_color())
+ pulse_fill = self._get_as_rgba(pulse_color.get_fill_color())
+
+ self._icon.set_stroke_color(
+ self._get_color_string(base_stroke, pulse_stroke))
+ self._icon.set_fill_color(
+ self._get_color_string(base_fill, pulse_fill))
+ else:
+ self._icon.xo_color = self._icon.base_color
+
+ def _get_as_rgba(self, html_color):
+ if html_color == 'none':
+ return 1.0, 1.0, 1.0
+ else:
+ color = gtk.gdk.color_parse(html_color)
+ return color.red / 65535.0, \
+ color.green / 65535.0, \
+ color.blue / 65535.0
+
+ def _get_color_string(self, orig_color, target_color):
+ r = orig_color[0] + self._level * (target_color[0] - orig_color[0])
+ g = orig_color[1] + self._level * (target_color[1] - orig_color[1])
+ b = orig_color[2] + self._level * (target_color[2] - orig_color[2])
+
+ return '#%02x%02x%02x' % (int(r * 255), int(g * 255), int(b * 255))
+
+ def __pulse_cb(self):
+ self._phase += _STEP
+ self._level = (math.sin(self._phase) + 1) / 2
+ self.update()
+
+ return True
+
+class PulsingIcon(Icon):
+ __gtype_name__ = 'SugarPulsingIcon'
+
+ def __init__(self, **kwargs):
+ self._pulser = Pulser(self)
+ self._base_color = None
+ self._pulse_color = None
+ self._paused = False
+ self._pulsing = False
+
+ Icon.__init__(self, **kwargs)
+
+ self._palette = None
+ self.connect('destroy', self.__destroy_cb)
+
+ def set_pulse_color(self, pulse_color):
+ self._pulse_color = pulse_color
+ self._pulser.update()
+
+ def get_pulse_color(self):
+ return self._pulse_color
+
+ pulse_color = gobject.property(
+ type=object, getter=get_pulse_color, setter=set_pulse_color)
+
+ def set_base_color(self, base_color):
+ self._base_color = base_color
+ self._pulser.update()
+
+ def get_base_color(self):
+ return self._base_color
+
+ base_color = gobject.property(
+ type=object, getter=get_base_color, setter=set_base_color)
+
+ def set_paused(self, paused):
+ self._paused = paused
+
+ if self._paused:
+ self._pulser.stop()
+ else:
+ self._pulser.start(restart=False)
+
+ def get_paused(self):
+ return self._paused
+
+ paused = gobject.property(
+ type=bool, default=False, getter=get_paused, setter=set_paused)
+
+ def set_pulsing(self, pulsing):
+ self._pulsing = pulsing
+
+ if self._pulsing:
+ self._pulser.start(restart=True)
+ else:
+ self._pulser.stop()
+
+ def get_pulsing(self):
+ return self._pulsing
+
+ pulsing = gobject.property(
+ type=bool, default=False, getter=get_pulsing, setter=set_pulsing)
+
+ def _get_palette(self):
+ return self._palette
+
+ def _set_palette(self, palette):
+ if self._palette is not None:
+ self._palette.props.invoker = None
+ self._palette = palette
+
+ palette = property(_get_palette, _set_palette)
+
+ def __destroy_cb(self, icon):
+ self._pulser.stop()
+ if self._palette is not None:
+ self._palette.destroy()
+
+class CanvasPulsingIcon(CanvasIcon):
+ __gtype_name__ = 'SugarCanvasPulsingIcon'
+
+ def __init__(self, **kwargs):
+ self._pulser = Pulser(self)
+ self._base_color = None
+ self._pulse_color = None
+ self._paused = False
+ self._pulsing = False
+
+ CanvasIcon.__init__(self, **kwargs)
+
+ self.connect('destroy', self.__destroy_cb)
+
+ def __destroy_cb(self, box):
+ self._pulser.stop()
+
+ def set_pulse_color(self, pulse_color):
+ self._pulse_color = pulse_color
+ self._pulser.update()
+
+ def get_pulse_color(self):
+ return self._pulse_color
+
+ pulse_color = gobject.property(
+ type=object, getter=get_pulse_color, setter=set_pulse_color)
+
+ def set_base_color(self, base_color):
+ self._base_color = base_color
+ self._pulser.update()
+
+ def get_base_color(self):
+ return self._base_color
+
+ base_color = gobject.property(
+ type=object, getter=get_base_color, setter=set_base_color)
+
+ def set_paused(self, paused):
+ self._paused = paused
+
+ if self._paused:
+ self._pulser.stop()
+ elif self._pulsing:
+ self._pulser.start(restart=False)
+
+ def get_paused(self):
+ return self._paused
+
+ paused = gobject.property(
+ type=bool, default=False, getter=get_paused, setter=set_paused)
+
+ def set_pulsing(self, pulsing):
+ self._pulsing = pulsing
+ if self._paused:
+ return
+
+ if self._pulsing:
+ self._pulser.start(restart=True)
+ else:
+ self._pulser.stop()
+
+ def get_pulsing(self):
+ return self._pulsing
+
+ pulsing = gobject.property(
+ type=bool, default=False, getter=get_pulsing, setter=set_pulsing)
diff --git a/shell/src/jarabe/view/service.py b/shell/src/jarabe/view/service.py
new file mode 100644
index 0000000..7af778a
--- /dev/null
+++ b/shell/src/jarabe/view/service.py
@@ -0,0 +1,89 @@
+# Copyright (C) 2006-2007 Red Hat, Inc.
+# Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# 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
+
+"""D-bus service providing access to the shell's functionality"""
+
+import dbus
+import gtk
+
+from jarabe.model import shell
+from jarabe.model import bundleregistry
+
+_DBUS_SERVICE = "org.laptop.Shell"
+_DBUS_SHELL_IFACE = "org.laptop.Shell"
+_DBUS_PATH = "/org/laptop/Shell"
+
+class UIService(dbus.service.Object):
+ """Provides d-bus service to script the shell's operations
+
+ Uses a shell_model object to observe events such as changes to:
+
+ * nickname
+ * colour
+ * icon
+ * currently active activity
+
+ and pass the event off to the methods in the dbus signature.
+
+ Key method here at the moment is add_bundle, which is used to
+ do a run-time registration of a bundle using it's application path.
+
+ XXX At the moment the d-bus service methods do not appear to do
+ anything other than add_bundle
+ """
+
+ def __init__(self):
+ bus = dbus.SessionBus()
+ bus_name = dbus.service.BusName(_DBUS_SERVICE, bus=bus)
+ dbus.service.Object.__init__(self, bus_name, _DBUS_PATH)
+
+ self._shell_model = shell.get_model()
+
+ @dbus.service.method(_DBUS_SHELL_IFACE,
+ in_signature="s", out_signature="s")
+ def GetBundlePath(self, bundle_id):
+ bundle = bundleregistry.get_registry().get_bundle(bundle_id)
+ if bundle:
+ return bundle.get_path()
+ else:
+ return ''
+
+ @dbus.service.method(_DBUS_SHELL_IFACE,
+ in_signature="s", out_signature="b")
+ def ActivateActivity(self, activity_id):
+ """Switch to the window related to this activity_id and return a boolean
+ indicating if there is a real (ie. not a launcher window) activity
+ already open.
+ """
+ activity = self._shell_model.get_activity_by_id(activity_id)
+
+ if activity is not None and activity.get_window() is not None:
+ activity.get_window().activate(gtk.get_current_event_time())
+ return self._shell_model.get_launcher(activity_id) is None
+
+ return False
+
+ @dbus.service.method(_DBUS_SHELL_IFACE,
+ in_signature="ss", out_signature="")
+ def NotifyLaunch(self, bundle_id, activity_id):
+ shell.get_model().notify_launch(activity_id, bundle_id)
+
+ @dbus.service.method(_DBUS_SHELL_IFACE,
+ in_signature="s", out_signature="")
+ def NotifyLaunchFailure(self, activity_id):
+ shell.get_model().notify_launch_failed(activity_id)
+
diff --git a/shell/src/jarabe/view/tabbinghandler.py b/shell/src/jarabe/view/tabbinghandler.py
new file mode 100644
index 0000000..f52bda3
--- /dev/null
+++ b/shell/src/jarabe/view/tabbinghandler.py
@@ -0,0 +1,148 @@
+# Copyright (C) 2008, Benjamin Berg <benjamin@sipsolutions.net>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import logging
+
+import gobject
+import gtk
+
+from jarabe.model import shell
+
+_RAISE_DELAY = 250
+
+class TabbingHandler(object):
+ def __init__(self, frame, modifier):
+ self._frame = frame
+ self._tabbing = False
+ self._modifier = modifier
+ self._timeout = None
+
+ def _start_tabbing(self):
+ if not self._tabbing:
+ logging.debug('Grabing the input.')
+
+ screen = gtk.gdk.screen_get_default()
+ window = screen.get_root_window()
+ keyboard_grab_result = gtk.gdk.keyboard_grab(window)
+ pointer_grab_result = gtk.gdk.pointer_grab(window)
+
+ self._tabbing = (keyboard_grab_result == gtk.gdk.GRAB_SUCCESS and
+ pointer_grab_result == gtk.gdk.GRAB_SUCCESS)
+
+ # Now test that the modifier is still active to prevent race
+ # conditions. We also test if one of the grabs failed.
+ mask = window.get_pointer()[2]
+ if not self._tabbing or not (mask & self._modifier):
+ logging.debug('Releasing grabs again.')
+
+ # ungrab keyboard/pointer if the grab was successfull.
+ if keyboard_grab_result == gtk.gdk.GRAB_SUCCESS:
+ gtk.gdk.keyboard_ungrab()
+ if pointer_grab_result == gtk.gdk.GRAB_SUCCESS:
+ gtk.gdk.pointer_ungrab()
+
+ self._tabbing = False
+ else:
+ self._frame.show(self._frame.MODE_NON_INTERACTIVE)
+
+ def __timeout_cb(self, event_time):
+ self._activate_current(event_time)
+ self._timeout = None
+ return False
+
+ def _start_timeout(self, event_time):
+ self._cancel_timeout()
+ self._timeout = gobject.timeout_add(_RAISE_DELAY,
+ lambda: self.__timeout_cb(event_time))
+
+ def _cancel_timeout(self):
+ if self._timeout:
+ gobject.source_remove(self._timeout)
+ self._timeout = None
+
+ def _activate_current(self, event_time):
+ home_model = shell.get_model()
+ activity = home_model.get_tabbing_activity()
+ if activity and activity.get_window():
+ activity.get_window().activate(event_time)
+
+ def next_activity(self, event_time):
+ if not self._tabbing:
+ first_switch = True
+ self._start_tabbing()
+ else:
+ first_switch = False
+
+ if self._tabbing:
+ shell_model = shell.get_model()
+ zoom_level = shell_model.zoom_level
+ zoom_activity = (zoom_level == shell.ShellModel.ZOOM_ACTIVITY)
+
+ if not zoom_activity and first_switch:
+ activity = shell_model.get_active_activity()
+ else:
+ activity = shell_model.get_tabbing_activity()
+ activity = shell_model.get_next_activity(current=activity)
+
+ shell_model.set_tabbing_activity(activity)
+ self._start_timeout(event_time)
+ else:
+ self._activate_next_activity(event_time)
+
+ def previous_activity(self, event_time):
+ if not self._tabbing:
+ first_switch = True
+ self._start_tabbing()
+ else:
+ first_switch = False
+
+ if self._tabbing:
+ shell_model = shell.get_model()
+ zoom_level = shell_model.zoom_level
+ zoom_activity = (zoom_level == shell.ShellModel.ZOOM_ACTIVITY)
+
+ if not zoom_activity and first_switch:
+ activity = shell_model.get_active_activity()
+ else:
+ activity = shell_model.get_tabbing_activity()
+ activity = shell_model.get_previous_activity(current=activity)
+
+ shell_model.set_tabbing_activity(activity)
+ self._start_timeout(event_time)
+ else:
+ self._activate_next_activity(event_time)
+
+ def _activate_next_activity(self, event_time):
+ next_activity = shell.get_model().get_next_activity()
+ if next_activity:
+ next_activity.get_window().activate(event_time)
+
+ def stop(self, event_time):
+ gtk.gdk.keyboard_ungrab()
+ gtk.gdk.pointer_ungrab()
+ self._tabbing = False
+
+ self._frame.hide()
+
+ self._cancel_timeout()
+ self._activate_current(event_time)
+
+ home_model = shell.get_model()
+ home_model.set_tabbing_activity(None)
+
+ def is_tabbing(self):
+ return self._tabbing
+
diff --git a/shell/src/jarabe/view/viewsource.py b/shell/src/jarabe/view/viewsource.py
new file mode 100644
index 0000000..290df18
--- /dev/null
+++ b/shell/src/jarabe/view/viewsource.py
@@ -0,0 +1,464 @@
+# Copyright (C) 2008 One Laptop Per Child
+# Copyright (C) 2009 Tomeu Vizoso, Simon Schampijer
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import logging
+import traceback
+from gettext import gettext as _
+
+import gobject
+import pango
+import gtk
+import gtksourceview2
+import dbus
+import gconf
+
+from sugar.graphics import style
+from sugar.graphics.icon import Icon
+from sugar.graphics.xocolor import XoColor
+from sugar.graphics.menuitem import MenuItem
+from sugar.graphics.toolbutton import ToolButton
+from sugar.graphics.radiotoolbutton import RadioToolButton
+from sugar.bundle.activitybundle import ActivityBundle
+from sugar.datastore import datastore
+from sugar import mime
+
+_SOURCE_FONT = pango.FontDescription('Monospace %d' % style.FONT_SIZE)
+
+_logger = logging.getLogger('ViewSource')
+map_activity_to_window = {}
+
+def setup_view_source(activity):
+ service = activity.get_service()
+ if service is not None:
+ try:
+ service.HandleViewSource()
+ return
+ except dbus.DBusException, e:
+ expected_exceptions = ['org.freedesktop.DBus.Error.UnknownMethod',
+ 'org.freedesktop.DBus.Python.NotImplementedError']
+ if e.get_dbus_name() not in expected_exceptions:
+ logging.error(traceback.format_exc())
+ except Exception:
+ logging.error(traceback.format_exc())
+
+ window_xid = activity.get_xid()
+ if window_xid is None:
+ _logger.error('Activity without a window xid')
+ return
+
+ bundle_path = activity.get_bundle_path()
+
+ if window_xid in map_activity_to_window:
+ _logger.debug('Viewsource window already open for %s %s', window_xid,
+ bundle_path)
+ return
+
+ document_path = None
+ if service is not None:
+ try:
+ document_path = service.GetDocumentPath()
+ except dbus.DBusException, e:
+ expected_exceptions = ['org.freedesktop.DBus.Error.UnknownMethod',
+ 'org.freedesktop.DBus.Python.NotImplementedError']
+ if e.get_dbus_name() not in expected_exceptions:
+ logging.error(traceback.format_exc())
+ except Exception:
+ logging.error(traceback.format_exc())
+
+ if bundle_path is None and document_path is None:
+ _logger.debug('Activity without bundle_path nor document_path')
+ return
+
+ view_source = ViewSource(window_xid, bundle_path, document_path,
+ activity.get_title())
+ map_activity_to_window[window_xid] = view_source
+ view_source.show()
+
+class ViewSource(gtk.Window):
+ __gtype_name__ = 'SugarViewSource'
+
+ def __init__(self, window_xid, bundle_path, document_path, title):
+ gtk.Window.__init__(self)
+
+ logging.debug('ViewSource paths: %r %r', bundle_path, document_path)
+
+ self.set_decorated(False)
+ self.set_position(gtk.WIN_POS_CENTER_ALWAYS)
+ self.set_border_width(style.LINE_WIDTH)
+
+ width = gtk.gdk.screen_width() - style.GRID_CELL_SIZE * 2
+ height = gtk.gdk.screen_height() - style.GRID_CELL_SIZE * 2
+ self.set_size_request(width, height)
+
+ self._parent_window_xid = window_xid
+
+ self.connect('realize', self.__realize_cb)
+ self.connect('destroy', self.__destroy_cb, document_path)
+ self.connect('key-press-event', self.__key_press_event_cb)
+
+ vbox = gtk.VBox()
+ self.add(vbox)
+ vbox.show()
+
+ toolbar = Toolbar(title, bundle_path, document_path)
+ vbox.pack_start(toolbar, expand=False)
+ toolbar.connect('stop-clicked', self.__stop_clicked_cb)
+ toolbar.connect('source-selected', self.__source_selected_cb)
+ toolbar.show()
+
+ pane = gtk.HPaned()
+ vbox.pack_start(pane)
+ pane.show()
+
+ self._selected_file = None
+ file_name = ''
+
+ activity_bundle = ActivityBundle(bundle_path)
+ command = activity_bundle.get_command()
+ if len(command.split(' ')) > 1:
+ name = command.split(' ')[1].split('.')[0]
+ file_name = name + '.py'
+ path = os.path.join(activity_bundle.get_path(), file_name)
+ self._selected_file = path
+
+ self._file_viewer = FileViewer(bundle_path, file_name)
+ self._file_viewer.connect('file-selected', self.__file_selected_cb)
+ pane.add1(self._file_viewer)
+ self._file_viewer.show()
+
+ self._source_display = SourceDisplay()
+ pane.add2(self._source_display)
+ self._source_display.show()
+ self._source_display.file_path = self._selected_file
+
+ if document_path is not None:
+ self._select_source(document_path)
+
+ def _calculate_char_width(self, char_count):
+ widget = gtk.Label('')
+ context = widget.get_pango_context()
+ pango_font = context.load_font(_SOURCE_FONT)
+ metrics = pango_font.get_metrics()
+ return pango.PIXELS(metrics.get_approximate_char_width()) * char_count
+
+ def __realize_cb(self, widget):
+ self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
+ self.window.set_accept_focus(True)
+
+ parent = gtk.gdk.window_foreign_new(self._parent_window_xid)
+ self.window.set_transient_for(parent)
+
+ def __stop_clicked_cb(self, widget):
+ self.destroy()
+
+ def __source_selected_cb(self, widget, path):
+ self._select_source(path)
+
+ def _select_source(self, path):
+ if os.path.isfile(path):
+ self._source_display.file_path = path
+ self._file_viewer.hide()
+ else:
+ self._file_viewer.set_path(path)
+ self._source_display.file_path = self._selected_file
+ self._file_viewer.show()
+
+ def __destroy_cb(self, window, document_path):
+ del map_activity_to_window[self._parent_window_xid]
+ if document_path is not None and os.path.exists(document_path):
+ os.unlink(document_path)
+
+ def __key_press_event_cb(self, window, event):
+ keyname = gtk.gdk.keyval_name(event.keyval)
+ if keyname == 'Escape':
+ self.destroy()
+
+ def __file_selected_cb(self, file_viewer, file_path):
+ if file_path is not None and os.path.isfile(file_path):
+ self._source_display.file_path = file_path
+ self._selected_file = file_path
+ else:
+ self._source_display.file_path = None
+
+class DocumentButton(RadioToolButton):
+ __gtype_name__ = 'SugarDocumentButton'
+
+ def __init__(self, file_name, document_path, title):
+ RadioToolButton.__init__(self)
+
+ self._document_path = document_path
+ self._title = title
+ self._jobject = None
+
+ self.props.tooltip = _('Instance Source')
+
+ client = gconf.client_get_default()
+ self._color = client.get_string('/desktop/sugar/user/color')
+ icon = Icon(file=file_name,
+ icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR,
+ xo_color=XoColor(self._color))
+ self.set_icon_widget(icon)
+ icon.show()
+
+ menu_item = MenuItem(_('Keep'))
+ icon = Icon(icon_name='document-save', icon_size=gtk.ICON_SIZE_MENU,
+ xo_color=XoColor(self._color))
+ menu_item.set_image(icon)
+
+ menu_item.connect('activate', self.__keep_in_journal_cb)
+ self.props.palette.menu.append(menu_item)
+ menu_item.show()
+
+ def __keep_in_journal_cb(self, menu_item):
+ mime_type = mime.get_from_file_name(self._document_path)
+ if mime_type == 'application/octet-stream':
+ mime_type = mime.get_for_file(self._document_path)
+
+ self._jobject = datastore.create()
+ title = _('Source') + ': ' + self._title
+ self._jobject.metadata['title'] = title
+ self._jobject.metadata['keep'] = '0'
+ self._jobject.metadata['buddies'] = ''
+ self._jobject.metadata['preview'] = ''
+ self._jobject.metadata['icon-color'] = self._color
+ self._jobject.metadata['mime_type'] = mime_type
+ self._jobject.metadata['source'] = '1'
+ self._jobject.file_path = self._document_path
+ datastore.write(self._jobject, transfer_ownership=True,
+ reply_handler=self.__internal_save_cb,
+ error_handler=self.__internal_save_error_cb)
+
+ def __internal_save_cb(self):
+ logging.debug("Saved Source object to datastore.")
+ self._jobject.destroy()
+
+ def __internal_save_error_cb(self, err):
+ logging.debug('Error saving Source object to datastore: %s', err)
+ self._jobject.destroy()
+
+class Toolbar(gtk.Toolbar):
+ __gtype_name__ = 'SugarViewSourceToolbar'
+
+ __gsignals__ = {
+ 'stop-clicked': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([])),
+ 'source-selected': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([str])),
+ }
+
+ def __init__(self, title, bundle_path, document_path):
+ gtk.Toolbar.__init__(self)
+
+ document_button = None
+
+ self._add_separator()
+
+ activity_bundle = ActivityBundle(bundle_path)
+ file_name = activity_bundle.get_icon()
+
+ if document_path is not None and os.path.exists(document_path):
+ document_button = DocumentButton(file_name, document_path, title)
+ document_button.connect('toggled', self.__button_toggled_cb,
+ document_path)
+ self.insert(document_button, -1)
+ document_button.show()
+ self._add_separator()
+
+ if bundle_path is not None and os.path.exists(bundle_path):
+ activity_button = RadioToolButton()
+ icon = Icon(file=file_name,
+ icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR,
+ fill_color=style.COLOR_TRANSPARENT.get_svg(),
+ stroke_color=style.COLOR_WHITE.get_svg())
+ activity_button.set_icon_widget(icon)
+ icon.show()
+ if document_button is not None:
+ activity_button.props.group = document_button
+ activity_button.props.tooltip = _('Activity Bundle Source')
+ activity_button.connect('toggled', self.__button_toggled_cb,
+ bundle_path)
+ self.insert(activity_button, -1)
+ activity_button.show()
+ self._add_separator()
+
+ text = _('View source: %r') % title
+ label = gtk.Label()
+ label.set_markup('<b>%s</b>' % text)
+ label.set_alignment(0, 0.5)
+ self._add_widget(label)
+
+ self._add_separator(True)
+
+ stop = ToolButton(icon_name='dialog-cancel')
+ stop.set_tooltip(_('Close'))
+ stop.connect('clicked', self.__stop_clicked_cb)
+ self.insert(stop, -1)
+ stop.show()
+
+ 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 __stop_clicked_cb(self, button):
+ self.emit('stop-clicked')
+
+ def __button_toggled_cb(self, button, path):
+ if button.props.active:
+ self.emit('source-selected', path)
+
+class FileViewer(gtk.ScrolledWindow):
+ __gtype_name__ = 'SugarFileViewer'
+
+ __gsignals__ = {
+ 'file-selected': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ([str])),
+ }
+
+ def __init__(self, path, initial_filename):
+ gtk.ScrolledWindow.__init__(self)
+
+ self.props.hscrollbar_policy = gtk.POLICY_AUTOMATIC
+ self.props.vscrollbar_policy = gtk.POLICY_AUTOMATIC
+ self.set_size_request(style.GRID_CELL_SIZE * 3, -1)
+
+ self._path = None
+ self._initial_filename = initial_filename
+
+ self._tree_view = gtk.TreeView()
+ self.add(self._tree_view)
+ self._tree_view.show()
+
+ self._tree_view.props.headers_visible = False
+ selection = self._tree_view.get_selection()
+ selection.connect('changed', self.__selection_changed_cb)
+
+ cell = gtk.CellRendererText()
+ column = gtk.TreeViewColumn()
+ column.pack_start(cell, True)
+ column.add_attribute(cell, 'text', 0)
+ self._tree_view.append_column(column)
+ self._tree_view.set_search_column(0)
+
+ self.set_path(path)
+
+ def set_path(self, path):
+ self.emit('file-selected', None)
+ if self._path == path:
+ return
+ self._path = path
+ self._tree_view.set_model(gtk.TreeStore(str, str))
+ self._add_dir_to_model(path)
+
+ def _add_dir_to_model(self, dir_path, parent=None):
+ model = self._tree_view.get_model()
+ for f in os.listdir(dir_path):
+ if not f.endswith('.pyc'):
+ full_path = os.path.join(dir_path, f)
+ if os.path.isdir(full_path):
+ new_iter = model.append(parent, [f, full_path])
+ self._add_dir_to_model(full_path, new_iter)
+ else:
+ current_iter = model.append(parent, [f, full_path])
+ if f == self._initial_filename:
+ selection = self._tree_view.get_selection()
+ selection.select_iter(current_iter)
+
+ def __selection_changed_cb(self, selection):
+ model, tree_iter = selection.get_selected()
+ if tree_iter is None:
+ file_path = None
+ else:
+ file_path = model.get_value(tree_iter, 1)
+ self.emit('file-selected', file_path)
+
+class SourceDisplay(gtk.ScrolledWindow):
+ __gtype_name__ = 'SugarSourceDisplay'
+
+ def __init__(self):
+ gtk.ScrolledWindow.__init__(self)
+
+ self.props.hscrollbar_policy = gtk.POLICY_AUTOMATIC
+ self.props.vscrollbar_policy = gtk.POLICY_AUTOMATIC
+
+ self._buffer = gtksourceview2.Buffer()
+ self._buffer.set_highlight_syntax(True)
+
+ self._source_view = gtksourceview2.View(self._buffer)
+ self._source_view.set_editable(False)
+ self._source_view.set_cursor_visible(True)
+ self._source_view.set_show_line_numbers(True)
+ self._source_view.set_show_right_margin(True)
+ self._source_view.set_right_margin_position(80)
+ #self._source_view.set_highlight_current_line(True) #FIXME: Ugly color
+ self._source_view.modify_font(_SOURCE_FONT)
+ self.add(self._source_view)
+ self._source_view.show()
+
+ self._file_path = None
+
+ def _set_file_path(self, file_path):
+ if file_path == self._file_path:
+ return
+ self._file_path = file_path
+
+ if self._file_path is None:
+ self._buffer.set_text('')
+ return
+
+ mime_type = mime.get_for_file(self._file_path)
+ logging.debug('Detected mime type: %r', mime_type)
+
+ language_manager = gtksourceview2.language_manager_get_default()
+ detected_language = None
+ for language_id in language_manager.get_language_ids():
+ language = language_manager.get_language(language_id)
+ if mime_type in language.get_mime_types():
+ detected_language = language
+ break
+
+ if detected_language is not None:
+ logging.debug('Detected language: %r',
+ detected_language.get_name())
+
+ self._buffer.set_language(detected_language)
+ self._buffer.set_text(open(self._file_path, 'r').read())
+
+ def _get_file_path(self):
+ return self._file_path
+
+ file_path = property(_get_file_path, _set_file_path)
+
diff --git a/toolkit/.gitignore b/toolkit/.gitignore
new file mode 100644
index 0000000..fe4cc56
--- /dev/null
+++ b/toolkit/.gitignore
@@ -0,0 +1,24 @@
+*.la
+*.lo
+*.pyc
+*~
+.deps
+.libs
+
+py-compile
+Makefile
+Makefile.in
+aclocal.m4
+autom4te.cache
+config.guess
+config.log
+config.status
+config.sub
+configure
+depcomp
+install-sh
+intltool-*
+libtool
+ltmain.sh
+missing
+compile
diff --git a/toolkit/AUTHORS b/toolkit/AUTHORS
new file mode 100644
index 0000000..8cd5dac
--- /dev/null
+++ b/toolkit/AUTHORS
@@ -0,0 +1,14 @@
+Marco Pesenti Gritti <mpg@redhat.com>
+Dan Williams <dcbw@redhat.com>
+Tomeu Vizoso <tomeu@tomeuvizoso.net>
+Dan Winship <dwinship@redhat.com>
+Benjamin Berg <benjamin@sipsolutions.net>
+Eduardo Silva <edsiper@gmail.com>
+Simon Schampijer <simon@schampijer.de>
+Bert Freudenberg <bert@freudenbergs.de>
+Guillaume Desmottes <guillaume.desmottes@collabora.co.uk>
+Dafydd Harries <daf@rhydd.org>
+John (J5) Palmieri <johnp@redhat.com>
+Morgan Collett <morgan.collett@gmail.com>
+Simon McVittie <simon.mcvittie@collabora.co.uk>
+Owen Williams <owen@ywwg.com>
diff --git a/toolkit/COPYING b/toolkit/COPYING
new file mode 100644
index 0000000..5ab7695
--- /dev/null
+++ b/toolkit/COPYING
@@ -0,0 +1,504 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library. It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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.1 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
+
+
diff --git a/toolkit/Makefile.am b/toolkit/Makefile.am
new file mode 100644
index 0000000..b62b8cc
--- /dev/null
+++ b/toolkit/Makefile.am
@@ -0,0 +1,13 @@
+ACLOCAL_AMFLAGS = -I m4
+
+DISTCLEANFILES = \
+ intltool-extract \
+ intltool-merge \
+ intltool-update
+
+EXTRA_DIST = \
+ intltool-merge.in \
+ intltool-update.in \
+ intltool-extract.in
+
+SUBDIRS = src po
diff --git a/toolkit/README b/toolkit/README
new file mode 100644
index 0000000..0c5cbce
--- /dev/null
+++ b/toolkit/README
@@ -0,0 +1,3 @@
+Sugar is the core of the OLPC Human Interface. The toolkit provides
+a set of widgets to build HIG compliant applications and interfaces
+to interact with system services like presence and the datastore.
diff --git a/toolkit/autogen.sh b/toolkit/autogen.sh
new file mode 100755
index 0000000..f25b0a3
--- /dev/null
+++ b/toolkit/autogen.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+export ACLOCAL="aclocal -I m4"
+
+intltoolize
+autoreconf -i
+./configure --enable-maintainer-mode "$@"
diff --git a/toolkit/configure.ac b/toolkit/configure.ac
new file mode 100644
index 0000000..f66d564
--- /dev/null
+++ b/toolkit/configure.ac
@@ -0,0 +1,48 @@
+AC_INIT([sugar-toolkit],[0.89.3],[],[sugar-toolkit])
+
+AC_PREREQ([2.59])
+
+AC_CONFIG_MACRO_DIR([m4])
+AC_CONFIG_SRCDIR([configure.ac])
+
+AM_INIT_AUTOMAKE([1.9 foreign dist-bzip2 no-dist-gzip])
+
+AM_MAINTAINER_MODE
+AC_DISABLE_STATIC
+AC_PROG_LIBTOOL
+
+GNOME_COMPILE_WARNINGS(maximum)
+
+AC_PATH_PROG([GLIB_GENMARSHAL], [glib-genmarshal])
+
+AM_PATH_PYTHON
+AM_CHECK_PYTHON_HEADERS(,[AC_MSG_ERROR(could not find Python headers)])
+
+AC_PATH_PROG(PYGTK_CODEGEN, pygtk-codegen-2.0, no)
+
+PKG_CHECK_MODULES(EXT, pygtk-2.0 gtk+-2.0 sm ice alsa)
+
+PYGTK_DEFSDIR=`$PKG_CONFIG --variable=defsdir pygtk-2.0`
+AC_SUBST(PYGTK_DEFSDIR)
+
+# Setup GETTEXT
+#
+ALL_LINGUAS="af am ar ay bg bi bn_IN bn ca cpp cs da de dz el en es fa_AF fa ff fil fr gu ha he hi ht hu id ig is it ja km ko kos mg mi mk ml mn mr ms mvo nb ne nl pa pap pis pl ps pt_BR pt qu ro ru rw sd si sk sl sq sv sw ta te th tpi tr tvl tzo ug ur vi wa yo zh_CN zh_TW"
+
+GETTEXT_PACKAGE=sugar-toolkit
+AC_PROG_INTLTOOL([0.33])
+AC_SUBST(GETTEXT_PACKAGE)
+AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE, "$GETTEXT_PACKAGE", [Gettext package])
+AM_GLIB_GNU_GETTEXT
+
+AC_OUTPUT([
+Makefile
+src/Makefile
+src/sugar/Makefile
+src/sugar/activity/Makefile
+src/sugar/bundle/Makefile
+src/sugar/graphics/Makefile
+src/sugar/presence/Makefile
+src/sugar/datastore/Makefile
+po/Makefile.in
+])
diff --git a/toolkit/examples/radiopalette.py b/toolkit/examples/radiopalette.py
new file mode 100644
index 0000000..85b43ce
--- /dev/null
+++ b/toolkit/examples/radiopalette.py
@@ -0,0 +1,74 @@
+import gtk
+
+from sugar.graphics.radiopalette import RadioPalette, RadioMenuButton, \
+ RadioToolsButton
+from sugar.graphics.radiotoolbutton import RadioToolButton
+from sugar.graphics.toolbutton import ToolButton
+from sugar.graphics import style
+
+window = gtk.Window()
+
+box = gtk.VBox()
+window.add(box)
+
+toolbar = gtk.Toolbar()
+box.pack_start(toolbar, False)
+
+text_view = gtk.TextView()
+box.pack_start(text_view)
+
+def echo(button, label):
+ if not button.props.active:
+ return
+ text_view.props.buffer.props.text += "\n" + label
+
+# RadioMenuButton
+
+palette = RadioPalette()
+
+group = RadioToolButton(
+ icon_name='document-open')
+group.connect('clicked', lambda button: echo(button, 'document-open'))
+palette.append(group, 'menu.document-open')
+
+button = RadioToolButton(
+ icon_name='document-save',
+ group=group)
+button.connect('clicked', lambda button: echo(button, 'document-save'))
+palette.append(button, 'menu.document-save')
+
+button = RadioToolButton(
+ icon_name='document-send',
+ group=group)
+button.connect('clicked', lambda button: echo(button, 'document-send'))
+palette.append(button, 'menu.document-send')
+
+button = RadioMenuButton(palette=palette)
+toolbar.insert(button, -1)
+
+# RadioToolsButton
+
+palette = RadioPalette()
+
+group = RadioToolButton(
+ icon_name='document-open')
+group.connect('clicked', lambda button: echo(button, 'document-open'))
+palette.append(group, 'menu.document-open')
+
+button = RadioToolButton(
+ icon_name='document-save',
+ group=group)
+button.connect('clicked', lambda button: echo(button, 'document-save'))
+palette.append(button, 'menu.document-save')
+
+button = RadioToolButton(
+ icon_name='document-send',
+ group=group)
+button.connect('clicked', lambda button: echo(button, 'document-send'))
+palette.append(button, 'menu.document-send')
+
+button = RadioToolsButton(palette=palette)
+toolbar.insert(button, -1)
+
+window.show_all()
+gtk.main()
diff --git a/toolkit/examples/toolbar.py b/toolkit/examples/toolbar.py
new file mode 100644
index 0000000..2faea1f
--- /dev/null
+++ b/toolkit/examples/toolbar.py
@@ -0,0 +1,50 @@
+import gtk
+
+from sugar.graphics.toolbutton import ToolButton
+from sugar.graphics.toolbarbox import ToolbarBox, ToolbarButton
+from sugar.graphics import style
+
+window = gtk.Window()
+
+box = gtk.VBox()
+window.add(box)
+
+toolbar = ToolbarBox()
+box.pack_start(toolbar, False)
+
+tollbarbutton_1 = ToolbarButton(
+ page=gtk.Button('sub-widget #1'),
+ icon_name='computer-xo')
+toolbar.toolbar.insert(tollbarbutton_1, -1)
+
+tollbarbutton_2 = ToolbarButton(
+ page=gtk.Button('sub-widget #2'),
+ icon_name='button_cancel',
+ tooltip='with custom palette instead of sub-widget')
+toolbar.toolbar.insert(tollbarbutton_2, -1)
+
+toolbar.toolbar.insert(gtk.SeparatorToolItem(), -1)
+
+def del_cb(widget):
+ toolbar.toolbar.remove(tollbarbutton_3)
+del_b = gtk.Button('delete sub-widget #3')
+del_b.connect('clicked', del_cb)
+tollbarbutton_3 = ToolbarButton(
+ page=del_b,
+ icon_name='activity-journal')
+toolbar.toolbar.insert(tollbarbutton_3, -1)
+
+subbar = gtk.Toolbar()
+subbutton = ToolButton(
+ icon_name='document-send',
+ tooltip='document-send')
+subbar.insert(subbutton, -1)
+subbar.show_all()
+
+tollbarbutton_4 = ToolbarButton(
+ page=subbar,
+ icon_name='document-save')
+toolbar.toolbar.insert(tollbarbutton_4, -1)
+
+window.show_all()
+gtk.main()
diff --git a/toolkit/m4/.gitignore b/toolkit/m4/.gitignore
new file mode 100644
index 0000000..e08c7c8
--- /dev/null
+++ b/toolkit/m4/.gitignore
@@ -0,0 +1,3 @@
+intltool.m4
+libtool.m4
+lt*.m4
diff --git a/toolkit/m4/gnome-compiler-flags.m4 b/toolkit/m4/gnome-compiler-flags.m4
new file mode 100644
index 0000000..b9db2fd
--- /dev/null
+++ b/toolkit/m4/gnome-compiler-flags.m4
@@ -0,0 +1,141 @@
+dnl GNOME_COMPILE_WARNINGS
+dnl Turn on many useful compiler warnings
+dnl For now, only works on GCC
+AC_DEFUN([GNOME_COMPILE_WARNINGS],[
+ dnl ******************************
+ dnl More compiler warnings
+ dnl ******************************
+
+ AC_ARG_ENABLE(compile-warnings,
+ AC_HELP_STRING([--enable-compile-warnings=@<:@no/minimum/yes/maximum/error@:>@],
+ [Turn on compiler warnings]),,
+ [enable_compile_warnings="m4_default([$1],[yes])"])
+
+ warnCFLAGS=
+ if test "x$GCC" != xyes; then
+ enable_compile_warnings=no
+ fi
+
+ warning_flags=
+ realsave_CFLAGS="$CFLAGS"
+
+ case "$enable_compile_warnings" in
+ no)
+ warning_flags=
+ ;;
+ minimum)
+ warning_flags="-Wall"
+ ;;
+ yes)
+ warning_flags="-Wall -Wmissing-prototypes"
+ ;;
+ maximum|error)
+ warning_flags="-Wall -Wmissing-prototypes -Wnested-externs -Wpointer-arith"
+ CFLAGS="$warning_flags $CFLAGS"
+ for option in -Wno-sign-compare; do
+ SAVE_CFLAGS="$CFLAGS"
+ CFLAGS="$CFLAGS $option"
+ AC_MSG_CHECKING([whether gcc understands $option])
+ AC_TRY_COMPILE([], [],
+ has_option=yes,
+ has_option=no,)
+ CFLAGS="$SAVE_CFLAGS"
+ AC_MSG_RESULT($has_option)
+ if test $has_option = yes; then
+ warning_flags="$warning_flags $option"
+ fi
+ unset has_option
+ unset SAVE_CFLAGS
+ done
+ unset option
+ if test "$enable_compile_warnings" = "error" ; then
+ warning_flags="$warning_flags -Werror"
+ fi
+ ;;
+ *)
+ AC_MSG_ERROR(Unknown argument '$enable_compile_warnings' to --enable-compile-warnings)
+ ;;
+ esac
+ CFLAGS="$realsave_CFLAGS"
+ AC_MSG_CHECKING(what warning flags to pass to the C compiler)
+ AC_MSG_RESULT($warning_flags)
+
+ AC_ARG_ENABLE(iso-c,
+ AC_HELP_STRING([--enable-iso-c],
+ [Try to warn if code is not ISO C ]),,
+ [enable_iso_c=no])
+
+ AC_MSG_CHECKING(what language compliance flags to pass to the C compiler)
+ complCFLAGS=
+ if test "x$enable_iso_c" != "xno"; then
+ if test "x$GCC" = "xyes"; then
+ case " $CFLAGS " in
+ *[\ \ ]-ansi[\ \ ]*) ;;
+ *) complCFLAGS="$complCFLAGS -ansi" ;;
+ esac
+ case " $CFLAGS " in
+ *[\ \ ]-pedantic[\ \ ]*) ;;
+ *) complCFLAGS="$complCFLAGS -pedantic" ;;
+ esac
+ fi
+ fi
+ AC_MSG_RESULT($complCFLAGS)
+
+ WARN_CFLAGS="$warning_flags $complCFLAGS"
+ AC_SUBST(WARN_CFLAGS)
+])
+
+dnl For C++, do basically the same thing.
+
+AC_DEFUN([GNOME_CXX_WARNINGS],[
+ AC_ARG_ENABLE(cxx-warnings,
+ AC_HELP_STRING([--enable-cxx-warnings=@<:@no/minimum/yes@:>@]
+ [Turn on compiler warnings.]),,
+ [enable_cxx_warnings="m4_default([$1],[minimum])"])
+
+ AC_MSG_CHECKING(what warning flags to pass to the C++ compiler)
+ warnCXXFLAGS=
+ if test "x$GXX" != xyes; then
+ enable_cxx_warnings=no
+ fi
+ if test "x$enable_cxx_warnings" != "xno"; then
+ if test "x$GXX" = "xyes"; then
+ case " $CXXFLAGS " in
+ *[\ \ ]-Wall[\ \ ]*) ;;
+ *) warnCXXFLAGS="-Wall -Wno-unused" ;;
+ esac
+
+ ## -W is not all that useful. And it cannot be controlled
+ ## with individual -Wno-xxx flags, unlike -Wall
+ if test "x$enable_cxx_warnings" = "xyes"; then
+ warnCXXFLAGS="$warnCXXFLAGS -Wshadow -Woverloaded-virtual"
+ fi
+ fi
+ fi
+ AC_MSG_RESULT($warnCXXFLAGS)
+
+ AC_ARG_ENABLE(iso-cxx,
+ AC_HELP_STRING([--enable-iso-cxx],
+ [Try to warn if code is not ISO C++ ]),,
+ [enable_iso_cxx=no])
+
+ AC_MSG_CHECKING(what language compliance flags to pass to the C++ compiler)
+ complCXXFLAGS=
+ if test "x$enable_iso_cxx" != "xno"; then
+ if test "x$GXX" = "xyes"; then
+ case " $CXXFLAGS " in
+ *[\ \ ]-ansi[\ \ ]*) ;;
+ *) complCXXFLAGS="$complCXXFLAGS -ansi" ;;
+ esac
+
+ case " $CXXFLAGS " in
+ *[\ \ ]-pedantic[\ \ ]*) ;;
+ *) complCXXFLAGS="$complCXXFLAGS -pedantic" ;;
+ esac
+ fi
+ fi
+ AC_MSG_RESULT($complCXXFLAGS)
+
+ WARN_CXXFLAGS="$CXXFLAGS $warnCXXFLAGS $complCXXFLAGS"
+ AC_SUBST(WARN_CXXFLAGS)
+])
diff --git a/m4/python.m4 b/toolkit/m4/python.m4
index e1c5266..e1c5266 100644
--- a/m4/python.m4
+++ b/toolkit/m4/python.m4
diff --git a/toolkit/po/.gitignore b/toolkit/po/.gitignore
new file mode 100644
index 0000000..da9bbde
--- /dev/null
+++ b/toolkit/po/.gitignore
@@ -0,0 +1,4 @@
+*.gmo
+Makefile.in.in
+POTFILES
+stamp-it
diff --git a/src/carquinyol/__init__.py b/toolkit/po/ChangeLog
index e69de29..e69de29 100644
--- a/src/carquinyol/__init__.py
+++ b/toolkit/po/ChangeLog
diff --git a/toolkit/po/POTFILES.in b/toolkit/po/POTFILES.in
new file mode 100644
index 0000000..c9a8443
--- /dev/null
+++ b/toolkit/po/POTFILES.in
@@ -0,0 +1,7 @@
+src/sugar/activity/activity.py
+src/sugar/activity/namingalert.py
+src/sugar/activity/widgets.py
+src/sugar/graphics/alert.py
+src/sugar/graphics/colorbutton.py
+src/sugar/graphics/objectchooser.py
+src/sugar/util.py
diff --git a/toolkit/po/POTFILES.skip b/toolkit/po/POTFILES.skip
new file mode 100644
index 0000000..a656b5a
--- /dev/null
+++ b/toolkit/po/POTFILES.skip
@@ -0,0 +1,6 @@
+# We don't care about these string, they are in code which we don't really
+# use and is there solely to not diverge too much from the "upstream"
+# versions of these files.
+src/sugar/eggdesktopfile.c
+src/sugar/eggsmclient.c
+src/sugar/gsm-xsmp.c
diff --git a/toolkit/po/af.po b/toolkit/po/af.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/toolkit/po/af.po
@@ -0,0 +1,153 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/am.po b/toolkit/po/am.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/toolkit/po/am.po
@@ -0,0 +1,153 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/ar.po b/toolkit/po/ar.po
new file mode 100644
index 0000000..8512cbd
--- /dev/null
+++ b/toolkit/po/ar.po
@@ -0,0 +1,630 @@
+# translation of sugar.po to Arabic
+# Khaled Hosny <khaledhosny@eglug.org>, 2007, 2008, 2009.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+msgid ""
+msgstr ""
+"Project-Id-Version: sugar\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-08-26 00:33-0400\n"
+"PO-Revision-Date: 2009-11-10 08:03-0400\n"
+"Last-Translator: Khaled Hosny <khaledhosny@eglug.org>\n"
+"Language-Team: Arabic <doc@arabeyes.org>\n"
+"Language: ar\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n>=3 && "
+"n<=10 ? 3 : n>=11 && n<=99 ? 4 : 5;\n"
+"X-Generator: Pootle 1.2.1\n"
+"Nplurals=6; Plural=N==0 ? 0: n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 "
+": n%100>=11 && n%100<=99 ? 4 : 5;\n"
+
+#: ../src/sugar/activity/activity.py:329
+#, python-format
+msgid "%s Activity"
+msgstr "نشاط %s"
+
+#: ../src/sugar/activity/activity.py:714
+msgid "Keep error"
+msgstr "خطأ في الحفظ"
+
+#: ../src/sugar/activity/activity.py:715
+msgid "Keep error: all changes will be lost"
+msgstr "خطأ في الحفظ: ستُفقد كل التغييرات"
+
+#: ../src/sugar/activity/activity.py:718
+msgid "Don't stop"
+msgstr "لا تُوقف"
+
+#: ../src/sugar/activity/activity.py:721
+msgid "Stop anyway"
+msgstr "أوقف على أي حال"
+
+#: ../src/sugar/activity/namingalert.py:82
+msgid "Name this entry"
+msgstr "سمّ هذه المدخلة"
+
+# self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+# TODO: Implement stopping downloads
+# self._stop_item.connect('activate', self._stop_item_activate_cb)
+# self.append_menu_item(self._stop_item)
+#: ../src/sugar/activity/namingalert.py:87
+#: ../src/sugar/activity/widgets.py:162
+msgid "Keep"
+msgstr "احفظ"
+
+#: ../src/sugar/activity/namingalert.py:283
+msgid "Untitled"
+msgstr "بدون عنوان"
+
+#: ../src/sugar/activity/namingalert.py:290
+msgid "Description:"
+msgstr "الوصف:"
+
+#: ../src/sugar/activity/namingalert.py:314
+msgid "Tags:"
+msgstr "الوُسوم:"
+
+#: ../src/sugar/activity/widgets.py:79
+msgid "Stop"
+msgstr "أوقف"
+
+#: ../src/sugar/activity/widgets.py:91
+msgid "Undo"
+msgstr "تراجع"
+
+#: ../src/sugar/activity/widgets.py:99
+msgid "Redo"
+msgstr "أعِد"
+
+#: ../src/sugar/activity/widgets.py:106
+msgid "Copy"
+msgstr "انسخ"
+
+#: ../src/sugar/activity/widgets.py:113
+msgid "Paste"
+msgstr "الصق"
+
+#: ../src/sugar/activity/widgets.py:123
+msgid "Private"
+msgstr "خاص"
+
+#: ../src/sugar/activity/widgets.py:130
+msgid "My Neighborhood"
+msgstr "جِوارِي"
+
+#: ../src/sugar/activity/widgets.py:341
+msgid "Activity"
+msgstr "النشاط"
+
+#: ../src/sugar/graphics/alert.py:286 ../src/sugar/graphics/alert.py:365
+msgid "Cancel"
+msgstr "ألغِ"
+
+#: ../src/sugar/graphics/alert.py:290 ../src/sugar/graphics/alert.py:424
+msgid "Ok"
+msgstr "حسنا"
+
+#: ../src/sugar/graphics/alert.py:375
+msgid "Continue"
+msgstr "واصِل"
+
+#: ../src/sugar/graphics/colorbutton.py:52
+msgid "Choose a color"
+msgstr "اختر لونًا"
+
+#: ../src/sugar/graphics/colorbutton.py:272
+msgid "Red"
+msgstr "أحمر"
+
+#: ../src/sugar/graphics/colorbutton.py:274
+msgid "Green"
+msgstr "أخضر"
+
+#: ../src/sugar/graphics/colorbutton.py:276
+msgid "Blue"
+msgstr "أزرق"
+
+#: ../src/sugar/util.py:218
+msgid " and "
+msgstr " و "
+
+#: ../src/sugar/util.py:219
+msgid ", "
+msgstr " و "
+
+# TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:222
+msgid "Seconds ago"
+msgstr "قبل بضعة ثوان"
+
+# TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",&lt;br /&gt;<br />
+# "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:226
+#, python-format
+msgid "%s ago"
+msgstr "منذ %s"
+
+# TRANS: Relative dates (eg. 1 month and 5 days).
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:241
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] "%d سنة"
+msgstr[1] "سنة"
+msgstr[2] "سنتين"
+msgstr[3] "%d سنوات"
+msgstr[4] "%d سنة"
+msgstr[5] "%d سنة"
+
+#: ../src/sugar/util.py:242
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] "%d شهر"
+msgstr[1] "شهر"
+msgstr[2] "شهرين"
+msgstr[3] "%d أشهر"
+msgstr[4] "%d شهرا"
+msgstr[5] "%d شهر"
+
+#: ../src/sugar/util.py:243
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] "%d أسبوع"
+msgstr[1] "أسبوع"
+msgstr[2] "أسبوعين"
+msgstr[3] "%d أسابيع"
+msgstr[4] "%d أسبوعا"
+msgstr[5] "%d أسبوع"
+
+#: ../src/sugar/util.py:244
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] "%d يوم"
+msgstr[1] "يوم"
+msgstr[2] "يومين"
+msgstr[3] "%d أيام"
+msgstr[4] "%d يوما"
+msgstr[5] "%d يوم"
+
+#: ../src/sugar/util.py:245
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] "%d ساعة"
+msgstr[1] "ساعة"
+msgstr[2] "ساعتين"
+msgstr[3] "%d ساعات"
+msgstr[4] "%d ساعة"
+msgstr[5] "%d ساعة"
+
+#: ../src/sugar/util.py:246
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] "%d دقيقة"
+msgstr[1] "دقيقة"
+msgstr[2] "دقيقتين"
+msgstr[3] "%d دقائق"
+msgstr[4] "%d دقيقة"
+msgstr[5] "%d دقيقة"
+
+#: ../src/sugar/util.py:339
+msgid "Empty"
+msgstr "فارغ"
+
+#: ../src/sugar/util.py:341
+#, python-format
+msgid "%d B"
+msgstr "%d بايت"
+
+#: ../src/sugar/util.py:343
+#, python-format
+msgid "%d KB"
+msgstr "%d ك.بايت"
+
+#: ../src/sugar/util.py:345
+#, python-format
+msgid "%d MB"
+msgstr "%d م.بايت"
+
+#: ../src/sugar/util.py:347
+#, python-format
+msgid "%d GB"
+msgstr "%d ج.بايت"
+
+#~ msgid "Share with:"
+#~ msgstr "شارِك مع:"
+
+#~ msgid "Name:"
+#~ msgstr "الاسم:"
+
+#~ msgid "Click to change color:"
+#~ msgstr "انقر لتغيير اللون:"
+
+#~ msgid "Back"
+#~ msgstr "السابق"
+
+#~ msgid "Done"
+#~ msgstr "تمّ"
+
+#~ msgid "Next"
+#~ msgstr "التالي"
+
+#~ msgid "Remove friend"
+#~ msgstr "أزل صديق"
+
+#~ msgid "Make friend"
+#~ msgstr "اصنع صديق"
+
+#~ msgid "Invite to %s"
+#~ msgstr "ادعُ إلى %s"
+
+#~ msgid "Remove"
+#~ msgstr "أزل"
+
+#~ msgid "Open"
+#~ msgstr "افتح"
+
+#~ msgid "Open with"
+#~ msgstr "افتح باستخدام"
+
+#~ msgid "Clipboard object: %s."
+#~ msgstr "عنصر الحافظة: %s."
+
+#~ msgid "Key Type:"
+#~ msgstr "نوع المفتاح:"
+
+#~ msgid "Authentication Type:"
+#~ msgstr "نوع الاستيثاق:"
+
+#~ msgid "Encryption Type:"
+#~ msgstr "نوع التعمية:"
+
+#~ msgid "Screenshot"
+#~ msgstr "لقطة شاشة"
+
+#~ msgid "List view"
+#~ msgstr "منظور القائمة"
+
+#~ msgid "<Ctrl>L"
+#~ msgstr "<Ctrl>ق"
+
+#~ msgid "<Ctrl>R"
+#~ msgstr "<Ctrl>ح"
+
+#~ msgid "Connect"
+#~ msgstr "اتصل"
+
+#~ msgid "Disconnect"
+#~ msgstr "اقطع الاتصال"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#~ msgid "Disconnecting..."
+#~ msgstr "يجري قطع الاتصال..."
+
+#~ msgid "Connecting..."
+#~ msgstr "يجري الاتصال..."
+
+# TODO: show the channel number
+#~ msgid "Connected"
+#~ msgstr "مُتّصل"
+
+#~ msgid "Mesh Network"
+#~ msgstr "شبكة عُروِيّة"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#~ msgid "Disconnect..."
+#~ msgstr "افصِل..."
+
+#~ msgid "Resume"
+#~ msgstr "استكمل"
+
+#~ msgid "Join"
+#~ msgstr "التحق"
+
+#~ msgid "My Battery"
+#~ msgstr "بطاريتي"
+
+#~ msgid "Charging"
+#~ msgstr "يشحن"
+
+#~ msgid "Very little power remaining"
+#~ msgstr "بقي القليل جدا من الطاقة"
+
+#~ msgid "%(hour)d:%(min).2d remaining"
+#~ msgstr "باقي %(hour)d:%(min).2d"
+
+#~ msgid "Charged"
+#~ msgstr "مشحون"
+
+#~ msgid "My Speakers"
+#~ msgstr "سماعاتي"
+
+#~ msgid "Unmute"
+#~ msgstr "افتح"
+
+#~ msgid "Mute"
+#~ msgstr "أصمِت"
+
+#~ msgid "Disconnected"
+#~ msgstr "مفصول"
+
+#~ msgid "Channel"
+#~ msgstr "قناة"
+
+#~ msgid "Neighborhood"
+#~ msgstr "الجِوَار"
+
+#~ msgid "Group"
+#~ msgstr "المجموعة"
+
+#~ msgid "Home"
+#~ msgstr "المنزل"
+
+#~ msgid "To apply your changes you have to restart sugar.\n"
+#~ msgstr "تحتاج لإعادة تشغيل «سُكّر» لتُطبق التغييرات.\n"
+
+#~ msgid "Warning"
+#~ msgstr "تحذير"
+
+#~ msgid "Cancel changes"
+#~ msgstr "ألغِ التغييرات"
+
+#~ msgid "Later"
+#~ msgstr "لاحقا"
+
+#~ msgid "You must enter a name."
+#~ msgstr "يجب أن تُدخِل اسما."
+
+#~ msgid "stroke: color=%s hue=%s"
+#~ msgstr "الحواف: اللون=%s التشبع=%s"
+
+#~ msgid "stroke: %s"
+#~ msgstr "الحواف: %s"
+
+#~ msgid "fill: color=%s hue=%s"
+#~ msgstr "الملء: اللون=%s التشبع=%s"
+
+#~ msgid "fill: %s"
+#~ msgstr "الملء: %s"
+
+#~ msgid "Error in specified color modifiers."
+#~ msgstr "خطأ في مُغيّرات الألوان المحددة."
+
+#~ msgid "Error in specified colors."
+#~ msgstr "خطأ في الألوان المحددة."
+
+#~ msgid "Not available"
+#~ msgstr "غير مُتاح"
+
+#~ msgid "Error timezone does not exist."
+#~ msgstr "خطأ: المنطقة الزمنية لا وجود لها."
+
+#, fuzzy
+#~ msgid "Value must be an integer."
+#~ msgstr "يجب أن تكون القيمة عددا صحيحا."
+
+#~ msgid "Could not access ~/.i18n. Create standard settings."
+#~ msgstr "تعذّر الوصول إلى ‭~/.i18n‬. سأنشئ إعدادات قياسية."
+
+#~ msgid "Language for code=%s could not be determined."
+#~ msgstr "لا يمكن تحديد لغة الرمز=%s."
+
+#~ msgid "Sorry I do not speak '%s'."
+#~ msgstr "آسف، لا أتحدث '%s'."
+
+#~ msgid "You must enter a server."
+#~ msgstr "يجب أن تُدخِل خادوما."
+
+#~ msgid "State is unknown."
+#~ msgstr "الحالة مجهولة."
+
+#~ msgid "Error in specified radio argument use on/off."
+#~ msgstr "خطأ في معامل الإذاعة المحدد، استخدم مُفعّل/مُعطّل."
+
+#~ msgid "About Me"
+#~ msgstr "عنّي"
+
+#~ msgid "Click to change your color:"
+#~ msgstr "انقر لتغيير اللون:"
+
+#~ msgid "About my XO"
+#~ msgstr "عن حاسوبي"
+
+#~ msgid "Identity"
+#~ msgstr "المعرّف"
+
+#~ msgid "Serial Number:"
+#~ msgstr "الرقم التسلسلي"
+
+#~ msgid "Software"
+#~ msgstr "البرمجيات"
+
+#~ msgid "Build:"
+#~ msgstr "البناء:"
+
+#~ msgid "Firmware:"
+#~ msgstr "البرمجيات الثابتة (Firmware):"
+
+#~ msgid "Date & Time"
+#~ msgstr "التاريخ والوقت"
+
+#~ msgid "Timezone"
+#~ msgstr "المنطقة الزمنية"
+
+#~ msgid "Frame"
+#~ msgstr "الإطار"
+
+#~ msgid "never"
+#~ msgstr "أبدا"
+
+#~ msgid "instantaneous"
+#~ msgstr "آني"
+
+#, fuzzy
+#~ msgid "%s seconds"
+#~ msgstr "%d ثوان"
+
+#~ msgid "Language"
+#~ msgstr "اللغة"
+
+#~ msgid "Network"
+#~ msgstr "الشبكة"
+
+#~ msgid "Wireless"
+#~ msgstr "اللاسلكي"
+
+#~ msgid "Radio:"
+#~ msgstr "الإذاعة:"
+
+#~ msgid "Mesh"
+#~ msgstr "الشبكة العروية"
+
+#~ msgid "Server:"
+#~ msgstr "الخادوم"
+
+#~ msgid "Connected to a School Mesh Portal"
+#~ msgstr "اتصل ببوابة شبكة مدرسة"
+
+#~ msgid "Looking for a School Mesh Portal..."
+#~ msgstr "يبحث عن بوابة شبكة مدرسة..."
+
+#~ msgid "Connected to an XO Mesh Portal"
+#~ msgstr "اتصل ببوابة شبكة XO"
+
+#~ msgid "Looking for an XO Mesh Portal..."
+#~ msgstr "يبحث عن بوابة شبكة XO..."
+
+#~ msgid "Connected to a Simple Mesh"
+#~ msgstr "اتصل بشبكة بسيطة"
+
+#~ msgid "Starting a Simple Mesh"
+#~ msgstr "يبدأ شبكة بسيطة"
+
+#~ msgid "Unknown Mesh"
+#~ msgstr "شبكة مجهولة"
+
+#~ msgid "Decline"
+#~ msgstr "ارفض"
+
+#~ msgid "Control Panel"
+#~ msgstr "لوحة التحكم"
+
+#~ msgid "Restart"
+#~ msgstr "أعد التشغيل"
+
+#~ msgid "Shutdown"
+#~ msgstr "أطفئ"
+
+#~ msgid "Register"
+#~ msgstr "سجّل"
+
+#~ msgid "Starting..."
+#~ msgstr "يبدأ..."
+
+#~ msgid "Start"
+#~ msgstr "ابدأ"
+
+#~ msgid "Show contents"
+#~ msgstr "أظهر المحتويات"
+
+#~ msgid "%(free_space)d MB Free"
+#~ msgstr "%(free_space)d م.بايت خالية"
+
+#~ msgid "Ring view"
+#~ msgstr "منظور الحلقة"
+
+#~ msgid "Remove from ring"
+#~ msgstr "أزِل من الحلقة"
+
+#~ msgid "Add to ring"
+#~ msgstr "أضِف للحلقة"
+
+#~ msgid "Changes require a sugar restart to take effect."
+#~ msgstr "يتطلب نفاذ التغييرات إعادة تشغيل «سُكّر»."
+
+#~ msgid "Changes require restart to take effect"
+#~ msgstr "يتطلب نفاذ التغييرات إعادة التشغيل."
+
+#~ msgid "Delay in milliseconds:"
+#~ msgstr "التأخير بالملي ثانية:"
+
+#~ msgid "Hot Corners"
+#~ msgstr "الزوايا النشطة"
+
+#~ msgid "Warm Edges"
+#~ msgstr "الحواف المتفاعلة"
+
+#~ msgid "off"
+#~ msgstr "معطّل"
+
+#~ msgid "on"
+#~ msgstr "مفعّل"
+
+#~ msgid "Permission denied. You need to be root to run this method."
+#~ msgstr "الصلاحية ممنوعة. تحتاج أن تكون الجذر لتشغل الوظيفة المطلوبة."
+
+#~ msgid "Error in reading timezone"
+#~ msgstr "خطأ في قراءة المنطقة الزمنية"
+
+#~ msgid "Error copying timezone (from %s): %s"
+#~ msgstr "خطأ في نسخ المنطقة الزمنية (من %s): %s"
+
+#~ msgid "Changing permission of timezone: %s"
+#~ msgstr "يجري تغيير صلاحيات المنطقة الزمنية: %s"
+
+#~ msgid "About this XO"
+#~ msgstr "عَنْ XO هذا"
+
+#~ msgid "Add to journal"
+#~ msgstr "أضِف إلى اليوميات"
+
+#~ msgid "Reboot"
+#~ msgstr "أعِد التشغيل"
+
+#~ msgid "My Battery life"
+#~ msgstr "عمر بطاريتي"
+
+#~ msgid "Battery charging"
+#~ msgstr "البطاريّة تشحن"
+
+#~ msgid "Battery discharging"
+#~ msgstr "البطاريّة تُفرّغ"
+
+#~ msgid "Battery fully charged"
+#~ msgstr "البطارية مشحونة تماما"
+
+#~ msgid "Invite"
+#~ msgstr "ادعُ"
+
+#~ msgid "Text"
+#~ msgstr "نص"
+
+#~ msgid "Image"
+#~ msgstr "صورة"
+
+#~ msgid "OK"
+#~ msgstr "حسنا"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+# TODO: show the channel number
+#~ msgid "%d second"
+#~ msgstr "%d ثانية"
diff --git a/toolkit/po/ay.po b/toolkit/po/ay.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/toolkit/po/ay.po
@@ -0,0 +1,153 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/bg.po b/toolkit/po/bg.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/toolkit/po/bg.po
@@ -0,0 +1,153 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/bi.po b/toolkit/po/bi.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/toolkit/po/bi.po
@@ -0,0 +1,153 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/bn.po b/toolkit/po/bn.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/toolkit/po/bn.po
@@ -0,0 +1,153 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/bn_IN.po b/toolkit/po/bn_IN.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/toolkit/po/bn_IN.po
@@ -0,0 +1,153 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/ca.po b/toolkit/po/ca.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/toolkit/po/ca.po
@@ -0,0 +1,153 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/cpp.po b/toolkit/po/cpp.po
new file mode 100644
index 0000000..6179aa0
--- /dev/null
+++ b/toolkit/po/cpp.po
@@ -0,0 +1,186 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-01-20 00:31-0500\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:123
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:125
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:126
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:133
+#: ../src/sugar/activity/namingalert.py:65
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:144
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:258
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:263
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:273
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:278
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:304
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:542
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:910
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:911
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:914
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:917
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/activity/namingalert.py:60
+msgid "Name this entry"
+msgstr ""
+
+#: ../src/sugar/activity/namingalert.py:248
+msgid "Untitled"
+msgstr ""
+
+#: ../src/sugar/activity/namingalert.py:255
+msgid "Description:"
+msgstr ""
+
+#: ../src/sugar/activity/namingalert.py:279
+msgid "Tags:"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:288 ../src/sugar/graphics/alert.py:367
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:292 ../src/sugar/graphics/alert.py:426
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:377
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/graphics/colorbutton.py:49
+msgid "Choose a color"
+msgstr ""
+
+#: ../src/sugar/graphics/colorbutton.py:262
+msgid "Red"
+msgstr ""
+
+#: ../src/sugar/graphics/colorbutton.py:264
+msgid "Green"
+msgstr ""
+
+#: ../src/sugar/graphics/colorbutton.py:266
+msgid "Blue"
+msgstr ""
+
+#: ../src/sugar/util.py:194
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:195
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:198
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:215
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:216
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:217
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:218
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:219
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:220
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/cs.po b/toolkit/po/cs.po
new file mode 100644
index 0000000..bf097b4
--- /dev/null
+++ b/toolkit/po/cs.po
@@ -0,0 +1,160 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
diff --git a/toolkit/po/da.po b/toolkit/po/da.po
new file mode 100644
index 0000000..edc59d0
--- /dev/null
+++ b/toolkit/po/da.po
@@ -0,0 +1,208 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-08-26 00:33-0400\n"
+"PO-Revision-Date: 2009-08-17 21:36-0400\n"
+"Last-Translator: Chris Leonard <cjl@laptop.org>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: da\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Pootle 1.2.1\n"
+
+#: ../src/sugar/activity/activity.py:329
+#, python-format
+msgid "%s Activity"
+msgstr "%s Aktivitet"
+
+#: ../src/sugar/activity/activity.py:714
+msgid "Keep error"
+msgstr "Beholdefejl"
+
+#: ../src/sugar/activity/activity.py:715
+msgid "Keep error: all changes will be lost"
+msgstr "Beholdefejl: alle ændringer går tabt"
+
+#: ../src/sugar/activity/activity.py:718
+msgid "Don't stop"
+msgstr "Stop ikke"
+
+#: ../src/sugar/activity/activity.py:721
+msgid "Stop anyway"
+msgstr "Stop alligevel"
+
+#: ../src/sugar/activity/namingalert.py:82
+msgid "Name this entry"
+msgstr ""
+
+#: ../src/sugar/activity/namingalert.py:87
+#: ../src/sugar/activity/widgets.py:162
+msgid "Keep"
+msgstr "Behold"
+
+#: ../src/sugar/activity/namingalert.py:283
+msgid "Untitled"
+msgstr "Uden navn"
+
+#: ../src/sugar/activity/namingalert.py:290
+msgid "Description:"
+msgstr "Beskrivelse:"
+
+#: ../src/sugar/activity/namingalert.py:314
+msgid "Tags:"
+msgstr "Mærkater:"
+
+#: ../src/sugar/activity/widgets.py:79
+msgid "Stop"
+msgstr "Stop"
+
+#: ../src/sugar/activity/widgets.py:91
+msgid "Undo"
+msgstr "Fortryd"
+
+#: ../src/sugar/activity/widgets.py:99
+msgid "Redo"
+msgstr "Gentag"
+
+#: ../src/sugar/activity/widgets.py:106
+msgid "Copy"
+msgstr "Kopiér"
+
+#: ../src/sugar/activity/widgets.py:113
+msgid "Paste"
+msgstr "Sæt ind"
+
+#: ../src/sugar/activity/widgets.py:123
+msgid "Private"
+msgstr "Privat"
+
+#: ../src/sugar/activity/widgets.py:130
+msgid "My Neighborhood"
+msgstr "Mit nabolag"
+
+#: ../src/sugar/activity/widgets.py:341
+msgid "Activity"
+msgstr "Aktivitet"
+
+#: ../src/sugar/graphics/alert.py:286 ../src/sugar/graphics/alert.py:365
+msgid "Cancel"
+msgstr "Afbryd"
+
+#: ../src/sugar/graphics/alert.py:290 ../src/sugar/graphics/alert.py:424
+msgid "Ok"
+msgstr "Ok"
+
+#: ../src/sugar/graphics/alert.py:375
+msgid "Continue"
+msgstr "Fortsæt"
+
+#: ../src/sugar/graphics/colorbutton.py:52
+msgid "Choose a color"
+msgstr ""
+
+#: ../src/sugar/graphics/colorbutton.py:272
+#, fuzzy
+msgid "Red"
+msgstr "Gentag"
+
+#: ../src/sugar/graphics/colorbutton.py:274
+msgid "Green"
+msgstr ""
+
+#: ../src/sugar/graphics/colorbutton.py:276
+msgid "Blue"
+msgstr ""
+
+#: ../src/sugar/util.py:218
+msgid " and "
+msgstr " og "
+
+#: ../src/sugar/util.py:219
+msgid ", "
+msgstr ", "
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:222
+msgid "Seconds ago"
+msgstr "sekunder siden"
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:226
+#, python-format
+msgid "%s ago"
+msgstr "%s siden"
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:241
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] "%d år"
+msgstr[1] "%d år"
+
+#: ../src/sugar/util.py:242
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] "%d måned"
+msgstr[1] "%d måneder"
+
+#: ../src/sugar/util.py:243
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] "%d uge"
+msgstr[1] "%d uger"
+
+#: ../src/sugar/util.py:244
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] "%d dag"
+msgstr[1] "%d dage"
+
+#: ../src/sugar/util.py:245
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] "%d time"
+msgstr[1] "%d timer"
+
+#: ../src/sugar/util.py:246
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] "%d minut"
+msgstr[1] "%d minutter"
+
+#: ../src/sugar/util.py:339
+msgid "Empty"
+msgstr ""
+
+#: ../src/sugar/util.py:341
+#, python-format
+msgid "%d B"
+msgstr ""
+
+#: ../src/sugar/util.py:343
+#, python-format
+msgid "%d KB"
+msgstr ""
+
+#: ../src/sugar/util.py:345
+#, python-format
+msgid "%d MB"
+msgstr ""
+
+#: ../src/sugar/util.py:347
+#, python-format
+msgid "%d GB"
+msgstr ""
diff --git a/toolkit/po/de.po b/toolkit/po/de.po
new file mode 100644
index 0000000..5ee3a4d
--- /dev/null
+++ b/toolkit/po/de.po
@@ -0,0 +1,228 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# Markus Schlager <m.slg@gmx.de>, 2008.
+msgid ""
+msgstr ""
+"Project-Id-Version: sugar-toolkit\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-01-20 13:10-0500\n"
+"PO-Revision-Date: 2010-03-26 20:39+0200\n"
+"Last-Translator: Markus <m.slg@gmx.de>\n"
+"Language-Team: OLPC-German <LL@li.org>\n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Pootle 2.0.3\n"
+
+#: ../src/sugar/activity/activity.py:338
+#, python-format
+msgid "%s Activity"
+msgstr "%s Aktivität"
+
+#: ../src/sugar/activity/activity.py:738
+msgid "Keep error"
+msgstr "Fehler beim Speichern"
+
+#: ../src/sugar/activity/activity.py:739
+msgid "Keep error: all changes will be lost"
+msgstr "Fehler beim Speichern: Alle Änderungen gehen verloren"
+
+#: ../src/sugar/activity/activity.py:742
+msgid "Don't stop"
+msgstr "Nicht beenden"
+
+#: ../src/sugar/activity/activity.py:745
+msgid "Stop anyway"
+msgstr "Trotzdem beenden"
+
+#: ../src/sugar/activity/namingalert.py:82
+msgid "Name this entry"
+msgstr "Diesen Eintrag benennen"
+
+# (Markus S.) war 'Behalten'
+#: ../src/sugar/activity/namingalert.py:87
+#: ../src/sugar/activity/widgets.py:166
+msgid "Keep"
+msgstr "Speichern"
+
+#: ../src/sugar/activity/namingalert.py:283
+msgid "Untitled"
+msgstr "Unbenannt"
+
+#: ../src/sugar/activity/namingalert.py:290
+msgid "Description:"
+msgstr "Beschreibung:"
+
+#: ../src/sugar/activity/namingalert.py:314
+msgid "Tags:"
+msgstr "Stichwörter:"
+
+#: ../src/sugar/activity/widgets.py:83
+msgid "Stop"
+msgstr "Beenden"
+
+#: ../src/sugar/activity/widgets.py:95
+msgid "Undo"
+msgstr "Rückgängig"
+
+#: ../src/sugar/activity/widgets.py:103
+msgid "Redo"
+msgstr "Wiederherstellen"
+
+#: ../src/sugar/activity/widgets.py:110
+msgid "Copy"
+msgstr "Kopieren"
+
+#: ../src/sugar/activity/widgets.py:117
+msgid "Paste"
+msgstr "Einfügen"
+
+#: ../src/sugar/activity/widgets.py:127
+msgid "Private"
+msgstr "Privat"
+
+#: ../src/sugar/activity/widgets.py:134
+msgid "My Neighborhood"
+msgstr "Meine Umgebung"
+
+#: ../src/sugar/activity/widgets.py:345
+msgid "Activity"
+msgstr "Aktivität"
+
+#: ../src/sugar/graphics/alert.py:286 ../src/sugar/graphics/alert.py:365
+msgid "Cancel"
+msgstr "Abbrechen"
+
+#: ../src/sugar/graphics/alert.py:290 ../src/sugar/graphics/alert.py:424
+msgid "Ok"
+msgstr "Ok"
+
+#: ../src/sugar/graphics/alert.py:375
+msgid "Continue"
+msgstr "Weitermachen"
+
+#: ../src/sugar/graphics/colorbutton.py:52
+msgid "Choose a color"
+msgstr "Farbe wählen"
+
+#: ../src/sugar/graphics/colorbutton.py:272
+msgid "Red"
+msgstr "Rot"
+
+#: ../src/sugar/graphics/colorbutton.py:274
+msgid "Green"
+msgstr "Grün"
+
+#: ../src/sugar/graphics/colorbutton.py:276
+msgid "Blue"
+msgstr "Blau"
+
+#: ../src/sugar/util.py:218
+msgid " and "
+msgstr " und "
+
+#: ../src/sugar/util.py:219
+msgid ", "
+msgstr ", "
+
+# TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:222
+msgid "Seconds ago"
+msgstr "Gerade eben"
+
+# TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+# "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:226
+#, python-format
+msgid "%s ago"
+msgstr "vor %s"
+
+# TRANS: Relative dates (eg. 1 month and 5 days).
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:241
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] "%d Jahr"
+msgstr[1] "%d Jahren"
+
+#: ../src/sugar/util.py:242
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] "%d Monat"
+msgstr[1] "%d Monaten"
+
+#: ../src/sugar/util.py:243
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] "%d Woche"
+msgstr[1] "%d Wochen"
+
+# Der String ergibt in dem UI - 'vor x Tagen', weswegen das 'n' hier wichtig ist.
+#: ../src/sugar/util.py:244
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] "%d Tag"
+msgstr[1] "%d Tagen"
+
+#: ../src/sugar/util.py:245
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] "%d Stunde"
+msgstr[1] "%d Stunden"
+
+#: ../src/sugar/util.py:246
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] "%d Minute"
+msgstr[1] "%d Minuten"
+
+#: ../src/sugar/util.py:339
+msgid "Empty"
+msgstr "Leer"
+
+#: ../src/sugar/util.py:341
+#, python-format
+msgid "%d B"
+msgstr "%d B"
+
+#: ../src/sugar/util.py:343
+#, python-format
+msgid "%d KB"
+msgstr "%d KB"
+
+#: ../src/sugar/util.py:345
+#, python-format
+msgid "%d MB"
+msgstr "%d MB"
+
+#: ../src/sugar/util.py:347
+#, python-format
+msgid "%d GB"
+msgstr "%d GB"
+
+#~ msgid "Share with:"
+#~ msgstr "Teilen mit:"
diff --git a/toolkit/po/dz.po b/toolkit/po/dz.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/toolkit/po/dz.po
@@ -0,0 +1,153 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/el.po b/toolkit/po/el.po
new file mode 100644
index 0000000..04a7126
--- /dev/null
+++ b/toolkit/po/el.po
@@ -0,0 +1,189 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-01-20 00:31-0500\n"
+"PO-Revision-Date: 2009-01-27 14:21-0500\n"
+"Last-Translator: Γιάννης Κασκαμανίδης <ttnfy17@yahoo.gr>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Pootle 1.1.0rc2\n"
+
+#: ../src/sugar/activity/activity.py:123
+msgid "Share with:"
+msgstr "Διαμοιρασμός με:"
+
+#: ../src/sugar/activity/activity.py:125
+msgid "Private"
+msgstr "Ιδιωτικό"
+
+#: ../src/sugar/activity/activity.py:126
+msgid "My Neighborhood"
+msgstr "Η γειτονιά μου"
+
+#: ../src/sugar/activity/activity.py:133
+#: ../src/sugar/activity/namingalert.py:65
+msgid "Keep"
+msgstr "Διατήρηση"
+
+#: ../src/sugar/activity/activity.py:144
+msgid "Stop"
+msgstr "Κλείσιμο"
+
+#: ../src/sugar/activity/activity.py:258
+msgid "Undo"
+msgstr "Αναίρεση"
+
+#: ../src/sugar/activity/activity.py:263
+msgid "Redo"
+msgstr "Ακύρωση αναίρεσης"
+
+#: ../src/sugar/activity/activity.py:273
+msgid "Copy"
+msgstr "Αντιγραφή"
+
+#: ../src/sugar/activity/activity.py:278
+msgid "Paste"
+msgstr "Επικόλληση"
+
+#: ../src/sugar/activity/activity.py:304
+msgid "Activity"
+msgstr "Δραστηριότητα"
+
+#: ../src/sugar/activity/activity.py:542
+#, python-format
+msgid "%s Activity"
+msgstr "%s Δραστηριότητα"
+
+#: ../src/sugar/activity/activity.py:910
+msgid "Keep error"
+msgstr "Το σφάλμα εξακολουθεί να υφίσταται"
+
+#: ../src/sugar/activity/activity.py:911
+msgid "Keep error: all changes will be lost"
+msgstr "Το σφάλμα εξακολουθεί να υφίσταται: όλες οι αλλαγές θα χαθούν"
+
+#: ../src/sugar/activity/activity.py:914
+msgid "Don't stop"
+msgstr "Αδυναμία κλεισίματος"
+
+#: ../src/sugar/activity/activity.py:917
+msgid "Stop anyway"
+msgstr "Κλείσιμο παρ' όλα αυτά"
+
+#: ../src/sugar/activity/namingalert.py:60
+msgid "Name this entry"
+msgstr "Ονομάστε αυτή την καταχώρηση"
+
+#: ../src/sugar/activity/namingalert.py:248
+msgid "Untitled"
+msgstr "Χωρίς τίτλο"
+
+#: ../src/sugar/activity/namingalert.py:255
+msgid "Description:"
+msgstr "Περιγραφή"
+
+#: ../src/sugar/activity/namingalert.py:279
+msgid "Tags:"
+msgstr "Ετικέτες:"
+
+#: ../src/sugar/graphics/alert.py:288 ../src/sugar/graphics/alert.py:367
+msgid "Cancel"
+msgstr "Άκυρο"
+
+#: ../src/sugar/graphics/alert.py:292 ../src/sugar/graphics/alert.py:426
+msgid "Ok"
+msgstr "Εντάξει"
+
+#: ../src/sugar/graphics/alert.py:377
+msgid "Continue"
+msgstr "Συνέχεια"
+
+#: ../src/sugar/graphics/colorbutton.py:49
+msgid "Choose a color"
+msgstr "Επιλογή χρώματος"
+
+#: ../src/sugar/graphics/colorbutton.py:262
+msgid "Red"
+msgstr "Κόκκινο"
+
+#: ../src/sugar/graphics/colorbutton.py:264
+msgid "Green"
+msgstr "Πράσινο"
+
+#: ../src/sugar/graphics/colorbutton.py:266
+msgid "Blue"
+msgstr "Μπλε"
+
+#: ../src/sugar/util.py:194
+msgid " and "
+msgstr " και "
+
+#: ../src/sugar/util.py:195
+msgid ", "
+msgstr ", "
+
+# TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:198
+msgid "Seconds ago"
+msgstr "Δευτερόλεπτα πριν"
+
+# TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+# "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%s ago"
+msgstr "%s πριν"
+
+# TRANS: Relative dates (eg. 1 month and 5 days).
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:215
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] "%d έτος"
+msgstr[1] "%d έτη"
+
+#: ../src/sugar/util.py:216
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] "%d μήνας"
+msgstr[1] "%d μήνες"
+
+#: ../src/sugar/util.py:217
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] "%d εβδομάδα"
+msgstr[1] "%d εβδομάδες"
+
+#: ../src/sugar/util.py:218
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] "%d ημέρα"
+msgstr[1] "%d ημέρες"
+
+#: ../src/sugar/util.py:219
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] "%d ώρα"
+msgstr[1] "%d ώρες"
+
+#: ../src/sugar/util.py:220
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] "%d λεπτό"
+msgstr[1] "%d λεπτά"
diff --git a/toolkit/po/en.po b/toolkit/po/en.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/toolkit/po/en.po
@@ -0,0 +1,153 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/es.po b/toolkit/po/es.po
new file mode 100644
index 0000000..183a54e
--- /dev/null
+++ b/toolkit/po/es.po
@@ -0,0 +1,691 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: olpc-sugar\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-01-20 13:10-0500\n"
+"PO-Revision-Date: 2010-03-11 23:18+0200\n"
+"Last-Translator: Roger Orellana <rjorellana@gmail.com>\n"
+"Language-Team: Fedora Spanish <fedora-trans-es@redhat.com>\n"
+"Language: es\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Pootle 2.0.1\n"
+"X-Poedit-Language: Spanish\n"
+"X-Poedit-SourceCharset: utf-8\n"
+"X-Poedit-Basepath: .\n"
+
+#: ../src/sugar/activity/activity.py:338
+#, python-format
+msgid "%s Activity"
+msgstr "Actividad %s"
+
+#: ../src/sugar/activity/activity.py:738
+msgid "Keep error"
+msgstr "Error al guardar"
+
+#: ../src/sugar/activity/activity.py:739
+msgid "Keep error: all changes will be lost"
+msgstr "Error al guardar: todos los cambios se perderán"
+
+#: ../src/sugar/activity/activity.py:742
+msgid "Don't stop"
+msgstr "No detener"
+
+#: ../src/sugar/activity/activity.py:745
+msgid "Stop anyway"
+msgstr "Detener de todas formas"
+
+#: ../src/sugar/activity/namingalert.py:82
+msgid "Name this entry"
+msgstr "Nombre esta entrada"
+
+# self._stop_item = MenuItem(_('Stop download'), 'stock-close')
+# TODO: Implement stopping downloads
+# self._stop_item.connect('activate', self._stop_item_activate_cb)
+# self.append_menu_item(self._stop_item)
+#: ../src/sugar/activity/namingalert.py:87
+#: ../src/sugar/activity/widgets.py:166
+msgid "Keep"
+msgstr "Guardar"
+
+#: ../src/sugar/activity/namingalert.py:283
+msgid "Untitled"
+msgstr "Sin título"
+
+#: ../src/sugar/activity/namingalert.py:290
+msgid "Description:"
+msgstr "Descripción:"
+
+#: ../src/sugar/activity/namingalert.py:314
+msgid "Tags:"
+msgstr "Etiquetas:"
+
+#: ../src/sugar/activity/widgets.py:83
+msgid "Stop"
+msgstr "Parar"
+
+#: ../src/sugar/activity/widgets.py:95
+msgid "Undo"
+msgstr "Deshacer"
+
+#: ../src/sugar/activity/widgets.py:103
+msgid "Redo"
+msgstr "Rehacer"
+
+#: ../src/sugar/activity/widgets.py:110
+msgid "Copy"
+msgstr "Copiar"
+
+#: ../src/sugar/activity/widgets.py:117
+msgid "Paste"
+msgstr "Pegar"
+
+#: ../src/sugar/activity/widgets.py:127
+msgid "Private"
+msgstr "Privado"
+
+#: ../src/sugar/activity/widgets.py:134
+msgid "My Neighborhood"
+msgstr "Mi Vecindario"
+
+#: ../src/sugar/activity/widgets.py:345
+msgid "Activity"
+msgstr "Actividad"
+
+#: ../src/sugar/graphics/alert.py:286 ../src/sugar/graphics/alert.py:365
+msgid "Cancel"
+msgstr "Cancelar"
+
+#: ../src/sugar/graphics/alert.py:290 ../src/sugar/graphics/alert.py:424
+msgid "Ok"
+msgstr "Aceptar"
+
+#: ../src/sugar/graphics/alert.py:375
+msgid "Continue"
+msgstr "Continuar"
+
+#: ../src/sugar/graphics/colorbutton.py:52
+msgid "Choose a color"
+msgstr "Escoja un color"
+
+#: ../src/sugar/graphics/colorbutton.py:272
+msgid "Red"
+msgstr "Rojo"
+
+#: ../src/sugar/graphics/colorbutton.py:274
+msgid "Green"
+msgstr "Verde"
+
+#: ../src/sugar/graphics/colorbutton.py:276
+msgid "Blue"
+msgstr "Azul"
+
+#: ../src/sugar/util.py:218
+msgid " and "
+msgstr " y "
+
+#: ../src/sugar/util.py:219
+msgid ", "
+msgstr ", "
+
+# TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:222
+msgid "Seconds ago"
+msgstr "Segundos atrás"
+
+# I used an expression, not a literal translation, but I think it's OK.
+# TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+# "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:226
+#, python-format
+msgid "%s ago"
+msgstr "%s atrás"
+
+# No entiendo porque colocaron el plural igual que el singular.
+# Traduction: I don't know why somebody wrote the same for plural and singular traduction.
+# TRANS: Relative dates (eg. 1 month and 5 days).
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:241
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] "%d año"
+msgstr[1] "%d años"
+
+#: ../src/sugar/util.py:242
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] "%d mes"
+msgstr[1] "%d meses"
+
+#: ../src/sugar/util.py:243
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] "%d semana"
+msgstr[1] "%d semanas"
+
+#: ../src/sugar/util.py:244
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] "%d día"
+msgstr[1] "%d días"
+
+#: ../src/sugar/util.py:245
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] "%d hora"
+msgstr[1] "%d horas"
+
+#: ../src/sugar/util.py:246
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] "%d minuto"
+msgstr[1] "%d minutos"
+
+#: ../src/sugar/util.py:339
+msgid "Empty"
+msgstr "Vacio"
+
+#: ../src/sugar/util.py:341
+#, python-format
+msgid "%d B"
+msgstr "%d B"
+
+#: ../src/sugar/util.py:343
+#, python-format
+msgid "%d KB"
+msgstr "%d KB"
+
+#: ../src/sugar/util.py:345
+#, python-format
+msgid "%d MB"
+msgstr "%d MB"
+
+#: ../src/sugar/util.py:347
+#, python-format
+msgid "%d GB"
+msgstr "%d GB"
+
+#~ msgid "Share with:"
+#~ msgstr "Compartir con:"
+
+#~ msgid "Name:"
+#~ msgstr "Nombre:"
+
+#~ msgid "Click to change color:"
+#~ msgstr "Clic para cambiar de color:"
+
+#~ msgid "Back"
+#~ msgstr "Atrás"
+
+#~ msgid "Done"
+#~ msgstr "Hecho"
+
+#~ msgid "Next"
+#~ msgstr "Siguiente"
+
+#~ msgid "Remove friend"
+#~ msgstr "Eliminar amigo"
+
+#~ msgid "Make friend"
+#~ msgstr "Agregar amigo"
+
+#~ msgid "Invite to %s"
+#~ msgstr "invitar a %s"
+
+#~ msgid "Remove"
+#~ msgstr "Eliminar"
+
+#~ msgid "Open"
+#~ msgstr "Abrir"
+
+#~ msgid "Open with"
+#~ msgstr "Abrir con"
+
+#~ msgid "Clipboard object: %s."
+#~ msgstr "Objeto de portapapel: %s."
+
+#~ msgid "Key Type:"
+#~ msgstr "Tipo de Tecla"
+
+#~ msgid "Authentication Type:"
+#~ msgstr "Tipo de Autenticación:"
+
+#~ msgid "Encryption Type:"
+#~ msgstr "Tipo de Encriptación:"
+
+#~ msgid "Screenshot"
+#~ msgstr "Captura de pantalla"
+
+#~ msgid "List view"
+#~ msgstr "Vista en lista"
+
+#~ msgid "<Ctrl>L"
+#~ msgstr "<Ctrl>L"
+
+#~ msgid "<Ctrl>R"
+#~ msgstr "<Ctrl>R"
+
+#~ msgid "Connect"
+#~ msgstr "Conectar"
+
+#~ msgid "Disconnect"
+#~ msgstr "Desconectar"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#, fuzzy
+#~ msgid "Disconnecting..."
+#~ msgstr "Desconectando..."
+
+#~ msgid "Connecting..."
+#~ msgstr "Conectando..."
+
+# TODO: show the channel number
+#~ msgid "Connected"
+#~ msgstr "Conectado"
+
+#~ msgid "Mesh Network"
+#~ msgstr "Red Malla"
+
+# Only show disconnect when there's a mesh device, because mesh takes
+# priority over the normal wireless device. NM doesn't have a "disconnect"
+# method for a device either (for various reasons) so this doesn't
+# have a good mapping
+#~ msgid "Disconnect..."
+#~ msgstr "Desconectando..."
+
+#~ msgid "Resume"
+#~ msgstr "Resumir"
+
+#~ msgid "Join"
+#~ msgstr "Unirse"
+
+#~ msgid "My Battery"
+#~ msgstr "Mi batería"
+
+#~ msgid "Charging"
+#~ msgstr "Cargando"
+
+#~ msgid "Very little power remaining"
+#~ msgstr "Queda muy poca batería"
+
+#~ msgid "%(hour)d:%(min).2d remaining"
+#~ msgstr "Quedan %(hour)d:%(min).2d"
+
+#~ msgid "Charged"
+#~ msgstr "Cargada"
+
+#~ msgid "My Speakers"
+#~ msgstr "Mis parlantes"
+
+# la traducción la tome del AlsaMixer de Gnome.
+#, fuzzy
+#~ msgid "Unmute"
+#~ msgstr "Dar voz"
+
+#~ msgid "Mute"
+#~ msgstr "Silenciar"
+
+#~ msgid "Disconnected"
+#~ msgstr "Desconectado"
+
+#~ msgid "Channel"
+#~ msgstr "Canal"
+
+#~ msgid "Neighborhood"
+#~ msgstr "Vecindario"
+
+#~ msgid "Group"
+#~ msgstr "Grupo"
+
+#~ msgid "Home"
+#~ msgstr "Hogar"
+
+#~ msgid ""
+#~ "sugar-control-panel: WARNING, found more than one option with the same "
+#~ "name: %s module: %r"
+#~ msgstr ""
+#~ "sugar-control-panel: ADVERTENCIA, hay más de una opción con el mismo "
+#~ "nombre: %s módulo: %r"
+
+#~ msgid "sugar-control-panel: key=%s not an available option"
+#~ msgstr "sugar-control-panel: clave=%s no es una opción disponible"
+
+#~ msgid "sugar-control-panel: %s"
+#~ msgstr "sugar-control-panel: %s"
+
+#~ msgid ""
+#~ "Usage: sugar-control-panel [ option ] key [ args ... ] \n"
+#~ " Control for the sugar environment. \n"
+#~ " Options: \n"
+#~ " -h show this help message and exit \n"
+#~ " -l list all the available options \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"
+#~ " "
+#~ msgstr ""
+#~ "Uso: sugar-control-panel [opción] clave [args ...] \n"
+#~ " Control para el ambiente de sugar. \n"
+#~ " Opciones: \n"
+#~ " -h muestra este mensaje de ayuda y sale \n"
+#~ " -l enumera todas las opciones disponibles \n"
+#~ " -h clave muestra la información sobre esta clave \n"
+#~ " -g clave obtiene el valor actual de la clave \n"
+#~ " -s clave establece el valor actual para la clave \n"
+#~ " "
+
+#~ msgid "To apply your changes you have to restart sugar.\n"
+#~ msgstr "Para aplicar sus cambios tiene que reiniciar sugar.\n"
+
+#~ msgid "Changes require restart"
+#~ msgstr "Los cambios requieren reiniciar"
+
+#~ msgid "Warning"
+#~ msgstr "Advertencia"
+
+#~ msgid "Cancel changes"
+#~ msgstr "Cancelar cambios"
+
+#~ msgid "Later"
+#~ msgstr "Después"
+
+#~ msgid "Restart now"
+#~ msgstr "Reiniciar ahora"
+
+#~ msgid "You must enter a name."
+#~ msgstr "Debe ingresar un nombre."
+
+#~ msgid "stroke: color=%s hue=%s"
+#~ msgstr "Borde: color=%s tonalidad=%s"
+
+#~ msgid "stroke: %s"
+#~ msgstr "Borde: %s"
+
+#~ msgid "fill: color=%s hue=%s"
+#~ msgstr "relleno: color=%s tonalidad=%s"
+
+#~ msgid "fill: %s"
+#~ msgstr "relleno: %s"
+
+#~ msgid "Error in specified color modifiers."
+#~ msgstr "Error en modificadores de color especificados."
+
+#~ msgid "Error in specified colors."
+#~ msgstr "Error en colores especificados."
+
+#~ msgid "Not available"
+#~ msgstr "No disponible"
+
+#~ msgid "Error timezone does not exist."
+#~ msgstr "Error, zona horaria no existe."
+
+#, fuzzy
+#~ msgid "Value must be an integer."
+#~ msgstr "El valor debe ser un entero."
+
+#, fuzzy
+#~ msgid "Could not access ~/.i18n. Create standard settings."
+#~ msgstr "No se puede acceder a ~/.i18n. Crear ajustes estándar."
+
+#~ msgid "Language for code=%s could not be determined."
+#~ msgstr "El lenguaje del código=%s no pudo ser determinado."
+
+#~ msgid "Sorry I do not speak '%s'."
+#~ msgstr "Lo siento yo no hablo '%s'."
+
+#~ msgid "You must enter a server."
+#~ msgstr "Debe ingresar un servidor"
+
+#~ msgid "State is unknown."
+#~ msgstr "Estado desconocido."
+
+#~ msgid "Error in specified radio argument use on/off."
+#~ msgstr "Error en argumento especificado de radio use on/off."
+
+#~ msgid "About Me"
+#~ msgstr "Acerca de mí."
+
+#, fuzzy
+#~ msgid "Click to change your color:"
+#~ msgstr "Clic para cambiar de color:"
+
+#~ msgid "About my XO"
+#~ msgstr "Acerca de mi XO"
+
+#~ msgid "Identity"
+#~ msgstr "Identidad"
+
+#~ msgid "Serial Number:"
+#~ msgstr "Número de Serie:"
+
+#~ msgid "Software"
+#~ msgstr "Software"
+
+#, fuzzy
+#~ msgid "Build:"
+#~ msgstr "Ensamble"
+
+#~ msgid "Firmware:"
+#~ msgstr "Firmware"
+
+#~ msgid "Date & Time"
+#~ msgstr "Fecha y Hora"
+
+#~ msgid "Timezone"
+#~ msgstr "Zona horaria"
+
+#~ msgid "Frame"
+#~ msgstr "Cuadro"
+
+#~ msgid "never"
+#~ msgstr "nunca"
+
+#~ msgid "instantaneous"
+#~ msgstr "instantáneo"
+
+#, fuzzy
+#~ msgid "%s seconds"
+#~ msgstr "%s segundos"
+
+#~ msgid "Activation Delay"
+#~ msgstr "Activación del retraso"
+
+#~ msgid "Corner"
+#~ msgstr "Esquina"
+
+#~ msgid "Edge"
+#~ msgstr "Borde"
+
+#~ msgid "Language"
+#~ msgstr "Idioma"
+
+#~ msgid "Network"
+#~ msgstr "Red"
+
+#~ msgid "Wireless"
+#~ msgstr "Inalámbrica"
+
+#~ msgid "Radio:"
+#~ msgstr "Radio:"
+
+#~ msgid "Mesh"
+#~ msgstr "Malla"
+
+#~ msgid "Server:"
+#~ msgstr "Servidor:"
+
+#, fuzzy
+#~ msgid "Connected to a School Mesh Portal"
+#~ msgstr "Conectado a un portal malla de colegio"
+
+# "portal malla de colegio", en Castellano de España suena fatal... ¿Realmente se quiere decir malla?
+#, fuzzy
+#~ msgid "Looking for a School Mesh Portal..."
+#~ msgstr "Buscando un portal malla de colegio..."
+
+#, fuzzy
+#~ msgid "Connected to an XO Mesh Portal"
+#~ msgstr "Conectado a un portal malla XO"
+
+#, fuzzy
+#~ msgid "Looking for an XO Mesh Portal..."
+#~ msgstr "Buscando un portal malla XO..."
+
+#, fuzzy
+#~ msgid "Connected to a Simple Mesh"
+#~ msgstr "Conectado a una Malla Simple"
+
+#, fuzzy
+#~ msgid "Starting a Simple Mesh"
+#~ msgstr "Empezando una Malla Simple"
+
+#, fuzzy
+#~ msgid "Unknown Mesh"
+#~ msgstr "Malla Desconocida"
+
+#~ msgid "Decline"
+#~ msgstr "Rechazar"
+
+#~ msgid "Control Panel"
+#~ msgstr "Panel de Control"
+
+#~ msgid "Restart"
+#~ msgstr "Reiniciar"
+
+#~ msgid "Shutdown"
+#~ msgstr "Apagar"
+
+#~ msgid "Register"
+#~ msgstr "Registro"
+
+#~ msgid "Starting..."
+#~ msgstr "Iniciando..."
+
+#~ msgid "Start"
+#~ msgstr "Iniciar"
+
+#~ msgid "Show contents"
+#~ msgstr "Mostrar contenidos"
+
+#~ msgid "%(free_space)d MB Free"
+#~ msgstr "%(free_space)d MB libres"
+
+#, fuzzy
+#~ msgid "Ring view"
+#~ msgstr "Vista de llamada"
+
+#~ msgid "Remove from ring"
+#~ msgstr "Eliminar del anillo"
+
+#~ msgid "Add to ring"
+#~ msgstr "Agregar al anillo"
+
+#~ msgid "Changes require a sugar restart to take effect."
+#~ msgstr "Los cambios requieren reiniciar sugar para ser efectivos."
+
+#~ msgid "Changes require restart to take effect"
+#~ msgstr "Los cambios requieren reiniciar para ser efectivos"
+
+#~ msgid "Delay in milliseconds:"
+#~ msgstr "Retraso en milisegundos:"
+
+#~ msgid "Hot Corners"
+#~ msgstr "Esquinas Activas"
+
+#~ msgid "Warm Edges"
+#~ msgstr "Bordes Activos"
+
+#~ msgid "off"
+#~ msgstr "apagado"
+
+#~ msgid "on"
+#~ msgstr "encendido"
+
+#~ msgid "Permission denied. You need to be root to run this method."
+#~ msgstr ""
+#~ "permiso denegado. Usted necesita ser root para ejecutar este método."
+
+#~ msgid "Error in reading timezone"
+#~ msgstr "Error en la lectura de la zona horaria"
+
+#~ msgid "Error copying timezone (from %s): %s"
+#~ msgstr "Error copiando zona horaria (desde %s): %s"
+
+#~ msgid "Changing permission of timezone: %s"
+#~ msgstr "Cambiando permisos de zona horaria: %s"
+
+#~ msgid "About this XO"
+#~ msgstr "Acerca de este XO"
+
+#~ msgid "Add to journal"
+#~ msgstr "Agregar al diario"
+
+#~ msgid "Reboot"
+#~ msgstr "Reiniciar"
+
+#~ msgid "My Battery life"
+#~ msgstr "Carga de mi batería"
+
+#~ msgid "Battery charging"
+#~ msgstr "Batería cargándose"
+
+#~ msgid "Battery discharging"
+#~ msgstr "Batería descargandose"
+
+#~ msgid "Battery fully charged"
+#~ msgstr "Batería totalmente cargada"
+
+#~ msgid "Invite"
+#~ msgstr "Invitar"
+
+#~ msgid "Text"
+#~ msgstr "Texto"
+
+#~ msgid "Image"
+#~ msgstr "Imagen"
+
+#~ msgid "Audio"
+#~ msgstr "Audio"
+
+#~ msgid "Video"
+#~ msgstr "Video"
+
+#~ msgid "Etoys project"
+#~ msgstr "Proyecto Etoys"
+
+#~ msgid "Link"
+#~ msgstr "Enlace"
+
+#~ msgid ""
+#~ "Text snippetWeb PagePDF fileMS Word fileRTF fileAbiword fileSqueak "
+#~ "projectOpenOffice text fileObjectPick a buddy pictureMy Picture:My Color:"
+#~ "Stop downloadCloseNo optionsSend"
+#~ msgstr ""
+#~ "Recorte de textoPágina webArchivo PDFArchivo MS-WordArchivo RTFArchivo "
+#~ "AbiwordProyecto de SqueakArchivo de texto de OpenOfficeObjetoElegir la "
+#~ "imagen de amigoMi imagen:Mi color:Interrumpir la bajadaCerrarNinguna "
+#~ "opciónEnviar"
+
+#~ msgid "OK"
+#~ msgstr "OK"
+
+#~ msgid "%d second"
+#~ msgstr "%d segundo"
diff --git a/toolkit/po/fa.po b/toolkit/po/fa.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/toolkit/po/fa.po
@@ -0,0 +1,153 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/fa_AF.po b/toolkit/po/fa_AF.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/toolkit/po/fa_AF.po
@@ -0,0 +1,153 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/ff.po b/toolkit/po/ff.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/toolkit/po/ff.po
@@ -0,0 +1,153 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/fil.po b/toolkit/po/fil.po
new file mode 100644
index 0000000..6179aa0
--- /dev/null
+++ b/toolkit/po/fil.po
@@ -0,0 +1,186 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-01-20 00:31-0500\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:123
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:125
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:126
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:133
+#: ../src/sugar/activity/namingalert.py:65
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:144
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:258
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:263
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:273
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:278
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:304
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:542
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:910
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:911
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:914
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:917
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/activity/namingalert.py:60
+msgid "Name this entry"
+msgstr ""
+
+#: ../src/sugar/activity/namingalert.py:248
+msgid "Untitled"
+msgstr ""
+
+#: ../src/sugar/activity/namingalert.py:255
+msgid "Description:"
+msgstr ""
+
+#: ../src/sugar/activity/namingalert.py:279
+msgid "Tags:"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:288 ../src/sugar/graphics/alert.py:367
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:292 ../src/sugar/graphics/alert.py:426
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:377
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/graphics/colorbutton.py:49
+msgid "Choose a color"
+msgstr ""
+
+#: ../src/sugar/graphics/colorbutton.py:262
+msgid "Red"
+msgstr ""
+
+#: ../src/sugar/graphics/colorbutton.py:264
+msgid "Green"
+msgstr ""
+
+#: ../src/sugar/graphics/colorbutton.py:266
+msgid "Blue"
+msgstr ""
+
+#: ../src/sugar/util.py:194
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:195
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:198
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:215
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:216
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:217
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:218
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:219
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:220
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/fr.po b/toolkit/po/fr.po
new file mode 100644
index 0000000..3e6b397
--- /dev/null
+++ b/toolkit/po/fr.po
@@ -0,0 +1,214 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-08-26 00:33-0400\n"
+"PO-Revision-Date: 2009-09-01 12:17-0400\n"
+"Last-Translator: samy boutayeb <s.boutayeb@free.fr>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: fr\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+"X-Generator: Pootle 1.2.1\n"
+
+#: ../src/sugar/activity/activity.py:329
+#, python-format
+msgid "%s Activity"
+msgstr "Activité %s"
+
+#: ../src/sugar/activity/activity.py:714
+msgid "Keep error"
+msgstr "Erreur d'enregistrement"
+
+#: ../src/sugar/activity/activity.py:715
+msgid "Keep error: all changes will be lost"
+msgstr "Erreur d'enregistrement : toutes les modifications seront perdues"
+
+#: ../src/sugar/activity/activity.py:718
+msgid "Don't stop"
+msgstr "Ne pas arrêter"
+
+#: ../src/sugar/activity/activity.py:721
+msgid "Stop anyway"
+msgstr "Arrêter quand même"
+
+#: ../src/sugar/activity/namingalert.py:82
+msgid "Name this entry"
+msgstr "Donner un nom à cette entrée"
+
+#: ../src/sugar/activity/namingalert.py:87
+#: ../src/sugar/activity/widgets.py:162
+msgid "Keep"
+msgstr "Conserver"
+
+#: ../src/sugar/activity/namingalert.py:283
+msgid "Untitled"
+msgstr "Sans titre"
+
+#: ../src/sugar/activity/namingalert.py:290
+msgid "Description:"
+msgstr "Description :"
+
+#: ../src/sugar/activity/namingalert.py:314
+msgid "Tags:"
+msgstr "Étiquettes :"
+
+#: ../src/sugar/activity/widgets.py:79
+msgid "Stop"
+msgstr "Arrêter"
+
+#: ../src/sugar/activity/widgets.py:91
+msgid "Undo"
+msgstr "Annuler"
+
+#: ../src/sugar/activity/widgets.py:99
+msgid "Redo"
+msgstr "Répéter"
+
+#: ../src/sugar/activity/widgets.py:106
+msgid "Copy"
+msgstr "Copier"
+
+#: ../src/sugar/activity/widgets.py:113
+msgid "Paste"
+msgstr "Coller"
+
+#: ../src/sugar/activity/widgets.py:123
+msgid "Private"
+msgstr "Privé"
+
+#: ../src/sugar/activity/widgets.py:130
+msgid "My Neighborhood"
+msgstr "Mon voisinage"
+
+#: ../src/sugar/activity/widgets.py:341
+msgid "Activity"
+msgstr "Activité"
+
+#: ../src/sugar/graphics/alert.py:286 ../src/sugar/graphics/alert.py:365
+msgid "Cancel"
+msgstr "Annuler"
+
+#: ../src/sugar/graphics/alert.py:290 ../src/sugar/graphics/alert.py:424
+msgid "Ok"
+msgstr "Ok"
+
+#: ../src/sugar/graphics/alert.py:375
+msgid "Continue"
+msgstr "Continuer"
+
+#: ../src/sugar/graphics/colorbutton.py:52
+msgid "Choose a color"
+msgstr "Choisir une couleur"
+
+#: ../src/sugar/graphics/colorbutton.py:272
+msgid "Red"
+msgstr "Rouge"
+
+#: ../src/sugar/graphics/colorbutton.py:274
+msgid "Green"
+msgstr "Vert"
+
+#: ../src/sugar/graphics/colorbutton.py:276
+msgid "Blue"
+msgstr "Bleu"
+
+#: ../src/sugar/util.py:218
+msgid " and "
+msgstr " et "
+
+#: ../src/sugar/util.py:219
+msgid ", "
+msgstr ", "
+
+# TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:222
+msgid "Seconds ago"
+msgstr "À l'instant"
+
+# TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+# "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:226
+#, python-format
+msgid "%s ago"
+msgstr "il y a %s"
+
+# TRANS: Relative dates (eg. 1 month and 5 days).
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:241
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] "%d an"
+msgstr[1] "%d ans"
+
+#: ../src/sugar/util.py:242
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] "%d mois"
+msgstr[1] "%d mois"
+
+#: ../src/sugar/util.py:243
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] "%d semaine"
+msgstr[1] "%d semaines"
+
+#: ../src/sugar/util.py:244
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] "%d jour"
+msgstr[1] "%d jours"
+
+#: ../src/sugar/util.py:245
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] "%d heure"
+msgstr[1] "%d heures"
+
+#: ../src/sugar/util.py:246
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] "%d minute"
+msgstr[1] "%d minutes"
+
+#: ../src/sugar/util.py:339
+msgid "Empty"
+msgstr "Vider"
+
+#: ../src/sugar/util.py:341
+#, python-format
+msgid "%d B"
+msgstr "%d o"
+
+#: ../src/sugar/util.py:343
+#, python-format
+msgid "%d KB"
+msgstr "%d Ko"
+
+#: ../src/sugar/util.py:345
+#, python-format
+msgid "%d MB"
+msgstr "%d Mo"
+
+#: ../src/sugar/util.py:347
+#, python-format
+msgid "%d GB"
+msgstr "%d Go"
+
+#~ msgid "Share with:"
+#~ msgstr "Partager avec:"
diff --git a/toolkit/po/gu.po b/toolkit/po/gu.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/toolkit/po/gu.po
@@ -0,0 +1,153 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/ha.po b/toolkit/po/ha.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/toolkit/po/ha.po
@@ -0,0 +1,153 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/he.po b/toolkit/po/he.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/toolkit/po/he.po
@@ -0,0 +1,153 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/hi.po b/toolkit/po/hi.po
new file mode 100644
index 0000000..321676a
--- /dev/null
+++ b/toolkit/po/hi.po
@@ -0,0 +1,206 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-01-20 13:10-0500\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:338
+#, python-format
+msgid "%s Activity"
+msgstr "%s क्रियाएँ"
+
+#: ../src/sugar/activity/activity.py:738
+msgid "Keep error"
+msgstr "त्रुटि को रखे रहें"
+
+#: ../src/sugar/activity/activity.py:739
+msgid "Keep error: all changes will be lost"
+msgstr "त्रुटि को रखे रहें: सारे परिवर्तन खो जाएँगे"
+
+#: ../src/sugar/activity/activity.py:742
+msgid "Don't stop"
+msgstr "बन्द न करें"
+
+#: ../src/sugar/activity/activity.py:745
+msgid "Stop anyway"
+msgstr "जैसे भी हो बन्द करें"
+
+#: ../src/sugar/activity/namingalert.py:82
+msgid "Name this entry"
+msgstr "इस प्रविष्टि को नाम दें"
+
+#: ../src/sugar/activity/namingalert.py:87
+#: ../src/sugar/activity/widgets.py:166
+msgid "Keep"
+msgstr "रखें"
+
+#: ../src/sugar/activity/namingalert.py:283
+msgid "Untitled"
+msgstr "बिना शीर्षक"
+
+#: ../src/sugar/activity/namingalert.py:290
+msgid "Description:"
+msgstr "वर्णन:"
+
+#: ../src/sugar/activity/namingalert.py:314
+msgid "Tags:"
+msgstr "टैग्स:"
+
+#: ../src/sugar/activity/widgets.py:83
+msgid "Stop"
+msgstr "रूकें"
+
+#: ../src/sugar/activity/widgets.py:95
+msgid "Undo"
+msgstr "पहले जैसा"
+
+#: ../src/sugar/activity/widgets.py:103
+msgid "Redo"
+msgstr "दोहराएँ"
+
+#: ../src/sugar/activity/widgets.py:110
+msgid "Copy"
+msgstr "नक़ल"
+
+#: ../src/sugar/activity/widgets.py:117
+msgid "Paste"
+msgstr "चिपकाएँ"
+
+#: ../src/sugar/activity/widgets.py:127
+msgid "Private"
+msgstr "निजी"
+
+#: ../src/sugar/activity/widgets.py:134
+msgid "My Neighborhood"
+msgstr "मेरा पड़ोस"
+
+#: ../src/sugar/activity/widgets.py:345
+msgid "Activity"
+msgstr "क्रियाएँ"
+
+#: ../src/sugar/graphics/alert.py:286 ../src/sugar/graphics/alert.py:365
+msgid "Cancel"
+msgstr "रद्द करें"
+
+#: ../src/sugar/graphics/alert.py:290 ../src/sugar/graphics/alert.py:424
+msgid "Ok"
+msgstr "ठीक"
+
+#: ../src/sugar/graphics/alert.py:375
+msgid "Continue"
+msgstr "जारी रखें"
+
+#: ../src/sugar/graphics/colorbutton.py:52
+msgid "Choose a color"
+msgstr "रंग चुनें"
+
+#: ../src/sugar/graphics/colorbutton.py:272
+msgid "Red"
+msgstr "लाल"
+
+#: ../src/sugar/graphics/colorbutton.py:274
+msgid "Green"
+msgstr "हरा"
+
+#: ../src/sugar/graphics/colorbutton.py:276
+msgid "Blue"
+msgstr "नीला"
+
+#: ../src/sugar/util.py:218
+msgid " and "
+msgstr " और"
+
+#: ../src/sugar/util.py:219
+msgid ", "
+msgstr ","
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:222
+msgid "Seconds ago"
+msgstr "सेकण्ड पहले"
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:226
+#, python-format
+msgid "%s ago"
+msgstr "%s पहले"
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:241
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] "%d वर्ष"
+msgstr[1] "%d वर्ष"
+
+#: ../src/sugar/util.py:242
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] "%d माह"
+msgstr[1] "%d माह"
+
+#: ../src/sugar/util.py:243
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] "%d सप्ताह"
+msgstr[1] "%d सप्ताह"
+
+#: ../src/sugar/util.py:244
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] "%d दिन"
+msgstr[1] "%d दिन"
+
+#: ../src/sugar/util.py:245
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] "%d घंटा"
+msgstr[1] "%d घंटा"
+
+#: ../src/sugar/util.py:246
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] "%d मिनट"
+msgstr[1] "%d मिनट"
+
+#: ../src/sugar/util.py:339
+msgid "Empty"
+msgstr "रिक्त"
+
+#: ../src/sugar/util.py:341
+#, python-format
+msgid "%d B"
+msgstr "%d बा."
+
+#: ../src/sugar/util.py:343
+#, python-format
+msgid "%d KB"
+msgstr "%d कि.बा."
+
+#: ../src/sugar/util.py:345
+#, python-format
+msgid "%d MB"
+msgstr "%d मे.बा."
+
+#: ../src/sugar/util.py:347
+#, python-format
+msgid "%d GB"
+msgstr "%d गी.बा."
diff --git a/toolkit/po/ht.po b/toolkit/po/ht.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/toolkit/po/ht.po
@@ -0,0 +1,153 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/hu.po b/toolkit/po/hu.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/toolkit/po/hu.po
@@ -0,0 +1,153 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/id.po b/toolkit/po/id.po
new file mode 100644
index 0000000..6179aa0
--- /dev/null
+++ b/toolkit/po/id.po
@@ -0,0 +1,186 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-01-20 00:31-0500\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:123
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:125
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:126
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:133
+#: ../src/sugar/activity/namingalert.py:65
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:144
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:258
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:263
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:273
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:278
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:304
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:542
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:910
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:911
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:914
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:917
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/activity/namingalert.py:60
+msgid "Name this entry"
+msgstr ""
+
+#: ../src/sugar/activity/namingalert.py:248
+msgid "Untitled"
+msgstr ""
+
+#: ../src/sugar/activity/namingalert.py:255
+msgid "Description:"
+msgstr ""
+
+#: ../src/sugar/activity/namingalert.py:279
+msgid "Tags:"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:288 ../src/sugar/graphics/alert.py:367
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:292 ../src/sugar/graphics/alert.py:426
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:377
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/graphics/colorbutton.py:49
+msgid "Choose a color"
+msgstr ""
+
+#: ../src/sugar/graphics/colorbutton.py:262
+msgid "Red"
+msgstr ""
+
+#: ../src/sugar/graphics/colorbutton.py:264
+msgid "Green"
+msgstr ""
+
+#: ../src/sugar/graphics/colorbutton.py:266
+msgid "Blue"
+msgstr ""
+
+#: ../src/sugar/util.py:194
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:195
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:198
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:215
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:216
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:217
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:218
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:219
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:220
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/ig.po b/toolkit/po/ig.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/toolkit/po/ig.po
@@ -0,0 +1,153 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/is.po b/toolkit/po/is.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/toolkit/po/is.po
@@ -0,0 +1,153 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/it.po b/toolkit/po/it.po
new file mode 100644
index 0000000..0013608
--- /dev/null
+++ b/toolkit/po/it.po
@@ -0,0 +1,219 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-01-20 13:10-0500\n"
+"PO-Revision-Date: 2009-09-01 12:12-0400\n"
+"Last-Translator: Carlo Falciola <cfalciola@yahoo.it>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: it\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Pootle 1.2.1\n"
+
+#: ../src/sugar/activity/activity.py:338
+#, python-format
+msgid "%s Activity"
+msgstr "Attività %s "
+
+#: ../src/sugar/activity/activity.py:738
+msgid "Keep error"
+msgstr "Errore di memorizzazione"
+
+#: ../src/sugar/activity/activity.py:739
+msgid "Keep error: all changes will be lost"
+msgstr "Errore di memorizzazione: tutte le modifiche saranno perse"
+
+#: ../src/sugar/activity/activity.py:742
+msgid "Don't stop"
+msgstr "Non Fermare"
+
+#: ../src/sugar/activity/activity.py:745
+msgid "Stop anyway"
+msgstr "Ferma comunque"
+
+#: ../src/sugar/activity/namingalert.py:82
+msgid "Name this entry"
+msgstr "Dai un nome a questo oggetto"
+
+#: ../src/sugar/activity/namingalert.py:87
+#: ../src/sugar/activity/widgets.py:166
+msgid "Keep"
+msgstr "Memorizza"
+
+#: ../src/sugar/activity/namingalert.py:283
+msgid "Untitled"
+msgstr "Senza nome"
+
+#: ../src/sugar/activity/namingalert.py:290
+msgid "Description:"
+msgstr "Descrizione:"
+
+#: ../src/sugar/activity/namingalert.py:314
+msgid "Tags:"
+msgstr "Etichette:"
+
+#: ../src/sugar/activity/widgets.py:83
+msgid "Stop"
+msgstr "Chiudi"
+
+#: ../src/sugar/activity/widgets.py:95
+msgid "Undo"
+msgstr "Annulla"
+
+#: ../src/sugar/activity/widgets.py:103
+msgid "Redo"
+msgstr "Ripeti"
+
+#: ../src/sugar/activity/widgets.py:110
+msgid "Copy"
+msgstr "Copia"
+
+#: ../src/sugar/activity/widgets.py:117
+msgid "Paste"
+msgstr "Incolla"
+
+#: ../src/sugar/activity/widgets.py:127
+msgid "Private"
+msgstr "Privato"
+
+#: ../src/sugar/activity/widgets.py:134
+msgid "My Neighborhood"
+msgstr "I miei vicini"
+
+#: ../src/sugar/activity/widgets.py:345
+msgid "Activity"
+msgstr "Attività"
+
+#: ../src/sugar/graphics/alert.py:286 ../src/sugar/graphics/alert.py:365
+msgid "Cancel"
+msgstr "Cancella"
+
+#: ../src/sugar/graphics/alert.py:290 ../src/sugar/graphics/alert.py:424
+msgid "Ok"
+msgstr "Ok"
+
+#: ../src/sugar/graphics/alert.py:375
+msgid "Continue"
+msgstr "Continua"
+
+#: ../src/sugar/graphics/colorbutton.py:52
+msgid "Choose a color"
+msgstr "Scegli un colore"
+
+#: ../src/sugar/graphics/colorbutton.py:272
+msgid "Red"
+msgstr "Rosso"
+
+#: ../src/sugar/graphics/colorbutton.py:274
+msgid "Green"
+msgstr "Verde"
+
+#: ../src/sugar/graphics/colorbutton.py:276
+msgid "Blue"
+msgstr "Blu"
+
+#: ../src/sugar/util.py:218
+msgid " and "
+msgstr " e "
+
+#: ../src/sugar/util.py:219
+msgid ", "
+msgstr ", "
+
+# TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:222
+msgid "Seconds ago"
+msgstr "Pochi secondi fa"
+
+# TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+# "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:226
+#, python-format
+msgid "%s ago"
+msgstr "%s fa"
+
+# True if Plural Form 1 means Singular.... (cf 2008_06_23)
+# TRANS: Relative dates (eg. 1 month and 5 days).
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:241
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] "%d anno"
+msgstr[1] "%d anni"
+
+# True if Plural Form 1 means Singular.... (cf 2008_06_23)
+#: ../src/sugar/util.py:242
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] "%d mese"
+msgstr[1] "%d mesi"
+
+# True if Plural Form 1 means Singular.... (cf 2008_06_23)
+#: ../src/sugar/util.py:243
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] "%d settimana"
+msgstr[1] "%d settimane"
+
+# True if Plural Form 1 means Singular.... (cf 2008_06_23)
+#: ../src/sugar/util.py:244
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] "%d giorno"
+msgstr[1] "%d giorni"
+
+# True if Plural Form 1 means Singular.... (cf 2008_06_23)
+#: ../src/sugar/util.py:245
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] "%d ora"
+msgstr[1] "%d ore"
+
+#: ../src/sugar/util.py:246
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] "%d minuto"
+msgstr[1] "%d minuti"
+
+#: ../src/sugar/util.py:339
+msgid "Empty"
+msgstr "Vuoto"
+
+#: ../src/sugar/util.py:341
+#, python-format
+msgid "%d B"
+msgstr "%d B"
+
+#: ../src/sugar/util.py:343
+#, python-format
+msgid "%d KB"
+msgstr "%d KB"
+
+#: ../src/sugar/util.py:345
+#, python-format
+msgid "%d MB"
+msgstr "%d MB"
+
+#: ../src/sugar/util.py:347
+#, python-format
+msgid "%d GB"
+msgstr "%d GB"
+
+#~ msgid "Share with:"
+#~ msgstr "Condividi con:"
diff --git a/toolkit/po/ja.po b/toolkit/po/ja.po
new file mode 100644
index 0000000..997570c
--- /dev/null
+++ b/toolkit/po/ja.po
@@ -0,0 +1,207 @@
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-01-20 13:10-0500\n"
+"PO-Revision-Date: 2009-09-14 08:00-0400\n"
+"Last-Translator: korakurider <korakurider@gmail.com>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: ja\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Pootle 1.2.1\n"
+
+#: ../src/sugar/activity/activity.py:338
+#, python-format
+msgid "%s Activity"
+msgstr "%s アクティビティ"
+
+#: ../src/sugar/activity/activity.py:738
+msgid "Keep error"
+msgstr "保存エラー"
+
+#: ../src/sugar/activity/activity.py:739
+msgid "Keep error: all changes will be lost"
+msgstr "保存エラー: 全ての変更は失われてしまいます"
+
+#: ../src/sugar/activity/activity.py:742
+msgid "Don't stop"
+msgstr "やめない"
+
+#: ../src/sugar/activity/activity.py:745
+msgid "Stop anyway"
+msgstr "とにかくやめる"
+
+#: ../src/sugar/activity/namingalert.py:82
+msgid "Name this entry"
+msgstr "これに名前をつける"
+
+#: ../src/sugar/activity/namingalert.py:87
+#: ../src/sugar/activity/widgets.py:166
+msgid "Keep"
+msgstr "ジャーナルに保存"
+
+#: ../src/sugar/activity/namingalert.py:283
+msgid "Untitled"
+msgstr "タイトル無し"
+
+#: ../src/sugar/activity/namingalert.py:290
+msgid "Description:"
+msgstr "説明:"
+
+#: ../src/sugar/activity/namingalert.py:314
+msgid "Tags:"
+msgstr "タグ:"
+
+#: ../src/sugar/activity/widgets.py:83
+msgid "Stop"
+msgstr "停止"
+
+#: ../src/sugar/activity/widgets.py:95
+msgid "Undo"
+msgstr "元に戻す"
+
+#: ../src/sugar/activity/widgets.py:103
+msgid "Redo"
+msgstr "やり直し"
+
+#: ../src/sugar/activity/widgets.py:110
+msgid "Copy"
+msgstr "コピー"
+
+#: ../src/sugar/activity/widgets.py:117
+msgid "Paste"
+msgstr "貼り付け"
+
+#: ../src/sugar/activity/widgets.py:127
+msgid "Private"
+msgstr "(共有しない)"
+
+#: ../src/sugar/activity/widgets.py:134
+msgid "My Neighborhood"
+msgstr "私のお隣さん"
+
+#: ../src/sugar/activity/widgets.py:345
+msgid "Activity"
+msgstr "アクティビティ"
+
+#: ../src/sugar/graphics/alert.py:286 ../src/sugar/graphics/alert.py:365
+msgid "Cancel"
+msgstr "中止"
+
+#: ../src/sugar/graphics/alert.py:290 ../src/sugar/graphics/alert.py:424
+msgid "Ok"
+msgstr "了解"
+
+#: ../src/sugar/graphics/alert.py:375
+msgid "Continue"
+msgstr "続ける"
+
+#: ../src/sugar/graphics/colorbutton.py:52
+msgid "Choose a color"
+msgstr "色を選ぶ"
+
+#: ../src/sugar/graphics/colorbutton.py:272
+msgid "Red"
+msgstr "赤"
+
+#: ../src/sugar/graphics/colorbutton.py:274
+msgid "Green"
+msgstr "緑"
+
+#: ../src/sugar/graphics/colorbutton.py:276
+msgid "Blue"
+msgstr "青"
+
+#: ../src/sugar/util.py:218
+msgid " and "
+msgstr " そして "
+
+#: ../src/sugar/util.py:219
+msgid ", "
+msgstr ", "
+
+# TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:222
+msgid "Seconds ago"
+msgstr "ちょっと前"
+
+# TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+# "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:226
+#, python-format
+msgid "%s ago"
+msgstr "%s前"
+
+# TRANS: Relative dates (eg. 1 month and 5 days).
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:241
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] "%d 年"
+
+#: ../src/sugar/util.py:242
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] "%d 月"
+
+#: ../src/sugar/util.py:243
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] "%d 週"
+
+#: ../src/sugar/util.py:244
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] "%d 日"
+
+#: ../src/sugar/util.py:245
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] "%d 時間"
+
+#: ../src/sugar/util.py:246
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] "%d 分"
+
+#: ../src/sugar/util.py:339
+msgid "Empty"
+msgstr "空"
+
+#: ../src/sugar/util.py:341
+#, python-format
+msgid "%d B"
+msgstr "%d B"
+
+#: ../src/sugar/util.py:343
+#, python-format
+msgid "%d KB"
+msgstr "%d KB"
+
+#: ../src/sugar/util.py:345
+#, python-format
+msgid "%d MB"
+msgstr "%d MB"
+
+#: ../src/sugar/util.py:347
+#, python-format
+msgid "%d GB"
+msgstr "%d GB"
+
+#~ msgid "Share with:"
+#~ msgstr "次の人と共有:"
diff --git a/toolkit/po/km.po b/toolkit/po/km.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/toolkit/po/km.po
@@ -0,0 +1,153 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/ko.po b/toolkit/po/ko.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/toolkit/po/ko.po
@@ -0,0 +1,153 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/kos.po b/toolkit/po/kos.po
new file mode 100644
index 0000000..5738513
--- /dev/null
+++ b/toolkit/po/kos.po
@@ -0,0 +1,207 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-08-26 00:33-0400\n"
+"PO-Revision-Date: 2009-09-02 21:21-0400\n"
+"Last-Translator: Chris Leonard <cjl@laptop.org>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: kos\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Pootle 1.2.1\n"
+
+#: ../src/sugar/activity/activity.py:329
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:714
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:715
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:718
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:721
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/activity/namingalert.py:82
+msgid "Name this entry"
+msgstr ""
+
+#: ../src/sugar/activity/namingalert.py:87
+#: ../src/sugar/activity/widgets.py:162
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/namingalert.py:283
+msgid "Untitled"
+msgstr ""
+
+#: ../src/sugar/activity/namingalert.py:290
+msgid "Description:"
+msgstr ""
+
+#: ../src/sugar/activity/namingalert.py:314
+msgid "Tags:"
+msgstr ""
+
+#: ../src/sugar/activity/widgets.py:79
+msgid "Stop"
+msgstr "Tui"
+
+#: ../src/sugar/activity/widgets.py:91
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/widgets.py:99
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/widgets.py:106
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/widgets.py:113
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/widgets.py:123
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/widgets.py:130
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/widgets.py:341
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:286 ../src/sugar/graphics/alert.py:365
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:290 ../src/sugar/graphics/alert.py:424
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:375
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/graphics/colorbutton.py:52
+msgid "Choose a color"
+msgstr ""
+
+#: ../src/sugar/graphics/colorbutton.py:272
+msgid "Red"
+msgstr ""
+
+#: ../src/sugar/graphics/colorbutton.py:274
+msgid "Green"
+msgstr ""
+
+#: ../src/sugar/graphics/colorbutton.py:276
+msgid "Blue"
+msgstr ""
+
+#: ../src/sugar/util.py:218
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:219
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:222
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:226
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:241
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:242
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:243
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:244
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:245
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:246
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:339
+msgid "Empty"
+msgstr ""
+
+#: ../src/sugar/util.py:341
+#, python-format
+msgid "%d B"
+msgstr ""
+
+#: ../src/sugar/util.py:343
+#, python-format
+msgid "%d KB"
+msgstr ""
+
+#: ../src/sugar/util.py:345
+#, python-format
+msgid "%d MB"
+msgstr ""
+
+#: ../src/sugar/util.py:347
+#, python-format
+msgid "%d GB"
+msgstr ""
diff --git a/toolkit/po/mg.po b/toolkit/po/mg.po
new file mode 100644
index 0000000..42c971b
--- /dev/null
+++ b/toolkit/po/mg.po
@@ -0,0 +1,210 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-08-26 00:33-0400\n"
+"PO-Revision-Date: 2009-06-05 07:50-0400\n"
+"Last-Translator: Rakoto Manassé <rakoto.manasse@auf.org>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: mg\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+"X-Generator: Pootle 1.2.1\n"
+
+#: ../src/sugar/activity/activity.py:329
+#, python-format
+msgid "%s Activity"
+msgstr "%s sahanasa"
+
+#: ../src/sugar/activity/activity.py:714
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:715
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:718
+msgid "Don't stop"
+msgstr "Aza ajanona"
+
+#: ../src/sugar/activity/activity.py:721
+msgid "Stop anyway"
+msgstr "ajanony ihany"
+
+#: ../src/sugar/activity/namingalert.py:82
+msgid "Name this entry"
+msgstr "Omeo anarana ity"
+
+#: ../src/sugar/activity/namingalert.py:87
+#: ../src/sugar/activity/widgets.py:162
+msgid "Keep"
+msgstr "tano"
+
+#: ../src/sugar/activity/namingalert.py:283
+msgid "Untitled"
+msgstr "tsy manana lohateny"
+
+#: ../src/sugar/activity/namingalert.py:290
+msgid "Description:"
+msgstr "fitanisana"
+
+#: ../src/sugar/activity/namingalert.py:314
+msgid "Tags:"
+msgstr "Marika"
+
+#: ../src/sugar/activity/widgets.py:79
+msgid "Stop"
+msgstr "ajanony"
+
+#: ../src/sugar/activity/widgets.py:91
+msgid "Undo"
+msgstr "avereno"
+
+#: ../src/sugar/activity/widgets.py:99
+msgid "Redo"
+msgstr "avereno"
+
+#: ../src/sugar/activity/widgets.py:106
+msgid "Copy"
+msgstr "adikao"
+
+#: ../src/sugar/activity/widgets.py:113
+msgid "Paste"
+msgstr "apetaho"
+
+#: ../src/sugar/activity/widgets.py:123
+msgid "Private"
+msgstr "manokana"
+
+#: ../src/sugar/activity/widgets.py:130
+msgid "My Neighborhood"
+msgstr "ny manodidina ahy"
+
+#: ../src/sugar/activity/widgets.py:341
+msgid "Activity"
+msgstr "sahanasa"
+
+#: ../src/sugar/graphics/alert.py:286 ../src/sugar/graphics/alert.py:365
+msgid "Cancel"
+msgstr "foano"
+
+#: ../src/sugar/graphics/alert.py:290 ../src/sugar/graphics/alert.py:424
+msgid "Ok"
+msgstr "Ekena"
+
+#: ../src/sugar/graphics/alert.py:375
+msgid "Continue"
+msgstr "Tohizo"
+
+#: ../src/sugar/graphics/colorbutton.py:52
+msgid "Choose a color"
+msgstr "mifidiana loko"
+
+#: ../src/sugar/graphics/colorbutton.py:272
+msgid "Red"
+msgstr "Mena"
+
+#: ../src/sugar/graphics/colorbutton.py:274
+msgid "Green"
+msgstr "Maitso"
+
+#: ../src/sugar/graphics/colorbutton.py:276
+msgid "Blue"
+msgstr "Manga"
+
+#: ../src/sugar/util.py:218
+msgid " and "
+msgstr "ary_"
+
+#: ../src/sugar/util.py:219
+msgid ", "
+msgstr ",_"
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:222
+msgid "Seconds ago"
+msgstr "Segondra lasa izay"
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:226
+#, python-format
+msgid "%s ago"
+msgstr "%s lasa izay"
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:241
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] "%d taona"
+msgstr[1] "%d taona"
+
+#: ../src/sugar/util.py:242
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] "%d volana"
+msgstr[1] "% volana"
+
+#: ../src/sugar/util.py:243
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] "%d herinandro"
+msgstr[1] "% herinandro"
+
+#: ../src/sugar/util.py:244
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] "%d andro"
+msgstr[1] "%d andro"
+
+#: ../src/sugar/util.py:245
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] "%d ora"
+msgstr[1] "%d ora"
+
+#: ../src/sugar/util.py:246
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] "%d minitra"
+msgstr[1] "%d minitra"
+
+#: ../src/sugar/util.py:339
+msgid "Empty"
+msgstr ""
+
+#: ../src/sugar/util.py:341
+#, python-format
+msgid "%d B"
+msgstr ""
+
+#: ../src/sugar/util.py:343
+#, python-format
+msgid "%d KB"
+msgstr ""
+
+#: ../src/sugar/util.py:345
+#, python-format
+msgid "%d MB"
+msgstr ""
+
+#: ../src/sugar/util.py:347
+#, python-format
+msgid "%d GB"
+msgstr ""
+
+#~ msgid "Share with:"
+#~ msgstr "zarao amin'i"
diff --git a/toolkit/po/mi.po b/toolkit/po/mi.po
new file mode 100644
index 0000000..6179aa0
--- /dev/null
+++ b/toolkit/po/mi.po
@@ -0,0 +1,186 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-01-20 00:31-0500\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:123
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:125
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:126
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:133
+#: ../src/sugar/activity/namingalert.py:65
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:144
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:258
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:263
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:273
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:278
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:304
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:542
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:910
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:911
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:914
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:917
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/activity/namingalert.py:60
+msgid "Name this entry"
+msgstr ""
+
+#: ../src/sugar/activity/namingalert.py:248
+msgid "Untitled"
+msgstr ""
+
+#: ../src/sugar/activity/namingalert.py:255
+msgid "Description:"
+msgstr ""
+
+#: ../src/sugar/activity/namingalert.py:279
+msgid "Tags:"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:288 ../src/sugar/graphics/alert.py:367
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:292 ../src/sugar/graphics/alert.py:426
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:377
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/graphics/colorbutton.py:49
+msgid "Choose a color"
+msgstr ""
+
+#: ../src/sugar/graphics/colorbutton.py:262
+msgid "Red"
+msgstr ""
+
+#: ../src/sugar/graphics/colorbutton.py:264
+msgid "Green"
+msgstr ""
+
+#: ../src/sugar/graphics/colorbutton.py:266
+msgid "Blue"
+msgstr ""
+
+#: ../src/sugar/util.py:194
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:195
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:198
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:215
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:216
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:217
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:218
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:219
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:220
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/mk.po b/toolkit/po/mk.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/toolkit/po/mk.po
@@ -0,0 +1,153 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/ml.po b/toolkit/po/ml.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/toolkit/po/ml.po
@@ -0,0 +1,153 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/mn.po b/toolkit/po/mn.po
new file mode 100644
index 0000000..11acda4
--- /dev/null
+++ b/toolkit/po/mn.po
@@ -0,0 +1,214 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-01-20 13:10-0500\n"
+"PO-Revision-Date: 2009-09-21 18:10-0400\n"
+"Last-Translator: Odontsetseg Bat-Erdene <obat-erdene@suffolk.edu>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: mn\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Pootle 1.2.1\n"
+
+#: ../src/sugar/activity/activity.py:338
+#, python-format
+msgid "%s Activity"
+msgstr "%s үйл ажиллагаа"
+
+#: ../src/sugar/activity/activity.py:738
+msgid "Keep error"
+msgstr "Хадгалахын алдаа"
+
+#: ../src/sugar/activity/activity.py:739
+msgid "Keep error: all changes will be lost"
+msgstr "Хадгалахын алдаа: бүх өөрчлөлтүүд устгагдана"
+
+#: ../src/sugar/activity/activity.py:742
+msgid "Don't stop"
+msgstr "Бүү хаа"
+
+#: ../src/sugar/activity/activity.py:745
+msgid "Stop anyway"
+msgstr "Ямар ч нөхцөлд хаах"
+
+#: ../src/sugar/activity/namingalert.py:82
+msgid "Name this entry"
+msgstr ""
+
+#: ../src/sugar/activity/namingalert.py:87
+#: ../src/sugar/activity/widgets.py:166
+msgid "Keep"
+msgstr "Хадгалах"
+
+#: ../src/sugar/activity/namingalert.py:283
+msgid "Untitled"
+msgstr ""
+
+#: ../src/sugar/activity/namingalert.py:290
+msgid "Description:"
+msgstr ""
+
+#: ../src/sugar/activity/namingalert.py:314
+msgid "Tags:"
+msgstr ""
+
+#: ../src/sugar/activity/widgets.py:83
+msgid "Stop"
+msgstr "Хаах"
+
+#: ../src/sugar/activity/widgets.py:95
+msgid "Undo"
+msgstr "Буцаах"
+
+#: ../src/sugar/activity/widgets.py:103
+msgid "Redo"
+msgstr "Давтах"
+
+#: ../src/sugar/activity/widgets.py:110
+msgid "Copy"
+msgstr "Хуулах"
+
+#: ../src/sugar/activity/widgets.py:117
+msgid "Paste"
+msgstr "Тавих"
+
+#: ../src/sugar/activity/widgets.py:127
+msgid "Private"
+msgstr "Хувийн"
+
+#: ../src/sugar/activity/widgets.py:134
+msgid "My Neighborhood"
+msgstr "Миний Хөршүүд"
+
+#: ../src/sugar/activity/widgets.py:345
+msgid "Activity"
+msgstr "Үйл ажиллагаа"
+
+#: ../src/sugar/graphics/alert.py:286 ../src/sugar/graphics/alert.py:365
+msgid "Cancel"
+msgstr "Болих"
+
+#: ../src/sugar/graphics/alert.py:290 ../src/sugar/graphics/alert.py:424
+msgid "Ok"
+msgstr "Тийм"
+
+#: ../src/sugar/graphics/alert.py:375
+msgid "Continue"
+msgstr "Үргэлжлүүлэх"
+
+#: ../src/sugar/graphics/colorbutton.py:52
+msgid "Choose a color"
+msgstr "Өнгө сонгох"
+
+#: ../src/sugar/graphics/colorbutton.py:272
+msgid "Red"
+msgstr "Улаан"
+
+#: ../src/sugar/graphics/colorbutton.py:274
+msgid "Green"
+msgstr "Ногоон"
+
+#: ../src/sugar/graphics/colorbutton.py:276
+msgid "Blue"
+msgstr "Цэнхэр"
+
+#: ../src/sugar/util.py:218
+msgid " and "
+msgstr " ба "
+
+#: ../src/sugar/util.py:219
+msgid ", "
+msgstr ", "
+
+# TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:222
+msgid "Seconds ago"
+msgstr "Секундын өмнө"
+
+# TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+# "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:226
+#, python-format
+msgid "%s ago"
+msgstr "%s-ын өмнө"
+
+# TRANS: Relative dates (eg. 1 month and 5 days).
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:241
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] "%d жил"
+msgstr[1] "%d жил"
+
+#: ../src/sugar/util.py:242
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] "%d сар"
+msgstr[1] "%d сар"
+
+#: ../src/sugar/util.py:243
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] "%d долоо хоног"
+msgstr[1] "%d долоо хоног"
+
+#: ../src/sugar/util.py:244
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] "%d хоног"
+msgstr[1] "%d хоног"
+
+#: ../src/sugar/util.py:245
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] "%d цаг"
+msgstr[1] "%d цаг"
+
+#: ../src/sugar/util.py:246
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] "%d минут"
+msgstr[1] "%d минут"
+
+#: ../src/sugar/util.py:339
+msgid "Empty"
+msgstr "Хоосон"
+
+#: ../src/sugar/util.py:341
+#, python-format
+msgid "%d B"
+msgstr ""
+
+#: ../src/sugar/util.py:343
+#, python-format
+msgid "%d KB"
+msgstr ""
+
+#: ../src/sugar/util.py:345
+#, python-format
+msgid "%d MB"
+msgstr ""
+
+#: ../src/sugar/util.py:347
+#, python-format
+msgid "%d GB"
+msgstr ""
+
+#~ msgid "Share with:"
+#~ msgstr "Хуваалцах:"
diff --git a/toolkit/po/mr.po b/toolkit/po/mr.po
new file mode 100644
index 0000000..aee6e3b
--- /dev/null
+++ b/toolkit/po/mr.po
@@ -0,0 +1,152 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: 2008-07-07 03:13-0400\n"
+"Last-Translator: Sandesh Patil <patil.sandesh@gmail.com>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Pootle 1.1.0rc2\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr "बरोबर वाटा :"
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr "खासगी"
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr "माझे शेजार"
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr "संभाला"
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr "थांबा"
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr "केलेल्या गोष्टीवर बोला फिरवने "
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr "परत करा"
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr "नक्कल"
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr "छापणे"
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr "क्रिया"
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr "%s क्रिया"
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr "चुक संभाला "
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr "चुक संभाला : सगळ्या सुधारणा नष्ट होतील"
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr "थांबू नका"
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr "कसेही थांबा "
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr "रद्द करणे "
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr "ठीक"
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr "चालू ठेवा"
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr "आणि"
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ",_"
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr "काही सेकंदांपूर्वी"
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr "%s पूर्वी"
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] "%d वर्ष"
+msgstr[1] "%d वर्षे"
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] "%d महिना"
+msgstr[1] "%d महिने"
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] "%d आठवडा"
+msgstr[1] "%d आठवडे"
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] "%d दिवस"
+msgstr[1] "%d दिवस"
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] "%d तास"
+msgstr[1] "%d तास"
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] "%d मिनिट "
+msgstr[1] "%d मिनिटे"
diff --git a/toolkit/po/ms.po b/toolkit/po/ms.po
new file mode 100644
index 0000000..6179aa0
--- /dev/null
+++ b/toolkit/po/ms.po
@@ -0,0 +1,186 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-01-20 00:31-0500\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:123
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:125
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:126
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:133
+#: ../src/sugar/activity/namingalert.py:65
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:144
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:258
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:263
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:273
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:278
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:304
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:542
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:910
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:911
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:914
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:917
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/activity/namingalert.py:60
+msgid "Name this entry"
+msgstr ""
+
+#: ../src/sugar/activity/namingalert.py:248
+msgid "Untitled"
+msgstr ""
+
+#: ../src/sugar/activity/namingalert.py:255
+msgid "Description:"
+msgstr ""
+
+#: ../src/sugar/activity/namingalert.py:279
+msgid "Tags:"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:288 ../src/sugar/graphics/alert.py:367
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:292 ../src/sugar/graphics/alert.py:426
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:377
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/graphics/colorbutton.py:49
+msgid "Choose a color"
+msgstr ""
+
+#: ../src/sugar/graphics/colorbutton.py:262
+msgid "Red"
+msgstr ""
+
+#: ../src/sugar/graphics/colorbutton.py:264
+msgid "Green"
+msgstr ""
+
+#: ../src/sugar/graphics/colorbutton.py:266
+msgid "Blue"
+msgstr ""
+
+#: ../src/sugar/util.py:194
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:195
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:198
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:215
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:216
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:217
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:218
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:219
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:220
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/mvo.po b/toolkit/po/mvo.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/toolkit/po/mvo.po
@@ -0,0 +1,153 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/nb.po b/toolkit/po/nb.po
new file mode 100644
index 0000000..3e958e1
--- /dev/null
+++ b/toolkit/po/nb.po
@@ -0,0 +1,152 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: 2008-07-22 21:04+0100\n"
+"Last-Translator: Kent Dahl <kentda@pvv.org>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr "Del med:"
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr "Privat"
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr "Mitt Nabolag"
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr "Behold"
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr "Stans"
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr "Gjøre om"
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr "Kopier"
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr "Lim inn"
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr "Lek"
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr "%s Lek"
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr "Ikke stans"
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr "Stans uansett"
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr "Avbryt"
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr "Fortsett"
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr " og "
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ", "
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr "For noen sekunder siden"
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr "%s siden"
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] "%d år"
+msgstr[1] "%d år"
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] "%d måned"
+msgstr[1] "%d måneder"
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] "%d uke"
+msgstr[1] "%d uker"
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] "%d dag"
+msgstr[1] "%d dager"
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] "%d time"
+msgstr[1] "%d timer"
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] "%d minutt"
+msgstr[1] "%d minutter"
diff --git a/toolkit/po/ne.po b/toolkit/po/ne.po
new file mode 100644
index 0000000..244c7a5
--- /dev/null
+++ b/toolkit/po/ne.po
@@ -0,0 +1,189 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-01-20 00:31-0500\n"
+"PO-Revision-Date: 2009-03-01 00:09-0500\n"
+"Last-Translator: Pradosh Kharel <pradosh@olenepal.org>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Pootle 1.1.0rc2\n"
+
+#: ../src/sugar/activity/activity.py:123
+msgid "Share with:"
+msgstr "संग बाँड:"
+
+#: ../src/sugar/activity/activity.py:125
+msgid "Private"
+msgstr "गुप्‍त"
+
+#: ../src/sugar/activity/activity.py:126
+msgid "My Neighborhood"
+msgstr "मेरो छिमेक"
+
+#: ../src/sugar/activity/activity.py:133
+#: ../src/sugar/activity/namingalert.py:65
+msgid "Keep"
+msgstr "राख"
+
+#: ../src/sugar/activity/activity.py:144
+msgid "Stop"
+msgstr "रोक"
+
+#: ../src/sugar/activity/activity.py:258
+msgid "Undo"
+msgstr "सच्याउ"
+
+#: ../src/sugar/activity/activity.py:263
+msgid "Redo"
+msgstr "पुन: सच्याउ"
+
+#: ../src/sugar/activity/activity.py:273
+msgid "Copy"
+msgstr "नकल गर"
+
+#: ../src/sugar/activity/activity.py:278
+msgid "Paste"
+msgstr "टाँस"
+
+#: ../src/sugar/activity/activity.py:304
+msgid "Activity"
+msgstr "क्रियाकलाप"
+
+#: ../src/sugar/activity/activity.py:542
+#, python-format
+msgid "%s Activity"
+msgstr "%s कृयाकलाप"
+
+#: ../src/sugar/activity/activity.py:910
+msgid "Keep error"
+msgstr "गलती राख"
+
+#: ../src/sugar/activity/activity.py:911
+msgid "Keep error: all changes will be lost"
+msgstr "गलती राख: सबै परिवर्तनहरु हराँउछन्"
+
+#: ../src/sugar/activity/activity.py:914
+msgid "Don't stop"
+msgstr "नरोक"
+
+#: ../src/sugar/activity/activity.py:917
+msgid "Stop anyway"
+msgstr "त्यैपनी रोक"
+
+#: ../src/sugar/activity/namingalert.py:60
+msgid "Name this entry"
+msgstr "यो एन्ट्रीलाई नाम देऊ"
+
+#: ../src/sugar/activity/namingalert.py:248
+msgid "Untitled"
+msgstr "नाम बिनाको"
+
+#: ../src/sugar/activity/namingalert.py:255
+msgid "Description:"
+msgstr "वर्णन"
+
+#: ../src/sugar/activity/namingalert.py:279
+msgid "Tags:"
+msgstr "ट्‍याग"
+
+#: ../src/sugar/graphics/alert.py:288 ../src/sugar/graphics/alert.py:367
+msgid "Cancel"
+msgstr "भैगो"
+
+#: ../src/sugar/graphics/alert.py:292 ../src/sugar/graphics/alert.py:426
+msgid "Ok"
+msgstr "हुन्छ"
+
+#: ../src/sugar/graphics/alert.py:377
+msgid "Continue"
+msgstr "क्रमश गरिराख"
+
+#: ../src/sugar/graphics/colorbutton.py:49
+msgid "Choose a color"
+msgstr "एउटा रंङ छान"
+
+#: ../src/sugar/graphics/colorbutton.py:262
+msgid "Red"
+msgstr "रातो"
+
+#: ../src/sugar/graphics/colorbutton.py:264
+msgid "Green"
+msgstr "हरियो"
+
+#: ../src/sugar/graphics/colorbutton.py:266
+msgid "Blue"
+msgstr "निलो"
+
+#: ../src/sugar/util.py:194
+msgid " and "
+msgstr " र "
+
+#: ../src/sugar/util.py:195
+msgid ", "
+msgstr ", "
+
+# TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:198
+msgid "Seconds ago"
+msgstr "केही बेर अगी"
+
+# TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+# "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%s ago"
+msgstr "%s अगी"
+
+# TRANS: Relative dates (eg. 1 month and 5 days).
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:215
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] "%d वर्ष"
+msgstr[1] "%d वर्ष"
+
+#: ../src/sugar/util.py:216
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] "%d महिना"
+msgstr[1] "%d महिना"
+
+#: ../src/sugar/util.py:217
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] "%d हप्ता"
+msgstr[1] "%d हप्ता"
+
+#: ../src/sugar/util.py:218
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] "%d दिन"
+msgstr[1] "%d दिन"
+
+#: ../src/sugar/util.py:219
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] "%d घन्टा"
+msgstr[1] "%d घन्टा"
+
+#: ../src/sugar/util.py:220
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] "%d मिनेट"
+msgstr[1] "%d मिनेट"
diff --git a/toolkit/po/nl.po b/toolkit/po/nl.po
new file mode 100644
index 0000000..1b1fc01
--- /dev/null
+++ b/toolkit/po/nl.po
@@ -0,0 +1,218 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-01-20 13:10-0500\n"
+"PO-Revision-Date: 2010-06-28 21:09+0200\n"
+"Last-Translator: whe <heppew@yahoo.com>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: nl\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Pootle 2.0.3\n"
+
+#: ../src/sugar/activity/activity.py:338
+#, python-format
+msgid "%s Activity"
+msgstr "%s Activiteit"
+
+#: ../src/sugar/activity/activity.py:738
+msgid "Keep error"
+msgstr "Bewaarfout"
+
+#: ../src/sugar/activity/activity.py:739
+msgid "Keep error: all changes will be lost"
+msgstr "Bewaarfout: alle veranderingen zijn verloren gegaan"
+
+#: ../src/sugar/activity/activity.py:742
+msgid "Don't stop"
+msgstr "Niet stoppen"
+
+#: ../src/sugar/activity/activity.py:745
+msgid "Stop anyway"
+msgstr "Toch stoppen"
+
+#: ../src/sugar/activity/namingalert.py:82
+msgid "Name this entry"
+msgstr "Benoem deze invoer"
+
+#: ../src/sugar/activity/namingalert.py:87
+#: ../src/sugar/activity/widgets.py:166
+msgid "Keep"
+msgstr "Behouden"
+
+#: ../src/sugar/activity/namingalert.py:283
+msgid "Untitled"
+msgstr "Naamloos"
+
+#: ../src/sugar/activity/namingalert.py:290
+msgid "Description:"
+msgstr "Beschrijving:"
+
+#: ../src/sugar/activity/namingalert.py:314
+msgid "Tags:"
+msgstr "Labels:"
+
+#: ../src/sugar/activity/widgets.py:83
+msgid "Stop"
+msgstr "Stop"
+
+#: ../src/sugar/activity/widgets.py:95
+msgid "Undo"
+msgstr "Ongedaan maken"
+
+#: ../src/sugar/activity/widgets.py:103
+msgid "Redo"
+msgstr "Herhalen"
+
+#: ../src/sugar/activity/widgets.py:110
+msgid "Copy"
+msgstr "Kopiëren"
+
+#: ../src/sugar/activity/widgets.py:117
+msgid "Paste"
+msgstr "Plakken"
+
+#: ../src/sugar/activity/widgets.py:127
+msgid "Private"
+msgstr "Privé"
+
+#: ../src/sugar/activity/widgets.py:134
+msgid "My Neighborhood"
+msgstr "Mijn omgeving"
+
+#: ../src/sugar/activity/widgets.py:345
+msgid "Activity"
+msgstr "Activiteit"
+
+#: ../src/sugar/graphics/alert.py:286 ../src/sugar/graphics/alert.py:365
+msgid "Cancel"
+msgstr "Annuleren"
+
+#: ../src/sugar/graphics/alert.py:290 ../src/sugar/graphics/alert.py:424
+msgid "Ok"
+msgstr "Ok"
+
+#: ../src/sugar/graphics/alert.py:375
+msgid "Continue"
+msgstr "Doorgaan"
+
+#: ../src/sugar/graphics/colorbutton.py:52
+msgid "Choose a color"
+msgstr "Kies een kleur"
+
+#: ../src/sugar/graphics/colorbutton.py:272
+msgid "Red"
+msgstr "Rood"
+
+#: ../src/sugar/graphics/colorbutton.py:274
+msgid "Green"
+msgstr "Groen"
+
+#: ../src/sugar/graphics/colorbutton.py:276
+msgid "Blue"
+msgstr "Blauw"
+
+#: ../src/sugar/util.py:218
+msgid " and "
+msgstr " en "
+
+#: ../src/sugar/util.py:219
+msgid ", "
+msgstr ", "
+
+# TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:222
+msgid "Seconds ago"
+msgstr "Seconden geleden"
+
+# TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+# "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:226
+#, python-format
+msgid "%s ago"
+msgstr "%s geleden"
+
+# TRANS: Relative dates (eg. 1 month and 5 days).
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:241
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] "%d jaar"
+msgstr[1] "%d jaren"
+
+#: ../src/sugar/util.py:242
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] "%d maand"
+msgstr[1] "%d maanden"
+
+#: ../src/sugar/util.py:243
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] "%d week"
+msgstr[1] "%d weken"
+
+#: ../src/sugar/util.py:244
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] "%d dag"
+msgstr[1] "%d dagen"
+
+#: ../src/sugar/util.py:245
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] "%d uur"
+msgstr[1] "%d uren"
+
+#: ../src/sugar/util.py:246
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] "%d minuut"
+msgstr[1] "%d minuten"
+
+#: ../src/sugar/util.py:339
+msgid "Empty"
+msgstr "Leeg"
+
+#: ../src/sugar/util.py:341
+#, python-format
+msgid "%d B"
+msgstr "%d B"
+
+#: ../src/sugar/util.py:343
+#, python-format
+msgid "%d KB"
+msgstr "%d KB"
+
+#: ../src/sugar/util.py:345
+#, python-format
+msgid "%d MB"
+msgstr "%d MB"
+
+#: ../src/sugar/util.py:347
+#, python-format
+msgid "%d GB"
+msgstr "%d GB"
+
+#~ msgid "Share with:"
+#~ msgstr "Delen met:"
diff --git a/toolkit/po/pa.po b/toolkit/po/pa.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/toolkit/po/pa.po
@@ -0,0 +1,153 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/pap.po b/toolkit/po/pap.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/toolkit/po/pap.po
@@ -0,0 +1,153 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/pis.po b/toolkit/po/pis.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/toolkit/po/pis.po
@@ -0,0 +1,153 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/pl.po b/toolkit/po/pl.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/toolkit/po/pl.po
@@ -0,0 +1,153 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/ps.po b/toolkit/po/ps.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/toolkit/po/ps.po
@@ -0,0 +1,153 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/pseudo.po b/toolkit/po/pseudo.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/toolkit/po/pseudo.po
@@ -0,0 +1,153 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/pt.po b/toolkit/po/pt.po
new file mode 100644
index 0000000..ac32617
--- /dev/null
+++ b/toolkit/po/pt.po
@@ -0,0 +1,214 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-08-26 00:33-0400\n"
+"PO-Revision-Date: 2009-09-13 18:41-0400\n"
+"Last-Translator: Eduardo H. Silva <HoboPrimate@gmail.com>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: pt\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Pootle 1.2.1\n"
+
+#: ../src/sugar/activity/activity.py:329
+#, python-format
+msgid "%s Activity"
+msgstr "Actividade %s"
+
+#: ../src/sugar/activity/activity.py:714
+msgid "Keep error"
+msgstr "Erro ao guardar"
+
+#: ../src/sugar/activity/activity.py:715
+msgid "Keep error: all changes will be lost"
+msgstr "Erro ao guardar: todas as mudanças serão perdidas"
+
+#: ../src/sugar/activity/activity.py:718
+msgid "Don't stop"
+msgstr "Não parar"
+
+#: ../src/sugar/activity/activity.py:721
+msgid "Stop anyway"
+msgstr "Parar mesmo assim"
+
+#: ../src/sugar/activity/namingalert.py:82
+msgid "Name this entry"
+msgstr "Dá um nome a esta entrada"
+
+#: ../src/sugar/activity/namingalert.py:87
+#: ../src/sugar/activity/widgets.py:162
+msgid "Keep"
+msgstr "Guardar"
+
+#: ../src/sugar/activity/namingalert.py:283
+msgid "Untitled"
+msgstr "Sem título"
+
+#: ../src/sugar/activity/namingalert.py:290
+msgid "Description:"
+msgstr "Descrição"
+
+#: ../src/sugar/activity/namingalert.py:314
+msgid "Tags:"
+msgstr "Etiquetas:"
+
+#: ../src/sugar/activity/widgets.py:79
+msgid "Stop"
+msgstr "Parar"
+
+#: ../src/sugar/activity/widgets.py:91
+msgid "Undo"
+msgstr "Desfazer"
+
+#: ../src/sugar/activity/widgets.py:99
+msgid "Redo"
+msgstr "Refazer"
+
+#: ../src/sugar/activity/widgets.py:106
+msgid "Copy"
+msgstr "Copiar"
+
+#: ../src/sugar/activity/widgets.py:113
+msgid "Paste"
+msgstr "Colar"
+
+#: ../src/sugar/activity/widgets.py:123
+msgid "Private"
+msgstr "Privado"
+
+#: ../src/sugar/activity/widgets.py:130
+msgid "My Neighborhood"
+msgstr "A minha vizinhança"
+
+#: ../src/sugar/activity/widgets.py:341
+msgid "Activity"
+msgstr "Actividade"
+
+#: ../src/sugar/graphics/alert.py:286 ../src/sugar/graphics/alert.py:365
+msgid "Cancel"
+msgstr "Cancelar"
+
+#: ../src/sugar/graphics/alert.py:290 ../src/sugar/graphics/alert.py:424
+msgid "Ok"
+msgstr "Ok"
+
+#: ../src/sugar/graphics/alert.py:375
+msgid "Continue"
+msgstr "Continuar"
+
+#: ../src/sugar/graphics/colorbutton.py:52
+msgid "Choose a color"
+msgstr "Escolhe uma cor"
+
+#: ../src/sugar/graphics/colorbutton.py:272
+msgid "Red"
+msgstr "Vermelho"
+
+#: ../src/sugar/graphics/colorbutton.py:274
+msgid "Green"
+msgstr "Verde"
+
+#: ../src/sugar/graphics/colorbutton.py:276
+msgid "Blue"
+msgstr "Azul"
+
+#: ../src/sugar/util.py:218
+msgid " and "
+msgstr " e "
+
+#: ../src/sugar/util.py:219
+msgid ", "
+msgstr ", "
+
+# TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:222
+msgid "Seconds ago"
+msgstr "Segundos atrás"
+
+# TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+# "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:226
+#, python-format
+msgid "%s ago"
+msgstr "%s atrás"
+
+# TRANS: Relative dates (eg. 1 month and 5 days).
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:241
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] "%d ano"
+msgstr[1] "%d anos"
+
+#: ../src/sugar/util.py:242
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] "%d mês"
+msgstr[1] "%d meses"
+
+#: ../src/sugar/util.py:243
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] "%d semana"
+msgstr[1] "%d semanas"
+
+#: ../src/sugar/util.py:244
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] "%d dia"
+msgstr[1] "%d dias"
+
+#: ../src/sugar/util.py:245
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] "%d hora"
+msgstr[1] "%d horas"
+
+#: ../src/sugar/util.py:246
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] "%d minuto"
+msgstr[1] "%d minutos"
+
+#: ../src/sugar/util.py:339
+msgid "Empty"
+msgstr "Vazio"
+
+#: ../src/sugar/util.py:341
+#, python-format
+msgid "%d B"
+msgstr "%d B"
+
+#: ../src/sugar/util.py:343
+#, python-format
+msgid "%d KB"
+msgstr "%d KB"
+
+#: ../src/sugar/util.py:345
+#, python-format
+msgid "%d MB"
+msgstr "%d MB"
+
+#: ../src/sugar/util.py:347
+#, python-format
+msgid "%d GB"
+msgstr "%d GB"
+
+#~ msgid "Share with:"
+#~ msgstr "Partilhar com:"
diff --git a/toolkit/po/pt_BR.po b/toolkit/po/pt_BR.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/toolkit/po/pt_BR.po
@@ -0,0 +1,153 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/qu.po b/toolkit/po/qu.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/toolkit/po/qu.po
@@ -0,0 +1,153 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/ro.po b/toolkit/po/ro.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/toolkit/po/ro.po
@@ -0,0 +1,153 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/ru.po b/toolkit/po/ru.po
new file mode 100644
index 0000000..c86e0fb
--- /dev/null
+++ b/toolkit/po/ru.po
@@ -0,0 +1,161 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: 2008-09-08 11:53-0400\n"
+"Last-Translator: Kirill Krinkin <homebox@pisem.net>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+"X-Generator: Pootle 1.1.0rc2\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr "Использовать совместно с:"
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr "Персонально"
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr "Мои соседи"
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr "Хранить"
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr "Стоп"
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr "Откат"
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr "Повтор"
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr "Копировать"
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr "Вставить"
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr "Активность"
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr "Активность %s"
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr "Ошибка хранения"
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr "Ошибка хранения: все изменения будут потеряны"
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr "Не останавливаться"
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr "Остановить в любом случае"
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr "Отмена"
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr "Ok"
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr "Продолжить"
+
+# требует проверки! что там в исходном коде?
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr "_and_"
+
+# требует проверки! что там в исходном коде?
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ",_"
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr "Секунд назад"
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr "%s назад"
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] "%d год"
+msgstr[1] "%d года"
+msgstr[2] "%d лет"
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] "%d месяц"
+msgstr[1] "%d месяца"
+msgstr[2] "%d месяцев"
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] "%d неделя"
+msgstr[1] "%d недели"
+msgstr[2] "%d недель"
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] "%d день"
+msgstr[1] "%d дня"
+msgstr[2] "%d дней"
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] "%d час"
+msgstr[1] "%d часа"
+msgstr[2] "%d часов"
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] "%d минута"
+msgstr[1] "%d минуты"
+msgstr[2] "%d минут"
diff --git a/toolkit/po/rw.po b/toolkit/po/rw.po
new file mode 100644
index 0000000..567ed5b
--- /dev/null
+++ b/toolkit/po/rw.po
@@ -0,0 +1,154 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: 2008-08-06 03:42-0400\n"
+"Last-Translator: Carine Umutesi <carine.umutesi@rita.rw>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Pootle 1.1.0rc2\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr "Gusangira na:"
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr "Byihariye"
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr "Guturana kwanjye"
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr "Gumana"
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr "Hagarika"
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr "Subiramo"
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr "Gukuraho icyariho"
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr "Gukoporora"
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr "Omeka"
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr "Igikorwa"
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr "%s Igikorwa"
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr "Gumana ikosa"
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr "Gumana ikosa:impinduka zose zirabura"
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr "Ntuhagarare"
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr "Hagarara muburyo bwose"
+
+#: ../src/sugar/graphics/alert.py:166
+#: ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr "Kuraho"
+
+#: ../src/sugar/graphics/alert.py:170
+#: ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr "Nibyo"
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr "Komeza"
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr " na "
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ", "
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr "Amasegonda ashize"
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr "%s ashize"
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/sd.po b/toolkit/po/sd.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/toolkit/po/sd.po
@@ -0,0 +1,153 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/si.po b/toolkit/po/si.po
new file mode 100644
index 0000000..c6892fb
--- /dev/null
+++ b/toolkit/po/si.po
@@ -0,0 +1,152 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: 2008-07-18 06:59-0400\n"
+"Last-Translator: Rashan Anushka <rashan.uoc@gmail.com>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Pootle 1.1.0rc2\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr "බෙදාගත යුත්තේ:"
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr "පුද්ගලික"
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr "මගේ වටපිටාව"
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr "තබාගන්න"
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr "නවත්වන්න"
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr "නිශ්ප්‍රභ කරන්න"
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr "යළි කරන්න"
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr "පිටපත් කරන්න"
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr "අලවන්න"
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr "ක්‍රියාකාරකම"
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr "%s ක්‍රියාකාරකම"
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr "දෝශය තබාගන්න"
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr "දෝශය තබාගන්න: සියළු වෙනස්කිරීම් නැතිවනු ඇත"
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr "නවත්වන්න එපා"
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr "කෙසේ හෝ නවත්වන්න"
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr "අවලංගු කරන්න"
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr "හරි"
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr "පවත්වාගෙන යන්න"
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr " හා "
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ", "
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr "තත්පර කිහිපයකට පෙර"
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr "%s ට පෙර"
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] "අවුරුද්දයි"
+msgstr[1] "අවුරුදු %d "
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] "මාසයයි"
+msgstr[1] "මාස %d"
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] "සතියයි"
+msgstr[1] "සති %d"
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] "දවසයි"
+msgstr[1] "දවස් %d"
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] "පැයයි"
+msgstr[1] "පැය %d"
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] "මිනිත්තුවයි"
+msgstr[1] "මිනිත්තු %d"
diff --git a/toolkit/po/sk.po b/toolkit/po/sk.po
new file mode 100644
index 0000000..bf097b4
--- /dev/null
+++ b/toolkit/po/sk.po
@@ -0,0 +1,160 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
diff --git a/toolkit/po/sl.po b/toolkit/po/sl.po
new file mode 100644
index 0000000..0109ec8
--- /dev/null
+++ b/toolkit/po/sl.po
@@ -0,0 +1,201 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-01-20 00:31-0500\n"
+"PO-Revision-Date: 2009-01-29 08:25-0500\n"
+"Last-Translator: Denis Oštir <denis.ostir@gmail.com>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Pootle 1.1.0rc2\n"
+
+#: ../src/sugar/activity/activity.py:123
+msgid "Share with:"
+msgstr "Deli z:"
+
+#: ../src/sugar/activity/activity.py:125
+msgid "Private"
+msgstr "Zasebno"
+
+#: ../src/sugar/activity/activity.py:126
+msgid "My Neighborhood"
+msgstr "Moja soseščina"
+
+#: ../src/sugar/activity/activity.py:133
+#: ../src/sugar/activity/namingalert.py:65
+msgid "Keep"
+msgstr "Obdrži"
+
+#: ../src/sugar/activity/activity.py:144
+msgid "Stop"
+msgstr "Ustavi"
+
+#: ../src/sugar/activity/activity.py:258
+msgid "Undo"
+msgstr "Razveljavi"
+
+#: ../src/sugar/activity/activity.py:263
+msgid "Redo"
+msgstr "Ponovi"
+
+#: ../src/sugar/activity/activity.py:273
+msgid "Copy"
+msgstr "Kopiraj"
+
+#: ../src/sugar/activity/activity.py:278
+msgid "Paste"
+msgstr "Prilepi"
+
+#: ../src/sugar/activity/activity.py:304
+msgid "Activity"
+msgstr "Aktivnost"
+
+#: ../src/sugar/activity/activity.py:542
+#, python-format
+msgid "%s Activity"
+msgstr "%s aktivnost"
+
+#: ../src/sugar/activity/activity.py:910
+msgid "Keep error"
+msgstr "Napaka pri shranjevanju"
+
+#: ../src/sugar/activity/activity.py:911
+msgid "Keep error: all changes will be lost"
+msgstr "Napaka pri shranjevanju: vse spremembe bodo izgubljene"
+
+#: ../src/sugar/activity/activity.py:914
+msgid "Don't stop"
+msgstr "Ne ustavi"
+
+#: ../src/sugar/activity/activity.py:917
+msgid "Stop anyway"
+msgstr "Vseeno ustavi"
+
+#: ../src/sugar/activity/namingalert.py:60
+msgid "Name this entry"
+msgstr "Poimenuj ta vnos"
+
+#: ../src/sugar/activity/namingalert.py:248
+msgid "Untitled"
+msgstr "Neimenovan"
+
+#: ../src/sugar/activity/namingalert.py:255
+msgid "Description:"
+msgstr "Opis:"
+
+#: ../src/sugar/activity/namingalert.py:279
+msgid "Tags:"
+msgstr "Oznake:"
+
+#: ../src/sugar/graphics/alert.py:288 ../src/sugar/graphics/alert.py:367
+msgid "Cancel"
+msgstr "Prekliči"
+
+#: ../src/sugar/graphics/alert.py:292 ../src/sugar/graphics/alert.py:426
+msgid "Ok"
+msgstr "V redu"
+
+#: ../src/sugar/graphics/alert.py:377
+msgid "Continue"
+msgstr "Nadaljuj"
+
+#: ../src/sugar/graphics/colorbutton.py:49
+msgid "Choose a color"
+msgstr "Izberi barvo"
+
+#: ../src/sugar/graphics/colorbutton.py:262
+msgid "Red"
+msgstr "Rdeca"
+
+#: ../src/sugar/graphics/colorbutton.py:264
+msgid "Green"
+msgstr "Zelena"
+
+#: ../src/sugar/graphics/colorbutton.py:266
+msgid "Blue"
+msgstr "Modra"
+
+#: ../src/sugar/util.py:194
+msgid " and "
+msgstr " in "
+
+#: ../src/sugar/util.py:195
+msgid ", "
+msgstr ", "
+
+# TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:198
+msgid "Seconds ago"
+msgstr "pred nekaj sekundami"
+
+# TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+# "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%s ago"
+msgstr "%s nazaj"
+
+# TRANS: Relative dates (eg. 1 month and 5 days).
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:215
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] "%d leto"
+msgstr[1] "%d leti"
+msgstr[2] "%d let"
+msgstr[3] ""
+
+#: ../src/sugar/util.py:216
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] "%d mesec"
+msgstr[1] "%d meseca"
+msgstr[2] "%d mesecev"
+msgstr[3] ""
+
+#: ../src/sugar/util.py:217
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] "%d teden"
+msgstr[1] "%d tedna"
+msgstr[2] "%d tedni"
+msgstr[3] "%d tednov"
+
+#: ../src/sugar/util.py:218
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] "%d dan"
+msgstr[1] "%d dni"
+msgstr[2] ""
+msgstr[3] ""
+
+#: ../src/sugar/util.py:219
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] "%d ura"
+msgstr[1] "%d uri"
+msgstr[2] "%d ure"
+msgstr[3] "%d ur"
+
+#: ../src/sugar/util.py:220
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] "%d minuta"
+msgstr[1] "%d minuti"
+msgstr[2] "%d minute"
+msgstr[3] "%d minut"
diff --git a/toolkit/po/sq.po b/toolkit/po/sq.po
new file mode 100644
index 0000000..80a0c88
--- /dev/null
+++ b/toolkit/po/sq.po
@@ -0,0 +1,208 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-08-26 00:33-0400\n"
+"PO-Revision-Date: 2009-06-18 11:13+0100\n"
+"Last-Translator: Heroid <heroid@ayih.org>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.3.0\n"
+
+#: ../src/sugar/activity/activity.py:329
+#, python-format
+msgid "%s Activity"
+msgstr "%s Aktivitet"
+
+#: ../src/sugar/activity/activity.py:714
+msgid "Keep error"
+msgstr "Gabim gjat mbajtjes"
+
+#: ../src/sugar/activity/activity.py:715
+msgid "Keep error: all changes will be lost"
+msgstr "Gabim gjat mbajtjes: të gjitha ndrryshimet do humbasin"
+
+#: ../src/sugar/activity/activity.py:718
+msgid "Don't stop"
+msgstr "Mos ndalo"
+
+#: ../src/sugar/activity/activity.py:721
+msgid "Stop anyway"
+msgstr "Ndalo gjithsesi"
+
+#: ../src/sugar/activity/namingalert.py:82
+msgid "Name this entry"
+msgstr "Ndrryshoja emrin"
+
+#: ../src/sugar/activity/namingalert.py:87
+#: ../src/sugar/activity/widgets.py:162
+msgid "Keep"
+msgstr "Mbaj"
+
+#: ../src/sugar/activity/namingalert.py:283
+msgid "Untitled"
+msgstr "E paemërtuar"
+
+#: ../src/sugar/activity/namingalert.py:290
+msgid "Description:"
+msgstr "Përshkrimi:"
+
+#: ../src/sugar/activity/namingalert.py:314
+msgid "Tags:"
+msgstr "Etiketat:"
+
+#: ../src/sugar/activity/widgets.py:79
+msgid "Stop"
+msgstr "Ndalo"
+
+#: ../src/sugar/activity/widgets.py:91
+msgid "Undo"
+msgstr "Zhbëj"
+
+#: ../src/sugar/activity/widgets.py:99
+msgid "Redo"
+msgstr "Bëj"
+
+#: ../src/sugar/activity/widgets.py:106
+msgid "Copy"
+msgstr "Kopjo"
+
+#: ../src/sugar/activity/widgets.py:113
+msgid "Paste"
+msgstr "Ngjit"
+
+#: ../src/sugar/activity/widgets.py:123
+msgid "Private"
+msgstr "Private"
+
+#: ../src/sugar/activity/widgets.py:130
+msgid "My Neighborhood"
+msgstr "Lagjja Ime"
+
+#: ../src/sugar/activity/widgets.py:341
+msgid "Activity"
+msgstr "Aktivitet"
+
+#: ../src/sugar/graphics/alert.py:286 ../src/sugar/graphics/alert.py:365
+msgid "Cancel"
+msgstr "Anulo"
+
+#: ../src/sugar/graphics/alert.py:290 ../src/sugar/graphics/alert.py:424
+msgid "Ok"
+msgstr "Në rregull"
+
+#: ../src/sugar/graphics/alert.py:375
+msgid "Continue"
+msgstr "Vazhdo"
+
+#: ../src/sugar/graphics/colorbutton.py:52
+msgid "Choose a color"
+msgstr "Zgjidh një ngjyrë"
+
+#: ../src/sugar/graphics/colorbutton.py:272
+msgid "Red"
+msgstr "Kuq"
+
+#: ../src/sugar/graphics/colorbutton.py:274
+msgid "Green"
+msgstr "Gjelbër"
+
+#: ../src/sugar/graphics/colorbutton.py:276
+msgid "Blue"
+msgstr "Kaltër"
+
+#: ../src/sugar/util.py:218
+msgid " and "
+msgstr "dhe"
+
+#: ../src/sugar/util.py:219
+msgid ", "
+msgstr ","
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:222
+msgid "Seconds ago"
+msgstr "Sekonda më parë"
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:226
+#, python-format
+msgid "%s ago"
+msgstr "%s më parë"
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:241
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:242
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:243
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:244
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:245
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:246
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:339
+msgid "Empty"
+msgstr ""
+
+#: ../src/sugar/util.py:341
+#, python-format
+msgid "%d B"
+msgstr ""
+
+#: ../src/sugar/util.py:343
+#, python-format
+msgid "%d KB"
+msgstr ""
+
+#: ../src/sugar/util.py:345
+#, python-format
+msgid "%d MB"
+msgstr ""
+
+#: ../src/sugar/util.py:347
+#, python-format
+msgid "%d GB"
+msgstr ""
+
+#~ msgid "Share with:"
+#~ msgstr "Ndaj me:"
diff --git a/toolkit/po/sv.po b/toolkit/po/sv.po
new file mode 100644
index 0000000..3c6d15c
--- /dev/null
+++ b/toolkit/po/sv.po
@@ -0,0 +1,189 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-01-20 00:31-0500\n"
+"PO-Revision-Date: 2009-01-28 15:29-0500\n"
+"Last-Translator: Susanna Björverud <susanna.bjorverud@telia.com>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Pootle 1.1.0rc2\n"
+
+#: ../src/sugar/activity/activity.py:123
+msgid "Share with:"
+msgstr "Dela med:"
+
+#: ../src/sugar/activity/activity.py:125
+msgid "Private"
+msgstr "Mig själv"
+
+#: ../src/sugar/activity/activity.py:126
+msgid "My Neighborhood"
+msgstr "Mina grannar"
+
+#: ../src/sugar/activity/activity.py:133
+#: ../src/sugar/activity/namingalert.py:65
+msgid "Keep"
+msgstr "Spara"
+
+#: ../src/sugar/activity/activity.py:144
+msgid "Stop"
+msgstr "Avsluta"
+
+#: ../src/sugar/activity/activity.py:258
+msgid "Undo"
+msgstr "Ångra"
+
+#: ../src/sugar/activity/activity.py:263
+msgid "Redo"
+msgstr "Återställ"
+
+#: ../src/sugar/activity/activity.py:273
+msgid "Copy"
+msgstr "Kopiera"
+
+#: ../src/sugar/activity/activity.py:278
+msgid "Paste"
+msgstr "Klistra in"
+
+#: ../src/sugar/activity/activity.py:304
+msgid "Activity"
+msgstr "Aktivitet"
+
+#: ../src/sugar/activity/activity.py:542
+#, python-format
+msgid "%s Activity"
+msgstr "(%s)aktivitet"
+
+#: ../src/sugar/activity/activity.py:910
+msgid "Keep error"
+msgstr "Sparfel"
+
+#: ../src/sugar/activity/activity.py:911
+msgid "Keep error: all changes will be lost"
+msgstr "Sparfel: alla ändringar kommer att förloras"
+
+#: ../src/sugar/activity/activity.py:914
+msgid "Don't stop"
+msgstr "Avsluta inte"
+
+#: ../src/sugar/activity/activity.py:917
+msgid "Stop anyway"
+msgstr "Stäng utan att spara"
+
+#: ../src/sugar/activity/namingalert.py:60
+msgid "Name this entry"
+msgstr "Ge ett namn till denna post"
+
+#: ../src/sugar/activity/namingalert.py:248
+msgid "Untitled"
+msgstr "Namnlös"
+
+#: ../src/sugar/activity/namingalert.py:255
+msgid "Description:"
+msgstr "Beskrivning:"
+
+#: ../src/sugar/activity/namingalert.py:279
+msgid "Tags:"
+msgstr "Nyckelord:"
+
+#: ../src/sugar/graphics/alert.py:288 ../src/sugar/graphics/alert.py:367
+msgid "Cancel"
+msgstr "Avbryt"
+
+#: ../src/sugar/graphics/alert.py:292 ../src/sugar/graphics/alert.py:426
+msgid "Ok"
+msgstr "Ok"
+
+#: ../src/sugar/graphics/alert.py:377
+msgid "Continue"
+msgstr "Fortsätt"
+
+#: ../src/sugar/graphics/colorbutton.py:49
+msgid "Choose a color"
+msgstr "Välj en färg"
+
+#: ../src/sugar/graphics/colorbutton.py:262
+msgid "Red"
+msgstr "Röd"
+
+#: ../src/sugar/graphics/colorbutton.py:264
+msgid "Green"
+msgstr "Grön"
+
+#: ../src/sugar/graphics/colorbutton.py:266
+msgid "Blue"
+msgstr "Blå"
+
+#: ../src/sugar/util.py:194
+msgid " and "
+msgstr " och "
+
+#: ../src/sugar/util.py:195
+msgid ", "
+msgstr ", "
+
+# TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:198
+msgid "Seconds ago"
+msgstr "sekunder sedan"
+
+# TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+# "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%s ago"
+msgstr "%s gammalt"
+
+# TRANS: Relative dates (eg. 1 month and 5 days).
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:215
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] "%d år"
+msgstr[1] "%d år"
+
+#: ../src/sugar/util.py:216
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] "%d månad"
+msgstr[1] "%d månader"
+
+#: ../src/sugar/util.py:217
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] "%d vecka"
+msgstr[1] "%d veckor"
+
+#: ../src/sugar/util.py:218
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] "%d dag"
+msgstr[1] "%d dagar"
+
+#: ../src/sugar/util.py:219
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] "%d timme"
+msgstr[1] "%d timmar"
+
+#: ../src/sugar/util.py:220
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] "%d minut"
+msgstr[1] "%d minuter"
diff --git a/toolkit/po/sw.po b/toolkit/po/sw.po
new file mode 100644
index 0000000..2c06ab5
--- /dev/null
+++ b/toolkit/po/sw.po
@@ -0,0 +1,152 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: 2008-11-21 14:10-0500\n"
+"Last-Translator: Fanuel Kalugendo <fanosbert@yahoo.com>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Pootle 1.1.0rc2\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr "Shirikina pamoja"
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr "Binafsi"
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr "Jirani yangu"
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr "Hifadhi/ibakishe"
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr "Simama"
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr "rudi kabla"
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr "Rudia kufanya"
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr "nakili"
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr "bandika"
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr "Kazi"
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr "%s Kazi"
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr "hifadhi kosa"
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr "hifadhi kosa: mabadiliko yote yatapotea"
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr "Usisimame"
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr "Simama hata hiyo"
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr "Ghahiri"
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr "Sawa"
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr "Endelea"
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr "na_"
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ",_"
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr "Dakika zilizopita"
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr "%s zilizopita"
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] "%d mwaka"
+msgstr[1] "%d miaka"
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] "%d mwezi"
+msgstr[1] "%d miezi"
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] "%d wiki"
+msgstr[1] "%d wiki"
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] "%d siku"
+msgstr[1] "%d siku"
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] "%d saa"
+msgstr[1] "%d masaa"
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] "%d dakika"
+msgstr[1] "%d dakika"
diff --git a/toolkit/po/ta.po b/toolkit/po/ta.po
new file mode 100644
index 0000000..4410409
--- /dev/null
+++ b/toolkit/po/ta.po
@@ -0,0 +1,210 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-08-26 00:33-0400\n"
+"PO-Revision-Date: 2009-06-05 02:13-0400\n"
+"Last-Translator: Deependra Ariyadewa <deependra@gmail.com>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: ta\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Pootle 1.2.1\n"
+
+#: ../src/sugar/activity/activity.py:329
+#, python-format
+msgid "%s Activity"
+msgstr "%s செயற்பாடு"
+
+#: ../src/sugar/activity/activity.py:714
+msgid "Keep error"
+msgstr "தவறு வைத்திரு"
+
+#: ../src/sugar/activity/activity.py:715
+msgid "Keep error: all changes will be lost"
+msgstr "தவறு வைத்திரு: எல்லா மாற்றங்களும் அழிந்துவிடும்."
+
+#: ../src/sugar/activity/activity.py:718
+msgid "Don't stop"
+msgstr "நிறுத்தாதே"
+
+#: ../src/sugar/activity/activity.py:721
+msgid "Stop anyway"
+msgstr "எப்படியாயினும் நிறுத்து"
+
+#: ../src/sugar/activity/namingalert.py:82
+msgid "Name this entry"
+msgstr "இப் பெயரை பதி"
+
+#: ../src/sugar/activity/namingalert.py:87
+#: ../src/sugar/activity/widgets.py:162
+msgid "Keep"
+msgstr "வைத்திரு"
+
+#: ../src/sugar/activity/namingalert.py:283
+msgid "Untitled"
+msgstr "தலைப்பில்லாத"
+
+#: ../src/sugar/activity/namingalert.py:290
+msgid "Description:"
+msgstr "விளக்கம்"
+
+#: ../src/sugar/activity/namingalert.py:314
+msgid "Tags:"
+msgstr "இணை"
+
+#: ../src/sugar/activity/widgets.py:79
+msgid "Stop"
+msgstr "நிறுத்து"
+
+#: ../src/sugar/activity/widgets.py:91
+msgid "Undo"
+msgstr "செயலை ரத்து செய்"
+
+#: ../src/sugar/activity/widgets.py:99
+msgid "Redo"
+msgstr "மீள் செய்"
+
+#: ../src/sugar/activity/widgets.py:106
+msgid "Copy"
+msgstr "பிரதி செய்"
+
+#: ../src/sugar/activity/widgets.py:113
+msgid "Paste"
+msgstr "ஒட்டு"
+
+#: ../src/sugar/activity/widgets.py:123
+msgid "Private"
+msgstr "தனியார்"
+
+#: ../src/sugar/activity/widgets.py:130
+msgid "My Neighborhood"
+msgstr "எனது அயல்"
+
+#: ../src/sugar/activity/widgets.py:341
+msgid "Activity"
+msgstr "செயற்பாடு"
+
+#: ../src/sugar/graphics/alert.py:286 ../src/sugar/graphics/alert.py:365
+msgid "Cancel"
+msgstr "இரத்துசெய்"
+
+#: ../src/sugar/graphics/alert.py:290 ../src/sugar/graphics/alert.py:424
+msgid "Ok"
+msgstr "சரி"
+
+#: ../src/sugar/graphics/alert.py:375
+msgid "Continue"
+msgstr "தொடர்ச்சி"
+
+#: ../src/sugar/graphics/colorbutton.py:52
+msgid "Choose a color"
+msgstr "நிறத்தை தெரிவு செய்"
+
+#: ../src/sugar/graphics/colorbutton.py:272
+msgid "Red"
+msgstr "சிவப்பு"
+
+#: ../src/sugar/graphics/colorbutton.py:274
+msgid "Green"
+msgstr "பச்சை"
+
+#: ../src/sugar/graphics/colorbutton.py:276
+msgid "Blue"
+msgstr "நீலம்"
+
+#: ../src/sugar/util.py:218
+msgid " and "
+msgstr "உடன்"
+
+#: ../src/sugar/util.py:219
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:222
+msgid "Seconds ago"
+msgstr "வினாடிகளின் பின்"
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:226
+#, python-format
+msgid "%s ago"
+msgstr "%sமுன்"
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:241
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] "%dவருடம்"
+msgstr[1] "%d வருடங்கள் "
+
+#: ../src/sugar/util.py:242
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] "%d மாதம்"
+msgstr[1] "%d மாதங்கள்"
+
+#: ../src/sugar/util.py:243
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] "%d கிழமை"
+msgstr[1] "%d கிழமைகள்"
+
+#: ../src/sugar/util.py:244
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] "%dநாள்"
+msgstr[1] "%dநாற்கள்"
+
+#: ../src/sugar/util.py:245
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] "%d மணித்தியாலம்"
+msgstr[1] ""
+
+#: ../src/sugar/util.py:246
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] "%dநிமிடம்"
+msgstr[1] "%dநிமிடங்கள்"
+
+#: ../src/sugar/util.py:339
+msgid "Empty"
+msgstr ""
+
+#: ../src/sugar/util.py:341
+#, python-format
+msgid "%d B"
+msgstr ""
+
+#: ../src/sugar/util.py:343
+#, python-format
+msgid "%d KB"
+msgstr ""
+
+#: ../src/sugar/util.py:345
+#, python-format
+msgid "%d MB"
+msgstr ""
+
+#: ../src/sugar/util.py:347
+#, python-format
+msgid "%d GB"
+msgstr ""
+
+#~ msgid "Share with:"
+#~ msgstr "பங்கீடு"
diff --git a/toolkit/po/te.po b/toolkit/po/te.po
new file mode 100644
index 0000000..bc632b1
--- /dev/null
+++ b/toolkit/po/te.po
@@ -0,0 +1,154 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: 2008-07-24 12:55+0100\n"
+"Last-Translator: Satyanarayana Murthy Saladi <saladism@gmail.com>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr "తో పంచుకో:"
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr "సొంతం"
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr "నా చుట్టుపక్కలవారు"
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr "ఉంచు"
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr "ఆపు"
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr "చివరిది రద్దుచేయి"
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr "చివరిది తిరిగిచేయి"
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr "నకలు"
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr "అతికించు"
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr "వ్యాపకం"
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr "%s వ్యాపకం"
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr "పొరబాటు జరిగింది"
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr "పొరబాటు జరిగింది : అన్ని మార్పులూ పోతాయి"
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr "ఆపవద్దు"
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr "ఏదేమైనా ఆపువేయి"
+
+#: ../src/sugar/graphics/alert.py:166
+#: ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr "రద్దు చేయి"
+
+#: ../src/sugar/graphics/alert.py:170
+#: ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr "సరి"
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr "సాగించు"
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr "మరియు"
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ", "
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr "సెకనులు ముందు"
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr "%s ముందు"
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] "%d సంవత్సరము"
+msgstr[1] ""
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] "%d నెల"
+msgstr[1] ""
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] "%d వారము"
+msgstr[1] ""
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] "%d రోజు"
+msgstr[1] ""
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] "%d గంట"
+msgstr[1] ""
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] "%d నిమిషము "
+msgstr[1] ""
diff --git a/toolkit/po/th.po b/toolkit/po/th.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/toolkit/po/th.po
@@ -0,0 +1,153 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/tpi.po b/toolkit/po/tpi.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/toolkit/po/tpi.po
@@ -0,0 +1,153 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/tr.po b/toolkit/po/tr.po
new file mode 100644
index 0000000..7cd7da5
--- /dev/null
+++ b/toolkit/po/tr.po
@@ -0,0 +1,153 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: 2008-09-26 09:52-0400\n"
+"Last-Translator: abdullah kocabas <abdullah.kocabas@abcdizustu.com>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Pootle 1.1.0rc2\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr "Paylaş"
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr "Özel"
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr "Komşularım"
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr "Kaydet"
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr "Durdur"
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr "Geri Al"
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr "Yinele"
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr "Kopyala"
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr "Yapıştır"
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr "Aktivite"
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr "%s Aktivite"
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr "Kayıt Hatası"
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr "Kayıt Hatası: tüm değişiklikler kaybedilecek"
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr "Durdurma"
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr "Durdur"
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr "İptal"
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr "Tamam"
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr "Devam Et"
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr "ve"
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ",_"
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr "Saniye Önce"
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr "%s önce"
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] "%d yıl"
+msgstr[1] "%d yıllar"
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] "%d ay"
+msgstr[1] "%d aylar"
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] "%d hafta"
+msgstr[1] "%d haftalar"
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] "%d gün"
+msgstr[1] "%d günler"
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] "%d saat"
+msgstr[1] "%d saatler"
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] "%d dakika"
+msgstr[1] "%d dakikalar"
diff --git a/toolkit/po/tvl.po b/toolkit/po/tvl.po
new file mode 100644
index 0000000..5e78660
--- /dev/null
+++ b/toolkit/po/tvl.po
@@ -0,0 +1,206 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-08-26 00:33-0400\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.3.0\n"
+
+#: ../src/sugar/activity/activity.py:329
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:714
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:715
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:718
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:721
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/activity/namingalert.py:82
+msgid "Name this entry"
+msgstr ""
+
+#: ../src/sugar/activity/namingalert.py:87
+#: ../src/sugar/activity/widgets.py:162
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/namingalert.py:283
+msgid "Untitled"
+msgstr ""
+
+#: ../src/sugar/activity/namingalert.py:290
+msgid "Description:"
+msgstr ""
+
+#: ../src/sugar/activity/namingalert.py:314
+msgid "Tags:"
+msgstr ""
+
+#: ../src/sugar/activity/widgets.py:79
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/widgets.py:91
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/widgets.py:99
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/widgets.py:106
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/widgets.py:113
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/widgets.py:123
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/widgets.py:130
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/widgets.py:341
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:286 ../src/sugar/graphics/alert.py:365
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:290 ../src/sugar/graphics/alert.py:424
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:375
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/graphics/colorbutton.py:52
+msgid "Choose a color"
+msgstr ""
+
+#: ../src/sugar/graphics/colorbutton.py:272
+msgid "Red"
+msgstr ""
+
+#: ../src/sugar/graphics/colorbutton.py:274
+msgid "Green"
+msgstr ""
+
+#: ../src/sugar/graphics/colorbutton.py:276
+msgid "Blue"
+msgstr ""
+
+#: ../src/sugar/util.py:218
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:219
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:222
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:226
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:241
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:242
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:243
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:244
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:245
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:246
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:339
+msgid "Empty"
+msgstr ""
+
+#: ../src/sugar/util.py:341
+#, python-format
+msgid "%d B"
+msgstr ""
+
+#: ../src/sugar/util.py:343
+#, python-format
+msgid "%d KB"
+msgstr ""
+
+#: ../src/sugar/util.py:345
+#, python-format
+msgid "%d MB"
+msgstr ""
+
+#: ../src/sugar/util.py:347
+#, python-format
+msgid "%d GB"
+msgstr ""
diff --git a/toolkit/po/tzo.po b/toolkit/po/tzo.po
new file mode 100644
index 0000000..5e78660
--- /dev/null
+++ b/toolkit/po/tzo.po
@@ -0,0 +1,206 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-08-26 00:33-0400\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.3.0\n"
+
+#: ../src/sugar/activity/activity.py:329
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:714
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:715
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:718
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:721
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/activity/namingalert.py:82
+msgid "Name this entry"
+msgstr ""
+
+#: ../src/sugar/activity/namingalert.py:87
+#: ../src/sugar/activity/widgets.py:162
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/namingalert.py:283
+msgid "Untitled"
+msgstr ""
+
+#: ../src/sugar/activity/namingalert.py:290
+msgid "Description:"
+msgstr ""
+
+#: ../src/sugar/activity/namingalert.py:314
+msgid "Tags:"
+msgstr ""
+
+#: ../src/sugar/activity/widgets.py:79
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/widgets.py:91
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/widgets.py:99
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/widgets.py:106
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/widgets.py:113
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/widgets.py:123
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/widgets.py:130
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/widgets.py:341
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:286 ../src/sugar/graphics/alert.py:365
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:290 ../src/sugar/graphics/alert.py:424
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:375
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/graphics/colorbutton.py:52
+msgid "Choose a color"
+msgstr ""
+
+#: ../src/sugar/graphics/colorbutton.py:272
+msgid "Red"
+msgstr ""
+
+#: ../src/sugar/graphics/colorbutton.py:274
+msgid "Green"
+msgstr ""
+
+#: ../src/sugar/graphics/colorbutton.py:276
+msgid "Blue"
+msgstr ""
+
+#: ../src/sugar/util.py:218
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:219
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:222
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:226
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:241
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:242
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:243
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:244
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:245
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:246
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:339
+msgid "Empty"
+msgstr ""
+
+#: ../src/sugar/util.py:341
+#, python-format
+msgid "%d B"
+msgstr ""
+
+#: ../src/sugar/util.py:343
+#, python-format
+msgid "%d KB"
+msgstr ""
+
+#: ../src/sugar/util.py:345
+#, python-format
+msgid "%d MB"
+msgstr ""
+
+#: ../src/sugar/util.py:347
+#, python-format
+msgid "%d GB"
+msgstr ""
diff --git a/toolkit/po/ug.po b/toolkit/po/ug.po
new file mode 100644
index 0000000..6179aa0
--- /dev/null
+++ b/toolkit/po/ug.po
@@ -0,0 +1,186 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-01-20 00:31-0500\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:123
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:125
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:126
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:133
+#: ../src/sugar/activity/namingalert.py:65
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:144
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:258
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:263
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:273
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:278
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:304
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:542
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:910
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:911
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:914
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:917
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/activity/namingalert.py:60
+msgid "Name this entry"
+msgstr ""
+
+#: ../src/sugar/activity/namingalert.py:248
+msgid "Untitled"
+msgstr ""
+
+#: ../src/sugar/activity/namingalert.py:255
+msgid "Description:"
+msgstr ""
+
+#: ../src/sugar/activity/namingalert.py:279
+msgid "Tags:"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:288 ../src/sugar/graphics/alert.py:367
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:292 ../src/sugar/graphics/alert.py:426
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:377
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/graphics/colorbutton.py:49
+msgid "Choose a color"
+msgstr ""
+
+#: ../src/sugar/graphics/colorbutton.py:262
+msgid "Red"
+msgstr ""
+
+#: ../src/sugar/graphics/colorbutton.py:264
+msgid "Green"
+msgstr ""
+
+#: ../src/sugar/graphics/colorbutton.py:266
+msgid "Blue"
+msgstr ""
+
+#: ../src/sugar/util.py:194
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:195
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:198
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:215
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:216
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:217
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:218
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:219
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:220
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/ur.po b/toolkit/po/ur.po
new file mode 100644
index 0000000..66ec1ce
--- /dev/null
+++ b/toolkit/po/ur.po
@@ -0,0 +1,152 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: 2008-07-08 04:38-0400\n"
+"Last-Translator: salman minhas <sulmanminhas@gmail.com>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Pootle 1.1.0rc2\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr "کے ساتھہ ‎‎‎شئر کريں:"
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr "پرايويٹ"
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr "ميرا گردونواح"
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr "رکھيں"
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr "روکيں"
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr "کلعدم کريں"
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr "دوبارہ کريں"
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr "کاپی"
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr "جوڑيں"
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr "سرگرمی"
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr "%sسرگرمی"
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr "غلطی رکھيں"
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr "غلطی رکھيں: تمام تبديلياں ختم ہو سکتی ہیں"
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr "مت رکيں"
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr "رک جائيں"
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr "منسوخ کريں"
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr "ٹھيک ہے"
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr "جاری رکھيں"
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr " اور "
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr "، "
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr "سيکنڈ پہلے"
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr "%s پہلے"
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] "%dسال"
+msgstr[1] "%dسال"
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] "%dمہينہ"
+msgstr[1] "%d مہينے"
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] "%d ہفتہ"
+msgstr[1] "%d ہفتے"
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] "%d دن"
+msgstr[1] "%s دن"
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] "%d گھنٹہ"
+msgstr[1] "%d گھنٹے"
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] "%d منٹ"
+msgstr[1] "%d منٹ"
diff --git a/toolkit/po/vi.po b/toolkit/po/vi.po
new file mode 100644
index 0000000..062173a
--- /dev/null
+++ b/toolkit/po/vi.po
@@ -0,0 +1,208 @@
+# Vietnamese translation for Sugar Tookit.
+# Copyright © 2009 Free Software Foundation, Inc.
+# Clytie Siddall <clytie@riverland.net.au>, 2009.
+msgid ""
+msgstr ""
+"Project-Id-Version: sugar-toolkit\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-01-20 13:10-0500\n"
+"PO-Revision-Date: 2009-09-25 09:03-0400\n"
+"Last-Translator: Clytie Siddall <clytie@riverland.net.au>\n"
+"Language-Team: Vietnamese <vi-VN@googlegroups.com>\n"
+"Language: vi\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Pootle 1.2.1\n"
+
+#: ../src/sugar/activity/activity.py:338
+#, python-format
+msgid "%s Activity"
+msgstr "Hoạt động %s"
+
+#: ../src/sugar/activity/activity.py:738
+msgid "Keep error"
+msgstr "Giữ lỗi"
+
+#: ../src/sugar/activity/activity.py:739
+msgid "Keep error: all changes will be lost"
+msgstr "Giữ lỗi: tất cả các thay đổi sẽ bị mất"
+
+#: ../src/sugar/activity/activity.py:742
+msgid "Don't stop"
+msgstr "Không dừng"
+
+#: ../src/sugar/activity/activity.py:745
+msgid "Stop anyway"
+msgstr "Vẫn dừng"
+
+#: ../src/sugar/activity/namingalert.py:82
+msgid "Name this entry"
+msgstr "Đặt tên mục nhập này"
+
+#: ../src/sugar/activity/namingalert.py:87
+#: ../src/sugar/activity/widgets.py:166
+msgid "Keep"
+msgstr "Giữ"
+
+#: ../src/sugar/activity/namingalert.py:283
+msgid "Untitled"
+msgstr "Không tên"
+
+#: ../src/sugar/activity/namingalert.py:290
+msgid "Description:"
+msgstr "Mô tả:"
+
+#: ../src/sugar/activity/namingalert.py:314
+msgid "Tags:"
+msgstr "Thẻ:"
+
+#: ../src/sugar/activity/widgets.py:83
+msgid "Stop"
+msgstr "Dừng"
+
+#: ../src/sugar/activity/widgets.py:95
+msgid "Undo"
+msgstr "Hủy bước"
+
+#: ../src/sugar/activity/widgets.py:103
+msgid "Redo"
+msgstr "Hoàn lại"
+
+#: ../src/sugar/activity/widgets.py:110
+msgid "Copy"
+msgstr "Chép"
+
+#: ../src/sugar/activity/widgets.py:117
+msgid "Paste"
+msgstr "Dán"
+
+#: ../src/sugar/activity/widgets.py:127
+msgid "Private"
+msgstr "Riêng"
+
+#: ../src/sugar/activity/widgets.py:134
+msgid "My Neighborhood"
+msgstr "Hàng xóm mình"
+
+#: ../src/sugar/activity/widgets.py:345
+msgid "Activity"
+msgstr "Hoạt động"
+
+#: ../src/sugar/graphics/alert.py:286 ../src/sugar/graphics/alert.py:365
+msgid "Cancel"
+msgstr "Thôi"
+
+#: ../src/sugar/graphics/alert.py:290 ../src/sugar/graphics/alert.py:424
+msgid "Ok"
+msgstr "OK"
+
+#: ../src/sugar/graphics/alert.py:375
+msgid "Continue"
+msgstr "Tiếp"
+
+#: ../src/sugar/graphics/colorbutton.py:52
+msgid "Choose a color"
+msgstr "Chọn một màu"
+
+#: ../src/sugar/graphics/colorbutton.py:272
+msgid "Red"
+msgstr "Đỏ"
+
+#: ../src/sugar/graphics/colorbutton.py:274
+msgid "Green"
+msgstr "Lục"
+
+#: ../src/sugar/graphics/colorbutton.py:276
+msgid "Blue"
+msgstr "Xanh"
+
+# Không cần từ. No word needed.
+#: ../src/sugar/util.py:218
+msgid " and "
+msgstr " "
+
+#: ../src/sugar/util.py:219
+msgid ", "
+msgstr ", "
+
+# TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:222
+msgid "Seconds ago"
+msgstr "Giây trước"
+
+# TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+# "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:226
+#, python-format
+msgid "%s ago"
+msgstr "%s trước"
+
+# TRANS: Relative dates (eg. 1 month and 5 days).
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:241
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] "%d năm"
+
+#: ../src/sugar/util.py:242
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] "%d tháng"
+
+#: ../src/sugar/util.py:243
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] "%d tuần"
+
+#: ../src/sugar/util.py:244
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] "%d ngày"
+
+#: ../src/sugar/util.py:245
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] "%d giờ"
+
+#: ../src/sugar/util.py:246
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] "%d phút"
+
+#: ../src/sugar/util.py:339
+msgid "Empty"
+msgstr "Trống"
+
+#: ../src/sugar/util.py:341
+#, python-format
+msgid "%d B"
+msgstr "%d B"
+
+#: ../src/sugar/util.py:343
+#, python-format
+msgid "%d KB"
+msgstr "%d KB"
+
+#: ../src/sugar/util.py:345
+#, python-format
+msgid "%d MB"
+msgstr "%d MB"
+
+#: ../src/sugar/util.py:347
+#, python-format
+msgid "%d GB"
+msgstr "%d GB"
+
+#~ msgid "Share with:"
+#~ msgstr "Chia sẻ với:"
diff --git a/toolkit/po/wa.po b/toolkit/po/wa.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/toolkit/po/wa.po
@@ -0,0 +1,153 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/yo.po b/toolkit/po/yo.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/toolkit/po/yo.po
@@ -0,0 +1,153 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/zh_CN.po b/toolkit/po/zh_CN.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/toolkit/po/zh_CN.po
@@ -0,0 +1,153 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr ""
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:170 ../src/sugar/graphics/alert.py:247
+msgid "Ok"
+msgstr ""
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr ""
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr ""
+
+#: ../src/sugar/util.py:182
+msgid ", "
+msgstr ""
+
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:185
+msgid "Seconds ago"
+msgstr ""
+
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:189
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
diff --git a/toolkit/po/zh_TW.po b/toolkit/po/zh_TW.po
new file mode 100644
index 0000000..082a720
--- /dev/null
+++ b/toolkit/po/zh_TW.po
@@ -0,0 +1,208 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-01-20 13:10-0500\n"
+"PO-Revision-Date: 2009-12-22 08:05-0400\n"
+"Last-Translator: Yuan Chao <yuanchao@gmail.com>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: zh_TW\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Pootle 1.2.1\n"
+
+#: ../src/sugar/activity/activity.py:338
+#, python-format
+msgid "%s Activity"
+msgstr "%s 活動"
+
+#: ../src/sugar/activity/activity.py:738
+msgid "Keep error"
+msgstr "保存時發生錯誤"
+
+#: ../src/sugar/activity/activity.py:739
+msgid "Keep error: all changes will be lost"
+msgstr "保存時發生錯誤:所作的變動將遺失"
+
+#: ../src/sugar/activity/activity.py:742
+msgid "Don't stop"
+msgstr "不停止"
+
+#: ../src/sugar/activity/activity.py:745
+msgid "Stop anyway"
+msgstr "確定停止"
+
+#: ../src/sugar/activity/namingalert.py:82
+msgid "Name this entry"
+msgstr "命名"
+
+#: ../src/sugar/activity/namingalert.py:87
+#: ../src/sugar/activity/widgets.py:166
+msgid "Keep"
+msgstr "保存"
+
+#: ../src/sugar/activity/namingalert.py:283
+msgid "Untitled"
+msgstr "未命名"
+
+#: ../src/sugar/activity/namingalert.py:290
+msgid "Description:"
+msgstr "描述:"
+
+#: ../src/sugar/activity/namingalert.py:314
+msgid "Tags:"
+msgstr "標籤:"
+
+#: ../src/sugar/activity/widgets.py:83
+msgid "Stop"
+msgstr "停止"
+
+#: ../src/sugar/activity/widgets.py:95
+msgid "Undo"
+msgstr "復原"
+
+#: ../src/sugar/activity/widgets.py:103
+msgid "Redo"
+msgstr "取消復原"
+
+#: ../src/sugar/activity/widgets.py:110
+msgid "Copy"
+msgstr "複製"
+
+#: ../src/sugar/activity/widgets.py:117
+msgid "Paste"
+msgstr "貼上"
+
+#: ../src/sugar/activity/widgets.py:127
+msgid "Private"
+msgstr "私人"
+
+#: ../src/sugar/activity/widgets.py:134
+msgid "My Neighborhood"
+msgstr "我的鄰居"
+
+#: ../src/sugar/activity/widgets.py:345
+msgid "Activity"
+msgstr "活動"
+
+#: ../src/sugar/graphics/alert.py:286 ../src/sugar/graphics/alert.py:365
+msgid "Cancel"
+msgstr "取消"
+
+#: ../src/sugar/graphics/alert.py:290 ../src/sugar/graphics/alert.py:424
+msgid "Ok"
+msgstr "確定"
+
+#: ../src/sugar/graphics/alert.py:375
+msgid "Continue"
+msgstr "繼續"
+
+#: ../src/sugar/graphics/colorbutton.py:52
+msgid "Choose a color"
+msgstr "選擇喜歡的顏色"
+
+#: ../src/sugar/graphics/colorbutton.py:272
+msgid "Red"
+msgstr "紅色"
+
+#: ../src/sugar/graphics/colorbutton.py:274
+msgid "Green"
+msgstr "綠色"
+
+#: ../src/sugar/graphics/colorbutton.py:276
+msgid "Blue"
+msgstr "藍色"
+
+#: ../src/sugar/util.py:218
+msgid " and "
+msgstr " 和 "
+
+#: ../src/sugar/util.py:219
+msgid ", "
+msgstr ", "
+
+# TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#. TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+#: ../src/sugar/util.py:222
+msgid "Seconds ago"
+msgstr "數秒鐘前"
+
+# TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+# "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#. TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+#. "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+#: ../src/sugar/util.py:226
+#, python-format
+msgid "%s ago"
+msgstr "%s 前"
+
+# TRANS: Relative dates (eg. 1 month and 5 days).
+#. TRANS: Relative dates (eg. 1 month and 5 days).
+#: ../src/sugar/util.py:241
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] "%d 年"
+
+#: ../src/sugar/util.py:242
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] "%d 個月"
+
+#: ../src/sugar/util.py:243
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] "%d 週"
+
+#: ../src/sugar/util.py:244
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] "%d 天"
+
+#: ../src/sugar/util.py:245
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] "%d 小時"
+
+#: ../src/sugar/util.py:246
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] "%d 分鐘"
+
+#: ../src/sugar/util.py:339
+msgid "Empty"
+msgstr "無"
+
+#: ../src/sugar/util.py:341
+#, python-format
+msgid "%d B"
+msgstr "%d B"
+
+#: ../src/sugar/util.py:343
+#, python-format
+msgid "%d KB"
+msgstr "%d KB"
+
+#: ../src/sugar/util.py:345
+#, python-format
+msgid "%d MB"
+msgstr "%d MB"
+
+#: ../src/sugar/util.py:347
+#, python-format
+msgid "%d GB"
+msgstr "%d GB"
+
+#~ msgid "Share with:"
+#~ msgstr "分享給:"
diff --git a/toolkit/src/Makefile.am b/toolkit/src/Makefile.am
new file mode 100644
index 0000000..4fa44db
--- /dev/null
+++ b/toolkit/src/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = sugar
diff --git a/toolkit/src/sugar/.gitignore b/toolkit/src/sugar/.gitignore
new file mode 100644
index 0000000..de24e35
--- /dev/null
+++ b/toolkit/src/sugar/.gitignore
@@ -0,0 +1,4 @@
+sugar-marshal.c
+sugar-marshal.h
+_sugarext.c
+_sugarext.c
diff --git a/toolkit/src/sugar/.license b/toolkit/src/sugar/.license
new file mode 100644
index 0000000..6989ebe
--- /dev/null
+++ b/toolkit/src/sugar/.license
@@ -0,0 +1 @@
+LGPL
diff --git a/toolkit/src/sugar/Makefile.am b/toolkit/src/sugar/Makefile.am
new file mode 100644
index 0000000..236e337
--- /dev/null
+++ b/toolkit/src/sugar/Makefile.am
@@ -0,0 +1,87 @@
+SUBDIRS = activity bundle graphics presence datastore
+
+sugardir = $(pythondir)/sugar
+sugar_PYTHON = \
+ env.py \
+ network.py \
+ profile.py \
+ session.py \
+ util.py \
+ wm.py
+
+pkgpyexecdir = $(pythondir)/sugar
+
+pkgpyexec_LTLIBRARIES = _sugarext.la
+
+_sugarext_la_CFLAGS = \
+ -DHAVE_ALSA \
+ $(WARN_CFLAGS) \
+ $(EXT_CFLAGS) \
+ $(PYTHON_INCLUDES)
+
+_sugarext_la_LDFLAGS = -module -avoid-version
+_sugarext_la_LIBADD = $(EXT_LIBS) -lSM -lICE
+
+_sugarext_la_SOURCES = \
+ $(BUILT_SOURCES) \
+ _sugarextmodule.c \
+ acme-volume.h \
+ acme-volume.c \
+ acme-volume-alsa.h \
+ acme-volume-alsa.c \
+ gsm-app.h \
+ gsm-app.c \
+ gsm-client.h \
+ gsm-client.c \
+ gsm-client-xsmp.h \
+ gsm-client-xsmp.c \
+ gsm-xsmp.h \
+ gsm-xsmp.c \
+ gsm-session.h \
+ gsm-session.c \
+ eggaccelerators.c \
+ eggaccelerators.h \
+ eggdesktopfile.h \
+ eggdesktopfile.c \
+ eggsmclient.h \
+ eggsmclient.c \
+ eggsmclient-private.h \
+ eggsmclient-xsmp.c \
+ sexy-icon-entry.h \
+ sexy-icon-entry.c \
+ sugar-address-entry.c \
+ sugar-address-entry.h \
+ sugar-grid.c \
+ sugar-grid.h \
+ sugar-key-grabber.c \
+ sugar-key-grabber.h \
+ sugar-menu.h \
+ sugar-menu.c
+
+BUILT_SOURCES = \
+ _sugarext.c \
+ sugar-marshal.c \
+ sugar-marshal.h
+
+_sugarext.c: _sugarext.defs _sugarext.override
+
+.defs.c:
+ (cd $(srcdir)\
+ && $(PYGTK_CODEGEN) \
+ --register $(PYGTK_DEFSDIR)/gdk-types.defs \
+ --register $(PYGTK_DEFSDIR)/gtk-types.defs \
+ --override $*.override \
+ --prefix py$* $*.defs) > gen-$*.c \
+ && cp gen-$*.c $*.c \
+ && rm -f gen-$*.c
+
+sugar-marshal.c: sugar-marshal.list
+ $(GLIB_GENMARSHAL) --prefix=sugar_marshal \
+ $(srcdir)/sugar-marshal.list --header --body > sugar-marshal.c
+
+sugar-marshal.h: sugar-marshal.list
+ $(GLIB_GENMARSHAL) --prefix=sugar_marshal \
+ $(srcdir)/sugar-marshal.list --header > sugar-marshal.h
+
+CLEANFILES = $(BUILT_SOURCES)
+EXTRA_DIST = sugar-marshal.list _sugarext.defs _sugarext.override
diff --git a/toolkit/src/sugar/__init__.py b/toolkit/src/sugar/__init__.py
new file mode 100644
index 0000000..44acb4d
--- /dev/null
+++ b/toolkit/src/sugar/__init__.py
@@ -0,0 +1,14 @@
+# 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.
diff --git a/toolkit/src/sugar/_sugarext.defs b/toolkit/src/sugar/_sugarext.defs
new file mode 100644
index 0000000..e36034d
--- /dev/null
+++ b/toolkit/src/sugar/_sugarext.defs
@@ -0,0 +1,422 @@
+;; -*- scheme -*-
+; object definitions
+
+(define-object AddressEntry
+ (in-module "Sugar")
+ (parent "GtkEntry")
+ (c-name "SugarAddressEntry")
+ (gtype-id "SUGAR_TYPE_ADDRESS_ENTRY")
+)
+
+(define-object KeyGrabber
+ (in-module "Sugar")
+ (parent "GObject")
+ (c-name "SugarKeyGrabber")
+ (gtype-id "SUGAR_TYPE_KEY_GRABBER")
+)
+
+(define-object Menu
+ (in-module "Sugar")
+ (parent "GtkMenu")
+ (c-name "SugarMenu")
+ (gtype-id "SUGAR_TYPE_MENU")
+)
+
+(define-object Grid
+ (in-module "Sugar")
+ (parent "GObject")
+ (c-name "SugarGrid")
+ (gtype-id "SUGAR_TYPE_GRID")
+)
+
+(define-object IconEntry
+ (in-module "Sexy")
+ (parent "GtkEntry")
+ (c-name "SexyIconEntry")
+ (gtype-id "SEXY_TYPE_ICON_ENTRY")
+)
+
+(define-object SMClientXSMP
+ (in-module "Egg")
+ (parent "EggSMClient")
+ (c-name "EggSMClientXSMP")
+ (gtype-id "EGG_TYPE_SM_CLIENT_XSMP")
+)
+
+(define-object SMClient
+ (in-module "Egg")
+ (parent "GObject")
+ (c-name "EggSMClient")
+ (gtype-id "EGG_TYPE_SM_CLIENT")
+)
+
+(define-object Session
+ (in-module "Gsm")
+ (parent "GObject")
+ (c-name "GsmSession")
+ (gtype-id "GSM_TYPE_SESSION")
+)
+
+(define-object Volume
+ (in-module "Acme")
+ (parent "GObject")
+ (c-name "AcmeVolume")
+ (gtype-id "ACME_TYPE_VOLUME")
+)
+
+(define-object VolumeAlsa
+ (in-module "Acme")
+ (parent "AcmeVolume")
+ (c-name "AcmeVolumeAlsa")
+ (gtype-id "ACME_TYPE_VOLUME_ALSA")
+)
+
+;; Enumerations and flags ...
+
+(define-enum IconEntryPosition
+ (in-module "Sexy")
+ (c-name "SexyIconEntryPosition")
+ (gtype-id "SEXY_TYPE_ICON_ENTRY_POSITION")
+ (values
+ '("primary" "SEXY_ICON_ENTRY_PRIMARY")
+ '("secondary" "SEXY_ICON_ENTRY_SECONDARY")
+ )
+)
+
+;; From sugar-menu.h
+
+(define-method set_active
+ (of-object "SugarMenu")
+ (c-name "sugar_menu_set_active")
+ (return-type "none")
+ (parameters
+ '("gboolean" "active")
+ )
+)
+
+(define-method embed
+ (of-object "SugarMenu")
+ (c-name "sugar_menu_embed")
+ (return-type "none")
+ (parameters
+ '("GtkContainer" "container")
+ )
+)
+
+(define-method unembed
+ (of-object "SugarMenu")
+ (c-name "sugar_menu_unembed")
+ (return-type "none")
+)
+
+;; From sugar-grid.h
+
+(define-method setup
+ (of-object "SugarGrid")
+ (c-name "sugar_grid_setup")
+ (return-type "none")
+ (parameters
+ '("gint" "width")
+ '("gint" "height")
+ )
+)
+
+(define-method add_weight
+ (of-object "SugarGrid")
+ (c-name "sugar_grid_add_weight")
+ (return-type "none")
+ (parameters
+ '("GdkRectangle*" "rect")
+ )
+)
+
+(define-method remove_weight
+ (of-object "SugarGrid")
+ (c-name "sugar_grid_remove_weight")
+ (return-type "none")
+ (parameters
+ '("GdkRectangle*" "rect")
+ )
+)
+
+(define-method compute_weight
+ (of-object "SugarGrid")
+ (c-name "sugar_grid_compute_weight")
+ (return-type "guint")
+ (parameters
+ '("GdkRectangle*" "rect")
+ )
+)
+
+;; From sugar-key-grabber.h
+
+(define-function sugar_key_grabber_get_type
+ (c-name "sugar_key_grabber_get_type")
+ (return-type "GType")
+)
+
+(define-method grab_keys
+ (of-object "SugarKeyGrabber")
+ (c-name "sugar_key_grabber_grab_keys")
+ (return-type "none")
+ (parameters
+ '("const-char*[]" "keys")
+ )
+)
+
+(define-method get_key
+ (of-object "SugarKeyGrabber")
+ (c-name "sugar_key_grabber_get_key")
+ (return-type "char*")
+ (parameters
+ '("guint" "keycode")
+ '("guint" "state")
+ )
+)
+
+(define-method is_modifier
+ (of-object "SugarKeyGrabber")
+ (c-name "sugar_key_grabber_is_modifier")
+ (return-type "gboolean")
+ (parameters
+ '("guint" "keycode")
+ '("guint" "mask" (default "-1"))
+ )
+)
+
+;; From sexy-icon-entry.h
+
+(define-function sexy_icon_entry_get_type
+ (c-name "sexy_icon_entry_get_type")
+ (return-type "GType")
+)
+
+(define-function sexy_icon_entry_new
+ (c-name "sexy_icon_entry_new")
+ (is-constructor-of "SexyIconEntry")
+ (return-type "GtkWidget*")
+)
+
+(define-method set_icon
+ (of-object "SexyIconEntry")
+ (c-name "sexy_icon_entry_set_icon")
+ (return-type "none")
+ (parameters
+ '("SexyIconEntryPosition" "position")
+ '("GtkImage*" "icon" (null-ok))
+ )
+)
+
+(define-method set_icon_highlight
+ (of-object "SexyIconEntry")
+ (c-name "sexy_icon_entry_set_icon_highlight")
+ (return-type "none")
+ (parameters
+ '("SexyIconEntryPosition" "position")
+ '("gboolean" "highlight")
+ )
+)
+
+(define-method get_icon
+ (of-object "SexyIconEntry")
+ (c-name "sexy_icon_entry_get_icon")
+ (return-type "GtkImage*")
+ (parameters
+ '("SexyIconEntryPosition" "position")
+ )
+)
+
+(define-method get_icon_highlight
+ (of-object "SexyIconEntry")
+ (c-name "sexy_icon_entry_get_icon_highlight")
+ (return-type "gboolean")
+ (parameters
+ '("SexyIconEntryPosition" "position")
+ )
+)
+
+(define-method add_clear_button
+ (of-object "SexyIconEntry")
+ (c-name "sexy_icon_entry_add_clear_button")
+ (return-type "none")
+)
+
+;; From eggsmclient.h
+
+(define-function egg_sm_client_get_type
+ (c-name "egg_sm_client_get_type")
+ (return-type "GType")
+)
+
+(define-function egg_sm_client_get_option_group
+ (c-name "egg_sm_client_get_option_group")
+ (return-type "GOptionGroup*")
+)
+
+(define-method is_resumed
+ (of-object "EggSMClient")
+ (c-name "egg_sm_client_is_resumed")
+ (return-type "gboolean")
+)
+
+(define-method get_state_file
+ (of-object "EggSMClient")
+ (c-name "egg_sm_client_get_state_file")
+ (return-type "GKeyFile*")
+)
+
+(define-method set_restart_command
+ (of-object "EggSMClient")
+ (c-name "egg_sm_client_set_restart_command")
+ (return-type "none")
+ (parameters
+ '("int" "argc")
+ '("const-char**" "argv")
+ )
+)
+
+(define-method startup
+ (of-object "EggSMClient")
+ (c-name "egg_sm_client_startup")
+ (return-type "none")
+)
+
+(define-method will_quit
+ (of-object "EggSMClient")
+ (c-name "egg_sm_client_will_quit")
+ (return-type "none")
+ (parameters
+ '("gboolean" "will_quit")
+ )
+)
+
+(define-function egg_sm_client_end_session
+ (c-name "egg_sm_client_end_session")
+ (return-type "gboolean")
+ (parameters
+ '("EggSMClientEndStyle" "style")
+ '("gboolean" "request_confirmation")
+ )
+)
+
+;; From xsmp.h
+
+(define-function xsmp_init
+ (c-name "gsm_xsmp_init")
+ (return-type "char*")
+)
+
+(define-function xsmp_run
+ (c-name "gsm_xsmp_run")
+ (return-type "none")
+)
+
+(define-function xsmp_shutdown
+ (c-name "gsm_xsmp_shutdown")
+ (return-type "none")
+)
+
+;; From session.h
+
+(define-method set_name
+ (of-object "GsmSession")
+ (c-name "gsm_session_set_name")
+ (return-type "none")
+ (parameters
+ '("const-char*" "name")
+ )
+)
+
+(define-method start
+ (of-object "GsmSession")
+ (c-name "gsm_session_start")
+ (return-type "none")
+)
+
+(define-method get_phase
+ (of-object "GsmSession")
+ (c-name "gsm_session_get_phase")
+ (return-type "GsmSessionPhase")
+)
+
+(define-method initiate_shutdown
+ (of-object "GsmSession")
+ (c-name "gsm_session_initiate_shutdown")
+ (return-type "none")
+)
+
+(define-method cancel_shutdown
+ (of-object "GsmSession")
+ (c-name "gsm_session_cancel_shutdown")
+ (return-type "none")
+)
+
+(define-method register_client
+ (of-object "GsmSession")
+ (c-name "gsm_session_register_client")
+ (return-type "char*")
+ (parameters
+ '("GsmClient*" "client")
+ '("const-char*" "previous_id")
+ )
+)
+
+(define-function session_create_global
+ (c-name "gsm_session_create_global")
+ (return-type "GsmSession*")
+)
+
+;; From acme-volume.h
+
+(define-function acme_volume_get_type
+ (c-name "acme_volume_get_type")
+ (return-type "GType")
+)
+
+(define-method get_volume
+ (of-object "AcmeVolume")
+ (c-name "acme_volume_get_volume")
+ (return-type "int")
+)
+
+(define-method set_volume
+ (of-object "AcmeVolume")
+ (c-name "acme_volume_set_volume")
+ (return-type "none")
+ (parameters
+ '("int" "val")
+ )
+)
+
+(define-method get_mute
+ (of-object "AcmeVolume")
+ (c-name "acme_volume_get_mute")
+ (return-type "gboolean")
+)
+
+(define-method set_mute
+ (of-object "AcmeVolume")
+ (c-name "acme_volume_set_mute")
+ (return-type "none")
+ (parameters
+ '("gboolean" "val")
+ )
+)
+
+(define-method mute_toggle
+ (of-object "AcmeVolume")
+ (c-name "acme_volume_mute_toggle")
+ (return-type "none")
+)
+
+(define-method get_threshold
+ (of-object "AcmeVolume")
+ (c-name "acme_volume_get_threshold")
+ (return-type "int")
+)
+
+(define-function acme_volume_new
+ (c-name "acme_volume_new")
+ (is-constructor-of "AcmeVolume")
+ (return-type "AcmeVolume*")
+)
diff --git a/toolkit/src/sugar/_sugarext.override b/toolkit/src/sugar/_sugarext.override
new file mode 100644
index 0000000..6b768bb
--- /dev/null
+++ b/toolkit/src/sugar/_sugarext.override
@@ -0,0 +1,81 @@
+/* -*- Mode: C; c-basic-offset: 4 -*- */
+%%
+headers
+#include <Python.h>
+
+#include "pygobject.h"
+#include "sugar-address-entry.h"
+#include "sugar-grid.h"
+#include "sugar-key-grabber.h"
+#include "sugar-menu.h"
+#include "sexy-icon-entry.h"
+#include "gsm-session.h"
+#include "gsm-xsmp.h"
+#include "acme-volume-alsa.h"
+
+#include "eggsmclient.h"
+#include "eggsmclient-private.h"
+
+#include <pygtk/pygtk.h>
+#include <glib.h>
+
+%%
+modulename sugar._sugarext
+%%
+import gobject.GObject as PyGObject_Type
+import gtk.Widget as PyGtkWidget_Type
+import gtk.Entry as PyGtkEntry_Type
+import gtk.Menu as PyGtkMenu_Type
+import gtk.Container as PyGtkContainer_Type
+import gtk.gdk.Window as PyGdkWindow_Type
+import gtk.Image as PyGtkImage_Type
+%%
+ignore-glob
+ *_get_type
+ _*
+%%
+override sugar_key_grabber_grab_keys kwargs
+static PyObject *
+_wrap_sugar_key_grabber_grab_keys(PyGObject *self, PyObject *args, PyObject *kwargs)
+{
+ static char *kwlist[] = { "key", NULL };
+ PyObject *py_keys;
+ char **keys;
+ int i, len;
+
+ if (!PyArg_ParseTupleAndKeywords(args,kwargs,
+ "O:SugarKeyGrabber.grab_keys",
+ kwlist, &py_keys))
+ return NULL;
+
+ if (!PySequence_Check(py_keys) || (len = PySequence_Size(py_keys)) < 0) {
+ PyErr_SetString(PyExc_ValueError,
+ "keys should be a sequence of strings");
+ return NULL;
+ }
+
+ keys = g_new(char*, len + 1);
+ for (i = 0; i < len; i++) {
+ PyObject *item = PySequence_GetItem(py_keys, i);
+ if (!item) {
+ g_free(keys);
+ return NULL;
+ }
+ if (!PyString_Check(item)) {
+ PyErr_SetString(PyExc_TypeError, "key must be a string");
+ g_free(keys);
+ Py_DECREF(item);
+ return NULL;
+ }
+ keys[i] = PyString_AsString(item);
+ Py_DECREF(item);
+ }
+ keys[len] = NULL;
+
+ sugar_key_grabber_grab_keys (SUGAR_KEY_GRABBER(self->obj), (const char**) keys);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+%%
diff --git a/toolkit/src/sugar/_sugarextmodule.c b/toolkit/src/sugar/_sugarextmodule.c
new file mode 100644
index 0000000..1bb8545
--- /dev/null
+++ b/toolkit/src/sugar/_sugarextmodule.c
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2006-2007, Red Hat, Inc.
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+/* include this first, before NO_IMPORT_PYGOBJECT is defined */
+#include <pygobject.h>
+#include <pygtk/pygtk.h>
+
+extern PyMethodDef py_sugarext_functions[];
+
+void py_sugarext_register_classes (PyObject *d);
+void py_sugarext_add_constants (PyObject *module, const gchar *strip_prefix);
+
+DL_EXPORT(void)
+init_sugarext(void)
+{
+ PyObject *m, *d;
+
+ init_pygobject();
+ init_pygtk();
+
+ m = Py_InitModule("_sugarext", py_sugarext_functions);
+ d = PyModule_GetDict(m);
+
+ py_sugarext_register_classes(d);
+ py_sugarext_add_constants(m, "SEXY_");
+
+ if (PyErr_Occurred ()) {
+ Py_FatalError ("can't initialise module _sugarext");
+ }
+}
diff --git a/toolkit/src/sugar/acme-volume-alsa.c b/toolkit/src/sugar/acme-volume-alsa.c
new file mode 100644
index 0000000..42bbf4e
--- /dev/null
+++ b/toolkit/src/sugar/acme-volume-alsa.c
@@ -0,0 +1,317 @@
+/* acme-volume-alsa.c
+
+ Copyright (C) 2002, 2003 Bastien Nocera
+
+ The Gnome Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Gnome 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Gnome Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Bastien Nocera <hadess@hadess.net>
+ */
+
+#ifdef HAVE_CONFIG
+#include "config.h"
+#endif
+
+#include "acme-volume-alsa.h"
+
+#include <alsa/asoundlib.h>
+
+#ifndef DEFAULT_CARD
+#define DEFAULT_CARD "default"
+#endif
+
+#undef LOG
+#ifdef LOG
+#define D(x...) g_message (x)
+#else
+#define D(x...)
+#endif
+
+#define ROUND(x) ((x - (int)x > 0.5) ? x+1 : x)
+
+struct AcmeVolumeAlsaPrivate
+{
+ long pmin, pmax;
+ gboolean has_mute, has_master;
+ snd_mixer_t *handle;
+ snd_mixer_elem_t *elem;
+ int saved_volume;
+ guint timer_id;
+};
+
+static int acme_volume_alsa_get_volume (AcmeVolume *self);
+static void acme_volume_alsa_set_volume (AcmeVolume *self, int val);
+static gboolean acme_volume_alsa_open (AcmeVolumeAlsa *self);
+static void acme_volume_alsa_close (AcmeVolumeAlsa *self);
+static gboolean acme_volume_alsa_close_real (AcmeVolumeAlsa *self);
+
+G_DEFINE_TYPE (AcmeVolumeAlsa, acme_volume_alsa, ACME_TYPE_VOLUME)
+
+static void
+acme_volume_alsa_finalize (GObject *object)
+{
+ AcmeVolumeAlsa *self;
+
+ self = ACME_VOLUME_ALSA (object);
+
+ if (self->_priv)
+ {
+ if (self->_priv->timer_id != 0)
+ {
+ g_source_remove (self->_priv->timer_id);
+ self->_priv->timer_id = 0;
+ }
+
+ acme_volume_alsa_close_real (self);
+ g_free (self->_priv);
+ self->_priv = NULL;
+ }
+
+ G_OBJECT_CLASS (acme_volume_alsa_parent_class)->finalize (object);
+}
+
+static void
+acme_volume_alsa_set_mute (AcmeVolume *vol, gboolean val)
+{
+ AcmeVolumeAlsa *self = (AcmeVolumeAlsa *) vol;
+
+ if (acme_volume_alsa_open (self) == FALSE)
+ return;
+
+ /* If we have a hardware mute */
+ if (self->_priv->has_mute)
+ {
+ snd_mixer_selem_set_playback_switch_all
+ (self->_priv->elem, !val);
+ acme_volume_alsa_close (self);
+ return;
+ }
+
+ acme_volume_alsa_close (self);
+
+ /* If we don't */
+ if (val == TRUE)
+ {
+ self->_priv->saved_volume = acme_volume_alsa_get_volume (vol);
+ acme_volume_alsa_set_volume (vol, 0);
+ } else {
+ if (self->_priv->saved_volume != -1)
+ acme_volume_alsa_set_volume (vol,
+ self->_priv->saved_volume);
+ }
+}
+
+static gboolean
+acme_volume_alsa_get_mute (AcmeVolume *vol)
+{
+ AcmeVolumeAlsa *self = (AcmeVolumeAlsa *) vol;
+ int ival;
+
+ if (acme_volume_alsa_open (self) == FALSE)
+ return FALSE;
+
+ if (self->_priv->has_mute)
+ {
+ snd_mixer_selem_get_playback_switch(self->_priv->elem,
+ SND_MIXER_SCHN_FRONT_LEFT, &ival);
+
+ acme_volume_alsa_close (self);
+
+ return !ival;
+ } else {
+ acme_volume_alsa_close (self);
+
+ return (acme_volume_alsa_get_volume (vol) == 0);
+ }
+}
+
+static int
+acme_volume_alsa_get_volume (AcmeVolume *vol)
+{
+ AcmeVolumeAlsa *self = (AcmeVolumeAlsa *) vol;
+ long lval, rval;
+ int tmp;
+ float alsa_vol;
+
+ if (acme_volume_alsa_open (self) == FALSE)
+ return 0;
+
+ snd_mixer_selem_get_playback_volume(self->_priv->elem,
+ SND_MIXER_SCHN_FRONT_LEFT, &lval);
+ snd_mixer_selem_get_playback_volume(self->_priv->elem,
+ SND_MIXER_SCHN_FRONT_RIGHT, &rval);
+
+ acme_volume_alsa_close (self);
+
+ alsa_vol = (lval + rval) / 2;
+ alsa_vol = alsa_vol * 100 / (self->_priv->pmax - self->_priv->pmin);
+ tmp = ROUND (alsa_vol);
+
+ return tmp;
+}
+
+static void
+acme_volume_alsa_set_volume (AcmeVolume *vol, int val)
+{
+ AcmeVolumeAlsa *self = (AcmeVolumeAlsa *) vol;
+ float volume;
+ int tmp;
+
+ if (acme_volume_alsa_open (self) == FALSE)
+ return;
+
+ volume = (float) val / 100 * (self->_priv->pmax - self->_priv->pmin);
+ volume = CLAMP (volume, self->_priv->pmin, self->_priv->pmax);
+ tmp = ROUND (volume);
+
+ snd_mixer_selem_set_playback_volume_all (self->_priv->elem, tmp);
+
+ acme_volume_alsa_close (self);
+}
+
+static int
+acme_volume_alsa_get_threshold (AcmeVolume *vol)
+{
+ AcmeVolumeAlsa *self = (AcmeVolumeAlsa *) vol;
+ int steps;
+
+ if (acme_volume_alsa_open (self) == FALSE)
+ return 1;
+
+ acme_volume_alsa_close (self);
+
+ steps = self->_priv->pmax - self->_priv->pmin;
+ return (steps > 0) ? 100 / steps + 1 : 1;
+}
+
+static gboolean
+acme_volume_alsa_close_real (AcmeVolumeAlsa *self)
+{
+ if (self->_priv == NULL)
+ return FALSE;
+
+ if (self->_priv->handle != NULL)
+ {
+ snd_mixer_detach (self->_priv->handle, DEFAULT_CARD);
+ snd_mixer_free (self->_priv->handle);
+ self->_priv->handle = NULL;
+ self->_priv->elem = NULL;
+ }
+
+ self->_priv->timer_id = 0;
+
+ return FALSE;
+}
+
+static gboolean
+acme_volume_alsa_open (AcmeVolumeAlsa *self)
+{
+ snd_mixer_selem_id_t *sid;
+ snd_mixer_t *handle;
+ snd_mixer_elem_t *elem;
+
+ if (self->_priv->timer_id != 0)
+ {
+ g_source_remove (self->_priv->timer_id);
+ self->_priv->timer_id = 0;
+ return TRUE;
+ }
+
+ /* open the mixer */
+ if (snd_mixer_open (&handle, 0) < 0)
+ {
+ D("snd_mixer_open");
+ return FALSE;
+ }
+ /* attach the handle to the default card */
+ if (snd_mixer_attach (handle, DEFAULT_CARD) <0)
+ {
+ D("snd_mixer_attach");
+ goto bail;
+ }
+ /* ? */
+ if (snd_mixer_selem_register (handle, NULL, NULL) < 0)
+ {
+ D("snd_mixer_selem_register");
+ goto bail;
+ }
+ if (snd_mixer_load (handle) < 0)
+ {
+ D("snd_mixer_load");
+ goto bail;
+ }
+
+ snd_mixer_selem_id_alloca (&sid);
+ snd_mixer_selem_id_set_name (sid, "Master");
+ elem = snd_mixer_find_selem (handle, sid);
+ if (!elem)
+ {
+ snd_mixer_selem_id_alloca (&sid);
+ snd_mixer_selem_id_set_name (sid, "PCM");
+ elem = snd_mixer_find_selem (handle, sid);
+ if (!elem)
+ {
+ D("snd_mixer_find_selem");
+ goto bail;
+ }
+ }
+
+ if (!snd_mixer_selem_has_playback_volume (elem))
+ {
+ D("snd_mixer_selem_has_playback_volume");
+ goto bail;
+ }
+
+ snd_mixer_selem_get_playback_volume_range (elem,
+ &(self->_priv->pmin),
+ &(self->_priv->pmax));
+
+ self->_priv->has_mute = snd_mixer_selem_has_playback_switch (elem);
+ self->_priv->handle = handle;
+ self->_priv->elem = elem;
+
+ return TRUE;
+
+bail:
+ acme_volume_alsa_close_real (self);
+ return FALSE;
+}
+
+static void
+acme_volume_alsa_close (AcmeVolumeAlsa *self)
+{
+ self->_priv->timer_id = g_timeout_add_seconds (4,
+ (GSourceFunc) acme_volume_alsa_close_real, self);
+}
+
+static void
+acme_volume_alsa_init (AcmeVolumeAlsa *self)
+{
+ self->_priv = g_new0 (AcmeVolumeAlsaPrivate, 1);
+}
+
+static void
+acme_volume_alsa_class_init (AcmeVolumeAlsaClass *klass)
+{
+ AcmeVolumeClass *volume_class = ACME_VOLUME_CLASS (klass);
+ G_OBJECT_CLASS (klass)->finalize = acme_volume_alsa_finalize;
+
+ volume_class->set_volume = acme_volume_alsa_set_volume;
+ volume_class->get_volume = acme_volume_alsa_get_volume;
+ volume_class->set_mute = acme_volume_alsa_set_mute;
+ volume_class->get_mute = acme_volume_alsa_get_mute;
+ volume_class->get_threshold = acme_volume_alsa_get_threshold;
+}
+
diff --git a/toolkit/src/sugar/acme-volume-alsa.h b/toolkit/src/sugar/acme-volume-alsa.h
new file mode 100644
index 0000000..b179a24
--- /dev/null
+++ b/toolkit/src/sugar/acme-volume-alsa.h
@@ -0,0 +1,47 @@
+/* acme-volume-alsa.h
+
+ Copyright (C) 2002, 2003 Bastien Nocera
+
+ The Gnome Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Gnome 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Gnome Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Bastien Nocera <hadess@hadess.net>
+ */
+
+#include <glib.h>
+#include <glib-object.h>
+#include "acme-volume.h"
+
+#define ACME_TYPE_VOLUME_ALSA (acme_volume_alsa_get_type ())
+#define ACME_VOLUME_ALSA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ACME_TYPE_VOLUME_ALSA, AcmeVolumeAlsa))
+#define ACME_VOLUME_ALSA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ACME_TYPE_VOLUME_ALSA, AcmeVolumeAlsaClass))
+#define ACME_IS_VOLUME_ALSA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ACME_TYPE_VOLUME_ALSA))
+#define ACME_VOLUME_ALSA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ACME_TYPE_VOLUME_ALSA, AcmeVolumeAlsaClass))
+
+typedef struct AcmeVolumeAlsa AcmeVolumeAlsa;
+typedef struct AcmeVolumeAlsaClass AcmeVolumeAlsaClass;
+typedef struct AcmeVolumeAlsaPrivate AcmeVolumeAlsaPrivate;
+
+struct AcmeVolumeAlsa {
+ AcmeVolume parent;
+ AcmeVolumeAlsaPrivate *_priv;
+};
+
+struct AcmeVolumeAlsaClass {
+ AcmeVolumeClass parent;
+};
+
+GType acme_volume_alsa_get_type (void);
+
diff --git a/toolkit/src/sugar/acme-volume.c b/toolkit/src/sugar/acme-volume.c
new file mode 100644
index 0000000..09ae1d2
--- /dev/null
+++ b/toolkit/src/sugar/acme-volume.c
@@ -0,0 +1,127 @@
+/* acme-volume.c
+
+ Copyright (C) 2002, 2003 Bastien Nocera
+
+ The Gnome Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Gnome 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Gnome Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Bastien Nocera <hadess@hadess.net>
+ */
+
+#ifdef HAVE_CONFIG
+#include "config.h"
+#endif
+#include "acme-volume.h"
+#ifdef HAVE_OSS
+#include "acme-volume-oss.h"
+#endif
+#ifdef HAVE_ALSA
+#include "acme-volume-alsa.h"
+#endif
+#ifdef HAVE_GSTREAMER
+#include "acme-volume-gstreamer.h"
+#endif
+
+G_DEFINE_TYPE (AcmeVolume, acme_volume, G_TYPE_OBJECT)
+
+static void
+acme_volume_class_init (AcmeVolumeClass *klass)
+{
+}
+
+static void
+acme_volume_init (AcmeVolume *vol)
+{
+}
+
+int
+acme_volume_get_volume (AcmeVolume *self)
+{
+ g_return_val_if_fail (self != NULL, 0);
+ g_return_val_if_fail (ACME_IS_VOLUME (self), 0);
+
+ return ACME_VOLUME_GET_CLASS (self)->get_volume (self);
+}
+
+void
+acme_volume_set_volume (AcmeVolume *self, int val)
+{
+ g_return_if_fail (self != NULL);
+ g_return_if_fail (ACME_IS_VOLUME (self));
+
+ ACME_VOLUME_GET_CLASS (self)->set_volume (self, val);
+}
+
+gboolean
+acme_volume_get_mute (AcmeVolume *self)
+{
+ g_return_val_if_fail (self != NULL, FALSE);
+ g_return_val_if_fail (ACME_IS_VOLUME (self), FALSE);
+
+ return ACME_VOLUME_GET_CLASS (self)->get_mute (self);
+}
+
+void
+acme_volume_set_mute (AcmeVolume *self, gboolean val)
+{
+ g_return_if_fail (self != NULL);
+ g_return_if_fail (ACME_IS_VOLUME (self));
+
+ ACME_VOLUME_GET_CLASS (self)->set_mute (self, val);
+}
+
+void
+acme_volume_mute_toggle (AcmeVolume *self)
+{
+ gboolean muted;
+
+ g_return_if_fail (self != NULL);
+ g_return_if_fail (ACME_IS_VOLUME (self));
+
+ muted = ACME_VOLUME_GET_CLASS (self)->get_mute (self);
+ ACME_VOLUME_GET_CLASS (self)->set_mute (self, !muted);
+}
+
+int
+acme_volume_get_threshold (AcmeVolume *self)
+{
+ g_return_val_if_fail (self != NULL, 0);
+ g_return_val_if_fail (ACME_IS_VOLUME (self), 0);
+
+ return ACME_VOLUME_GET_CLASS (self)->get_threshold (self);
+}
+
+AcmeVolume *acme_volume_new (void)
+{
+ AcmeVolume *vol;
+
+#ifdef HAVE_GSTREAMER
+ vol = ACME_VOLUME (g_object_new (acme_volume_gstreamer_get_type (), NULL));
+ return vol;
+#endif
+#ifdef HAVE_ALSA
+ vol = ACME_VOLUME (g_object_new (acme_volume_alsa_get_type (), NULL));
+ if (vol != NULL && ACME_VOLUME_ALSA (vol)->_priv != NULL)
+ return vol;
+ if (ACME_VOLUME_ALSA (vol)->_priv == NULL)
+ g_object_unref (vol);
+#endif
+#ifdef HAVE_OSS
+ vol = ACME_VOLUME (g_object_new (acme_volume_oss_get_type (), NULL));
+ return vol;
+#endif
+ return NULL;
+}
+
diff --git a/toolkit/src/sugar/acme-volume.h b/toolkit/src/sugar/acme-volume.h
new file mode 100644
index 0000000..ec5ee3d
--- /dev/null
+++ b/toolkit/src/sugar/acme-volume.h
@@ -0,0 +1,63 @@
+/* acme-volume.h
+
+ Copyright (C) 2002, 2003 Bastien Nocera
+
+ The Gnome Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Gnome 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with the Gnome Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Bastien Nocera <hadess@hadess.net>
+ */
+
+#ifndef _ACME_VOLUME_H
+#define _ACME_VOLUME_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define ACME_TYPE_VOLUME (acme_volume_get_type ())
+#define ACME_VOLUME(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), ACME_TYPE_VOLUME, AcmeVolume))
+#define ACME_VOLUME_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), ACME_TYPE_VOLUME, AcmeVolumeClass))
+#define ACME_IS_VOLUME(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ACME_TYPE_VOLUME))
+#define ACME_VOLUME_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), ACME_TYPE_VOLUME, AcmeVolumeClass))
+
+typedef struct {
+ GObject parent;
+} AcmeVolume;
+
+typedef struct {
+ GObjectClass parent;
+
+ void (* set_volume) (AcmeVolume *self, int val);
+ int (* get_volume) (AcmeVolume *self);
+ void (* set_mute) (AcmeVolume *self, gboolean val);
+ int (* get_mute) (AcmeVolume *self);
+ int (* get_threshold) (AcmeVolume *self);
+} AcmeVolumeClass;
+
+GType acme_volume_get_type (void);
+int acme_volume_get_volume (AcmeVolume *self);
+void acme_volume_set_volume (AcmeVolume *self, int val);
+gboolean acme_volume_get_mute (AcmeVolume *self);
+void acme_volume_set_mute (AcmeVolume *self,
+ gboolean val);
+void acme_volume_mute_toggle (AcmeVolume *self);
+int acme_volume_get_threshold (AcmeVolume *self);
+AcmeVolume *acme_volume_new (void);
+
+G_END_DECLS
+
+#endif /* _ACME_VOLUME_H */
diff --git a/toolkit/src/sugar/activity/Makefile.am b/toolkit/src/sugar/activity/Makefile.am
new file mode 100644
index 0000000..2c2eff1
--- /dev/null
+++ b/toolkit/src/sugar/activity/Makefile.am
@@ -0,0 +1,12 @@
+sugardir = $(pythondir)/sugar/activity
+sugar_PYTHON = \
+ __init__.py \
+ activity.py \
+ activityfactory.py \
+ activityhandle.py \
+ activityservice.py \
+ bundlebuilder.py \
+ i18n.py \
+ main.py \
+ namingalert.py \
+ widgets.py
diff --git a/toolkit/src/sugar/activity/__init__.py b/toolkit/src/sugar/activity/__init__.py
new file mode 100644
index 0000000..b69646c
--- /dev/null
+++ b/toolkit/src/sugar/activity/__init__.py
@@ -0,0 +1,55 @@
+# Copyright (C) 2006-2007, Red Hat, Inc.
+#
+# 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.
+
+"""Activity implementation code for Sugar-based activities
+
+Each activity within the OLPC environment must provide two
+dbus services. The first, patterned after the
+
+ sugar.activity.activityfactory.ActivityFactory
+
+class is responsible for providing a "create" method which
+takes a small dictionary with values corresponding to a
+
+ sugar.activity.activityhandle.ActivityHandle
+
+describing an individual instance of the activity.
+
+Each activity so registered is described by a
+
+ sugar.activity.bundle.Bundle
+
+instance, which parses a specially formatted activity.info
+file (stored in the activity directory's ./activity
+subdirectory). The
+
+ sugar.activity.bundlebuilder
+
+module provides facilities for the standard setup.py module
+which produces and registers bundles from activity source
+directories.
+
+Once instantiated by the ActivityFactory's create method,
+each activity must provide an introspection API patterned
+after the
+
+ sugar.activity.activityservice.ActivityService
+
+class. This class allows for querying the ID of the root
+window, requesting sharing across the network, and basic
+"what type of application are you" queries.
+"""
diff --git a/toolkit/src/sugar/activity/activity.py b/toolkit/src/sugar/activity/activity.py
new file mode 100644
index 0000000..0bda2ea
--- /dev/null
+++ b/toolkit/src/sugar/activity/activity.py
@@ -0,0 +1,986 @@
+"""Base class for activities written in Python
+
+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.
+
+STABLE.
+"""
+# Copyright (C) 2006-2007 Red Hat, Inc.
+# Copyright (C) 2007-2009 One Laptop Per Child
+# Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# 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 gettext
+import logging
+import os
+import time
+from hashlib import sha1
+from functools import partial
+
+import gconf
+import gtk
+import gobject
+import dbus
+import dbus.service
+from dbus import PROPERTIES_IFACE
+import cjson
+from telepathy.server import DBusProperties
+from telepathy.interfaces import CHANNEL, \
+ CHANNEL_TYPE_TEXT, \
+ CLIENT, \
+ CLIENT_HANDLER
+from telepathy.constants import CONNECTION_HANDLE_TYPE_CONTACT
+
+from sugar import util
+from sugar.presence import presenceservice
+from sugar.activity.activityservice import ActivityService
+from sugar.activity.namingalert import NamingAlert
+from sugar.graphics import style
+from sugar.graphics.window import Window
+from sugar.graphics.alert import Alert
+from sugar.graphics.icon import Icon
+from sugar.datastore import datastore
+from sugar.session import XSMPClient
+from sugar import wm
+
+# support deprecated imports
+from sugar.activity.widgets import ActivityToolbar, EditToolbar
+from sugar.activity.widgets import ActivityToolbox
+
+
+_ = lambda msg: gettext.dgettext('sugar-toolkit', msg)
+
+SCOPE_PRIVATE = "private"
+SCOPE_INVITE_ONLY = "invite" # shouldn't be shown in UI, it's implicit
+SCOPE_NEIGHBORHOOD = "public"
+
+J_DBUS_SERVICE = 'org.laptop.Journal'
+J_DBUS_PATH = '/org/laptop/Journal'
+J_DBUS_INTERFACE = 'org.laptop.Journal'
+
+CONN_INTERFACE_ACTIVITY_PROPERTIES = 'org.laptop.Telepathy.ActivityProperties'
+
+class _ActivitySession(gobject.GObject):
+
+ __gsignals__ = {
+ 'quit-requested': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
+ 'quit': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
+ }
+
+ def __init__(self):
+ gobject.GObject.__init__(self)
+
+ self._xsmp_client = XSMPClient()
+ self._xsmp_client.connect('quit-requested',
+ self.__sm_quit_requested_cb)
+ self._xsmp_client.connect('quit', self.__sm_quit_cb)
+ self._xsmp_client.startup()
+
+ self._activities = []
+ self._will_quit = []
+
+ def register(self, activity):
+ self._activities.append(activity)
+
+ def unregister(self, activity):
+ self._activities.remove(activity)
+
+ if len(self._activities) == 0:
+ logging.debug('Quitting the activity process.')
+ gtk.main_quit()
+
+ def will_quit(self, activity, will_quit):
+ if will_quit:
+ self._will_quit.append(activity)
+
+ # We can quit only when all the instances agreed to
+ for activity in self._activities:
+ if activity not in self._will_quit:
+ return
+
+ self._xsmp_client.will_quit(True)
+ else:
+ self._will_quit = []
+ self._xsmp_client.will_quit(False)
+
+ def __sm_quit_requested_cb(self, client):
+ self.emit('quit-requested')
+
+ def __sm_quit_cb(self, client):
+ self.emit('quit')
+
+
+class Activity(Window, gtk.Container):
+ """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__ = {
+ 'shared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
+ 'joined': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
+ }
+
+ def __init__(self, handle, create_jobject=True):
+ """Initialise the Activity
+
+ handle -- sugar.activity.activityhandle.ActivityHandle
+ instance providing the activity id and access to the
+ presence service which *may* provide sharing for this
+ application
+
+ create_jobject -- boolean
+ define if it should create a journal object if we are
+ not resuming
+
+ Side effects:
+
+ Sets the gdk screen DPI setting (resolution) to the
+ Sugar screen resolution.
+
+ Connects our "destroy" message to our _destroy_cb
+ method.
+
+ Creates a base gtk.Window within this window.
+
+ 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)
+
+ if os.environ.has_key('SUGAR_ACTIVITY_ROOT'):
+ # If this activity runs inside Sugar, we want it to take all the
+ # screen. Would be better if it was the shell to do this, but we
+ # haven't found yet a good way to do it there. See #1263.
+ self.connect('window-state-event', self.__window_state_event_cb)
+ screen = gtk.gdk.screen_get_default()
+ screen.connect('size-changed', self.__screen_size_changed_cb)
+ self._adapt_window_to_screen()
+
+ # process titles will only show 15 characters
+ # but they get truncated anyway so if more characters
+ # are supported in the future we will get a better view
+ # of the processes
+ proc_title = "%s <%s>" % (get_bundle_name(), handle.activity_id)
+ util.set_proc_title(proc_title)
+
+ self.connect('realize', self.__realize_cb)
+ self.connect('delete-event', self.__delete_event_cb)
+
+ self._active = False
+ self._activity_id = handle.activity_id
+ self.shared_activity = None
+ self._join_id = None
+ self._updating_jobject = False
+ self._closing = False
+ self._quit_requested = False
+ self._deleting = False
+ self._max_participants = 0
+ self._invites_queue = []
+ self._jobject = None
+ self._read_file_called = False
+
+ self._session = _get_session()
+ self._session.register(self)
+ self._session.connect('quit-requested',
+ self.__session_quit_requested_cb)
+ self._session.connect('quit', self.__session_quit_cb)
+
+ accel_group = gtk.AccelGroup()
+ self.set_data('sugar-accel-group', accel_group)
+ self.add_accel_group(accel_group)
+
+ self._bus = ActivityService(self)
+ self._owns_file = False
+
+ share_scope = SCOPE_PRIVATE
+
+ if handle.object_id:
+ self._jobject = datastore.get(handle.object_id)
+ self.set_title(self._jobject.metadata['title'])
+
+ if self._jobject.metadata.has_key('share-scope'):
+ share_scope = self._jobject.metadata['share-scope']
+
+ self.shared_activity = None
+ self._join_id = None
+
+ if handle.invited:
+ wait_loop = gobject.MainLoop()
+ self._client_handler = _ClientHandler(
+ self.get_bundle_id(),
+ partial(self.__got_channel_cb, wait_loop))
+ # FIXME: The current API requires that self.shared_activity is set
+ # before exiting from __init__, so we wait until we have got the
+ # shared activity. http://bugs.sugarlabs.org/ticket/2168
+ wait_loop.run()
+ else:
+ pservice = presenceservice.get_instance()
+ mesh_instance = pservice.get_activity(self._activity_id,
+ warn_if_none=False)
+ self._set_up_sharing(mesh_instance, share_scope)
+
+ if handle.object_id is None and create_jobject:
+ logging.debug('Creating a jobject.')
+ self._jobject = self._initialize_journal_object()
+ self.set_title(self._jobject.metadata['title'])
+
+ def _initialize_journal_object(self):
+ title = _('%s Activity') % get_bundle_name()
+
+ if self.shared_activity is not None:
+ icon_color = self.shared_activity.props.color
+ else:
+ client = gconf.client_get_default()
+ icon_color = client.get_string('/desktop/sugar/user/color')
+
+ jobject = datastore.create()
+ jobject.metadata['title'] = title
+ jobject.metadata['title_set_by_user'] = '0'
+ jobject.metadata['activity'] = self.get_bundle_id()
+ jobject.metadata['activity_id'] = self.get_id()
+ jobject.metadata['keep'] = '0'
+ jobject.metadata['preview'] = ''
+ jobject.metadata['share-scope'] = SCOPE_PRIVATE
+ jobject.metadata['icon-color'] = icon_color
+ jobject.file_path = ''
+
+ # FIXME: We should be able to get an ID synchronously from the DS,
+ # then call async the actual create.
+ # http://bugs.sugarlabs.org/ticket/2169
+ datastore.write(jobject)
+
+ return jobject
+
+ def _set_up_sharing(self, mesh_instance, share_scope):
+ # handle activity share/join
+ logging.debug("*** Act %s, mesh instance %r, scope %s",
+ self._activity_id, mesh_instance, share_scope)
+ if mesh_instance is not None:
+ # There's already an instance on the mesh, join it
+ logging.debug("*** Act %s joining existing mesh instance %r",
+ self._activity_id, mesh_instance)
+ self.shared_activity = mesh_instance
+ self.shared_activity.connect('notify::private',
+ self.__privacy_changed_cb)
+ self._join_id = self.shared_activity.connect("joined",
+ self.__joined_cb)
+ if not self.shared_activity.props.joined:
+ self.shared_activity.join()
+ else:
+ self.__joined_cb(self.shared_activity, True, None)
+ elif share_scope != SCOPE_PRIVATE:
+ logging.debug('*** Act %s no existing mesh instance, but used to '
+ 'be shared, will share', self._activity_id)
+ # no existing mesh instance, but activity used to be shared, so
+ # restart the share
+ if share_scope == SCOPE_INVITE_ONLY:
+ self.share(private=True)
+ elif share_scope == SCOPE_NEIGHBORHOOD:
+ self.share(private=False)
+ else:
+ logging.debug('Unknown share scope %r', share_scope)
+
+ def __got_channel_cb(self, wait_loop, connection_path, channel_path):
+ logging.debug('Activity.__got_channel_cb')
+ connection_name = connection_path.replace('/', '.')[1:]
+
+ bus = dbus.SessionBus()
+ channel = bus.get_object(connection_name, channel_path)
+ room_handle = channel.Get(CHANNEL, 'TargetHandle')
+
+ pservice = presenceservice.get_instance()
+ mesh_instance = pservice.get_activity_by_handle(connection_path,
+ room_handle)
+ self._set_up_sharing(mesh_instance, SCOPE_PRIVATE)
+ wait_loop.quit()
+
+ def get_active(self):
+ return self._active
+
+ def set_active(self, active):
+ if self._active != active:
+ self._active = active
+ if not self._active and self._jobject:
+ self.save()
+
+ active = gobject.property(
+ type=bool, default=False, getter=get_active, setter=set_active)
+
+ def get_max_participants(self):
+ return self._max_participants
+
+ def set_max_participants(self, participants):
+ self._max_participants = participants
+
+ max_participants = gobject.property(
+ type=int, default=0, getter=get_max_participants,
+ setter=set_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 get_canvas(self):
+ return Window.get_canvas(self)
+
+ 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)
+ if not self._read_file_called:
+ canvas.connect('map', self.__canvas_map_cb)
+
+ canvas = property(get_canvas, set_canvas)
+
+ def __screen_size_changed_cb(self, screen):
+ self._adapt_window_to_screen()
+
+ def __window_state_event_cb(self, window, event):
+ self.move(0, 0)
+
+ def _adapt_window_to_screen(self):
+ screen = gtk.gdk.screen_get_default()
+ self.set_geometry_hints(None,
+ screen.get_width(), screen.get_height(),
+ screen.get_width(), screen.get_height(),
+ screen.get_width(), screen.get_height(),
+ 1, 1, 1, 1)
+
+ def __session_quit_requested_cb(self, session):
+ self._quit_requested = True
+
+ if self._prepare_close() and not self._updating_jobject:
+ session.will_quit(self, True)
+
+ def __session_quit_cb(self, client):
+ self._complete_close()
+
+ def __canvas_map_cb(self, canvas):
+ logging.debug('Activity.__canvas_map_cb')
+ if self._jobject and self._jobject.file_path and \
+ not self._read_file_called:
+ self.read_file(self._jobject.file_path)
+ self._read_file_called = True
+ canvas.disconnect_by_func(self.__canvas_map_cb)
+
+ def __jobject_create_cb(self):
+ pass
+
+ def __jobject_error_cb(self, err):
+ logging.debug('Error creating activity datastore object: %s', err)
+
+ def get_activity_root(self):
+ """ FIXME: Deprecated. This part of the API has been moved
+ out of this class to the module itself
+
+ 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']:
+ return os.environ['SUGAR_ACTIVITY_ROOT']
+ else:
+ return '/'
+
+ def read_file(self, file_path):
+ """
+ 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
+
+ def write_file(self, file_path):
+ """
+ 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
+
+ def __save_cb(self):
+ logging.debug('Activity.__save_cb')
+ self._updating_jobject = False
+ if self._quit_requested:
+ self._session.will_quit(self, True)
+ elif self._closing:
+ self._complete_close()
+
+ def __save_error_cb(self, err):
+ logging.debug('Activity.__save_error_cb')
+ self._updating_jobject = False
+ if self._quit_requested:
+ self._session.will_quit(self, False)
+ if self._closing:
+ self._show_keep_failed_dialog()
+ self._closing = False
+ raise RuntimeError('Error saving activity object to datastore: %s', err)
+
+ def _cleanup_jobject(self):
+ if self._jobject:
+ if self._owns_file and os.path.isfile(self._jobject.file_path):
+ logging.debug('_cleanup_jobject: removing %r',
+ self._jobject.file_path)
+ os.remove(self._jobject.file_path)
+ self._owns_file = False
+ self._jobject.destroy()
+ self._jobject = None
+
+ def get_preview(self):
+ """Returns an image representing the state of the activity. Generally
+ this is what the user is seeing in this moment.
+
+ Activities can override this method, which should return a str with the
+ binary content of a png image with a width of 300 and a height of 225
+ pixels.
+ """
+ if self.canvas is None or not hasattr(self.canvas, 'get_snapshot'):
+ return None
+ pixmap = self.canvas.get_snapshot((-1, -1, 0, 0))
+
+ width, height = pixmap.get_size()
+ pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, 0, 8, width, height)
+ pixbuf = pixbuf.get_from_drawable(pixmap, pixmap.get_colormap(),
+ 0, 0, 0, 0, width, height)
+ pixbuf = pixbuf.scale_simple(style.zoom(300), style.zoom(225),
+ gtk.gdk.INTERP_BILINEAR)
+
+ preview_data = []
+
+ def save_func(buf, data):
+ data.append(buf)
+
+ pixbuf.save_to_callback(save_func, 'png', user_data=preview_data)
+ preview_data = ''.join(preview_data)
+
+ return preview_data
+
+ def _get_buddies(self):
+ if self.shared_activity is not None:
+ buddies = {}
+ for buddy in self.shared_activity.get_joined_buddies():
+ if not buddy.props.owner:
+ buddy_id = sha1(buddy.props.key).hexdigest()
+ buddies[buddy_id] = [buddy.props.nick, buddy.props.color]
+ return buddies
+ else:
+ return {}
+
+ def save(self):
+ """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.
+ """
+
+ if self._jobject is None:
+ logging.debug('Cannot save, no journal object.')
+ return
+
+ logging.debug('Activity.save: %r', self._jobject.object_id)
+
+ if self._updating_jobject:
+ logging.info('Activity.save: still processing a previous request.')
+ return
+
+ buddies_dict = self._get_buddies()
+ if buddies_dict:
+ self.metadata['buddies_id'] = cjson.encode(buddies_dict.keys())
+ self.metadata['buddies'] = cjson.encode(self._get_buddies())
+
+ preview = self.get_preview()
+ if preview is not None:
+ self.metadata['preview'] = dbus.ByteArray(preview)
+
+ if not self.metadata.get('activity_id', ''):
+ self.metadata['activity_id'] = self.get_id()
+
+ file_path = os.path.join(self.get_activity_root(), 'instance',
+ '%i' % time.time())
+ try:
+ self.write_file(file_path)
+ except NotImplementedError:
+ logging.debug('Activity.write_file is not implemented.')
+ else:
+ if os.path.exists(file_path):
+ self._owns_file = True
+ self._jobject.file_path = file_path
+
+ # Cannot call datastore.write async for creates:
+ # https://dev.laptop.org/ticket/3071
+ if self._jobject.object_id is None:
+ datastore.write(self._jobject, transfer_ownership=True)
+ else:
+ self._updating_jobject = True
+ datastore.write(self._jobject,
+ transfer_ownership=True,
+ reply_handler=self.__save_cb,
+ 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.save()
+ self._jobject.object_id = None
+
+ def __privacy_changed_cb(self, shared_activity, param_spec):
+ logging.debug('__privacy_changed_cb %r', shared_activity.props.private)
+ if shared_activity.props.private:
+ self._jobject.metadata['share-scope'] = SCOPE_INVITE_ONLY
+ else:
+ self._jobject.metadata['share-scope'] = SCOPE_NEIGHBORHOOD
+
+ def __joined_cb(self, activity, success, err):
+ """Callback when join has finished"""
+ logging.debug('Activity.__joined_cb %r', success)
+ self.shared_activity.disconnect(self._join_id)
+ self._join_id = None
+ if not success:
+ logging.debug('Failed to join activity: %s', err)
+ return
+
+ self.reveal()
+ self.emit('joined')
+ self.__privacy_changed_cb(self.shared_activity, None)
+
+ def get_shared_activity(self):
+ """Returns an instance of the shared Activity or None
+
+ The shared activity is of type sugar.presence.activity.Activity
+ """
+ return self._shared_activity
+
+ def get_shared(self):
+ """Returns TRUE if the activity is shared on the mesh."""
+ if not self.shared_activity:
+ return False
+ return self.shared_activity.props.joined
+
+ def __share_cb(self, ps, success, activity, err):
+ if not success:
+ logging.debug('Share of activity %s failed: %s.',
+ self._activity_id, err)
+ return
+
+ logging.debug('Share of activity %s successful, PS activity is %r.',
+ self._activity_id, activity)
+
+ activity.props.name = self._jobject.metadata['title']
+
+ self.shared_activity = activity
+ self.shared_activity.connect('notify::private',
+ self.__privacy_changed_cb)
+ self.emit('shared')
+ self.__privacy_changed_cb(self.shared_activity, None)
+
+ self._send_invites()
+
+ def _invite_response_cb(self, error):
+ if error:
+ logging.error('Invite failed: %s', error)
+
+ def _send_invites(self):
+ while self._invites_queue:
+ account_path, contact_id = self._invites_queue.pop()
+ pservice = presenceservice.get_instance()
+ buddy = pservice.get_buddy(account_path, contact_id)
+ if buddy:
+ self.shared_activity.invite(
+ buddy, '', self._invite_response_cb)
+ else:
+ logging.error('Cannot invite %s %s, no such buddy',
+ account_path, contact_id)
+
+ def invite(self, account_path, contact_id):
+ """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((account_path, contact_id))
+
+ if (self.shared_activity is None
+ or not self.shared_activity.props.joined):
+ self.share(True)
+ else:
+ self._send_invites()
+
+ def share(self, private=False):
+ """Request that the activity be shared on the network.
+
+ private -- bool: True to share by invitation only,
+ False to advertise as shared to everyone.
+
+ Once the activity is shared, its privacy can be changed by setting
+ its 'private' property.
+ """
+ if self.shared_activity and self.shared_activity.props.joined:
+ raise RuntimeError("Activity %s already shared." %
+ self._activity_id)
+ verb = private and 'private' or 'public'
+ logging.debug('Requesting %s share of activity %s.', verb,
+ self._activity_id)
+ pservice = presenceservice.get_instance()
+ pservice.connect('activity-shared', self.__share_cb)
+ pservice.share_activity(self, private=private)
+
+ def _show_keep_failed_dialog(self):
+ alert = Alert()
+ alert.props.title = _('Keep error')
+ alert.props.msg = _('Keep error: all changes will be lost')
+
+ cancel_icon = Icon(icon_name='dialog-cancel')
+ alert.add_button(gtk.RESPONSE_CANCEL, _('Don\'t stop'), cancel_icon)
+
+ stop_icon = Icon(icon_name='dialog-ok')
+ alert.add_button(gtk.RESPONSE_OK, _('Stop anyway'), stop_icon)
+
+ self.add_alert(alert)
+ alert.connect('response', self._keep_failed_dialog_response_cb)
+
+ self.reveal()
+
+ def _keep_failed_dialog_response_cb(self, alert, response_id):
+ self.remove_alert(alert)
+ if response_id == gtk.RESPONSE_OK:
+ self.close(skip_save=True)
+ if self._quit_requested:
+ self._session.will_quit(self, True)
+ elif self._quit_requested:
+ self._session.will_quit(self, False)
+
+ def can_close(self):
+ """Activities should override this function if they want to perform
+ extra checks before actually closing."""
+
+ return True
+
+ def _prepare_close(self, skip_save=False):
+ if not skip_save:
+ try:
+ self.save()
+ except:
+ logging.exception('Error saving activity object to datastore')
+ self._show_keep_failed_dialog()
+ return False
+
+ self._closing = True
+
+ return True
+
+ def _complete_close(self):
+ self.destroy()
+
+ if self.shared_activity:
+ self.shared_activity.leave()
+
+ self._cleanup_jobject()
+
+ # Make the exported object inaccessible
+ dbus.service.Object.remove_from_connection(self._bus)
+
+ self._session.unregister(self)
+
+ def close(self, skip_save=False):
+ """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. If the application wants
+ to control wether it can close, it should override can_close().
+ """
+ if not self.can_close():
+ return
+
+ if skip_save or self._jobject is None or \
+ self.metadata.get('title_set_by_user', '0') == '1':
+ if not self._closing:
+ if not self._prepare_close(skip_save):
+ return
+
+ if not self._updating_jobject:
+ self._complete_close()
+ else:
+ title_alert = NamingAlert(self, get_bundle_path())
+ title_alert.set_transient_for(self.get_toplevel())
+ title_alert.show()
+ self.reveal()
+
+ def __realize_cb(self, window):
+ wm.set_bundle_id(window.window, self.get_bundle_id())
+ wm.set_activity_id(window.window, str(self._activity_id))
+
+ def __delete_event_cb(self, widget, event):
+ self.close()
+ 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:
+ return None
+
+ metadata = property(get_metadata, None)
+
+ def handle_view_source(self):
+ raise NotImplementedError
+
+ def get_document_path(self, async_cb, async_err_cb):
+ async_err_cb(NotImplementedError())
+
+ # DEPRECATED
+ _shared_activity = property(lambda self: self.shared_activity, None)
+
+
+class _ClientHandler(dbus.service.Object, DBusProperties):
+ def __init__(self, bundle_id, got_channel_cb):
+ self._interfaces = set([CLIENT, CLIENT_HANDLER, PROPERTIES_IFACE])
+ self._got_channel_cb = got_channel_cb
+
+ bus = dbus.Bus()
+ name = CLIENT + '.' + bundle_id
+ bus_name = dbus.service.BusName(name, bus=bus)
+
+ path = '/' + name.replace('.', '/')
+ dbus.service.Object.__init__(self, bus_name, path)
+ DBusProperties.__init__(self)
+
+ self._implement_property_get(CLIENT, {
+ 'Interfaces': lambda: list(self._interfaces),
+ })
+ self._implement_property_get(CLIENT_HANDLER, {
+ 'HandlerChannelFilter': self.__get_filters_cb,
+ })
+
+ def __get_filters_cb(self):
+ logging.debug('__get_filters_cb')
+ filters = {
+ CHANNEL + '.ChannelType' : CHANNEL_TYPE_TEXT,
+ CHANNEL + '.TargetHandleType': CONNECTION_HANDLE_TYPE_CONTACT,
+ }
+ filter_dict = dbus.Dictionary(filters, signature='sv')
+ logging.debug('__get_filters_cb %r', dbus.Array([filter_dict],
+ signature='a{sv}'))
+ return dbus.Array([filter_dict], signature='a{sv}')
+
+ @dbus.service.method(dbus_interface=CLIENT_HANDLER,
+ in_signature='ooa(oa{sv})aota{sv}', out_signature='')
+ def HandleChannels(self, account, connection, channels, requests_satisfied,
+ user_action_time, handler_info):
+ logging.debug('HandleChannels\n\t%r\n\t%r\n\t%r\n\t%r\n\t%r\n\t%r',
+ account, connection, channels, requests_satisfied,
+ user_action_time, handler_info)
+ try:
+ for channel in channels:
+ self._got_channel_cb(connection, channel[0])
+ except Exception, e:
+ logging.exception(e)
+
+_session = None
+
+
+def _get_session():
+ global _session
+
+ if _session is None:
+ _session = _ActivitySession()
+
+ return _session
+
+
+def get_bundle_name():
+ """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 os.environ['SUGAR_BUNDLE_PATH']
+
+
+def get_activity_root():
+ """Returns a path for saving Activity specific preferences, etc."""
+ if os.environ.has_key('SUGAR_ACTIVITY_ROOT') and \
+ os.environ['SUGAR_ACTIVITY_ROOT']:
+ return os.environ['SUGAR_ACTIVITY_ROOT']
+ else:
+ raise RuntimeError("No SUGAR_ACTIVITY_ROOT set.")
+
+
+def show_object_in_journal(object_id):
+ bus = dbus.SessionBus()
+ obj = bus.get_object(J_DBUS_SERVICE, J_DBUS_PATH)
+ journal = dbus.Interface(obj, J_DBUS_INTERFACE)
+ journal.ShowObject(object_id)
diff --git a/toolkit/src/sugar/activity/activityfactory.py b/toolkit/src/sugar/activity/activityfactory.py
new file mode 100644
index 0000000..46dc346
--- /dev/null
+++ b/toolkit/src/sugar/activity/activityfactory.py
@@ -0,0 +1,374 @@
+# Copyright (C) 2006-2007 Red Hat, Inc.
+# Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# 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.
+
+"""Shell side object which manages request to start activity
+
+UNSTABLE. Activities are currently not allowed to run other activities so at
+the moment there is no reason to stabilize this API.
+"""
+
+import logging
+import uuid
+
+import dbus
+import gobject
+
+from sugar.activity.activityhandle import ActivityHandle
+from sugar import util
+from sugar import env
+from sugar.datastore import datastore
+
+from errno import EEXIST, ENOSPC
+
+import os
+import tempfile
+import subprocess
+import pwd
+
+_SHELL_SERVICE = "org.laptop.Shell"
+_SHELL_PATH = "/org/laptop/Shell"
+_SHELL_IFACE = "org.laptop.Shell"
+
+_ACTIVITY_FACTORY_INTERFACE = "org.laptop.ActivityFactory"
+
+# helper method to close all filedescriptors
+# borrowed from subprocess.py
+try:
+ MAXFD = os.sysconf("SC_OPEN_MAX")
+except ValueError:
+ MAXFD = 256
+
+
+def _close_fds():
+ for i in xrange(3, MAXFD):
+ try:
+ os.close(i)
+ # pylint: disable-msg=W0704
+ except Exception:
+ pass
+
+
+def create_activity_id():
+ """Generate a new, unique ID for this activity"""
+ return util.unique_id(uuid.getnode())
+
+
+def get_environment(activity):
+ environ = os.environ.copy()
+
+ bin_path = os.path.join(activity.get_path(), 'bin')
+
+ activity_root = env.get_profile_path(activity.get_bundle_id())
+ if not os.path.exists(activity_root):
+ os.mkdir(activity_root)
+
+ data_dir = os.path.join(activity_root, 'instance')
+ if not os.path.exists(data_dir):
+ os.mkdir(data_dir)
+
+ data_dir = os.path.join(activity_root, 'data')
+ if not os.path.exists(data_dir):
+ os.mkdir(data_dir)
+
+ tmp_dir = os.path.join(activity_root, 'tmp')
+ if not os.path.exists(tmp_dir):
+ os.mkdir(tmp_dir)
+
+ environ['SUGAR_BUNDLE_PATH'] = activity.get_path()
+ environ['SUGAR_BUNDLE_ID'] = activity.get_bundle_id()
+ environ['SUGAR_ACTIVITY_ROOT'] = activity_root
+ environ['PATH'] = bin_path + ':' + environ['PATH']
+
+ if activity.get_path().startswith(env.get_user_activities_path()):
+ environ['SUGAR_LOCALEDIR'] = os.path.join(activity.get_path(),
+ 'locale')
+
+ return environ
+
+
+def get_command(activity, activity_id=None, object_id=None, uri=None,
+ activity_invite=False):
+ if not activity_id:
+ activity_id = create_activity_id()
+
+ command = activity.get_command().split(' ')
+ command.extend(['-b', activity.get_bundle_id()])
+ command.extend(['-a', activity_id])
+
+ if object_id is not None:
+ command.extend(['-o', object_id])
+ if uri is not None:
+ command.extend(['-u', uri])
+ if activity_invite:
+ command.append('-i')
+
+ # if the command is in $BUNDLE_ROOT/bin, execute the absolute path so there
+ # is no need to mangle with the shell's PATH
+ if '/' not in command[0]:
+ bin_path = os.path.join(activity.get_path(), 'bin')
+ absolute_path = os.path.join(bin_path, command[0])
+ if os.path.exists(absolute_path):
+ command[0] = absolute_path
+
+ logging.debug('launching: %r', command)
+
+ return command
+
+
+def open_log_file(activity):
+ i = 1
+ while True:
+ path = env.get_logs_path('%s-%s.log' % (activity.get_bundle_id(), i))
+ try:
+ fd = os.open(path, os.O_EXCL | os.O_CREAT | os.O_WRONLY, 0644)
+ f = os.fdopen(fd, 'w', 0)
+ return (path, f)
+ except OSError, e:
+ if e.errno == EEXIST:
+ i += 1
+ elif e.errno == ENOSPC:
+ # not the end of the world; let's try to keep going.
+ return ('/dev/null', open('/dev/null', 'w'))
+ else:
+ raise e
+
+
+class ActivityCreationHandler(gobject.GObject):
+ """Sugar-side activity creation interface
+
+ This object uses a dbus method on the ActivityFactory
+ service to create the new activity. It generates
+ GObject events in response to the success/failure of
+ activity startup using callbacks to the service's
+ create call.
+ """
+
+ def __init__(self, bundle, handle):
+ """Initialise the handler
+
+ bundle -- the ActivityBundle to launch
+ activity_handle -- stores the values which are to
+ be passed to the service to uniquely identify
+ the activity to be created and the sharing
+ service that may or may not be connected with it
+
+ sugar.activity.activityhandle.ActivityHandle instance
+
+ calls the "create" method on the service for this
+ particular activity type and registers the
+ _reply_handler and _error_handler methods on that
+ call's results.
+
+ The specific service which creates new instances of this
+ particular type of activity is created during the activity
+ registration process in shell bundle registry which creates
+ service definition files for each registered bundle type.
+
+ If the file '/etc/olpc-security' exists, then activity launching
+ will be delegated to the prototype 'Rainbow' security service.
+ """
+ gobject.GObject.__init__(self)
+
+ self._bundle = bundle
+ self._service_name = bundle.get_bundle_id()
+ self._handle = handle
+
+ bus = dbus.SessionBus()
+ bus_object = bus.get_object(_SHELL_SERVICE, _SHELL_PATH)
+ self._shell = dbus.Interface(bus_object, _SHELL_IFACE)
+
+ if handle.activity_id is not None and handle.object_id is None:
+ datastore.find({'activity_id': self._handle.activity_id},
+ reply_handler=self._find_object_reply_handler,
+ error_handler=self._find_object_error_handler)
+ else:
+ self._launch_activity()
+
+ def _launch_activity(self):
+ if self._handle.activity_id != None:
+ self._shell.ActivateActivity(self._handle.activity_id,
+ reply_handler=self._activate_reply_handler,
+ error_handler=self._activate_error_handler)
+ else:
+ self._create_activity()
+
+ def _create_activity(self):
+ if self._handle.activity_id is None:
+ self._handle.activity_id = create_activity_id()
+
+ self._shell.NotifyLaunch(
+ self._service_name, self._handle.activity_id,
+ reply_handler=self._no_reply_handler,
+ error_handler=self._notify_launch_error_handler)
+
+ environ = get_environment(self._bundle)
+ (log_path, log_file) = open_log_file(self._bundle)
+ command = get_command(self._bundle, self._handle.activity_id,
+ self._handle.object_id, self._handle.uri,
+ self._handle.invited)
+
+ dev_null = file('/dev/null', 'w')
+ environment_dir = None
+ rainbow_found = subprocess.call(['which', 'rainbow-run'],
+ stdout=dev_null, stderr=dev_null) == 0
+ use_rainbow = rainbow_found and os.path.exists('/etc/olpc-security')
+ if use_rainbow:
+ environment_dir = tempfile.mkdtemp()
+ command = ['sudo', '-E', '--',
+ 'rainbow-run',
+ '-v', '-v',
+ '-a', 'rainbow-sugarize',
+ '-s', '/var/spool/rainbow/2',
+ '-f', '1',
+ '-f', '2',
+ '-c', self._bundle.get_path(),
+ '-u', pwd.getpwuid(os.getuid()).pw_name,
+ '-i', environ['SUGAR_BUNDLE_ID'],
+ '-e', environment_dir,
+ '--',
+ ] + command
+
+ for key, value in environ.items():
+ file_path = os.path.join(environment_dir, str(key))
+ open(file_path, 'w').write(str(value))
+
+ log_file.write(' '.join(command) + '\n\n')
+
+ dev_null = file('/dev/null', 'r')
+ child = subprocess.Popen([str(s) for s in command],
+ env=environ,
+ cwd=str(self._bundle.get_path()),
+ close_fds=True,
+ stdin=dev_null.fileno(),
+ stdout=log_file.fileno(),
+ stderr=log_file.fileno())
+
+ gobject.child_watch_add(child.pid,
+ _child_watch_cb,
+ (environment_dir, log_file,
+ self._handle.activity_id))
+
+ def _no_reply_handler(self, *args):
+ pass
+
+ def _notify_launch_failure_error_handler(self, err):
+ logging.error('Notify launch failure failed %s', err)
+
+ def _notify_launch_error_handler(self, err):
+ logging.debug('Notify launch failed %s', err)
+
+ def _activate_reply_handler(self, activated):
+ if not activated:
+ self._create_activity()
+
+ def _activate_error_handler(self, err):
+ logging.error('Activity activation request failed %s', err)
+
+ def _create_reply_handler(self):
+ logging.debug('Activity created %s (%s).',
+ self._handle.activity_id, self._service_name)
+
+ def _create_error_handler(self, err):
+ logging.error("Couldn't create activity %s (%s): %s",
+ self._handle.activity_id, self._service_name, err)
+ self._shell.NotifyLaunchFailure(
+ self._handle.activity_id, reply_handler=self._no_reply_handler,
+ error_handler=self._notify_launch_failure_error_handler)
+
+ def _find_object_reply_handler(self, jobjects, count):
+ if count > 0:
+ if count > 1:
+ logging.debug("Multiple objects has the same activity_id.")
+ self._handle.object_id = jobjects[0]['uid']
+ self._launch_activity()
+
+ def _find_object_error_handler(self, err):
+ logging.error('Datastore find failed %s', err)
+ self._launch_activity()
+
+
+def create(bundle, activity_handle=None):
+ """Create a new activity from its name."""
+ if not activity_handle:
+ activity_handle = ActivityHandle()
+ return ActivityCreationHandler(bundle, activity_handle)
+
+
+def create_with_uri(bundle, uri):
+ """Create a new activity and pass the uri as handle."""
+ activity_handle = ActivityHandle(uri=uri)
+ return ActivityCreationHandler(bundle, activity_handle)
+
+
+def create_with_object_id(bundle, object_id):
+ """Create a new activity and pass the object id as handle."""
+ activity_handle = ActivityHandle(object_id=object_id)
+ return ActivityCreationHandler(bundle, activity_handle)
+
+
+def _child_watch_cb(pid, condition, user_data):
+ # FIXME we use standalone method here instead of ActivityCreationHandler's
+ # member to have workaround code, see #1123
+ environment_dir, log_file, activity_id = user_data
+ if environment_dir is not None:
+ subprocess.call(['/bin/rm', '-rf', environment_dir])
+
+ if os.WIFEXITED(condition):
+ status = os.WEXITSTATUS(condition)
+ signum = None
+ message = 'Exited with status %s' % status
+ elif os.WIFSIGNALED(condition):
+ status = None
+ signum = os.WTERMSIG(condition)
+ message = 'Terminated by signal %s' % signum
+ else:
+ status = None
+ signum = os.WTERMSIG(condition)
+ message = 'Undefined status with signal %s' % signum
+
+ try:
+ log_file.write('%s, pid %s data %s\n' % (message, pid, user_data))
+ finally:
+ log_file.close()
+
+ # try to reap zombies in case SIGCHLD has not been set to SIG_IGN
+ try:
+ os.waitpid(pid, 0)
+ except OSError:
+ # SIGCHLD = SIG_IGN, no zombies
+ pass
+
+ if status or signum:
+ # XXX have to recreate dbus object since we can't reuse
+ # ActivityCreationHandler's one, see
+ # https://bugs.freedesktop.org/show_bug.cgi?id=23507
+ bus = dbus.SessionBus()
+ bus_object = bus.get_object(_SHELL_SERVICE, _SHELL_PATH)
+ shell = dbus.Interface(bus_object, _SHELL_IFACE)
+
+ def reply_handler_cb(*args):
+ pass
+
+ def error_handler_cb(error):
+ logging.error('Cannot send NotifyLaunchFailure to the shell')
+
+ # TODO send launching failure but activity could already show
+ # main window, see http://bugs.sugarlabs.org/ticket/1447#comment:19
+ shell.NotifyLaunchFailure(activity_id,
+ reply_handler=reply_handler_cb,
+ error_handler=error_handler_cb)
diff --git a/toolkit/src/sugar/activity/activityhandle.py b/toolkit/src/sugar/activity/activityhandle.py
new file mode 100644
index 0000000..4aeac71
--- /dev/null
+++ b/toolkit/src/sugar/activity/activityhandle.py
@@ -0,0 +1,75 @@
+# Copyright (C) 2006-2007 Red Hat, Inc.
+#
+# 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.
+
+"""
+STABLE.
+"""
+
+
+class ActivityHandle(object):
+ """Data structure storing simple activity metadata"""
+
+ def __init__(self, activity_id=None, object_id=None, uri=None,
+ invited=False):
+ """Initialise the handle from activity_id
+
+ activity_id -- unique id for the activity to be
+ created
+ object_id -- identity of the journal object
+ associated with the activity. It was used by
+ the journal prototype implementation, might
+ change when we do the real one.
+
+ When you resume an activity from the journal
+ the object_id will be passed in. It's optional
+ since new activities does not have an
+ associated object (yet).
+
+ XXX Not clear how this relates to the activity
+ id yet, i.e. not sure we really need both. TBF
+ uri -- URI associated with the activity. Used when
+ opening an external file or resource in the
+ activity, rather than a journal object
+ (downloads stored on the file system for
+ example or web pages)
+ invited -- the activity is being launched for handling an invite
+ from the network
+ """
+ self.activity_id = activity_id
+ self.object_id = object_id
+ self.uri = uri
+ self.invited = invited
+
+ def get_dict(self):
+ """Retrieve our settings as a dictionary"""
+ result = {'activity_id': self.activity_id,
+ 'invited': self.invited}
+ if self.object_id:
+ result['object_id'] = self.object_id
+ if self.uri:
+ result['uri'] = self.uri
+
+ return result
+
+
+def create_from_dict(handle_dict):
+ """Create a handle from a dictionary of parameters"""
+ result = ActivityHandle(handle_dict['activity_id'],
+ object_id = handle_dict.get('object_id'),
+ uri = handle_dict.get('uri'),
+ invited = handle_dict.get('invited'))
+ return result
diff --git a/toolkit/src/sugar/activity/activityservice.py b/toolkit/src/sugar/activity/activityservice.py
new file mode 100644
index 0000000..ff15471
--- /dev/null
+++ b/toolkit/src/sugar/activity/activityservice.py
@@ -0,0 +1,83 @@
+# Copyright (C) 2006-2007 Red Hat, Inc.
+#
+# 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.
+
+"""
+UNSTABLE. It should really be internal to the Activity class.
+"""
+
+import logging
+
+import dbus
+import dbus.service
+
+
+_ACTIVITY_SERVICE_NAME = "org.laptop.Activity"
+_ACTIVITY_SERVICE_PATH = "/org/laptop/Activity"
+_ACTIVITY_INTERFACE = "org.laptop.Activity"
+
+
+class ActivityService(dbus.service.Object):
+ """Base dbus service object that each Activity uses to export dbus methods.
+
+ The dbus service is separate from the actual Activity object so that we can
+ tightly control what stuff passes through the dbus python bindings."""
+
+ def __init__(self, activity):
+ """Initialise the service for the given activity
+
+ activity -- sugar.activity.activity.Activity instance
+
+ Creates dbus services that use the instance's activity_id
+ as discriminants among all active services
+ of this type. That is, the services are all available
+ as names/paths derived from the instance's activity_id.
+
+ The various methods exposed on dbus are just forwarded
+ to the client Activity object's equally-named methods.
+ """
+ activity.realize()
+
+ activity_id = activity.get_id()
+ service_name = _ACTIVITY_SERVICE_NAME + activity_id
+ object_path = _ACTIVITY_SERVICE_PATH + '/' + activity_id
+
+ bus = dbus.SessionBus()
+ bus_name = dbus.service.BusName(service_name, bus=bus)
+ dbus.service.Object.__init__(self, bus_name, object_path)
+
+ self._activity = activity
+
+ @dbus.service.method(_ACTIVITY_INTERFACE)
+ def SetActive(self, active):
+ logging.debug('ActivityService.set_active: %s.', active)
+ self._activity.props.active = active
+
+ @dbus.service.method(_ACTIVITY_INTERFACE)
+ def InviteContact(self, account_path, contact_id):
+ self._activity.invite(account_path, contact_id)
+
+ @dbus.service.method(_ACTIVITY_INTERFACE)
+ def HandleViewSource(self):
+ self._activity.handle_view_source()
+
+ @dbus.service.method(_ACTIVITY_INTERFACE,
+ async_callbacks=('async_cb', 'async_err_cb'))
+ def GetDocumentPath(self, async_cb, async_err_cb):
+ try:
+ self._activity.get_document_path(async_cb, async_err_cb)
+ except Exception, e:
+ async_err_cb(e)
diff --git a/toolkit/src/sugar/activity/bundlebuilder.py b/toolkit/src/sugar/activity/bundlebuilder.py
new file mode 100644
index 0000000..fc8ebc8
--- /dev/null
+++ b/toolkit/src/sugar/activity/bundlebuilder.py
@@ -0,0 +1,424 @@
+# Copyright (C) 2008 Red Hat, Inc.
+#
+# 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.
+
+"""
+STABLE.
+"""
+
+import os
+import sys
+import zipfile
+import tarfile
+import shutil
+import subprocess
+import re
+import gettext
+from optparse import OptionParser
+import logging
+from fnmatch import fnmatch
+
+from sugar import env
+from sugar.bundle.activitybundle import ActivityBundle
+
+
+IGNORE_DIRS = ['dist', '.git']
+IGNORE_FILES = ['.gitignore', 'MANIFEST', '*.pyc', '*~', '*.bak', 'pseudo.po']
+
+
+def list_files(base_dir, ignore_dirs=None, ignore_files=None):
+ result = []
+
+ base_dir = os.path.abspath(base_dir)
+
+ for root, dirs, files in os.walk(base_dir):
+ if ignore_files:
+ for pattern in ignore_files:
+ files = [f for f in files if not fnmatch(f, pattern)]
+
+ rel_path = root[len(base_dir) + 1:]
+ for f in files:
+ result.append(os.path.join(rel_path, f))
+
+ if ignore_dirs and root == base_dir:
+ for ignore in ignore_dirs:
+ if ignore in dirs:
+ dirs.remove(ignore)
+
+ return result
+
+
+class Config(object):
+
+ def __init__(self, source_dir=None, dist_dir = None, dist_name = None):
+ self.source_dir = source_dir or os.getcwd()
+ self.dist_dir = dist_dir or os.path.join(self.source_dir, 'dist')
+ self.dist_name = dist_name
+ self.bundle = None
+ self.version = None
+ self.activity_name = None
+ self.bundle_id = None
+ self.bundle_name = None
+ self.bundle_root_dir = None
+ self.tar_root_dir = None
+ self.xo_name = None
+ self.tar_name = None
+
+ self.update()
+
+ def update(self):
+ self.bundle = bundle = ActivityBundle(self.source_dir)
+ self.version = bundle.get_activity_version()
+ self.activity_name = bundle.get_bundle_name()
+ self.bundle_id = bundle.get_bundle_id()
+ self.bundle_name = reduce(lambda x, y: x+y, self.activity_name.split())
+ self.bundle_root_dir = self.bundle_name + '.activity'
+ self.tar_root_dir = '%s-%d' % (self.bundle_name, self.version)
+
+ if self.dist_name:
+ self.xo_name = self.tar_name = self.dist_name
+ else:
+ self.xo_name = '%s-%d.xo' % (self.bundle_name, self.version)
+ self.tar_name = '%s-%d.tar.bz2' % (self.bundle_name, self.version)
+
+
+class Builder(object):
+
+ def __init__(self, config):
+ self.config = config
+
+ def build(self):
+ self.build_locale()
+
+ def build_locale(self):
+ po_dir = os.path.join(self.config.source_dir, 'po')
+
+ if not self.config.bundle.is_dir(po_dir):
+ logging.warn("Missing po/ dir, cannot build_locale")
+ return
+
+ locale_dir = os.path.join(self.config.source_dir, 'locale')
+
+ if os.path.exists(locale_dir):
+ shutil.rmtree(locale_dir)
+
+ for f in os.listdir(po_dir):
+ if not f.endswith('.po') or f == 'pseudo.po':
+ continue
+
+ file_name = os.path.join(po_dir, f)
+ lang = f[:-3]
+
+ localedir = os.path.join(self.config.source_dir, 'locale', lang)
+ mo_path = os.path.join(localedir, 'LC_MESSAGES')
+ if not os.path.isdir(mo_path):
+ os.makedirs(mo_path)
+
+ mo_file = os.path.join(mo_path, "%s.mo" % self.config.bundle_id)
+ args = ["msgfmt", "--output-file=%s" % mo_file, file_name]
+ retcode = subprocess.call(args)
+ if retcode:
+ print 'ERROR - msgfmt failed with return code %i.' % retcode
+
+ cat = gettext.GNUTranslations(open(mo_file, 'r'))
+ translated_name = cat.gettext(self.config.activity_name)
+ linfo_file = os.path.join(localedir, 'activity.linfo')
+ f = open(linfo_file, 'w')
+ f.write('[Activity]\nname = %s\n' % translated_name)
+ f.close()
+
+ def get_files(self):
+ files = self.config.bundle.get_files()
+
+ if not files:
+ logging.error('No files found, fixing the MANIFEST.')
+ self.fix_manifest()
+ files = self.config.bundle.get_files()
+
+ return files
+
+ def check_manifest(self):
+ missing_files = []
+
+ allfiles = list_files(self.config.source_dir,
+ IGNORE_DIRS, IGNORE_FILES)
+ for path in allfiles:
+ if path not in self.config.bundle.manifest:
+ missing_files.append(path)
+
+ return missing_files
+
+ def fix_manifest(self):
+ self.build()
+
+ manifest = self.config.bundle.manifest
+
+ for path in self.check_manifest():
+ manifest.append(path)
+
+ f = open(os.path.join(self.config.source_dir, "MANIFEST"), "wb")
+ for line in manifest:
+ f.write(line + "\n")
+
+
+class Packager(object):
+
+ def __init__(self, config):
+ self.config = config
+ self.package_path = None
+
+ if not os.path.exists(self.config.dist_dir):
+ os.mkdir(self.config.dist_dir)
+
+
+class XOPackager(Packager):
+
+ def __init__(self, builder):
+ Packager.__init__(self, builder.config)
+
+ self.builder = builder
+ self.package_path = os.path.join(self.config.dist_dir,
+ self.config.xo_name)
+
+ def package(self):
+ bundle_zip = zipfile.ZipFile(self.package_path, 'w',
+ zipfile.ZIP_DEFLATED)
+
+ missing_files = self.builder.check_manifest()
+ if missing_files:
+ logging.warn('These files are not included in the manifest ' \
+ 'and will not be present in the bundle:\n\n' +
+ '\n'.join(missing_files) +
+ '\n\nUse fix_manifest if you want to add them.')
+
+ for f in self.builder.get_files():
+ bundle_zip.write(os.path.join(self.config.source_dir, f),
+ os.path.join(self.config.bundle_root_dir, f))
+
+ bundle_zip.close()
+
+
+class SourcePackager(Packager):
+
+ def __init__(self, config):
+ Packager.__init__(self, config)
+ self.package_path = os.path.join(self.config.dist_dir,
+ self.config.tar_name)
+
+ def get_files(self):
+ git_ls = subprocess.Popen(['git', 'ls-files'], stdout=subprocess.PIPE,
+ cwd=self.config.source_dir)
+ stdout, _ = git_ls.communicate()
+ if git_ls.returncode:
+ # Fall back to filtered list
+ return list_files(self.config.source_dir,
+ IGNORE_DIRS, IGNORE_FILES)
+
+ return [path.strip() for path in stdout.strip('\n').split('\n')]
+
+ def package(self):
+ tar = tarfile.open(self.package_path, 'w:bz2')
+ for f in self.get_files():
+ tar.add(os.path.join(self.config.source_dir, f),
+ os.path.join(self.config.tar_root_dir, f))
+ tar.close()
+
+
+class Installer(object):
+ IGNORES = ['po/*', 'MANIFEST', 'AUTHORS']
+
+ def __init__(self, builder):
+ self.config = builder.config
+ self.builder = builder
+
+ def should_ignore(self, f):
+ for pattern in self.IGNORES:
+ if fnmatch(f, pattern):
+ return True
+ return False
+
+ def install(self, prefix):
+ self.builder.build()
+
+ activity_path = os.path.join(prefix, 'share', 'sugar', 'activities',
+ self.config.bundle_root_dir)
+
+ source_to_dest = {}
+ for f in self.builder.get_files():
+ if self.should_ignore(f):
+ pass
+ elif f.startswith('locale/') and f.endswith('.mo'):
+ source_to_dest[f] = os.path.join(prefix, 'share', f)
+ else:
+ source_to_dest[f] = os.path.join(activity_path, f)
+
+ for source, dest in source_to_dest.items():
+ print 'Install %s to %s.' % (source, dest)
+
+ path = os.path.dirname(dest)
+ if not os.path.exists(path):
+ os.makedirs(path)
+
+ shutil.copy(source, dest)
+
+
+def cmd_dev(config, args):
+ '''Setup for development'''
+
+ if args:
+ print 'Usage: %prog dev'
+ return
+
+ bundle_path = env.get_user_activities_path()
+ if not os.path.isdir(bundle_path):
+ os.mkdir(bundle_path)
+ bundle_path = os.path.join(bundle_path, config.bundle_root_dir)
+ try:
+ os.symlink(config.source_dir, bundle_path)
+ except OSError:
+ if os.path.islink(bundle_path):
+ print 'ERROR - The bundle has been already setup for development.'
+ else:
+ print 'ERROR - A bundle with the same name is already installed.'
+
+
+def cmd_dist_xo(config, args):
+ '''Create a xo bundle package'''
+
+ if args:
+ print 'Usage: %prog dist_xo'
+ return
+
+ packager = XOPackager(Builder(config))
+ packager.package()
+
+
+def cmd_fix_manifest(config, args):
+ '''Add missing files to the manifest'''
+
+ if args:
+ print 'Usage: %prog fix_manifest'
+ return
+
+ builder = Builder(config)
+ builder.fix_manifest()
+
+
+def cmd_dist_source(config, args):
+ '''Create a tar source package'''
+
+ if args:
+ print 'Usage: %prog dist_source'
+ return
+
+ packager = SourcePackager(config)
+ packager.package()
+
+
+def cmd_install(config, args):
+ '''Install the activity in the system'''
+
+ parser = OptionParser(usage='usage: %prog install [options]')
+ parser.add_option('--prefix', dest='prefix', default=sys.prefix,
+ help='Prefix to install files to')
+ (suboptions, subargs) = parser.parse_args(args)
+ if subargs:
+ parser.print_help()
+ return
+
+ installer = Installer(Builder(config))
+ installer.install(suboptions.prefix)
+
+
+def cmd_genpot(config, args):
+ '''Generate the gettext pot file'''
+
+ if args:
+ print 'Usage: %prog genpot'
+ return
+
+ po_path = os.path.join(config.source_dir, 'po')
+ if not os.path.isdir(po_path):
+ os.mkdir(po_path)
+
+ python_files = []
+ for root, dirs_dummy, files in os.walk(config.source_dir):
+ for file_name in files:
+ if file_name.endswith('.py'):
+ python_files.append(os.path.join(root, file_name))
+
+ # First write out a stub .pot file containing just the translated
+ # activity name, then have xgettext merge the rest of the
+ # translations into that. (We can't just append the activity name
+ # to the end of the .pot file afterwards, because that might
+ # create a duplicate msgid.)
+ pot_file = os.path.join('po', '%s.pot' % config.bundle_name)
+ escaped_name = re.sub('([\\\\"])', '\\\\\\1', config.activity_name)
+ f = open(pot_file, 'w')
+ f.write('#: activity/activity.info:2\n')
+ f.write('msgid "%s"\n' % escaped_name)
+ f.write('msgstr ""\n')
+ f.close()
+
+ args = ['xgettext', '--join-existing', '--language=Python',
+ '--keyword=_', '--add-comments=TRANS:', '--output=%s' % pot_file]
+
+ args += python_files
+ retcode = subprocess.call(args)
+ if retcode:
+ print 'ERROR - xgettext failed with return code %i.' % retcode
+
+
+def cmd_build(config, args):
+ '''Build generated files'''
+
+ if args:
+ print 'Usage: %prog build'
+ return
+
+ builder = Builder(config)
+ builder.build()
+
+
+def print_commands():
+ print 'Available commands:\n'
+
+ for name, func in globals().items():
+ if name.startswith('cmd_'):
+ print "%-20s %s" % (name.replace('cmd_', ''), func.__doc__)
+
+ print '\n(Type "./setup.py <command> --help" for help about a ' \
+ 'particular command\'s options.'
+
+
+def start(bundle_name=None):
+ if bundle_name:
+ logging.warn("bundle_name deprecated, now comes from activity.info")
+
+ parser = OptionParser(usage='[action] [options]')
+ parser.disable_interspersed_args()
+ (options_, args) = parser.parse_args()
+
+ config = Config()
+
+ try:
+ globals()['cmd_' + args[0]](config, args[1:])
+ except (KeyError, IndexError):
+ print_commands()
+
+
+if __name__ == '__main__':
+ start()
diff --git a/toolkit/src/sugar/activity/i18n.py b/toolkit/src/sugar/activity/i18n.py
new file mode 100644
index 0000000..1c3c893
--- /dev/null
+++ b/toolkit/src/sugar/activity/i18n.py
@@ -0,0 +1,144 @@
+# Copyright (C) 2010 One Laptop Per Child
+#
+# Author: Sayamindu Dasgupta <sayamindu@laptop.org>
+#
+# 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 gconf
+
+import locale
+import os
+import struct
+import sys
+
+import dateutil.parser
+import time
+
+_MO_BIG_ENDIAN = 0xde120495
+_MO_LITTLE_ENDIAN = 0x950412de
+
+
+def _read_bin(handle, format_string, byte_count):
+ read_bytes = handle.read(byte_count)
+ return_value = struct.unpack(format_string, read_bytes)
+ if len(return_value) == 1:
+ return return_value[0]
+ else:
+ return return_value
+
+
+def _extract_header(file_path):
+ header = ''
+ handle = open(file_path, 'rb')
+ magic_number = _read_bin(handle, '<I', 4)
+
+ if magic_number == _MO_BIG_ENDIAN:
+ format_string = '>II'
+ elif magic_number == _MO_LITTLE_ENDIAN:
+ format_string = '<II'
+ else:
+ raise IOError('File does not seem to be a valid MO file')
+
+ version_, num_of_strings = _read_bin(handle, format_string, 8)
+
+ msgids_hash_offset, msgstrs_hash_offset = _read_bin(handle, \
+ format_string, 8)
+ handle.seek(msgids_hash_offset)
+
+ msgids_index = []
+ for i in range(num_of_strings):
+ msgids_index.append(_read_bin(handle, format_string, 8))
+ handle.seek(msgstrs_hash_offset)
+
+ msgstrs_index = []
+ for i in range(num_of_strings):
+ msgstrs_index.append(_read_bin(handle, format_string, 8))
+
+ for i in range(num_of_strings):
+ handle.seek(msgids_index[i][1])
+ msgid = handle.read(msgids_index[i][0])
+ if msgid == '':
+ handle.seek(msgstrs_index[i][1])
+ msgstr = handle.read(msgstrs_index[i][0])
+ header = msgstr
+ break
+ else:
+ continue
+
+ handle.close()
+ return header
+
+
+def _extract_modification_time(file_path):
+ header = _extract_header(file_path)
+ items = header.split('\n')
+ for item in items:
+ if item.startswith('PO-Revision-Date:'):
+ time_str = item.split(': ')[1]
+ parsed_time = dateutil.parser.parse(time_str)
+ return time.mktime(parsed_time.timetuple())
+
+ raise ValueError('Could not find a revision date')
+
+
+def get_locale_path(bundle_id):
+ """ Returns the locale path, which is the directory where the preferred
+ MO file is located.
+
+ The preferred MO file is the one with the latest translation.
+
+ @type bundle_id: string
+ @param bundle_id: The bundle id of the activity in question
+ @rtype: string
+ @return: the preferred locale path
+ """
+
+ # Note: We pre-assign weights to the directories so that if no translations
+ # exist, the appropriate fallbacks (eg: bn for bn_BD) can be loaded
+ # The directory with the highest weight is returned, and if a MO file is
+ # found, the weight of the directory is set to the MO's modification time
+ # (as described in the MO header, and _not_ the filesystem mtime)
+
+ candidate_dirs = {}
+
+ if 'SUGAR_LOCALEDIR' in os.environ:
+ candidate_dirs[os.environ['SUGAR_LOCALEDIR']] = 2
+
+ gconf_client = gconf.client_get_default()
+ package_dir = gconf_client.get_string("/desktop/sugar/i18n/langpackdir")
+ if package_dir is not None and package_dir is not '':
+ candidate_dirs[package_dir] = 1
+
+ candidate_dirs[os.path.join(sys.prefix, 'share', 'locale')] = 0
+
+ for candidate_dir in candidate_dirs.keys():
+ if os.path.exists(candidate_dir):
+ full_path = os.path.join(candidate_dir, \
+ locale.getdefaultlocale()[0], 'LC_MESSAGES', \
+ bundle_id + '.mo')
+ if os.path.exists(full_path):
+ try:
+ candidate_dirs[candidate_dir] = \
+ _extract_modification_time(full_path)
+ except (IOError, ValueError):
+ # The mo file is damaged or has not been initialized
+ # Set lowest priority
+ candidate_dirs[candidate_dir] = -1
+
+ available_paths = sorted(candidate_dirs.iteritems(), key=lambda (k, v): \
+ (v, k), reverse=True)
+ preferred_path = available_paths[0][0]
+ return preferred_path
diff --git a/toolkit/src/sugar/activity/main.py b/toolkit/src/sugar/activity/main.py
new file mode 100644
index 0000000..3a3950d
--- /dev/null
+++ b/toolkit/src/sugar/activity/main.py
@@ -0,0 +1,159 @@
+# Copyright (C) 2008 Red Hat, Inc.
+#
+# 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 os
+import sys
+import gettext
+from optparse import OptionParser
+
+import gtk
+import dbus
+import dbus.service
+import dbus.glib
+
+import sugar
+from sugar.activity import activityhandle
+from sugar.activity import i18n
+from sugar.bundle.activitybundle import ActivityBundle
+from sugar.graphics import style
+from sugar import logger
+
+
+def create_activity_instance(constructor, handle):
+ activity = constructor(handle)
+ activity.show()
+
+
+def get_single_process_name(bundle_id):
+ return bundle_id
+
+
+def get_single_process_path(bundle_id):
+ return '/' + bundle_id.replace('.', '/')
+
+
+class SingleProcess(dbus.service.Object):
+
+ def __init__(self, name_service, constructor):
+ self.constructor = constructor
+
+ bus = dbus.SessionBus()
+ bus_name = dbus.service.BusName(name_service, bus=bus)
+ object_path = get_single_process_path(name_service)
+ dbus.service.Object.__init__(self, bus_name, object_path)
+
+ @dbus.service.method("org.laptop.SingleProcess", in_signature="a{ss}")
+ def create(self, handle_dict):
+ handle = activityhandle.create_from_dict(handle_dict)
+ create_activity_instance(self.constructor, handle)
+
+
+def main():
+ parser = OptionParser()
+ parser.add_option("-b", "--bundle-id", dest="bundle_id",
+ help="identifier of the activity bundle")
+ parser.add_option("-a", "--activity-id", dest="activity_id",
+ help="identifier of the activity instance")
+ parser.add_option("-o", "--object-id", dest="object_id",
+ help="identifier of the associated datastore object")
+ parser.add_option("-u", "--uri", dest="uri",
+ help="URI to load")
+ parser.add_option('-s', '--single-process', dest='single_process',
+ action='store_true',
+ help='start all the instances in the same process')
+ parser.add_option('-i', '--invited', dest='invited',
+ action='store_true',
+ help='the activity is being launched for handling an '
+ 'invite from the network')
+ (options, args) = parser.parse_args()
+
+ logger.start()
+
+ if 'SUGAR_BUNDLE_PATH' not in os.environ:
+ print 'SUGAR_BUNDLE_PATH is not defined in the environment.'
+ sys.exit(1)
+
+ if len(args) == 0:
+ print 'A python class must be specified as first argument.'
+ sys.exit(1)
+
+ bundle_path = os.environ['SUGAR_BUNDLE_PATH']
+ sys.path.append(bundle_path)
+
+ bundle = ActivityBundle(bundle_path)
+
+ os.environ['SUGAR_BUNDLE_ID'] = bundle.get_bundle_id()
+ os.environ['SUGAR_BUNDLE_NAME'] = bundle.get_name()
+ os.environ['SUGAR_BUNDLE_VERSION'] = str(bundle.get_activity_version())
+
+ gtk.icon_theme_get_default().append_search_path(bundle.get_icons_path())
+
+ # This code can be removed when we grow an xsettings daemon (the GTK+
+ # init routines will then automatically figure out the font settings)
+ settings = gtk.settings_get_default()
+ settings.set_property('gtk-font-name',
+ '%s %f' % (style.FONT_FACE, style.FONT_SIZE))
+
+ locale_path = i18n.get_locale_path(bundle.get_bundle_id())
+
+ gettext.bindtextdomain(bundle.get_bundle_id(), locale_path)
+ gettext.bindtextdomain('sugar-toolkit', sugar.locale_path)
+ gettext.textdomain(bundle.get_bundle_id())
+
+ splitted_module = args[0].rsplit('.', 1)
+ module_name = splitted_module[0]
+ class_name = splitted_module[1]
+
+ module = __import__(module_name)
+ for comp in module_name.split('.')[1:]:
+ module = getattr(module, comp)
+
+ activity_constructor = getattr(module, class_name)
+ activity_handle = activityhandle.ActivityHandle(
+ activity_id=options.activity_id,
+ object_id=options.object_id, uri=options.uri,
+ invited=options.invited)
+
+ if options.single_process is True:
+ sessionbus = dbus.SessionBus()
+
+ service_name = get_single_process_name(options.bundle_id)
+ service_path = get_single_process_path(options.bundle_id)
+
+ bus_object = sessionbus.get_object(
+ 'org.freedesktop.DBus', '/org/freedesktop/DBus')
+ try:
+ name = bus_object.GetNameOwner(
+ service_name, dbus_interface='org.freedesktop.DBus')
+ except dbus.DBusException:
+ name = None
+
+ if not name:
+ SingleProcess(service_name, activity_constructor)
+ else:
+ single_process = sessionbus.get_object(service_name, service_path)
+ single_process.create(activity_handle.get_dict())
+
+ print 'Created %s in a single process.' % service_name
+ sys.exit(0)
+
+ if hasattr(module, 'start'):
+ module.start()
+
+ create_activity_instance(activity_constructor, activity_handle)
+
+ gtk.main()
diff --git a/toolkit/src/sugar/activity/namingalert.py b/toolkit/src/sugar/activity/namingalert.py
new file mode 100644
index 0000000..72db8dc
--- /dev/null
+++ b/toolkit/src/sugar/activity/namingalert.py
@@ -0,0 +1,355 @@
+# Copyright (C) 2009 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 gettext
+import os
+
+import gio
+import gtk
+import gobject
+import hippo
+import gconf
+
+from sugar.graphics import style
+from sugar.graphics.icon import Icon
+from sugar.graphics.xocolor import XoColor
+from sugar.graphics.icon import CanvasIcon
+from sugar.graphics.icon import get_icon_file_name
+from sugar.graphics.entry import CanvasEntry
+from sugar.graphics.toolbutton import ToolButton
+from sugar.graphics.canvastextview import CanvasTextView
+
+from sugar.bundle.activitybundle import ActivityBundle
+
+
+_ = lambda msg: gettext.dgettext('sugar-toolkit', msg)
+
+
+def _get_icon_name(metadata):
+ file_name = None
+
+ mime_type = metadata.get('mime_type', '')
+ if not file_name and mime_type:
+ icons = gio.content_type_get_icon(mime_type)
+ for icon_name in icons.props.names:
+ file_name = get_icon_file_name(icon_name)
+ if file_name is not None:
+ break
+
+ if file_name is None or not os.path.exists(file_name):
+ file_name = get_icon_file_name('application-octet-stream')
+
+ return file_name
+
+
+class NamingToolbar(gtk.Toolbar):
+ """ Toolbar of the naming alert
+ """
+
+ __gtype_name__ = 'SugarNamingToolbar'
+
+ __gsignals__ = {
+ 'keep-clicked': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
+ }
+
+ def __init__(self):
+ gtk.Toolbar.__init__(self)
+
+ client = gconf.client_get_default()
+ color = XoColor(client.get_string('/desktop/sugar/user/color'))
+ icon = Icon()
+ icon.set_from_icon_name('activity-journal',
+ gtk.ICON_SIZE_LARGE_TOOLBAR)
+ icon.props.xo_color = color
+ self._add_widget(icon)
+
+ self._add_separator()
+
+ self._title = gtk.Label(_('Name this entry'))
+ self._add_widget(self._title)
+
+ self._add_separator(True)
+
+ self._keep_button = ToolButton('dialog-ok', tooltip=_('Keep'))
+ self._keep_button.props.accelerator = 'Return'
+ self._keep_button.connect('clicked', self.__keep_button_clicked_cb)
+ self.insert(self._keep_button, -1)
+ self._keep_button.show()
+
+ 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 __keep_button_clicked_cb(self, widget, data=None):
+ self.emit('keep-clicked')
+
+
+class FavoriteIcon(CanvasIcon):
+
+ def __init__(self, favorite):
+ CanvasIcon.__init__(self, icon_name='emblem-favorite',
+ box_width=style.GRID_CELL_SIZE * 3 / 5,
+ size=style.SMALL_ICON_SIZE)
+ self._favorite = None
+ self.set_favorite(favorite)
+ self.connect('button-release-event', self.__release_event_cb)
+ self.connect('motion-notify-event', self.__motion_notify_event_cb)
+
+ def set_favorite(self, favorite):
+ if favorite == self._favorite:
+ return
+
+ self._favorite = favorite
+ if favorite:
+ client = gconf.client_get_default()
+ color = XoColor(client.get_string('/desktop/sugar/user/color'))
+ self.props.xo_color = color
+ else:
+ self.props.stroke_color = style.COLOR_BUTTON_GREY.get_svg()
+ self.props.fill_color = style.COLOR_WHITE.get_svg()
+
+ def get_favorite(self):
+ return self._favorite
+
+ favorite = gobject.property(
+ type=bool, default=False, getter=get_favorite, setter=set_favorite)
+
+ def __release_event_cb(self, icon, event):
+ self.props.favorite = not self.props.favorite
+
+ def __motion_notify_event_cb(self, icon, event):
+ if not self._favorite:
+ if event.detail == hippo.MOTION_DETAIL_ENTER:
+ icon.props.fill_color = style.COLOR_BUTTON_GREY.get_svg()
+ elif event.detail == hippo.MOTION_DETAIL_LEAVE:
+ icon.props.fill_color = style.COLOR_TRANSPARENT.get_svg()
+
+
+class NamingAlert(gtk.Window):
+
+ __gtype_name__ = 'SugarNamingAlert'
+
+ def __init__(self, activity, bundle_path):
+ gtk.Window.__init__(self)
+
+ self._bundle_path = bundle_path
+ self._favorite_icon = None
+ self._title = None
+ self._description = None
+ self._tags = None
+
+ accel_group = gtk.AccelGroup()
+ self.set_data('sugar-accel-group', accel_group)
+ self.add_accel_group(accel_group)
+
+ 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.connect('realize', self.__realize_cb)
+
+ self._activity = activity
+
+ vbox = gtk.VBox()
+ self.add(vbox)
+ vbox.show()
+
+ toolbar = NamingToolbar()
+ toolbar.connect('keep-clicked', self.__keep_cb)
+ vbox.pack_start(toolbar, False)
+ toolbar.show()
+
+ canvas = hippo.Canvas()
+ self._root = hippo.CanvasBox()
+ self._root.props.background_color = style.COLOR_WHITE.get_int()
+ canvas.set_root(self._root)
+ vbox.pack_start(canvas)
+ canvas.show()
+
+ body = self._create_body()
+ self._root.append(body, hippo.PACK_EXPAND)
+
+ widget = self._title.get_property('widget')
+ widget.grab_focus()
+
+ def _create_body(self):
+ body = hippo.CanvasBox()
+ body.props.orientation = hippo.ORIENTATION_VERTICAL
+ body.props.background_color = style.COLOR_WHITE.get_int()
+ body.props.padding_top = style.DEFAULT_SPACING * 3
+
+ header = hippo.CanvasBox(orientation=hippo.ORIENTATION_HORIZONTAL,
+ padding=style.DEFAULT_PADDING,
+ padding_right=style.GRID_CELL_SIZE,
+ spacing=style.DEFAULT_SPACING)
+ body.append(header)
+
+ descriptions = hippo.CanvasBox(
+ orientation=hippo.ORIENTATION_HORIZONTAL,
+ spacing=style.DEFAULT_SPACING * 3,
+ padding_left=style.GRID_CELL_SIZE,
+ padding_right=style.GRID_CELL_SIZE,
+ padding_top=style.DEFAULT_SPACING * 3)
+
+ body.append(descriptions, hippo.PACK_EXPAND)
+
+ first_column = hippo.CanvasBox(orientation=hippo.ORIENTATION_VERTICAL,
+ spacing=style.DEFAULT_SPACING)
+ descriptions.append(first_column)
+
+ second_column = hippo.CanvasBox(orientation=hippo.ORIENTATION_VERTICAL,
+ spacing=style.DEFAULT_SPACING)
+ descriptions.append(second_column, hippo.PACK_EXPAND)
+
+ self._favorite_icon = self._create_favorite_icon()
+ header.append(self._favorite_icon)
+
+ entry_icon = self._create_entry_icon()
+ header.append(entry_icon)
+
+ self._title = self._create_title()
+ header.append(self._title, hippo.PACK_EXPAND)
+
+ if gtk.widget_get_default_direction() == gtk.TEXT_DIR_RTL:
+ header.reverse()
+
+ description_box, self._description = self._create_description()
+ second_column.append(description_box)
+
+ tags_box, self._tags = self._create_tags()
+ second_column.append(tags_box)
+
+ return body
+
+ def _create_favorite_icon(self):
+ favorite_icon = FavoriteIcon(False)
+ return favorite_icon
+
+ def _create_entry_icon(self):
+ bundle_id = self._activity.metadata.get('activity', '')
+ if not bundle_id:
+ bundle_id = self._activity.metadata.get('bundle_id', '')
+
+ if bundle_id == '':
+ file_name = _get_icon_name(self._activity.metadata)
+ else:
+ activity_bundle = ActivityBundle(self._bundle_path)
+ file_name = activity_bundle.get_icon()
+ entry_icon = CanvasIcon(file_name=file_name)
+ if self._activity.metadata.has_key('icon-color') and \
+ self._activity.metadata['icon-color']:
+ entry_icon.props.xo_color = XoColor( \
+ self._activity.metadata['icon-color'])
+ return entry_icon
+
+ def _create_title(self):
+ title = CanvasEntry()
+ title.set_background(style.COLOR_WHITE.get_html())
+ title.props.text = self._activity.metadata.get('title', _('Untitled'))
+ return title
+
+ def _create_description(self):
+ vbox = hippo.CanvasBox()
+ vbox.props.spacing = style.DEFAULT_SPACING
+
+ text = hippo.CanvasText(text=_('Description:'),
+ font_desc=style.FONT_NORMAL.get_pango_desc())
+ text.props.color = style.COLOR_BUTTON_GREY.get_int()
+
+ if gtk.widget_get_default_direction() == gtk.TEXT_DIR_RTL:
+ text.props.xalign = hippo.ALIGNMENT_END
+ else:
+ text.props.xalign = hippo.ALIGNMENT_START
+
+ vbox.append(text)
+
+ description = self._activity.metadata.get('description', '')
+ text_view = CanvasTextView(description,
+ box_height=style.GRID_CELL_SIZE * 2)
+ vbox.append(text_view, hippo.PACK_EXPAND)
+
+ text_view.text_view_widget.props.accepts_tab = False
+
+ return vbox, text_view
+
+ def _create_tags(self):
+ vbox = hippo.CanvasBox()
+ vbox.props.spacing = style.DEFAULT_SPACING
+
+ text = hippo.CanvasText(text=_('Tags:'),
+ font_desc=style.FONT_NORMAL.get_pango_desc())
+ text.props.color = style.COLOR_BUTTON_GREY.get_int()
+
+ if gtk.widget_get_default_direction() == gtk.TEXT_DIR_RTL:
+ text.props.xalign = hippo.ALIGNMENT_END
+ else:
+ text.props.xalign = hippo.ALIGNMENT_START
+
+ vbox.append(text)
+
+ tags = self._activity.metadata.get('tags', '')
+ text_view = CanvasTextView(tags, box_height=style.GRID_CELL_SIZE * 2)
+ vbox.append(text_view, hippo.PACK_EXPAND)
+
+ text_view.text_view_widget.props.accepts_tab = False
+
+ return vbox, text_view
+
+ def __realize_cb(self, widget):
+ self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
+ self.window.set_accept_focus(True)
+
+ def __keep_cb(self, widget):
+ is_favorite = self._favorite_icon.get_favorite()
+ if is_favorite:
+ self._activity.metadata['keep'] = 1
+ else:
+ self._activity.metadata['keep'] = 0
+
+ self._activity.metadata['title'] = self._title.props.text
+
+ new_tags = self._tags.text_view_widget.props.buffer.props.text
+ self._activity.metadata['tags'] = new_tags
+
+ new_description = \
+ self._description.text_view_widget.props.buffer.props.text
+ self._activity.metadata['description'] = new_description
+
+ self._activity.metadata['title_set_by_user'] = '1'
+ self._activity.close()
+ self.destroy()
diff --git a/toolkit/src/sugar/activity/widgets.py b/toolkit/src/sugar/activity/widgets.py
new file mode 100644
index 0000000..b5e4ce7
--- /dev/null
+++ b/toolkit/src/sugar/activity/widgets.py
@@ -0,0 +1,353 @@
+# Copyright (C) 2009, Aleksey Lim, Simon Schampijer
+#
+# 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 gtk
+import gobject
+import gettext
+import gconf
+
+from sugar.graphics.toolbutton import ToolButton
+from sugar.graphics.toolbarbox import ToolbarButton
+from sugar.graphics.radiopalette import RadioPalette, RadioMenuButton
+from sugar.graphics.radiotoolbutton import RadioToolButton
+from sugar.graphics.toolbox import Toolbox
+from sugar.graphics.xocolor import XoColor
+from sugar.graphics.icon import Icon
+from sugar.bundle.activitybundle import ActivityBundle
+
+
+_ = lambda msg: gettext.dgettext('sugar-toolkit', msg)
+
+
+def _create_activity_icon(metadata):
+ if metadata.get('icon-color', ''):
+ color = XoColor(metadata['icon-color'])
+ else:
+ client = gconf.client_get_default()
+ color = XoColor(client.get_string('/desktop/sugar/user/color'))
+
+ from sugar.activity.activity import get_bundle_path
+ bundle = ActivityBundle(get_bundle_path())
+ icon = Icon(file=bundle.get_icon(), xo_color=color)
+
+ return icon
+
+
+class ActivityButton(ToolButton):
+
+ def __init__(self, activity, **kwargs):
+ ToolButton.__init__(self, **kwargs)
+
+ icon = _create_activity_icon(activity.metadata)
+ self.set_icon_widget(icon)
+ icon.show()
+
+ self.props.tooltip = activity.metadata['title']
+ activity.metadata.connect('updated', self.__jobject_updated_cb)
+
+ def __jobject_updated_cb(self, jobject):
+ self.props.tooltip = jobject['title']
+
+
+class ActivityToolbarButton(ToolbarButton):
+
+ def __init__(self, activity, **kwargs):
+ toolbar = ActivityToolbar(activity, orientation_left=True)
+ toolbar.stop.hide()
+
+ ToolbarButton.__init__(self, page=toolbar, **kwargs)
+
+ icon = _create_activity_icon(activity.metadata)
+ self.set_icon_widget(icon)
+ icon.show()
+
+
+class StopButton(ToolButton):
+
+ def __init__(self, activity, **kwargs):
+ ToolButton.__init__(self, 'activity-stop', **kwargs)
+ self.props.tooltip = _('Stop')
+ self.props.accelerator = '<Ctrl>Q'
+ self.connect('clicked', self.__stop_button_clicked_cb, activity)
+
+ def __stop_button_clicked_cb(self, button, activity):
+ activity.close()
+
+
+class UndoButton(ToolButton):
+
+ def __init__(self, **kwargs):
+ ToolButton.__init__(self, 'edit-undo', **kwargs)
+ self.props.tooltip = _('Undo')
+ self.props.accelerator = '<Ctrl>Z'
+
+
+class RedoButton(ToolButton):
+
+ def __init__(self, **kwargs):
+ ToolButton.__init__(self, 'edit-redo', **kwargs)
+ self.props.tooltip = _('Redo')
+
+
+class CopyButton(ToolButton):
+
+ def __init__(self, **kwargs):
+ ToolButton.__init__(self, 'edit-copy', **kwargs)
+ self.props.tooltip = _('Copy')
+ self.props.accelerator = '<Ctrl>C'
+
+
+class PasteButton(ToolButton):
+
+ def __init__(self, **kwargs):
+ ToolButton.__init__(self, 'edit-paste', **kwargs)
+ self.props.tooltip = _('Paste')
+ self.props.accelerator = '<Ctrl>V'
+
+
+class ShareButton(RadioMenuButton):
+
+ def __init__(self, activity, **kwargs):
+ palette = RadioPalette()
+
+ self.private = RadioToolButton(
+ icon_name='zoom-home')
+ palette.append(self.private, _('Private'))
+
+ self.neighborhood = RadioToolButton(
+ icon_name='zoom-neighborhood',
+ group=self.private)
+ self._neighborhood_handle = self.neighborhood.connect(
+ 'clicked', self.__neighborhood_clicked_cb, activity)
+ palette.append(self.neighborhood, _('My Neighborhood'))
+
+ activity.connect('shared', self.__update_share_cb)
+ activity.connect('joined', self.__update_share_cb)
+
+ RadioMenuButton.__init__(self, **kwargs)
+ self.props.palette = palette
+ if activity.max_participants == 1:
+ self.props.sensitive = False
+
+ def __neighborhood_clicked_cb(self, button, activity):
+ activity.share()
+
+ def __update_share_cb(self, activity):
+ self.neighborhood.handler_block(self._neighborhood_handle)
+ try:
+ if activity.shared_activity is not None and \
+ not activity.shared_activity.props.private:
+ self.private.props.sensitive = False
+ self.neighborhood.props.sensitive = False
+ self.neighborhood.props.active = True
+ else:
+ self.private.props.sensitive = True
+ self.neighborhood.props.sensitive = True
+ self.private.props.active = True
+ finally:
+ self.neighborhood.handler_unblock(self._neighborhood_handle)
+
+
+class KeepButton(ToolButton):
+
+ def __init__(self, activity, **kwargs):
+ ToolButton.__init__(self, **kwargs)
+ self.props.tooltip = _('Keep')
+ self.props.accelerator = '<Ctrl>S'
+
+ client = gconf.client_get_default()
+ color = XoColor(client.get_string('/desktop/sugar/user/color'))
+ keep_icon = Icon(icon_name='document-save', xo_color=color)
+ keep_icon.show()
+
+ self.set_icon_widget(keep_icon)
+ self.connect('clicked', self.__keep_button_clicked_cb, activity)
+
+ def __keep_button_clicked_cb(self, button, activity):
+ activity.copy()
+
+
+class TitleEntry(gtk.ToolItem):
+
+ def __init__(self, activity, **kwargs):
+ gtk.ToolItem.__init__(self)
+ self.set_expand(False)
+ self._update_title_sid = None
+
+ self.entry = gtk.Entry(**kwargs)
+ self.entry.set_size_request(int(gtk.gdk.screen_width() / 3), -1)
+ self.entry.set_text(activity.metadata['title'])
+ self.entry.connect('changed', self.__title_changed_cb, activity)
+ self.entry.show()
+ self.add(self.entry)
+
+ activity.metadata.connect('updated', self.__jobject_updated_cb)
+
+ def modify_bg(self, state, color):
+ gtk.ToolItem.modify_bg(self, state, color)
+ self.entry.modify_bg(state, color)
+
+ def __jobject_updated_cb(self, jobject):
+ self.entry.set_text(jobject['title'])
+
+ def __title_changed_cb(self, entry, activity):
+ if self._update_title_sid is not None:
+ gobject.source_remove(self._update_title_sid)
+ self._update_title_sid = gobject.timeout_add_seconds(
+ 1, self.__update_title_cb, activity)
+
+ def __update_title_cb(self, activity):
+ title = self.entry.get_text()
+
+ activity.metadata['title'] = title
+ activity.metadata['title_set_by_user'] = '1'
+ activity.save()
+
+ shared_activity = activity.get_shared_activity()
+ if shared_activity is not None:
+ shared_activity.props.name = title
+
+ self._update_title_sid = None
+ return False
+
+
+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, orientation_left=False):
+ gtk.Toolbar.__init__(self)
+
+ self._activity = activity
+
+ if activity.metadata:
+ title_button = TitleEntry(activity)
+ title_button.show()
+ self.insert(title_button, -1)
+ self.title = title_button.entry
+
+ if orientation_left == False:
+ separator = gtk.SeparatorToolItem()
+ separator.props.draw = False
+ separator.set_expand(True)
+ self.insert(separator, -1)
+ separator.show()
+
+ self.share = ShareButton(activity)
+ self.share.show()
+ self.insert(self.share, -1)
+
+ self.keep = KeepButton(activity)
+ self.insert(self.keep, -1)
+ self.keep.show()
+
+ self.stop = StopButton(activity)
+ self.insert(self.stop, -1)
+ self.stop.show()
+
+
+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)
+
+ self.undo = UndoButton()
+ self.insert(self.undo, -1)
+ self.undo.show()
+
+ self.redo = RedoButton()
+ self.insert(self.redo, -1)
+ self.redo.show()
+
+ self.separator = gtk.SeparatorToolItem()
+ self.separator.set_draw(True)
+ self.insert(self.separator, -1)
+ self.separator.show()
+
+ self.copy = CopyButton()
+ self.insert(self.copy, -1)
+ self.copy.show()
+
+ self.paste = PasteButton()
+ self.insert(self.paste, -1)
+ 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_toolbar_box(toolbox)
+ # And make it visible:
+ toolbox.show()
+ """
+
+ def __init__(self, activity):
+ Toolbox.__init__(self)
+
+ self._activity_toolbar = ActivityToolbar(activity)
+ self.add_toolbar(_('Activity'), self._activity_toolbar)
+ self._activity_toolbar.show()
+
+ def get_activity_toolbar(self):
+ return self._activity_toolbar
diff --git a/toolkit/src/sugar/bundle/Makefile.am b/toolkit/src/sugar/bundle/Makefile.am
new file mode 100644
index 0000000..f1af791
--- /dev/null
+++ b/toolkit/src/sugar/bundle/Makefile.am
@@ -0,0 +1,6 @@
+sugardir = $(pythondir)/sugar/bundle
+sugar_PYTHON = \
+ __init__.py \
+ bundle.py \
+ activitybundle.py \
+ contentbundle.py
diff --git a/toolkit/src/sugar/bundle/__init__.py b/toolkit/src/sugar/bundle/__init__.py
new file mode 100644
index 0000000..85ebced
--- /dev/null
+++ b/toolkit/src/sugar/bundle/__init__.py
@@ -0,0 +1,16 @@
+# Copyright (C) 2006-2007, Red Hat, Inc.
+#
+# 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.
diff --git a/toolkit/src/sugar/bundle/activitybundle.py b/toolkit/src/sugar/bundle/activitybundle.py
new file mode 100644
index 0000000..aeec8be
--- /dev/null
+++ b/toolkit/src/sugar/bundle/activitybundle.py
@@ -0,0 +1,428 @@
+# Copyright (C) 2007, Red Hat, Inc.
+#
+# 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.
+
+"""Sugar activity bundles
+
+UNSTABLE.
+"""
+
+from ConfigParser import ConfigParser
+import locale
+import os
+import tempfile
+import logging
+import warnings
+
+from sugar import env
+from sugar import util
+from sugar.bundle.bundle import Bundle, \
+ MalformedBundleException, NotInstalledException
+
+
+class ActivityBundle(Bundle):
+ """A Sugar activity bundle
+
+ See http://wiki.laptop.org/go/Activity_bundles for details
+ """
+
+ MIME_TYPE = 'application/vnd.olpc-sugar'
+ DEPRECATED_MIME_TYPE = 'application/vnd.olpc-x-sugar'
+
+ _zipped_extension = '.xo'
+ _unzipped_extension = '.activity'
+ _infodir = 'activity'
+
+ def __init__(self, path):
+ Bundle.__init__(self, path)
+ self.activity_class = None
+ self.bundle_exec = None
+
+ self._name = None
+ self._local_name = None
+ self._icon = None
+ self._bundle_id = None
+ self._mime_types = None
+ self._show_launcher = True
+ self._tags = None
+ self._activity_version = 0
+ self._installation_time = os.stat(path).st_mtime
+ self._manifest = None
+
+ info_file = self.get_file('activity/activity.info')
+ if info_file is None:
+ raise MalformedBundleException('No activity.info file')
+ self._parse_info(info_file)
+
+ linfo_file = self._get_linfo_file()
+ if linfo_file:
+ self._parse_linfo(linfo_file)
+
+ if self._local_name == None:
+ self._local_name = self._name
+
+ def _get_manifest(self):
+ if self._manifest is None:
+ self._manifest = self._read_manifest()
+ return self._manifest
+
+ manifest = property(_get_manifest, None, None,
+ "NOTICE: this property is potentially quite slow, so better make sure "
+ "that it's not called at performance-critical points like shell or "
+ "activity startup.")
+
+ def _raw_manifest(self):
+ f = self.get_file("MANIFEST")
+ if not f:
+ logging.warning("Activity directory lacks a MANIFEST file.")
+ return []
+
+ ret = [line.strip() for line in f.readlines()]
+ f.close()
+ return ret
+
+ def _read_manifest(self):
+ """return a list with the lines in MANIFEST, with invalid lines
+ replaced by empty lines.
+
+ Since absolute order carries information on file history, it should
+ be preserved. For instance, when renaming a file, you should leave
+ the new name on the same line as the old one.
+ """
+ logging.debug('STARTUP: Reading manifest')
+ lines = self._raw_manifest()
+
+ # Remove trailing newlines, they do not help keep absolute position.
+ while lines and lines[-1] == "":
+ lines = lines[:-1]
+
+ for num, line in enumerate(lines):
+ if not line:
+ continue
+
+ # Remove duplicates
+ if line in lines[0:num]:
+ lines[num] = ""
+ logging.warning('Bundle %s: duplicate entry in MANIFEST: %s',
+ self._name, line)
+ continue
+
+ # Remove MANIFEST
+ if line == "MANIFEST":
+ lines[num] = ""
+ logging.warning('Bundle %s: MANIFEST includes itself: %s',
+ self._name, line)
+
+ # Remove invalid files
+ if not self.is_file(line):
+ lines[num] = ""
+ logging.warning('Bundle %s: invalid entry in MANIFEST: %s',
+ self._name, line)
+
+ return lines
+
+ def get_files(self, manifest = None):
+ files = [line for line in (manifest or self.manifest) if line]
+
+ if self.is_file('MANIFEST'):
+ files.append('MANIFEST')
+
+ return files
+
+ def _parse_info(self, info_file):
+ cp = ConfigParser()
+ cp.readfp(info_file)
+
+ section = 'Activity'
+
+ if cp.has_option(section, 'bundle_id'):
+ self._bundle_id = cp.get(section, 'bundle_id')
+ # FIXME deprecated
+ elif cp.has_option(section, 'service_name'):
+ warnings.warn('use bundle_id instead of service_name ' \
+ 'in your activity.info', DeprecationWarning)
+ self._bundle_id = cp.get(section, 'service_name')
+ else:
+ raise MalformedBundleException(
+ 'Activity bundle %s does not specify a bundle id' %
+ self._path)
+
+ if cp.has_option(section, 'name'):
+ self._name = cp.get(section, 'name')
+ else:
+ raise MalformedBundleException(
+ 'Activity bundle %s does not specify a name' % self._path)
+
+ # FIXME class is deprecated
+ if cp.has_option(section, 'class'):
+ warnings.warn('use exec instead of class ' \
+ 'in your activity.info', DeprecationWarning)
+ self.activity_class = cp.get(section, 'class')
+ elif cp.has_option(section, 'exec'):
+ self.bundle_exec = cp.get(section, 'exec')
+ else:
+ raise MalformedBundleException(
+ 'Activity bundle %s must specify either class or exec' %
+ self._path)
+
+ if cp.has_option(section, 'mime_types'):
+ mime_list = cp.get(section, 'mime_types').strip(';')
+ self._mime_types = [mime.strip() for mime in mime_list.split(';')]
+
+ if cp.has_option(section, 'show_launcher'):
+ if cp.get(section, 'show_launcher') == 'no':
+ self._show_launcher = False
+
+ if cp.has_option(section, 'tags'):
+ tag_list = cp.get(section, 'tags').strip(';')
+ self._tags = [tag.strip() for tag in tag_list.split(';')]
+
+ if cp.has_option(section, 'icon'):
+ self._icon = cp.get(section, 'icon')
+
+ if cp.has_option(section, 'activity_version'):
+ version = cp.get(section, 'activity_version')
+ try:
+ self._activity_version = int(version)
+ except ValueError:
+ raise MalformedBundleException(
+ 'Activity bundle %s has invalid version number %s' %
+ (self._path, version))
+
+ def _get_linfo_file(self):
+ lang = locale.getdefaultlocale()[0]
+ if not lang:
+ return None
+
+ linfo_path = os.path.join('locale', lang, 'activity.linfo')
+ linfo_file = self.get_file(linfo_path)
+ if linfo_file is not None:
+ return linfo_file
+
+ linfo_path = os.path.join('locale', lang[:2], 'activity.linfo')
+ linfo_file = self.get_file(linfo_path)
+ if linfo_file is not None:
+ return linfo_file
+
+ return None
+
+ def _parse_linfo(self, linfo_file):
+ cp = ConfigParser()
+ cp.readfp(linfo_file)
+
+ section = 'Activity'
+
+ if cp.has_option(section, 'name'):
+ self._local_name = cp.get(section, 'name')
+
+ if cp.has_option(section, 'tags'):
+ tag_list = cp.get(section, 'tags').strip(';')
+ self._tags = [tag.strip() for tag in tag_list.split(';')]
+
+ def get_locale_path(self):
+ """Get the locale path inside the (installed) activity bundle."""
+ if self._zip_file is not None:
+ raise NotInstalledException
+ return os.path.join(self._path, 'locale')
+
+ def get_icons_path(self):
+ """Get the icons path inside the (installed) activity bundle."""
+ if self._zip_file is not None:
+ raise NotInstalledException
+ return os.path.join(self._path, 'icons')
+
+ def get_path(self):
+ """Get the activity bundle path."""
+ return self._path
+
+ def get_name(self):
+ """Get the activity user-visible name."""
+ return self._local_name
+
+ def get_bundle_name(self):
+ """Get the activity bundle name."""
+ return self._name
+
+ def get_installation_time(self):
+ """Get a timestamp representing the time at which this activity was
+ installed."""
+ return self._installation_time
+
+ def get_bundle_id(self):
+ """Get the activity bundle id"""
+ return self._bundle_id
+
+ def get_icon(self):
+ """Get the activity icon name"""
+ # FIXME: this should return the icon data, not a filename, so that
+ # we don't need to create a temp file in the zip case
+ icon_path = os.path.join('activity', self._icon + '.svg')
+ if self._zip_file is None:
+ return os.path.join(self._path, icon_path)
+ else:
+ icon_data = self.get_file(icon_path).read()
+ temp_file, temp_file_path = tempfile.mkstemp(prefix=self._icon,
+ suffix='.svg')
+ os.write(temp_file, icon_data)
+ os.close(temp_file)
+ return util.TempFilePath(temp_file_path)
+
+ def get_activity_version(self):
+ """Get the activity version"""
+ return self._activity_version
+
+ def get_command(self):
+ """Get the command to execute to launch the activity factory"""
+ if self.bundle_exec:
+ command = os.path.expandvars(self.bundle_exec)
+ else:
+ command = 'sugar-activity ' + self.activity_class
+
+ return command
+
+ def get_mime_types(self):
+ """Get the MIME types supported by the activity"""
+ return self._mime_types
+
+ def get_tags(self):
+ """Get the tags that describe the activity"""
+ return self._tags
+
+ def get_show_launcher(self):
+ """Get whether there should be a visible launcher for the activity"""
+ return self._show_launcher
+
+ def install(self, install_dir=None, strict_manifest=False):
+ if install_dir is None:
+ install_dir = env.get_user_activities_path()
+
+ self._unzip(install_dir)
+
+ install_path = os.path.join(install_dir, self._zip_root_dir)
+
+ # List installed files
+ manifestfiles = self.get_files(self._raw_manifest())
+ paths = []
+ for root, dirs_, files in os.walk(install_path):
+ rel_path = root[len(install_path) + 1:]
+ for f in files:
+ paths.append(os.path.join(rel_path, f))
+
+ # Check the list against the MANIFEST
+ for path in paths:
+ if path in manifestfiles:
+ manifestfiles.remove(path)
+ elif path != "MANIFEST":
+ logging.warning('Bundle %s: %s not in MANIFEST', self._name,
+ path)
+ if strict_manifest:
+ os.remove(os.path.join(install_path, path))
+
+ # Is anything in MANIFEST left over after accounting for all files?
+ if manifestfiles:
+ err = ("Bundle %s: files in MANIFEST not included: %s"%
+ (self._name, str(manifestfiles)))
+ if strict_manifest:
+ raise MalformedBundleException(err)
+ else:
+ logging.warning(err)
+
+ self.install_mime_type(install_path)
+
+ return install_path
+
+ def install_mime_type(self, install_path):
+ ''' Update the mime type database and install the mime type icon
+ '''
+ xdg_data_home = os.getenv('XDG_DATA_HOME',
+ os.path.expanduser('~/.local/share'))
+
+ mime_path = os.path.join(install_path, 'activity', 'mimetypes.xml')
+ if os.path.isfile(mime_path):
+ mime_dir = os.path.join(xdg_data_home, 'mime')
+ mime_pkg_dir = os.path.join(mime_dir, 'packages')
+ if not os.path.isdir(mime_pkg_dir):
+ os.makedirs(mime_pkg_dir)
+ installed_mime_path = os.path.join(mime_pkg_dir,
+ '%s.xml' % self._bundle_id)
+ self._symlink(mime_path, installed_mime_path)
+ os.spawnlp(os.P_WAIT, 'update-mime-database',
+ 'update-mime-database', mime_dir)
+
+ mime_types = self.get_mime_types()
+ if mime_types is not None:
+ installed_icons_dir = os.path.join(xdg_data_home,
+ 'icons/sugar/scalable/mimetypes')
+ if not os.path.isdir(installed_icons_dir):
+ os.makedirs(installed_icons_dir)
+
+ for mime_type in mime_types:
+ mime_icon_base = os.path.join(install_path, 'activity',
+ mime_type.replace('/', '-'))
+ svg_file = mime_icon_base + '.svg'
+ info_file = mime_icon_base + '.icon'
+ self._symlink(svg_file,
+ os.path.join(installed_icons_dir,
+ os.path.basename(svg_file)))
+ self._symlink(info_file,
+ os.path.join(installed_icons_dir,
+ os.path.basename(info_file)))
+
+ def _symlink(self, src, dst):
+ if not os.path.isfile(src):
+ return
+ if not os.path.islink(dst) and os.path.exists(dst):
+ raise RuntimeError('Do not remove %s if it was not '
+ 'installed by sugar', dst)
+ logging.debug('Link resource %s to %s', src, dst)
+ if os.path.lexists(dst):
+ logging.debug('Relink %s', dst)
+ os.unlink(dst)
+ os.symlink(src, dst)
+
+ def uninstall(self, install_path, force=False):
+ if os.path.islink(install_path):
+ # Don't remove the actual activity dir if it's a symbolic link
+ # because we may be removing user data.
+ os.unlink(install_path)
+ return
+
+ xdg_data_home = os.getenv('XDG_DATA_HOME',
+ os.path.expanduser('~/.local/share'))
+
+ mime_dir = os.path.join(xdg_data_home, 'mime')
+ installed_mime_path = os.path.join(mime_dir, 'packages',
+ '%s.xml' % self._bundle_id)
+ if os.path.exists(installed_mime_path):
+ os.remove(installed_mime_path)
+ os.spawnlp(os.P_WAIT, 'update-mime-database',
+ 'update-mime-database', mime_dir)
+
+ mime_types = self.get_mime_types()
+ if mime_types is not None:
+ installed_icons_dir = os.path.join(xdg_data_home,
+ 'icons/sugar/scalable/mimetypes')
+ if os.path.isdir(installed_icons_dir):
+ for f in os.listdir(installed_icons_dir):
+ path = os.path.join(installed_icons_dir, f)
+ if os.path.islink(path) and \
+ os.readlink(path).startswith(install_path):
+ os.remove(path)
+
+ self._uninstall(install_path)
+
+ def is_user_activity(self):
+ return self.get_path().startswith(env.get_user_activities_path())
diff --git a/toolkit/src/sugar/bundle/bundle.py b/toolkit/src/sugar/bundle/bundle.py
new file mode 100644
index 0000000..cb110cc
--- /dev/null
+++ b/toolkit/src/sugar/bundle/bundle.py
@@ -0,0 +1,205 @@
+# Copyright (C) 2007, Red Hat, Inc.
+#
+# 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.
+
+"""Sugar bundle file handler
+
+UNSTABLE.
+"""
+
+import os
+import logging
+import shutil
+import StringIO
+import zipfile
+
+
+class AlreadyInstalledException(Exception):
+ pass
+
+
+class NotInstalledException(Exception):
+ pass
+
+
+class InvalidPathException(Exception):
+ pass
+
+
+class ZipExtractException(Exception):
+ pass
+
+
+class RegistrationException(Exception):
+ pass
+
+
+class MalformedBundleException(Exception):
+ pass
+
+
+class Bundle(object):
+ """A Sugar activity, content module, etc.
+
+ The bundle itself may be either a zip file or a directory
+ hierarchy, with metadata about the bundle stored various files
+ inside it.
+
+ This is an abstract base class. See ActivityBundle and
+ ContentBundle for more details on those bundle types.
+ """
+
+ _zipped_extension = None
+ _unzipped_extension = None
+
+ def __init__(self, path):
+ self._path = path
+ self._zip_root_dir = None
+ self._zip_file = None
+
+ if not os.path.isdir(self._path):
+ self._zip_file = zipfile.ZipFile(self._path)
+ self._check_zip_bundle()
+
+ # manifest = self._get_file(self._infodir + '/contents')
+ # if manifest is None:
+ # raise MalformedBundleException('No manifest file')
+
+ # signature = self._get_file(self._infodir + '/contents.sig')
+ # if signature is None:
+ # raise MalformedBundleException('No signature file')
+
+ def __del__(self):
+ if self._zip_file is not None:
+ self._zip_file.close()
+
+ def _check_zip_bundle(self):
+ file_names = self._zip_file.namelist()
+ if len(file_names) == 0:
+ raise MalformedBundleException('Empty zip file')
+
+ if file_names[0] == 'mimetype':
+ del file_names[0]
+
+ self._zip_root_dir = file_names[0].split('/')[0]
+ if self._zip_root_dir.startswith('.'):
+ raise MalformedBundleException(
+ 'root directory starts with .')
+ if self._unzipped_extension is not None:
+ (name_, ext) = os.path.splitext(self._zip_root_dir)
+ if ext != self._unzipped_extension:
+ raise MalformedBundleException(
+ 'All files in the bundle must be inside a single ' +
+ 'directory whose name ends with "%s"' %
+ self._unzipped_extension)
+
+ for file_name in file_names:
+ if not file_name.startswith(self._zip_root_dir):
+ raise MalformedBundleException(
+ 'All files in the bundle must be inside a single ' +
+ 'top-level directory')
+
+ def get_file(self, filename):
+ f = None
+
+ if self._zip_file is None:
+ path = os.path.join(self._path, filename)
+ try:
+ f = open(path, "rb")
+ except IOError:
+ return None
+ else:
+ path = os.path.join(self._zip_root_dir, filename)
+ try:
+ data = self._zip_file.read(path)
+ f = StringIO.StringIO(data)
+ except KeyError:
+ logging.debug('%s not found.', filename)
+
+ return f
+
+ def is_file(self, filename):
+ if self._zip_file is None:
+ path = os.path.join(self._path, filename)
+ return os.path.isfile(path)
+ else:
+ path = os.path.join(self._zip_root_dir, filename)
+ try:
+ self._zip_file.getinfo(path)
+ except KeyError:
+ return False
+
+ return True
+
+ def is_dir(self, filename):
+ if self._zip_file is None:
+ path = os.path.join(self._path, filename)
+ return os.path.isdir(path)
+ else:
+ path = os.path.join(self._zip_root_dir, filename, "")
+ for f in self._zip_file.namelist():
+ if f.startswith(path):
+ return True
+ return False
+
+ def get_path(self):
+ """Get the bundle path."""
+ return self._path
+
+ def _unzip(self, install_dir):
+ if self._zip_file is None:
+ raise AlreadyInstalledException
+
+ if not os.path.isdir(install_dir):
+ os.mkdir(install_dir, 0775)
+
+ # zipfile provides API that in theory would let us do this
+ # correctly by hand, but handling all the oddities of
+ # Windows/UNIX mappings, extension attributes, deprecated
+ # features, etc makes it impractical.
+ # FIXME: use manifest
+ if os.spawnlp(os.P_WAIT, 'unzip', 'unzip', '-o', self._path,
+ '-x', 'mimetype', '-d', install_dir):
+ # clean up install dir after failure
+ shutil.rmtree(os.path.join(install_dir, self._zip_root_dir),
+ ignore_errors=True)
+ # indicate failure.
+ raise ZipExtractException
+
+ def _zip(self, bundle_path):
+ if self._zip_file is not None:
+ raise NotInstalledException
+
+ raise NotImplementedError
+
+ def _uninstall(self, install_path):
+ if not os.path.isdir(install_path):
+ raise InvalidPathException
+ if self._unzipped_extension is not None:
+ (name_, ext) = os.path.splitext(install_path)
+ if ext != self._unzipped_extension:
+ raise InvalidPathException
+
+ for root, dirs, files in os.walk(install_path, topdown=False):
+ for name in files:
+ os.remove(os.path.join(root, name))
+ for name in dirs:
+ path = os.path.join(root, name)
+ if os.path.islink(path):
+ os.remove(path)
+ else:
+ os.rmdir(path)
+ os.rmdir(install_path)
diff --git a/toolkit/src/sugar/bundle/contentbundle.py b/toolkit/src/sugar/bundle/contentbundle.py
new file mode 100644
index 0000000..48e05a1
--- /dev/null
+++ b/toolkit/src/sugar/bundle/contentbundle.py
@@ -0,0 +1,239 @@
+# Copyright (C) 2007, Red Hat, Inc.
+# Copyright (C) 2009 Aleksey Lim
+#
+# 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.
+
+"""Sugar content bundles
+
+UNSTABLE.
+"""
+
+from ConfigParser import ConfigParser
+import os
+import urllib
+
+from sugar import env
+from sugar.bundle.bundle import Bundle, NotInstalledException, \
+ MalformedBundleException
+
+
+class ContentBundle(Bundle):
+ """A Sugar content bundle
+
+ See http://wiki.laptop.org/go/Content_bundles for details
+ """
+
+ MIME_TYPE = 'application/vnd.olpc-content'
+
+ _zipped_extension = '.xol'
+ _unzipped_extension = None
+ _infodir = 'library'
+
+ def __init__(self, path):
+ Bundle.__init__(self, path)
+
+ self._locale = None
+ self._l10n = None
+ self._category = None
+ self._name = None
+ self._subcategory = None
+ self._category_class = None
+ self._category_icon = None
+ self._library_version = None
+ self._bundle_class = None
+ self._activity_start = None
+ self._global_name = None
+
+ info_file = self.get_file('library/library.info')
+ if info_file is None:
+ raise MalformedBundleException('No library.info file')
+ self._parse_info(info_file)
+
+ if (self.get_file('index.html') is None and
+ self.get_file('library/library.xml') is None):
+ raise MalformedBundleException(
+ 'Content bundle %s has neither index.html nor library.xml' %
+ self._path)
+
+ def _parse_info(self, info_file):
+ cp = ConfigParser()
+ cp.readfp(info_file)
+
+ section = 'Library'
+
+ if cp.has_option(section, 'name'):
+ self._name = cp.get(section, 'name')
+ else:
+ raise MalformedBundleException(
+ 'Content bundle %s does not specify a name' % self._path)
+
+ if cp.has_option(section, 'library_version'):
+ version = cp.get(section, 'library_version')
+ try:
+ self._library_version = int(version)
+ except ValueError:
+ raise MalformedBundleException(
+ 'Content bundle %s has invalid version number %s' %
+ (self._path, version))
+
+ if cp.has_option(section, 'l10n'):
+ l10n = cp.get(section, 'l10n')
+ if l10n == 'true':
+ self._l10n = True
+ elif l10n == 'false':
+ self._l10n = False
+ else:
+ raise MalformedBundleException(
+ 'Content bundle %s has invalid l10n key "%s"' %
+ (self._path, l10n))
+ else:
+ raise MalformedBundleException(
+ 'Content bundle %s does not specify if it is localized' %
+ self._path)
+
+ if cp.has_option(section, 'locale'):
+ self._locale = cp.get(section, 'locale')
+ else:
+ raise MalformedBundleException(
+ 'Content bundle %s does not specify a locale' % self._path)
+
+ if cp.has_option(section, 'category'):
+ self._category = cp.get(section, 'category')
+ else:
+ raise MalformedBundleException(
+ 'Content bundle %s does not specify a category' % self._path)
+
+ if cp.has_option(section, 'global_name'):
+ self._global_name = cp.get(section, 'global_name')
+ else:
+ self._global_name = None
+
+ if cp.has_option(section, 'category_icon'):
+ self._category_icon = cp.get(section, 'category_icon')
+ else:
+ self._category_icon = None
+
+ if cp.has_option(section, 'category_class'):
+ self._category_class = cp.get(section, 'category_class')
+ else:
+ self._category_class = None
+
+ if cp.has_option(section, 'subcategory'):
+ self._subcategory = cp.get(section, 'subcategory')
+ else:
+ self._subcategory = None
+
+ if cp.has_option(section, 'bundle_class'):
+ self._bundle_class = cp.get(section, 'bundle_class')
+ else:
+ self._bundle_class = None
+
+ if cp.has_option(section, 'activity_start'):
+ self._activity_start = cp.get(section, 'activity_start')
+ else:
+ self._activity_start = 'index.html'
+
+ if self._bundle_class is None and self._global_name is None:
+ raise MalformedBundleException(
+ 'Content bundle %s must specify either global_name or '
+ 'bundle_class' % self._path)
+
+ def get_name(self):
+ return self._name
+
+ def get_library_version(self):
+ return self._library_version
+
+ def get_l10n(self):
+ return self._l10n
+
+ def get_locale(self):
+ return self._locale
+
+ def get_category(self):
+ return self._category
+
+ def get_category_icon(self):
+ return self._category_icon
+
+ def get_category_class(self):
+ return self._category_class
+
+ def get_subcategory(self):
+ return self._subcategory
+
+ def get_bundle_class(self):
+ return self._bundle_class
+
+ def get_activity_start(self):
+ return self._activity_start
+
+ def _run_indexer(self):
+ xdg_data_dirs = os.getenv('XDG_DATA_DIRS',
+ '/usr/local/share/:/usr/share/')
+ for path in xdg_data_dirs.split(':'):
+ indexer = os.path.join(path, 'library-common', 'make_index.py')
+ if os.path.exists(indexer):
+ os.spawnlp(os.P_WAIT, 'python', 'python', indexer)
+
+ def get_root_dir(self):
+ return os.path.join(env.get_user_library_path(), self._zip_root_dir)
+
+ def get_start_path(self):
+ return os.path.join(self.get_root_dir(), self._activity_start)
+
+ def get_start_uri(self):
+ return "file://" + urllib.pathname2url(self.get_start_path())
+
+ def get_bundle_id(self):
+ # TODO treat ContentBundle in special way
+ # needs rethinking while fixing ContentBundle support
+ if self._bundle_class is not None:
+ return self._bundle_class
+ else:
+ return self._global_name
+
+ def get_activity_version(self):
+ # TODO treat ContentBundle in special way
+ # needs rethinking while fixing ContentBundle support
+ return self._library_version
+
+ def is_installed(self):
+ if self._zip_file is None:
+ return True
+ elif os.path.isdir(self.get_root_dir()):
+ return ContentBundle(self.get_root_dir()).get_library_version() \
+ == self.get_library_version()
+ else:
+ return False
+
+ def install(self, install_path):
+ # TODO ignore passed install_path argument
+ # needs rethinking while fixing ContentBundle support
+ install_path = env.get_user_library_path()
+ self._unzip(install_path)
+ self._run_indexer()
+ return self.get_root_dir()
+
+ def uninstall(self):
+ if self._zip_file is None:
+ if not self.is_installed():
+ raise NotInstalledException
+ install_dir = self._path
+ else:
+ install_dir = os.path.join(self.get_root_dir())
+ self._uninstall(install_dir)
+ self._run_indexer()
diff --git a/toolkit/src/sugar/datastore/Makefile.am b/toolkit/src/sugar/datastore/Makefile.am
new file mode 100644
index 0000000..81d760c
--- /dev/null
+++ b/toolkit/src/sugar/datastore/Makefile.am
@@ -0,0 +1,4 @@
+sugardir = $(pythondir)/sugar/datastore
+sugar_PYTHON = \
+ __init__.py \
+ datastore.py
diff --git a/toolkit/src/sugar/datastore/__init__.py b/toolkit/src/sugar/datastore/__init__.py
new file mode 100644
index 0000000..bdb658b
--- /dev/null
+++ b/toolkit/src/sugar/datastore/__init__.py
@@ -0,0 +1,16 @@
+# 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.
diff --git a/toolkit/src/sugar/datastore/datastore.py b/toolkit/src/sugar/datastore/datastore.py
new file mode 100644
index 0000000..3f5188e
--- /dev/null
+++ b/toolkit/src/sugar/datastore/datastore.py
@@ -0,0 +1,549 @@
+# Copyright (C) 2007, One Laptop Per Child
+# Copyright (C) 2010, Simon Schampijer
+#
+# 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.
+
+"""
+STABLE
+"""
+
+import logging
+import time
+from datetime import datetime
+import os
+import tempfile
+import gobject
+import gconf
+import gio
+import dbus
+import dbus.glib
+
+from sugar import env
+from sugar import mime
+from sugar import dispatch
+
+DS_DBUS_SERVICE = "org.laptop.sugar.DataStore"
+DS_DBUS_INTERFACE = "org.laptop.sugar.DataStore"
+DS_DBUS_PATH = "/org/laptop/sugar/DataStore"
+
+_data_store = None
+
+
+def _get_data_store():
+ global _data_store
+
+ if not _data_store:
+ _bus = dbus.SessionBus()
+ _data_store = dbus.Interface(_bus.get_object(DS_DBUS_SERVICE,
+ DS_DBUS_PATH),
+ DS_DBUS_INTERFACE)
+ _data_store.connect_to_signal('Created', __datastore_created_cb)
+ _data_store.connect_to_signal('Deleted', __datastore_deleted_cb)
+ _data_store.connect_to_signal('Updated', __datastore_updated_cb)
+
+ return _data_store
+
+
+def __datastore_created_cb(object_id):
+ metadata = _get_data_store().get_properties(object_id, byte_arrays=True)
+ updated.send(None, object_id=object_id, metadata=metadata)
+
+
+def __datastore_updated_cb(object_id):
+ metadata = _get_data_store().get_properties(object_id, byte_arrays=True)
+ updated.send(None, object_id=object_id, metadata=metadata)
+
+
+def __datastore_deleted_cb(object_id):
+ deleted.send(None, object_id=object_id)
+
+created = dispatch.Signal()
+deleted = dispatch.Signal()
+updated = dispatch.Signal()
+
+_get_data_store()
+
+
+class DSMetadata(gobject.GObject):
+ """A representation of the metadata associated with a DS entry."""
+ __gsignals__ = {
+ 'updated': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
+ }
+
+ def __init__(self, properties=None):
+ gobject.GObject.__init__(self)
+ if not properties:
+ self._properties = {}
+ else:
+ self._properties = properties
+
+ default_keys = ['activity', 'activity_id',
+ 'mime_type', 'title_set_by_user']
+ for key in default_keys:
+ if key not in self._properties:
+ self._properties[key] = ''
+
+ def __getitem__(self, key):
+ return self._properties[key]
+
+ def __setitem__(self, key, value):
+ if key not in self._properties or self._properties[key] != value:
+ self._properties[key] = value
+ self.emit('updated')
+
+ def __delitem__(self, key):
+ del self._properties[key]
+
+ def __contains__(self, key):
+ return self._properties.__contains__(key)
+
+ def has_key(self, key):
+ logging.warning(".has_key() is deprecated, use 'in'")
+ return key in self._properties
+
+ def keys(self):
+ return self._properties.keys()
+
+ def get_dictionary(self):
+ return self._properties
+
+ def copy(self):
+ return DSMetadata(self._properties.copy())
+
+ def get(self, key, default=None):
+ if key in self._properties:
+ return self._properties[key]
+ else:
+ return default
+
+ def update(self, properties):
+ """Update all of the metadata"""
+ for (key, value) in properties.items():
+ self[key] = value
+
+
+class DSObject(object):
+ """A representation of a DS entry."""
+
+ def __init__(self, object_id, metadata=None, file_path=None):
+ self._update_signal_match = None
+ self._object_id = None
+
+ self.set_object_id(object_id)
+
+ self._metadata = metadata
+ self._file_path = file_path
+ self._destroyed = False
+ self._owns_file = False
+
+ def get_object_id(self):
+ return self._object_id
+
+ def set_object_id(self, object_id):
+ if self._update_signal_match is not None:
+ self._update_signal_match.remove()
+ if object_id is not None:
+ self._update_signal_match = _get_data_store().connect_to_signal(
+ 'Updated', self.__object_updated_cb, arg0=object_id)
+
+ self._object_id = object_id
+
+ object_id = property(get_object_id, set_object_id)
+
+ def __object_updated_cb(self, object_id):
+ properties = _get_data_store().get_properties(self._object_id,
+ byte_arrays=True)
+ self._metadata.update(properties)
+
+ def get_metadata(self):
+ if self._metadata is None and not self.object_id is None:
+ properties = _get_data_store().get_properties(self.object_id)
+ metadata = DSMetadata(properties)
+ self._metadata = metadata
+ return self._metadata
+
+ def set_metadata(self, metadata):
+ if self._metadata != metadata:
+ self._metadata = metadata
+
+ metadata = property(get_metadata, set_metadata)
+
+ def get_file_path(self, fetch=True):
+ if fetch and self._file_path is None and not self.object_id is None:
+ self.set_file_path(_get_data_store().get_filename(self.object_id))
+ self._owns_file = True
+ return self._file_path
+
+ def set_file_path(self, file_path):
+ if self._file_path != file_path:
+ if self._file_path and self._owns_file:
+ if os.path.isfile(self._file_path):
+ os.remove(self._file_path)
+ self._owns_file = False
+ self._file_path = file_path
+
+ file_path = property(get_file_path, set_file_path)
+
+ def destroy(self):
+ if self._destroyed:
+ logging.warning('This DSObject has already been destroyed!.')
+ return
+ self._destroyed = True
+ if self._file_path and self._owns_file:
+ if os.path.isfile(self._file_path):
+ os.remove(self._file_path)
+ self._owns_file = False
+ self._file_path = None
+
+ def __del__(self):
+ if not self._destroyed:
+ logging.warning('DSObject was deleted without cleaning up first. '
+ 'Call DSObject.destroy() before disposing it.')
+ self.destroy()
+
+ def copy(self):
+ return DSObject(None, self._metadata.copy(), self._file_path)
+
+
+class RawObject(object):
+ """A representation for objects not in the DS but
+ in the file system.
+
+ """
+
+ def __init__(self, file_path):
+ stat = os.stat(file_path)
+ client = gconf.client_get_default()
+ metadata = {
+ 'uid': file_path,
+ 'title': os.path.basename(file_path),
+ 'timestamp': stat.st_mtime,
+ 'mime_type': gio.content_type_guess(filename=file_path),
+ 'activity': '',
+ 'activity_id': '',
+ 'icon-color': client.get_string('/desktop/sugar/user/color'),
+ 'description': file_path,
+ }
+
+ self.object_id = file_path
+ self._metadata = DSMetadata(metadata)
+ self._file_path = None
+ self._destroyed = False
+
+ def get_metadata(self):
+ return self._metadata
+
+ metadata = property(get_metadata)
+
+ def get_file_path(self, fetch=True):
+ # we have to create symlink since its a common practice
+ # to create hardlinks to jobject files
+ # and w/o this, it wouldn't work since we have file from mounted device
+ if self._file_path is None:
+ data_path = os.path.join(env.get_profile_path(), 'data')
+ self._file_path = tempfile.mktemp(
+ prefix='rawobject', dir=data_path)
+ if not os.path.exists(data_path):
+ os.makedirs(data_path)
+ os.symlink(self.object_id, self._file_path)
+ return self._file_path
+
+ file_path = property(get_file_path)
+
+ def destroy(self):
+ if self._destroyed:
+ logging.warning('This RawObject has already been destroyed!.')
+ return
+ self._destroyed = True
+ if self._file_path is not None:
+ if os.path.exists(self._file_path):
+ os.remove(self._file_path)
+ self._file_path = None
+
+ def __del__(self):
+ if not self._destroyed:
+ logging.warning('RawObject was deleted without cleaning up. '
+ 'Call RawObject.destroy() before disposing it.')
+ self.destroy()
+
+
+def get(object_id):
+ """Get the properties of the object with the ID given.
+
+ Keyword arguments:
+ object_id -- unique identifier of the object
+
+ Return: a DSObject
+
+ """
+ logging.debug('datastore.get')
+
+ if object_id.startswith('/'):
+ return RawObject(object_id)
+
+ metadata = _get_data_store().get_properties(object_id, byte_arrays=True)
+
+ ds_object = DSObject(object_id, DSMetadata(metadata), None)
+ # TODO: register the object for updates
+ return ds_object
+
+
+def create():
+ """Create a new DSObject.
+
+ Return: a DSObject
+
+ """
+ metadata = DSMetadata()
+ metadata['mtime'] = datetime.now().isoformat()
+ metadata['timestamp'] = int(time.time())
+ return DSObject(object_id=None, metadata=metadata, file_path=None)
+
+
+def _update_ds_entry(uid, properties, filename, transfer_ownership=False,
+ reply_handler=None, error_handler=None, timeout=-1):
+ debug_properties = properties.copy()
+ if "preview" in debug_properties:
+ debug_properties["preview"] = "<omitted>"
+ logging.debug('dbus_helpers.update: %s, %s, %s, %s', uid, filename,
+ debug_properties, transfer_ownership)
+ if reply_handler and error_handler:
+ _get_data_store().update(uid, dbus.Dictionary(properties), filename,
+ transfer_ownership,
+ reply_handler=reply_handler,
+ error_handler=error_handler,
+ timeout=timeout)
+ else:
+ _get_data_store().update(uid, dbus.Dictionary(properties),
+ filename, transfer_ownership)
+
+
+def _create_ds_entry(properties, filename, transfer_ownership=False):
+ object_id = _get_data_store().create(dbus.Dictionary(properties), filename,
+ transfer_ownership)
+ return object_id
+
+
+def write(ds_object, update_mtime=True, transfer_ownership=False,
+ reply_handler=None, error_handler=None, timeout=-1):
+ """Write the DSObject given to the datastore. Creates a new entry if
+ the entry does not exist yet.
+
+ Keyword arguments:
+ update_mtime -- boolean if the mtime of the entry should be regenerated
+ (default True)
+ transfer_ownership -- set it to true if the ownership of the entry should
+ be passed - who is responsible to delete the file
+ when done with it (default False)
+ reply_handler -- will be called with the method's return values as
+ arguments (default None)
+ error_handler -- will be called with an instance of a DBusException
+ representing a remote exception (default None)
+ timeout -- dbus timeout for the caller to wait (default -1)
+
+ """
+ logging.debug('datastore.write')
+
+ properties = ds_object.metadata.get_dictionary().copy()
+
+ if update_mtime:
+ properties['mtime'] = datetime.now().isoformat()
+ properties['timestamp'] = int(time.time())
+
+ file_path = ds_object.get_file_path(fetch=False)
+ if file_path is None:
+ file_path = ''
+
+ # FIXME: this func will be sync for creates regardless of the handlers
+ # supplied. This is very bad API, need to decide what to do here.
+ if ds_object.object_id:
+ _update_ds_entry(ds_object.object_id,
+ properties,
+ file_path,
+ transfer_ownership,
+ reply_handler=reply_handler,
+ error_handler=error_handler,
+ timeout=timeout)
+ else:
+ if reply_handler or error_handler:
+ logging.warning('datastore.write() cannot currently be called' \
+ 'async for creates, see ticket 3071')
+ ds_object.object_id = _create_ds_entry(properties, file_path,
+ transfer_ownership)
+ ds_object.metadata['uid'] = ds_object.object_id
+ # TODO: register the object for updates
+ logging.debug('Written object %s to the datastore.', ds_object.object_id)
+
+
+def delete(object_id):
+ """Delete the datastore entry with the given uid.
+
+ Keyword arguments:
+ object_id -- uid of the datastore entry
+
+ """
+ logging.debug('datastore.delete')
+ _get_data_store().delete(object_id)
+
+
+def find(query, sorting=None, limit=None, offset=None, properties=None,
+ reply_handler=None, error_handler=None):
+ """Find DS entries that match the query provided.
+
+ Keyword arguments:
+ query -- a dictionary containing metadata key value pairs
+ for a fulltext search use the key 'query' e.g. {'query': 'blue*'}
+ other possible well-known properties are:
+ 'activity': 'my.organization.MyActivity'
+ 'activity_id': '6f7f3acacca87886332f50bdd522d805f0abbf1f'
+ 'title': 'My new project'
+ 'title_set_by_user': '0'
+ 'keep': '0'
+ 'ctime': '1972-05-12T18:41:08'
+ 'mtime': '2007-06-16T03:42:33'
+ 'timestamp': 1192715145
+ 'preview': ByteArray(png file data, 300x225 px)
+ 'icon-color': '#ff0000,#ffff00'
+ 'mime_type': 'application/x-my-activity'
+ 'share-scope': # if shared
+ 'buddies': '{}'
+ 'description': 'some longer text'
+ 'tags': 'one two'
+ sorting -- key to order results by e.g. 'timestamp' (default None)
+ limit -- return only limit results (default None)
+ offset -- return only results starting at offset (default None)
+ properties -- you can specify here a list of metadata you want to be
+ present in the result e.g. ['title, 'keep'] (default None)
+ reply_handler -- will be called with the method's return values as
+ arguments (default None)
+ error_handler -- will be called with an instance of a DBusException
+ representing a remote exception (default None)
+
+ Return: DSObjects matching the query, number of matches
+
+ """
+ query = query.copy()
+
+ if properties is None:
+ properties = []
+
+ if sorting:
+ query['order_by'] = sorting
+ if limit:
+ query['limit'] = limit
+ if offset:
+ query['offset'] = offset
+
+ if reply_handler and error_handler:
+ _get_data_store().find(query, properties,
+ reply_handler=reply_handler,
+ error_handler=error_handler,
+ byte_arrays=True)
+ return
+ else:
+ entries, total_count = _get_data_store().find(query, properties,
+ byte_arrays=True)
+ ds_objects = []
+ for entry in entries:
+ object_id = entry['uid']
+ del entry['uid']
+
+ ds_object = DSObject(object_id, DSMetadata(entry), None)
+ ds_objects.append(ds_object)
+
+ return ds_objects, total_count
+
+
+def copy(ds_object, mount_point):
+ """Copy a datastore entry
+
+ Keyword arguments:
+ ds_object -- DSObject to copy
+ mount_point -- mount point of the new datastore entry
+
+ """
+ new_ds_object = ds_object.copy()
+ new_ds_object.metadata['mountpoint'] = mount_point
+
+ if 'title' in ds_object.metadata:
+ filename = ds_object.metadata['title']
+
+ if 'mime_type' in ds_object.metadata:
+ mime_type = ds_object.metadata['mime_type']
+ extension = mime.get_primary_extension(mime_type)
+ if extension:
+ filename += '.' + extension
+
+ new_ds_object.metadata['suggested_filename'] = filename
+
+ # this will cause the file be retrieved from the DS
+ new_ds_object.file_path = ds_object.file_path
+
+ write(new_ds_object)
+
+
+def mount(uri, options, timeout=-1):
+ """Deprecated. API private to the shell. Mount a device.
+
+ Keyword arguments:
+ uri -- identifier of the device
+ options -- mount options
+ timeout -- dbus timeout for the caller to wait (default -1)
+
+ Return: empty string
+
+ """
+ return _get_data_store().mount(uri, options, timeout=timeout)
+
+
+def unmount(mount_point_id):
+ """Deprecated. API private to the shell.
+
+ Keyword arguments:
+ mount_point_id -- id of the mount point
+
+ Note: API private to the shell.
+
+ """
+ _get_data_store().unmount(mount_point_id)
+
+
+def mounts():
+ """Deprecated. Returns the mount point of the datastore. We get mount
+ points through gio now. API private to the shell.
+
+ Return: datastore mount point
+
+ """
+ return _get_data_store().mounts()
+
+
+def complete_indexing():
+ """Deprecated. API private to the shell."""
+ logging.warning('The method complete_indexing has been deprecated.')
+
+
+def get_unique_values(key):
+ """Retrieve an array of unique values for a field.
+
+ Keyword arguments:
+ key -- only the property activity is currently supported
+
+ Return: list of activities
+
+ """
+ return _get_data_store().get_uniquevaluesfor(
+ key, dbus.Dictionary({}, signature='ss'))
diff --git a/toolkit/src/sugar/eggaccelerators.c b/toolkit/src/sugar/eggaccelerators.c
new file mode 100644
index 0000000..0487db0
--- /dev/null
+++ b/toolkit/src/sugar/eggaccelerators.c
@@ -0,0 +1,702 @@
+/* eggaccelerators.c
+ * Copyright (C) 2002 Red Hat, Inc.; Copyright 1998, 2001 Tim Janik
+ * Developed by Havoc Pennington, Tim Janik
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library 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.
+ */
+
+#include "eggaccelerators.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <gdk/gdkx.h>
+#include <gdk/gdkkeysyms.h>
+
+enum
+{
+ EGG_MODMAP_ENTRY_SHIFT = 0,
+ EGG_MODMAP_ENTRY_LOCK = 1,
+ EGG_MODMAP_ENTRY_CONTROL = 2,
+ EGG_MODMAP_ENTRY_MOD1 = 3,
+ EGG_MODMAP_ENTRY_MOD2 = 4,
+ EGG_MODMAP_ENTRY_MOD3 = 5,
+ EGG_MODMAP_ENTRY_MOD4 = 6,
+ EGG_MODMAP_ENTRY_MOD5 = 7,
+ EGG_MODMAP_ENTRY_LAST = 8
+};
+
+#define MODMAP_ENTRY_TO_MODIFIER(x) (1 << (x))
+
+typedef struct
+{
+ EggVirtualModifierType mapping[EGG_MODMAP_ENTRY_LAST];
+
+} EggModmap;
+
+const EggModmap* egg_keymap_get_modmap (GdkKeymap *keymap);
+
+static inline gboolean
+is_alt (const gchar *string)
+{
+ return ((string[0] == '<') &&
+ (string[1] == 'a' || string[1] == 'A') &&
+ (string[2] == 'l' || string[2] == 'L') &&
+ (string[3] == 't' || string[3] == 'T') &&
+ (string[4] == '>'));
+}
+
+static inline gboolean
+is_ctl (const gchar *string)
+{
+ return ((string[0] == '<') &&
+ (string[1] == 'c' || string[1] == 'C') &&
+ (string[2] == 't' || string[2] == 'T') &&
+ (string[3] == 'l' || string[3] == 'L') &&
+ (string[4] == '>'));
+}
+
+static inline gboolean
+is_modx (const gchar *string)
+{
+ return ((string[0] == '<') &&
+ (string[1] == 'm' || string[1] == 'M') &&
+ (string[2] == 'o' || string[2] == 'O') &&
+ (string[3] == 'd' || string[3] == 'D') &&
+ (string[4] >= '1' && string[4] <= '5') &&
+ (string[5] == '>'));
+}
+
+static inline gboolean
+is_ctrl (const gchar *string)
+{
+ return ((string[0] == '<') &&
+ (string[1] == 'c' || string[1] == 'C') &&
+ (string[2] == 't' || string[2] == 'T') &&
+ (string[3] == 'r' || string[3] == 'R') &&
+ (string[4] == 'l' || string[4] == 'L') &&
+ (string[5] == '>'));
+}
+
+static inline gboolean
+is_shft (const gchar *string)
+{
+ return ((string[0] == '<') &&
+ (string[1] == 's' || string[1] == 'S') &&
+ (string[2] == 'h' || string[2] == 'H') &&
+ (string[3] == 'f' || string[3] == 'F') &&
+ (string[4] == 't' || string[4] == 'T') &&
+ (string[5] == '>'));
+}
+
+static inline gboolean
+is_shift (const gchar *string)
+{
+ return ((string[0] == '<') &&
+ (string[1] == 's' || string[1] == 'S') &&
+ (string[2] == 'h' || string[2] == 'H') &&
+ (string[3] == 'i' || string[3] == 'I') &&
+ (string[4] == 'f' || string[4] == 'F') &&
+ (string[5] == 't' || string[5] == 'T') &&
+ (string[6] == '>'));
+}
+
+static inline gboolean
+is_control (const gchar *string)
+{
+ return ((string[0] == '<') &&
+ (string[1] == 'c' || string[1] == 'C') &&
+ (string[2] == 'o' || string[2] == 'O') &&
+ (string[3] == 'n' || string[3] == 'N') &&
+ (string[4] == 't' || string[4] == 'T') &&
+ (string[5] == 'r' || string[5] == 'R') &&
+ (string[6] == 'o' || string[6] == 'O') &&
+ (string[7] == 'l' || string[7] == 'L') &&
+ (string[8] == '>'));
+}
+
+static inline gboolean
+is_release (const gchar *string)
+{
+ return ((string[0] == '<') &&
+ (string[1] == 'r' || string[1] == 'R') &&
+ (string[2] == 'e' || string[2] == 'E') &&
+ (string[3] == 'l' || string[3] == 'L') &&
+ (string[4] == 'e' || string[4] == 'E') &&
+ (string[5] == 'a' || string[5] == 'A') &&
+ (string[6] == 's' || string[6] == 'S') &&
+ (string[7] == 'e' || string[7] == 'E') &&
+ (string[8] == '>'));
+}
+
+static inline gboolean
+is_meta (const gchar *string)
+{
+ return ((string[0] == '<') &&
+ (string[1] == 'm' || string[1] == 'M') &&
+ (string[2] == 'e' || string[2] == 'E') &&
+ (string[3] == 't' || string[3] == 'T') &&
+ (string[4] == 'a' || string[4] == 'A') &&
+ (string[5] == '>'));
+}
+
+static inline gboolean
+is_super (const gchar *string)
+{
+ return ((string[0] == '<') &&
+ (string[1] == 's' || string[1] == 'S') &&
+ (string[2] == 'u' || string[2] == 'U') &&
+ (string[3] == 'p' || string[3] == 'P') &&
+ (string[4] == 'e' || string[4] == 'E') &&
+ (string[5] == 'r' || string[5] == 'R') &&
+ (string[6] == '>'));
+}
+
+static inline gboolean
+is_hyper (const gchar *string)
+{
+ return ((string[0] == '<') &&
+ (string[1] == 'h' || string[1] == 'H') &&
+ (string[2] == 'y' || string[2] == 'Y') &&
+ (string[3] == 'p' || string[3] == 'P') &&
+ (string[4] == 'e' || string[4] == 'E') &&
+ (string[5] == 'r' || string[5] == 'R') &&
+ (string[6] == '>'));
+}
+
+static inline gboolean
+is_keycode (const gchar *string)
+{
+ return ((string[0] == '0') &&
+ (string[1] == 'x'));
+}
+
+/**
+ * egg_accelerator_parse_virtual:
+ * @accelerator: string representing an accelerator
+ * @accelerator_key: return location for accelerator keyval
+ * @accelerator_mods: return location for accelerator modifier mask
+ *
+ * Parses a string representing a virtual accelerator. The format
+ * looks like "&lt;Control&gt;a" or "&lt;Shift&gt;&lt;Alt&gt;F1" or
+ * "&lt;Release&gt;z" (the last one is for key release). The parser
+ * is fairly liberal and allows lower or upper case, and also
+ * abbreviations such as "&lt;Ctl&gt;" and "&lt;Ctrl&gt;".
+ *
+ * If the parse fails, @accelerator_key and @accelerator_mods will
+ * be set to 0 (zero) and %FALSE will be returned. If the string contains
+ * only modifiers, @accelerator_key will be set to 0 but %TRUE will be
+ * returned.
+ *
+ * The virtual vs. concrete accelerator distinction is a relic of
+ * how the X Window System works; there are modifiers Mod2-Mod5 that
+ * can represent various keyboard keys (numlock, meta, hyper, etc.),
+ * the virtual modifier represents the keyboard key, the concrete
+ * modifier the actual Mod2-Mod5 bits in the key press event.
+ *
+ * Returns: %TRUE on success.
+ */
+gboolean
+egg_accelerator_parse_virtual (const gchar *accelerator,
+ guint *accelerator_key,
+ guint *keycode,
+ EggVirtualModifierType *accelerator_mods)
+{
+ guint keyval;
+ GdkModifierType mods;
+ gint len;
+ gboolean bad_keyval;
+
+ if (accelerator_key)
+ *accelerator_key = 0;
+ if (accelerator_mods)
+ *accelerator_mods = 0;
+ if (keycode)
+ *keycode = 0;
+
+ g_return_val_if_fail (accelerator != NULL, FALSE);
+
+ bad_keyval = FALSE;
+
+ keyval = 0;
+ mods = 0;
+ len = strlen (accelerator);
+ while (len)
+ {
+ if (*accelerator == '<')
+ {
+ if (len >= 9 && is_release (accelerator))
+ {
+ accelerator += 9;
+ len -= 9;
+ mods |= EGG_VIRTUAL_RELEASE_MASK;
+ }
+ else if (len >= 9 && is_control (accelerator))
+ {
+ accelerator += 9;
+ len -= 9;
+ mods |= EGG_VIRTUAL_CONTROL_MASK;
+ }
+ else if (len >= 7 && is_shift (accelerator))
+ {
+ accelerator += 7;
+ len -= 7;
+ mods |= EGG_VIRTUAL_SHIFT_MASK;
+ }
+ else if (len >= 6 && is_shft (accelerator))
+ {
+ accelerator += 6;
+ len -= 6;
+ mods |= EGG_VIRTUAL_SHIFT_MASK;
+ }
+ else if (len >= 6 && is_ctrl (accelerator))
+ {
+ accelerator += 6;
+ len -= 6;
+ mods |= EGG_VIRTUAL_CONTROL_MASK;
+ }
+ else if (len >= 6 && is_modx (accelerator))
+ {
+ static const guint mod_vals[] = {
+ EGG_VIRTUAL_ALT_MASK, EGG_VIRTUAL_MOD2_MASK, EGG_VIRTUAL_MOD3_MASK,
+ EGG_VIRTUAL_MOD4_MASK, EGG_VIRTUAL_MOD5_MASK
+ };
+
+ len -= 6;
+ accelerator += 4;
+ mods |= mod_vals[*accelerator - '1'];
+ accelerator += 2;
+ }
+ else if (len >= 5 && is_ctl (accelerator))
+ {
+ accelerator += 5;
+ len -= 5;
+ mods |= EGG_VIRTUAL_CONTROL_MASK;
+ }
+ else if (len >= 5 && is_alt (accelerator))
+ {
+ accelerator += 5;
+ len -= 5;
+ mods |= EGG_VIRTUAL_ALT_MASK;
+ }
+ else if (len >= 6 && is_meta (accelerator))
+ {
+ accelerator += 6;
+ len -= 6;
+ mods |= EGG_VIRTUAL_META_MASK;
+ }
+ else if (len >= 7 && is_hyper (accelerator))
+ {
+ accelerator += 7;
+ len -= 7;
+ mods |= EGG_VIRTUAL_HYPER_MASK;
+ }
+ else if (len >= 7 && is_super (accelerator))
+ {
+ accelerator += 7;
+ len -= 7;
+ mods |= EGG_VIRTUAL_SUPER_MASK;
+ }
+ else
+ {
+ gchar last_ch;
+
+ last_ch = *accelerator;
+ while (last_ch && last_ch != '>')
+ {
+ last_ch = *accelerator;
+ accelerator += 1;
+ len -= 1;
+ }
+ }
+ }
+ else
+ {
+ keyval = gdk_keyval_from_name (accelerator);
+
+ if (keyval == 0)
+ {
+ /* If keyval is 0, than maybe it's a keycode. Check for 0x## */
+ if (len >= 4 && is_keycode (accelerator))
+ {
+ char keystring[5];
+ gchar *endptr;
+ gint tmp_keycode;
+
+ memcpy (keystring, accelerator, 4);
+ keystring [4] = '\000';
+
+ tmp_keycode = strtol (keystring, &endptr, 16);
+
+ if (endptr == NULL || *endptr != '\000')
+ {
+ bad_keyval = TRUE;
+ }
+ else if (keycode != NULL)
+ {
+ *keycode = tmp_keycode;
+ /* 0x00 is an invalid keycode too. */
+ if (*keycode == 0)
+ bad_keyval = TRUE;
+ }
+ }
+ } else if (keycode != NULL)
+ *keycode = XKeysymToKeycode (GDK_DISPLAY(), keyval);
+
+ accelerator += len;
+ len -= len;
+ }
+ }
+
+ if (accelerator_key)
+ *accelerator_key = gdk_keyval_to_lower (keyval);
+ if (accelerator_mods)
+ *accelerator_mods = mods;
+
+ return !bad_keyval;
+}
+
+
+/**
+ * egg_virtual_accelerator_name:
+ * @accelerator_key: accelerator keyval
+ * @accelerator_mods: accelerator modifier mask
+ * @returns: a newly-allocated accelerator name
+ *
+ * Converts an accelerator keyval and modifier mask
+ * into a string parseable by egg_accelerator_parse_virtual().
+ * For example, if you pass in #GDK_q and #EGG_VIRTUAL_CONTROL_MASK,
+ * this function returns "&lt;Control&gt;q".
+ *
+ * The caller of this function must free the returned string.
+ */
+gchar*
+egg_virtual_accelerator_name (guint accelerator_key,
+ guint keycode,
+ EggVirtualModifierType accelerator_mods)
+{
+ static const gchar text_release[] = "<Release>";
+ static const gchar text_shift[] = "<Shift>";
+ static const gchar text_control[] = "<Control>";
+ static const gchar text_mod1[] = "<Alt>";
+ static const gchar text_mod2[] = "<Mod2>";
+ static const gchar text_mod3[] = "<Mod3>";
+ static const gchar text_mod4[] = "<Mod4>";
+ static const gchar text_mod5[] = "<Mod5>";
+ static const gchar text_meta[] = "<Meta>";
+ static const gchar text_super[] = "<Super>";
+ static const gchar text_hyper[] = "<Hyper>";
+ guint l;
+ gchar *keyval_name;
+ gchar *accelerator;
+
+ accelerator_mods &= EGG_VIRTUAL_MODIFIER_MASK;
+
+ if (!accelerator_key)
+ {
+ keyval_name = g_strdup_printf ("0x%02x", keycode);
+ }
+ else
+ {
+ keyval_name = gdk_keyval_name (gdk_keyval_to_lower (accelerator_key));
+ if (!keyval_name)
+ keyval_name = "";
+ }
+
+ l = 0;
+ if (accelerator_mods & EGG_VIRTUAL_RELEASE_MASK)
+ l += sizeof (text_release) - 1;
+ if (accelerator_mods & EGG_VIRTUAL_SHIFT_MASK)
+ l += sizeof (text_shift) - 1;
+ if (accelerator_mods & EGG_VIRTUAL_CONTROL_MASK)
+ l += sizeof (text_control) - 1;
+ if (accelerator_mods & EGG_VIRTUAL_ALT_MASK)
+ l += sizeof (text_mod1) - 1;
+ if (accelerator_mods & EGG_VIRTUAL_MOD2_MASK)
+ l += sizeof (text_mod2) - 1;
+ if (accelerator_mods & EGG_VIRTUAL_MOD3_MASK)
+ l += sizeof (text_mod3) - 1;
+ if (accelerator_mods & EGG_VIRTUAL_MOD4_MASK)
+ l += sizeof (text_mod4) - 1;
+ if (accelerator_mods & EGG_VIRTUAL_MOD5_MASK)
+ l += sizeof (text_mod5) - 1;
+ if (accelerator_mods & EGG_VIRTUAL_META_MASK)
+ l += sizeof (text_meta) - 1;
+ if (accelerator_mods & EGG_VIRTUAL_HYPER_MASK)
+ l += sizeof (text_hyper) - 1;
+ if (accelerator_mods & EGG_VIRTUAL_SUPER_MASK)
+ l += sizeof (text_super) - 1;
+ l += strlen (keyval_name);
+
+ accelerator = g_new (gchar, l + 1);
+
+ l = 0;
+ accelerator[l] = 0;
+ if (accelerator_mods & EGG_VIRTUAL_RELEASE_MASK)
+ {
+ strcpy (accelerator + l, text_release);
+ l += sizeof (text_release) - 1;
+ }
+ if (accelerator_mods & EGG_VIRTUAL_SHIFT_MASK)
+ {
+ strcpy (accelerator + l, text_shift);
+ l += sizeof (text_shift) - 1;
+ }
+ if (accelerator_mods & EGG_VIRTUAL_CONTROL_MASK)
+ {
+ strcpy (accelerator + l, text_control);
+ l += sizeof (text_control) - 1;
+ }
+ if (accelerator_mods & EGG_VIRTUAL_ALT_MASK)
+ {
+ strcpy (accelerator + l, text_mod1);
+ l += sizeof (text_mod1) - 1;
+ }
+ if (accelerator_mods & EGG_VIRTUAL_MOD2_MASK)
+ {
+ strcpy (accelerator + l, text_mod2);
+ l += sizeof (text_mod2) - 1;
+ }
+ if (accelerator_mods & EGG_VIRTUAL_MOD3_MASK)
+ {
+ strcpy (accelerator + l, text_mod3);
+ l += sizeof (text_mod3) - 1;
+ }
+ if (accelerator_mods & EGG_VIRTUAL_MOD4_MASK)
+ {
+ strcpy (accelerator + l, text_mod4);
+ l += sizeof (text_mod4) - 1;
+ }
+ if (accelerator_mods & EGG_VIRTUAL_MOD5_MASK)
+ {
+ strcpy (accelerator + l, text_mod5);
+ l += sizeof (text_mod5) - 1;
+ }
+ if (accelerator_mods & EGG_VIRTUAL_META_MASK)
+ {
+ strcpy (accelerator + l, text_meta);
+ l += sizeof (text_meta) - 1;
+ }
+ if (accelerator_mods & EGG_VIRTUAL_HYPER_MASK)
+ {
+ strcpy (accelerator + l, text_hyper);
+ l += sizeof (text_hyper) - 1;
+ }
+ if (accelerator_mods & EGG_VIRTUAL_SUPER_MASK)
+ {
+ strcpy (accelerator + l, text_super);
+ l += sizeof (text_super) - 1;
+ }
+
+ strcpy (accelerator + l, keyval_name);
+
+ return accelerator;
+}
+
+void
+egg_keymap_resolve_virtual_modifiers (GdkKeymap *keymap,
+ EggVirtualModifierType virtual_mods,
+ GdkModifierType *concrete_mods)
+{
+ GdkModifierType concrete;
+ int i;
+ const EggModmap *modmap;
+
+ g_return_if_fail (GDK_IS_KEYMAP (keymap));
+ g_return_if_fail (concrete_mods != NULL);
+
+ modmap = egg_keymap_get_modmap (keymap);
+
+ /* Not so sure about this algorithm. */
+
+ concrete = 0;
+ i = 0;
+ while (i < EGG_MODMAP_ENTRY_LAST)
+ {
+ if (modmap->mapping[i] & virtual_mods)
+ concrete |= (1 << i);
+
+ ++i;
+ }
+
+ *concrete_mods = concrete;
+}
+
+void
+egg_keymap_virtualize_modifiers (GdkKeymap *keymap,
+ GdkModifierType concrete_mods,
+ EggVirtualModifierType *virtual_mods)
+{
+ GdkModifierType virtual;
+ int i;
+ const EggModmap *modmap;
+
+ g_return_if_fail (GDK_IS_KEYMAP (keymap));
+ g_return_if_fail (virtual_mods != NULL);
+
+ modmap = egg_keymap_get_modmap (keymap);
+
+ /* Not so sure about this algorithm. */
+
+ virtual = 0;
+ i = 0;
+ while (i < EGG_MODMAP_ENTRY_LAST)
+ {
+ if ((1 << i) & concrete_mods)
+ {
+ EggVirtualModifierType cleaned;
+
+ cleaned = modmap->mapping[i] & ~(EGG_VIRTUAL_MOD2_MASK |
+ EGG_VIRTUAL_MOD3_MASK |
+ EGG_VIRTUAL_MOD4_MASK |
+ EGG_VIRTUAL_MOD5_MASK);
+
+ if (cleaned != 0)
+ {
+ virtual |= cleaned;
+ }
+ else
+ {
+ /* Rather than dropping mod2->mod5 if not bound,
+ * go ahead and use the concrete names
+ */
+ virtual |= modmap->mapping[i];
+ }
+ }
+
+ ++i;
+ }
+
+ *virtual_mods = virtual;
+}
+
+static void
+reload_modmap (GdkKeymap *keymap,
+ EggModmap *modmap)
+{
+ XModifierKeymap *xmodmap;
+ int map_size;
+ int i;
+
+ /* FIXME multihead */
+ xmodmap = XGetModifierMapping (gdk_x11_get_default_xdisplay ());
+
+ memset (modmap->mapping, 0, sizeof (modmap->mapping));
+
+ /* there are 8 modifiers, and the first 3 are shift, shift lock,
+ * and control
+ */
+ map_size = 8 * xmodmap->max_keypermod;
+ i = 3 * xmodmap->max_keypermod;
+ while (i < map_size)
+ {
+ /* get the key code at this point in the map,
+ * see if its keysym is one we're interested in
+ */
+ int keycode = xmodmap->modifiermap[i];
+ GdkKeymapKey *keys;
+ guint *keyvals;
+ int n_entries;
+ int j;
+ EggVirtualModifierType mask;
+
+ keys = NULL;
+ keyvals = NULL;
+ n_entries = 0;
+
+ gdk_keymap_get_entries_for_keycode (keymap,
+ keycode,
+ &keys, &keyvals, &n_entries);
+
+ mask = 0;
+ j = 0;
+ while (j < n_entries)
+ {
+ if (keyvals[j] == GDK_Num_Lock)
+ mask |= EGG_VIRTUAL_NUM_LOCK_MASK;
+ else if (keyvals[j] == GDK_Scroll_Lock)
+ mask |= EGG_VIRTUAL_SCROLL_LOCK_MASK;
+ else if (keyvals[j] == GDK_Meta_L ||
+ keyvals[j] == GDK_Meta_R)
+ mask |= EGG_VIRTUAL_META_MASK;
+ else if (keyvals[j] == GDK_Hyper_L ||
+ keyvals[j] == GDK_Hyper_R)
+ mask |= EGG_VIRTUAL_HYPER_MASK;
+ else if (keyvals[j] == GDK_Super_L ||
+ keyvals[j] == GDK_Super_R)
+ mask |= EGG_VIRTUAL_SUPER_MASK;
+ else if (keyvals[j] == GDK_Mode_switch)
+ mask |= EGG_VIRTUAL_MODE_SWITCH_MASK;
+
+ ++j;
+ }
+
+ /* Mod1Mask is 1 << 3 for example, i.e. the
+ * fourth modifier, i / keyspermod is the modifier
+ * index
+ */
+ modmap->mapping[i/xmodmap->max_keypermod] |= mask;
+
+ g_free (keyvals);
+ g_free (keys);
+
+ ++i;
+ }
+
+ /* Add in the not-really-virtual fixed entries */
+ modmap->mapping[EGG_MODMAP_ENTRY_SHIFT] |= EGG_VIRTUAL_SHIFT_MASK;
+ modmap->mapping[EGG_MODMAP_ENTRY_CONTROL] |= EGG_VIRTUAL_CONTROL_MASK;
+ modmap->mapping[EGG_MODMAP_ENTRY_LOCK] |= EGG_VIRTUAL_LOCK_MASK;
+ modmap->mapping[EGG_MODMAP_ENTRY_MOD1] |= EGG_VIRTUAL_ALT_MASK;
+ modmap->mapping[EGG_MODMAP_ENTRY_MOD2] |= EGG_VIRTUAL_MOD2_MASK;
+ modmap->mapping[EGG_MODMAP_ENTRY_MOD3] |= EGG_VIRTUAL_MOD3_MASK;
+ modmap->mapping[EGG_MODMAP_ENTRY_MOD4] |= EGG_VIRTUAL_MOD4_MASK;
+ modmap->mapping[EGG_MODMAP_ENTRY_MOD5] |= EGG_VIRTUAL_MOD5_MASK;
+
+ XFreeModifiermap (xmodmap);
+}
+
+const EggModmap*
+egg_keymap_get_modmap (GdkKeymap *keymap)
+{
+ EggModmap *modmap;
+
+ /* This is all a hack, much simpler when we can just
+ * modify GDK directly.
+ */
+
+ modmap = g_object_get_data (G_OBJECT (keymap),
+ "egg-modmap");
+
+ if (modmap == NULL)
+ {
+ modmap = g_new0 (EggModmap, 1);
+
+ /* FIXME modify keymap change events with an event filter
+ * and force a reload if we get one
+ */
+
+ reload_modmap (keymap, modmap);
+
+ g_object_set_data_full (G_OBJECT (keymap),
+ "egg-modmap",
+ modmap,
+ g_free);
+ }
+
+ g_assert (modmap != NULL);
+
+ return modmap;
+}
diff --git a/toolkit/src/sugar/eggaccelerators.h b/toolkit/src/sugar/eggaccelerators.h
new file mode 100644
index 0000000..96d5390
--- /dev/null
+++ b/toolkit/src/sugar/eggaccelerators.h
@@ -0,0 +1,89 @@
+/* eggaccelerators.h
+ * Copyright (C) 2002 Red Hat, Inc.
+ * Developed by Havoc Pennington
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library 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.
+ */
+
+#ifndef __EGG_ACCELERATORS_H__
+#define __EGG_ACCELERATORS_H__
+
+#include <gtk/gtkaccelgroup.h>
+#include <gdk/gdk.h>
+
+G_BEGIN_DECLS
+
+/* Where a value is also in GdkModifierType we coincide,
+ * otherwise we don't overlap.
+ */
+typedef enum
+{
+ EGG_VIRTUAL_SHIFT_MASK = 1 << 0,
+ EGG_VIRTUAL_LOCK_MASK = 1 << 1,
+ EGG_VIRTUAL_CONTROL_MASK = 1 << 2,
+
+ EGG_VIRTUAL_ALT_MASK = 1 << 3, /* fixed as Mod1 */
+
+ EGG_VIRTUAL_MOD2_MASK = 1 << 4,
+ EGG_VIRTUAL_MOD3_MASK = 1 << 5,
+ EGG_VIRTUAL_MOD4_MASK = 1 << 6,
+ EGG_VIRTUAL_MOD5_MASK = 1 << 7,
+
+#if 0
+ GDK_BUTTON1_MASK = 1 << 8,
+ GDK_BUTTON2_MASK = 1 << 9,
+ GDK_BUTTON3_MASK = 1 << 10,
+ GDK_BUTTON4_MASK = 1 << 11,
+ GDK_BUTTON5_MASK = 1 << 12,
+ /* 13, 14 are used by Xkb for the keyboard group */
+#endif
+
+ EGG_VIRTUAL_META_MASK = 1 << 24,
+ EGG_VIRTUAL_SUPER_MASK = 1 << 25,
+ EGG_VIRTUAL_HYPER_MASK = 1 << 26,
+ EGG_VIRTUAL_MODE_SWITCH_MASK = 1 << 27,
+ EGG_VIRTUAL_NUM_LOCK_MASK = 1 << 28,
+ EGG_VIRTUAL_SCROLL_LOCK_MASK = 1 << 29,
+
+ /* Also in GdkModifierType */
+ EGG_VIRTUAL_RELEASE_MASK = 1 << 30,
+
+ /* 28-31 24-27 20-23 16-19 12-15 8-11 4-7 0-3
+ * 7 f 0 0 0 0 f f
+ */
+ EGG_VIRTUAL_MODIFIER_MASK = 0x7f0000ff
+
+} EggVirtualModifierType;
+
+gboolean egg_accelerator_parse_virtual (const gchar *accelerator,
+ guint *accelerator_key,
+ guint *keycode,
+ EggVirtualModifierType *accelerator_mods);
+void egg_keymap_resolve_virtual_modifiers (GdkKeymap *keymap,
+ EggVirtualModifierType virtual_mods,
+ GdkModifierType *concrete_mods);
+void egg_keymap_virtualize_modifiers (GdkKeymap *keymap,
+ GdkModifierType concrete_mods,
+ EggVirtualModifierType *virtual_mods);
+
+gchar* egg_virtual_accelerator_name (guint accelerator_key,
+ guint keycode,
+ EggVirtualModifierType accelerator_mods);
+
+G_END_DECLS
+
+
+#endif /* __EGG_ACCELERATORS_H__ */
diff --git a/toolkit/src/sugar/eggdesktopfile.c b/toolkit/src/sugar/eggdesktopfile.c
new file mode 100644
index 0000000..d095a2f
--- /dev/null
+++ b/toolkit/src/sugar/eggdesktopfile.c
@@ -0,0 +1,1437 @@
+/* eggdesktopfile.c - Freedesktop.Org Desktop Files
+ * Copyright (C) 2007 Novell, Inc.
+ *
+ * Based on gnome-desktop-item.c
+ * Copyright (C) 1999, 2000 Red Hat Inc.
+ * Copyright (C) 2001 George Lebl
+ *
+ * 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; see the file COPYING.LIB. If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place -
+ * Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "eggdesktopfile.h"
+
+#include <string.h>
+#include <unistd.h>
+
+#include <glib/gi18n.h>
+#include <gdk/gdk.h>
+#include <gtk/gtkwindow.h>
+#include <gdk/gdkx.h>
+
+struct EggDesktopFile {
+ GKeyFile *key_file;
+ char *source;
+
+ char *name, *icon;
+ EggDesktopFileType type;
+ char document_code;
+};
+
+/**
+ * egg_desktop_file_new:
+ * @desktop_file_path: path to a Freedesktop-style Desktop file
+ * @error: error pointer
+ *
+ * Creates a new #EggDesktopFile for @desktop_file.
+ *
+ * Return value: the new #EggDesktopFile, or %NULL on error.
+ **/
+EggDesktopFile *
+egg_desktop_file_new (const char *desktop_file_path, GError **error)
+{
+ GKeyFile *key_file;
+
+ key_file = g_key_file_new ();
+ if (!g_key_file_load_from_file (key_file, desktop_file_path, 0, error))
+ {
+ g_key_file_free (key_file);
+ return NULL;
+ }
+
+ return egg_desktop_file_new_from_key_file (key_file, desktop_file_path,
+ error);
+}
+
+/**
+ * egg_desktop_file_new_from_data_dirs:
+ * @desktop_file_path: relative path to a Freedesktop-style Desktop file
+ * @error: error pointer
+ *
+ * Looks for @desktop_file_path in the paths returned from
+ * g_get_user_data_dir() and g_get_system_data_dirs(), and creates
+ * a new #EggDesktopFile from it.
+ *
+ * Return value: the new #EggDesktopFile, or %NULL on error.
+ **/
+EggDesktopFile *
+egg_desktop_file_new_from_data_dirs (const char *desktop_file_path,
+ GError **error)
+{
+ EggDesktopFile *desktop_file;
+ GKeyFile *key_file;
+ char *full_path;
+
+ key_file = g_key_file_new ();
+ if (!g_key_file_load_from_data_dirs (key_file, desktop_file_path,
+ &full_path, 0, error))
+ {
+ g_key_file_free (key_file);
+ return NULL;
+ }
+
+ desktop_file = egg_desktop_file_new_from_key_file (key_file,
+ full_path,
+ error);
+ g_free (full_path);
+ return desktop_file;
+}
+
+/**
+ * egg_desktop_file_new_from_key_file:
+ * @key_file: a #GKeyFile representing a desktop file
+ * @source: the path or URI that @key_file was loaded from, or %NULL
+ * @error: error pointer
+ *
+ * Creates a new #EggDesktopFile for @key_file. Assumes ownership of
+ * @key_file (on success or failure); you should consider @key_file to
+ * be freed after calling this function.
+ *
+ * Return value: the new #EggDesktopFile, or %NULL on error.
+ **/
+EggDesktopFile *
+egg_desktop_file_new_from_key_file (GKeyFile *key_file,
+ const char *source,
+ GError **error)
+{
+ EggDesktopFile *desktop_file;
+ char *version, *type;
+
+ if (!g_key_file_has_group (key_file, EGG_DESKTOP_FILE_GROUP))
+ {
+ g_set_error (error, EGG_DESKTOP_FILE_ERROR,
+ EGG_DESKTOP_FILE_ERROR_INVALID,
+ _("File is not a valid .desktop file"));
+ g_key_file_free (key_file);
+ return NULL;
+ }
+
+ version = g_key_file_get_value (key_file, EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_VERSION,
+ NULL);
+ if (version)
+ {
+ double version_num;
+ char *end;
+
+ version_num = g_ascii_strtod (version, &end);
+ if (*end)
+ {
+ g_warning ("Invalid Version string '%s' in %s",
+ version, source ? source : "(unknown)");
+ }
+ else if (version_num > 1.0)
+ {
+ g_set_error (error, EGG_DESKTOP_FILE_ERROR,
+ EGG_DESKTOP_FILE_ERROR_INVALID,
+ _("Unrecognized desktop file Version '%s'"), version);
+ g_free (version);
+ g_key_file_free (key_file);
+ return NULL;
+ }
+ else
+ g_free (version);
+ }
+
+ desktop_file = g_new0 (EggDesktopFile, 1);
+ desktop_file->key_file = key_file;
+
+ if (g_path_is_absolute (source))
+ desktop_file->source = g_filename_to_uri (source, NULL, NULL);
+ else
+ desktop_file->source = g_strdup (source);
+
+ desktop_file->name = g_key_file_get_string (key_file, EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_NAME, error);
+ if (!desktop_file->name)
+ {
+ egg_desktop_file_free (desktop_file);
+ return NULL;
+ }
+
+ type = g_key_file_get_string (key_file, EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_TYPE, error);
+ if (!type)
+ {
+ egg_desktop_file_free (desktop_file);
+ return NULL;
+ }
+
+ if (!strcmp (type, "Application"))
+ {
+ char *exec, *p;
+
+ desktop_file->type = EGG_DESKTOP_FILE_TYPE_APPLICATION;
+
+ exec = g_key_file_get_string (key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_EXEC,
+ error);
+ if (!exec)
+ {
+ egg_desktop_file_free (desktop_file);
+ return NULL;
+ }
+
+ /* See if it takes paths or URIs or neither */
+ for (p = exec; *p; p++)
+ {
+ if (*p == '%')
+ {
+ if (p[1] == '\0' || strchr ("FfUu", p[1]))
+ {
+ desktop_file->document_code = p[1];
+ break;
+ }
+ p++;
+ }
+ }
+
+ g_free (exec);
+ }
+ else if (!strcmp (type, "Link"))
+ {
+ char *url;
+
+ desktop_file->type = EGG_DESKTOP_FILE_TYPE_LINK;
+
+ url = g_key_file_get_string (key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_URL,
+ error);
+ if (!url)
+ {
+ egg_desktop_file_free (desktop_file);
+ return NULL;
+ }
+ g_free (url);
+ }
+ else if (!strcmp (type, "Directory"))
+ desktop_file->type = EGG_DESKTOP_FILE_TYPE_DIRECTORY;
+ else
+ desktop_file->type = EGG_DESKTOP_FILE_TYPE_UNRECOGNIZED;
+
+ g_free (type);
+
+ /* Check the Icon key */
+ desktop_file->icon = g_key_file_get_string (key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_ICON,
+ NULL);
+ if (desktop_file->icon && !g_path_is_absolute (desktop_file->icon))
+ {
+ char *ext;
+
+ /* Lots of .desktop files still get this wrong */
+ ext = strrchr (desktop_file->icon, '.');
+ if (ext && (!strcmp (ext, ".png") ||
+ !strcmp (ext, ".xpm") ||
+ !strcmp (ext, ".svg")))
+ {
+ g_warning ("Desktop file '%s' has malformed Icon key '%s'"
+ "(should not include extension)",
+ source ? source : "(unknown)",
+ desktop_file->icon);
+ *ext = '\0';
+ }
+ }
+
+ return desktop_file;
+}
+
+/**
+ * egg_desktop_file_free:
+ * @desktop_file: an #EggDesktopFile
+ *
+ * Frees @desktop_file.
+ **/
+void
+egg_desktop_file_free (EggDesktopFile *desktop_file)
+{
+ g_key_file_free (desktop_file->key_file);
+ g_free (desktop_file->source);
+ g_free (desktop_file->name);
+ g_free (desktop_file->icon);
+ g_free (desktop_file);
+}
+
+/**
+ * egg_desktop_file_get_source:
+ * @desktop_file: an #EggDesktopFile
+ *
+ * Gets the URI that @desktop_file was loaded from.
+ *
+ * Return value: @desktop_file's source URI
+ **/
+const char *
+egg_desktop_file_get_source (EggDesktopFile *desktop_file)
+{
+ return desktop_file->source;
+}
+
+/**
+ * egg_desktop_file_get_desktop_file_type:
+ * @desktop_file: an #EggDesktopFile
+ *
+ * Gets the desktop file type of @desktop_file.
+ *
+ * Return value: @desktop_file's type
+ **/
+EggDesktopFileType
+egg_desktop_file_get_desktop_file_type (EggDesktopFile *desktop_file)
+{
+ return desktop_file->type;
+}
+
+/**
+ * egg_desktop_file_get_name:
+ * @desktop_file: an #EggDesktopFile
+ *
+ * Gets the (localized) value of @desktop_file's "Name" key.
+ *
+ * Return value: the application/link name
+ **/
+const char *
+egg_desktop_file_get_name (EggDesktopFile *desktop_file)
+{
+ return desktop_file->name;
+}
+
+/**
+ * egg_desktop_file_get_icon:
+ * @desktop_file: an #EggDesktopFile
+ *
+ * Gets the value of @desktop_file's "Icon" key.
+ *
+ * If the icon string is a full path (that is, if g_path_is_absolute()
+ * returns %TRUE when called on it), it points to a file containing an
+ * unthemed icon. If the icon string is not a full path, it is the
+ * name of a themed icon, which can be looked up with %GtkIconTheme,
+ * or passed directly to a theme-aware widget like %GtkImage or
+ * %GtkCellRendererPixbuf.
+ *
+ * Return value: the icon path or name
+ **/
+const char *
+egg_desktop_file_get_icon (EggDesktopFile *desktop_file)
+{
+ return desktop_file->icon;
+}
+
+gboolean
+egg_desktop_file_has_key (EggDesktopFile *desktop_file,
+ const char *key,
+ GError **error)
+{
+ return g_key_file_has_key (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP, key,
+ error);
+}
+
+char *
+egg_desktop_file_get_string (EggDesktopFile *desktop_file,
+ const char *key,
+ GError **error)
+{
+ return g_key_file_get_string (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP, key,
+ error);
+}
+
+char *
+egg_desktop_file_get_locale_string (EggDesktopFile *desktop_file,
+ const char *key,
+ const char *locale,
+ GError **error)
+{
+ return g_key_file_get_locale_string (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP, key, locale,
+ error);
+}
+
+gboolean
+egg_desktop_file_get_boolean (EggDesktopFile *desktop_file,
+ const char *key,
+ GError **error)
+{
+ return g_key_file_get_boolean (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP, key,
+ error);
+}
+
+double
+egg_desktop_file_get_numeric (EggDesktopFile *desktop_file,
+ const char *key,
+ GError **error)
+{
+#if 0
+ return g_key_file_get_double (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP, key,
+ error);
+#else
+ return 0.0;
+#endif
+}
+
+char **
+egg_desktop_file_get_string_list (EggDesktopFile *desktop_file,
+ const char *key,
+ gsize *length,
+ GError **error)
+{
+ return g_key_file_get_string_list (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP, key, length,
+ error);
+}
+
+char **
+egg_desktop_file_get_locale_string_list (EggDesktopFile *desktop_file,
+ const char *key,
+ const char *locale,
+ gsize *length,
+ GError **error)
+{
+ return g_key_file_get_locale_string_list (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP, key,
+ locale, length,
+ error);
+}
+
+/**
+ * egg_desktop_file_can_launch:
+ * @desktop_file: an #EggDesktopFile
+ * @desktop_environment: the name of the running desktop environment,
+ * or %NULL
+ *
+ * Tests if @desktop_file can/should be launched in the current
+ * environment. If @desktop_environment is non-%NULL, @desktop_file's
+ * "OnlyShowIn" and "NotShowIn" keys are checked to make sure that
+ * this desktop_file is appropriate for the named environment.
+ *
+ * Furthermore, if @desktop_file has type
+ * %EGG_DESKTOP_FILE_TYPE_APPLICATION, its "TryExec" key (if any) is
+ * also checked, to make sure the binary it points to exists.
+ *
+ * egg_desktop_file_can_launch() does NOT check the value of the
+ * "Hidden" key.
+ *
+ * Return value: %TRUE if @desktop_file can be launched
+ **/
+gboolean
+egg_desktop_file_can_launch (EggDesktopFile *desktop_file,
+ const char *desktop_environment)
+{
+ char *try_exec, *found_program;
+ char **only_show_in, **not_show_in;
+ gboolean found;
+ int i;
+
+ if (desktop_file->type != EGG_DESKTOP_FILE_TYPE_APPLICATION &&
+ desktop_file->type != EGG_DESKTOP_FILE_TYPE_LINK)
+ return FALSE;
+
+ if (desktop_environment)
+ {
+ only_show_in = g_key_file_get_string_list (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_ONLY_SHOW_IN,
+ NULL, NULL);
+ if (only_show_in)
+ {
+ for (i = 0, found = FALSE; only_show_in[i] && !found; i++)
+ {
+ if (!strcmp (only_show_in[i], desktop_environment))
+ found = TRUE;
+ }
+
+ g_strfreev (only_show_in);
+
+ if (!found)
+ return FALSE;
+ }
+
+ not_show_in = g_key_file_get_string_list (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_NOT_SHOW_IN,
+ NULL, NULL);
+ if (not_show_in)
+ {
+ for (i = 0, found = FALSE; not_show_in[i] && !found; i++)
+ {
+ if (!strcmp (not_show_in[i], desktop_environment))
+ found = TRUE;
+ }
+
+ g_strfreev (not_show_in);
+
+ if (found)
+ return FALSE;
+ }
+ }
+
+ if (desktop_file->type == EGG_DESKTOP_FILE_TYPE_APPLICATION)
+ {
+ try_exec = g_key_file_get_string (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_TRY_EXEC,
+ NULL);
+ if (try_exec)
+ {
+ found_program = g_find_program_in_path (try_exec);
+ g_free (try_exec);
+
+ if (!found_program)
+ return FALSE;
+ g_free (found_program);
+ }
+ }
+
+ return TRUE;
+}
+
+/**
+ * egg_desktop_file_accepts_documents:
+ * @desktop_file: an #EggDesktopFile
+ *
+ * Tests if @desktop_file represents an application that can accept
+ * documents on the command line.
+ *
+ * Return value: %TRUE or %FALSE
+ **/
+gboolean
+egg_desktop_file_accepts_documents (EggDesktopFile *desktop_file)
+{
+ return desktop_file->document_code != 0;
+}
+
+/**
+ * egg_desktop_file_accepts_multiple:
+ * @desktop_file: an #EggDesktopFile
+ *
+ * Tests if @desktop_file can accept multiple documents at once.
+ *
+ * If this returns %FALSE, you can still pass multiple documents to
+ * egg_desktop_file_launch(), but that will result in multiple copies
+ * of the application being launched. See egg_desktop_file_launch()
+ * for more details.
+ *
+ * Return value: %TRUE or %FALSE
+ **/
+gboolean
+egg_desktop_file_accepts_multiple (EggDesktopFile *desktop_file)
+{
+ return (desktop_file->document_code == 'F' ||
+ desktop_file->document_code == 'U');
+}
+
+/**
+ * egg_desktop_file_accepts_uris:
+ * @desktop_file: an #EggDesktopFile
+ *
+ * Tests if @desktop_file can accept (non-"file:") URIs as documents to
+ * open.
+ *
+ * Return value: %TRUE or %FALSE
+ **/
+gboolean
+egg_desktop_file_accepts_uris (EggDesktopFile *desktop_file)
+{
+ return (desktop_file->document_code == 'U' ||
+ desktop_file->document_code == 'u');
+}
+
+static void
+append_quoted_word (GString *str,
+ const char *s,
+ gboolean in_single_quotes,
+ gboolean in_double_quotes)
+{
+ const char *p;
+
+ if (!in_single_quotes && !in_double_quotes)
+ g_string_append_c (str, '\'');
+ else if (!in_single_quotes && in_double_quotes)
+ g_string_append (str, "\"'");
+
+ if (!strchr (s, '\''))
+ g_string_append (str, s);
+ else
+ {
+ for (p = s; *p != '\0'; p++)
+ {
+ if (*p == '\'')
+ g_string_append (str, "'\\''");
+ else
+ g_string_append_c (str, *p);
+ }
+ }
+
+ if (!in_single_quotes && !in_double_quotes)
+ g_string_append_c (str, '\'');
+ else if (!in_single_quotes && in_double_quotes)
+ g_string_append (str, "'\"");
+}
+
+static void
+do_percent_subst (EggDesktopFile *desktop_file,
+ char code,
+ GString *str,
+ GSList **documents,
+ gboolean in_single_quotes,
+ gboolean in_double_quotes)
+{
+ GSList *d;
+ char *doc;
+
+ switch (code)
+ {
+ case '%':
+ g_string_append_c (str, '%');
+ break;
+
+ case 'F':
+ case 'U':
+ for (d = *documents; d; d = d->next)
+ {
+ doc = d->data;
+ g_string_append (str, " ");
+ append_quoted_word (str, doc, in_single_quotes, in_double_quotes);
+ }
+ *documents = NULL;
+ break;
+
+ case 'f':
+ case 'u':
+ if (*documents)
+ {
+ doc = (*documents)->data;
+ g_string_append (str, " ");
+ append_quoted_word (str, doc, in_single_quotes, in_double_quotes);
+ *documents = (*documents)->next;
+ }
+ break;
+
+ case 'i':
+ if (desktop_file->icon)
+ {
+ g_string_append (str, "--icon ");
+ append_quoted_word (str, desktop_file->icon,
+ in_single_quotes, in_double_quotes);
+ }
+ break;
+
+ case 'c':
+ if (desktop_file->name)
+ {
+ append_quoted_word (str, desktop_file->name,
+ in_single_quotes, in_double_quotes);
+ }
+ break;
+
+ case 'k':
+ if (desktop_file->source)
+ {
+ append_quoted_word (str, desktop_file->source,
+ in_single_quotes, in_double_quotes);
+ }
+ break;
+
+ case 'D':
+ case 'N':
+ case 'd':
+ case 'n':
+ case 'v':
+ case 'm':
+ /* Deprecated; skip */
+ break;
+
+ default:
+ g_warning ("Unrecognized %%-code '%%%c' in Exec", code);
+ break;
+ }
+}
+
+static char *
+parse_exec (EggDesktopFile *desktop_file,
+ GSList **documents,
+ GError **error)
+{
+ char *exec, *p, *command;
+ gboolean escape, single_quot, double_quot;
+ GString *gs;
+
+ exec = g_key_file_get_string (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_EXEC,
+ error);
+ if (!exec)
+ return NULL;
+
+ /* Build the command */
+ gs = g_string_new (NULL);
+ escape = single_quot = double_quot = FALSE;
+
+ for (p = exec; *p != '\0'; p++)
+ {
+ if (escape)
+ {
+ escape = FALSE;
+ g_string_append_c (gs, *p);
+ }
+ else if (*p == '\\')
+ {
+ if (!single_quot)
+ escape = TRUE;
+ g_string_append_c (gs, *p);
+ }
+ else if (*p == '\'')
+ {
+ g_string_append_c (gs, *p);
+ if (!single_quot && !double_quot)
+ single_quot = TRUE;
+ else if (single_quot)
+ single_quot = FALSE;
+ }
+ else if (*p == '"')
+ {
+ g_string_append_c (gs, *p);
+ if (!single_quot && !double_quot)
+ double_quot = TRUE;
+ else if (double_quot)
+ double_quot = FALSE;
+ }
+ else if (*p == '%' && p[1])
+ {
+ do_percent_subst (desktop_file, p[1], gs, documents,
+ single_quot, double_quot);
+ p++;
+ }
+ else
+ g_string_append_c (gs, *p);
+ }
+
+ g_free (exec);
+ command = g_string_free (gs, FALSE);
+
+ /* Prepend "xdg-terminal " if needed (FIXME: use gvfs) */
+ if (g_key_file_has_key (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_TERMINAL,
+ NULL))
+ {
+ GError *terminal_error = NULL;
+ gboolean use_terminal =
+ g_key_file_get_boolean (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_TERMINAL,
+ &terminal_error);
+ if (terminal_error)
+ {
+ g_free (command);
+ g_propagate_error (error, terminal_error);
+ return NULL;
+ }
+
+ if (use_terminal)
+ {
+ gs = g_string_new ("xdg-terminal ");
+ append_quoted_word (gs, command, FALSE, FALSE);
+ g_free (command);
+ command = g_string_free (gs, FALSE);
+ }
+ }
+
+ return command;
+}
+
+static GSList *
+translate_document_list (EggDesktopFile *desktop_file, GSList *documents)
+{
+ gboolean accepts_uris = egg_desktop_file_accepts_uris (desktop_file);
+ GSList *ret, *d;
+
+ for (d = documents, ret = NULL; d; d = d->next)
+ {
+ const char *document = d->data;
+ gboolean is_uri = !g_path_is_absolute (document);
+ char *translated;
+
+ if (accepts_uris)
+ {
+ if (is_uri)
+ translated = g_strdup (document);
+ else
+ translated = g_filename_to_uri (document, NULL, NULL);
+ }
+ else
+ {
+ if (is_uri)
+ translated = g_filename_from_uri (document, NULL, NULL);
+ else
+ translated = g_strdup (document);
+ }
+
+ if (translated)
+ ret = g_slist_prepend (ret, translated);
+ }
+
+ return g_slist_reverse (ret);
+}
+
+static void
+free_document_list (GSList *documents)
+{
+ GSList *d;
+
+ for (d = documents; d; d = d->next)
+ g_free (d->data);
+ g_slist_free (documents);
+}
+
+/**
+ * egg_desktop_file_parse_exec:
+ * @desktop_file: a #EggDesktopFile
+ * @documents: a list of document paths or URIs
+ * @error: error pointer
+ *
+ * Parses @desktop_file's Exec key, inserting @documents into it, and
+ * returns the result.
+ *
+ * If @documents contains non-file: URIs and @desktop_file does not
+ * accept URIs, those URIs will be ignored. Likewise, if @documents
+ * contains more elements than @desktop_file accepts, the extra
+ * documents will be ignored.
+ *
+ * Return value: the parsed Exec string
+ **/
+char *
+egg_desktop_file_parse_exec (EggDesktopFile *desktop_file,
+ GSList *documents,
+ GError **error)
+{
+ GSList *translated, *docs;
+ char *command;
+
+ docs = translated = translate_document_list (desktop_file, documents);
+ command = parse_exec (desktop_file, &docs, error);
+ free_document_list (translated);
+
+ return command;
+}
+
+static gboolean
+parse_link (EggDesktopFile *desktop_file,
+ EggDesktopFile **app_desktop_file,
+ GSList **documents,
+ GError **error)
+{
+ char *url;
+ GKeyFile *key_file;
+
+ url = g_key_file_get_string (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_URL,
+ error);
+ if (!url)
+ return FALSE;
+ *documents = g_slist_prepend (NULL, url);
+
+ /* FIXME: use gvfs */
+ key_file = g_key_file_new ();
+ g_key_file_set_string (key_file, EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_NAME,
+ "xdg-open");
+ g_key_file_set_string (key_file, EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_TYPE,
+ "Application");
+ g_key_file_set_string (key_file, EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_EXEC,
+ "xdg-open %u");
+ *app_desktop_file = egg_desktop_file_new_from_key_file (key_file, NULL, NULL);
+ return TRUE;
+}
+
+#ifdef HAVE_GDK_X11_DISPLAY_BROADCAST_STARTUP_MESSAGE
+static char *
+start_startup_notification (GdkDisplay *display,
+ EggDesktopFile *desktop_file,
+ const char *argv0,
+ int screen,
+ int workspace,
+ guint32 launch_time)
+{
+ static int sequence = 0;
+ char *startup_id;
+ char *description, *wmclass;
+ char *screen_str, *workspace_str;
+
+ if (g_key_file_has_key (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_STARTUP_NOTIFY,
+ NULL))
+ {
+ if (!g_key_file_get_boolean (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_STARTUP_NOTIFY,
+ NULL))
+ return NULL;
+ wmclass = NULL;
+ }
+ else
+ {
+ wmclass = g_key_file_get_string (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_STARTUP_WM_CLASS,
+ NULL);
+ if (!wmclass)
+ return NULL;
+ }
+
+ if (launch_time == (guint32)-1)
+ launch_time = gdk_x11_display_get_user_time (display);
+ startup_id = g_strdup_printf ("%s-%lu-%s-%s-%d_TIME%lu",
+ g_get_prgname (),
+ (unsigned long)getpid (),
+ g_get_host_name (),
+ argv0,
+ sequence++,
+ (unsigned long)launch_time);
+
+ description = g_strdup_printf (_("Starting %s"), desktop_file->name);
+ screen_str = g_strdup_printf ("%d", screen);
+ workspace_str = workspace == -1 ? NULL : g_strdup_printf ("%d", workspace);
+
+ gdk_x11_display_broadcast_startup_message (display, "new",
+ "ID", startup_id,
+ "NAME", desktop_file->name,
+ "SCREEN", screen_str,
+ "BIN", argv0,
+ "ICON", desktop_file->icon,
+ "DESKTOP", workspace_str,
+ "DESCRIPTION", description,
+ "WMCLASS", wmclass,
+ NULL);
+
+ g_free (description);
+ g_free (wmclass);
+ g_free (screen_str);
+ g_free (workspace_str);
+
+ return startup_id;
+}
+
+static void
+end_startup_notification (GdkDisplay *display,
+ const char *startup_id)
+{
+ gdk_x11_display_broadcast_startup_message (display, "remove",
+ "ID", startup_id,
+ NULL);
+}
+
+#define EGG_DESKTOP_FILE_SN_TIMEOUT_LENGTH 30 /* seconds */
+
+typedef struct {
+ GdkDisplay *display;
+ char *startup_id;
+} StartupNotificationData;
+
+static gboolean
+startup_notification_timeout (gpointer data)
+{
+ StartupNotificationData *sn_data = data;
+
+ end_startup_notification (sn_data->display, sn_data->startup_id);
+ g_object_unref (sn_data->display);
+ g_free (sn_data->startup_id);
+ g_free (sn_data);
+
+ return FALSE;
+}
+
+static void
+set_startup_notification_timeout (GdkDisplay *display,
+ const char *startup_id)
+{
+ StartupNotificationData *sn_data;
+
+ sn_data = g_new (StartupNotificationData, 1);
+ sn_data->display = g_object_ref (display);
+ sn_data->startup_id = g_strdup (startup_id);
+
+ g_timeout_add_seconds (EGG_DESKTOP_FILE_SN_TIMEOUT_LENGTH,
+ startup_notification_timeout, sn_data);
+}
+#endif /* HAVE_GDK_X11_DISPLAY_BROADCAST_STARTUP_MESSAGE */
+
+extern char **environ;
+
+static GPtrArray *
+array_putenv (GPtrArray *env, char *variable)
+{
+ int i, keylen;
+
+ if (!env)
+ {
+ env = g_ptr_array_new ();
+
+ for (i = 0; environ[i]; i++)
+ g_ptr_array_add (env, g_strdup (environ[i]));
+ }
+
+ keylen = strcspn (variable, "=");
+
+ /* Remove old value of key */
+ for (i = 0; i < env->len; i++)
+ {
+ char *envvar = env->pdata[i];
+
+ if (!strncmp (envvar, variable, keylen) && envvar[keylen] == '=')
+ {
+ g_free (envvar);
+ g_ptr_array_remove_index_fast (env, i);
+ break;
+ }
+ }
+
+ /* Add new value */
+ g_ptr_array_add (env, g_strdup (variable));
+
+ return env;
+}
+
+static gboolean
+egg_desktop_file_launchv (EggDesktopFile *desktop_file,
+ GSList *documents, va_list args,
+ GError **error)
+{
+ EggDesktopFileLaunchOption option;
+ GSList *translated_documents = NULL, *docs;
+ char *command, **argv;
+ int argc, i, screen_num;
+ gboolean success, current_success;
+ GdkDisplay *display;
+ char *startup_id;
+
+ GPtrArray *env = NULL;
+ char **variables = NULL;
+ GdkScreen *screen = NULL;
+ int workspace = -1;
+ const char *directory = NULL;
+ guint32 launch_time = (guint32)-1;
+ GSpawnFlags flags = G_SPAWN_SEARCH_PATH;
+ GSpawnChildSetupFunc setup_func = NULL;
+ gpointer setup_data = NULL;
+
+ GPid *ret_pid = NULL;
+ int *ret_stdin = NULL, *ret_stdout = NULL, *ret_stderr = NULL;
+ char **ret_startup_id = NULL;
+
+ if (documents && desktop_file->document_code == 0)
+ {
+ g_set_error (error, EGG_DESKTOP_FILE_ERROR,
+ EGG_DESKTOP_FILE_ERROR_NOT_LAUNCHABLE,
+ _("Application does not accept documents on command line"));
+ return FALSE;
+ }
+
+ /* Read the options: technically it's incorrect for the caller to
+ * NULL-terminate the list of options (rather than 0-terminating
+ * it), but NULL-terminating lets us use G_GNUC_NULL_TERMINATED,
+ * it's more consistent with other glib/gtk methods, and it will
+ * work as long as sizeof (int) <= sizeof (NULL), and NULL is
+ * represented as 0. (Which is true everywhere we care about.)
+ */
+ while ((option = va_arg (args, EggDesktopFileLaunchOption)))
+ {
+ switch (option)
+ {
+ case EGG_DESKTOP_FILE_LAUNCH_CLEARENV:
+ if (env)
+ g_ptr_array_free (env, TRUE);
+ env = g_ptr_array_new ();
+ break;
+ case EGG_DESKTOP_FILE_LAUNCH_PUTENV:
+ variables = va_arg (args, char **);
+ for (i = 0; variables[i]; i++)
+ env = array_putenv (env, variables[i]);
+ break;
+
+ case EGG_DESKTOP_FILE_LAUNCH_SCREEN:
+ screen = va_arg (args, GdkScreen *);
+ break;
+ case EGG_DESKTOP_FILE_LAUNCH_WORKSPACE:
+ workspace = va_arg (args, int);
+ break;
+
+ case EGG_DESKTOP_FILE_LAUNCH_DIRECTORY:
+ directory = va_arg (args, const char *);
+ break;
+ case EGG_DESKTOP_FILE_LAUNCH_TIME:
+ launch_time = va_arg (args, guint32);
+ break;
+ case EGG_DESKTOP_FILE_LAUNCH_FLAGS:
+ flags |= va_arg (args, GSpawnFlags);
+ /* Make sure they didn't set any flags that don't make sense. */
+ flags &= ~G_SPAWN_FILE_AND_ARGV_ZERO;
+ break;
+ case EGG_DESKTOP_FILE_LAUNCH_SETUP_FUNC:
+ setup_func = va_arg (args, GSpawnChildSetupFunc);
+ setup_data = va_arg (args, gpointer);
+ break;
+
+ case EGG_DESKTOP_FILE_LAUNCH_RETURN_PID:
+ ret_pid = va_arg (args, GPid *);
+ break;
+ case EGG_DESKTOP_FILE_LAUNCH_RETURN_STDIN_PIPE:
+ ret_stdin = va_arg (args, int *);
+ break;
+ case EGG_DESKTOP_FILE_LAUNCH_RETURN_STDOUT_PIPE:
+ ret_stdout = va_arg (args, int *);
+ break;
+ case EGG_DESKTOP_FILE_LAUNCH_RETURN_STDERR_PIPE:
+ ret_stderr = va_arg (args, int *);
+ break;
+ case EGG_DESKTOP_FILE_LAUNCH_RETURN_STARTUP_ID:
+ ret_startup_id = va_arg (args, char **);
+ break;
+
+ default:
+ g_set_error (error, EGG_DESKTOP_FILE_ERROR,
+ EGG_DESKTOP_FILE_ERROR_UNRECOGNIZED_OPTION,
+ _("Unrecognized launch option: %d"),
+ GPOINTER_TO_INT (option));
+ success = FALSE;
+ goto out;
+ }
+ }
+
+ if (screen)
+ {
+ char *display_name = gdk_screen_make_display_name (screen);
+ char *display_env = g_strdup_printf ("DISPLAY=%s", display_name);
+ env = array_putenv (env, display_env);
+ g_free (display_name);
+ g_free (display_env);
+
+ display = gdk_screen_get_display (screen);
+ }
+ else
+ {
+ display = gdk_display_get_default ();
+ screen = gdk_display_get_default_screen (display);
+ }
+ screen_num = gdk_screen_get_number (screen);
+
+ translated_documents = translate_document_list (desktop_file, documents);
+ docs = translated_documents;
+
+ success = FALSE;
+
+ do
+ {
+ command = parse_exec (desktop_file, &docs, error);
+ if (!command)
+ goto out;
+
+ if (!g_shell_parse_argv (command, &argc, &argv, error))
+ {
+ g_free (command);
+ goto out;
+ }
+ g_free (command);
+
+#ifdef HAVE_GDK_X11_DISPLAY_BROADCAST_STARTUP_MESSAGE
+ startup_id = start_startup_notification (display, desktop_file,
+ argv[0], screen_num,
+ workspace, launch_time);
+ if (startup_id)
+ {
+ char *startup_id_env = g_strdup_printf ("DESKTOP_STARTUP_ID=%s",
+ startup_id);
+ env = array_putenv (env, startup_id_env);
+ g_free (startup_id_env);
+ }
+#else
+ startup_id = NULL;
+#endif /* HAVE_GDK_X11_DISPLAY_BROADCAST_STARTUP_MESSAGE */
+
+ if (env != NULL)
+ {
+ /* Add NULL item in the end of array */
+ g_ptr_array_add (env, NULL);
+ }
+
+ current_success =
+ g_spawn_async_with_pipes (directory,
+ argv,
+ env ? (char **)(env->pdata) : NULL,
+ flags,
+ setup_func, setup_data,
+ ret_pid,
+ ret_stdin, ret_stdout, ret_stderr,
+ error);
+ g_strfreev (argv);
+
+ if (startup_id)
+ {
+#ifdef HAVE_GDK_X11_DISPLAY_BROADCAST_STARTUP_MESSAGE
+ if (current_success)
+ {
+ set_startup_notification_timeout (display, startup_id);
+
+ if (ret_startup_id)
+ *ret_startup_id = startup_id;
+ else
+ g_free (startup_id);
+ }
+ else
+#endif /* HAVE_GDK_X11_DISPLAY_BROADCAST_STARTUP_MESSAGE */
+ g_free (startup_id);
+ }
+ else if (ret_startup_id)
+ *ret_startup_id = NULL;
+
+ if (current_success)
+ {
+ /* If we successfully launch any instances of the app, make
+ * sure we return TRUE and don't set @error.
+ */
+ success = TRUE;
+ error = NULL;
+
+ /* Also, only set the output params on the first one */
+ ret_pid = NULL;
+ ret_stdin = ret_stdout = ret_stderr = NULL;
+ ret_startup_id = NULL;
+ }
+ }
+ while (docs && current_success);
+
+ out:
+ if (env)
+ {
+ g_ptr_array_free (env, TRUE);
+ }
+ free_document_list (translated_documents);
+
+ return success;
+}
+
+/**
+ * egg_desktop_file_launch:
+ * @desktop_file: an #EggDesktopFile
+ * @documents: a list of URIs or paths to documents to open
+ * @error: error pointer
+ * @...: additional options
+ *
+ * Launches @desktop_file with the given arguments. Additional options
+ * can be specified as follows:
+ *
+ * %EGG_DESKTOP_FILE_LAUNCH_CLEARENV: (no arguments)
+ * clears the environment in the child process
+ * %EGG_DESKTOP_FILE_LAUNCH_PUTENV: (char **variables)
+ * adds the NAME=VALUE strings in the given %NULL-terminated
+ * array to the child process's environment
+ * %EGG_DESKTOP_FILE_LAUNCH_SCREEN: (GdkScreen *screen)
+ * causes the application to be launched on the given screen
+ * %EGG_DESKTOP_FILE_LAUNCH_WORKSPACE: (int workspace)
+ * causes the application to be launched on the given workspace
+ * %EGG_DESKTOP_FILE_LAUNCH_DIRECTORY: (char *dir)
+ * causes the application to be launched in the given directory
+ * %EGG_DESKTOP_FILE_LAUNCH_TIME: (guint32 launch_time)
+ * sets the "launch time" for the application. If the user
+ * interacts with another window after @launch_time but before
+ * the launched application creates its first window, the window
+ * manager may choose to not give focus to the new application.
+ * Passing 0 for @launch_time will explicitly request that the
+ * application not receive focus.
+ * %EGG_DESKTOP_FILE_LAUNCH_FLAGS (GSpawnFlags flags)
+ * Sets additional #GSpawnFlags to use. See g_spawn_async() for
+ * more details.
+ * %EGG_DESKTOP_FILE_LAUNCH_SETUP_FUNC (GSpawnChildSetupFunc, gpointer)
+ * Sets the child setup callback and the data to pass to it.
+ * (See g_spawn_async() for more details.)
+ *
+ * %EGG_DESKTOP_FILE_LAUNCH_RETURN_PID (GPid **pid)
+ * On a successful launch, sets *@pid to the PID of the launched
+ * application.
+ * %EGG_DESKTOP_FILE_LAUNCH_RETURN_STARTUP_ID (char **startup_id)
+ * On a successful launch, sets *@startup_id to the Startup
+ * Notification "startup id" of the launched application.
+ * %EGG_DESKTOP_FILE_LAUNCH_RETURN_STDIN_PIPE (int *fd)
+ * On a successful launch, sets *@fd to the file descriptor of
+ * a pipe connected to the application's stdin.
+ * %EGG_DESKTOP_FILE_LAUNCH_RETURN_STDOUT_PIPE (int *fd)
+ * On a successful launch, sets *@fd to the file descriptor of
+ * a pipe connected to the application's stdout.
+ * %EGG_DESKTOP_FILE_LAUNCH_RETURN_STDERR_PIPE (int *fd)
+ * On a successful launch, sets *@fd to the file descriptor of
+ * a pipe connected to the application's stderr.
+ *
+ * The options should be terminated with a single %NULL.
+ *
+ * If @documents contains multiple documents, but
+ * egg_desktop_file_accepts_multiple() returns %FALSE for
+ * @desktop_file, then egg_desktop_file_launch() will actually launch
+ * multiple instances of the application. In that case, the return
+ * value (as well as any values passed via
+ * %EGG_DESKTOP_FILE_LAUNCH_RETURN_PID, etc) will only reflect the
+ * first instance of the application that was launched (but the
+ * %EGG_DESKTOP_FILE_LAUNCH_SETUP_FUNC will be called for each
+ * instance).
+ *
+ * Return value: %TRUE if the application was successfully launched.
+ **/
+gboolean
+egg_desktop_file_launch (EggDesktopFile *desktop_file,
+ GSList *documents, GError **error,
+ ...)
+{
+ va_list args;
+ gboolean success;
+ EggDesktopFile *app_desktop_file;
+
+ switch (desktop_file->type)
+ {
+ case EGG_DESKTOP_FILE_TYPE_APPLICATION:
+ va_start (args, error);
+ success = egg_desktop_file_launchv (desktop_file, documents,
+ args, error);
+ va_end (args);
+ break;
+
+ case EGG_DESKTOP_FILE_TYPE_LINK:
+ if (documents)
+ {
+ g_set_error (error, EGG_DESKTOP_FILE_ERROR,
+ EGG_DESKTOP_FILE_ERROR_NOT_LAUNCHABLE,
+ _("Can't pass document URIs to a 'Type=Link' desktop entry"));
+ return FALSE;
+ }
+
+ if (!parse_link (desktop_file, &app_desktop_file, &documents, error))
+ return FALSE;
+
+ va_start (args, error);
+ success = egg_desktop_file_launchv (app_desktop_file, documents,
+ args, error);
+ va_end (args);
+
+ egg_desktop_file_free (app_desktop_file);
+ free_document_list (documents);
+ break;
+
+ default:
+ g_set_error (error, EGG_DESKTOP_FILE_ERROR,
+ EGG_DESKTOP_FILE_ERROR_NOT_LAUNCHABLE,
+ _("Not a launchable item"));
+ success = FALSE;
+ break;
+ }
+
+ return success;
+}
+
+
+GQuark
+egg_desktop_file_error_quark (void)
+{
+ return g_quark_from_static_string ("egg-desktop_file-error-quark");
+}
+
+
+G_LOCK_DEFINE_STATIC (egg_desktop_file);
+static EggDesktopFile *egg_desktop_file;
+
+/**
+ * egg_set_desktop_file:
+ * @desktop_file_path: path to the application's desktop file
+ *
+ * Creates an #EggDesktopFile for the application from the data at
+ * @desktop_file_path. This will also call g_set_application_name()
+ * with the localized application name from the desktop file, and
+ * gtk_window_set_default_icon_name() or
+ * gtk_window_set_default_icon_from_file() with the application's
+ * icon. Other code may use additional information from the desktop
+ * file.
+ *
+ * Note that for thread safety reasons, this function can only
+ * be called once.
+ **/
+void
+egg_set_desktop_file (const char *desktop_file_path)
+{
+ GError *error = NULL;
+
+ G_LOCK (egg_desktop_file);
+ if (egg_desktop_file)
+ egg_desktop_file_free (egg_desktop_file);
+
+ egg_desktop_file = egg_desktop_file_new (desktop_file_path, &error);
+ if (error)
+ {
+ g_warning ("Could not load desktop file '%s': %s",
+ desktop_file_path, error->message);
+ g_error_free (error);
+ }
+
+ /* Set localized application name and default window icon */
+ if (egg_desktop_file->name)
+ g_set_application_name (egg_desktop_file->name);
+ if (egg_desktop_file->icon)
+ {
+ if (g_path_is_absolute (egg_desktop_file->icon))
+ gtk_window_set_default_icon_from_file (egg_desktop_file->icon, NULL);
+ else
+ gtk_window_set_default_icon_name (egg_desktop_file->icon);
+ }
+
+ G_UNLOCK (egg_desktop_file);
+}
+
+/**
+ * egg_get_desktop_file:
+ *
+ * Gets the application's #EggDesktopFile, as set by
+ * egg_set_desktop_file().
+ *
+ * Return value: the #EggDesktopFile, or %NULL if it hasn't been set.
+ **/
+EggDesktopFile *
+egg_get_desktop_file (void)
+{
+ EggDesktopFile *retval;
+
+ G_LOCK (egg_desktop_file);
+ retval = egg_desktop_file;
+ G_UNLOCK (egg_desktop_file);
+
+ return retval;
+}
diff --git a/toolkit/src/sugar/eggdesktopfile.h b/toolkit/src/sugar/eggdesktopfile.h
new file mode 100644
index 0000000..270aec8
--- /dev/null
+++ b/toolkit/src/sugar/eggdesktopfile.h
@@ -0,0 +1,156 @@
+/* eggdesktopfile.h - Freedesktop.Org Desktop Files
+ * Copyright (C) 2007 Novell, Inc.
+ *
+ * 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; see the file COPYING.LIB. If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place -
+ * Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __EGG_DESKTOP_FILE_H__
+#define __EGG_DESKTOP_FILE_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef struct EggDesktopFile EggDesktopFile;
+
+typedef enum {
+ EGG_DESKTOP_FILE_TYPE_UNRECOGNIZED,
+
+ EGG_DESKTOP_FILE_TYPE_APPLICATION,
+ EGG_DESKTOP_FILE_TYPE_LINK,
+ EGG_DESKTOP_FILE_TYPE_DIRECTORY,
+} EggDesktopFileType;
+
+EggDesktopFile *egg_desktop_file_new (const char *desktop_file_path,
+ GError **error);
+
+EggDesktopFile *egg_desktop_file_new_from_data_dirs (const char *desktop_file_path,
+ GError **error);
+EggDesktopFile *egg_desktop_file_new_from_key_file (GKeyFile *key_file,
+ const char *source,
+ GError **error);
+
+void egg_desktop_file_free (EggDesktopFile *desktop_file);
+
+const char *egg_desktop_file_get_source (EggDesktopFile *desktop_file) G_GNUC_PURE;
+
+EggDesktopFileType egg_desktop_file_get_desktop_file_type (EggDesktopFile *desktop_file) G_GNUC_PURE;
+
+const char *egg_desktop_file_get_name (EggDesktopFile *desktop_file) G_GNUC_PURE;
+const char *egg_desktop_file_get_icon (EggDesktopFile *desktop_file) G_GNUC_PURE;
+
+gboolean egg_desktop_file_can_launch (EggDesktopFile *desktop_file,
+ const char *desktop_environment);
+
+gboolean egg_desktop_file_accepts_documents (EggDesktopFile *desktop_file);
+gboolean egg_desktop_file_accepts_multiple (EggDesktopFile *desktop_file);
+gboolean egg_desktop_file_accepts_uris (EggDesktopFile *desktop_file);
+
+char *egg_desktop_file_parse_exec (EggDesktopFile *desktop_file,
+ GSList *documents,
+ GError **error);
+
+gboolean egg_desktop_file_launch (EggDesktopFile *desktop_file,
+ GSList *documents,
+ GError **error,
+ ...) G_GNUC_NULL_TERMINATED;
+
+typedef enum {
+ EGG_DESKTOP_FILE_LAUNCH_CLEARENV = 1,
+ EGG_DESKTOP_FILE_LAUNCH_PUTENV,
+ EGG_DESKTOP_FILE_LAUNCH_SCREEN,
+ EGG_DESKTOP_FILE_LAUNCH_WORKSPACE,
+ EGG_DESKTOP_FILE_LAUNCH_DIRECTORY,
+ EGG_DESKTOP_FILE_LAUNCH_TIME,
+ EGG_DESKTOP_FILE_LAUNCH_FLAGS,
+ EGG_DESKTOP_FILE_LAUNCH_SETUP_FUNC,
+ EGG_DESKTOP_FILE_LAUNCH_RETURN_PID,
+ EGG_DESKTOP_FILE_LAUNCH_RETURN_STDIN_PIPE,
+ EGG_DESKTOP_FILE_LAUNCH_RETURN_STDOUT_PIPE,
+ EGG_DESKTOP_FILE_LAUNCH_RETURN_STDERR_PIPE,
+ EGG_DESKTOP_FILE_LAUNCH_RETURN_STARTUP_ID
+} EggDesktopFileLaunchOption;
+
+/* Standard Keys */
+#define EGG_DESKTOP_FILE_GROUP "Desktop Entry"
+
+#define EGG_DESKTOP_FILE_KEY_TYPE "Type"
+#define EGG_DESKTOP_FILE_KEY_VERSION "Version"
+#define EGG_DESKTOP_FILE_KEY_NAME "Name"
+#define EGG_DESKTOP_FILE_KEY_GENERIC_NAME "GenericName"
+#define EGG_DESKTOP_FILE_KEY_NO_DISPLAY "NoDisplay"
+#define EGG_DESKTOP_FILE_KEY_COMMENT "Comment"
+#define EGG_DESKTOP_FILE_KEY_ICON "Icon"
+#define EGG_DESKTOP_FILE_KEY_HIDDEN "Hidden"
+#define EGG_DESKTOP_FILE_KEY_ONLY_SHOW_IN "OnlyShowIn"
+#define EGG_DESKTOP_FILE_KEY_NOT_SHOW_IN "NotShowIn"
+#define EGG_DESKTOP_FILE_KEY_TRY_EXEC "TryExec"
+#define EGG_DESKTOP_FILE_KEY_EXEC "Exec"
+#define EGG_DESKTOP_FILE_KEY_PATH "Path"
+#define EGG_DESKTOP_FILE_KEY_TERMINAL "Terminal"
+#define EGG_DESKTOP_FILE_KEY_MIME_TYPE "MimeType"
+#define EGG_DESKTOP_FILE_KEY_CATEGORIES "Categories"
+#define EGG_DESKTOP_FILE_KEY_STARTUP_NOTIFY "StartupNotify"
+#define EGG_DESKTOP_FILE_KEY_STARTUP_WM_CLASS "StartupWMClass"
+#define EGG_DESKTOP_FILE_KEY_URL "URL"
+
+/* Accessors */
+gboolean egg_desktop_file_has_key (EggDesktopFile *desktop_file,
+ const char *key,
+ GError **error);
+char *egg_desktop_file_get_string (EggDesktopFile *desktop_file,
+ const char *key,
+ GError **error) G_GNUC_MALLOC;
+char *egg_desktop_file_get_locale_string (EggDesktopFile *desktop_file,
+ const char *key,
+ const char *locale,
+ GError **error) G_GNUC_MALLOC;
+gboolean egg_desktop_file_get_boolean (EggDesktopFile *desktop_file,
+ const char *key,
+ GError **error);
+double egg_desktop_file_get_numeric (EggDesktopFile *desktop_file,
+ const char *key,
+ GError **error);
+char **egg_desktop_file_get_string_list (EggDesktopFile *desktop_file,
+ const char *key,
+ gsize *length,
+ GError **error) G_GNUC_MALLOC;
+char **egg_desktop_file_get_locale_string_list (EggDesktopFile *desktop_file,
+ const char *key,
+ const char *locale,
+ gsize *length,
+ GError **error) G_GNUC_MALLOC;
+
+
+/* Errors */
+#define EGG_DESKTOP_FILE_ERROR egg_desktop_file_error_quark()
+
+GQuark egg_desktop_file_error_quark (void);
+
+typedef enum {
+ EGG_DESKTOP_FILE_ERROR_INVALID,
+ EGG_DESKTOP_FILE_ERROR_NOT_LAUNCHABLE,
+ EGG_DESKTOP_FILE_ERROR_UNRECOGNIZED_OPTION,
+} EggDesktopFileError;
+
+/* Global application desktop file */
+void egg_set_desktop_file (const char *desktop_file_path);
+EggDesktopFile *egg_get_desktop_file (void);
+
+
+G_END_DECLS
+
+#endif /* __EGG_DESKTOP_FILE_H__ */
diff --git a/toolkit/src/sugar/eggsmclient-private.h b/toolkit/src/sugar/eggsmclient-private.h
new file mode 100644
index 0000000..d2958c9
--- /dev/null
+++ b/toolkit/src/sugar/eggsmclient-private.h
@@ -0,0 +1,56 @@
+/* eggsmclient-private.h
+ * Copyright (C) 2007 Novell, Inc.
+ *
+ * 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.
+ */
+
+#ifndef __EGG_SM_CLIENT_PRIVATE_H__
+#define __EGG_SM_CLIENT_PRIVATE_H__
+
+#include <gdkconfig.h>
+#include "eggsmclient.h"
+
+G_BEGIN_DECLS
+
+#define EGG_SM_CLIENT_BACKEND_XSMP
+
+GKeyFile *egg_sm_client_save_state (EggSMClient *client);
+void egg_sm_client_quit_requested (EggSMClient *client);
+void egg_sm_client_quit_cancelled (EggSMClient *client);
+void egg_sm_client_quit (EggSMClient *client);
+
+#if defined (GDK_WINDOWING_X11)
+# ifdef EGG_SM_CLIENT_BACKEND_XSMP
+#define EGG_TYPE_SM_CLIENT_XSMP (egg_sm_client_xsmp_get_type ())
+GType egg_sm_client_xsmp_get_type (void);
+EggSMClient *egg_sm_client_xsmp_new (void);
+# endif
+# ifdef EGG_SM_CLIENT_BACKEND_DBUS
+GType egg_sm_client_dbus_get_type (void);
+EggSMClient *egg_sm_client_dbus_new (void);
+# endif
+#elif defined (GDK_WINDOWING_WIN32)
+GType egg_sm_client_win32_get_type (void);
+EggSMClient *egg_sm_client_win32_new (void);
+#elif defined (GDK_WINDOWING_QUARTZ)
+GType egg_sm_client_osx_get_type (void);
+EggSMClient *egg_sm_client_osx_new (void);
+#endif
+
+G_END_DECLS
+
+
+#endif /* __EGG_SM_CLIENT_PRIVATE_H__ */
diff --git a/toolkit/src/sugar/eggsmclient-xsmp.c b/toolkit/src/sugar/eggsmclient-xsmp.c
new file mode 100644
index 0000000..2a9532a
--- /dev/null
+++ b/toolkit/src/sugar/eggsmclient-xsmp.c
@@ -0,0 +1,1359 @@
+/*
+ * Copyright (C) 2007 Novell, Inc.
+ *
+ * Inspired by various other pieces of code including GsmClient (C)
+ * 2001 Havoc Pennington, GnomeClient (C) 1998 Carsten Schaar, and twm
+ * session code (C) 1998 The Open Group.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "eggsmclient.h"
+#include "eggsmclient-private.h"
+
+#include "eggdesktopfile.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <X11/SM/SMlib.h>
+
+#include <gdk/gdk.h>
+
+#define EGG_TYPE_SM_CLIENT_XSMP (egg_sm_client_xsmp_get_type ())
+#define EGG_SM_CLIENT_XSMP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_SM_CLIENT_XSMP, EggSMClientXSMP))
+#define EGG_SM_CLIENT_XSMP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_SM_CLIENT_XSMP, EggSMClientXSMPClass))
+#define EGG_IS_SM_CLIENT_XSMP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_SM_CLIENT_XSMP))
+#define EGG_IS_SM_CLIENT_XSMP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_SM_CLIENT_XSMP))
+#define EGG_SM_CLIENT_XSMP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_SM_CLIENT_XSMP, EggSMClientXSMPClass))
+
+typedef struct _EggSMClientXSMP EggSMClientXSMP;
+typedef struct _EggSMClientXSMPClass EggSMClientXSMPClass;
+
+/* These mostly correspond to the similarly-named states in section
+ * 9.1 of the XSMP spec. Some of the states there aren't represented
+ * here, because we don't need them. SHUTDOWN_CANCELLED is slightly
+ * different from the spec; we use it when the client is IDLE after a
+ * ShutdownCancelled message, but the application is still interacting
+ * and doesn't know the shutdown has been cancelled yet.
+ */
+typedef enum
+{
+ XSMP_STATE_START,
+ XSMP_STATE_IDLE,
+ XSMP_STATE_SAVE_YOURSELF,
+ XSMP_STATE_INTERACT_REQUEST,
+ XSMP_STATE_INTERACT,
+ XSMP_STATE_SAVE_YOURSELF_DONE,
+ XSMP_STATE_SHUTDOWN_CANCELLED,
+ XSMP_STATE_CONNECTION_CLOSED,
+} EggSMClientXSMPState;
+
+static const char *state_names[] = {
+ "start",
+ "idle",
+ "save-yourself",
+ "interact-request",
+ "interact",
+ "save-yourself-done",
+ "shutdown-cancelled",
+ "connection-closed"
+};
+
+#define EGG_SM_CLIENT_XSMP_STATE(xsmp) (state_names[(xsmp)->state])
+
+struct _EggSMClientXSMP
+{
+ EggSMClient parent;
+
+ SmcConn connection;
+ char *client_id;
+
+ EggSMClientXSMPState state;
+ char **restart_command;
+ gboolean set_restart_command;
+ int restart_style;
+
+ guint idle;
+
+ /* Current SaveYourself state */
+ guint expecting_initial_save_yourself : 1;
+ guint need_save_state : 1;
+ guint need_quit_requested : 1;
+ guint interact_errors : 1;
+ guint shutting_down : 1;
+
+ /* Todo list */
+ guint waiting_to_emit_quit : 1;
+ guint waiting_to_emit_quit_cancelled : 1;
+ guint waiting_to_save_myself : 1;
+
+};
+
+struct _EggSMClientXSMPClass
+{
+ EggSMClientClass parent_class;
+
+};
+
+static void sm_client_xsmp_startup (EggSMClient *client,
+ const char *client_id);
+static void sm_client_xsmp_set_restart_command (EggSMClient *client,
+ int argc,
+ const char **argv);
+static void sm_client_xsmp_will_quit (EggSMClient *client,
+ gboolean will_quit);
+static gboolean sm_client_xsmp_end_session (EggSMClient *client,
+ EggSMClientEndStyle style,
+ gboolean request_confirmation);
+
+static void xsmp_save_yourself (SmcConn smc_conn,
+ SmPointer client_data,
+ int save_style,
+ Bool shutdown,
+ int interact_style,
+ Bool fast);
+static void xsmp_die (SmcConn smc_conn,
+ SmPointer client_data);
+static void xsmp_save_complete (SmcConn smc_conn,
+ SmPointer client_data);
+static void xsmp_shutdown_cancelled (SmcConn smc_conn,
+ SmPointer client_data);
+static void xsmp_interact (SmcConn smc_conn,
+ SmPointer client_data);
+
+static SmProp *array_prop (const char *name,
+ ...);
+static SmProp *ptrarray_prop (const char *name,
+ GPtrArray *values);
+static SmProp *string_prop (const char *name,
+ const char *value);
+static SmProp *card8_prop (const char *name,
+ unsigned char value);
+
+static void set_properties (EggSMClientXSMP *xsmp, ...);
+static void delete_properties (EggSMClientXSMP *xsmp, ...);
+
+static GPtrArray *generate_command (char **restart_command,
+ const char *client_id,
+ const char *state_file);
+
+static void save_state (EggSMClientXSMP *xsmp);
+static void do_save_yourself (EggSMClientXSMP *xsmp);
+static void update_pending_events (EggSMClientXSMP *xsmp);
+
+static void ice_init (void);
+static gboolean process_ice_messages (IceConn ice_conn);
+static void smc_error_handler (SmcConn smc_conn,
+ Bool swap,
+ int offending_minor_opcode,
+ unsigned long offending_sequence,
+ int error_class,
+ int severity,
+ SmPointer values);
+
+G_DEFINE_TYPE (EggSMClientXSMP, egg_sm_client_xsmp, EGG_TYPE_SM_CLIENT)
+
+static void
+egg_sm_client_xsmp_init (EggSMClientXSMP *xsmp)
+{
+ xsmp->state = XSMP_STATE_CONNECTION_CLOSED;
+ xsmp->connection = NULL;
+ xsmp->restart_style = SmRestartIfRunning;
+ xsmp->client_id = NULL;
+}
+
+static void
+egg_sm_client_xsmp_class_init (EggSMClientXSMPClass *klass)
+{
+ EggSMClientClass *sm_client_class = EGG_SM_CLIENT_CLASS (klass);
+
+ sm_client_class->startup = sm_client_xsmp_startup;
+ sm_client_class->set_restart_command = sm_client_xsmp_set_restart_command;
+ sm_client_class->will_quit = sm_client_xsmp_will_quit;
+ sm_client_class->end_session = sm_client_xsmp_end_session;
+}
+
+EggSMClient *
+egg_sm_client_xsmp_new (void)
+{
+ if (!g_getenv ("SESSION_MANAGER"))
+ return NULL;
+
+ return g_object_new (EGG_TYPE_SM_CLIENT_XSMP, NULL);
+}
+
+static gboolean
+sm_client_xsmp_connect (gpointer user_data)
+{
+ EggSMClientXSMP *xsmp = user_data;
+ SmcCallbacks callbacks;
+ char *client_id;
+ char error_string_ret[256];
+ char pid_str[64];
+ EggDesktopFile *desktop_file;
+ GPtrArray *clone, *restart;
+
+ g_source_remove (xsmp->idle);
+ xsmp->idle = 0;
+
+ ice_init ();
+ SmcSetErrorHandler (smc_error_handler);
+
+ callbacks.save_yourself.callback = xsmp_save_yourself;
+ callbacks.die.callback = xsmp_die;
+ callbacks.save_complete.callback = xsmp_save_complete;
+ callbacks.shutdown_cancelled.callback = xsmp_shutdown_cancelled;
+
+ callbacks.save_yourself.client_data = xsmp;
+ callbacks.die.client_data = xsmp;
+ callbacks.save_complete.client_data = xsmp;
+ callbacks.shutdown_cancelled.client_data = xsmp;
+
+ client_id = NULL;
+ error_string_ret[0] = '\0';
+ xsmp->connection =
+ SmcOpenConnection (NULL, xsmp, SmProtoMajor, SmProtoMinor,
+ SmcSaveYourselfProcMask | SmcDieProcMask |
+ SmcSaveCompleteProcMask |
+ SmcShutdownCancelledProcMask,
+ &callbacks,
+ //xsmp->client_id, &client_id,
+ NULL, &client_id,
+ sizeof (error_string_ret), error_string_ret);
+
+ if (!xsmp->connection)
+ {
+ g_warning ("Failed to connect to the session manager: %s\n",
+ error_string_ret[0] ?
+ error_string_ret : "no error message given");
+ xsmp->state = XSMP_STATE_CONNECTION_CLOSED;
+ return FALSE;
+ }
+
+ /* We expect a pointless initial SaveYourself if either (a) we
+ * didn't have an initial client ID, or (b) we DID have an initial
+ * client ID, but the server rejected it and gave us a new one.
+ */
+ if (!xsmp->client_id ||
+ (client_id && strcmp (xsmp->client_id, client_id) != 0))
+ xsmp->expecting_initial_save_yourself = TRUE;
+
+ if (client_id)
+ {
+ g_free (xsmp->client_id);
+ xsmp->client_id = g_strdup (client_id);
+ free (client_id);
+
+ gdk_threads_enter ();
+ gdk_set_sm_client_id (xsmp->client_id);
+ gdk_threads_leave ();
+
+ g_debug ("Got client ID \"%s\"", xsmp->client_id);
+ }
+
+ /* Parse info out of desktop file */
+ desktop_file = egg_get_desktop_file ();
+ if (desktop_file)
+ {
+ GError *err = NULL;
+ char *cmdline, **argv;
+ int argc;
+
+ if (xsmp->restart_style == SmRestartIfRunning)
+ {
+ if (egg_desktop_file_get_boolean (desktop_file,
+ "X-GNOME-AutoRestart", NULL))
+ xsmp->restart_style = SmRestartImmediately;
+ }
+
+ if (!xsmp->set_restart_command)
+ {
+ cmdline = egg_desktop_file_parse_exec (desktop_file, NULL, &err);
+ if (cmdline && g_shell_parse_argv (cmdline, &argc, &argv, &err))
+ {
+ egg_sm_client_set_restart_command (EGG_SM_CLIENT (xsmp),
+ argc, (const char **)argv);
+ g_strfreev (argv);
+ }
+ else
+ {
+ g_warning ("Could not parse Exec line in desktop file: %s",
+ err->message);
+ g_error_free (err);
+ }
+ }
+ }
+
+ if (!xsmp->set_restart_command)
+ xsmp->restart_command = g_strsplit (g_get_prgname (), " ", -1);
+
+ clone = generate_command (xsmp->restart_command, NULL, NULL);
+ restart = generate_command (xsmp->restart_command, xsmp->client_id, NULL);
+
+ g_debug ("Setting initial properties");
+
+ /* Program, CloneCommand, RestartCommand, and UserID are required.
+ * ProcessID isn't required, but the SM may be able to do something
+ * useful with it.
+ */
+ g_snprintf (pid_str, sizeof (pid_str), "%lu", (gulong) getpid ());
+ set_properties (xsmp,
+ string_prop (SmProgram, g_get_prgname ()),
+ ptrarray_prop (SmCloneCommand, clone),
+ ptrarray_prop (SmRestartCommand, restart),
+ string_prop (SmUserID, g_get_user_name ()),
+ string_prop (SmProcessID, pid_str),
+ card8_prop (SmRestartStyleHint, xsmp->restart_style),
+ NULL);
+ g_ptr_array_free (clone, TRUE);
+ g_ptr_array_free (restart, TRUE);
+
+ if (desktop_file)
+ {
+ set_properties (xsmp,
+ string_prop ("_GSM_DesktopFile", egg_desktop_file_get_source (desktop_file)),
+ NULL);
+ }
+
+ xsmp->state = XSMP_STATE_IDLE;
+ return FALSE;
+}
+
+/* This gets called from two different places: xsmp_die() (when the
+ * server asks us to disconnect) and process_ice_messages() (when the
+ * server disconnects unexpectedly).
+ */
+static void
+sm_client_xsmp_disconnect (EggSMClientXSMP *xsmp)
+{
+ SmcConn connection;
+
+ if (!xsmp->connection)
+ return;
+
+ g_debug ("Disconnecting");
+
+ connection = xsmp->connection;
+ xsmp->connection = NULL;
+ SmcCloseConnection (connection, 0, NULL);
+ xsmp->state = XSMP_STATE_CONNECTION_CLOSED;
+
+ xsmp->waiting_to_save_myself = FALSE;
+ update_pending_events (xsmp);
+}
+
+static void
+sm_client_xsmp_startup (EggSMClient *client,
+ const char *client_id)
+{
+ EggSMClientXSMP *xsmp = (EggSMClientXSMP *)client;
+
+ xsmp->state = XSMP_STATE_START;
+ if (xsmp->client_id)
+ g_free (xsmp->client_id);
+ xsmp->client_id = g_strdup (client_id);
+
+ /* Don't connect to the session manager until we reach the main
+ * loop, since the session manager may assume we're fully up and
+ * running once we connect. (This also gives the application a
+ * chance to call egg_set_desktop_file() before we set the initial
+ * properties.)
+ */
+ xsmp->idle = g_idle_add (sm_client_xsmp_connect, client);
+}
+
+static void
+sm_client_xsmp_set_restart_command (EggSMClient *client,
+ int argc,
+ const char **argv)
+{
+ EggSMClientXSMP *xsmp = (EggSMClientXSMP *)client;
+ int i;
+
+ g_strfreev (xsmp->restart_command);
+
+ xsmp->restart_command = g_new (char *, argc + 1);
+ for (i = 0; i < argc; i++)
+ xsmp->restart_command[i] = g_strdup (argv[i]);
+ xsmp->restart_command[i] = NULL;
+
+ xsmp->set_restart_command = TRUE;
+}
+
+static void
+sm_client_xsmp_will_quit (EggSMClient *client,
+ gboolean will_quit)
+{
+ EggSMClientXSMP *xsmp = (EggSMClientXSMP *)client;
+
+ if (xsmp->state == XSMP_STATE_CONNECTION_CLOSED)
+ {
+ /* The session manager has already exited! Schedule a quit
+ * signal.
+ */
+ xsmp->waiting_to_emit_quit = TRUE;
+ update_pending_events (xsmp);
+ return;
+ }
+ else if (xsmp->state == XSMP_STATE_SHUTDOWN_CANCELLED)
+ {
+ /* We received a ShutdownCancelled message while the application
+ * was interacting; Schedule a quit_cancelled signal.
+ */
+ xsmp->waiting_to_emit_quit_cancelled = TRUE;
+ update_pending_events (xsmp);
+ return;
+ }
+
+ g_return_if_fail (xsmp->state == XSMP_STATE_INTERACT);
+
+ g_debug ("Sending InteractDone(%s)", will_quit ? "False" : "True");
+ SmcInteractDone (xsmp->connection, !will_quit);
+
+ if (will_quit && xsmp->need_save_state)
+ save_state (xsmp);
+
+ g_debug ("Sending SaveYourselfDone(%s)", will_quit ? "True" : "False");
+ SmcSaveYourselfDone (xsmp->connection, will_quit);
+ xsmp->state = XSMP_STATE_SAVE_YOURSELF_DONE;
+}
+
+static gboolean
+sm_client_xsmp_end_session (EggSMClient *client,
+ EggSMClientEndStyle style,
+ gboolean request_confirmation)
+{
+ EggSMClientXSMP *xsmp = (EggSMClientXSMP *)client;
+ int save_type;
+
+ /* To end the session via XSMP, we have to send a
+ * SaveYourselfRequest. We aren't allowed to do that if anything
+ * else is going on, but we don't want to expose this fact to the
+ * application. So we do our best to patch things up here...
+ *
+ * In the worst case, this method might block for some length of
+ * time in process_ice_messages, but the only time that code path is
+ * honestly likely to get hit is if the application tries to end the
+ * session as the very first thing it does, in which case it
+ * probably won't actually block anyway. It's not worth gunking up
+ * the API to try to deal nicely with the other 0.01% of cases where
+ * this happens.
+ */
+
+ while (xsmp->state != XSMP_STATE_IDLE ||
+ xsmp->expecting_initial_save_yourself)
+ {
+ /* If we're already shutting down, we don't need to do anything. */
+ if (xsmp->shutting_down)
+ return TRUE;
+
+ switch (xsmp->state)
+ {
+ case XSMP_STATE_START:
+ /* Force the connection to complete (or fail) now. */
+ sm_client_xsmp_connect (xsmp);
+ break;
+
+ case XSMP_STATE_CONNECTION_CLOSED:
+ return FALSE;
+
+ case XSMP_STATE_SAVE_YOURSELF:
+ /* Trying to log out from the save_state callback? Whatever.
+ * Abort the save_state.
+ */
+ SmcSaveYourselfDone (xsmp->connection, FALSE);
+ xsmp->state = XSMP_STATE_SAVE_YOURSELF_DONE;
+ break;
+
+ case XSMP_STATE_INTERACT_REQUEST:
+ case XSMP_STATE_INTERACT:
+ case XSMP_STATE_SHUTDOWN_CANCELLED:
+ /* Already in a shutdown-related state, just ignore
+ * the new shutdown request...
+ */
+ return TRUE;
+
+ case XSMP_STATE_IDLE:
+ if (!xsmp->expecting_initial_save_yourself)
+ break;
+ /* else fall through */
+
+ case XSMP_STATE_SAVE_YOURSELF_DONE:
+ /* We need to wait for some response from the server.*/
+ process_ice_messages (SmcGetIceConnection (xsmp->connection));
+ break;
+
+ default:
+ /* Hm... shouldn't happen */
+ return FALSE;
+ }
+ }
+
+ /* xfce4-session will do the wrong thing if we pass SmSaveGlobal and
+ * the user chooses to save the session. But gnome-session will do
+ * the wrong thing if we pass SmSaveBoth and the user chooses NOT to
+ * save the session... Sigh.
+ */
+ if (!strcmp (SmcVendor (xsmp->connection), "xfce4-session"))
+ save_type = SmSaveBoth;
+ else
+ save_type = SmSaveGlobal;
+
+ g_debug ("Sending SaveYourselfRequest(SmSaveGlobal, Shutdown, SmInteractStyleAny, %sFast)", request_confirmation ? "!" : "");
+ SmcRequestSaveYourself (xsmp->connection,
+ save_type,
+ True, /* shutdown */
+ SmInteractStyleAny,
+ !request_confirmation, /* fast */
+ True /* global */);
+ return TRUE;
+}
+
+static gboolean
+idle_do_pending_events (gpointer data)
+{
+ EggSMClientXSMP *xsmp = data;
+ EggSMClient *client = data;
+
+ gdk_threads_enter ();
+
+ xsmp->idle = 0;
+
+ if (xsmp->waiting_to_emit_quit)
+ {
+ xsmp->waiting_to_emit_quit = FALSE;
+ egg_sm_client_quit (client);
+ goto out;
+ }
+
+ if (xsmp->waiting_to_emit_quit_cancelled)
+ {
+ xsmp->waiting_to_emit_quit_cancelled = FALSE;
+ egg_sm_client_quit_cancelled (client);
+ xsmp->state = XSMP_STATE_IDLE;
+ }
+
+ if (xsmp->waiting_to_save_myself)
+ {
+ xsmp->waiting_to_save_myself = FALSE;
+ do_save_yourself (xsmp);
+ }
+
+ out:
+ gdk_threads_leave ();
+ return FALSE;
+}
+
+static void
+update_pending_events (EggSMClientXSMP *xsmp)
+{
+ gboolean want_idle =
+ xsmp->waiting_to_emit_quit ||
+ xsmp->waiting_to_emit_quit_cancelled ||
+ xsmp->waiting_to_save_myself;
+
+ if (want_idle)
+ {
+ if (xsmp->idle == 0)
+ xsmp->idle = g_idle_add (idle_do_pending_events, xsmp);
+ }
+ else
+ {
+ if (xsmp->idle != 0)
+ g_source_remove (xsmp->idle);
+ xsmp->idle = 0;
+ }
+}
+
+static void
+fix_broken_state (EggSMClientXSMP *xsmp, const char *message,
+ gboolean send_interact_done,
+ gboolean send_save_yourself_done)
+{
+ g_warning ("Received XSMP %s message in state %s: client or server error",
+ message, EGG_SM_CLIENT_XSMP_STATE (xsmp));
+
+ /* Forget any pending SaveYourself plans we had */
+ xsmp->waiting_to_save_myself = FALSE;
+ update_pending_events (xsmp);
+
+ if (send_interact_done)
+ SmcInteractDone (xsmp->connection, False);
+ if (send_save_yourself_done)
+ SmcSaveYourselfDone (xsmp->connection, True);
+
+ xsmp->state = send_save_yourself_done ? XSMP_STATE_SAVE_YOURSELF_DONE : XSMP_STATE_IDLE;
+}
+
+/* SM callbacks */
+
+static void
+xsmp_save_yourself (SmcConn smc_conn,
+ SmPointer client_data,
+ int save_type,
+ Bool shutdown,
+ int interact_style,
+ Bool fast)
+{
+ EggSMClientXSMP *xsmp = client_data;
+ gboolean wants_quit_requested;
+
+ g_debug ("Received SaveYourself(%s, %s, %s, %s) in state %s",
+ save_type == SmSaveLocal ? "SmSaveLocal" :
+ save_type == SmSaveGlobal ? "SmSaveGlobal" : "SmSaveBoth",
+ shutdown ? "Shutdown" : "!Shutdown",
+ interact_style == SmInteractStyleAny ? "SmInteractStyleAny" :
+ interact_style == SmInteractStyleErrors ? "SmInteractStyleErrors" :
+ "SmInteractStyleNone", fast ? "Fast" : "!Fast",
+ EGG_SM_CLIENT_XSMP_STATE (xsmp));
+
+ if (xsmp->state != XSMP_STATE_IDLE &&
+ xsmp->state != XSMP_STATE_SHUTDOWN_CANCELLED)
+ {
+ fix_broken_state (xsmp, "SaveYourself", FALSE, TRUE);
+ return;
+ }
+
+ /* If this is the initial SaveYourself, ignore it; we've already set
+ * properties and there's no reason to actually save state too.
+ */
+ if (xsmp->expecting_initial_save_yourself)
+ {
+ xsmp->expecting_initial_save_yourself = FALSE;
+
+ if (save_type == SmSaveLocal &&
+ interact_style == SmInteractStyleNone &&
+ !shutdown && !fast)
+ {
+ g_debug ("Sending SaveYourselfDone(True) for initial SaveYourself");
+ SmcSaveYourselfDone (xsmp->connection, True);
+ /* As explained in the comment at the end of
+ * do_save_yourself(), SAVE_YOURSELF_DONE is the correct
+ * state here, not IDLE.
+ */
+ xsmp->state = XSMP_STATE_SAVE_YOURSELF_DONE;
+ return;
+ }
+ else
+ g_warning ("First SaveYourself was not the expected one!");
+ }
+
+ /* Even ignoring the "fast" flag completely, there are still 18
+ * different combinations of save_type, shutdown and interact_style.
+ * We interpret them as follows:
+ *
+ * Type Shutdown Interact Interpretation
+ * G F A/E/N do nothing (1)
+ * G T N do nothing (1)*
+ * G T A/E quit_requested (2)
+ * L/B F A/E/N save_state (3)
+ * L/B T N save_state (3)*
+ * L/B T A/E quit_requested, then save_state (4)
+ *
+ * 1. Do nothing, because the SM asked us to do something
+ * uninteresting (save open files, but then don't quit
+ * afterward) or rude (save open files without asking the user
+ * for confirmation).
+ *
+ * 2. Request interaction and then emit ::quit_requested. This
+ * perhaps isn't quite correct for the SmInteractStyleErrors
+ * case, but we don't care.
+ *
+ * 3. Emit ::save_state. The SmSaveBoth SaveYourselfs in these
+ * rows essentially get demoted to SmSaveLocal, because their
+ * Global halves correspond to "do nothing".
+ *
+ * 4. Request interaction, emit ::quit_requested, and then emit
+ * ::save_state after interacting. This is the SmSaveBoth
+ * equivalent of #2, but we also promote SmSaveLocal shutdown
+ * SaveYourselfs to SmSaveBoth here, because we want to give
+ * the user a chance to save open files before quitting.
+ *
+ * (* It would be nice if we could do something useful when the
+ * session manager sends a SaveYourself with shutdown True and
+ * SmInteractStyleNone. But we can't, so we just pretend it didn't
+ * even tell us it was shutting down. The docs for ::quit mention
+ * that it might not always be preceded by ::quit_requested.)
+ */
+
+ /* As an optimization, we don't actually request interaction and
+ * emit ::quit_requested if the application isn't listening to the
+ * signal.
+ */
+ wants_quit_requested = g_signal_has_handler_pending (xsmp, g_signal_lookup ("quit_requested", EGG_TYPE_SM_CLIENT), 0, FALSE);
+
+ xsmp->need_save_state = (save_type != SmSaveGlobal);
+ xsmp->need_quit_requested = (shutdown && wants_quit_requested &&
+ interact_style != SmInteractStyleNone);
+ xsmp->interact_errors = (interact_style == SmInteractStyleErrors);
+
+ xsmp->shutting_down = shutdown;
+
+ do_save_yourself (xsmp);
+}
+
+static void
+do_save_yourself (EggSMClientXSMP *xsmp)
+{
+ if (xsmp->state == XSMP_STATE_SHUTDOWN_CANCELLED)
+ {
+ /* The SM cancelled a previous SaveYourself, but we haven't yet
+ * had a chance to tell the application, so we can't start
+ * processing this SaveYourself yet.
+ */
+ xsmp->waiting_to_save_myself = TRUE;
+ update_pending_events (xsmp);
+ return;
+ }
+
+ if (xsmp->need_quit_requested)
+ {
+ xsmp->state = XSMP_STATE_INTERACT_REQUEST;
+
+ g_debug ("Sending InteractRequest(%s)",
+ xsmp->interact_errors ? "Error" : "Normal");
+ SmcInteractRequest (xsmp->connection,
+ xsmp->interact_errors ? SmDialogError : SmDialogNormal,
+ xsmp_interact,
+ xsmp);
+ return;
+ }
+
+ if (xsmp->need_save_state)
+ {
+ save_state (xsmp);
+
+ /* Though unlikely, the client could have been disconnected
+ * while the application was saving its state.
+ */
+ if (!xsmp->connection)
+ return;
+ }
+
+ g_debug ("Sending SaveYourselfDone(True)");
+ SmcSaveYourselfDone (xsmp->connection, True);
+
+ /* The client state diagram in the XSMP spec says that after a
+ * non-shutdown SaveYourself, we go directly back to "idle". But
+ * everything else in both the XSMP spec and the libSM docs
+ * disagrees.
+ */
+ xsmp->state = XSMP_STATE_SAVE_YOURSELF_DONE;
+}
+
+static void
+save_state (EggSMClientXSMP *xsmp)
+{
+ GKeyFile *state_file;
+ char *state_file_path, *data;
+ EggDesktopFile *desktop_file;
+ GPtrArray *restart;
+ int offset, fd;
+
+ /* We set xsmp->state before emitting save_state, but our caller is
+ * responsible for setting it back afterward.
+ */
+ xsmp->state = XSMP_STATE_SAVE_YOURSELF;
+
+ state_file = egg_sm_client_save_state ((EggSMClient *)xsmp);
+ if (!state_file)
+ {
+ restart = generate_command (xsmp->restart_command, xsmp->client_id, NULL);
+ set_properties (xsmp,
+ ptrarray_prop (SmRestartCommand, restart),
+ NULL);
+ g_ptr_array_free (restart, TRUE);
+ delete_properties (xsmp, SmDiscardCommand, NULL);
+ return;
+ }
+
+ desktop_file = egg_get_desktop_file ();
+ if (desktop_file)
+ {
+ GKeyFile *merged_file;
+
+ merged_file = g_key_file_new ();
+ if (g_key_file_load_from_file (merged_file,
+ egg_desktop_file_get_source (desktop_file),
+ G_KEY_FILE_KEEP_COMMENTS |
+ G_KEY_FILE_KEEP_TRANSLATIONS, NULL))
+ {
+ int g, k, i;
+ char **groups, **keys, *value, *exec;
+
+ groups = g_key_file_get_groups (state_file, NULL);
+ for (g = 0; groups[g]; g++)
+ {
+ keys = g_key_file_get_keys (state_file, groups[g], NULL, NULL);
+ for (k = 0; keys[k]; k++)
+ {
+ value = g_key_file_get_value (state_file, groups[g],
+ keys[k], NULL);
+ if (value)
+ {
+ g_key_file_set_value (merged_file, groups[g],
+ keys[k], value);
+ g_free (value);
+ }
+ }
+ g_strfreev (keys);
+ }
+ g_strfreev (groups);
+
+ g_key_file_free (state_file);
+ state_file = merged_file;
+
+ /* Update Exec key using "--sm-client-state-file %k" */
+ restart = generate_command (xsmp->restart_command,
+ NULL, "%k");
+ for (i = 0; i < restart->len; i++)
+ restart->pdata[i] = g_shell_quote (restart->pdata[i]);
+ g_ptr_array_add (restart, NULL);
+ exec = g_strjoinv (" ", (char **)restart->pdata);
+ g_strfreev ((char **)restart->pdata);
+ g_ptr_array_free (restart, FALSE);
+
+ g_key_file_set_string (state_file, EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_EXEC,
+ exec);
+ g_free (exec);
+
+ }
+ }
+
+ /* Now write state_file to disk. (We can't use mktemp(), because
+ * that requires the filename to end with "XXXXXX", and we want
+ * it to end with ".desktop".)
+ */
+
+ data = g_key_file_to_data (state_file, NULL, NULL);
+ g_key_file_free (state_file);
+
+ offset = 0;
+ while (1)
+ {
+ state_file_path = g_strdup_printf ("%s%csession-state%c%s-%ld.%s",
+ g_get_user_config_dir (),
+ G_DIR_SEPARATOR, G_DIR_SEPARATOR,
+ g_get_prgname (),
+ (long)time (NULL) + offset,
+ desktop_file ? "desktop" : "state");
+
+ fd = open (state_file_path, O_WRONLY | O_CREAT | O_EXCL, 0644);
+ if (fd == -1)
+ {
+ if (errno == EEXIST)
+ {
+ offset++;
+ g_free (state_file_path);
+ continue;
+ }
+ else if (errno == ENOTDIR || errno == ENOENT)
+ {
+ char *sep = strrchr (state_file_path, G_DIR_SEPARATOR);
+
+ *sep = '\0';
+ if (g_mkdir_with_parents (state_file_path, 0755) != 0)
+ {
+ g_warning ("Could not create directory '%s'",
+ state_file_path);
+ g_free (state_file_path);
+ state_file_path = NULL;
+ break;
+ }
+
+ continue;
+ }
+
+ g_warning ("Could not create file '%s': %s",
+ state_file_path, g_strerror (errno));
+ g_free (state_file_path);
+ state_file_path = NULL;
+ break;
+ }
+
+ close (fd);
+ g_file_set_contents (state_file_path, data, -1, NULL);
+ break;
+ }
+ g_free (data);
+
+ restart = generate_command (xsmp->restart_command, xsmp->client_id,
+ state_file_path);
+ set_properties (xsmp,
+ ptrarray_prop (SmRestartCommand, restart),
+ NULL);
+ g_ptr_array_free (restart, TRUE);
+
+ if (state_file_path)
+ {
+ set_properties (xsmp,
+ array_prop (SmDiscardCommand,
+ "/bin/rm", "-rf", state_file_path,
+ NULL),
+ NULL);
+ g_free (state_file_path);
+ }
+}
+
+static void
+xsmp_interact (SmcConn smc_conn,
+ SmPointer client_data)
+{
+ EggSMClientXSMP *xsmp = client_data;
+ EggSMClient *client = client_data;
+
+ g_debug ("Received Interact message in state %s",
+ EGG_SM_CLIENT_XSMP_STATE (xsmp));
+
+ if (xsmp->state != XSMP_STATE_INTERACT_REQUEST)
+ {
+ fix_broken_state (xsmp, "Interact", TRUE, TRUE);
+ return;
+ }
+
+ xsmp->state = XSMP_STATE_INTERACT;
+ egg_sm_client_quit_requested (client);
+}
+
+static void
+xsmp_die (SmcConn smc_conn,
+ SmPointer client_data)
+{
+ EggSMClientXSMP *xsmp = client_data;
+ EggSMClient *client = client_data;
+
+ g_debug ("Received Die message in state %s",
+ EGG_SM_CLIENT_XSMP_STATE (xsmp));
+
+ sm_client_xsmp_disconnect (xsmp);
+ egg_sm_client_quit (client);
+}
+
+static void
+xsmp_save_complete (SmcConn smc_conn,
+ SmPointer client_data)
+{
+ EggSMClientXSMP *xsmp = client_data;
+
+ g_debug ("Received SaveComplete message in state %s",
+ EGG_SM_CLIENT_XSMP_STATE (xsmp));
+
+ if (xsmp->state == XSMP_STATE_SAVE_YOURSELF_DONE)
+ xsmp->state = XSMP_STATE_IDLE;
+ else
+ fix_broken_state (xsmp, "SaveComplete", FALSE, FALSE);
+}
+
+static void
+xsmp_shutdown_cancelled (SmcConn smc_conn,
+ SmPointer client_data)
+{
+ EggSMClientXSMP *xsmp = client_data;
+ EggSMClient *client = client_data;
+
+ g_debug ("Received ShutdownCancelled message in state %s",
+ EGG_SM_CLIENT_XSMP_STATE (xsmp));
+
+ xsmp->shutting_down = FALSE;
+
+ if (xsmp->state == XSMP_STATE_SAVE_YOURSELF_DONE)
+ {
+ /* We've finished interacting and now the SM has agreed to
+ * cancel the shutdown.
+ */
+ xsmp->state = XSMP_STATE_IDLE;
+ egg_sm_client_quit_cancelled (client);
+ }
+ else if (xsmp->state == XSMP_STATE_SHUTDOWN_CANCELLED)
+ {
+ /* Hm... ok, so we got a shutdown SaveYourself, which got
+ * cancelled, but the application was still interacting, so we
+ * didn't tell it yet, and then *another* SaveYourself arrived,
+ * which we must still be waiting to tell the app about, except
+ * that now that SaveYourself has been cancelled too! Dizzy yet?
+ */
+ xsmp->waiting_to_save_myself = FALSE;
+ update_pending_events (xsmp);
+ }
+ else
+ {
+ g_debug ("Sending SaveYourselfDone(False)");
+ SmcSaveYourselfDone (xsmp->connection, False);
+
+ if (xsmp->state == XSMP_STATE_INTERACT)
+ {
+ /* The application is currently interacting, so we can't
+ * tell it about the cancellation yet; we will wait until
+ * after it calls egg_sm_client_will_quit().
+ */
+ xsmp->state = XSMP_STATE_SHUTDOWN_CANCELLED;
+ }
+ else
+ {
+ /* The shutdown was cancelled before the application got a
+ * chance to interact.
+ */
+ xsmp->state = XSMP_STATE_IDLE;
+ }
+ }
+}
+
+/* Utilities */
+
+/* Create a restart/clone/Exec command based on @restart_command.
+ * If @client_id is non-%NULL, add "--sm-client-id @client_id".
+ * If @state_file is non-%NULL, add "--sm-client-state-file @state_file".
+ *
+ * None of the input strings are g_strdup()ed; the caller must keep
+ * them around until it is done with the returned GPtrArray, and must
+ * then free the array, but not its contents.
+ */
+static GPtrArray *
+generate_command (char **restart_command, const char *client_id,
+ const char *state_file)
+{
+ GPtrArray *cmd;
+ int i;
+
+ cmd = g_ptr_array_new ();
+ g_ptr_array_add (cmd, restart_command[0]);
+
+ if (client_id)
+ {
+ g_ptr_array_add (cmd, "--sm-client-id");
+ g_ptr_array_add (cmd, (char *)client_id);
+ }
+
+ if (state_file)
+ {
+ g_ptr_array_add (cmd, "--sm-client-state-file");
+ g_ptr_array_add (cmd, (char *)state_file);
+ }
+
+ for (i = 1; restart_command[i]; i++)
+ g_ptr_array_add (cmd, restart_command[i]);
+
+ return cmd;
+}
+
+/* Takes a NULL-terminated list of SmProp * values, created by
+ * array_prop, ptrarray_prop, string_prop, card8_prop, sets them, and
+ * frees them.
+ */
+static void
+set_properties (EggSMClientXSMP *xsmp, ...)
+{
+ GPtrArray *props;
+ SmProp *prop;
+ va_list ap;
+ int i;
+
+ props = g_ptr_array_new ();
+
+ va_start (ap, xsmp);
+ while ((prop = va_arg (ap, SmProp *)))
+ g_ptr_array_add (props, prop);
+ va_end (ap);
+
+ if (xsmp->connection)
+ {
+ SmcSetProperties (xsmp->connection, props->len,
+ (SmProp **)props->pdata);
+ }
+
+ for (i = 0; i < props->len; i++)
+ {
+ prop = props->pdata[i];
+ g_free (prop->vals);
+ g_free (prop);
+ }
+ g_ptr_array_free (props, TRUE);
+}
+
+/* Takes a NULL-terminated list of property names and deletes them. */
+static void
+delete_properties (EggSMClientXSMP *xsmp, ...)
+{
+ GPtrArray *props;
+ char *prop;
+ va_list ap;
+
+ if (!xsmp->connection)
+ return;
+
+ props = g_ptr_array_new ();
+
+ va_start (ap, xsmp);
+ while ((prop = va_arg (ap, char *)))
+ g_ptr_array_add (props, prop);
+ va_end (ap);
+
+ SmcDeleteProperties (xsmp->connection, props->len,
+ (char **)props->pdata);
+
+ g_ptr_array_free (props, TRUE);
+}
+
+/* Takes an array of strings and creates a LISTofARRAY8 property. The
+ * strings are neither dupped nor freed; they need to remain valid
+ * until you're done with the SmProp.
+ */
+static SmProp *
+array_prop (const char *name, ...)
+{
+ SmProp *prop;
+ SmPropValue pv;
+ GArray *vals;
+ char *value;
+ va_list ap;
+
+ prop = g_new (SmProp, 1);
+ prop->name = (char *)name;
+ prop->type = SmLISTofARRAY8;
+
+ vals = g_array_new (FALSE, FALSE, sizeof (SmPropValue));
+
+ va_start (ap, name);
+ while ((value = va_arg (ap, char *)))
+ {
+ pv.length = strlen (value);
+ pv.value = value;
+ g_array_append_val (vals, pv);
+ }
+
+ prop->num_vals = vals->len;
+ prop->vals = (SmPropValue *)vals->data;
+
+ g_array_free (vals, FALSE);
+
+ return prop;
+}
+
+/* Takes a GPtrArray of strings and creates a LISTofARRAY8 property.
+ * The array contents are neither dupped nor freed; they need to
+ * remain valid until you're done with the SmProp.
+ */
+static SmProp *
+ptrarray_prop (const char *name, GPtrArray *values)
+{
+ SmProp *prop;
+ SmPropValue pv;
+ GArray *vals;
+ int i;
+
+ prop = g_new (SmProp, 1);
+ prop->name = (char *)name;
+ prop->type = SmLISTofARRAY8;
+
+ vals = g_array_new (FALSE, FALSE, sizeof (SmPropValue));
+
+ for (i = 0; i < values->len; i++)
+ {
+ pv.length = strlen (values->pdata[i]);
+ pv.value = values->pdata[i];
+ g_array_append_val (vals, pv);
+ }
+
+ prop->num_vals = vals->len;
+ prop->vals = (SmPropValue *)vals->data;
+
+ g_array_free (vals, FALSE);
+
+ return prop;
+}
+
+/* Takes a string and creates an ARRAY8 property. The string is
+ * neither dupped nor freed; it needs to remain valid until you're
+ * done with the SmProp.
+ */
+static SmProp *
+string_prop (const char *name, const char *value)
+{
+ SmProp *prop;
+
+ prop = g_new (SmProp, 1);
+ prop->name = (char *)name;
+ prop->type = SmARRAY8;
+
+ prop->num_vals = 1;
+ prop->vals = g_new (SmPropValue, 1);
+
+ prop->vals[0].length = strlen (value);
+ prop->vals[0].value = (char *)value;
+
+ return prop;
+}
+
+/* Takes a char and creates a CARD8 property. */
+static SmProp *
+card8_prop (const char *name, unsigned char value)
+{
+ SmProp *prop;
+ char *card8val;
+
+ /* To avoid having to allocate and free prop->vals[0], we cheat and
+ * make vals a 2-element-long array and then use the second element
+ * to store value.
+ */
+
+ prop = g_new (SmProp, 1);
+ prop->name = (char *)name;
+ prop->type = SmCARD8;
+
+ prop->num_vals = 1;
+ prop->vals = g_new (SmPropValue, 2);
+ card8val = (char *)(&prop->vals[1]);
+ card8val[0] = value;
+
+ prop->vals[0].length = 1;
+ prop->vals[0].value = card8val;
+
+ return prop;
+}
+
+/* ICE code. This makes no effort to play nice with anyone else trying
+ * to use libICE. Fortunately, no one uses libICE for anything other
+ * than SM. (DCOP uses ICE, but it has its own private copy of
+ * libICE.)
+ *
+ * When this moves to gtk, it will need to be cleverer, to avoid
+ * tripping over old apps that use GnomeClient or that use libSM
+ * directly.
+ */
+
+#include <X11/ICE/ICElib.h>
+#include <fcntl.h>
+
+static void ice_error_handler (IceConn ice_conn,
+ Bool swap,
+ int offending_minor_opcode,
+ unsigned long offending_sequence,
+ int error_class,
+ int severity,
+ IcePointer values);
+static void ice_io_error_handler (IceConn ice_conn);
+static void ice_connection_watch (IceConn ice_conn,
+ IcePointer client_data,
+ Bool opening,
+ IcePointer *watch_data);
+
+static void
+ice_init (void)
+{
+ IceSetIOErrorHandler (ice_io_error_handler);
+ IceSetErrorHandler (ice_error_handler);
+ IceAddConnectionWatch (ice_connection_watch, NULL);
+}
+
+static gboolean
+process_ice_messages (IceConn ice_conn)
+{
+ IceProcessMessagesStatus status;
+
+ gdk_threads_enter ();
+ status = IceProcessMessages (ice_conn, NULL, NULL);
+ gdk_threads_leave ();
+
+ switch (status)
+ {
+ case IceProcessMessagesSuccess:
+ return TRUE;
+
+ case IceProcessMessagesIOError:
+ sm_client_xsmp_disconnect (IceGetConnectionContext (ice_conn));
+ return FALSE;
+
+ case IceProcessMessagesConnectionClosed:
+ return FALSE;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static gboolean
+ice_iochannel_watch (GIOChannel *channel,
+ GIOCondition condition,
+ gpointer client_data)
+{
+ return process_ice_messages (client_data);
+}
+
+static void
+ice_connection_watch (IceConn ice_conn,
+ IcePointer client_data,
+ Bool opening,
+ IcePointer *watch_data)
+{
+ guint watch_id;
+
+ if (opening)
+ {
+ GIOChannel *channel;
+ int fd = IceConnectionNumber (ice_conn);
+
+ fcntl (fd, F_SETFD, fcntl (fd, F_GETFD, 0) | FD_CLOEXEC);
+ channel = g_io_channel_unix_new (fd);
+ watch_id = g_io_add_watch (channel, G_IO_IN | G_IO_ERR,
+ ice_iochannel_watch, ice_conn);
+ g_io_channel_unref (channel);
+
+ *watch_data = GUINT_TO_POINTER (watch_id);
+ }
+ else
+ {
+ watch_id = GPOINTER_TO_UINT (*watch_data);
+ g_source_remove (watch_id);
+ }
+}
+
+static void
+ice_error_handler (IceConn ice_conn,
+ Bool swap,
+ int offending_minor_opcode,
+ unsigned long offending_sequence,
+ int error_class,
+ int severity,
+ IcePointer values)
+{
+ /* Do nothing */
+}
+
+static void
+ice_io_error_handler (IceConn ice_conn)
+{
+ /* Do nothing */
+}
+
+static void
+smc_error_handler (SmcConn smc_conn,
+ Bool swap,
+ int offending_minor_opcode,
+ unsigned long offending_sequence,
+ int error_class,
+ int severity,
+ SmPointer values)
+{
+ /* Do nothing */
+}
diff --git a/toolkit/src/sugar/eggsmclient.c b/toolkit/src/sugar/eggsmclient.c
new file mode 100644
index 0000000..a59cea0
--- /dev/null
+++ b/toolkit/src/sugar/eggsmclient.c
@@ -0,0 +1,392 @@
+/*
+ * Copyright (C) 2007 Novell, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+#include <glib/gi18n.h>
+
+#include "eggsmclient.h"
+#include "eggsmclient-private.h"
+
+static void egg_sm_client_debug_handler (const char *log_domain,
+ GLogLevelFlags log_level,
+ const char *message,
+ gpointer user_data);
+
+enum {
+ SAVE_STATE,
+ QUIT_REQUESTED,
+ QUIT_CANCELLED,
+ QUIT,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+struct _EggSMClientPrivate {
+ GKeyFile *state_file;
+};
+
+#define EGG_SM_CLIENT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), EGG_TYPE_SM_CLIENT, EggSMClientPrivate))
+
+G_DEFINE_TYPE (EggSMClient, egg_sm_client, G_TYPE_OBJECT)
+
+static void
+egg_sm_client_init (EggSMClient *client)
+{
+}
+
+static void
+egg_sm_client_class_init (EggSMClientClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (EggSMClientPrivate));
+
+ /**
+ * EggSMClient::save_state:
+ * @client: the client
+ * @state_file: a #GKeyFile to save state information into
+ *
+ * Emitted when the session manager has requested that the
+ * application save information about its current state. The
+ * application should save its state into @state_file, and then the
+ * session manager may then restart the application in a future
+ * session and tell it to initialize itself from that state.
+ *
+ * You should not save any data into @state_file's "start group"
+ * (ie, the %NULL group). Instead, applications should save their
+ * data into groups with names that start with the application name,
+ * and libraries that connect to this signal should save their data
+ * into groups with names that start with the library name.
+ *
+ * Alternatively, rather than (or in addition to) using @state_file,
+ * the application can save its state by calling
+ * egg_sm_client_set_restart_command() during the processing of this
+ * signal (eg, to include a list of files to open).
+ **/
+ signals[SAVE_STATE] =
+ g_signal_new ("save_state",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EggSMClientClass, save_state),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE,
+ 1, G_TYPE_POINTER);
+
+ /**
+ * EggSMClient::quit_requested:
+ * @client: the client
+ *
+ * Emitted when the session manager requests that the application
+ * exit (generally because the user is logging out). The application
+ * should decide whether or not it is willing to quit (perhaps after
+ * asking the user what to do with documents that have unsaved
+ * changes) and then call egg_sm_client_will_quit(), passing %TRUE
+ * or %FALSE to give its answer to the session manager. (It does not
+ * need to give an answer before returning from the signal handler;
+ * it can interact with the user asynchronously and then give its
+ * answer later on.) If the application does not connect to this
+ * signal, then #EggSMClient will automatically return %TRUE on its
+ * behalf.
+ *
+ * The application should not save its session state as part of
+ * handling this signal; if the user has requested that the session
+ * be saved when logging out, then ::save_state will be emitted
+ * separately.
+ *
+ * If the application agrees to quit, it should then wait for either
+ * the ::quit_cancelled or ::quit signals to be emitted.
+ **/
+ signals[QUIT_REQUESTED] =
+ g_signal_new ("quit_requested",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EggSMClientClass, quit_requested),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ /**
+ * EggSMClient::quit_cancelled:
+ * @client: the client
+ *
+ * Emitted when the session manager decides to cancel a logout after
+ * the application has already agreed to quit. After receiving this
+ * signal, the application can go back to what it was doing before
+ * receiving the ::quit_requested signal.
+ **/
+ signals[QUIT_CANCELLED] =
+ g_signal_new ("quit_cancelled",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EggSMClientClass, quit_cancelled),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ /**
+ * EggSMClient::quit:
+ * @client: the client
+ *
+ * Emitted when the session manager wants the application to quit
+ * (generally because the user is logging out). The application
+ * should exit as soon as possible after receiving this signal; if
+ * it does not, the session manager may choose to forcibly kill it.
+ *
+ * Normally a GUI application would only be sent a ::quit if it
+ * agreed to quit in response to a ::quit_requested signal. However,
+ * this is not guaranteed; in some situations the session manager
+ * may decide to end the session without giving applications a
+ * chance to object.
+ **/
+ signals[QUIT] =
+ g_signal_new ("quit",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EggSMClientClass, quit),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+}
+
+static gboolean sm_client_disable = FALSE;
+static char *sm_client_state_file = NULL;
+static char *sm_client_id = NULL;
+
+static GOptionEntry entries[] = {
+ { "sm-client-disable", 0, 0,
+ G_OPTION_ARG_NONE, &sm_client_disable,
+ N_("Disable connection to session manager"), NULL },
+ { "sm-client-state-file", 0, 0,
+ G_OPTION_ARG_STRING, &sm_client_state_file,
+ N_("Specify file containing saved configuration"), N_("FILE") },
+ { "sm-client-id", 0, 0,
+ G_OPTION_ARG_STRING, &sm_client_id,
+ N_("Specify session management ID"), N_("ID") },
+ { NULL }
+};
+
+/**
+ * egg_sm_client_is_resumed:
+ * @client: the client
+ *
+ * Checks whether or not the current session has been resumed from
+ * a previous saved session. If so, the application should call
+ * egg_sm_client_get_state_file() and restore its state from the
+ * returned #GKeyFile.
+ *
+ * Return value: %TRUE if the session has been resumed
+ **/
+gboolean
+egg_sm_client_is_resumed (EggSMClient *client)
+{
+ return sm_client_state_file != NULL;
+}
+
+/**
+ * egg_sm_client_get_state_file:
+ * @client: the client
+ *
+ * If the application was resumed by the session manager, this will
+ * return the #GKeyFile containing its state from the previous
+ * session.
+ *
+ * Note that other libraries and #EggSMClient itself may also store
+ * state in the key file, so if you call egg_sm_client_get_groups(),
+ * on it, the return value will likely include groups that you did not
+ * put there yourself. (It is also not guaranteed that the first
+ * group created by the application will still be the "start group"
+ * when it is resumed.)
+ *
+ * Return value: the #GKeyFile containing the application's earlier
+ * state, or %NULL on error. You should not free this key file; it
+ * is owned by @client.
+ **/
+GKeyFile *
+egg_sm_client_get_state_file (EggSMClient *client)
+{
+ EggSMClientPrivate *priv = EGG_SM_CLIENT_GET_PRIVATE (client);
+ char *state_file_path;
+ GError *err = NULL;
+
+ if (!sm_client_state_file)
+ return NULL;
+ if (priv->state_file)
+ return priv->state_file;
+
+ if (!strncmp (sm_client_state_file, "file://", 7))
+ state_file_path = g_filename_from_uri (sm_client_state_file, NULL, NULL);
+ else
+ state_file_path = g_strdup (sm_client_state_file);
+
+ priv->state_file = g_key_file_new ();
+ if (!g_key_file_load_from_file (priv->state_file, state_file_path, 0, &err))
+ {
+ g_warning ("Could not load SM state file '%s': %s",
+ sm_client_state_file, err->message);
+ g_clear_error (&err);
+ g_key_file_free (priv->state_file);
+ priv->state_file = NULL;
+ }
+
+ g_free (state_file_path);
+ return priv->state_file;
+}
+
+/**
+ * egg_sm_client_set_restart_command:
+ * @client: the client
+ * @argc: the length of @argv
+ * @argv: argument vector
+ *
+ * Sets the command used to restart @client if it does not have a
+ * .desktop file that can be used to find its restart command.
+ *
+ * This can also be used when handling the ::save_state signal, to
+ * save the current state via an updated command line. (Eg, providing
+ * a list of filenames to open when the application is resumed.)
+ **/
+void
+egg_sm_client_set_restart_command (EggSMClient *client,
+ int argc,
+ const char **argv)
+{
+ g_return_if_fail (EGG_IS_SM_CLIENT (client));
+
+ if (EGG_SM_CLIENT_GET_CLASS (client)->set_restart_command)
+ EGG_SM_CLIENT_GET_CLASS (client)->set_restart_command (client, argc, argv);
+}
+
+/**
+ * egg_sm_client_will_quit:
+ * @client: the client
+ * @will_quit: whether or not the application is willing to quit
+ *
+ * This MUST be called in response to the ::quit_requested signal, to
+ * indicate whether or not the application is willing to quit. The
+ * application may call it either directly from the signal handler, or
+ * at some later point (eg, after asynchronously interacting with the
+ * user).
+ *
+ * If the application does not connect to ::quit_requested,
+ * #EggSMClient will call this method on its behalf (passing %TRUE
+ * for @will_quit).
+ *
+ * After calling this method, the application should wait to receive
+ * either ::quit_cancelled or ::quit.
+ **/
+void
+egg_sm_client_will_quit (EggSMClient *client,
+ gboolean will_quit)
+{
+ g_return_if_fail (EGG_IS_SM_CLIENT (client));
+
+ if (EGG_SM_CLIENT_GET_CLASS (client)->will_quit)
+ EGG_SM_CLIENT_GET_CLASS (client)->will_quit (client, will_quit);
+}
+
+/* Signal-emitting callbacks from platform-specific code */
+
+GKeyFile *
+egg_sm_client_save_state (EggSMClient *client)
+{
+ GKeyFile *state_file;
+ char *group;
+
+ state_file = g_key_file_new ();
+
+ g_debug ("Emitting save_state");
+ g_signal_emit (client, signals[SAVE_STATE], 0, state_file);
+ g_debug ("Done emitting save_state");
+
+ group = g_key_file_get_start_group (state_file);
+ if (group)
+ {
+ g_free (group);
+ return state_file;
+ }
+ else
+ {
+ g_key_file_free (state_file);
+ return NULL;
+ }
+}
+
+void
+egg_sm_client_quit_requested (EggSMClient *client)
+{
+ if (!g_signal_has_handler_pending (client, signals[QUIT_REQUESTED], 0, FALSE))
+ {
+ g_debug ("Not emitting quit_requested because no one is listening");
+ egg_sm_client_will_quit (client, TRUE);
+ return;
+ }
+
+ g_debug ("Emitting quit_requested");
+ g_signal_emit (client, signals[QUIT_REQUESTED], 0);
+ g_debug ("Done emitting quit_requested");
+}
+
+void
+egg_sm_client_quit_cancelled (EggSMClient *client)
+{
+ g_debug ("Emitting quit_cancelled");
+ g_signal_emit (client, signals[QUIT_CANCELLED], 0);
+ g_debug ("Done emitting quit_cancelled");
+}
+
+void
+egg_sm_client_quit (EggSMClient *client)
+{
+ g_debug ("Emitting quit");
+ g_signal_emit (client, signals[QUIT], 0);
+ g_debug ("Done emitting quit");
+
+ /* FIXME: should we just call gtk_main_quit() here? */
+}
+
+void
+egg_sm_client_startup (EggSMClient *client)
+{
+ if (EGG_SM_CLIENT_GET_CLASS (client)->startup)
+ EGG_SM_CLIENT_GET_CLASS (client)->startup (client, sm_client_id);
+}
+
+static void
+egg_sm_client_debug_handler (const char *log_domain,
+ GLogLevelFlags log_level,
+ const char *message,
+ gpointer user_data)
+{
+ static int debug = -1;
+
+ if (debug < 0)
+ debug = (g_getenv ("EGG_SM_CLIENT_DEBUG") != NULL);
+
+ if (debug)
+ g_log_default_handler (log_domain, log_level, message, NULL);
+}
diff --git a/toolkit/src/sugar/eggsmclient.h b/toolkit/src/sugar/eggsmclient.h
new file mode 100644
index 0000000..52d85de
--- /dev/null
+++ b/toolkit/src/sugar/eggsmclient.h
@@ -0,0 +1,112 @@
+/* eggsmclient.h
+ * Copyright (C) 2007 Novell, Inc.
+ *
+ * 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.
+ */
+
+#ifndef __EGG_SM_CLIENT_H__
+#define __EGG_SM_CLIENT_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define EGG_TYPE_SM_CLIENT (egg_sm_client_get_type ())
+#define EGG_SM_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_SM_CLIENT, EggSMClient))
+#define EGG_SM_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_SM_CLIENT, EggSMClientClass))
+#define EGG_IS_SM_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_SM_CLIENT))
+#define EGG_IS_SM_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_SM_CLIENT))
+#define EGG_SM_CLIENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_SM_CLIENT, EggSMClientClass))
+
+typedef struct _EggSMClient EggSMClient;
+typedef struct _EggSMClientClass EggSMClientClass;
+typedef struct _EggSMClientPrivate EggSMClientPrivate;
+
+typedef enum {
+ EGG_SM_CLIENT_END_SESSION_DEFAULT,
+ EGG_SM_CLIENT_LOGOUT,
+ EGG_SM_CLIENT_REBOOT,
+ EGG_SM_CLIENT_SHUTDOWN
+} EggSMClientEndStyle;
+
+typedef enum {
+ EGG_SM_CLIENT_MODE_DISABLED,
+ EGG_SM_CLIENT_MODE_NO_RESTART,
+ EGG_SM_CLIENT_MODE_NORMAL
+} EggSMClientMode;
+
+struct _EggSMClient
+{
+ GObject parent;
+
+};
+
+struct _EggSMClientClass
+{
+ GObjectClass parent_class;
+
+ /* signals */
+ void (*save_state) (EggSMClient *client,
+ GKeyFile *state_file);
+
+ void (*quit_requested) (EggSMClient *client);
+ void (*quit_cancelled) (EggSMClient *client);
+ void (*quit) (EggSMClient *client);
+
+ /* virtual methods */
+ void (*startup) (EggSMClient *client,
+ const char *client_id);
+ void (*set_restart_command) (EggSMClient *client,
+ int argc,
+ const char **argv);
+ void (*will_quit) (EggSMClient *client,
+ gboolean will_quit);
+ gboolean (*end_session) (EggSMClient *client,
+ EggSMClientEndStyle style,
+ gboolean request_confirmation);
+
+ /* Padding for future expansion */
+ void (*_egg_reserved1) (void);
+ void (*_egg_reserved2) (void);
+ void (*_egg_reserved3) (void);
+ void (*_egg_reserved4) (void);
+};
+
+GType egg_sm_client_get_type (void) G_GNUC_CONST;
+
+/* Resuming a saved session */
+gboolean egg_sm_client_is_resumed (EggSMClient *client);
+GKeyFile *egg_sm_client_get_state_file (EggSMClient *client);
+
+/* Alternate means of saving state */
+void egg_sm_client_set_restart_command (EggSMClient *client,
+ int argc,
+ const char **argv);
+
+/* Handling "quit_requested" signal */
+void egg_sm_client_will_quit (EggSMClient *client,
+ gboolean will_quit);
+
+void egg_sm_client_startup (EggSMClient *client);
+
+/* Initiate a logout/reboot/shutdown */
+gboolean egg_sm_client_end_session (EggSMClientEndStyle style,
+ gboolean request_confirmation);
+
+G_END_DECLS
+
+
+#endif /* __EGG_SM_CLIENT_H__ */
diff --git a/toolkit/src/sugar/env.py b/toolkit/src/sugar/env.py
new file mode 100644
index 0000000..655d18d
--- /dev/null
+++ b/toolkit/src/sugar/env.py
@@ -0,0 +1,65 @@
+"""Calculates file-paths for the Sugar working environment"""
+# Copyright (C) 2006-2007 Red Hat, Inc.
+#
+# 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.
+
+"""
+STABLE.
+"""
+
+import os
+
+
+def is_emulator():
+ if os.environ.has_key('SUGAR_EMULATOR'):
+ if os.environ['SUGAR_EMULATOR'] == 'yes':
+ return True
+ return False
+
+
+def get_profile_path(path=None):
+ if os.environ.has_key('SUGAR_PROFILE'):
+ profile_id = os.environ['SUGAR_PROFILE']
+ else:
+ profile_id = 'default'
+
+ base = os.path.join(os.path.expanduser('~/.sugar'), profile_id)
+ if not os.path.isdir(base):
+ try:
+ os.makedirs(base, 0770)
+ except OSError:
+ print "Could not create user directory."
+
+ if path != None:
+ return os.path.join(base, path)
+ else:
+ return base
+
+
+def get_logs_path(path=None):
+ base = os.environ.get('SUGAR_LOGS_DIR', get_profile_path('logs'))
+ if path != None:
+ return os.path.join(base, path)
+ else:
+ return base
+
+
+def get_user_activities_path():
+ return os.path.expanduser('~/Activities')
+
+
+def get_user_library_path():
+ return os.path.expanduser('~/Library')
diff --git a/toolkit/src/sugar/graphics/Makefile.am b/toolkit/src/sugar/graphics/Makefile.am
new file mode 100644
index 0000000..7334288
--- /dev/null
+++ b/toolkit/src/sugar/graphics/Makefile.am
@@ -0,0 +1,30 @@
+sugardir = $(pythondir)/sugar/graphics
+sugar_PYTHON = \
+ alert.py \
+ animator.py \
+ canvastextview.py \
+ colorbutton.py \
+ combobox.py \
+ entry.py \
+ iconentry.py \
+ icon.py \
+ __init__.py \
+ menuitem.py \
+ notebook.py \
+ objectchooser.py \
+ palettegroup.py \
+ palette.py \
+ palettewindow.py \
+ panel.py \
+ radiopalette.py \
+ radiotoolbutton.py \
+ roundbox.py \
+ style.py \
+ toggletoolbutton.py \
+ toolbarbox.py \
+ toolbox.py \
+ toolbutton.py \
+ toolcombobox.py \
+ tray.py \
+ window.py \
+ xocolor.py
diff --git a/toolkit/src/sugar/graphics/__init__.py b/toolkit/src/sugar/graphics/__init__.py
new file mode 100644
index 0000000..1e7e0f9
--- /dev/null
+++ b/toolkit/src/sugar/graphics/__init__.py
@@ -0,0 +1,18 @@
+"""Graphics/controls for use in Sugar"""
+
+# Copyright (C) 2006-2007, Red Hat, Inc.
+#
+# 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.
diff --git a/toolkit/src/sugar/graphics/alert.py b/toolkit/src/sugar/graphics/alert.py
new file mode 100644
index 0000000..a4dd017
--- /dev/null
+++ b/toolkit/src/sugar/graphics/alert.py
@@ -0,0 +1,479 @@
+"""
+Alerts appear at the top of the body of your activity.
+
+At a high level, Alert and its different variations (TimeoutAlert,
+ConfirmationAlert, etc.) have a title, an alert message and then several
+buttons that the user can click. The Alert class will pass "response" events
+to your activity when any of these buttons are clicked, along with a
+response_id to help you identify what button was clicked.
+
+
+Examples
+--------
+create a simple alert message.
+
+.. code-block:: python
+ from sugar.graphics.alert import Alert
+ ...
+ # Create a new simple alert
+ alert = Alert()
+ # Populate the title and text body of the alert.
+ alert.props.title=_('Title of Alert Goes Here')
+ alert.props.msg = _('Text message of alert goes here')
+ # Call the add_alert() method (inherited via the sugar.graphics.Window
+ # superclass of Activity) to add this alert to the activity window.
+ self.add_alert(alert)
+ alert.show()
+
+STABLE.
+"""
+# Copyright (C) 2007, One Laptop Per Child
+# Copyright (C) 2010, Anish Mangal <anishmangal2002@gmail.com>
+#
+# 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 gettext
+
+import gtk
+import gobject
+import hippo
+import math
+
+from sugar.graphics import style
+from sugar.graphics.icon import Icon
+
+
+_ = lambda msg: gettext.dgettext('sugar-toolkit', msg)
+
+
+class Alert(gtk.EventBox):
+ """
+ UI interface for 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:
+ 'title': the title of the alert,
+ 'message': the message of the alert,
+ 'icon': the icon that appears at the far left
+
+ See __gproperties__
+
+ """
+
+ __gtype_name__ = 'SugarAlert'
+
+ __gsignals__ = {
+ 'response': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([object])),
+ }
+
+ __gproperties__ = {
+ 'title': (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._title = None
+ self._msg = None
+ self._icon = None
+ self._buttons = {}
+
+ self._hbox = gtk.HBox()
+ self._hbox.set_border_width(style.DEFAULT_SPACING)
+ self._hbox.set_spacing(style.DEFAULT_SPACING)
+
+ self._msg_box = gtk.VBox()
+ self._title_label = gtk.Label()
+ self._title_label.set_alignment(0, 0.5)
+ self._msg_box.pack_start(self._title_label, False)
+
+ self._msg_label = gtk.Label()
+ self._msg_label.set_alignment(0, 0.5)
+ self._msg_box.pack_start(self._msg_label, False)
+ self._hbox.pack_start(self._msg_box, False)
+
+ self._buttons_box = gtk.HButtonBox()
+ self._buttons_box.set_layout(gtk.BUTTONBOX_END)
+ self._buttons_box.set_spacing(style.DEFAULT_SPACING)
+ self._hbox.pack_start(self._buttons_box)
+
+ gobject.GObject.__init__(self, **kwargs)
+
+ self.set_visible_window(True)
+ self.add(self._hbox)
+ self._title_label.show()
+ self._msg_label.show()
+ self._buttons_box.show()
+ self._msg_box.show()
+ self._hbox.show()
+ self.show()
+
+ def do_set_property(self, pspec, value):
+ """
+ Set alert property
+
+ Parameters
+ ----------
+ pspec :
+
+ value :
+
+ Returns
+ -------
+ None
+
+ """
+ if pspec.name == 'title':
+ if self._title != value:
+ self._title = value
+ self._title_label.set_markup("<b>" + self._title + "</b>")
+ elif pspec.name == 'msg':
+ if self._msg != value:
+ self._msg = value
+ self._msg_label.set_markup(self._msg)
+ self._msg_label.set_line_wrap(True)
+ 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):
+ """
+ Get alert property
+
+ Parameters
+ ----------
+ pspec :
+ property for which the value will be returned
+
+ Returns
+ -------
+ value of the property specified
+
+ """
+ if pspec.name == 'title':
+ return self._title
+ elif pspec.name == 'msg':
+ return self._msg
+
+ def add_button(self, response_id, label, icon=None, position=-1):
+ """
+ Add a button to the alert
+
+ Parameters
+ ----------
+ response_id :
+ will be emitted with the response signal a response ID should one
+ of the pre-defined GTK Response Type Constants or a positive number
+ label :
+ that will occure right to the buttom
+
+ icon :
+ this can be a SugarIcon or a gtk.Image
+
+ postion :
+ the position of the button in the box (optional)
+
+ Returns
+ -------
+ button :gtk.Button
+
+ """
+ button = gtk.Button()
+ self._buttons[response_id] = button
+ if icon is not None:
+ button.set_image(icon)
+ button.set_label(label)
+ self._buttons_box.pack_start(button)
+ button.show()
+ button.connect('clicked', self.__button_clicked_cb, response_id)
+ if position != -1:
+ self._buttons_box.reorder_child(button, position)
+ return button
+
+ def remove_button(self, response_id):
+ """
+ Remove a button from the alert by the given response id
+
+ Parameters
+ ----------
+ response_id :
+
+ Returns
+ -------
+ None
+
+ """
+ self._buttons_box.remove(self._buttons[response_id])
+
+ def _response(self, response_id):
+ """Emitting response when we have a result
+
+ A result can be that a user has clicked a button or
+ a timeout has occured, the id identifies the button
+ that has been clicked and -1 for a timeout
+ """
+ self.emit('response', response_id)
+
+ def __button_clicked_cb(self, button, response_id):
+ self._response(response_id)
+
+
+class ConfirmationAlert(Alert):
+ """
+ This is a ready-made two button (Cancel,Ok) alert.
+
+ A confirmation alert is a nice shortcut from a standard Alert because it
+ comes with 'OK' and 'Cancel' buttons already built-in. When clicked, the
+ 'OK' button will emit a response with a response_id of gtk.RESPONSE_OK,
+ while the 'Cancel' button will emit gtk.RESPONSE_CANCEL.
+
+ Examples
+ --------
+
+ .. code-block:: python
+ from sugar.graphics.alert import ConfirmationAlert
+ ...
+ #### Method: _alert_confirmation, create a Confirmation alert (with ok
+ and cancel buttons standard)
+ # and add it to the UI.
+ def _alert_confirmation(self):
+ alert = ConfirmationAlert()
+ alert.props.title=_('Title of Alert Goes Here')
+ alert.props.msg = _('Text message of alert goes here')
+ alert.connect('response', self._alert_response_cb)
+ self.add_alert(alert)
+
+
+ #### Method: _alert_response_cb, called when an alert object throws a
+ response event.
+ def _alert_response_cb(self, alert, response_id):
+ #remove the alert from the screen, since either a response button
+ #was clicked or there was a timeout
+ self.remove_alert(alert)
+
+ #Do any work that is specific to the type of button clicked.
+ if response_id is gtk.RESPONSE_OK:
+ print 'Ok Button was clicked. Do any work upon ok here ...'
+ elif response_id is gtk.RESPONSE_CANCEL:
+ print 'Cancel Button was clicked.'
+
+ """
+
+ def __init__(self, **kwargs):
+ Alert.__init__(self, **kwargs)
+
+ icon = Icon(icon_name='dialog-cancel')
+ self.add_button(gtk.RESPONSE_CANCEL, _('Cancel'), icon)
+ icon.show()
+
+ icon = Icon(icon_name='dialog-ok')
+ self.add_button(gtk.RESPONSE_OK, _('Ok'), icon)
+ icon.show()
+
+class ErrorAlert(Alert):
+ """
+ This is a ready-made one button (Ok) alert.
+
+ An error alert is a nice shortcut from a standard Alert because it
+ comes with the 'OK' button already built-in. When clicked, the
+ 'OK' button will emit a response with a response_id of gtk.RESPONSE_OK.
+
+ Examples
+ --------
+
+ .. code-block:: python
+ from sugar.graphics.alert import ErrorAlert
+ ...
+ #### Method: _alert_error, create a Error alert (with ok
+ button standard)
+ # and add it to the UI.
+ def _alert_error(self):
+ alert = ErrorAlert()
+ alert.props.title=_('Title of Alert Goes Here')
+ alert.props.msg = _('Text message of alert goes here')
+ alert.connect('response', self._alert_response_cb)
+ self.add_alert(alert)
+
+
+ #### Method: _alert_response_cb, called when an alert object throws a
+ response event.
+ def _alert_response_cb(self, alert, response_id):
+ #remove the alert from the screen, since either a response button
+ #was clicked or there was a timeout
+ self.remove_alert(alert)
+
+ #Do any work that is specific to the response_id.
+ if response_id is gtk.RESPONSE_OK:
+ print 'Ok Button was clicked. Do any work upon ok here ...'
+
+ """
+
+ def __init__(self, **kwargs):
+ Alert.__init__(self, **kwargs)
+
+ icon = Icon(icon_name='dialog-ok')
+ self.add_button(gtk.RESPONSE_OK, _('Ok'), icon)
+ icon.show()
+
+class _TimeoutIcon(hippo.CanvasText, hippo.CanvasItem):
+ """An icon with a round border"""
+ __gtype_name__ = 'AlertTimeoutIcon'
+
+ def __init__(self, **kwargs):
+ hippo.CanvasText.__init__(self, **kwargs)
+
+ self.props.orientation = hippo.ORIENTATION_HORIZONTAL
+ self.props.border_left = style.DEFAULT_SPACING
+ self.props.border_right = style.DEFAULT_SPACING
+
+ def do_paint_background(self, cr, damaged_box):
+ [width, height] = self.get_allocation()
+
+ xval = width * 0.5
+ yval = height * 0.5
+ radius = min(width * 0.5, height * 0.5)
+
+ hippo.cairo_set_source_rgba32(cr, self.props.background_color)
+ cr.arc(xval, yval, radius, 0, 2*math.pi)
+ cr.fill_preserve()
+
+
+class TimeoutAlert(Alert):
+ """
+ This is a ready-made two button (Cancel,Continue) alert
+
+ It times out with a positive response after the given amount of seconds.
+
+
+ Examples
+ --------
+
+ .. code-block:: python
+ from sugar.graphics.alert import TimeoutAlert
+ ...
+ #### Method: _alert_timeout, create a Timeout alert (with ok and cancel
+ buttons standard)
+ # and add it to the UI.
+ def _alert_timeout(self):
+ #Notice that for a TimeoutAlert, you pass the number of seconds in
+ #which to timeout. By default, this is 5.
+ alert = TimeoutAlert(10)
+ alert.props.title=_('Title of Alert Goes Here')
+ alert.props.msg = _('Text message of timeout alert goes here')
+ alert.connect('response', self._alert_response_cb)
+ self.add_alert(alert)
+
+ #### Method: _alert_response_cb, called when an alert object throws a
+ response event.
+ def _alert_response_cb(self, alert, response_id):
+ #remove the alert from the screen, since either a response button
+ #was clicked or there was a timeout
+ self.remove_alert(alert)
+
+ #Do any work that is specific to the type of button clicked.
+ if response_id is gtk.RESPONSE_OK:
+ print 'Ok Button was clicked. Do any work upon ok here ...'
+ elif response_id is gtk.RESPONSE_CANCEL:
+ print 'Cancel Button was clicked.'
+ elif response_id == -1:
+ print 'Timout occurred'
+
+ """
+
+ def __init__(self, timeout=5, **kwargs):
+ Alert.__init__(self, **kwargs)
+
+ self._timeout = timeout
+
+ icon = Icon(icon_name='dialog-cancel')
+ self.add_button(gtk.RESPONSE_CANCEL, _('Cancel'), icon)
+ icon.show()
+
+ self._timeout_text = _TimeoutIcon(
+ text=self._timeout,
+ color=style.COLOR_BUTTON_GREY.get_int(),
+ background_color=style.COLOR_WHITE.get_int())
+ canvas = hippo.Canvas()
+ canvas.set_root(self._timeout_text)
+ canvas.show()
+ self.add_button(gtk.RESPONSE_OK, _('Continue'), canvas)
+
+ gobject.timeout_add_seconds(1, self.__timeout)
+
+ def __timeout(self):
+ self._timeout -= 1
+ self._timeout_text.props.text = self._timeout
+ if self._timeout == 0:
+ self._response(gtk.RESPONSE_OK)
+ return False
+ return True
+
+
+class NotifyAlert(Alert):
+ """
+ Timeout alert with only an "OK" button - just for notifications
+
+ Examples
+ --------
+
+ .. code-block:: python
+ from sugar.graphics.alert import NotifyAlert
+ ...
+ #### Method: _alert_notify, create a Notify alert (with only an 'OK'
+ button)
+ # and add it to the UI.
+ def _alert_notify(self):
+ #Notice that for a NotifyAlert, you pass the number of seconds in
+ #which to notify. By default, this is 5.
+ alert = NotifyAlert(10)
+ alert.props.title=_('Title of Alert Goes Here')
+ alert.props.msg = _('Text message of notify alert goes here')
+ alert.connect('response', self._alert_response_cb)
+ self.add_alert(alert)
+
+ """
+
+ def __init__(self, timeout=5, **kwargs):
+ Alert.__init__(self, **kwargs)
+
+ self._timeout = timeout
+
+ self._timeout_text = _TimeoutIcon(
+ text=self._timeout,
+ color=style.COLOR_BUTTON_GREY.get_int(),
+ background_color=style.COLOR_WHITE.get_int())
+ canvas = hippo.Canvas()
+ canvas.set_root(self._timeout_text)
+ canvas.show()
+ self.add_button(gtk.RESPONSE_OK, _('Ok'), canvas)
+
+ gobject.timeout_add(1000, self.__timeout)
+
+ def __timeout(self):
+ self._timeout -= 1
+ self._timeout_text.props.text = self._timeout
+ if self._timeout == 0:
+ self._response(gtk.RESPONSE_OK)
+ return False
+ return True
diff --git a/toolkit/src/sugar/graphics/animator.py b/toolkit/src/sugar/graphics/animator.py
new file mode 100644
index 0000000..8fb298b
--- /dev/null
+++ b/toolkit/src/sugar/graphics/animator.py
@@ -0,0 +1,151 @@
+# Copyright (C) 2007, Red Hat, Inc.
+#
+# 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.
+
+"""
+STABLE.
+"""
+
+import time
+
+import gobject
+
+EASE_OUT_EXPO = 0
+EASE_IN_EXPO = 1
+
+
+class Animator(gobject.GObject):
+
+ __gsignals__ = {
+ 'completed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
+ }
+
+ def __init__(self, duration, fps=20, easing=EASE_OUT_EXPO):
+ gobject.GObject.__init__(self)
+ self._animations = []
+ self._duration = duration
+ self._interval = 1.0 / fps
+ self._easing = easing
+ self._timeout_sid = 0
+ self._start_time = None
+
+ def add(self, animation):
+ """
+ Parameter
+ ---------
+ animation :
+
+ """
+ self._animations.append(animation)
+
+ def remove_all(self):
+ """
+ Parameters
+ ----------
+ None :
+
+ Returns
+ -------
+ None :
+
+ """
+ self.stop()
+ self._animations = []
+
+ def start(self):
+ """
+ Parameters
+ ----------
+ None :
+
+ Returns
+ -------
+ None
+
+ """
+ if self._timeout_sid:
+ self.stop()
+
+ self._start_time = time.time()
+ self._timeout_sid = gobject.timeout_add(
+ int(self._interval * 1000), self._next_frame_cb)
+
+ def stop(self):
+ """
+ Parameters
+ ----------
+ None :
+
+ Returns
+ -------
+ None :
+
+ """
+ if self._timeout_sid:
+ gobject.source_remove(self._timeout_sid)
+ self._timeout_sid = 0
+ self.emit('completed')
+
+ def _next_frame_cb(self):
+ current_time = min(self._duration, time.time() - self._start_time)
+ current_time = max(current_time, 0.0)
+
+ for animation in self._animations:
+ animation.do_frame(current_time, self._duration, self._easing)
+
+ if current_time == self._duration:
+ self.stop()
+ return False
+ else:
+ return True
+
+
+class Animation(object):
+
+ def __init__(self, start, end):
+ self.start = start
+ self.end = end
+
+ def do_frame(self, t, duration, easing):
+ """
+ Parameters
+ ----------
+ t:
+
+ duration:
+
+ easing:
+
+ Returns
+ None:
+
+ """
+ start = self.start
+ change = self.end - self.start
+
+ if t == duration:
+ # last frame
+ frame = self.end
+ else:
+ if easing == EASE_OUT_EXPO:
+ frame = change * (-pow(2, -10 * t / duration) + 1) + start
+ elif easing == EASE_IN_EXPO:
+ frame = change * pow(2, 10 * (t / duration - 1)) + start
+
+ self.next_frame(frame)
+
+ def next_frame(self, frame):
+ pass
diff --git a/toolkit/src/sugar/graphics/canvastextview.py b/toolkit/src/sugar/graphics/canvastextview.py
new file mode 100644
index 0000000..853af9f
--- /dev/null
+++ b/toolkit/src/sugar/graphics/canvastextview.py
@@ -0,0 +1,41 @@
+# Copyright (C) 2008 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 gtk
+import hippo
+
+from sugar.graphics import style
+
+
+class CanvasTextView(hippo.CanvasWidget):
+
+ def __init__(self, text, **kwargs):
+ hippo.CanvasWidget.__init__(self, **kwargs)
+ self.text_view_widget = gtk.TextView()
+ self.text_view_widget.props.buffer.props.text = text
+ self.text_view_widget.props.left_margin = style.DEFAULT_SPACING
+ self.text_view_widget.props.right_margin = style.DEFAULT_SPACING
+ self.text_view_widget.props.wrap_mode = gtk.WRAP_WORD
+ self.text_view_widget.show()
+
+ # TODO: These fields should expand vertically instead of scrolling
+ scrolled_window = gtk.ScrolledWindow()
+ scrolled_window.set_shadow_type(gtk.SHADOW_OUT)
+ scrolled_window.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+ scrolled_window.add(self.text_view_widget)
+
+ self.props.widget = scrolled_window
diff --git a/toolkit/src/sugar/graphics/colorbutton.py b/toolkit/src/sugar/graphics/colorbutton.py
new file mode 100644
index 0000000..1fed96d
--- /dev/null
+++ b/toolkit/src/sugar/graphics/colorbutton.py
@@ -0,0 +1,536 @@
+# Copyright (C) 2007, Red Hat, Inc.
+# Copyright (C) 2008, Benjamin Berg <benjamin@sipsolutions.net>
+#
+# 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 gettext
+import gtk
+import gobject
+import struct
+import logging
+
+from sugar.graphics import style
+from sugar.graphics.icon import Icon
+from sugar.graphics.palette import Palette, ToolInvoker, WidgetInvoker
+
+
+_ = lambda msg: gettext.dgettext('sugar-toolkit', msg)
+
+
+def get_svg_color_string(color):
+ return '#%.2X%.2X%.2X' % (color.red / 257, color.green / 257,
+ color.blue / 257)
+
+
+class _ColorButton(gtk.Button):
+ """This is a ColorButton for Sugar. It is similar to the gtk.ColorButton,
+ but does not have any alpha support.
+ Instead of a color selector dialog it will pop up a Sugar palette.
+
+ As a preview an sugar.graphics.Icon is used. The fill color will be set to
+ the current color, and the stroke color is set to the font color.
+ """
+
+ __gtype_name__ = 'SugarColorButton'
+ __gsignals__ = {'color-set': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ tuple())}
+
+ def __init__(self, **kwargs):
+ self._title = _('Choose a color')
+ self._color = gtk.gdk.Color(0, 0, 0)
+ self._has_palette = True
+ self._has_invoker = True
+ self._palette = None
+ self._accept_drag = True
+
+ self._preview = Icon(icon_name='color-preview',
+ icon_size=gtk.ICON_SIZE_BUTTON)
+
+ gobject.GObject.__init__(self, **kwargs)
+
+ if self._accept_drag:
+ self.drag_dest_set(gtk.DEST_DEFAULT_MOTION |
+ gtk.DEST_DEFAULT_HIGHLIGHT |
+ gtk.DEST_DEFAULT_DROP,
+ [('application/x-color', 0, 0)],
+ gtk.gdk.ACTION_COPY)
+ self.drag_source_set(gtk.gdk.BUTTON1_MASK | gtk.gdk.BUTTON3_MASK,
+ [('application/x-color', 0, 0)],
+ gtk.gdk.ACTION_COPY)
+ self.connect('drag_data_received', self.__drag_data_received_cb)
+ self.connect('drag_data_get', self.__drag_data_get_cb)
+
+ self._preview.fill_color = get_svg_color_string(self._color)
+ self._preview.stroke_color = \
+ get_svg_color_string(self.style.fg[gtk.STATE_NORMAL])
+ self.set_image(self._preview)
+
+ if self._has_palette and self._has_invoker:
+ self._invoker = WidgetInvoker(self)
+ # FIXME: This is a hack.
+ self._invoker.has_rectangle_gap = lambda: False
+ self._invoker.palette = self._palette
+
+ def create_palette(self):
+ if self._has_palette:
+ self._palette = _ColorPalette(color=self._color,
+ primary_text=self._title)
+ self._palette.connect('color-set', self.__palette_color_set_cb)
+ self._palette.connect('notify::color', self.
+ __palette_color_changed)
+
+ return self._palette
+
+ def __palette_color_set_cb(self, palette):
+ self.emit('color-set')
+
+ def __palette_color_changed(self, palette, pspec):
+ self.color = self._palette.color
+
+ def do_style_set(self, previous_style):
+ self._preview.stroke_color = \
+ get_svg_color_string(self.style.fg[gtk.STATE_NORMAL])
+
+ def do_clicked(self):
+ if self._palette:
+ if not self._palette.is_up():
+ self._palette.popup(immediate=True,
+ state=self._palette.SECONDARY)
+ else:
+ self._palette.popdown(immediate=True)
+ return True
+
+ def set_color(self, color):
+ assert isinstance(color, gtk.gdk.Color)
+
+ if self._color.red == color.red and \
+ self._color.green == color.green and \
+ self._color.blue == color.blue:
+ return
+
+ self._color = gtk.gdk.Color(color.red, color.green, color.blue)
+ self._preview.fill_color = get_svg_color_string(self._color)
+ if self._palette:
+ self._palette.props.color = self._color
+ self.notify('color')
+
+ def get_color(self):
+ return self._color
+
+ color = gobject.property(type=object, getter=get_color, setter=set_color)
+
+ def set_icon_name(self, icon_name):
+ self._preview.props.icon_name = icon_name
+
+ def get_icon_name(self):
+ return self._preview.props.icon_name
+
+ icon_name = gobject.property(type=str,
+ getter=get_icon_name, setter=set_icon_name)
+
+ def set_icon_size(self, icon_size):
+ self._preview.props.icon_size = icon_size
+
+ def get_icon_size(self):
+ return self._preview.props.icon_size
+
+ icon_size = gobject.property(type=int,
+ getter=get_icon_size, setter=set_icon_size)
+
+ def set_title(self, title):
+ self._title = title
+ if self._palette:
+ self._palette.primary_text = self._title
+
+ def get_title(self):
+ return self._title
+
+ title = gobject.property(type=str, getter=get_title, setter=set_title)
+
+ def _set_has_invoker(self, has_invoker):
+ self._has_invoker = has_invoker
+
+ def _get_has_invoker(self):
+ return self._has_invoker
+
+ has_invoker = gobject.property(type=bool, default=True,
+ flags=gobject.PARAM_READWRITE |
+ gobject.PARAM_CONSTRUCT_ONLY,
+ getter=_get_has_invoker,
+ setter=_set_has_invoker)
+
+ def _set_has_palette(self, has_palette):
+ self._has_palette = has_palette
+
+ def _get_has_palette(self):
+ return self._has_palette
+
+ has_palette = gobject.property(type=bool, default=True,
+ flags=gobject.PARAM_READWRITE |
+ gobject.PARAM_CONSTRUCT_ONLY,
+ getter=_get_has_palette,
+ setter=_set_has_palette)
+
+ def _set_accept_drag(self, accept_drag):
+ self._accept_drag = accept_drag
+
+ def _get_accept_drag(self):
+ return self._accept_drag
+
+ accept_drag = gobject.property(type=bool, default=True,
+ flags=gobject.PARAM_READWRITE |
+ gobject.PARAM_CONSTRUCT_ONLY,
+ getter=_get_accept_drag,
+ setter=_set_accept_drag)
+
+ def __drag_begin_cb(self, widget, context):
+ # Drag and Drop
+ pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8,
+ style.SMALL_ICON_SIZE,
+ style.SMALL_ICON_SIZE)
+
+ red = self._color.red / 257
+ green = self._color.green / 257
+ blue = self._color.blue / 257
+
+ pixbuf.fill(red << 24 + green << 16 + blue << 8 + 0xff)
+
+ context.set_icon_pixbuf(pixbuf)
+
+ def __drag_data_get_cb(self, widget, context, selection_data, info, time):
+ data = struct.pack('=HHHH', self._color.red, self._color.green,
+ self._color.blue, 65535)
+ selection_data.set(selection_data.target, 16, data)
+
+ def __drag_data_received_cb(self, widget, context, x, y, selection_data, \
+ info, time):
+ if len(selection_data.data) != 8:
+ return
+
+ dropped = selection_data.data
+ red = struct.unpack_from('=H', dropped, 0)[0]
+ green = struct.unpack_from('=H', dropped, 2)[0]
+ blue = struct.unpack_from('=H', dropped, 4)[0]
+ # dropped[6] and dropped[7] is alpha, but we ignore the alpha channel
+
+ color = gtk.gdk.Color(red, green, blue)
+ self.set_color(color)
+
+
+class _ColorPalette(Palette):
+ """This is a color picker palette. It will usually be used indirectly
+ trough a sugar.graphics.ColorButton.
+ """
+ _RED = 0
+ _GREEN = 1
+ _BLUE = 2
+
+ __gtype_name__ = 'SugarColorPalette'
+
+ # The color-set signal is emitted when the user is finished selecting
+ # a color.
+ __gsignals__ = {'color-set': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ tuple())}
+
+ def __init__(self, **kwargs):
+ self._color = gtk.gdk.Color(0, 0, 0)
+ self._previous_color = self._color.copy()
+ self._scales = None
+
+ Palette.__init__(self, **kwargs)
+
+ self.connect('popup', self.__popup_cb)
+ self.connect('popdown', self.__popdown_cb)
+
+ self._picker_hbox = gtk.HBox()
+ self.set_content(self._picker_hbox)
+
+ self._swatch_tray = gtk.Table()
+
+ self._picker_hbox.pack_start(self._swatch_tray)
+ self._picker_hbox.pack_start(gtk.VSeparator(),
+ padding=style.DEFAULT_SPACING)
+
+ self._chooser_table = gtk.Table(3, 2)
+ self._chooser_table.set_col_spacing(0, style.DEFAULT_PADDING)
+
+ self._scales = []
+ self._scales.append(
+ self._create_color_scale(_('Red'), self._RED, 0))
+ self._scales.append(
+ self._create_color_scale(_('Green'), self._GREEN, 1))
+ self._scales.append(
+ self._create_color_scale(_('Blue'), self._BLUE, 2))
+
+ self._picker_hbox.add(self._chooser_table)
+
+ self._picker_hbox.show_all()
+
+ self._build_swatches()
+
+ def _create_color_scale(self, text, color, row):
+ label = gtk.Label(text)
+ label.props.xalign = 1.0
+ scale = gtk.HScale()
+ scale.set_size_request(style.zoom(250), -1)
+ scale.set_draw_value(False)
+ scale.set_range(0, 1.0)
+ scale.set_increments(0.1, 0.2)
+
+ if color == self._RED:
+ scale.set_value(self._color.red / 65535.0)
+ elif color == self._GREEN:
+ scale.set_value(self._color.green / 65535.0)
+ elif color == self._BLUE:
+ scale.set_value(self._color.blue / 65535.0)
+
+ scale.connect('value-changed',
+ self.__scale_value_changed_cb,
+ color)
+ self._chooser_table.attach(label, 0, 1, row, row + 1)
+ self._chooser_table.attach(scale, 1, 2, row, row + 1)
+
+ return scale
+
+ def _build_swatches(self):
+ for child in self._swatch_tray.get_children():
+ child.destroy()
+
+ # Use a hardcoded list of colors for now.
+ colors = ['#ed2529', '#69bc47', '#3c54a3',
+ '#f57f25', '#0b6b3a', '#00a0c6',
+ '#f6eb1a', '#b93f94', '#5b4a9c',
+ '#000000', '#919496', '#ffffff']
+
+ # We want 3 rows of colors.
+ rows = 3
+ i = 0
+ self._swatch_tray.props.n_rows = rows
+ self._swatch_tray.props.n_columns = (len(colors) + rows - 1) / rows
+ for color in colors:
+ button = _ColorButton(has_palette=False,
+ color=gtk.gdk.color_parse(color),
+ accept_drag=False,
+ icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR)
+ button.set_relief(gtk.RELIEF_NONE)
+ self._swatch_tray.attach(button,
+ i % rows, i % rows + 1,
+ i / rows, i / rows + 1,
+ yoptions=0, xoptions=0)
+ button.connect('clicked', self.__swatch_button_clicked_cb)
+ i += 1
+
+ self._swatch_tray.show_all()
+
+ def __popup_cb(self, palette):
+ self._previous_color = self._color.copy()
+
+ def __popdown_cb(self, palette):
+ self.emit('color-set')
+
+ def __scale_value_changed_cb(self, widget, color):
+ new_color = self._color.copy()
+ if color == self._RED:
+ new_color.red = int(65535 * widget.get_value())
+ elif color == self._GREEN:
+ new_color.green = int(65535 * widget.get_value())
+ elif color == self._BLUE:
+ new_color.blue = int(65535 * widget.get_value())
+ self.color = new_color
+
+ def do_key_press_event(self, event):
+ if event.keyval == gtk.keysyms.Escape:
+ self.props.color = self._previous_color
+ self.popdown(immediate=True)
+ return True
+ elif event.keyval == gtk.keysyms.Return:
+ self.popdown(immediate=True)
+ return True
+ return False
+
+ def __swatch_button_clicked_cb(self, button):
+ self.props.color = button.get_color()
+
+ def set_color(self, color):
+ assert isinstance(color, gtk.gdk.Color)
+
+ if self._color.red == color.red and \
+ self._color.green == color.green and \
+ self._color.blue == color.blue:
+ return
+
+ self._color = color.copy()
+
+ if self._scales:
+ self._scales[self._RED].set_value(self._color.red / 65535.0)
+ self._scales[self._GREEN].set_value(self._color.green / 65535.0)
+ self._scales[self._BLUE].set_value(self._color.blue / 65535.0)
+
+ self.notify('color')
+
+ def get_color(self):
+ return self._color
+
+ color = gobject.property(type=object, getter=get_color, setter=set_color)
+
+
+def _add_accelerator(tool_button):
+ if not tool_button.props.accelerator or not tool_button.get_toplevel() or \
+ not tool_button.child:
+ return
+
+ # TODO: should we remove the accelerator from the prev top level?
+
+ accel_group = tool_button.get_toplevel().get_data('sugar-accel-group')
+ if not accel_group:
+ logging.warning('No gtk.AccelGroup in the top level window.')
+ return
+
+ keyval, mask = gtk.accelerator_parse(tool_button.props.accelerator)
+ # the accelerator needs to be set at the child, so the gtk.AccelLabel
+ # in the palette can pick it up.
+ tool_button.child.add_accelerator('clicked', accel_group, keyval, mask,
+ gtk.ACCEL_LOCKED | gtk.ACCEL_VISIBLE)
+
+
+def _hierarchy_changed_cb(tool_button, previous_toplevel):
+ _add_accelerator(tool_button)
+
+
+def setup_accelerator(tool_button):
+ _add_accelerator(tool_button)
+ tool_button.connect('hierarchy-changed', _hierarchy_changed_cb)
+
+
+class ColorToolButton(gtk.ToolItem):
+ # This not ideal. It would be better to subclass gtk.ToolButton, however
+ # the python bindings do not seem to be powerfull enough for that.
+ # (As we need to change a variable in the class structure.)
+
+ __gtype_name__ = 'SugarColorToolButton'
+ __gsignals__ = {'color-set': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ tuple())}
+
+ def __init__(self, icon_name='color-preview', **kwargs):
+ self._accelerator = None
+ self._tooltip = None
+ self._palette_invoker = ToolInvoker()
+ self._palette = None
+
+ gobject.GObject.__init__(self, **kwargs)
+
+ # The gtk.ToolButton has already added a normal button.
+ # Replace it with a ColorButton
+ color_button = _ColorButton(icon_name=icon_name, has_invoker=False)
+ self.add(color_button)
+
+ # The following is so that the behaviour on the toolbar is correct.
+ color_button.set_relief(gtk.RELIEF_NONE)
+ color_button.icon_size = gtk.ICON_SIZE_LARGE_TOOLBAR
+
+ self._palette_invoker.attach_tool(self)
+
+ # This widget just proxies the following properties to the colorbutton
+ color_button.connect('notify::color', self.__notify_change)
+ color_button.connect('notify::icon-name', self.__notify_change)
+ color_button.connect('notify::icon-size', self.__notify_change)
+ color_button.connect('notify::title', self.__notify_change)
+ color_button.connect('color-set', self.__color_set_cb)
+ color_button.connect('can-activate-accel',
+ self.__button_can_activate_accel_cb)
+
+ def __button_can_activate_accel_cb(self, button, signal_id):
+ # Accept activation via accelerators regardless of this widget's state
+ return True
+
+ def set_accelerator(self, accelerator):
+ self._accelerator = accelerator
+ setup_accelerator(self)
+
+ def get_accelerator(self):
+ return self._accelerator
+
+ accelerator = gobject.property(type=str, setter=set_accelerator,
+ getter=get_accelerator)
+
+ def create_palette(self):
+ self._palette = self.get_child().create_palette()
+ return self._palette
+
+ def get_palette_invoker(self):
+ return self._palette_invoker
+
+ def set_palette_invoker(self, palette_invoker):
+ self._palette_invoker.detach()
+ self._palette_invoker = palette_invoker
+
+ palette_invoker = gobject.property(
+ type=object, setter=set_palette_invoker, getter=get_palette_invoker)
+
+ def set_color(self, color):
+ self.get_child().props.color = color
+
+ def get_color(self):
+ return self.get_child().props.color
+
+ color = gobject.property(type=object, getter=get_color, setter=set_color)
+
+ def set_icon_name(self, icon_name):
+ self.get_child().props.icon_name = icon_name
+
+ def get_icon_name(self):
+ return self.get_child().props.icon_name
+
+ icon_name = gobject.property(type=str,
+ getter=get_icon_name, setter=set_icon_name)
+
+ def set_icon_size(self, icon_size):
+ self.get_child().props.icon_size = icon_size
+
+ def get_icon_size(self):
+ return self.get_child().props.icon_size
+
+ icon_size = gobject.property(type=int,
+ getter=get_icon_size, setter=set_icon_size)
+
+ def set_title(self, title):
+ self.get_child().props.title = title
+
+ def get_title(self):
+ return self.get_child().props.title
+
+ title = gobject.property(type=str, getter=get_title, setter=set_title)
+
+ def do_expose_event(self, event):
+ child = self.get_child()
+ allocation = self.get_allocation()
+ if self._palette and self._palette.is_up():
+ invoker = self._palette.props.invoker
+ invoker.draw_rectangle(event, self._palette)
+ elif child.state == gtk.STATE_PRELIGHT:
+ child.style.paint_box(event.window, gtk.STATE_PRELIGHT,
+ gtk.SHADOW_NONE, event.area,
+ child, 'toolbutton-prelight',
+ allocation.x, allocation.y,
+ allocation.width, allocation.height)
+
+ gtk.ToolButton.do_expose_event(self, event)
+
+ def __notify_change(self, widget, pspec):
+ self.notify(pspec.name)
+
+ def __color_set_cb(self, widget):
+ self.emit('color-set')
diff --git a/toolkit/src/sugar/graphics/combobox.py b/toolkit/src/sugar/graphics/combobox.py
new file mode 100644
index 0000000..bc759c2
--- /dev/null
+++ b/toolkit/src/sugar/graphics/combobox.py
@@ -0,0 +1,170 @@
+# 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.
+
+"""
+STABLE.
+"""
+
+import gobject
+import gtk
+
+
+class ComboBox(gtk.ComboBox):
+
+ __gtype_name__ = 'SugarComboBox'
+
+ def __init__(self):
+ gtk.ComboBox.__init__(self)
+
+ self._text_renderer = None
+ self._icon_renderer = None
+
+ self._model = gtk.ListStore(gobject.TYPE_PYOBJECT,
+ gobject.TYPE_STRING,
+ gtk.gdk.Pixbuf,
+ gobject.TYPE_BOOLEAN)
+ self.set_model(self._model)
+
+ self.set_row_separator_func(self._is_separator)
+
+ def get_value(self):
+ """
+ Parameters
+ ----------
+ None :
+
+ Returns:
+ --------
+ value :
+
+ """
+ row = self.get_active_item()
+ if not row:
+ return None
+ return row[0]
+
+ value = gobject.property(
+ type=object, getter=get_value, setter=None)
+
+ def _get_real_name_from_theme(self, name, size):
+ icon_theme = gtk.icon_theme_get_default()
+ width, height = gtk.icon_size_lookup(size)
+ info = icon_theme.lookup_icon(name, max(width, height), 0)
+ if not info:
+ raise ValueError("Icon '" + name + "' not found.")
+ fname = info.get_filename()
+ del info
+ return fname
+
+ def append_item(self, action_id, text, icon_name=None, file_name=None):
+ """
+ Parameters
+ ----------
+ action_id :
+
+ text :
+
+ icon_name=None :
+
+ file_name=None :
+
+ Returns
+ -------
+ None
+
+ """
+ if not self._icon_renderer and (icon_name or file_name):
+ self._icon_renderer = gtk.CellRendererPixbuf()
+
+ settings = self.get_settings()
+ w, h = gtk.icon_size_lookup_for_settings(
+ settings, gtk.ICON_SIZE_MENU)
+ self._icon_renderer.props.stock_size = max(w, h)
+
+ self.pack_start(self._icon_renderer, False)
+ self.add_attribute(self._icon_renderer, 'pixbuf', 2)
+
+ if not self._text_renderer and text:
+ self._text_renderer = gtk.CellRendererText()
+ self.pack_end(self._text_renderer, True)
+ self.add_attribute(self._text_renderer, 'text', 1)
+
+ if icon_name or file_name:
+ if text:
+ size = gtk.ICON_SIZE_MENU
+ else:
+ size = gtk.ICON_SIZE_LARGE_TOOLBAR
+ width, height = gtk.icon_size_lookup(size)
+
+ if icon_name:
+ file_name = self._get_real_name_from_theme(icon_name, size)
+
+ pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(
+ file_name, width, height)
+ else:
+ pixbuf = None
+
+ self._model.append([action_id, text, pixbuf, False])
+
+ def append_separator(self):
+ """
+ Parameters
+ ----------
+ None
+
+ Returns
+ -------
+ None
+
+ """
+ self._model.append([0, None, None, True])
+
+ def get_active_item(self):
+ """
+ Parameters
+ ----------
+ None
+
+ Returns
+ -------
+ Active_item :
+
+ """
+ index = self.get_active()
+ if index == -1:
+ index = 0
+
+ row = self._model.iter_nth_child(None, index)
+ if not row:
+ return None
+ return self._model[row]
+
+ def remove_all(self):
+ """
+ Parameters
+ ----------
+ None
+
+ Returns
+ -------
+ None
+
+ """
+ self._model.clear()
+
+ def _is_separator(self, model, row):
+ return model[row][3]
diff --git a/toolkit/src/sugar/graphics/entry.py b/toolkit/src/sugar/graphics/entry.py
new file mode 100644
index 0000000..6afafa0
--- /dev/null
+++ b/toolkit/src/sugar/graphics/entry.py
@@ -0,0 +1,41 @@
+# Copyright (C) 2007, Red Hat, Inc.
+#
+# 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.
+
+"""
+STABLE.
+"""
+
+import gtk
+import hippo
+
+
+class CanvasEntry(hippo.CanvasEntry):
+
+ def set_background(self, color_spec):
+ """
+ Parameters
+ ----------
+ color_spec :
+
+ Returns
+ -------
+ None
+
+ """
+ color = gtk.gdk.color_parse(color_spec)
+ self.props.widget.modify_bg(gtk.STATE_INSENSITIVE, color)
+ self.props.widget.modify_base(gtk.STATE_INSENSITIVE, color)
diff --git a/toolkit/src/sugar/graphics/icon.py b/toolkit/src/sugar/graphics/icon.py
new file mode 100644
index 0000000..4a94479
--- /dev/null
+++ b/toolkit/src/sugar/graphics/icon.py
@@ -0,0 +1,1176 @@
+# Copyright (C) 2006-2007 Red Hat, Inc.
+#
+# 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.
+
+"""
+A small fixed size picture, typically used to decorate components.
+
+STABLE.
+"""
+
+import re
+import math
+import logging
+
+import gobject
+import gtk
+import hippo
+import cairo
+
+from sugar.graphics.xocolor import XoColor
+from sugar.util import LRU
+
+
+_BADGE_SIZE = 0.45
+
+
+class _SVGLoader(object):
+
+ def __init__(self):
+ self._cache = LRU(50)
+
+ def load(self, file_name, entities, cache):
+ if file_name in self._cache:
+ icon = self._cache[file_name]
+ else:
+ icon_file = open(file_name, 'r')
+ icon = icon_file.read()
+ icon_file.close()
+
+ if cache:
+ self._cache[file_name] = icon
+
+ for entity, value in entities.items():
+ if isinstance(value, basestring):
+ xml = '<!ENTITY %s "%s">' % (entity, value)
+ icon = re.sub('<!ENTITY %s .*>' % entity, xml, icon)
+ else:
+ logging.error(
+ 'Icon %s, entity %s is invalid.', file_name, entity)
+
+ import rsvg # XXX this is very slow! why?
+ return rsvg.Handle(data=icon)
+
+
+class _IconInfo(object):
+
+ def __init__(self):
+ self.file_name = None
+ self.attach_x = 0
+ self.attach_y = 0
+
+
+class _BadgeInfo(object):
+
+ def __init__(self):
+ self.attach_x = 0
+ self.attach_y = 0
+ self.size = 0
+ self.icon_padding = 0
+
+
+class _IconBuffer(object):
+
+ _surface_cache = LRU(50)
+ _loader = _SVGLoader()
+
+ def __init__(self):
+ self.icon_name = None
+ self.icon_size = None
+ self.file_name = None
+ self.fill_color = None
+ self.background_color = None
+ self.stroke_color = None
+ self.badge_name = None
+ self.width = None
+ self.height = None
+ self.cache = False
+ self.scale = 1.0
+
+ def _get_cache_key(self, sensitive):
+ if self.background_color is None:
+ color = None
+ else:
+ color = (self.background_color.red, self.background_color.green,
+ self.background_color.blue)
+ return (self.icon_name, self.file_name, self.fill_color,
+ self.stroke_color, self.badge_name, self.width, self.height,
+ color, sensitive)
+
+ def _load_svg(self, file_name):
+ entities = {}
+ if self.fill_color:
+ entities['fill_color'] = self.fill_color
+ if self.stroke_color:
+ entities['stroke_color'] = self.stroke_color
+
+ return self._loader.load(file_name, entities, self.cache)
+
+ def _get_attach_points(self, info, size_request):
+ attach_points = info.get_attach_points()
+
+ if attach_points:
+ attach_x = float(attach_points[0][0]) / size_request
+ attach_y = float(attach_points[0][1]) / size_request
+ else:
+ attach_x = attach_y = 0
+
+ return attach_x, attach_y
+
+ def _get_icon_info(self):
+ icon_info = _IconInfo()
+
+ if self.file_name:
+ icon_info.file_name = self.file_name
+ elif self.icon_name:
+ theme = gtk.icon_theme_get_default()
+
+ size = 50
+ if self.width != None:
+ size = self.width
+
+ info = theme.lookup_icon(self.icon_name, size, 0)
+ if info:
+ attach_x, attach_y = self._get_attach_points(info, size)
+
+ icon_info.file_name = info.get_filename()
+ icon_info.attach_x = attach_x
+ icon_info.attach_y = attach_y
+
+ del info
+ else:
+ logging.warning('No icon with the name %s was found in the '
+ 'theme.', self.icon_name)
+
+ return icon_info
+
+ def _draw_badge(self, context, size, sensitive, widget):
+ theme = gtk.icon_theme_get_default()
+ badge_info = theme.lookup_icon(self.badge_name, size, 0)
+ if badge_info:
+ badge_file_name = badge_info.get_filename()
+ if badge_file_name.endswith('.svg'):
+ handle = self._loader.load(badge_file_name, {}, self.cache)
+
+ dimensions = handle.get_dimension_data()
+ icon_width = int(dimensions[0])
+ icon_height = int(dimensions[1])
+
+ pixbuf = handle.get_pixbuf()
+ else:
+ pixbuf = gtk.gdk.pixbuf_new_from_file(badge_file_name)
+
+ icon_width = pixbuf.get_width()
+ icon_height = pixbuf.get_height()
+
+ context.scale(float(size) / icon_width,
+ float(size) / icon_height)
+
+ if not sensitive:
+ pixbuf = self._get_insensitive_pixbuf(pixbuf, widget)
+ surface = hippo.cairo_surface_from_gdk_pixbuf(pixbuf)
+ context.set_source_surface(surface, 0, 0)
+ context.paint()
+
+ def _get_size(self, icon_width, icon_height, padding):
+ if self.width is not None and self.height is not None:
+ width = self.width + padding
+ height = self.height + padding
+ else:
+ width = icon_width + padding
+ height = icon_height + padding
+
+ return width, height
+
+ def _get_badge_info(self, icon_info, icon_width, icon_height):
+ info = _BadgeInfo()
+ if self.badge_name is None:
+ return info
+
+ info.size = int(_BADGE_SIZE * icon_width)
+ info.attach_x = int(icon_info.attach_x * icon_width - info.size / 2)
+ info.attach_y = int(icon_info.attach_y * icon_height - info.size / 2)
+
+ if info.attach_x < 0 or info.attach_y < 0:
+ info.icon_padding = max(-info.attach_x, -info.attach_y)
+ elif info.attach_x + info.size > icon_width or \
+ info.attach_y + info.size > icon_height:
+ x_padding = info.attach_x + info.size - icon_width
+ y_padding = info.attach_y + info.size - icon_height
+ info.icon_padding = max(x_padding, y_padding)
+
+ return info
+
+ def _get_xo_color(self):
+ if self.stroke_color and self.fill_color:
+ return XoColor('%s,%s' % (self.stroke_color, self.fill_color))
+ else:
+ return None
+
+ def _set_xo_color(self, xo_color):
+ if xo_color:
+ self.stroke_color = xo_color.get_stroke_color()
+ self.fill_color = xo_color.get_fill_color()
+ else:
+ self.stroke_color = None
+ self.fill_color = None
+
+ def _get_insensitive_pixbuf(self, pixbuf, widget):
+ if not (widget and widget.style):
+ return pixbuf
+
+ icon_source = gtk.IconSource()
+ # Special size meaning "don't touch"
+ icon_source.set_size(-1)
+ icon_source.set_pixbuf(pixbuf)
+ icon_source.set_state(gtk.STATE_INSENSITIVE)
+ icon_source.set_direction_wildcarded(False)
+ icon_source.set_size_wildcarded(False)
+
+ # Please note that the pixbuf returned by this function is leaked
+ # with current stable versions of pygtk. The relevant bug is
+ # http://bugzilla.gnome.org/show_bug.cgi?id=502871
+ # -- 2007-12-14 Benjamin Berg
+ pixbuf = widget.style.render_icon(icon_source, widget.get_direction(),
+ gtk.STATE_INSENSITIVE, -1, widget,
+ "sugar-icon")
+
+ return pixbuf
+
+ def get_surface(self, sensitive=True, widget=None):
+ cache_key = self._get_cache_key(sensitive)
+ if cache_key in self._surface_cache:
+ return self._surface_cache[cache_key]
+
+ icon_info = self._get_icon_info()
+ if icon_info.file_name is None:
+ return None
+
+ is_svg = icon_info.file_name.endswith('.svg')
+
+ if is_svg:
+ handle = self._load_svg(icon_info.file_name)
+ dimensions = handle.get_dimension_data()
+ icon_width = int(dimensions[0])
+ icon_height = int(dimensions[1])
+ else:
+ pixbuf = gtk.gdk.pixbuf_new_from_file(icon_info.file_name)
+ icon_width = pixbuf.get_width()
+ icon_height = pixbuf.get_height()
+
+ badge_info = self._get_badge_info(icon_info, icon_width, icon_height)
+
+ padding = badge_info.icon_padding
+ width, height = self._get_size(icon_width, icon_height, padding)
+ if self.background_color is None:
+ surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
+ context = cairo.Context(surface)
+ else:
+ surface = cairo.ImageSurface(cairo.FORMAT_RGB24, width, height)
+ context = cairo.Context(surface)
+ context = gtk.gdk.CairoContext(context)
+ context.set_source_color(self.background_color)
+ context.paint()
+
+ context.scale(float(width) / (icon_width + padding * 2),
+ float(height) / (icon_height + padding * 2))
+ context.save()
+
+ context.translate(padding, padding)
+ if is_svg:
+ if sensitive:
+ handle.render_cairo(context)
+ else:
+ pixbuf = handle.get_pixbuf()
+ pixbuf = self._get_insensitive_pixbuf(pixbuf, widget)
+
+ pixbuf_surface = hippo.cairo_surface_from_gdk_pixbuf(pixbuf)
+ context.set_source_surface(pixbuf_surface, 0, 0)
+ context.paint()
+ else:
+ if not sensitive:
+ pixbuf = self._get_insensitive_pixbuf(pixbuf, widget)
+ pixbuf_surface = hippo.cairo_surface_from_gdk_pixbuf(pixbuf)
+ context.set_source_surface(pixbuf_surface, 0, 0)
+ context.paint()
+
+ if self.badge_name:
+ context.restore()
+ context.translate(badge_info.attach_x, badge_info.attach_y)
+ self._draw_badge(context, badge_info.size, sensitive, widget)
+
+ self._surface_cache[cache_key] = surface
+
+ return surface
+
+ xo_color = property(_get_xo_color, _set_xo_color)
+
+
+class Icon(gtk.Image):
+
+ __gtype_name__ = 'SugarIcon'
+
+ def __init__(self, **kwargs):
+ self._buffer = _IconBuffer()
+ # HACK: need to keep a reference to the path so it doesn't get garbage
+ # collected while it's still used if it's a sugar.util.TempFilePath.
+ # See #1175
+ self._file = None
+
+ gobject.GObject.__init__(self, **kwargs)
+
+ def get_file(self):
+ return self._file
+
+ def set_file(self, file_name):
+ self._file = file_name
+ self._buffer.file_name = file_name
+
+ file = gobject.property(type=object, setter=set_file, getter=get_file)
+
+ def _sync_image_properties(self):
+ if self._buffer.icon_name != self.props.icon_name:
+ self._buffer.icon_name = self.props.icon_name
+
+ if self._buffer.file_name != self.props.file:
+ self._buffer.file_name = self.props.file
+
+ if self.props.pixel_size == -1:
+ width, height = gtk.icon_size_lookup(self.props.icon_size)
+ else:
+ width = height = self.props.pixel_size
+ if self._buffer.width != width or self._buffer.height != height:
+ self._buffer.width = width
+ self._buffer.height = height
+
+ def _icon_size_changed_cb(self, image, pspec):
+ self._buffer.icon_size = self.props.icon_size
+
+ def _icon_name_changed_cb(self, image, pspec):
+ self._buffer.icon_name = self.props.icon_name
+
+ def _file_changed_cb(self, image, pspec):
+ self._buffer.file_name = self.props.file
+
+ def do_size_request(self, requisition):
+ """
+ Parameters
+ ----------
+ requisition :
+
+ Returns
+ -------
+ None
+
+ """
+ self._sync_image_properties()
+ surface = self._buffer.get_surface()
+ if surface:
+ requisition[0] = surface.get_width()
+ requisition[1] = surface.get_height()
+ elif self._buffer.width and self._buffer.height:
+ requisition[0] = self._buffer.width
+ requisition[1] = self._buffer.width
+ else:
+ requisition[0] = requisition[1] = 0
+
+ def do_expose_event(self, event):
+ """
+ Parameters
+ ----------
+ event :
+
+ Returns:
+ --------
+ None
+
+ """
+ self._sync_image_properties()
+ sensitive = (self.state != gtk.STATE_INSENSITIVE)
+ surface = self._buffer.get_surface(sensitive, self)
+ if surface is None:
+ return
+
+ xpad, ypad = self.get_padding()
+ xalign, yalign = self.get_alignment()
+ requisition = self.get_child_requisition()
+ if self.get_direction() != gtk.TEXT_DIR_LTR:
+ xalign = 1.0 - xalign
+
+ allocation = self.get_allocation()
+ x = math.floor(allocation.x + xpad +
+ (allocation.width - requisition[0]) * xalign)
+ y = math.floor(allocation.y + ypad +
+ (allocation.height - requisition[1]) * yalign)
+
+ cr = self.window.cairo_create()
+ cr.set_source_surface(surface, x, y)
+ cr.paint()
+
+ def set_xo_color(self, value):
+ """
+ Parameters
+ ----------
+ value :
+
+ Returns
+ -------
+ None
+
+ """
+ if self._buffer.xo_color != value:
+ self._buffer.xo_color = value
+ self.queue_draw()
+
+ xo_color = gobject.property(
+ type=object, getter=None, setter=set_xo_color)
+
+ def set_fill_color(self, value):
+ """
+ Parameters
+ ----------
+ value :
+
+ Returns
+ -------
+ None
+
+ """
+ if self._buffer.fill_color != value:
+ self._buffer.fill_color = value
+ self.queue_draw()
+
+ def get_fill_color(self):
+ """
+ Parameters
+ ----------
+ None
+
+ Returns
+ -------
+ fill_color :
+
+ """
+ return self._buffer.fill_color
+
+ fill_color = gobject.property(
+ type=object, getter=get_fill_color, setter=set_fill_color)
+
+ def set_stroke_color(self, value):
+ """
+ Parameters
+ ----------
+ value :
+
+ Returns
+ -------
+ None
+
+ """
+ if self._buffer.stroke_color != value:
+ self._buffer.stroke_color = value
+ self.queue_draw()
+
+ def get_stroke_color(self):
+ """
+ Parameters
+ ----------
+ None
+
+ Returns
+ -------
+ stroke_color :
+
+ """
+ return self._buffer.stroke_color
+
+ stroke_color = gobject.property(
+ type=object, getter=get_stroke_color, setter=set_stroke_color)
+
+ def set_badge_name(self, value):
+ """
+ Parameters
+ ----------
+ value:
+
+ Returns
+ -------
+ None
+
+ """
+ if self._buffer.badge_name != value:
+ self._buffer.badge_name = value
+ self.queue_resize()
+
+ def get_badge_name(self):
+ return self._buffer.badge_name
+
+ badge_name = gobject.property(
+ type=str, getter=get_badge_name, setter=set_badge_name)
+
+
+class CanvasIcon(hippo.CanvasBox, hippo.CanvasItem):
+
+ __gtype_name__ = 'CanvasIcon'
+
+ def __init__(self, **kwargs):
+ from sugar.graphics.palette import CanvasInvoker
+
+ self._buffer = _IconBuffer()
+ self._palette_invoker = CanvasInvoker()
+
+ hippo.CanvasBox.__init__(self, **kwargs)
+
+ self._palette_invoker.attach(self)
+
+ self.connect('destroy', self.__destroy_cb)
+
+ def _emit_paint_needed_icon_area(self):
+ surface = self._buffer.get_surface()
+ if surface:
+ width, height = self.get_allocation()
+ s_width = surface.get_width()
+ s_height = surface.get_height()
+
+ x = (width - s_width) / 2
+ y = (height - s_height) / 2
+
+ self.emit_paint_needed(x, y, s_width, s_height)
+
+ def __destroy_cb(self, icon):
+ if self._palette_invoker is not None:
+ self._palette_invoker.detach()
+
+ def set_file_name(self, value):
+ """
+ Parameters
+ ----------
+ value:
+
+ Returns
+ -------
+ None
+
+ \"\"\"
+
+ """
+ if self._buffer.file_name != value:
+ self._buffer.file_name = value
+ self.emit_paint_needed(0, 0, -1, -1)
+
+ def get_file_name(self):
+ """
+ Parameters
+ ----------
+ None
+
+ Returns
+ -------
+ file name :
+
+ """
+ return self._buffer.file_name
+
+ file_name = gobject.property(
+ type=object, getter=get_file_name, setter=set_file_name)
+
+ def set_icon_name(self, value):
+ """
+ Parameters
+ ----------
+ value:
+
+ Returns
+ -------
+ None
+
+ """
+ if self._buffer.icon_name != value:
+ self._buffer.icon_name = value
+ self.emit_paint_needed(0, 0, -1, -1)
+
+ def get_icon_name(self):
+ """
+ Parameters
+ ----------
+ None
+
+ Returns
+ -------
+ icon name :
+
+ """
+ return self._buffer.icon_name
+
+ icon_name = gobject.property(
+ type=object, getter=get_icon_name, setter=set_icon_name)
+
+ def set_xo_color(self, value):
+ """
+ Parameters
+ ----------
+ value:
+
+ Returns
+ -------
+ None
+
+ """
+ if self._buffer.xo_color != value:
+ self._buffer.xo_color = value
+ self._emit_paint_needed_icon_area()
+
+ xo_color = gobject.property(
+ type=object, getter=None, setter=set_xo_color)
+
+ def set_fill_color(self, value):
+ """
+ Parameters
+ ----------
+ value:
+
+ Returns
+ -------
+ None
+
+ """
+ if self._buffer.fill_color != value:
+ self._buffer.fill_color = value
+ self._emit_paint_needed_icon_area()
+
+ def get_fill_color(self):
+ """
+ Parameters
+ ----------
+ None
+
+ Returns
+ -------
+ fill color :
+
+ """
+ return self._buffer.fill_color
+
+ fill_color = gobject.property(
+ type=object, getter=get_fill_color, setter=set_fill_color)
+
+ def set_stroke_color(self, value):
+ """
+ Parameters
+ ----------
+ value:
+
+ Returns
+ -------
+ None
+
+ """
+ if self._buffer.stroke_color != value:
+ self._buffer.stroke_color = value
+ self._emit_paint_needed_icon_area()
+
+ def get_stroke_color(self):
+ """
+ Parameters
+ ----------
+ None
+
+ Returns
+ -------
+ stroke color :
+
+ """
+ return self._buffer.stroke_color
+
+ stroke_color = gobject.property(
+ type=object, getter=get_stroke_color, setter=set_stroke_color)
+
+ def set_background_color(self, value):
+ """
+ Parameters
+ ----------
+ value:
+
+ Returns
+ -------
+ None
+
+ """
+ if self._buffer.background_color != value:
+ self._buffer.background_color = value
+ self.emit_paint_needed(0, 0, -1, -1)
+
+ def get_background_color(self):
+ """
+ Parameters
+ ----------
+ None
+
+ Returns
+ -------
+ fill color :
+
+ """
+ return self._buffer.background_color
+
+ background_color = gobject.property(
+ type=object, getter=get_background_color, setter=set_background_color)
+
+ def set_size(self, value):
+ """
+ Parameters
+ ----------
+ value:
+
+ Returns
+ -------
+ None
+
+ """
+ if self._buffer.width != value:
+ self._buffer.width = value
+ self._buffer.height = value
+ self.emit_request_changed()
+
+ def get_size(self):
+ """
+ Parameters
+ ----------
+ None
+
+ Returns
+ -------
+ size :
+
+ """
+ return self._buffer.width
+
+ size = gobject.property(
+ type=object, getter=get_size, setter=set_size)
+
+ def set_scale(self, value):
+ """
+ Parameters
+ ----------
+ value:
+
+ Returns
+ -------
+ None
+
+ """
+ logging.warning(
+ 'CanvasIcon: the scale parameter is currently unsupported')
+ if self._buffer.scale != value:
+ self._buffer.scale = value
+ self.emit_request_changed()
+
+ def get_scale(self):
+ """
+ Parameters
+ ----------
+ None
+
+ Returns
+ -------
+ scale :
+
+ """
+ return self._buffer.scale
+
+ scale = gobject.property(
+ type=float, getter=get_scale, setter=set_scale)
+
+ def set_cache(self, value):
+ """
+ Parameters
+ ----------
+ cache
+
+ Returns
+ -------
+ None
+
+ """
+ self._buffer.cache = value
+
+ def get_cache(self):
+ """
+ Parameters
+ ----------
+ None
+
+ Returns
+ -------
+ cache :
+
+ """
+ return self._buffer.cache
+
+ cache = gobject.property(
+ type=bool, default=False, getter=get_cache, setter=set_cache)
+
+ def set_badge_name(self, value):
+ """
+ Parameters
+ ----------
+ value :
+
+ Returns
+ -------
+ None
+
+ """
+ if self._buffer.badge_name != value:
+ self._buffer.badge_name = value
+ self.emit_paint_needed(0, 0, -1, -1)
+
+ def get_badge_name(self):
+ """
+ Parameters
+ ----------
+ None
+
+ Returns
+ -------
+ badge name :
+
+ """
+ return self._buffer.badge_name
+
+ badge_name = gobject.property(
+ type=object, getter=get_badge_name, setter=set_badge_name)
+
+ def do_paint_below_children(self, cr, damaged_box):
+ """
+ Parameters
+ ----------
+ cr :
+
+ damaged_box :
+
+ Returns
+ -------
+ None
+
+ """
+ surface = self._buffer.get_surface()
+ if surface:
+ width, height = self.get_allocation()
+
+ x = (width - surface.get_width()) / 2
+ y = (height - surface.get_height()) / 2
+
+ cr.set_source_surface(surface, x, y)
+ cr.paint()
+
+ def do_get_content_width_request(self):
+ """
+ Parameters
+ ----------
+ None
+
+ Returns
+ -------
+ width :
+
+ """
+ surface = self._buffer.get_surface()
+ if surface:
+ size = surface.get_width()
+ elif self._buffer.width:
+ size = self._buffer.width
+ else:
+ size = 0
+
+ return size, size
+
+ def do_get_content_height_request(self, for_width):
+ surface = self._buffer.get_surface()
+ if surface:
+ size = surface.get_height()
+ elif self._buffer.height:
+ size = self._buffer.height
+ else:
+ size = 0
+
+ return size, size
+
+ def do_button_press_event(self, event):
+ if event.button == 1:
+ self.emit_activated()
+ return True
+ else:
+ return False
+
+ def create_palette(self):
+ return None
+
+ def get_palette(self):
+ return self._palette_invoker.palette
+
+ def set_palette(self, palette):
+ self._palette_invoker.palette = palette
+
+ palette = gobject.property(
+ type=object, setter=set_palette, getter=get_palette)
+
+ def get_palette_invoker(self):
+ return self._palette_invoker
+
+ def set_palette_invoker(self, palette_invoker):
+ self._palette_invoker.detach()
+ self._palette_invoker = palette_invoker
+
+ palette_invoker = gobject.property(
+ type=object, setter=set_palette_invoker, getter=get_palette_invoker)
+
+ def set_tooltip(self, text):
+ from sugar.graphics.palette import Palette
+
+ self.set_palette(Palette(text))
+
+
+class CellRendererIcon(gtk.GenericCellRenderer):
+
+ __gtype_name__ = 'SugarCellRendererIcon'
+
+ __gsignals__ = {
+ 'clicked': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, [object]),
+ }
+
+ def __init__(self, tree_view):
+ from sugar.graphics.palette import CellRendererInvoker
+
+ self._buffer = _IconBuffer()
+ self._buffer.cache = True
+ self._xo_color = None
+ self._fill_color = None
+ self._stroke_color = None
+ self._prelit_fill_color = None
+ self._prelit_stroke_color = None
+ self._palette_invoker = CellRendererInvoker()
+
+ gobject.GObject.__init__(self)
+
+ self._palette_invoker.attach_cell_renderer(tree_view, self)
+
+ self.connect('destroy', self.__destroy_cb)
+
+ def __destroy_cb(self, icon):
+ self._palette_invoker.detach()
+
+ def create_palette(self):
+ return None
+
+ def get_palette_invoker(self):
+ return self._palette_invoker
+
+ palette_invoker = gobject.property(type=object, getter=get_palette_invoker)
+
+ def set_file_name(self, value):
+ if self._buffer.file_name != value:
+ self._buffer.file_name = value
+
+ file_name = gobject.property(type=str, setter=set_file_name)
+
+ def set_icon_name(self, value):
+ if self._buffer.icon_name != value:
+ self._buffer.icon_name = value
+
+ icon_name = gobject.property(type=str, setter=set_icon_name)
+
+ def get_xo_color(self):
+ return self._xo_color
+
+ def set_xo_color(self, value):
+ self._xo_color = value
+
+ xo_color = gobject.property(type=object,
+ getter=get_xo_color, setter=set_xo_color)
+
+ def set_fill_color(self, value):
+ if self._fill_color != value:
+ self._fill_color = value
+
+ fill_color = gobject.property(type=object, setter=set_fill_color)
+
+ def set_stroke_color(self, value):
+ if self._stroke_color != value:
+ self._stroke_color = value
+
+ stroke_color = gobject.property(type=object, setter=set_stroke_color)
+
+ def set_prelit_fill_color(self, value):
+ if self._prelit_fill_color != value:
+ self._prelit_fill_color = value
+
+ prelit_fill_color = gobject.property(type=object,
+ setter=set_prelit_fill_color)
+
+ def set_prelit_stroke_color(self, value):
+ if self._prelit_stroke_color != value:
+ self._prelit_stroke_color = value
+
+ prelit_stroke_color = gobject.property(type=object,
+ setter=set_prelit_stroke_color)
+
+ def set_background_color(self, value):
+ if self._buffer.background_color != value:
+ self._buffer.background_color = value
+
+ background_color = gobject.property(type=object,
+ setter=set_background_color)
+
+ def set_size(self, value):
+ if self._buffer.width != value:
+ self._buffer.width = value
+ self._buffer.height = value
+
+ size = gobject.property(type=object, setter=set_size)
+
+ def on_get_size(self, widget, cell_area):
+ width = self._buffer.width + self.props.xpad * 2
+ height = self._buffer.height + self.props.ypad * 2
+ xoffset = 0
+ yoffset = 0
+
+ if width > 0 and height > 0 and cell_area is not None:
+
+ if widget.get_direction() == gtk.TEXT_DIR_RTL:
+ xoffset = 1.0 - self.props.xalign
+ else:
+ xoffset = self.props.xalign
+
+ xoffset = max(xoffset * (cell_area.width - width), 0)
+ yoffset = max(self.props.yalign * (cell_area.height - height), 0)
+
+ return xoffset, yoffset, width, height
+
+ def on_activate(self, event, widget, path, background_area, cell_area,
+ flags):
+ pass
+
+ def on_start_editing(self, event, widget, path, background_area, cell_area,
+ flags):
+ pass
+
+ def _is_prelit(self, tree_view):
+ x, y = tree_view.get_pointer()
+ x, y = tree_view.convert_widget_to_bin_window_coords(x, y)
+ pos = tree_view.get_path_at_pos(x, y)
+ if pos is None:
+ return False
+
+ path_, column, x, y = pos
+
+ for cell_renderer in column.get_cell_renderers():
+ if cell_renderer == self:
+ cell_x, cell_width = column.cell_get_position(cell_renderer)
+ if x > cell_x and x < (cell_x + cell_width):
+ return True
+ return False
+
+ return False
+
+ def on_render(self, window, widget, background_area, cell_area,
+ expose_area, flags):
+ if self._xo_color is not None:
+ stroke_color = self._xo_color.get_stroke_color()
+ fill_color = self._xo_color.get_fill_color()
+ prelit_fill_color = None
+ prelit_stroke_color = None
+ else:
+ stroke_color = self._stroke_color
+ fill_color = self._fill_color
+ prelit_fill_color = self._prelit_fill_color
+ prelit_stroke_color = self._prelit_stroke_color
+
+ has_prelit_colors = None not in [prelit_fill_color,
+ prelit_stroke_color]
+
+ if flags & gtk.CELL_RENDERER_PRELIT and has_prelit_colors and \
+ self._is_prelit(widget):
+
+ self._buffer.fill_color = prelit_fill_color
+ self._buffer.stroke_color = prelit_stroke_color
+ else:
+ self._buffer.fill_color = fill_color
+ self._buffer.stroke_color = stroke_color
+
+ surface = self._buffer.get_surface()
+ if surface is None:
+ return
+
+ xoffset, yoffset, width_, height_ = self.on_get_size(widget, cell_area)
+
+ x = cell_area.x + xoffset
+ y = cell_area.y + yoffset
+
+ cr = window.cairo_create()
+ cr.set_source_surface(surface, math.floor(x), math.floor(y))
+ cr.rectangle(expose_area)
+ cr.paint()
+
+
+def get_icon_state(base_name, perc, step=5):
+ strength = round(perc / step) * step
+ icon_theme = gtk.icon_theme_get_default()
+
+ while strength <= 100 and strength >= 0:
+ icon_name = '%s-%03d' % (base_name, strength)
+ if icon_theme.has_icon(icon_name):
+ return icon_name
+
+ strength = strength + step
+
+
+def get_icon_file_name(icon_name):
+ icon_theme = gtk.icon_theme_get_default()
+ info = icon_theme.lookup_icon(icon_name, gtk.ICON_SIZE_LARGE_TOOLBAR, 0)
+ if not info:
+ return None
+ filename = info.get_filename()
+ del info
+ return filename
+
+
+def get_surface(**kwargs):
+ """Get cached cairo surface.
+
+ Keyword arguments:
+ icon_name -- name of icon to load, default None
+ file_name -- path to image file, default None
+ fill_color -- for svg images, change default fill color
+ default None
+ stroke_color -- for svg images, change default stroke color
+ default None
+ background_color -- draw background or surface will be transparent
+ default None
+ badge_name -- name of icon which will be drawn on top of
+ original image, default None
+ width -- change image width, default None
+ height -- change image height, default None
+ cache -- if image is svg, keep svg file content for later
+ scale -- scale image, default 1.0
+
+ Return: cairo surface or None if image was not found
+
+ """
+ icon = _IconBuffer()
+ for key, value in kwargs.items():
+ icon.__setattr__(key, value)
+ return icon.get_surface()
diff --git a/toolkit/src/sugar/graphics/iconentry.py b/toolkit/src/sugar/graphics/iconentry.py
new file mode 100644
index 0000000..4ce2c4f
--- /dev/null
+++ b/toolkit/src/sugar/graphics/iconentry.py
@@ -0,0 +1,106 @@
+# 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 gtk
+
+from sugar import _sugarext
+
+from sugar.graphics import style
+from sugar.graphics.icon import _SVGLoader
+
+ICON_ENTRY_PRIMARY = _sugarext.ICON_ENTRY_PRIMARY
+ICON_ENTRY_SECONDARY = _sugarext.ICON_ENTRY_SECONDARY
+
+
+class IconEntry(_sugarext.IconEntry):
+
+ def __init__(self):
+ _sugarext.IconEntry.__init__(self)
+
+ self._clear_icon = None
+ self._clear_shown = False
+
+ self.connect('key_press_event', self._keypress_event_cb)
+
+ def set_icon_from_name(self, position, name):
+ icon_theme = gtk.icon_theme_get_default()
+ icon_info = icon_theme.lookup_icon(name,
+ gtk.ICON_SIZE_SMALL_TOOLBAR,
+ 0)
+
+ if icon_info.get_filename().endswith('.svg'):
+ loader = _SVGLoader()
+ entities = {'fill_color': style.COLOR_TOOLBAR_GREY.get_svg(),
+ 'stroke_color': style.COLOR_TOOLBAR_GREY.get_svg()}
+ handle = loader.load(icon_info.get_filename(), entities, None)
+ pixbuf = handle.get_pixbuf()
+ else:
+ pixbuf = gtk.gdk.pixbuf_new_from_file(icon_info.get_filename())
+ del icon_info
+
+ image = gtk.Image()
+ image.set_from_pixbuf(pixbuf)
+ image.show()
+
+ self.set_icon(position, image)
+
+ def set_icon(self, position, image):
+ if image.get_storage_type() not in [gtk.IMAGE_PIXBUF, gtk.IMAGE_STOCK]:
+ raise ValueError('Image must have a storage type of pixbuf or ' +
+ 'stock, not %r.' % image.get_storage_type())
+ _sugarext.IconEntry.set_icon(self, position, image)
+
+ def remove_icon(self, position):
+ _sugarext.IconEntry.set_icon(self, position, None)
+
+ def add_clear_button(self):
+ if self.props.text != "":
+ self.show_clear_button()
+ else:
+ self.hide_clear_button()
+
+ self.connect('icon-pressed', self._icon_pressed_cb)
+ self.connect('changed', self._changed_cb)
+
+ def show_clear_button(self):
+ if not self._clear_shown:
+ self.set_icon_from_name(ICON_ENTRY_SECONDARY,
+ 'dialog-cancel')
+ self._clear_shown = True
+
+ def hide_clear_button(self):
+ if self._clear_shown:
+ self.remove_icon(ICON_ENTRY_SECONDARY)
+ self._clear_shown = False
+
+ def _keypress_event_cb(self, widget, event):
+ keyval = gtk.gdk.keyval_name(event.keyval)
+ if keyval == 'Escape':
+ self.props.text = ''
+ return True
+ return False
+
+ def _icon_pressed_cb(self, entru, icon_pos, button):
+ if icon_pos == ICON_ENTRY_SECONDARY:
+ self.set_text('')
+ self.hide_clear_button()
+
+ def _changed_cb(self, icon_entry):
+ if not self.props.text:
+ self.hide_clear_button()
+ else:
+ self.show_clear_button()
diff --git a/toolkit/src/sugar/graphics/menuitem.py b/toolkit/src/sugar/graphics/menuitem.py
new file mode 100644
index 0000000..655f7cc
--- /dev/null
+++ b/toolkit/src/sugar/graphics/menuitem.py
@@ -0,0 +1,95 @@
+# Copyright (C) 2007, Eduardo Silva <edsiper@gmail.com>
+#
+# 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.
+
+"""
+STABLE.
+"""
+
+import logging
+
+import gobject
+import pango
+import gtk
+
+from sugar.graphics.icon import Icon
+
+
+class MenuItem(gtk.ImageMenuItem):
+
+ def __init__(self, text_label=None, icon_name=None, text_maxlen=60,
+ xo_color=None, file_name=None):
+ gobject.GObject.__init__(self)
+ self._accelerator = None
+
+ label = gtk.AccelLabel(text_label)
+ label.set_alignment(0.0, 0.5)
+ label.set_accel_widget(self)
+ if text_maxlen > 0:
+ label.set_ellipsize(pango.ELLIPSIZE_MIDDLE)
+ label.set_max_width_chars(text_maxlen)
+ self.add(label)
+ label.show()
+
+ if icon_name is not None:
+ icon = Icon(icon_name=icon_name,
+ icon_size=gtk.ICON_SIZE_SMALL_TOOLBAR)
+ if xo_color is not None:
+ icon.props.xo_color = xo_color
+ self.set_image(icon)
+ icon.show()
+
+ elif file_name is not None:
+ icon = Icon(file=file_name, icon_size=gtk.ICON_SIZE_SMALL_TOOLBAR)
+ if xo_color is not None:
+ icon.props.xo_color = xo_color
+ self.set_image(icon)
+ icon.show()
+
+ self.connect('can-activate-accel', self.__can_activate_accel_cb)
+ self.connect('hierarchy-changed', self.__hierarchy_changed_cb)
+
+ def __hierarchy_changed_cb(self, widget, previous_toplevel):
+ self._add_accelerator()
+
+ def __can_activate_accel_cb(self, widget, signal_id):
+ # Accept activation via accelerators regardless of this widget's state
+ return True
+
+ def _add_accelerator(self):
+ if self._accelerator is None or self.get_toplevel() is None:
+ return
+
+ # TODO: should we remove the accelerator from the prev top level?
+
+ accel_group = self.get_toplevel().get_data('sugar-accel-group')
+ if not accel_group:
+ logging.warning('No gtk.AccelGroup in the top level window.')
+ return
+
+ keyval, mask = gtk.accelerator_parse(self._accelerator)
+ self.add_accelerator('activate', accel_group, keyval, mask,
+ gtk.ACCEL_LOCKED | gtk.ACCEL_VISIBLE)
+
+ def set_accelerator(self, accelerator):
+ self._accelerator = accelerator
+ self._add_accelerator()
+
+ def get_accelerator(self):
+ return self._accelerator
+
+ accelerator = gobject.property(type=str, setter=set_accelerator,
+ getter=get_accelerator)
diff --git a/toolkit/src/sugar/graphics/notebook.py b/toolkit/src/sugar/graphics/notebook.py
new file mode 100644
index 0000000..603b7c9
--- /dev/null
+++ b/toolkit/src/sugar/graphics/notebook.py
@@ -0,0 +1,151 @@
+# Copyright (C) 2007, Eduardo Silva (edsiper@gmail.com)
+#
+# 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.
+
+"""Notebook class
+
+This class create a gtk.Notebook() widget supporting
+a close button in every tab when the 'can-close-tabs' gproperty
+is enabled (True)
+
+STABLE.
+"""
+
+import gtk
+import gobject
+
+
+class Notebook(gtk.Notebook):
+
+ __gtype_name__ = 'SugarNotebook'
+
+ __gproperties__ = {
+ 'can-close-tabs': (bool, None, None, False,
+ gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT_ONLY),
+ }
+
+ def __init__(self, **kwargs):
+ # Initialise the Widget
+
+ # Side effects:
+ # Set the 'can-close-tabs' property using **kwargs
+ # Set True the scrollable notebook property
+
+ gobject.GObject.__init__(self, **kwargs)
+
+ self._can_close_tabs = None
+
+ self.set_scrollable(True)
+ self.show()
+
+ def do_set_property(self, pspec, value):
+ """
+ Set notebook property
+
+ Parameters
+ ----------
+ pspec :
+ property for which the value will be set
+
+ Returns
+ -------
+ None
+
+ Raises
+ ------
+ AssertionError
+
+ """
+ if pspec.name == 'can-close-tabs':
+ self._can_close_tabs = value
+ else:
+ raise AssertionError
+
+ def _add_icon_to_button(self, button):
+ icon_box = gtk.HBox()
+ image = gtk.Image()
+ image.set_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU)
+ gtk.Button.set_relief(button, gtk.RELIEF_NONE)
+
+ settings = gtk.Widget.get_settings(button)
+ w, h = gtk.icon_size_lookup_for_settings(settings, gtk.ICON_SIZE_MENU)
+ gtk.Widget.set_size_request(button, w + 4, h + 4)
+ image.show()
+ icon_box.pack_start(image, True, False, 0)
+ button.add(icon_box)
+ icon_box.show()
+
+ def _create_custom_tab(self, text, child):
+ event_box = gtk.EventBox()
+
+ tab_box = gtk.HBox(False, 2)
+ tab_label = gtk.Label(text)
+
+ tab_button = gtk.Button()
+ tab_button.connect('clicked', self._close_page, child)
+
+ # Add a picture on a button
+ self._add_icon_to_button(tab_button)
+
+ event_box.show()
+ tab_button.show()
+ tab_label.show()
+
+ tab_box.pack_start(tab_label, True)
+ tab_box.pack_start(tab_button, True)
+
+ tab_box.show_all()
+ event_box.add(tab_box)
+
+ return event_box
+
+ def add_page(self, text_label, widget):
+ """
+ Adds a page to the notebook.
+
+ Parameters
+ ----------
+ text_label :
+
+ widget :
+
+ Returns
+ -------
+ Boolean
+ Returns TRUE if the page is successfully added to th notebook.
+
+ """
+ # Add a new page to the notebook
+ if self._can_close_tabs:
+ eventbox = self._create_custom_tab(text_label, widget)
+ self.append_page(widget, eventbox)
+ else:
+ self.append_page(widget, gtk.Label(text_label))
+
+ pages = self.get_n_pages()
+
+ # Set the new page
+ self.set_current_page(pages - 1)
+ self.show_all()
+
+ return True
+
+ def _close_page(self, button, child):
+ # Remove a page from the notebook
+ page = self.page_num(child)
+
+ if page != -1:
+ self.remove_page(page)
diff --git a/toolkit/src/sugar/graphics/objectchooser.py b/toolkit/src/sugar/graphics/objectchooser.py
new file mode 100644
index 0000000..590e35d
--- /dev/null
+++ b/toolkit/src/sugar/graphics/objectchooser.py
@@ -0,0 +1,132 @@
+# 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.
+
+"""
+STABLE.
+"""
+
+import logging
+
+import gobject
+import gtk
+import dbus
+
+from sugar.datastore import datastore
+
+
+J_DBUS_SERVICE = 'org.laptop.Journal'
+J_DBUS_INTERFACE = 'org.laptop.Journal'
+J_DBUS_PATH = '/org/laptop/Journal'
+
+
+class ObjectChooser(object):
+
+ def __init__(self, title=None, parent=None, flags=None, buttons=None,
+ what_filter=None):
+ # For backwards compatibility:
+ # - We ignore title, flags and buttons.
+ # - 'parent' can be a xid or a gtk.Window
+
+ if title is not None or flags is not None or buttons is not None:
+ logging.warning('Invocation of ObjectChooser() has deprecated '
+ 'parameters.')
+
+ if parent is None:
+ parent_xid = 0
+ elif hasattr(parent, 'window') and hasattr(parent.window, 'xid'):
+ parent_xid = parent.window.xid
+ else:
+ parent_xid = parent
+
+ self._parent_xid = parent_xid
+ self._main_loop = None
+ self._object_id = None
+ self._bus = None
+ self._chooser_id = None
+ self._response_code = gtk.RESPONSE_NONE
+ self._what_filter = what_filter
+
+ def run(self):
+ self._object_id = None
+
+ self._main_loop = gobject.MainLoop()
+
+ self._bus = dbus.SessionBus(mainloop=self._main_loop)
+ self._bus.add_signal_receiver(
+ self.__name_owner_changed_cb,
+ signal_name="NameOwnerChanged",
+ dbus_interface="org.freedesktop.DBus",
+ arg0=J_DBUS_SERVICE)
+
+ obj = self._bus.get_object(J_DBUS_SERVICE, J_DBUS_PATH)
+ journal = dbus.Interface(obj, J_DBUS_INTERFACE)
+ journal.connect_to_signal('ObjectChooserResponse',
+ self.__chooser_response_cb)
+ journal.connect_to_signal('ObjectChooserCancelled',
+ self.__chooser_cancelled_cb)
+
+ if self._what_filter is None:
+ what_filter = ''
+ else:
+ what_filter = self._what_filter
+
+ self._chooser_id = journal.ChooseObject(self._parent_xid, what_filter)
+
+ gtk.gdk.threads_leave()
+ try:
+ self._main_loop.run()
+ finally:
+ gtk.gdk.threads_enter()
+ self._main_loop = None
+
+ return self._response_code
+
+ def get_selected_object(self):
+ if self._object_id is None:
+ return None
+ else:
+ return datastore.get(self._object_id)
+
+ def destroy(self):
+ self._cleanup()
+
+ def _cleanup(self):
+ if self._main_loop is not None:
+ self._main_loop.quit()
+ self._main_loop = None
+ self._bus = None
+
+ def __chooser_response_cb(self, chooser_id, object_id):
+ if chooser_id != self._chooser_id:
+ return
+ logging.debug('ObjectChooser.__chooser_response_cb: %r', object_id)
+ self._response_code = gtk.RESPONSE_ACCEPT
+ self._object_id = object_id
+ self._cleanup()
+
+ def __chooser_cancelled_cb(self, chooser_id):
+ if chooser_id != self._chooser_id:
+ return
+ logging.debug('ObjectChooser.__chooser_cancelled_cb: %r', chooser_id)
+ self._response_code = gtk.RESPONSE_CANCEL
+ self._cleanup()
+
+ def __name_owner_changed_cb(self, name, old, new):
+ logging.debug('ObjectChooser.__name_owner_changed_cb')
+ # Journal service disappeared from the bus
+ self._response_code = gtk.RESPONSE_CANCEL
+ self._cleanup()
diff --git a/toolkit/src/sugar/graphics/palette.py b/toolkit/src/sugar/graphics/palette.py
new file mode 100644
index 0000000..d4632eb
--- /dev/null
+++ b/toolkit/src/sugar/graphics/palette.py
@@ -0,0 +1,446 @@
+# Copyright (C) 2007, Eduardo Silva <edsiper@gmail.com>
+# Copyright (C) 2008, One Laptop Per Child
+# Copyright (C) 2009, Tomeu Vizoso
+#
+# 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.
+
+"""
+STABLE.
+"""
+
+import logging
+
+import gtk
+import gobject
+import pango
+
+from sugar.graphics import palettegroup
+from sugar.graphics import animator
+from sugar.graphics import style
+from sugar.graphics.icon import Icon
+from sugar.graphics.palettewindow import PaletteWindow
+from sugar import _sugarext
+
+# DEPRECATED
+# Import these for backwards compatibility
+from sugar.graphics.palettewindow import MouseSpeedDetector, Invoker, \
+ WidgetInvoker, CanvasInvoker, ToolInvoker, CellRendererInvoker
+
+
+class Palette(PaletteWindow):
+ PRIMARY = 0
+ SECONDARY = 1
+
+ __gtype_name__ = 'SugarPalette'
+
+ def __init__(self, label=None, accel_path=None, menu_after_content=False,
+ text_maxlen=60, **kwargs):
+ # DEPRECATED: label is passed with the primary-text property,
+ # accel_path is set via the invoker property, and menu_after_content
+ # is not used
+
+ self._primary_text = None
+ self._secondary_text = None
+ self._icon = None
+ self._icon_visible = True
+ self._palette_state = self.PRIMARY
+
+ palette_box = gtk.VBox()
+
+ primary_box = gtk.HBox()
+ palette_box.pack_start(primary_box, expand=False)
+ primary_box.show()
+
+ self._icon_box = gtk.HBox()
+ self._icon_box.set_size_request(style.GRID_CELL_SIZE, -1)
+ primary_box.pack_start(self._icon_box, expand=False)
+
+ labels_box = gtk.VBox()
+ self._label_alignment = gtk.Alignment(xalign=0, yalign=0.5,
+ xscale=1, yscale=0.33)
+ self._label_alignment.set_padding(0, 0, style.DEFAULT_SPACING,
+ style.DEFAULT_SPACING)
+ self._label_alignment.add(labels_box)
+ self._label_alignment.show()
+ primary_box.pack_start(self._label_alignment, expand=True)
+ labels_box.show()
+
+ self._label = gtk.AccelLabel('')
+ self._label.set_alignment(0, 0.5)
+
+ if text_maxlen > 0:
+ self._label.set_max_width_chars(text_maxlen)
+ self._label.set_ellipsize(pango.ELLIPSIZE_MIDDLE)
+ labels_box.pack_start(self._label, expand=True)
+
+ self._secondary_label = gtk.Label()
+ self._secondary_label.set_alignment(0, 0.5)
+
+ if text_maxlen > 0:
+ self._secondary_label.set_max_width_chars(text_maxlen)
+ self._secondary_label.set_ellipsize(pango.ELLIPSIZE_END)
+
+ labels_box.pack_start(self._secondary_label, expand=True)
+
+ self._secondary_box = gtk.VBox()
+ palette_box.pack_start(self._secondary_box)
+
+ self._separator = gtk.HSeparator()
+ self._secondary_box.pack_start(self._separator)
+
+ self._menu_content_separator = gtk.HSeparator()
+
+ self._secondary_anim = animator.Animator(2.0, 10)
+ self._secondary_anim.add(_SecondaryAnimation(self))
+
+ # we init after initializing all of our containers
+ PaletteWindow.__init__(self, **kwargs)
+
+ primary_box.set_size_request(-1, style.GRID_CELL_SIZE
+ - 2 * self.get_border_width())
+
+ self._full_request = [0, 0]
+ self._menu_box = None
+ self._content = None
+
+ # we set these for backward compatibility
+ if label is not None:
+ self.props.primary_text = label
+
+ self._add_menu()
+ self._secondary_box.pack_start(self._menu_content_separator)
+ self._add_content()
+
+ self.action_bar = PaletteActionBar()
+ self._secondary_box.pack_start(self.action_bar)
+ self.action_bar.show()
+
+ self.add(palette_box)
+ palette_box.show()
+
+ # The menu is not shown here until an item is added
+ self.menu = _Menu(self)
+ self.menu.connect('item-inserted', self.__menu_item_inserted_cb)
+
+ self.connect('realize', self.__realize_cb)
+ self.connect('show', self.__show_cb)
+ self.connect('hide', self.__hide_cb)
+ self.connect('notify::invoker', self.__notify_invoker_cb)
+ self.connect('destroy', self.__destroy_cb)
+
+ def _invoker_right_click_cb(self, invoker):
+ self.popup(immediate=True, state=self.SECONDARY)
+
+ def do_style_set(self, previous_style):
+ # Prevent a warning from pygtk
+ if previous_style is not None:
+ gtk.Window.do_style_set(self, previous_style)
+ self.set_border_width(self.get_style().xthickness)
+
+ def __menu_item_inserted_cb(self, menu):
+ self._update_separators()
+
+ def __destroy_cb(self, palette):
+ self._secondary_anim.stop()
+ self.popdown(immediate=True)
+ # Break the reference cycle. It looks like the gc is not able to free
+ # it, possibly because gtk.Menu memory handling is very special.
+ self.menu = None
+
+ def __show_cb(self, widget):
+ self.menu.set_active(True)
+
+ def __hide_cb(self, widget):
+ self.menu.set_active(False)
+ self.menu.cancel()
+ self._secondary_anim.stop()
+
+ def __notify_invoker_cb(self, palette, pspec):
+ invoker = self.props.invoker
+ if invoker is not None and hasattr(invoker.props, 'widget'):
+ self._update_accel_widget()
+ self._invoker.connect('notify::widget',
+ self.__invoker_widget_changed_cb)
+
+ def __invoker_widget_changed_cb(self, invoker, spec):
+ self._update_accel_widget()
+
+ def get_full_size_request(self):
+ return self._full_request
+
+ def popup(self, immediate=False, state=None):
+ if self._invoker is not None:
+ self._update_full_request()
+
+ PaletteWindow.popup(self, immediate)
+
+ if state is None:
+ state = self.PRIMARY
+ self.set_palette_state(state)
+
+ if state == self.PRIMARY:
+ self._secondary_anim.start()
+ else:
+ self._secondary_anim.stop()
+
+ def popdown(self, immediate=False):
+ if immediate:
+ self._secondary_anim.stop()
+ self._popdown_submenus()
+ # to suppress glitches while later re-opening
+ self.set_palette_state(self.PRIMARY)
+ PaletteWindow.popdown(self, immediate)
+
+ def _popdown_submenus(self):
+ # TODO explicit hiding of subitems
+ # should be removed after fixing #1301
+ if self.menu is not None:
+ for menu_item in self.menu.get_children():
+ if menu_item.props.submenu is not None:
+ menu_item.props.submenu.popdown()
+
+ def on_enter(self, event):
+ PaletteWindow.on_enter(self, event)
+ self._secondary_anim.start()
+
+ def _add_menu(self):
+ self._menu_box = gtk.VBox()
+ self._secondary_box.pack_start(self._menu_box)
+ self._menu_box.show()
+
+ def _add_content(self):
+ # The content is not shown until a widget is added
+ self._content = gtk.VBox()
+ self._content.set_border_width(style.DEFAULT_SPACING)
+ self._secondary_box.pack_start(self._content)
+
+ def _update_accel_widget(self):
+ assert self.props.invoker is not None
+ self._label.props.accel_widget = self.props.invoker.props.widget
+
+ def set_primary_text(self, label, accel_path=None):
+ self._primary_text = label
+
+ if label is not None:
+ self._label.set_markup('<b>%s</b>' % label)
+ self._label.show()
+
+ def get_primary_text(self):
+ return self._primary_text
+
+ primary_text = gobject.property(type=str,
+ getter=get_primary_text,
+ setter=set_primary_text)
+
+ def set_secondary_text(self, label):
+ if label is not None:
+ label = label.split('\n', 1)[0]
+ self._secondary_text = label
+
+ if label is None:
+ self._secondary_label.hide()
+ else:
+ self._secondary_label.set_text(label)
+ self._secondary_label.show()
+
+ def get_secondary_text(self):
+ return self._secondary_text
+
+ secondary_text = gobject.property(type=str, getter=get_secondary_text,
+ setter=set_secondary_text)
+
+ def _show_icon(self):
+ self._label_alignment.set_padding(0, 0, 0, style.DEFAULT_SPACING)
+ self._icon_box.show()
+
+ def _hide_icon(self):
+ self._icon_box.hide()
+ self._label_alignment.set_padding(0, 0, style.DEFAULT_SPACING,
+ style.DEFAULT_SPACING)
+
+ def set_icon(self, icon):
+ if icon is None:
+ self._icon = None
+ self._hide_icon()
+ else:
+ if self._icon:
+ self._icon_box.remove(self._icon_box.get_children()[0])
+
+ event_box = gtk.EventBox()
+ event_box.connect('button-release-event',
+ self.__icon_button_release_event_cb)
+ self._icon_box.pack_start(event_box)
+ event_box.show()
+
+ self._icon = icon
+ self._icon.props.icon_size = gtk.ICON_SIZE_LARGE_TOOLBAR
+ event_box.add(self._icon)
+ self._icon.show()
+ self._show_icon()
+
+ def get_icon(self):
+ return self._icon
+
+ icon = gobject.property(type=object, getter=get_icon, setter=set_icon)
+
+ def __icon_button_release_event_cb(self, icon, event):
+ self.emit('activate')
+
+ def set_icon_visible(self, visible):
+ self._icon_visible = visible
+
+ if visible and self._icon is not None:
+ self._show_icon()
+ else:
+ self._hide_icon()
+
+ def get_icon_visible(self):
+ return self._icon_visilbe
+
+ icon_visible = gobject.property(type=bool,
+ default=True,
+ getter=get_icon_visible,
+ setter=set_icon_visible)
+
+ def set_content(self, widget):
+ if len(self._content.get_children()) > 0:
+ self._content.remove(self._content.get_children()[0])
+
+ if widget is not None:
+ self._content.add(widget)
+ self._content.show()
+ else:
+ self._content.hide()
+
+ self._update_accept_focus()
+ self._update_separators()
+
+ def do_size_request(self, requisition):
+ PaletteWindow.do_size_request(self, requisition)
+
+ # gtk.AccelLabel request doesn't include the accelerator.
+ label_width = self._label_alignment.size_request()[0] + \
+ self._label.get_accel_width() + \
+ 2 * self.get_border_width()
+
+ requisition.width = max(requisition.width,
+ label_width,
+ self._full_request[0])
+
+ def _update_separators(self):
+ visible = len(self.menu.get_children()) > 0 or \
+ len(self._content.get_children()) > 0
+ self._separator.props.visible = visible
+
+ visible = len(self.menu.get_children()) > 0 and \
+ len(self._content.get_children()) > 0
+ self._menu_content_separator.props.visible = visible
+
+ def _update_accept_focus(self):
+ accept_focus = len(self._content.get_children())
+ if self.window:
+ self.window.set_accept_focus(accept_focus)
+
+ def __realize_cb(self, widget):
+ self._update_accept_focus()
+
+ def _update_full_request(self):
+ if self._palette_state == self.PRIMARY:
+ self.menu.embed(self._menu_box)
+ self._secondary_box.show()
+
+ self._full_request = self.size_request()
+
+ if self._palette_state == self.PRIMARY:
+ self.menu.unembed()
+ self._secondary_box.hide()
+
+ def _set_palette_state(self, state):
+ if self._palette_state == state:
+ return
+
+ if state == self.PRIMARY:
+ self.menu.unembed()
+ self._secondary_box.hide()
+ elif state == self.SECONDARY:
+ self.menu.embed(self._menu_box)
+ self._secondary_box.show()
+ self.update_position()
+
+ self._palette_state = state
+
+
+class PaletteActionBar(gtk.HButtonBox):
+
+ def add_action(self, label, icon_name=None):
+ button = gtk.Button(label)
+
+ if icon_name:
+ icon = Icon(icon_name)
+ button.set_image(icon)
+ icon.show()
+
+ self.pack_start(button)
+ button.show()
+
+
+class _Menu(_sugarext.Menu):
+
+ __gtype_name__ = 'SugarPaletteMenu'
+
+ __gsignals__ = {
+ 'item-inserted': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
+ }
+
+ def __init__(self, palette):
+ _sugarext.Menu.__init__(self)
+ self._palette = palette
+
+ def do_insert(self, item, position):
+ _sugarext.Menu.do_insert(self, item, position)
+ self.emit('item-inserted')
+ self.show()
+
+ def attach(self, child, left_attach, right_attach,
+ top_attach, bottom_attach):
+ _sugarext.Menu.attach(self, child, left_attach, right_attach,
+ top_attach, bottom_attach)
+ self.emit('item-inserted')
+ self.show()
+
+ def do_expose_event(self, event):
+ # Ignore the Menu expose, just do the MenuShell expose to prevent any
+ # border from being drawn here. A border is drawn by the palette object
+ # around everything.
+ gtk.MenuShell.do_expose_event(self, event)
+
+ def do_grab_notify(self, was_grabbed):
+ # Ignore grab_notify as the menu would close otherwise
+ pass
+
+ def do_deactivate(self):
+ self._palette.hide()
+
+
+class _SecondaryAnimation(animator.Animation):
+
+ def __init__(self, palette):
+ animator.Animation.__init__(self, 0.0, 1.0)
+ self._palette = palette
+
+ def next_frame(self, current):
+ if current == 1.0:
+ self._palette.set_palette_state(Palette.SECONDARY)
diff --git a/toolkit/src/sugar/graphics/palettegroup.py b/toolkit/src/sugar/graphics/palettegroup.py
new file mode 100644
index 0000000..05c713c
--- /dev/null
+++ b/toolkit/src/sugar/graphics/palettegroup.py
@@ -0,0 +1,106 @@
+# Copyright (C) 2007 Red Hat, Inc.
+#
+# 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.
+
+"""
+STABLE.
+"""
+
+import gobject
+
+
+_groups = {}
+
+
+def get_group(group_id):
+ if _groups.has_key(group_id):
+ group = _groups[group_id]
+ else:
+ group = Group()
+ _groups[group_id] = group
+
+ return group
+
+
+def popdown_all():
+ for group in _groups.values():
+ group.popdown()
+
+
+class Group(gobject.GObject):
+
+ __gsignals__ = {
+ 'popup': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
+ 'popdown': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
+ }
+
+ def __init__(self):
+ gobject.GObject.__init__(self)
+ self._up = False
+ self._palettes = []
+ self._sig_ids = {}
+
+ def is_up(self):
+ return self._up
+
+ def get_state(self):
+ for palette in self._palettes:
+ if palette.is_up():
+ return palette.palette_state
+
+ return None
+
+ def add(self, palette):
+ self._palettes.append(palette)
+
+ self._sig_ids[palette] = []
+
+ sid = palette.connect('popup', self._palette_popup_cb)
+ self._sig_ids[palette].append(sid)
+
+ sid = palette.connect('popdown', self._palette_popdown_cb)
+ self._sig_ids[palette].append(sid)
+
+ def remove(self, palette):
+ sig_ids = self._sig_ids[palette]
+ for sid in sig_ids:
+ palette.disconnect(sid)
+
+ self._palettes.remove(palette)
+ del self._sig_ids[palette]
+
+ def popdown(self):
+ for palette in self._palettes:
+ if palette.is_up():
+ palette.popdown(immediate=True)
+
+ def _palette_popup_cb(self, palette):
+ for i in self._palettes:
+ if i != palette:
+ i.popdown(immediate=True)
+ if not self._up:
+ self.emit('popup')
+ self._up = True
+
+ def _palette_popdown_cb(self, palette):
+ down = True
+ for palette in self._palettes:
+ if palette.is_up():
+ down = False
+
+ if down:
+ self._up = False
+ self.emit('popdown')
diff --git a/toolkit/src/sugar/graphics/palettewindow.py b/toolkit/src/sugar/graphics/palettewindow.py
new file mode 100644
index 0000000..22131c2
--- /dev/null
+++ b/toolkit/src/sugar/graphics/palettewindow.py
@@ -0,0 +1,976 @@
+# Copyright (C) 2007, Eduardo Silva <edsiper@gmail.com>
+# Copyright (C) 2008, One Laptop Per Child
+# Copyright (C) 2009, Tomeu Vizoso
+#
+# 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.
+
+"""
+STABLE.
+"""
+
+import logging
+
+import gtk
+import gobject
+import hippo
+
+from sugar.graphics import palettegroup
+from sugar.graphics import animator
+from sugar.graphics import style
+
+
+def _calculate_gap(a, b):
+ """Helper function to find the gap position and size of widget a"""
+ # Test for each side if the palette and invoker are
+ # adjacent to each other.
+ gap = True
+
+ if a.y + a.height == b.y:
+ gap_side = gtk.POS_BOTTOM
+ elif a.x + a.width == b.x:
+ gap_side = gtk.POS_RIGHT
+ elif a.x == b.x + b.width:
+ gap_side = gtk.POS_LEFT
+ elif a.y == b.y + b.height:
+ gap_side = gtk.POS_TOP
+ else:
+ gap = False
+
+ if gap:
+ if gap_side == gtk.POS_BOTTOM or gap_side == gtk.POS_TOP:
+ gap_start = min(a.width, max(0, b.x - a.x))
+ gap_size = max(0, min(a.width,
+ (b.x + b.width) - a.x) - gap_start)
+ elif gap_side == gtk.POS_RIGHT or gap_side == gtk.POS_LEFT:
+ gap_start = min(a.height, max(0, b.y - a.y))
+ gap_size = max(0, min(a.height,
+ (b.y + b.height) - a.y) - gap_start)
+
+ if gap and gap_size > 0:
+ return (gap_side, gap_start, gap_size)
+ else:
+ return False
+
+
+class MouseSpeedDetector(gobject.GObject):
+
+ __gsignals__ = {
+ 'motion-slow': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
+ 'motion-fast': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
+ }
+
+ _MOTION_SLOW = 1
+ _MOTION_FAST = 2
+
+ def __init__(self, parent, delay, thresh):
+ """Create MouseSpeedDetector object,
+ delay in msec
+ threshold in pixels (per tick of 'delay' msec)"""
+
+ gobject.GObject.__init__(self)
+
+ self._threshold = thresh
+ self._parent = parent
+ self._delay = delay
+ self._state = None
+ self._timeout_hid = None
+ self._mouse_pos = None
+
+ def start(self):
+ self.stop()
+
+ self._mouse_pos = self._get_mouse_position()
+ self._timeout_hid = gobject.timeout_add(self._delay, self._timer_cb)
+
+ def stop(self):
+ if self._timeout_hid is not None:
+ gobject.source_remove(self._timeout_hid)
+ self._state = None
+
+ def _get_mouse_position(self):
+ display = gtk.gdk.display_get_default()
+ screen_, x, y, mask_ = display.get_pointer()
+ return (x, y)
+
+ def _detect_motion(self):
+ oldx, oldy = self._mouse_pos
+ (x, y) = self._get_mouse_position()
+ self._mouse_pos = (x, y)
+
+ dist2 = (oldx - x)**2 + (oldy - y)**2
+ if dist2 > self._threshold**2:
+ return True
+ else:
+ return False
+
+ def _timer_cb(self):
+ motion = self._detect_motion()
+ if motion and self._state != self._MOTION_FAST:
+ self.emit('motion-fast')
+ self._state = self._MOTION_FAST
+ elif not motion and self._state != self._MOTION_SLOW:
+ self.emit('motion-slow')
+ self._state = self._MOTION_SLOW
+
+ return True
+
+
+class PaletteWindow(gtk.Window):
+
+ __gtype_name__ = 'SugarPaletteWindow'
+
+ __gsignals__ = {
+ 'popup': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
+ 'popdown': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
+ 'activate': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
+ }
+
+ def __init__(self, **kwargs):
+ self._group_id = None
+ self._invoker = None
+ self._invoker_hids = []
+ self._cursor_x = 0
+ self._cursor_y = 0
+ self._alignment = None
+ self._up = False
+ self._old_alloc = None
+
+ self._popup_anim = animator.Animator(.5, 10)
+ self._popup_anim.add(_PopupAnimation(self))
+
+ self._popdown_anim = animator.Animator(0.6, 10)
+ self._popdown_anim.add(_PopdownAnimation(self))
+
+ gobject.GObject.__init__(self, **kwargs)
+
+ self.set_decorated(False)
+ self.set_resizable(False)
+ # Just assume xthickness and ythickness are the same
+ self.set_border_width(self.get_style().xthickness)
+
+ accel_group = gtk.AccelGroup()
+ self.set_data('sugar-accel-group', accel_group)
+ self.add_accel_group(accel_group)
+
+ self.set_group_id("default")
+
+ self.connect('show', self.__show_cb)
+ self.connect('hide', self.__hide_cb)
+ self.connect('realize', self.__realize_cb)
+ self.connect('destroy', self.__destroy_cb)
+ self.connect('enter-notify-event', self.__enter_notify_event_cb)
+ self.connect('leave-notify-event', self.__leave_notify_event_cb)
+
+ self._mouse_detector = MouseSpeedDetector(self, 200, 5)
+ self._mouse_detector.connect('motion-slow', self._mouse_slow_cb)
+
+ def __destroy_cb(self, palette):
+ self.set_group_id(None)
+
+ def set_invoker(self, invoker):
+ for hid in self._invoker_hids[:]:
+ self._invoker.disconnect(hid)
+ self._invoker_hids.remove(hid)
+
+ self._invoker = invoker
+ if invoker is not None:
+ self._invoker_hids.append(self._invoker.connect(
+ 'mouse-enter', self._invoker_mouse_enter_cb))
+ self._invoker_hids.append(self._invoker.connect(
+ 'mouse-leave', self._invoker_mouse_leave_cb))
+ self._invoker_hids.append(self._invoker.connect(
+ 'right-click', self._invoker_right_click_cb))
+
+ def get_invoker(self):
+ return self._invoker
+
+ invoker = gobject.property(type=object,
+ getter=get_invoker,
+ setter=set_invoker)
+
+ def __realize_cb(self, widget):
+ self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
+
+ def _mouse_slow_cb(self, widget):
+ self._mouse_detector.stop()
+ self._palette_do_popup()
+
+ def _palette_do_popup(self):
+ immediate = False
+
+ if self.is_up():
+ self._popdown_anim.stop()
+ return
+
+ if self._group_id:
+ group = palettegroup.get_group(self._group_id)
+ if group and group.is_up():
+ immediate = True
+ group.popdown()
+
+ self.popup(immediate=immediate)
+
+ def is_up(self):
+ return self._up
+
+ def set_group_id(self, group_id):
+ if self._group_id:
+ group = palettegroup.get_group(self._group_id)
+ group.remove(self)
+ if group_id:
+ self._group_id = group_id
+ group = palettegroup.get_group(group_id)
+ group.add(self)
+
+ def get_group_id(self):
+ return self._group_id
+
+ group_id = gobject.property(type=str,
+ getter=get_group_id,
+ setter=set_group_id)
+
+ def do_size_request(self, requisition):
+ gtk.Window.do_size_request(self, requisition)
+ requisition.width = max(requisition.width, style.GRID_CELL_SIZE * 2)
+
+ def do_size_allocate(self, allocation):
+ gtk.Window.do_size_allocate(self, allocation)
+
+ if self._old_alloc is None or \
+ self._old_alloc.x != allocation.x or \
+ self._old_alloc.y != allocation.y or \
+ self._old_alloc.width != allocation.width or \
+ self._old_alloc.height != allocation.height:
+ self.queue_draw()
+
+ # We need to store old allocation because when size_allocate
+ # is called widget.allocation is already updated.
+ # gtk.Window resizing is different from normal containers:
+ # the X window is resized, widget.allocation is updated from
+ # the configure request handler and finally size_allocate is called.
+ self._old_alloc = allocation
+
+ def do_expose_event(self, event):
+ # We want to draw a border with a beautiful gap
+ if self._invoker is not None and self._invoker.has_rectangle_gap():
+ invoker = self._invoker.get_rect()
+ palette = self.get_rect()
+
+ gap = _calculate_gap(palette, invoker)
+ else:
+ gap = False
+
+ allocation = self.get_allocation()
+ wstyle = self.get_style()
+
+ if gap:
+ wstyle.paint_box_gap(event.window, gtk.STATE_PRELIGHT,
+ gtk.SHADOW_IN, event.area, self, "palette",
+ 0, 0, allocation.width, allocation.height,
+ gap[0], gap[1], gap[2])
+ else:
+ wstyle.paint_box(event.window, gtk.STATE_PRELIGHT,
+ gtk.SHADOW_IN, event.area, self, "palette",
+ 0, 0, allocation.width, allocation.height)
+
+ # Fall trough to the container expose handler.
+ # (Leaving out the window expose handler which redraws everything)
+ gtk.Bin.do_expose_event(self, event)
+
+ def update_position(self):
+ invoker = self._invoker
+ if invoker is None or self._alignment is None:
+ logging.error('Cannot update the palette position.')
+ return
+
+ rect = self.size_request()
+ position = invoker.get_position_for_alignment(self._alignment, rect)
+ if position is None:
+ position = invoker.get_position(rect)
+
+ self.move(position.x, position.y)
+
+ def get_full_size_request(self):
+ return self.size_request()
+
+ def popup(self, immediate=False):
+ if self._invoker is not None:
+ full_size_request = self.get_full_size_request()
+ self._alignment = self._invoker.get_alignment(full_size_request)
+
+ self.update_position()
+ self.set_transient_for(self._invoker.get_toplevel())
+
+ self._popdown_anim.stop()
+
+ if not immediate:
+ self._popup_anim.start()
+ else:
+ self._popup_anim.stop()
+ self.show()
+ # we have to invoke update_position() twice
+ # since WM could ignore first move() request
+ self.update_position()
+
+ def popdown(self, immediate=False):
+ self._popup_anim.stop()
+ self._mouse_detector.stop()
+
+ if not immediate:
+ self._popdown_anim.start()
+ else:
+ self._popdown_anim.stop()
+ self.size_request()
+ self.hide()
+
+ def on_invoker_enter(self):
+ self._popdown_anim.stop()
+ self._mouse_detector.start()
+
+ def on_invoker_leave(self):
+ self._mouse_detector.stop()
+ self.popdown()
+
+ def on_enter(self, event):
+ self._popdown_anim.stop()
+
+ def on_leave(self, event):
+ self.popdown()
+
+ def _invoker_mouse_enter_cb(self, invoker):
+ self.on_invoker_enter()
+
+ def _invoker_mouse_leave_cb(self, invoker):
+ self.on_invoker_leave()
+
+ def _invoker_right_click_cb(self, invoker):
+ self.popup(immediate=True)
+
+ def __enter_notify_event_cb(self, widget, event):
+ if event.detail != gtk.gdk.NOTIFY_INFERIOR and \
+ event.mode == gtk.gdk.CROSSING_NORMAL:
+ self.on_enter(event)
+
+ def __leave_notify_event_cb(self, widget, event):
+ if event.detail != gtk.gdk.NOTIFY_INFERIOR and \
+ event.mode == gtk.gdk.CROSSING_NORMAL:
+ self.on_leave(event)
+
+ def __show_cb(self, widget):
+ if self._invoker is not None:
+ self._invoker.notify_popup()
+
+ self._up = True
+ self.emit('popup')
+
+ def __hide_cb(self, widget):
+ if self._invoker:
+ self._invoker.notify_popdown()
+
+ self._up = False
+ self.emit('popdown')
+
+ def get_rect(self):
+ win_x, win_y = self.window.get_origin()
+ rectangle = self.get_allocation()
+
+ x = win_x + rectangle.x
+ y = win_y + rectangle.y
+ width, height = self.size_request()
+
+ return gtk.gdk.Rectangle(x, y, width, height)
+
+ def get_palette_state(self):
+ return self._palette_state
+
+ def _set_palette_state(self, state):
+ self._palette_state = state
+
+ def set_palette_state(self, state):
+ self._set_palette_state(state)
+
+ palette_state = property(get_palette_state)
+
+
+class _PopupAnimation(animator.Animation):
+
+ def __init__(self, palette):
+ animator.Animation.__init__(self, 0.0, 1.0)
+ self._palette = palette
+
+ def next_frame(self, current):
+ if current == 1.0:
+ self._palette.popup(immediate=True)
+
+
+class _PopdownAnimation(animator.Animation):
+
+ def __init__(self, palette):
+ animator.Animation.__init__(self, 0.0, 1.0)
+ self._palette = palette
+
+ def next_frame(self, current):
+ if current == 1.0:
+ self._palette.popdown(immediate=True)
+
+
+class Invoker(gobject.GObject):
+
+ __gtype_name__ = 'SugarPaletteInvoker'
+
+ __gsignals__ = {
+ 'mouse-enter': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
+ 'mouse-leave': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
+ 'right-click': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
+ 'focus-out': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])),
+ }
+
+ ANCHORED = 0
+ AT_CURSOR = 1
+
+ BOTTOM = [(0.0, 0.0, 0.0, 1.0), (-1.0, 0.0, 1.0, 1.0)]
+ RIGHT = [(0.0, 0.0, 1.0, 0.0), (0.0, -1.0, 1.0, 1.0)]
+ TOP = [(0.0, -1.0, 0.0, 0.0), (-1.0, -1.0, 1.0, 0.0)]
+ LEFT = [(-1.0, 0.0, 0.0, 0.0), (-1.0, -1.0, 0.0, 1.0)]
+
+ def __init__(self):
+ gobject.GObject.__init__(self)
+
+ self.parent = None
+
+ self._screen_area = gtk.gdk.Rectangle(0, 0, gtk.gdk.screen_width(),
+ gtk.gdk.screen_height())
+ self._position_hint = self.ANCHORED
+ self._cursor_x = -1
+ self._cursor_y = -1
+ self._palette = None
+
+ def attach(self, parent):
+ self.parent = parent
+
+ def detach(self):
+ self.parent = None
+ if self._palette is not None:
+ self._palette.destroy()
+ self._palette = None
+
+ def _get_position_for_alignment(self, alignment, palette_dim):
+ palette_halign = alignment[0]
+ palette_valign = alignment[1]
+ invoker_halign = alignment[2]
+ invoker_valign = alignment[3]
+
+ if self._cursor_x == -1 or self._cursor_y == -1:
+ display = gtk.gdk.display_get_default()
+ screen_, x, y, mask_ = display.get_pointer()
+ self._cursor_x = x
+ self._cursor_y = y
+
+ if self._position_hint is self.ANCHORED:
+ rect = self.get_rect()
+ else:
+ dist = style.PALETTE_CURSOR_DISTANCE
+ rect = gtk.gdk.Rectangle(self._cursor_x - dist,
+ self._cursor_y - dist,
+ dist * 2, dist * 2)
+
+ palette_width, palette_height = palette_dim
+
+ x = rect.x + rect.width * invoker_halign + \
+ palette_width * palette_halign
+
+ y = rect.y + rect.height * invoker_valign + \
+ palette_height * palette_valign
+
+ return gtk.gdk.Rectangle(int(x), int(y),
+ palette_width, palette_height)
+
+ def _in_screen(self, rect):
+ return rect.x >= self._screen_area.x and \
+ rect.y >= self._screen_area.y and \
+ rect.x + rect.width <= self._screen_area.width and \
+ rect.y + rect.height <= self._screen_area.height
+
+ def _get_area_in_screen(self, rect):
+ """Return area of rectangle visible in the screen"""
+
+ x1 = max(rect.x, self._screen_area.x)
+ y1 = max(rect.y, self._screen_area.y)
+ x2 = min(rect.x + rect.width,
+ self._screen_area.x + self._screen_area.width)
+ y2 = min(rect.y + rect.height,
+ self._screen_area.y + self._screen_area.height)
+
+ return (x2 - x1) * (y2 - y1)
+
+ def _get_alignments(self):
+ if self._position_hint is self.AT_CURSOR:
+ return [(0.0, 0.0, 1.0, 1.0),
+ (0.0, -1.0, 1.0, 0.0),
+ (-1.0, -1.0, 0.0, 0.0),
+ (-1.0, 0.0, 0.0, 1.0)]
+ else:
+ return self.BOTTOM + self.RIGHT + self.TOP + self.LEFT
+
+ def get_position_for_alignment(self, alignment, palette_dim):
+ rect = self._get_position_for_alignment(alignment, palette_dim)
+ if self._in_screen(rect):
+ return rect
+ else:
+ return None
+
+ def get_position(self, palette_dim):
+ alignment = self.get_alignment(palette_dim)
+ rect = self._get_position_for_alignment(alignment, palette_dim)
+
+ # In case our efforts to find an optimum place inside the screen
+ # failed, just make sure the palette fits inside the screen if at all
+ # possible.
+ rect.x = max(0, rect.x)
+ rect.y = max(0, rect.y)
+
+ rect.x = min(rect.x, self._screen_area.width - rect.width)
+ rect.y = min(rect.y, self._screen_area.height - rect.height)
+
+ return rect
+
+ def get_alignment(self, palette_dim):
+ best_alignment = None
+ best_area = -1
+ for alignment in self._get_alignments():
+ pos = self._get_position_for_alignment(alignment, palette_dim)
+ if self._in_screen(pos):
+ return alignment
+
+ area = self._get_area_in_screen(pos)
+ if area > best_area:
+ best_alignment = alignment
+ best_area = area
+
+ # Palette horiz/vert alignment
+ ph = best_alignment[0]
+ pv = best_alignment[1]
+
+ # Invoker horiz/vert alignment
+ ih = best_alignment[2]
+ iv = best_alignment[3]
+
+ rect = self.get_rect()
+ screen_area = self._screen_area
+
+ if best_alignment in self.LEFT or best_alignment in self.RIGHT:
+ dtop = rect.y - screen_area.y
+ dbottom = screen_area.y + screen_area.height - rect.y - rect.width
+
+ iv = 0
+
+ # Set palette_valign to align to screen on the top
+ if dtop > dbottom:
+ pv = -float(dtop) / palette_dim[1]
+
+ # Set palette_valign to align to screen on the bottom
+ else:
+ pv = -float(palette_dim[1] - dbottom - rect.height) \
+ / palette_dim[1]
+
+ elif best_alignment in self.TOP or best_alignment in self.BOTTOM:
+ dleft = rect.x - screen_area.x
+ dright = screen_area.x + screen_area.width - rect.x - rect.width
+
+ ih = 0
+
+ # Set palette_halign to align to screen on left
+ if dleft > dright:
+ ph = -float(dleft) / palette_dim[0]
+
+ # Set palette_halign to align to screen on right
+ else:
+ ph = -float(palette_dim[0] - dright - rect.width) \
+ / palette_dim[0]
+
+ return (ph, pv, ih, iv)
+
+ def has_rectangle_gap(self):
+ return False
+
+ def draw_rectangle(self, event, palette):
+ pass
+
+ def notify_popup(self):
+ pass
+
+ def notify_popdown(self):
+ self._cursor_x = -1
+ self._cursor_y = -1
+
+ def _ensure_palette_exists(self):
+ if self.parent and self.palette is None:
+ palette = self.parent.create_palette()
+ if palette is not None:
+ self.palette = palette
+
+ def notify_mouse_enter(self):
+ self._ensure_palette_exists()
+ self.emit('mouse-enter')
+
+ def notify_mouse_leave(self):
+ self.emit('mouse-leave')
+
+ def notify_right_click(self):
+ self._ensure_palette_exists()
+ self.emit('right-click')
+
+ def get_palette(self):
+ return self._palette
+
+ def set_palette(self, palette):
+ if self._palette is not None:
+ self._palette.popdown(immediate=True)
+
+ if self._palette:
+ self._palette.props.invoker = None
+
+ self._palette = palette
+
+ if self._palette:
+ self._palette.props.invoker = self
+
+ palette = gobject.property(
+ type=object, setter=set_palette, getter=get_palette)
+
+
+class WidgetInvoker(Invoker):
+
+ def __init__(self, parent=None, widget=None):
+ Invoker.__init__(self)
+
+ self._widget = None
+ self._enter_hid = None
+ self._leave_hid = None
+ self._release_hid = None
+
+ if parent or widget:
+ self.attach_widget(parent, widget)
+
+ def attach_widget(self, parent, widget=None):
+ if widget:
+ self._widget = widget
+ else:
+ self._widget = parent
+
+ self.notify('widget')
+
+ self._enter_hid = self._widget.connect('enter-notify-event',
+ self.__enter_notify_event_cb)
+ self._leave_hid = self._widget.connect('leave-notify-event',
+ self.__leave_notify_event_cb)
+ self._release_hid = self._widget.connect('button-release-event',
+ self.__button_release_event_cb)
+
+ self.attach(parent)
+
+ def detach(self):
+ Invoker.detach(self)
+ self._widget.disconnect(self._enter_hid)
+ self._widget.disconnect(self._leave_hid)
+ self._widget.disconnect(self._release_hid)
+
+ def get_rect(self):
+ allocation = self._widget.get_allocation()
+ if self._widget.window is not None:
+ x, y = self._widget.window.get_origin()
+ else:
+ logging.warning(
+ "Trying to position palette with invoker that's not realized.")
+ x = 0
+ y = 0
+
+ if self._widget.flags() & gtk.NO_WINDOW:
+ x += allocation.x
+ y += allocation.y
+
+ width = allocation.width
+ height = allocation.height
+
+ return gtk.gdk.Rectangle(x, y, width, height)
+
+ def has_rectangle_gap(self):
+ return True
+
+ def draw_rectangle(self, event, palette):
+ if self._widget.flags() & gtk.NO_WINDOW:
+ x, y = self._widget.allocation.x, self._widget.allocation.y
+ else:
+ x = y = 0
+
+ wstyle = self._widget.get_style()
+ gap = _calculate_gap(self.get_rect(), palette.get_rect())
+
+ if gap:
+ wstyle.paint_box_gap(event.window, gtk.STATE_PRELIGHT,
+ gtk.SHADOW_IN, event.area, self._widget,
+ "palette-invoker", x, y,
+ self._widget.allocation.width,
+ self._widget.allocation.height,
+ gap[0], gap[1], gap[2])
+ else:
+ wstyle.paint_box(event.window, gtk.STATE_PRELIGHT,
+ gtk.SHADOW_IN, event.area, self._widget,
+ "palette-invoker", x, y,
+ self._widget.allocation.width,
+ self._widget.allocation.height)
+
+ def __enter_notify_event_cb(self, widget, event):
+ self.notify_mouse_enter()
+
+ def __leave_notify_event_cb(self, widget, event):
+ self.notify_mouse_leave()
+
+ def __button_release_event_cb(self, widget, event):
+ if event.button == 3:
+ self.notify_right_click()
+ return True
+ else:
+ return False
+
+ def get_toplevel(self):
+ return self._widget.get_toplevel()
+
+ def notify_popup(self):
+ Invoker.notify_popup(self)
+ self._widget.queue_draw()
+
+ def notify_popdown(self):
+ Invoker.notify_popdown(self)
+ self._widget.queue_draw()
+
+ def _get_widget(self):
+ return self._widget
+ widget = gobject.property(type=object, getter=_get_widget, setter=None)
+
+
+class CanvasInvoker(Invoker):
+
+ def __init__(self, parent=None):
+ Invoker.__init__(self)
+
+ self._position_hint = self.AT_CURSOR
+ self._motion_hid = None
+ self._release_hid = None
+ self._item = None
+
+ if parent:
+ self.attach(parent)
+
+ def attach(self, parent):
+ Invoker.attach(self, parent)
+
+ self._item = parent
+ self._motion_hid = self._item.connect('motion-notify-event',
+ self.__motion_notify_event_cb)
+ self._release_hid = self._item.connect('button-release-event',
+ self.__button_release_event_cb)
+
+ def detach(self):
+ Invoker.detach(self)
+ self._item.disconnect(self._motion_hid)
+ self._item.disconnect(self._release_hid)
+
+ def get_default_position(self):
+ return self.AT_CURSOR
+
+ def get_rect(self):
+ context = self._item.get_context()
+ if context:
+ x, y = context.translate_to_screen(self._item)
+ width, height = self._item.get_allocation()
+ return gtk.gdk.Rectangle(x, y, width, height)
+ else:
+ return gtk.gdk.Rectangle()
+
+ def __motion_notify_event_cb(self, button, event):
+ if event.detail == hippo.MOTION_DETAIL_ENTER:
+ self.notify_mouse_enter()
+ elif event.detail == hippo.MOTION_DETAIL_LEAVE:
+ self.notify_mouse_leave()
+
+ return False
+
+ def __button_release_event_cb(self, button, event):
+ if event.button == 3:
+ self.notify_right_click()
+ return True
+ else:
+ return False
+
+ def get_toplevel(self):
+ return hippo.get_canvas_for_item(self._item).get_toplevel()
+
+
+class ToolInvoker(WidgetInvoker):
+
+ def __init__(self, parent=None):
+ WidgetInvoker.__init__(self)
+
+ if parent:
+ self.attach_tool(parent)
+
+ def attach_tool(self, widget):
+ self.attach_widget(widget, widget.child)
+
+ def _get_alignments(self):
+ parent = self._widget.get_parent()
+ if parent is None:
+ return WidgetInvoker._get_alignments()
+
+ if parent.get_orientation() is gtk.ORIENTATION_HORIZONTAL:
+ return self.BOTTOM + self.TOP
+ else:
+ return self.LEFT + self.RIGHT
+
+
+class CellRendererInvoker(Invoker):
+
+ def __init__(self):
+ Invoker.__init__(self)
+
+ self._position_hint = self.AT_CURSOR
+ self._tree_view = None
+ self._cell_renderer = None
+ self._motion_hid = None
+ self._leave_hid = None
+ self._release_hid = None
+ self.path = None
+
+ def attach_cell_renderer(self, tree_view, cell_renderer):
+ self._tree_view = tree_view
+ self._cell_renderer = cell_renderer
+
+ self._motion_hid = tree_view.connect('motion-notify-event',
+ self.__motion_notify_event_cb)
+ self._leave_hid = tree_view.connect('leave-notify-event',
+ self.__leave_notify_event_cb)
+ self._release_hid = tree_view.connect('button-release-event',
+ self.__button_release_event_cb)
+
+ self.attach(cell_renderer)
+
+ def detach(self):
+ Invoker.detach(self)
+ self._tree_view.disconnect(self._motion_hid)
+ self._tree_view.disconnect(self._leave_hid)
+ self._tree_view.disconnect(self._release_hid)
+
+ def get_rect(self):
+ allocation = self._tree_view.get_allocation()
+ if self._tree_view.window is not None:
+ x, y = self._tree_view.window.get_origin()
+ else:
+ logging.warning(
+ "Trying to position palette with invoker that's not realized.")
+ x = 0
+ y = 0
+
+ if self._tree_view.flags() & gtk.NO_WINDOW:
+ x += allocation.x
+ y += allocation.y
+
+ width = allocation.width
+ height = allocation.height
+
+ return gtk.gdk.Rectangle(x, y, width, height)
+
+ def __motion_notify_event_cb(self, widget, event):
+ if event.window != widget.get_bin_window():
+ return
+ if self._point_in_cell_renderer(event.x, event.y):
+
+ tree_view = self._tree_view
+ path, column_, x_, y_ = tree_view.get_path_at_pos(int(event.x),
+ int(event.y))
+ if path != self.path:
+ if self.path is not None:
+ self._redraw_path(self.path)
+ if path is not None:
+ self._redraw_path(path)
+ if self.palette is not None:
+ self.palette.popdown(immediate=True)
+ self.palette = None
+ self.path = path
+
+ self.notify_mouse_enter()
+ else:
+ if self.path is not None:
+ self._redraw_path(self.path)
+ self.path = None
+ self.notify_mouse_leave()
+
+ def _redraw_path(self, path):
+ for column in self._tree_view.get_columns():
+ if self._cell_renderer in column.get_cell_renderers():
+ break
+ area = self._tree_view.get_background_area(path, column)
+ x, y = \
+ self._tree_view.convert_bin_window_to_widget_coords(area.x, area.y)
+ self._tree_view.queue_draw_area(x, y, area.width, area.height)
+
+ def __leave_notify_event_cb(self, widget, event):
+ self.notify_mouse_leave()
+
+ def __button_release_event_cb(self, widget, event):
+ if event.button == 1 and self._point_in_cell_renderer(event.x,
+ event.y):
+ tree_view = self._tree_view
+ path, column_, x_, y_ = tree_view.get_path_at_pos(int(event.x),
+ int(event.y))
+ self._cell_renderer.emit('clicked', path)
+ # So the treeview receives it and knows a drag isn't going on
+ return False
+ if event.button == 3 and self._point_in_cell_renderer(event.x,
+ event.y):
+ self.notify_right_click()
+ return True
+ else:
+ return False
+
+ def _point_in_cell_renderer(self, event_x, event_y):
+ pos = self._tree_view.get_path_at_pos(int(event_x), int(event_y))
+ if pos is None:
+ return False
+
+ path_, column, x, y_ = pos
+
+ for cell_renderer in column.get_cell_renderers():
+ if cell_renderer == self._cell_renderer:
+ cell_x, cell_width = column.cell_get_position(cell_renderer)
+ if x > cell_x and x < (cell_x + cell_width):
+ return True
+ return False
+
+ return False
+
+ def get_toplevel(self):
+ return self._tree_view.get_toplevel()
+
+ def notify_popup(self):
+ Invoker.notify_popup(self)
+
+ def notify_popdown(self):
+ Invoker.notify_popdown(self)
+ self.palette = None
+
+ def get_default_position(self):
+ return self.AT_CURSOR
diff --git a/toolkit/src/sugar/graphics/panel.py b/toolkit/src/sugar/graphics/panel.py
new file mode 100644
index 0000000..441e7a9
--- /dev/null
+++ b/toolkit/src/sugar/graphics/panel.py
@@ -0,0 +1,30 @@
+# Copyright (C) 2007, Red Hat, Inc.
+#
+# 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.
+
+"""
+STABLE.
+"""
+
+import gtk
+
+
+class Panel(gtk.VBox):
+
+ __gtype_name__ = 'SugarPanel'
+
+ def __init__(self):
+ gtk.VBox.__init__(self)
diff --git a/toolkit/src/sugar/graphics/radiopalette.py b/toolkit/src/sugar/graphics/radiopalette.py
new file mode 100644
index 0000000..8199165
--- /dev/null
+++ b/toolkit/src/sugar/graphics/radiopalette.py
@@ -0,0 +1,104 @@
+# Copyright (C) 2009, Aleksey Lim
+#
+# 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 gtk
+
+from sugar.graphics.toolbutton import ToolButton
+from sugar.graphics.palette import Palette
+
+
+class RadioMenuButton(ToolButton):
+
+ def __init__(self, **kwargs):
+ ToolButton.__init__(self, **kwargs)
+ self.selected_button = None
+
+ if self.props.palette:
+ self.__palette_cb(None, None)
+
+ self.connect('notify::palette', self.__palette_cb)
+
+ def __palette_cb(self, widget, pspec):
+ if not isinstance(self.props.palette, RadioPalette):
+ return
+ self.props.palette.update_button()
+
+ def do_clicked(self):
+ if self.palette is None:
+ return
+ if self.palette.is_up() and \
+ self.palette.palette_state == Palette.SECONDARY:
+ self.palette.popdown(immediate=True)
+ else:
+ self.palette.popup(immediate=True, state=Palette.SECONDARY)
+
+
+class RadioToolsButton(RadioMenuButton):
+
+ def __init__(self, **kwargs):
+ RadioMenuButton.__init__(self, **kwargs)
+
+ def do_clicked(self):
+ if not self.selected_button:
+ return
+ self.selected_button.emit('clicked')
+
+
+class RadioPalette(Palette):
+
+ def __init__(self, **kwargs):
+ Palette.__init__(self, **kwargs)
+
+ self.button_box = gtk.HBox()
+ self.button_box.show()
+ self.set_content(self.button_box)
+
+ def append(self, button, label):
+ children = self.button_box.get_children()
+
+ if button.palette is not None:
+ raise RuntimeError("Palette's button should not have sub-palettes")
+
+ button.show()
+ button.connect('clicked', self.__clicked_cb)
+ self.button_box.pack_start(button, fill=False)
+ button.palette_label = label
+
+ if not children:
+ self.__clicked_cb(button)
+
+ def update_button(self):
+ for i in self.button_box.get_children():
+ self.__clicked_cb(i)
+
+ def __clicked_cb(self, button):
+ if not button.get_active():
+ return
+
+ self.set_primary_text(button.palette_label)
+ self.popdown(immediate=True)
+
+ if self.invoker is not None:
+ parent = self.invoker.parent
+ else:
+ parent = None
+ if not isinstance(parent, RadioMenuButton):
+ return
+
+ parent.props.label = button.palette_label
+ parent.set_icon(button.props.icon_name)
+ parent.selected_button = button
diff --git a/toolkit/src/sugar/graphics/radiotoolbutton.py b/toolkit/src/sugar/graphics/radiotoolbutton.py
new file mode 100644
index 0000000..37267b4
--- /dev/null
+++ b/toolkit/src/sugar/graphics/radiotoolbutton.py
@@ -0,0 +1,182 @@
+# Copyright (C) 2007, Red Hat, Inc.
+# Copyright (C) 2007-2008, 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.
+
+"""
+STABLE.
+"""
+
+import gtk
+import gobject
+
+from sugar.graphics.icon import Icon
+from sugar.graphics.palette import Palette, ToolInvoker
+from sugar.graphics import toolbutton
+
+
+class RadioToolButton(gtk.RadioToolButton):
+ """
+ An implementation of a "push" button.
+
+ """
+
+ __gtype_name__ = 'SugarRadioToolButton'
+
+ def __init__(self, **kwargs):
+ self._accelerator = None
+ self._tooltip = None
+ self._xo_color = None
+ self._palette_invoker = ToolInvoker()
+
+ gobject.GObject.__init__(self, **kwargs)
+
+ self._palette_invoker.attach_tool(self)
+
+ self.connect('destroy', self.__destroy_cb)
+
+ def __destroy_cb(self, icon):
+ if self._palette_invoker is not None:
+ self._palette_invoker.detach()
+
+ def set_tooltip(self, tooltip):
+ """
+ Set a simple palette with just a single label.
+
+ Parameters
+ ----------
+ tooltip:
+
+ Returns
+ -------
+ None
+
+ """
+ if self.palette is None or self._tooltip is None:
+ self.palette = Palette(tooltip)
+ elif self.palette is not None:
+ self.palette.set_primary_text(tooltip)
+
+ self._tooltip = tooltip
+
+ # Set label, shows up when toolbar overflows
+ gtk.RadioToolButton.set_label(self, tooltip)
+
+ def get_tooltip(self):
+ return self._tooltip
+
+ tooltip = gobject.property(type=str, setter=set_tooltip,
+ getter=get_tooltip)
+
+ def set_accelerator(self, accelerator):
+ """
+ Sets the accelerator.
+
+ Parameters
+ ----------
+ accelerator:
+
+ Returns
+ -------
+ None
+
+ """
+ self._accelerator = accelerator
+ toolbutton.setup_accelerator(self)
+
+ def get_accelerator(self):
+ """
+ Returns the accelerator for the button.
+
+ Parameters
+ ----------
+ None
+
+ Returns
+ ------
+ accelerator:
+
+ """
+ return self._accelerator
+
+ accelerator = gobject.property(type=str, setter=set_accelerator,
+ getter=get_accelerator)
+
+ def set_named_icon(self, named_icon):
+ icon = Icon(icon_name=named_icon,
+ xo_color=self._xo_color,
+ icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR)
+ self.set_icon_widget(icon)
+ icon.show()
+
+ def get_named_icon(self):
+ if self.props.icon_widget is not None:
+ return self.props.icon_widget.props.icon_name
+ else:
+ return None
+
+ named_icon = gobject.property(type=str, setter=set_named_icon,
+ getter=get_named_icon)
+
+ def set_xo_color(self, xo_color):
+ if self._xo_color != xo_color:
+ self._xo_color = xo_color
+ if self.props.icon_widget is not None:
+ self.props.icon_widget.props.xo_color = xo_color
+
+ def get_xo_color(self):
+ return self._xo_color
+
+ xo_color = gobject.property(type=object, setter=set_xo_color,
+ getter=get_xo_color)
+
+ def create_palette(self):
+ return None
+
+ def get_palette(self):
+ return self._palette_invoker.palette
+
+ def set_palette(self, palette):
+ self._palette_invoker.palette = palette
+
+ palette = gobject.property(
+ type=object, setter=set_palette, getter=get_palette)
+
+ def get_palette_invoker(self):
+ return self._palette_invoker
+
+ def set_palette_invoker(self, palette_invoker):
+ self._palette_invoker.detach()
+ self._palette_invoker = palette_invoker
+
+ palette_invoker = gobject.property(
+ type=object, setter=set_palette_invoker, getter=get_palette_invoker)
+
+ def do_expose_event(self, event):
+ child = self.get_child()
+ allocation = self.get_allocation()
+
+ if self.palette and self.palette.is_up():
+ invoker = self.palette.props.invoker
+ invoker.draw_rectangle(event, self.palette)
+ elif child.state == gtk.STATE_PRELIGHT:
+ child.style.paint_box(event.window, gtk.STATE_PRELIGHT,
+ gtk.SHADOW_NONE, event.area,
+ child, "toolbutton-prelight",
+ allocation.x, allocation.y,
+ allocation.width, allocation.height)
+
+ gtk.RadioToolButton.do_expose_event(self, event)
diff --git a/toolkit/src/sugar/graphics/roundbox.py b/toolkit/src/sugar/graphics/roundbox.py
new file mode 100644
index 0000000..75141f0
--- /dev/null
+++ b/toolkit/src/sugar/graphics/roundbox.py
@@ -0,0 +1,71 @@
+# Copyright (C) 2006-2007 Red Hat, Inc.
+#
+# 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.
+
+"""
+STABLE.
+"""
+
+import math
+
+import hippo
+
+from sugar.graphics import style
+
+
+class CanvasRoundBox(hippo.CanvasBox, hippo.CanvasItem):
+ __gtype_name__ = 'SugarRoundBox'
+
+ _BORDER_DEFAULT = style.LINE_WIDTH
+
+ def __init__(self, **kwargs):
+ hippo.CanvasBox.__init__(self, **kwargs)
+
+ # TODO: we should calculate radius depending on the height of the box.
+ self._radius = style.zoom(10)
+
+ self.props.orientation = hippo.ORIENTATION_HORIZONTAL
+ self.props.border = self._BORDER_DEFAULT
+ self.props.border_left = self._radius
+ self.props.border_right = self._radius
+ self.props.border_color = style.COLOR_BLACK.get_int()
+
+ def do_paint_background(self, cr, damaged_box):
+ [width, height] = self.get_allocation()
+
+ x = self._BORDER_DEFAULT / 2
+ y = self._BORDER_DEFAULT / 2
+ width -= self._BORDER_DEFAULT
+ height -= self._BORDER_DEFAULT
+
+ cr.move_to(x + self._radius, y)
+ cr.arc(x + width - self._radius, y + self._radius,
+ self._radius, math.pi * 1.5, math.pi * 2)
+ cr.arc(x + width - self._radius, x + height - self._radius,
+ self._radius, 0, math.pi * 0.5)
+ cr.arc(x + self._radius, y + height - self._radius,
+ self._radius, math.pi * 0.5, math.pi)
+ cr.arc(x + self._radius, y + self._radius, self._radius,
+ math.pi, math.pi * 1.5)
+
+ hippo.cairo_set_source_rgba32(cr, self.props.background_color)
+ cr.fill_preserve()
+
+ # TODO: we should be more consistent here with the border properties.
+ if self.props.border_color:
+ hippo.cairo_set_source_rgba32(cr, self.props.border_color)
+ cr.set_line_width(self.props.border_top)
+ cr.stroke()
diff --git a/toolkit/src/sugar/graphics/style.py b/toolkit/src/sugar/graphics/style.py
new file mode 100644
index 0000000..2828b7f
--- /dev/null
+++ b/toolkit/src/sugar/graphics/style.py
@@ -0,0 +1,148 @@
+# Copyright (C) 2007, Red Hat, Inc.
+#
+# 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.
+
+"""
+All the constants are expressed in pixels. They are defined for the XO screen
+and are usually adapted to different resolution by applying a zoom factor.
+
+STABLE.
+"""
+
+import os
+import logging
+
+import gtk
+import pango
+import gconf
+
+
+FOCUS_LINE_WIDTH = 2
+_TAB_CURVATURE = 1
+
+
+def _compute_zoom_factor():
+ if os.environ.has_key('SUGAR_SCALING'):
+ try:
+ scaling = int(os.environ['SUGAR_SCALING'])
+ return scaling / 100.0
+ except ValueError:
+ logging.error('Invalid SUGAR_SCALING.')
+
+ return 1.0
+
+
+class Font(object):
+
+ def __init__(self, desc):
+ self._desc = desc
+
+ def __str__(self):
+ return self._desc
+
+ def get_pango_desc(self):
+ return pango.FontDescription(self._desc)
+
+
+class Color(object):
+
+ def __init__(self, color, alpha=1.0):
+ self._r, self._g, self._b = self._html_to_rgb(color)
+ self._a = alpha
+
+ def get_rgba(self):
+ return (self._r, self._g, self._b, self._a)
+
+ def get_int(self):
+ return int(self._a * 255) + (int(self._b * 255) << 8) + \
+ (int(self._g * 255) << 16) + (int(self._r * 255) << 24)
+
+ def get_gdk_color(self):
+ return gtk.gdk.Color(int(self._r * 65535), int(self._g * 65535),
+ int(self._b * 65535))
+
+ def get_html(self):
+ return '#%02x%02x%02x' % (self._r * 255, self._g * 255, self._b * 255)
+
+ def _html_to_rgb(self, html_color):
+ """ #RRGGBB -> (r, g, b) tuple (in float format) """
+
+ html_color = html_color.strip()
+ if html_color[0] == '#':
+ html_color = html_color[1:]
+ if len(html_color) != 6:
+ raise ValueError, "input #%s is not in #RRGGBB format" % html_color
+
+ r, g, b = html_color[:2], html_color[2:4], html_color[4:]
+ r, g, b = [int(n, 16) for n in (r, g, b)]
+ r, g, b = (r / 255.0, g / 255.0, b / 255.0)
+
+ return (r, g, b)
+
+ def get_svg(self):
+ if self._a == 0.0:
+ return 'none'
+ else:
+ return self.get_html()
+
+
+def zoom(units):
+ return int(ZOOM_FACTOR * units)
+
+
+ZOOM_FACTOR = _compute_zoom_factor()
+
+DEFAULT_SPACING = zoom(15)
+DEFAULT_PADDING = zoom(6)
+GRID_CELL_SIZE = zoom(75)
+LINE_WIDTH = zoom(2)
+
+STANDARD_ICON_SIZE = zoom(55)
+SMALL_ICON_SIZE = zoom(55 * 0.5)
+MEDIUM_ICON_SIZE = zoom(55 * 1.5)
+LARGE_ICON_SIZE = zoom(55 * 2.0)
+XLARGE_ICON_SIZE = zoom(55 * 2.75)
+
+client = gconf.client_get_default()
+FONT_SIZE = client.get_float('/desktop/sugar/font/default_size')
+FONT_FACE = client.get_string('/desktop/sugar/font/default_face')
+
+FONT_NORMAL = Font('%s %f' % (FONT_FACE, FONT_SIZE))
+FONT_BOLD = Font('%s bold %f' % (FONT_FACE, FONT_SIZE))
+FONT_NORMAL_H = zoom(24)
+FONT_BOLD_H = zoom(24)
+
+TOOLBOX_SEPARATOR_HEIGHT = zoom(9)
+TOOLBOX_HORIZONTAL_PADDING = zoom(75)
+TOOLBOX_TAB_VBORDER = int((zoom(36) - FONT_NORMAL_H - FOCUS_LINE_WIDTH) / 2)
+TOOLBOX_TAB_HBORDER = zoom(15) - FOCUS_LINE_WIDTH - _TAB_CURVATURE
+TOOLBOX_TAB_LABEL_WIDTH = zoom(150 - 15 * 2)
+
+COLOR_BLACK = Color('#000000')
+COLOR_WHITE = Color('#FFFFFF')
+COLOR_TRANSPARENT = Color('#FFFFFF', alpha=0.0)
+COLOR_PANEL_GREY = Color('#C0C0C0')
+COLOR_SELECTION_GREY = Color('#A6A6A6')
+COLOR_TOOLBAR_GREY = Color('#282828')
+COLOR_BUTTON_GREY = Color('#808080')
+COLOR_INACTIVE_FILL = Color('#9D9FA1')
+COLOR_INACTIVE_STROKE = Color('#757575')
+COLOR_TEXT_FIELD_GREY = Color('#E5E5E5')
+COLOR_HIGHLIGHT = Color('#E7E7E7')
+
+PALETTE_CURSOR_DISTANCE = zoom(10)
+
+TOOLBAR_ARROW_SIZE = zoom(24)
diff --git a/toolkit/src/sugar/graphics/toggletoolbutton.py b/toolkit/src/sugar/graphics/toggletoolbutton.py
new file mode 100644
index 0000000..cdaf2f0
--- /dev/null
+++ b/toolkit/src/sugar/graphics/toggletoolbutton.py
@@ -0,0 +1,91 @@
+# Copyright (C) 2007, Red Hat, Inc.
+#
+# 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.
+
+"""
+STABLE.
+"""
+
+import gobject
+import gtk
+
+from sugar.graphics.icon import Icon
+from sugar.graphics.palette import Palette, ToolInvoker
+
+
+class ToggleToolButton(gtk.ToggleToolButton):
+
+ __gtype_name__ = "SugarToggleToolButton"
+
+ def __init__(self, named_icon=None):
+ gtk.ToggleToolButton.__init__(self)
+
+ self._palette_invoker = ToolInvoker(self)
+ self.set_named_icon(named_icon)
+
+ self.connect('destroy', self.__destroy_cb)
+
+ def __destroy_cb(self, icon):
+ if self._palette_invoker is not None:
+ self._palette_invoker.detach()
+
+ def set_named_icon(self, named_icon):
+ icon = Icon(icon_name=named_icon)
+ self.set_icon_widget(icon)
+ icon.show()
+
+ def create_palette(self):
+ return None
+
+ def get_palette(self):
+ return self._palette_invoker.palette
+
+ def set_palette(self, palette):
+ self._palette_invoker.palette = palette
+
+ palette = gobject.property(
+ type=object, setter=set_palette, getter=get_palette)
+
+ def get_palette_invoker(self):
+ return self._palette_invoker
+
+ def set_palette_invoker(self, palette_invoker):
+ self._palette_invoker.detach()
+ self._palette_invoker = palette_invoker
+
+ palette_invoker = gobject.property(
+ type=object, setter=set_palette_invoker, getter=get_palette_invoker)
+
+ def set_tooltip(self, text):
+ self.set_palette(Palette(text))
+
+ def do_expose_event(self, event):
+ allocation = self.get_allocation()
+ child = self.get_child()
+
+ if self.palette and self.palette.is_up():
+ invoker = self.palette.props.invoker
+ invoker.draw_rectangle(event, self.palette)
+ elif child.state == gtk.STATE_PRELIGHT:
+ child.style.paint_box(event.window, gtk.STATE_PRELIGHT,
+ gtk.SHADOW_NONE, event.area,
+ child, "toolbutton-prelight",
+ allocation.x, allocation.y,
+ allocation.width, allocation.height)
+
+ gtk.ToggleToolButton.do_expose_event(self, event)
+
+ palette = property(get_palette, set_palette)
diff --git a/toolkit/src/sugar/graphics/toolbarbox.py b/toolkit/src/sugar/graphics/toolbarbox.py
new file mode 100644
index 0000000..b674e8d
--- /dev/null
+++ b/toolkit/src/sugar/graphics/toolbarbox.py
@@ -0,0 +1,323 @@
+# Copyright (C) 2009, Aleksey Lim
+#
+# 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 gtk
+import gobject
+
+from sugar.graphics import style
+from sugar.graphics.palette import PaletteWindow, ToolInvoker
+from sugar.graphics.toolbutton import ToolButton
+from sugar.graphics import palettegroup
+
+
+class ToolbarButton(ToolButton):
+
+ def __init__(self, page=None, **kwargs):
+ ToolButton.__init__(self, **kwargs)
+
+ self.page_widget = None
+
+ self.set_page(page)
+
+ self.connect('clicked',
+ lambda widget: self.set_expanded(not self.is_expanded()))
+
+ def get_toolbar_box(self):
+ if not hasattr(self.parent, 'owner'):
+ return None
+ return self.parent.owner
+
+ toolbar_box = property(get_toolbar_box)
+
+ def get_page(self):
+ if self.page_widget is None:
+ return None
+ return _get_embedded_page(self.page_widget)
+
+ def set_page(self, page):
+ if page is None:
+ self.page_widget = None
+ return
+ self.page_widget, alignment_ = _embed_page(_Box, page)
+ self.page_widget.set_size_request(-1, style.GRID_CELL_SIZE)
+ page.show()
+ if self.props.palette is None:
+ self.props.palette = _ToolbarPalette(invoker=ToolInvoker(self))
+ self._move_page_to_palette()
+
+ page = gobject.property(type=object, getter=get_page, setter=set_page)
+
+ def is_in_palette(self):
+ return self.page is not None and \
+ self.page_widget.parent == self.props.palette
+
+ def is_expanded(self):
+ return self.page is not None and \
+ not self.is_in_palette()
+
+ def popdown(self):
+ if self.props.palette is not None:
+ self.props.palette.popdown(immediate=True)
+
+ def set_expanded(self, expanded):
+ self.popdown()
+
+ if self.page is None or self.is_expanded() == expanded:
+ return
+
+ if not expanded:
+ self._move_page_to_palette()
+ return
+
+ box = self.toolbar_box
+
+ if box.expanded_button is not None:
+ if box.expanded_button.window is not None:
+ # need to redraw it to erase arrow
+ box.expanded_button.window.invalidate_rect(None, True)
+ box.expanded_button.set_expanded(False)
+ box.expanded_button = self
+
+ self._unparent()
+
+ self.modify_bg(gtk.STATE_NORMAL, box.background)
+ _setup_page(self.page_widget, box.background, box.props.padding)
+ box.pack_start(self.page_widget)
+
+ def _move_page_to_palette(self):
+ if self.is_in_palette():
+ return
+
+ self._unparent()
+
+ if isinstance(self.props.palette, _ToolbarPalette):
+ self.props.palette.add(self.page_widget)
+
+ def _unparent(self):
+ if self.page_widget.parent is None:
+ return
+ self.page_widget.parent.remove(self.page_widget)
+
+ def do_expose_event(self, event):
+ if not self.is_expanded() or self.props.palette is not None and \
+ self.props.palette.is_up():
+ ToolButton.do_expose_event(self, event)
+ _paint_arrow(self, event, gtk.ARROW_DOWN)
+ return
+
+ alloc = self.allocation
+
+ self.get_style().paint_box(event.window,
+ gtk.STATE_NORMAL, gtk.SHADOW_IN, event.area, self,
+ 'palette-invoker', alloc.x, 0,
+ alloc.width, alloc.height + style.FOCUS_LINE_WIDTH)
+
+ if self.child.state != gtk.STATE_PRELIGHT:
+ self.get_style().paint_box(event.window,
+ gtk.STATE_NORMAL, gtk.SHADOW_NONE, event.area, self, None,
+ alloc.x + style.FOCUS_LINE_WIDTH, style.FOCUS_LINE_WIDTH,
+ alloc.width - style.FOCUS_LINE_WIDTH * 2, alloc.height)
+
+ gtk.ToolButton.do_expose_event(self, event)
+ _paint_arrow(self, event, gtk.ARROW_UP)
+
+
+class ToolbarBox(gtk.VBox):
+
+ def __init__(self, padding=style.TOOLBOX_HORIZONTAL_PADDING):
+ gtk.VBox.__init__(self)
+ self._expanded_button_index = -1
+ self.background = None
+
+ self._toolbar = gtk.Toolbar()
+ self._toolbar.owner = self
+ self._toolbar.connect('remove', self.__remove_cb)
+
+ self._toolbar_widget, self._toolbar_alignment = \
+ _embed_page(gtk.EventBox, self._toolbar)
+ self.pack_start(self._toolbar_widget)
+
+ self.props.padding = padding
+ self.modify_bg(gtk.STATE_NORMAL,
+ style.COLOR_TOOLBAR_GREY.get_gdk_color())
+
+ def get_toolbar(self):
+ return self._toolbar
+
+ toolbar = property(get_toolbar)
+
+ def get_expanded_button(self):
+ if self._expanded_button_index == -1:
+ return None
+ return self.toolbar.get_nth_item(self._expanded_button_index)
+
+ def set_expanded_button(self, button):
+ if not button in self.toolbar:
+ self._expanded_button_index = -1
+ return
+ self._expanded_button_index = self.toolbar.get_item_index(button)
+
+ expanded_button = property(get_expanded_button, set_expanded_button)
+
+ def get_padding(self):
+ return self._toolbar_alignment.props.left_padding
+
+ def set_padding(self, pad):
+ self._toolbar_alignment.set_padding(0, 0, pad, pad)
+
+ padding = gobject.property(type=object,
+ getter=get_padding, setter=set_padding)
+
+ def modify_bg(self, state, color):
+ if state == gtk.STATE_NORMAL:
+ self.background = color
+ self._toolbar_widget.modify_bg(state, color)
+ self.toolbar.modify_bg(state, color)
+
+ def __remove_cb(self, sender, button):
+ if not isinstance(button, ToolbarButton):
+ return
+ button.popdown()
+ if button == self.expanded_button:
+ self.remove(button.page_widget)
+ self._expanded_button_index = -1
+
+
+class _ToolbarPalette(PaletteWindow):
+
+ def __init__(self, **kwargs):
+ PaletteWindow.__init__(self, **kwargs)
+ self.set_border_width(0)
+ self._has_focus = False
+
+ group = palettegroup.get_group('default')
+ group.connect('popdown', self.__group_popdown_cb)
+ self.set_group_id('toolbarbox')
+
+ def get_expanded_button(self):
+ return self.invoker.parent
+
+ expanded_button = property(get_expanded_button)
+
+ def on_invoker_enter(self):
+ PaletteWindow.on_invoker_enter(self)
+ self._set_focus(True)
+
+ def on_invoker_leave(self):
+ PaletteWindow.on_invoker_leave(self)
+ self._set_focus(False)
+
+ def on_enter(self, event):
+ PaletteWindow.on_enter(self, event)
+ self._set_focus(True)
+
+ def on_leave(self, event):
+ PaletteWindow.on_enter(self, event)
+ self._set_focus(False)
+
+ def _set_focus(self, new_focus):
+ self._has_focus = new_focus
+ if not self._has_focus:
+ group = palettegroup.get_group('default')
+ if not group.is_up():
+ self.popdown()
+
+ def do_size_request(self, requisition):
+ gtk.Window.do_size_request(self, requisition)
+ requisition.width = max(requisition.width,
+ gtk.gdk.screen_width())
+
+ def popup(self, immediate=False):
+ button = self.expanded_button
+ if button.is_expanded():
+ return
+ box = button.toolbar_box
+ _setup_page(button.page_widget, style.COLOR_BLACK.get_gdk_color(),
+ box.props.padding)
+ PaletteWindow.popup(self, immediate)
+
+ def __group_popdown_cb(self, group):
+ if not self._has_focus:
+ self.popdown(immediate=True)
+
+
+class _Box(gtk.EventBox):
+
+ def __init__(self):
+ gtk.EventBox.__init__(self)
+ self.connect('expose-event', self.do_expose_event)
+ self.set_app_paintable(True)
+
+ def do_expose_event(self, widget, event):
+ if self.parent.expanded_button is None:
+ return
+ alloc = self.parent.expanded_button.allocation
+ self.get_style().paint_box(event.window,
+ gtk.STATE_NORMAL, gtk.SHADOW_IN, event.area, self,
+ 'palette-invoker', -style.FOCUS_LINE_WIDTH, 0,
+ self.allocation.width + style.FOCUS_LINE_WIDTH * 2,
+ self.allocation.height + style.FOCUS_LINE_WIDTH)
+ self.get_style().paint_box(event.window,
+ gtk.STATE_NORMAL, gtk.SHADOW_NONE, event.area, self, None,
+ alloc.x + style.FOCUS_LINE_WIDTH, 0,
+ alloc.width - style.FOCUS_LINE_WIDTH * 2,
+ style.FOCUS_LINE_WIDTH)
+
+
+def _setup_page(page_widget, color, hpad):
+ vpad = style.FOCUS_LINE_WIDTH
+ page_widget.child.set_padding(vpad, vpad, hpad, hpad)
+
+ page = _get_embedded_page(page_widget)
+ page.modify_bg(gtk.STATE_NORMAL, color)
+ if isinstance(page, gtk.Container):
+ for i in page.get_children():
+ i.modify_bg(gtk.STATE_INSENSITIVE, color)
+
+ page_widget.modify_bg(gtk.STATE_NORMAL, color)
+ page_widget.modify_bg(gtk.STATE_PRELIGHT, color)
+
+
+def _embed_page(box_class, page):
+ page.show()
+
+ alignment = gtk.Alignment(0.0, 0.0, 1.0, 1.0)
+ alignment.add(page)
+ alignment.show()
+
+ page_widget = box_class()
+ page_widget.modify_bg(gtk.STATE_ACTIVE,
+ style.COLOR_BUTTON_GREY.get_gdk_color())
+ page_widget.add(alignment)
+ page_widget.show()
+
+ return (page_widget, alignment)
+
+
+def _get_embedded_page(page_widget):
+ return page_widget.child.child
+
+
+def _paint_arrow(widget, event, arrow_type):
+ alloc = widget.allocation
+ x = alloc.x + alloc.width / 2 - style.TOOLBAR_ARROW_SIZE / 2
+ y = alloc.y + alloc.height - int(style.TOOLBAR_ARROW_SIZE * .85)
+
+ widget.get_style().paint_arrow(event.window,
+ gtk.STATE_NORMAL, gtk.SHADOW_NONE, event.area, widget,
+ None, arrow_type, True,
+ x, y, style.TOOLBAR_ARROW_SIZE, style.TOOLBAR_ARROW_SIZE)
diff --git a/toolkit/src/sugar/graphics/toolbox.py b/toolkit/src/sugar/graphics/toolbox.py
new file mode 100644
index 0000000..9f29281
--- /dev/null
+++ b/toolkit/src/sugar/graphics/toolbox.py
@@ -0,0 +1,101 @@
+# Copyright (C) 2007, Red Hat, Inc.
+#
+# 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.
+
+"""
+STABLE.
+"""
+
+import gtk
+import gobject
+import hippo
+
+from sugar.graphics import style
+
+
+class Toolbox(gtk.VBox):
+
+ __gtype_name__ = 'SugarToolbox'
+
+ __gsignals__ = {
+ 'current-toolbar-changed': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([int])),
+ }
+
+ def __init__(self):
+ gtk.VBox.__init__(self)
+
+ self._notebook = gtk.Notebook()
+ self._notebook.set_tab_pos(gtk.POS_BOTTOM)
+ self._notebook.set_show_border(False)
+ self._notebook.set_show_tabs(False)
+ self._notebook.props.tab_vborder = style.TOOLBOX_TAB_VBORDER
+ self._notebook.props.tab_hborder = style.TOOLBOX_TAB_HBORDER
+ self.pack_start(self._notebook)
+ self._notebook.show()
+
+ # FIXME improve gtk.Notebook and do this in the theme
+ self._separator = hippo.Canvas()
+ box = hippo.CanvasBox(
+ border_color=style.COLOR_BUTTON_GREY.get_int(),
+ background_color=style.COLOR_PANEL_GREY.get_int(),
+ box_height=style.TOOLBOX_SEPARATOR_HEIGHT,
+ border_bottom=style.LINE_WIDTH)
+ self._separator.set_root(box)
+ self.pack_start(self._separator, False)
+
+ self._notebook.connect('notify::page', self._notify_page_cb)
+
+ def _notify_page_cb(self, notebook, pspec):
+ self.emit('current-toolbar-changed', notebook.props.page)
+
+ def add_toolbar(self, name, toolbar):
+ label = gtk.Label(name)
+ width, height_ = label.size_request()
+ label.set_size_request(max(width, style.TOOLBOX_TAB_LABEL_WIDTH), -1)
+ label.set_alignment(0.0, 0.5)
+
+ event_box = gtk.EventBox()
+
+ alignment = gtk.Alignment(0.0, 0.0, 1.0, 1.0)
+ alignment.set_padding(0, 0, style.TOOLBOX_HORIZONTAL_PADDING,
+ style.TOOLBOX_HORIZONTAL_PADDING)
+
+ alignment.add(toolbar)
+ event_box.add(alignment)
+ alignment.show()
+ event_box.show()
+
+ self._notebook.append_page(event_box, label)
+
+ if self._notebook.get_n_pages() > 1:
+ self._notebook.set_show_tabs(True)
+ self._separator.show()
+
+ def remove_toolbar(self, index):
+ self._notebook.remove_page(index)
+
+ if self._notebook.get_n_pages() < 2:
+ self._notebook.set_show_tabs(False)
+ self._separator.hide()
+
+ def set_current_toolbar(self, index):
+ self._notebook.set_current_page(index)
+
+ def get_current_toolbar(self):
+ return self._notebook.get_current_page()
+
+ current_toolbar = property(get_current_toolbar, set_current_toolbar)
diff --git a/toolkit/src/sugar/graphics/toolbutton.py b/toolkit/src/sugar/graphics/toolbutton.py
new file mode 100644
index 0000000..f15e406
--- /dev/null
+++ b/toolkit/src/sugar/graphics/toolbutton.py
@@ -0,0 +1,162 @@
+# Copyright (C) 2007, Red Hat, Inc.
+# Copyright (C) 2008, 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.
+
+"""
+STABLE.
+"""
+
+import logging
+
+import gtk
+import gobject
+
+from sugar.graphics.icon import Icon
+from sugar.graphics.palette import Palette, ToolInvoker
+
+
+def _add_accelerator(tool_button):
+ if not tool_button.props.accelerator or not tool_button.get_toplevel() or \
+ not tool_button.child:
+ return
+
+ # TODO: should we remove the accelerator from the prev top level?
+
+ accel_group = tool_button.get_toplevel().get_data('sugar-accel-group')
+ if not accel_group:
+ logging.warning('No gtk.AccelGroup in the top level window.')
+ return
+
+ keyval, mask = gtk.accelerator_parse(tool_button.props.accelerator)
+ # the accelerator needs to be set at the child, so the gtk.AccelLabel
+ # in the palette can pick it up.
+ tool_button.child.add_accelerator('clicked', accel_group, keyval, mask,
+ gtk.ACCEL_LOCKED | gtk.ACCEL_VISIBLE)
+
+
+def _hierarchy_changed_cb(tool_button, previous_toplevel):
+ _add_accelerator(tool_button)
+
+
+def setup_accelerator(tool_button):
+ _add_accelerator(tool_button)
+ tool_button.connect('hierarchy-changed', _hierarchy_changed_cb)
+
+
+class ToolButton(gtk.ToolButton):
+
+ __gtype_name__ = "SugarToolButton"
+
+ def __init__(self, icon_name=None, **kwargs):
+ self._accelerator = None
+ self._tooltip = None
+ self._palette_invoker = ToolInvoker()
+
+ gobject.GObject.__init__(self, **kwargs)
+
+ self._palette_invoker.attach_tool(self)
+
+ if icon_name:
+ self.set_icon(icon_name)
+
+ self.get_child().connect('can-activate-accel',
+ self.__button_can_activate_accel_cb)
+
+ self.connect('destroy', self.__destroy_cb)
+
+ def __destroy_cb(self, icon):
+ if self._palette_invoker is not None:
+ self._palette_invoker.detach()
+
+ def __button_can_activate_accel_cb(self, button, signal_id):
+ # Accept activation via accelerators regardless of this widget's state
+ return True
+
+ def set_tooltip(self, tooltip):
+ """ Set a simple palette with just a single label.
+ """
+ if self.palette is None or self._tooltip is None:
+ self.palette = Palette(tooltip)
+ elif self.palette is not None:
+ self.palette.set_primary_text(tooltip)
+
+ self._tooltip = tooltip
+
+ # Set label, shows up when toolbar overflows
+ gtk.ToolButton.set_label(self, tooltip)
+
+ def get_tooltip(self):
+ return self._tooltip
+
+ tooltip = gobject.property(type=str, setter=set_tooltip,
+ getter=get_tooltip)
+
+ def set_accelerator(self, accelerator):
+ self._accelerator = accelerator
+ setup_accelerator(self)
+
+ def get_accelerator(self):
+ return self._accelerator
+
+ accelerator = gobject.property(type=str, setter=set_accelerator,
+ getter=get_accelerator)
+
+ def set_icon(self, icon_name):
+ icon = Icon(icon_name=icon_name)
+ self.set_icon_widget(icon)
+ icon.show()
+
+ def create_palette(self):
+ return None
+
+ def get_palette(self):
+ return self._palette_invoker.palette
+
+ def set_palette(self, palette):
+ self._palette_invoker.palette = palette
+
+ palette = gobject.property(
+ type=object, setter=set_palette, getter=get_palette)
+
+ def get_palette_invoker(self):
+ return self._palette_invoker
+
+ def set_palette_invoker(self, palette_invoker):
+ self._palette_invoker.detach()
+ self._palette_invoker = palette_invoker
+
+ palette_invoker = gobject.property(
+ type=object, setter=set_palette_invoker, getter=get_palette_invoker)
+
+ def do_expose_event(self, event):
+ child = self.get_child()
+ allocation = self.get_allocation()
+ if self.palette and self.palette.is_up():
+ invoker = self.palette.props.invoker
+ invoker.draw_rectangle(event, self.palette)
+ elif child.state == gtk.STATE_PRELIGHT:
+ child.style.paint_box(event.window, gtk.STATE_PRELIGHT,
+ gtk.SHADOW_NONE, event.area,
+ child, "toolbutton-prelight",
+ allocation.x, allocation.y,
+ allocation.width, allocation.height)
+
+ gtk.ToolButton.do_expose_event(self, event)
+
+ def do_clicked(self):
+ if self.palette:
+ self.palette.popdown(True)
diff --git a/toolkit/src/sugar/graphics/toolcombobox.py b/toolkit/src/sugar/graphics/toolcombobox.py
new file mode 100644
index 0000000..1b2fdb0
--- /dev/null
+++ b/toolkit/src/sugar/graphics/toolcombobox.py
@@ -0,0 +1,64 @@
+# Copyright (C) 2007, Red Hat, Inc.
+#
+# 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.
+
+"""
+STABLE.
+"""
+
+import gtk
+import gobject
+
+from sugar.graphics.combobox import ComboBox
+from sugar.graphics import style
+
+
+class ToolComboBox(gtk.ToolItem):
+
+ __gproperties__ = {
+ 'label-text': (str, None, None, None, gobject.PARAM_WRITABLE),
+ }
+
+ def __init__(self, combo=None, **kwargs):
+ self.label = None
+ self._label_text = ''
+
+ gobject.GObject.__init__(self, **kwargs)
+
+ self.set_border_width(style.DEFAULT_PADDING)
+
+ hbox = gtk.HBox(False, style.DEFAULT_SPACING)
+
+ self.label = gtk.Label(self._label_text)
+ hbox.pack_start(self.label, False)
+ self.label.show()
+
+ if combo:
+ self.combo = combo
+ else:
+ self.combo = ComboBox()
+
+ hbox.pack_start(self.combo)
+ self.combo.show()
+
+ self.add(hbox)
+ hbox.show()
+
+ def do_set_property(self, pspec, value):
+ if pspec.name == 'label-text':
+ self._label_text = value
+ if self.label:
+ self.label.set_text(self._label_text)
diff --git a/toolkit/src/sugar/graphics/tray.py b/toolkit/src/sugar/graphics/tray.py
new file mode 100644
index 0000000..172123a
--- /dev/null
+++ b/toolkit/src/sugar/graphics/tray.py
@@ -0,0 +1,468 @@
+# 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.
+
+"""
+STABLE.
+"""
+
+import gobject
+import gtk
+
+from sugar.graphics import style
+from sugar.graphics.palette import ToolInvoker
+from sugar.graphics.toolbutton import ToolButton
+from sugar.graphics.icon import Icon
+
+
+_PREVIOUS_PAGE = 0
+_NEXT_PAGE = 1
+
+
+class _TrayViewport(gtk.Viewport):
+
+ __gproperties__ = {
+ 'scrollable': (bool, None, None, False, gobject.PARAM_READABLE),
+ 'can-scroll-prev': (bool, None, None, False, gobject.PARAM_READABLE),
+ 'can-scroll-next': (bool, None, None, False, gobject.PARAM_READABLE),
+ }
+
+ def __init__(self, orientation):
+ self.orientation = orientation
+ self._scrollable = False
+ self._can_scroll_next = False
+ self._can_scroll_prev = False
+
+ gobject.GObject.__init__(self)
+
+ self.set_shadow_type(gtk.SHADOW_NONE)
+
+ self.traybar = gtk.Toolbar()
+ self.traybar.set_orientation(orientation)
+ self.traybar.set_show_arrow(False)
+ self.add(self.traybar)
+ self.traybar.show()
+
+ self.connect('size_allocate', self._size_allocate_cb)
+
+ if self.orientation == gtk.ORIENTATION_HORIZONTAL:
+ adj = self.get_hadjustment()
+ else:
+ adj = self.get_vadjustment()
+ adj.connect('changed', self._adjustment_changed_cb)
+ adj.connect('value-changed', self._adjustment_changed_cb)
+
+ def scroll(self, direction):
+ if direction == _PREVIOUS_PAGE:
+ self._scroll_previous()
+ elif direction == _NEXT_PAGE:
+ self._scroll_next()
+
+ def scroll_to_item(self, item):
+ """This function scrolls the viewport so that item will be visible."""
+ assert item in self.traybar.get_children()
+
+ # Get the allocation, and make sure that it is visible
+ if self.orientation == gtk.ORIENTATION_HORIZONTAL:
+ adj = self.get_hadjustment()
+ start = item.allocation.x
+ stop = item.allocation.x + item.allocation.width
+ else:
+ adj = self.get_vadjustment()
+ start = item.allocation.y
+ stop = item.allocation.y + item.allocation.height
+
+ if start < adj.value:
+ adj.value = start
+ elif stop > adj.value + adj.page_size:
+ adj.value = stop - adj.page_size
+
+ def _scroll_next(self):
+ allocation = self.get_allocation()
+ if self.orientation == gtk.ORIENTATION_HORIZONTAL:
+ adj = self.get_hadjustment()
+ new_value = adj.value + allocation.width
+ adj.value = min(new_value, adj.upper - allocation.width)
+ else:
+ adj = self.get_vadjustment()
+ new_value = adj.value + allocation.height
+ adj.value = min(new_value, adj.upper - allocation.height)
+
+ def _scroll_previous(self):
+ allocation = self.get_allocation()
+ if self.orientation == gtk.ORIENTATION_HORIZONTAL:
+ adj = self.get_hadjustment()
+ new_value = adj.value - allocation.width
+ adj.value = max(adj.lower, new_value)
+ else:
+ adj = self.get_vadjustment()
+ new_value = adj.value - allocation.height
+ adj.value = max(adj.lower, new_value)
+
+ def do_size_request(self, requisition):
+ child_requisition = self.get_child().size_request()
+ if self.orientation == gtk.ORIENTATION_HORIZONTAL:
+ requisition[0] = 0
+ requisition[1] = child_requisition[1]
+ else:
+ requisition[0] = child_requisition[0]
+ requisition[1] = 0
+
+ def do_get_property(self, pspec):
+ if pspec.name == 'scrollable':
+ return self._scrollable
+ elif pspec.name == 'can-scroll-next':
+ return self._can_scroll_next
+ elif pspec.name == 'can-scroll-prev':
+ return self._can_scroll_prev
+
+ def _size_allocate_cb(self, viewport, allocation):
+ bar_requisition = self.traybar.get_child_requisition()
+ if self.orientation == gtk.ORIENTATION_HORIZONTAL:
+ scrollable = bar_requisition[0] > allocation.width
+ else:
+ scrollable = bar_requisition[1] > allocation.height
+
+ if scrollable != self._scrollable:
+ self._scrollable = scrollable
+ self.notify('scrollable')
+
+ def _adjustment_changed_cb(self, adjustment):
+ if adjustment.value <= adjustment.lower:
+ can_scroll_prev = False
+ else:
+ can_scroll_prev = True
+
+ if adjustment.value + adjustment.page_size >= adjustment.upper:
+ can_scroll_next = False
+ else:
+ can_scroll_next = True
+
+ if can_scroll_prev != self._can_scroll_prev:
+ self._can_scroll_prev = can_scroll_prev
+ self.notify('can-scroll-prev')
+
+ if can_scroll_next != self._can_scroll_next:
+ self._can_scroll_next = can_scroll_next
+ self.notify('can-scroll-next')
+
+
+class _TrayScrollButton(ToolButton):
+
+ def __init__(self, icon_name, scroll_direction):
+ ToolButton.__init__(self)
+ self._viewport = None
+
+ self._scroll_direction = scroll_direction
+
+ self.set_size_request(style.GRID_CELL_SIZE, style.GRID_CELL_SIZE)
+
+ self.icon = Icon(icon_name = icon_name,
+ icon_size=gtk.ICON_SIZE_SMALL_TOOLBAR)
+ # The alignment is a hack to work around gtk.ToolButton code
+ # that sets the icon_size when the icon_widget is a gtk.Image
+ alignment = gtk.Alignment(0.5, 0.5)
+ alignment.add(self.icon)
+ self.set_icon_widget(alignment)
+ alignment.show_all()
+
+ self.connect('clicked', self._clicked_cb)
+
+ def set_viewport(self, viewport):
+ self._viewport = viewport
+ self._viewport.connect('notify::scrollable',
+ self._viewport_scrollable_changed_cb)
+
+ if self._scroll_direction == _PREVIOUS_PAGE:
+ self._viewport.connect('notify::can-scroll-prev',
+ self._viewport_can_scroll_dir_changed_cb)
+ self.set_sensitive(self._viewport.props.can_scroll_prev)
+ else:
+ self._viewport.connect('notify::can-scroll-next',
+ self._viewport_can_scroll_dir_changed_cb)
+ self.set_sensitive(self._viewport.props.can_scroll_next)
+
+ def _viewport_scrollable_changed_cb(self, viewport, pspec):
+ self.props.visible = self._viewport.props.scrollable
+
+ def _viewport_can_scroll_dir_changed_cb(self, viewport, pspec):
+ if self._scroll_direction == _PREVIOUS_PAGE:
+ sensitive = self._viewport.props.can_scroll_prev
+ else:
+ sensitive = self._viewport.props.can_scroll_next
+
+ self.set_sensitive(sensitive)
+
+ def _clicked_cb(self, button):
+ self._viewport.scroll(self._scroll_direction)
+
+ viewport = property(fset=set_viewport)
+
+
+ALIGN_TO_START = 0
+ALIGN_TO_END = 1
+
+
+class HTray(gtk.HBox):
+
+ __gtype_name__ = 'SugarHTray'
+
+ __gproperties__ = {
+ 'align': (int, None, None, 0, 1, ALIGN_TO_START,
+ gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT_ONLY),
+ 'drag-active': (bool, None, None, False, gobject.PARAM_READWRITE),
+ }
+
+ def __init__(self, **kwargs):
+ self._drag_active = False
+ self.align = ALIGN_TO_START
+
+ gobject.GObject.__init__(self, **kwargs)
+
+ scroll_left = _TrayScrollButton('go-left', _PREVIOUS_PAGE)
+ self.pack_start(scroll_left, False)
+
+ self._viewport = _TrayViewport(gtk.ORIENTATION_HORIZONTAL)
+ self.pack_start(self._viewport)
+ self._viewport.show()
+
+ scroll_right = _TrayScrollButton('go-right', _NEXT_PAGE)
+ self.pack_start(scroll_right, False)
+
+ scroll_left.viewport = self._viewport
+ scroll_right.viewport = self._viewport
+
+ if self.align == ALIGN_TO_END:
+ spacer = gtk.SeparatorToolItem()
+ spacer.set_size_request(0, 0)
+ spacer.props.draw = False
+ spacer.set_expand(True)
+ self._viewport.traybar.insert(spacer, 0)
+ spacer.show()
+
+ def do_set_property(self, pspec, value):
+ if pspec.name == 'align':
+ self.align = value
+ elif pspec.name == 'drag-active':
+ self._set_drag_active(value)
+ else:
+ raise AssertionError
+
+ def do_get_property(self, pspec):
+ if pspec.name == 'align':
+ return self.align
+ elif pspec.name == 'drag-active':
+ return self._drag_active
+ else:
+ raise AssertionError
+
+ def _set_drag_active(self, active):
+ if self._drag_active != active:
+ self._drag_active = active
+ if self._drag_active:
+ self._viewport.traybar.modify_bg(gtk.STATE_NORMAL,
+ style.COLOR_BLACK.get_gdk_color())
+ else:
+ self._viewport.traybar.modify_bg(gtk.STATE_NORMAL, None)
+
+ def get_children(self):
+ children = self._viewport.traybar.get_children()[:]
+ if self.align == ALIGN_TO_END:
+ children = children[1:]
+ return children
+
+ def add_item(self, item, index=-1):
+ if self.align == ALIGN_TO_END and index > -1:
+ index += 1
+ self._viewport.traybar.insert(item, index)
+
+ def remove_item(self, item):
+ self._viewport.traybar.remove(item)
+
+ def get_item_index(self, item):
+ index = self._viewport.traybar.get_item_index(item)
+ if self.align == ALIGN_TO_END:
+ index -= 1
+ return index
+
+ def scroll_to_item(self, item):
+ self._viewport.scroll_to_item(item)
+
+
+class VTray(gtk.VBox):
+
+ __gtype_name__ = 'SugarVTray'
+
+ __gproperties__ = {
+ 'align': (int, None, None, 0, 1, ALIGN_TO_START,
+ gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT_ONLY),
+ 'drag-active': (bool, None, None, False, gobject.PARAM_READWRITE),
+ }
+
+ def __init__(self, **kwargs):
+ self._drag_active = False
+ self.align = ALIGN_TO_START
+
+ gobject.GObject.__init__(self, **kwargs)
+
+ scroll_up = _TrayScrollButton('go-up', _PREVIOUS_PAGE)
+ self.pack_start(scroll_up, False)
+
+ self._viewport = _TrayViewport(gtk.ORIENTATION_VERTICAL)
+ self.pack_start(self._viewport)
+ self._viewport.show()
+
+ scroll_down = _TrayScrollButton('go-down', _NEXT_PAGE)
+ self.pack_start(scroll_down, False)
+
+ scroll_up.viewport = self._viewport
+ scroll_down.viewport = self._viewport
+
+ if self.align == ALIGN_TO_END:
+ spacer = gtk.SeparatorToolItem()
+ spacer.set_size_request(0, 0)
+ spacer.props.draw = False
+ spacer.set_expand(True)
+ self._viewport.traybar.insert(spacer, 0)
+ spacer.show()
+
+ def do_set_property(self, pspec, value):
+ if pspec.name == 'align':
+ self.align = value
+ elif pspec.name == 'drag-active':
+ self._set_drag_active(value)
+ else:
+ raise AssertionError
+
+ def do_get_property(self, pspec):
+ if pspec.name == 'align':
+ return self.align
+ elif pspec.name == 'drag-active':
+ return self._drag_active
+ else:
+ raise AssertionError
+
+ def _set_drag_active(self, active):
+ if self._drag_active != active:
+ self._drag_active = active
+ if self._drag_active:
+ self._viewport.traybar.modify_bg(gtk.STATE_NORMAL,
+ style.COLOR_BLACK.get_gdk_color())
+ else:
+ self._viewport.traybar.modify_bg(gtk.STATE_NORMAL, None)
+
+ def get_children(self):
+ children = self._viewport.traybar.get_children()[:]
+ if self.align == ALIGN_TO_END:
+ children = children[1:]
+ return children
+
+ def add_item(self, item, index=-1):
+ if self.align == ALIGN_TO_END and index > -1:
+ index += 1
+ self._viewport.traybar.insert(item, index)
+
+ def remove_item(self, item):
+ self._viewport.traybar.remove(item)
+
+ def get_item_index(self, item):
+ index = self._viewport.traybar.get_item_index(item)
+ if self.align == ALIGN_TO_END:
+ index -= 1
+ return index
+
+ def scroll_to_item(self, item):
+ self._viewport.scroll_to_item(item)
+
+
+class TrayButton(ToolButton):
+
+ def __init__(self, **kwargs):
+ ToolButton.__init__(self, **kwargs)
+
+
+class _IconWidget(gtk.EventBox):
+
+ __gtype_name__ = "SugarTrayIconWidget"
+
+ def __init__(self, icon_name=None, xo_color=None):
+ gtk.EventBox.__init__(self)
+
+ self.set_app_paintable(True)
+
+ self._icon = Icon(icon_name=icon_name, xo_color=xo_color,
+ icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR)
+ self.add(self._icon)
+ self._icon.show()
+
+ def do_expose_event(self, event):
+ palette = self.parent.palette
+ if palette and palette.is_up():
+ invoker = palette.props.invoker
+ invoker.draw_rectangle(event, palette)
+
+ gtk.EventBox.do_expose_event(self, event)
+
+ def get_icon(self):
+ return self._icon
+
+
+class TrayIcon(gtk.ToolItem):
+
+ __gtype_name__ = "SugarTrayIcon"
+
+ def __init__(self, icon_name=None, xo_color=None):
+ gtk.ToolItem.__init__(self)
+
+ self._icon_widget = _IconWidget(icon_name, xo_color)
+ self.add(self._icon_widget)
+ self._icon_widget.show()
+
+ self._palette_invoker = ToolInvoker(self)
+
+ self.set_size_request(style.GRID_CELL_SIZE, style.GRID_CELL_SIZE)
+
+ self.connect('destroy', self.__destroy_cb)
+
+ def __destroy_cb(self, icon):
+ if self._palette_invoker is not None:
+ self._palette_invoker.detach()
+
+ def create_palette(self):
+ return None
+
+ def get_palette(self):
+ return self._palette_invoker.palette
+
+ def set_palette(self, palette):
+ self._palette_invoker.palette = palette
+
+ palette = gobject.property(
+ type=object, setter=set_palette, getter=get_palette)
+
+ def get_palette_invoker(self):
+ return self._palette_invoker
+
+ def set_palette_invoker(self, palette_invoker):
+ self._palette_invoker.detach()
+ self._palette_invoker = palette_invoker
+
+ palette_invoker = gobject.property(
+ type=object, setter=set_palette_invoker, getter=get_palette_invoker)
+
+ def get_icon(self):
+ return self._icon_widget.get_icon()
+ icon = property(get_icon, None)
diff --git a/toolkit/src/sugar/graphics/window.py b/toolkit/src/sugar/graphics/window.py
new file mode 100644
index 0000000..e3bef6b
--- /dev/null
+++ b/toolkit/src/sugar/graphics/window.py
@@ -0,0 +1,297 @@
+# Copyright (C) 2007, Red Hat, Inc.
+# Copyright (C) 2009, Aleksey Lim, Sayamindu Dasgupta
+#
+# 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.
+
+"""
+STABLE.
+"""
+
+import gobject
+import gtk
+import warnings
+
+from sugar.graphics.icon import Icon
+from sugar.graphics import palettegroup
+
+
+_UNFULLSCREEN_BUTTON_VISIBILITY_TIMEOUT = 2
+
+
+class UnfullscreenButton(gtk.Window):
+
+ def __init__(self):
+ gtk.Window.__init__(self)
+
+ self.set_decorated(False)
+ self.set_resizable(False)
+ self.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
+
+ self.set_border_width(0)
+
+ self.props.accept_focus = False
+
+ #Setup estimate of width, height
+ w, h = gtk.icon_size_lookup(gtk.ICON_SIZE_LARGE_TOOLBAR)
+ self._width = w
+ self._height = h
+
+ self.connect('size-request', self._size_request_cb)
+
+ screen = self.get_screen()
+ screen.connect('size-changed', self._screen_size_changed_cb)
+
+ self._button = gtk.Button()
+ self._button.set_relief(gtk.RELIEF_NONE)
+
+ self._icon = Icon(icon_name='view-return',
+ icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR)
+ self._icon.show()
+ self._button.add(self._icon)
+
+ self._button.show()
+ self.add(self._button)
+
+ def connect_button_press(self, cb):
+ self._button.connect('button-press-event', cb)
+
+ def _reposition(self):
+ x = gtk.gdk.screen_width() - self._width
+ self.move(x, 0)
+
+ def _size_request_cb(self, widget, req):
+ self._width = req.width
+ self._height = req.height
+ self._reposition()
+
+ def _screen_size_changed_cb(self, screen):
+ self._reposition()
+
+
+class Window(gtk.Window):
+
+ def __init__(self, **args):
+ self._enable_fullscreen_mode = True
+
+ gtk.Window.__init__(self, **args)
+
+ self.connect('realize', self.__window_realize_cb)
+ self.connect('key-press-event', self.__key_press_cb)
+
+ self._toolbar_box = None
+ self._alerts = []
+ self._canvas = None
+ self.tray = None
+
+ self.__vbox = gtk.VBox()
+ self.__hbox = gtk.HBox()
+ self.__vbox.pack_start(self.__hbox)
+ self.__hbox.show()
+
+ self._event_box = gtk.EventBox()
+ self.__hbox.pack_start(self._event_box)
+ self._event_box.show()
+ self._event_box.add_events(gtk.gdk.POINTER_MOTION_HINT_MASK
+ | gtk.gdk.POINTER_MOTION_MASK)
+ self._event_box.connect('motion-notify-event', self.__motion_notify_cb)
+
+ self.add(self.__vbox)
+ self.__vbox.show()
+
+ self._is_fullscreen = False
+ self._unfullscreen_button = UnfullscreenButton()
+ self._unfullscreen_button.set_transient_for(self)
+ self._unfullscreen_button.connect_button_press(
+ self.__unfullscreen_button_pressed)
+ self._unfullscreen_button_timeout_id = None
+
+ def reveal(self):
+ """ Make window active
+
+ In contrast with present(), brings window to the top
+ even after invoking on response on non-gtk events.
+ See #1423.
+ """
+ if self.window is None:
+ self.show()
+ return
+ timestamp = gtk.get_current_event_time()
+ if not timestamp:
+ timestamp = gtk.gdk.x11_get_server_time(self.window)
+ self.window.focus(timestamp)
+
+ def fullscreen(self):
+ palettegroup.popdown_all()
+ if self._toolbar_box is not None:
+ self._toolbar_box.hide()
+ if self.tray is not None:
+ self.tray.hide()
+
+ self._is_fullscreen = True
+
+ if self.props.enable_fullscreen_mode:
+ self._unfullscreen_button.show()
+
+ if self._unfullscreen_button_timeout_id is not None:
+ gobject.source_remove(self._unfullscreen_button_timeout_id)
+ self._unfullscreen_button_timeout_id = None
+
+ self._unfullscreen_button_timeout_id = \
+ gobject.timeout_add_seconds( \
+ _UNFULLSCREEN_BUTTON_VISIBILITY_TIMEOUT, \
+ self.__unfullscreen_button_timeout_cb)
+
+ def unfullscreen(self):
+ if self._toolbar_box is not None:
+ self._toolbar_box.show()
+ if self.tray is not None:
+ self.tray.show()
+
+ self._is_fullscreen = False
+
+ if self.props.enable_fullscreen_mode:
+ self._unfullscreen_button.hide()
+
+ if self._unfullscreen_button_timeout_id:
+ gobject.source_remove(self._unfullscreen_button_timeout_id)
+ self._unfullscreen_button_timeout_id = None
+
+ def set_canvas(self, canvas):
+ if self._canvas:
+ self._event_box.remove(self._canvas)
+
+ if canvas:
+ self._event_box.add(canvas)
+
+ self._canvas = canvas
+ self.__vbox.set_focus_child(self._canvas)
+
+ def get_canvas(self):
+ return self._canvas
+
+ canvas = property(get_canvas, set_canvas)
+
+ def get_toolbar_box(self):
+ return self._toolbar_box
+
+ def set_toolbar_box(self, toolbar_box):
+ if self._toolbar_box:
+ self.__vbox.remove(self._toolbar_box)
+
+ self.__vbox.pack_start(toolbar_box, False)
+ self.__vbox.reorder_child(toolbar_box, 0)
+
+ self._toolbar_box = toolbar_box
+
+ toolbar_box = property(get_toolbar_box, set_toolbar_box)
+
+ def set_tray(self, tray, position):
+ if self.tray:
+ box = self.tray.get_parent()
+ box.remove(self.tray)
+
+ if position == gtk.POS_LEFT:
+ self.__hbox.pack_start(tray, False)
+ elif position == gtk.POS_RIGHT:
+ self.__hbox.pack_end(tray, False)
+ elif position == gtk.POS_BOTTOM:
+ self.__vbox.pack_end(tray, False)
+
+ self.tray = tray
+
+ def add_alert(self, alert):
+ self._alerts.append(alert)
+ if len(self._alerts) == 1:
+ self.__vbox.pack_start(alert, False)
+ if self._toolbar_box is not None:
+ self.__vbox.reorder_child(alert, 1)
+ else:
+ self.__vbox.reorder_child(alert, 0)
+
+ def remove_alert(self, alert):
+ if alert in self._alerts:
+ self._alerts.remove(alert)
+ # if the alert is the visible one on top of the queue
+ if alert.get_parent() is not None:
+ self.__vbox.remove(alert)
+ if len(self._alerts) >= 1:
+ self.__vbox.pack_start(self._alerts[0], False)
+ if self._toolbar_box is not None:
+ self.__vbox.reorder_child(self._alerts[0], 1)
+ else:
+ self.__vbox.reorder_child(self._alert[0], 0)
+
+ def __window_realize_cb(self, window):
+ group = gtk.Window()
+ group.realize()
+ window.window.set_group(group.window)
+
+ def __key_press_cb(self, widget, event):
+ key = gtk.gdk.keyval_name(event.keyval)
+ if event.state & gtk.gdk.MOD1_MASK:
+ if self.tray is not None and key == 'space':
+ self.tray.props.visible = not self.tray.props.visible
+ return True
+ elif key == 'Escape' and self._is_fullscreen and \
+ self.props.enable_fullscreen_mode:
+ self.unfullscreen()
+ return True
+ return False
+
+ def __unfullscreen_button_pressed(self, widget, event):
+ self.unfullscreen()
+
+ def __motion_notify_cb(self, widget, event):
+ if self._is_fullscreen and self.props.enable_fullscreen_mode:
+ if not self._unfullscreen_button.props.visible:
+ self._unfullscreen_button.show()
+ else:
+ # Reset the timer
+ if self._unfullscreen_button_timeout_id is not None:
+ gobject.source_remove(self._unfullscreen_button_timeout_id)
+ self._unfullscreen_button_timeout_id = None
+
+ self._unfullscreen_button_timeout_id = \
+ gobject.timeout_add_seconds( \
+ _UNFULLSCREEN_BUTTON_VISIBILITY_TIMEOUT, \
+ self.__unfullscreen_button_timeout_cb)
+ return False
+
+ def __unfullscreen_button_timeout_cb(self):
+ self._unfullscreen_button.hide()
+ self._unfullscreen_button_timeout_id = None
+ return False
+
+ def set_enable_fullscreen_mode(self, enable_fullscreen_mode):
+ self._enable_fullscreen_mode = enable_fullscreen_mode
+
+ def get_enable_fullscreen_mode(self):
+ return self._enable_fullscreen_mode
+
+ enable_fullscreen_mode = gobject.property(type=object,
+ setter=set_enable_fullscreen_mode, getter=get_enable_fullscreen_mode)
+
+ # DEPRECATED
+
+ def set_toolbox(self, toolbar_box):
+ warnings.warn('use toolbar_box instead of toolbox', DeprecationWarning)
+ self.set_toolbar_box(toolbar_box)
+
+ def get_toolbox(self):
+ warnings.warn('use toolbar_box instead of toolbox', DeprecationWarning)
+ return self._toolbar_box
+
+ toolbox = property(get_toolbox, set_toolbox)
diff --git a/toolkit/src/sugar/graphics/xocolor.py b/toolkit/src/sugar/graphics/xocolor.py
new file mode 100644
index 0000000..fd329cb
--- /dev/null
+++ b/toolkit/src/sugar/graphics/xocolor.py
@@ -0,0 +1,278 @@
+# Copyright (C) 2006-2007 Red Hat, Inc.
+#
+# 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.
+
+"""
+STABLE.
+"""
+
+import random
+import logging
+
+import gconf
+
+colors = [
+['#B20008', '#FF2B34'], \
+['#FF2B34', '#B20008'], \
+['#E6000A', '#FF2B34'], \
+['#FF2B34', '#E6000A'], \
+['#FFADCE', '#FF2B34'], \
+['#9A5200', '#FF2B34'], \
+['#FF2B34', '#9A5200'], \
+['#FF8F00', '#FF2B34'], \
+['#FF2B34', '#FF8F00'], \
+['#FFC169', '#FF2B34'], \
+['#807500', '#FF2B34'], \
+['#FF2B34', '#807500'], \
+['#BE9E00', '#FF2B34'], \
+['#FF2B34', '#BE9E00'], \
+['#F8E800', '#FF2B34'], \
+['#008009', '#FF2B34'], \
+['#FF2B34', '#008009'], \
+['#00B20D', '#FF2B34'], \
+['#FF2B34', '#00B20D'], \
+['#8BFF7A', '#FF2B34'], \
+['#00588C', '#FF2B34'], \
+['#FF2B34', '#00588C'], \
+['#005FE4', '#FF2B34'], \
+['#FF2B34', '#005FE4'], \
+['#BCCDFF', '#FF2B34'], \
+['#5E008C', '#FF2B34'], \
+['#FF2B34', '#5E008C'], \
+['#7F00BF', '#FF2B34'], \
+['#FF2B34', '#7F00BF'], \
+['#D1A3FF', '#FF2B34'], \
+['#9A5200', '#FF8F00'], \
+['#FF8F00', '#9A5200'], \
+['#C97E00', '#FF8F00'], \
+['#FF8F00', '#C97E00'], \
+['#FFC169', '#FF8F00'], \
+['#807500', '#FF8F00'], \
+['#FF8F00', '#807500'], \
+['#BE9E00', '#FF8F00'], \
+['#FF8F00', '#BE9E00'], \
+['#F8E800', '#FF8F00'], \
+['#008009', '#FF8F00'], \
+['#FF8F00', '#008009'], \
+['#00B20D', '#FF8F00'], \
+['#FF8F00', '#00B20D'], \
+['#8BFF7A', '#FF8F00'], \
+['#00588C', '#FF8F00'], \
+['#FF8F00', '#00588C'], \
+['#005FE4', '#FF8F00'], \
+['#FF8F00', '#005FE4'], \
+['#BCCDFF', '#FF8F00'], \
+['#5E008C', '#FF8F00'], \
+['#FF8F00', '#5E008C'], \
+['#A700FF', '#FF8F00'], \
+['#FF8F00', '#A700FF'], \
+['#D1A3FF', '#FF8F00'], \
+['#B20008', '#FF8F00'], \
+['#FF8F00', '#B20008'], \
+['#FF2B34', '#FF8F00'], \
+['#FF8F00', '#FF2B34'], \
+['#FFADCE', '#FF8F00'], \
+['#807500', '#F8E800'], \
+['#F8E800', '#807500'], \
+['#BE9E00', '#F8E800'], \
+['#F8E800', '#BE9E00'], \
+['#FFFA00', '#EDDE00'], \
+['#008009', '#F8E800'], \
+['#F8E800', '#008009'], \
+['#00EA11', '#F8E800'], \
+['#F8E800', '#00EA11'], \
+['#8BFF7A', '#F8E800'], \
+['#00588C', '#F8E800'], \
+['#F8E800', '#00588C'], \
+['#00A0FF', '#F8E800'], \
+['#F8E800', '#00A0FF'], \
+['#BCCEFF', '#F8E800'], \
+['#5E008C', '#F8E800'], \
+['#F8E800', '#5E008C'], \
+['#AC32FF', '#F8E800'], \
+['#F8E800', '#AC32FF'], \
+['#D1A3FF', '#F8E800'], \
+['#B20008', '#F8E800'], \
+['#F8E800', '#B20008'], \
+['#FF2B34', '#F8E800'], \
+['#F8E800', '#FF2B34'], \
+['#FFADCE', '#F8E800'], \
+['#9A5200', '#F8E800'], \
+['#F8E800', '#9A5200'], \
+['#FF8F00', '#F8E800'], \
+['#F8E800', '#FF8F00'], \
+['#FFC169', '#F8E800'], \
+['#008009', '#00EA11'], \
+['#00EA11', '#008009'], \
+['#00B20D', '#00EA11'], \
+['#00EA11', '#00B20D'], \
+['#8BFF7A', '#00EA11'], \
+['#00588C', '#00EA11'], \
+['#00EA11', '#00588C'], \
+['#005FE4', '#00EA11'], \
+['#00EA11', '#005FE4'], \
+['#BCCDFF', '#00EA11'], \
+['#5E008C', '#00EA11'], \
+['#00EA11', '#5E008C'], \
+['#7F00BF', '#00EA11'], \
+['#00EA11', '#7F00BF'], \
+['#D1A3FF', '#00EA11'], \
+['#B20008', '#00EA11'], \
+['#00EA11', '#B20008'], \
+['#FF2B34', '#00EA11'], \
+['#00EA11', '#FF2B34'], \
+['#FFADCE', '#00EA11'], \
+['#9A5200', '#00EA11'], \
+['#00EA11', '#9A5200'], \
+['#FF8F00', '#00EA11'], \
+['#00EA11', '#FF8F00'], \
+['#FFC169', '#00EA11'], \
+['#807500', '#00EA11'], \
+['#00EA11', '#807500'], \
+['#BE9E00', '#00EA11'], \
+['#00EA11', '#BE9E00'], \
+['#F8E800', '#00EA11'], \
+['#00588C', '#00A0FF'], \
+['#00A0FF', '#00588C'], \
+['#005FE4', '#00A0FF'], \
+['#00A0FF', '#005FE4'], \
+['#BCCDFF', '#00A0FF'], \
+['#5E008C', '#00A0FF'], \
+['#00A0FF', '#5E008C'], \
+['#9900E6', '#00A0FF'], \
+['#00A0FF', '#9900E6'], \
+['#D1A3FF', '#00A0FF'], \
+['#B20008', '#00A0FF'], \
+['#00A0FF', '#B20008'], \
+['#FF2B34', '#00A0FF'], \
+['#00A0FF', '#FF2B34'], \
+['#FFADCE', '#00A0FF'], \
+['#9A5200', '#00A0FF'], \
+['#00A0FF', '#9A5200'], \
+['#FF8F00', '#00A0FF'], \
+['#00A0FF', '#FF8F00'], \
+['#FFC169', '#00A0FF'], \
+['#807500', '#00A0FF'], \
+['#00A0FF', '#807500'], \
+['#BE9E00', '#00A0FF'], \
+['#00A0FF', '#BE9E00'], \
+['#F8E800', '#00A0FF'], \
+['#008009', '#00A0FF'], \
+['#00A0FF', '#008009'], \
+['#00B20D', '#00A0FF'], \
+['#00A0FF', '#00B20D'], \
+['#8BFF7A', '#00A0FF'], \
+['#5E008C', '#AC32FF'], \
+['#AC32FF', '#5E008C'], \
+['#7F00BF', '#AC32FF'], \
+['#AC32FF', '#7F00BF'], \
+['#D1A3FF', '#AC32FF'], \
+['#B20008', '#AC32FF'], \
+['#AC32FF', '#B20008'], \
+['#FF2B34', '#AC32FF'], \
+['#AC32FF', '#FF2B34'], \
+['#FFADCE', '#AC32FF'], \
+['#9A5200', '#AC32FF'], \
+['#AC32FF', '#9A5200'], \
+['#FF8F00', '#AC32FF'], \
+['#AC32FF', '#FF8F00'], \
+['#FFC169', '#AC32FF'], \
+['#807500', '#AC32FF'], \
+['#AC32FF', '#807500'], \
+['#BE9E00', '#AC32FF'], \
+['#AC32FF', '#BE9E00'], \
+['#F8E800', '#AC32FF'], \
+['#008009', '#AC32FF'], \
+['#AC32FF', '#008009'], \
+['#00B20D', '#AC32FF'], \
+['#AC32FF', '#00B20D'], \
+['#8BFF7A', '#AC32FF'], \
+['#00588C', '#AC32FF'], \
+['#AC32FF', '#00588C'], \
+['#005FE4', '#AC32FF'], \
+['#AC32FF', '#005FE4'], \
+['#BCCDFF', '#AC32FF'], \
+]
+
+
+def _parse_string(color_string):
+ if color_string == 'white':
+ return ['#ffffff', '#414141']
+ elif color_string == 'insensitive':
+ return ['#ffffff', '#e2e2e2']
+
+ splitted = color_string.split(',')
+ if len(splitted) == 2:
+ return [splitted[0], splitted[1]]
+ else:
+ return None
+
+
+def is_valid(color_string):
+ return (_parse_string(color_string) != None)
+
+
+class XoColor:
+
+ def __init__(self, color_string=None):
+ if color_string == None:
+ randomize = True
+ elif not is_valid(color_string):
+ logging.debug('Color string is not valid: %s, '
+ 'fallback to default', color_string)
+ client = gconf.client_get_default()
+ color_string = client.get_string('/desktop/sugar/user/color')
+ randomize = False
+ else:
+ randomize = False
+
+ if randomize:
+ n = int(random.random() * (len(colors) - 1))
+ [self.stroke, self.fill] = colors[n]
+ else:
+ [self.stroke, self.fill] = _parse_string(color_string)
+
+ def __cmp__(self, other):
+ if isinstance(other, XoColor):
+ if self.stroke == other.stroke and self.fill == other.fill:
+ return 0
+ return -1
+
+ def get_stroke_color(self):
+ return self.stroke
+
+ def get_fill_color(self):
+ return self.fill
+
+ def to_string(self):
+ return '%s,%s' % (self.stroke, self.fill)
+
+
+if __name__ == "__main__":
+ import sys
+ import re
+
+ f = open(sys.argv[1], 'r')
+
+ print 'colors = ['
+
+ for line in f.readlines():
+ match = re.match(r'fill: ([A-Z0-9]*) stroke: ([A-Z0-9]*)', line)
+ print "['#%s', '#%s'], \\" % (match.group(2), match.group(1))
+
+ print ']'
+
+ f.close()
diff --git a/toolkit/src/sugar/gsm-app.c b/toolkit/src/sugar/gsm-app.c
new file mode 100644
index 0000000..96b65ce
--- /dev/null
+++ b/toolkit/src/sugar/gsm-app.c
@@ -0,0 +1,396 @@
+/* app.c
+ * Copyright (C) 2007 Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser 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., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <glib.h>
+#include <string.h>
+#include <sys/wait.h>
+
+#include "gsm-app.h"
+
+enum {
+ EXITED,
+ REGISTERED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+enum {
+ PROP_0,
+
+ PROP_DESKTOP_FILE,
+ PROP_CLIENT_ID,
+
+ LAST_PROP
+};
+
+static void set_property (GObject *object, guint prop_id,
+ const GValue *value, GParamSpec *pspec);
+static void get_property (GObject *object, guint prop_id,
+ GValue *value, GParamSpec *pspec);
+static void dispose (GObject *object);
+
+static const char *get_basename (GsmApp *app);
+static pid_t launch (GsmApp *app, GError **err);
+
+G_DEFINE_TYPE (GsmApp, gsm_app, G_TYPE_OBJECT)
+
+static void
+gsm_app_init (GsmApp *app)
+{
+ app->pid = -1;
+}
+
+static void
+gsm_app_class_init (GsmAppClass *app_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (app_class);
+
+ object_class->set_property = set_property;
+ object_class->get_property = get_property;
+ object_class->dispose = dispose;
+
+ app_class->get_basename = get_basename;
+ app_class->launch = launch;
+
+ g_object_class_install_property (object_class,
+ PROP_DESKTOP_FILE,
+ g_param_spec_string ("desktop-file",
+ "Desktop file",
+ "Freedesktop .desktop file",
+ NULL,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_CLIENT_ID,
+ g_param_spec_string ("client-id",
+ "Client ID",
+ "Session management client ID",
+ NULL,
+ G_PARAM_READWRITE));
+
+ signals[EXITED] =
+ g_signal_new ("exited",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GsmAppClass, exited),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ signals[REGISTERED] =
+ g_signal_new ("registered",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GsmAppClass, registered),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+}
+
+static void
+set_property (GObject *object, guint prop_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ GsmApp *app = GSM_APP (object);
+ const char *desktop_file;
+ char *phase;
+ GError *error = NULL;
+
+ switch (prop_id)
+ {
+ case PROP_DESKTOP_FILE:
+ if (app->desktop_file)
+ egg_desktop_file_free (app->desktop_file);
+ desktop_file = g_value_get_string (value);
+ if (!desktop_file)
+ {
+ app->desktop_file = NULL;
+ break;
+ }
+
+ app->desktop_file = egg_desktop_file_new (desktop_file, &error);
+ if (!app->desktop_file)
+ {
+ g_warning ("Could not parse desktop file %s: %s",
+ desktop_file, error->message);
+ g_error_free (error);
+ break;
+ }
+
+ phase = egg_desktop_file_get_string (app->desktop_file,
+ "X-GNOME-Autostart-Phase", NULL);
+ if (phase)
+ {
+ if (!strcmp (phase, "Initialization"))
+ app->phase = GSM_SESSION_PHASE_INITIALIZATION;
+ else if (!strcmp (phase, "WindowManager"))
+ app->phase = GSM_SESSION_PHASE_WINDOW_MANAGER;
+ else if (!strcmp (phase, "Panel"))
+ app->phase = GSM_SESSION_PHASE_PANEL;
+ else if (!strcmp (phase, "Desktop"))
+ app->phase = GSM_SESSION_PHASE_DESKTOP;
+ else
+ app->phase = GSM_SESSION_PHASE_APPLICATION;
+
+ g_free (phase);
+ }
+ else
+ app->phase = GSM_SESSION_PHASE_APPLICATION;
+ break;
+
+ case PROP_CLIENT_ID:
+ g_free (app->client_id);
+ app->client_id = g_value_dup_string (value);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void
+get_property (GObject *object, guint prop_id,
+ GValue *value, GParamSpec *pspec)
+{
+ GsmApp *app = GSM_APP (object);
+
+ switch (prop_id)
+ {
+ case PROP_DESKTOP_FILE:
+ if (app->desktop_file)
+ g_value_set_string (value, egg_desktop_file_get_source (app->desktop_file));
+ else
+ g_value_set_string (value, NULL);
+ break;
+
+ case PROP_CLIENT_ID:
+ g_value_set_string (value, app->client_id);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void
+dispose(GObject *object)
+{
+ GsmApp *app = GSM_APP (object);
+
+ if (app->desktop_file)
+ {
+ egg_desktop_file_free (app->desktop_file);
+ app->desktop_file = NULL;
+ }
+
+ if (app->startup_id)
+ {
+ g_free (app->startup_id);
+ app->startup_id = NULL;
+ }
+
+ if (app->client_id)
+ {
+ g_free (app->client_id);
+ app->client_id = NULL;
+ }
+}
+
+/**
+ * gsm_app_get_basename:
+ * @app: a %GsmApp
+ *
+ * Returns an identifying name for @app, e.g. the basename of the path to
+ * @app's desktop file (if any).
+ *
+ * Return value: an identifying name for @app, or %NULL.
+ **/
+const char *
+gsm_app_get_basename (GsmApp *app)
+{
+ return GSM_APP_GET_CLASS (app)->get_basename (app);
+}
+
+static const char *
+get_basename (GsmApp *app)
+{
+ const char *location, *slash;
+
+ if (!app->desktop_file)
+ return NULL;
+
+ location = egg_desktop_file_get_source (app->desktop_file);
+
+ slash = strrchr (location, '/');
+ if (slash)
+ return slash + 1;
+ else
+ return location;
+}
+
+/**
+ * gsm_app_get_phase:
+ * @app: a %GsmApp
+ *
+ * Returns @app's startup phase.
+ *
+ * Return value: @app's startup phase
+ **/
+GsmSessionPhase
+gsm_app_get_phase (GsmApp *app)
+{
+ g_return_val_if_fail (GSM_IS_APP (app), GSM_SESSION_PHASE_APPLICATION);
+
+ return app->phase;
+}
+
+/**
+ * gsm_app_is_disabled:
+ * @app: a %GsmApp
+ *
+ * Tests if @app is disabled
+ *
+ * Return value: whether or not @app is disabled
+ **/
+gboolean
+gsm_app_is_disabled (GsmApp *app)
+{
+ g_return_val_if_fail (GSM_IS_APP (app), FALSE);
+
+ if (GSM_APP_GET_CLASS (app)->is_disabled)
+ return GSM_APP_GET_CLASS (app)->is_disabled (app);
+ else
+ return FALSE;
+}
+
+gboolean
+gsm_app_provides (GsmApp *app, const char *service)
+{
+ char **provides;
+ gsize len, i;
+
+ g_return_val_if_fail (GSM_IS_APP (app), FALSE);
+
+ if (!app->desktop_file)
+ return FALSE;
+
+ provides = egg_desktop_file_get_string_list (app->desktop_file,
+ "X-GNOME-Provides",
+ &len, NULL);
+ if (!provides)
+ return FALSE;
+
+ for (i = 0; i < len; i++)
+ {
+ if (!strcmp (provides[i], service))
+ {
+ g_strfreev (provides);
+ return TRUE;
+ }
+ }
+
+ g_strfreev (provides);
+ return FALSE;
+}
+
+static void
+app_exited (GPid pid, gint status, gpointer data)
+{
+ if (WIFEXITED (status))
+ g_signal_emit (GSM_APP (data), signals[EXITED], 0);
+}
+
+static pid_t
+launch (GsmApp *app,
+ GError **err)
+{
+ char *env[2] = { NULL, NULL };
+ gboolean success;
+
+ g_return_val_if_fail (app->desktop_file != NULL, (pid_t)-1);
+
+ if (egg_desktop_file_get_boolean (app->desktop_file,
+ "X-GNOME-Autostart-Notify", NULL) ||
+ egg_desktop_file_get_boolean (app->desktop_file,
+ "AutostartNotify", NULL))
+ env[0] = g_strdup_printf ("DESKTOP_AUTOSTART_ID=%s", app->client_id);
+
+#if 0
+ g_debug ("launching %s with client_id %s\n",
+ gsm_app_get_basename (app), app->client_id);
+#endif
+
+ success =
+ egg_desktop_file_launch (app->desktop_file, NULL, err,
+ EGG_DESKTOP_FILE_LAUNCH_PUTENV, env,
+ EGG_DESKTOP_FILE_LAUNCH_FLAGS, G_SPAWN_DO_NOT_REAP_CHILD,
+ EGG_DESKTOP_FILE_LAUNCH_RETURN_PID, &app->pid,
+ EGG_DESKTOP_FILE_LAUNCH_RETURN_STARTUP_ID, &app->startup_id,
+ NULL);
+
+ g_free (env[0]);
+
+ if (success)
+ {
+ /* In case the app belongs to Initialization phase, we monitor
+ * if it exits to emit proper "exited" signal to session. */
+ if (app->phase == GSM_SESSION_PHASE_INITIALIZATION)
+ g_child_watch_add ((GPid) app->pid, app_exited, app);
+
+ return app->pid;
+ }
+ else
+ return (pid_t) -1;
+}
+
+/**
+ * gsm_app_launch:
+ * @app: a %GsmApp
+ * @err: an error pointer
+ *
+ * Launches @app
+ *
+ * Return value: the pid of the new process, or -1 on error
+ **/
+pid_t
+gsm_app_launch (GsmApp *app, GError **err)
+{
+ return GSM_APP_GET_CLASS (app)->launch (app, err);
+}
+
+/**
+ * gsm_app_registered:
+ * @app: a %GsmApp
+ *
+ * Emits "registered" signal in @app
+ **/
+void
+gsm_app_registered (GsmApp *app)
+{
+ g_return_if_fail (GSM_IS_APP (app));
+
+ g_signal_emit (app, signals[REGISTERED], 0);
+}
+
diff --git a/toolkit/src/sugar/gsm-app.h b/toolkit/src/sugar/gsm-app.h
new file mode 100644
index 0000000..038caee
--- /dev/null
+++ b/toolkit/src/sugar/gsm-app.h
@@ -0,0 +1,70 @@
+/* gsmapp.h
+ * Copyright (C) 2006 Novell, Inc.
+ *
+ */
+
+#ifndef __GSM_APP_H__
+#define __GSM_APP_H__
+
+#include <glib-object.h>
+#include <sys/types.h>
+
+#include "eggdesktopfile.h"
+#include "gsm-session.h"
+
+G_BEGIN_DECLS
+
+#define GSM_TYPE_APP (gsm_app_get_type ())
+#define GSM_APP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSM_TYPE_APP, GsmApp))
+#define GSM_APP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSM_TYPE_APP, GsmAppClass))
+#define GSM_IS_APP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSM_TYPE_APP))
+#define GSM_IS_APP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSM_TYPE_APP))
+#define GSM_APP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSM_TYPE_APP, GsmAppClass))
+
+typedef struct _GsmApp GsmApp;
+typedef struct _GsmAppClass GsmAppClass;
+typedef struct _GsmAppPrivate GsmAppPrivate;
+
+struct _GsmApp
+{
+ GObject parent;
+
+ EggDesktopFile *desktop_file;
+ GsmSessionPhase phase;
+
+ pid_t pid;
+ char *startup_id, *client_id;
+};
+
+struct _GsmAppClass
+{
+ GObjectClass parent_class;
+
+ /* signals */
+ void (*exited) (GsmApp *app, int status);
+ void (*registered) (GsmApp *app);
+
+ /* virtual methods */
+ const char *(*get_basename) (GsmApp *app);
+ gboolean (*is_disabled) (GsmApp *app);
+ pid_t (*launch) (GsmApp *app, GError **err);
+ void (*set_client) (GsmApp *app, GsmClient *client);
+};
+
+GType gsm_app_get_type (void) G_GNUC_CONST;
+
+const char *gsm_app_get_basename (GsmApp *app);
+GsmSessionPhase gsm_app_get_phase (GsmApp *app);
+gboolean gsm_app_provides (GsmApp *app,
+ const char *service);
+gboolean gsm_app_is_disabled (GsmApp *app);
+pid_t gsm_app_launch (GsmApp *app,
+ GError **err);
+void gsm_app_set_client (GsmApp *app,
+ GsmClient *client);
+
+void gsm_app_registered (GsmApp *app);
+
+G_END_DECLS
+
+#endif /* __GSM_APP_H__ */
diff --git a/toolkit/src/sugar/gsm-client-xsmp.c b/toolkit/src/sugar/gsm-client-xsmp.c
new file mode 100644
index 0000000..632dec9
--- /dev/null
+++ b/toolkit/src/sugar/gsm-client-xsmp.c
@@ -0,0 +1,828 @@
+/* client-xsmp.c
+ * Copyright (C) 2007 Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser 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., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "gsm-client-xsmp.h"
+#include "gsm-session.h"
+
+/* FIXME */
+#define GsmDesktopFile "_Gsm_DesktopFile"
+
+static gboolean client_iochannel_watch (GIOChannel *channel,
+ GIOCondition condition,
+ gpointer data);
+static gboolean client_protocol_timeout (gpointer data);
+
+static void set_description (GsmClientXSMP *xsmp);
+
+static const char *xsmp_get_client_id (GsmClient *client);
+static pid_t xsmp_get_pid (GsmClient *client);
+static char *xsmp_get_desktop_file (GsmClient *client);
+static char *xsmp_get_restart_command (GsmClient *client);
+static char *xsmp_get_discard_command (GsmClient *client);
+static gboolean xsmp_get_autorestart (GsmClient *client);
+
+static void xsmp_finalize (GObject *object);
+static void xsmp_restart (GsmClient *client,
+ GError **error);
+static void xsmp_save_yourself (GsmClient *client,
+ gboolean save_state);
+static void xsmp_save_yourself_phase2 (GsmClient *client);
+static void xsmp_interact (GsmClient *client);
+static void xsmp_shutdown_cancelled (GsmClient *client);
+static void xsmp_die (GsmClient *client);
+
+G_DEFINE_TYPE (GsmClientXSMP, gsm_client_xsmp, GSM_TYPE_CLIENT)
+
+static void
+gsm_client_xsmp_init (GsmClientXSMP *xsmp)
+{
+ ;
+}
+
+static void
+gsm_client_xsmp_class_init (GsmClientXSMPClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GsmClientClass *client_class = GSM_CLIENT_CLASS (klass);
+
+ object_class->finalize = xsmp_finalize;
+
+ client_class->get_client_id = xsmp_get_client_id;
+ client_class->get_pid = xsmp_get_pid;
+ client_class->get_desktop_file = xsmp_get_desktop_file;
+ client_class->get_restart_command = xsmp_get_restart_command;
+ client_class->get_discard_command = xsmp_get_discard_command;
+ client_class->get_autorestart = xsmp_get_autorestart;
+
+ client_class->restart = xsmp_restart;
+ client_class->save_yourself = xsmp_save_yourself;
+ client_class->save_yourself_phase2 = xsmp_save_yourself_phase2;
+ client_class->interact = xsmp_interact;
+ client_class->shutdown_cancelled = xsmp_shutdown_cancelled;
+ client_class->die = xsmp_die;
+}
+
+GsmClientXSMP *
+gsm_client_xsmp_new (IceConn ice_conn)
+{
+ GsmClientXSMP *xsmp;
+ GIOChannel *channel;
+ int fd;
+
+ xsmp = g_object_new (GSM_TYPE_CLIENT_XSMP, NULL);
+ xsmp->props = g_ptr_array_new ();
+
+ xsmp->ice_conn = ice_conn;
+ xsmp->current_save_yourself = -1;
+ xsmp->next_save_yourself = -1;
+
+ fd = IceConnectionNumber (ice_conn);
+ fcntl (fd, F_SETFD, fcntl (fd, F_GETFD, 0) | FD_CLOEXEC);
+
+ channel = g_io_channel_unix_new (fd);
+ xsmp->watch_id = g_io_add_watch (channel, G_IO_IN | G_IO_ERR,
+ client_iochannel_watch, xsmp);
+ g_io_channel_unref (channel);
+
+ xsmp->protocol_timeout = g_timeout_add_seconds (5, client_protocol_timeout, xsmp);
+
+ set_description (xsmp);
+ g_debug ("New client '%s'", xsmp->description);
+
+ return xsmp;
+}
+
+static void
+xsmp_finalize (GObject *object)
+{
+ GsmClientXSMP *xsmp = (GsmClientXSMP *) object;
+
+ g_debug ("xsmp_finalize (%s)", xsmp->description);
+
+ if (xsmp->watch_id)
+ g_source_remove (xsmp->watch_id);
+
+ if (xsmp->conn)
+ SmsCleanUp (xsmp->conn);
+ else
+ IceCloseConnection (xsmp->ice_conn);
+
+ if (xsmp->protocol_timeout)
+ g_source_remove (xsmp->protocol_timeout);
+
+ G_OBJECT_CLASS (gsm_client_xsmp_parent_class)->finalize (object);
+}
+
+static gboolean
+client_iochannel_watch (GIOChannel *channel,
+ GIOCondition condition,
+ gpointer data)
+{
+ GsmClient *client = data;
+ GsmClientXSMP *xsmp = data;
+
+ switch (IceProcessMessages (xsmp->ice_conn, NULL, NULL))
+ {
+ case IceProcessMessagesSuccess:
+ return TRUE;
+
+ case IceProcessMessagesIOError:
+ g_debug ("IceProcessMessagesIOError on '%s'", xsmp->description);
+ gsm_client_disconnected (client);
+ return FALSE;
+
+ case IceProcessMessagesConnectionClosed:
+ g_debug ("IceProcessMessagesConnectionClosed on '%s'",
+ xsmp->description);
+ return FALSE;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+/* Called if too much time passes between the initial connection and
+ * the XSMP protocol setup.
+ */
+static gboolean
+client_protocol_timeout (gpointer data)
+{
+ GsmClient *client = data;
+ GsmClientXSMP *xsmp = data;
+
+ g_debug ("client_protocol_timeout for client '%s' in ICE status %d",
+ xsmp->description, IceConnectionStatus (xsmp->ice_conn));
+ gsm_client_disconnected (client);
+
+ return FALSE;
+}
+
+static Status
+register_client_callback (SmsConn conn,
+ SmPointer manager_data,
+ char *previous_id)
+{
+ GsmClient *client = manager_data;
+ GsmClientXSMP *xsmp = manager_data;
+ char *id;
+
+ g_debug ("Client '%s' received RegisterClient(%s)",
+ xsmp->description,
+ previous_id ? previous_id : "NULL");
+
+ id = gsm_session_register_client (global_session, client, previous_id);
+
+ if (id == NULL)
+ {
+ g_debug (" rejected: invalid previous_id");
+ free (previous_id);
+ return FALSE;
+ }
+
+ xsmp->id = id;
+
+ set_description (xsmp);
+
+ g_debug ("Sending RegisterClientReply to '%s'", xsmp->description);
+
+ SmsRegisterClientReply (conn, xsmp->id);
+
+ if (!previous_id)
+ {
+ /* Send the initial SaveYourself. */
+ g_debug ("Sending initial SaveYourself");
+ SmsSaveYourself (conn, SmSaveLocal, False, SmInteractStyleNone, False);
+ xsmp->current_save_yourself = SmSaveLocal;
+
+ free (previous_id);
+ }
+
+ return TRUE;
+}
+
+static void
+do_save_yourself (GsmClientXSMP *xsmp, int save_type)
+{
+ if (xsmp->next_save_yourself != -1)
+ {
+ /* Either we're currently doing a shutdown and there's a checkpoint
+ * queued after it, or vice versa. Either way, the new SaveYourself
+ * is redundant.
+ */
+ g_debug (" skipping redundant SaveYourself for '%s'",
+ xsmp->description);
+ }
+ else if (xsmp->current_save_yourself != -1)
+ {
+ g_debug (" queuing new SaveYourself for '%s'",
+ xsmp->description);
+ xsmp->next_save_yourself = save_type;
+ }
+ else
+ {
+ xsmp->current_save_yourself = save_type;
+
+ switch (save_type)
+ {
+ case SmSaveLocal:
+ /* Save state */
+ SmsSaveYourself (xsmp->conn, SmSaveLocal, FALSE,
+ SmInteractStyleNone, FALSE);
+ break;
+
+ default:
+ /* Logout */
+ SmsSaveYourself (xsmp->conn, save_type, TRUE,
+ SmInteractStyleAny, FALSE);
+ break;
+ }
+ }
+}
+
+static void
+save_yourself_request_callback (SmsConn conn,
+ SmPointer manager_data,
+ int save_type,
+ Bool shutdown,
+ int interact_style,
+ Bool fast,
+ Bool global)
+{
+ GsmClientXSMP *xsmp = manager_data;
+
+ g_debug ("Client '%s' received SaveYourselfRequest(%s, %s, %s, %s, %s)",
+ xsmp->description,
+ save_type == SmSaveLocal ? "SmSaveLocal" :
+ save_type == SmSaveGlobal ? "SmSaveGlobal" : "SmSaveBoth",
+ shutdown ? "Shutdown" : "!Shutdown",
+ interact_style == SmInteractStyleAny ? "SmInteractStyleAny" :
+ interact_style == SmInteractStyleErrors ? "SmInteractStyleErrors" :
+ "SmInteractStyleNone", fast ? "Fast" : "!Fast",
+ global ? "Global" : "!Global");
+
+ /* Examining the g_debug above, you can see that there are a total
+ * of 72 different combinations of options that this could have been
+ * called with. However, most of them are stupid.
+ *
+ * If @shutdown and @global are both TRUE, that means the caller is
+ * requesting that a logout message be sent to all clients, so we do
+ * that. We use @fast to decide whether or not to show a
+ * confirmation dialog. (This isn't really what @fast is for, but
+ * the old gnome-session and ksmserver both interpret it that way,
+ * so we do too.) We ignore @save_type because we pick the correct
+ * save_type ourselves later based on user prefs, dialog choices,
+ * etc, and we ignore @interact_style, because clients have not used
+ * it correctly consistently enough to make it worth honoring.
+ *
+ * If @shutdown is TRUE and @global is FALSE, the caller is
+ * confused, so we ignore the request.
+ *
+ * If @shutdown is FALSE and @save_type is SmSaveGlobal or
+ * SmSaveBoth, then the client wants us to ask some or all open
+ * applications to save open files to disk, but NOT quit. This is
+ * silly and so we ignore the request.
+ *
+ * If @shutdown is FALSE and @save_type is SmSaveLocal, then the
+ * client wants us to ask some or all open applications to update
+ * their current saved state, but not log out. At the moment, the
+ * code only supports this for the !global case (ie, a client
+ * requesting that it be allowed to update *its own* saved state,
+ * but not having everyone else update their saved state).
+ */
+
+ if (shutdown && global)
+ {
+ g_debug (" initiating shutdown");
+/* gsm_session_initiate_shutdown (global_session,
+ !fast,
+ GSM_SESSION_LOGOUT_TYPE_LOGOUT);
+*/
+ }
+ else if (!shutdown && !global)
+ {
+ g_debug (" initiating checkpoint");
+ do_save_yourself (xsmp, SmSaveLocal);
+ }
+ else
+ g_debug (" ignoring");
+}
+
+static void
+xsmp_restart (GsmClient *client, GError **error)
+{
+ char *restart_cmd = gsm_client_get_restart_command (client);
+
+ g_spawn_command_line_async (restart_cmd, error);
+
+ g_free (restart_cmd);
+}
+
+static void
+xsmp_save_yourself (GsmClient *client, gboolean save_state)
+{
+ GsmClientXSMP *xsmp = (GsmClientXSMP *)client;
+
+ g_debug ("xsmp_save_yourself ('%s', %s)", xsmp->description,
+ save_state ? "True" : "False");
+
+ do_save_yourself (xsmp, save_state ? SmSaveBoth : SmSaveGlobal);
+}
+
+static void
+save_yourself_phase2_request_callback (SmsConn conn,
+ SmPointer manager_data)
+{
+ GsmClient *client = manager_data;
+ GsmClientXSMP *xsmp = manager_data;
+
+ g_debug ("Client '%s' received SaveYourselfPhase2Request",
+ xsmp->description);
+
+ if (xsmp->current_save_yourself == SmSaveLocal)
+ {
+ /* WTF? Anyway, if it's checkpointing, it doesn't have to wait
+ * for anyone else.
+ */
+ SmsSaveYourselfPhase2 (xsmp->conn);
+ }
+ else
+ gsm_client_request_phase2 (client);
+}
+
+static void
+xsmp_save_yourself_phase2 (GsmClient *client)
+{
+ GsmClientXSMP *xsmp = (GsmClientXSMP *)client;
+
+ g_debug ("xsmp_save_yourself_phase2 ('%s')", xsmp->description);
+
+ SmsSaveYourselfPhase2 (xsmp->conn);
+}
+
+static void
+interact_request_callback (SmsConn conn,
+ SmPointer manager_data,
+ int dialog_type)
+{
+ GsmClient *client = manager_data;
+ GsmClientXSMP *xsmp = manager_data;
+
+ g_debug ("Client '%s' received InteractRequest(%s)", xsmp->description,
+ dialog_type == SmInteractStyleAny ? "Any" : "Errors");
+
+ gsm_client_request_interaction (client);
+}
+
+static void
+xsmp_interact (GsmClient *client)
+{
+ GsmClientXSMP *xsmp = (GsmClientXSMP *) client;
+
+ g_debug ("xsmp_interact ('%s')", xsmp->description);
+
+ SmsInteract (xsmp->conn);
+}
+
+static void
+interact_done_callback (SmsConn conn,
+ SmPointer manager_data,
+ Bool cancel_shutdown)
+{
+ GsmClient *client = manager_data;
+ GsmClientXSMP *xsmp = manager_data;
+
+ g_debug ("Client '%s' received InteractDone(cancel_shutdown = %s)",
+ xsmp->description, cancel_shutdown ? "True" : "False");
+
+ gsm_client_interaction_done (client, cancel_shutdown);
+}
+
+static void
+xsmp_shutdown_cancelled (GsmClient *client)
+{
+ GsmClientXSMP *xsmp = (GsmClientXSMP *) client;
+
+ g_debug ("xsmp_shutdown_cancelled ('%s')", xsmp->description);
+
+ SmsShutdownCancelled (xsmp->conn);
+}
+
+static void
+xsmp_die (GsmClient *client)
+{
+ GsmClientXSMP *xsmp = (GsmClientXSMP *) client;
+
+ g_debug ("xsmp_die ('%s')", xsmp->description);
+
+ SmsDie (xsmp->conn);
+}
+
+static void
+save_yourself_done_callback (SmsConn conn,
+ SmPointer manager_data,
+ Bool success)
+{
+ GsmClient *client = manager_data;
+ GsmClientXSMP *xsmp = manager_data;
+
+ g_debug ("Client '%s' received SaveYourselfDone(success = %s)",
+ xsmp->description, success ? "True" : "False");
+
+ if (xsmp->current_save_yourself == SmSaveLocal)
+ {
+ xsmp->current_save_yourself = -1;
+ SmsSaveComplete (xsmp->conn);
+ gsm_client_saved_state (client);
+ }
+ else
+ {
+ xsmp->current_save_yourself = -1;
+ gsm_client_save_yourself_done (client);
+ }
+
+ if (xsmp->next_save_yourself)
+ {
+ int save_type = xsmp->next_save_yourself;
+
+ xsmp->next_save_yourself = -1;
+ do_save_yourself (xsmp, save_type);
+ }
+}
+
+static void
+close_connection_callback (SmsConn conn,
+ SmPointer manager_data,
+ int count,
+ char **reason_msgs)
+{
+ GsmClient *client = manager_data;
+ GsmClientXSMP *xsmp = manager_data;
+ int i;
+
+ g_debug ("Client '%s' received CloseConnection", xsmp->description);
+ for (i = 0; i < count; i++)
+ g_debug (" close reason: '%s'", reason_msgs[i]);
+ SmFreeReasons (count, reason_msgs);
+
+ gsm_client_disconnected (client);
+}
+
+static void
+debug_print_property (SmProp *prop)
+{
+ GString *tmp;
+ int i;
+
+ switch (prop->type[0])
+ {
+ case 'C': /* CARD8 */
+ g_debug (" %s = %d", prop->name, *(unsigned char *)prop->vals[0].value);
+ break;
+
+ case 'A': /* ARRAY8 */
+ g_debug (" %s = '%s'", prop->name, (char *)prop->vals[0].value);
+ break;
+
+ case 'L': /* LISTofARRAY8 */
+ tmp = g_string_new (NULL);
+ for (i = 0; i < prop->num_vals; i++)
+ {
+ g_string_append_printf (tmp, "'%.*s' ", prop->vals[i].length,
+ (char *)prop->vals[i].value);
+ }
+ g_debug (" %s = %s", prop->name, tmp->str);
+ g_string_free (tmp, TRUE);
+ break;
+
+ default:
+ g_debug (" %s = ??? (%s)", prop->name, prop->type);
+ break;
+ }
+}
+
+static SmProp *
+find_property (GsmClientXSMP *client, const char *name, int *index)
+{
+ SmProp *prop;
+ int i;
+
+ for (i = 0; i < client->props->len; i++)
+ {
+ prop = client->props->pdata[i];
+
+ if (!strcmp (prop->name, name))
+ {
+ if (index)
+ *index = i;
+ return prop;
+ }
+ }
+
+ return NULL;
+}
+
+static void
+delete_property (GsmClientXSMP *client, const char *name)
+{
+ int index;
+ SmProp *prop;
+
+ prop = find_property (client, name, &index);
+ if (!prop)
+ return;
+
+#if 0
+ /* This is wrong anyway; we can't unconditionally run the current
+ * discard command; if this client corresponds to a GsmAppResumed,
+ * and the current discard command is identical to the app's
+ * discard_command, then we don't run the discard command now,
+ * because that would delete a saved state we may want to resume
+ * again later.
+ */
+ if (!strcmp (name, SmDiscardCommand))
+ gsm_client_run_discard (client);
+#endif
+
+ g_ptr_array_remove_index_fast (client->props, index);
+ SmFreeProperty (prop);
+}
+
+static void
+set_properties_callback (SmsConn conn,
+ SmPointer manager_data,
+ int num_props,
+ SmProp **props)
+{
+ GsmClientXSMP *client = manager_data;
+ int i;
+
+ g_debug ("Set properties from client '%s'", client->description);
+
+ for (i = 0; i < num_props; i++)
+ {
+ delete_property (client, props[i]->name);
+ g_ptr_array_add (client->props, props[i]);
+
+ debug_print_property (props[i]);
+
+ if (!strcmp (props[i]->name, SmProgram))
+ set_description (client);
+ }
+
+ free (props);
+
+}
+
+static void
+delete_properties_callback (SmsConn conn,
+ SmPointer manager_data,
+ int num_props,
+ char **prop_names)
+{
+ GsmClientXSMP *client = manager_data;
+ int i;
+
+ g_debug ("Delete properties from '%s'", client->description);
+
+ for (i = 0; i < num_props; i++)
+ {
+ delete_property (client, prop_names[i]);
+
+ g_debug (" %s", prop_names[i]);
+ }
+
+ free (prop_names);
+}
+
+static void
+get_properties_callback (SmsConn conn,
+ SmPointer manager_data)
+{
+ GsmClientXSMP *client = manager_data;
+
+ g_debug ("Get properties request from '%s'", client->description);
+
+ SmsReturnProperties (conn, client->props->len,
+ (SmProp **)client->props->pdata);
+}
+
+static const char *
+xsmp_get_client_id (GsmClient *client)
+{
+ GsmClientXSMP *xsmp = (GsmClientXSMP *) client;
+
+ return xsmp->id;
+}
+
+static pid_t
+xsmp_get_pid (GsmClient *client)
+{
+ GsmClientXSMP *xsmp = (GsmClientXSMP *) client;
+ SmProp *prop = find_property (xsmp, SmProcessID, NULL);
+ char buf[32];
+
+ if (!prop || strcmp (prop->type, SmARRAY8) != 0)
+ return (pid_t)-1;
+
+ /* prop->vals[0].value might not be '\0'-terminated... */
+ g_strlcpy (buf, prop->vals[0].value, MIN (prop->vals[0].length, sizeof (buf)));
+ return (pid_t)strtoul (buf, NULL, 10);
+}
+
+static char *
+xsmp_get_desktop_file (GsmClient *client)
+{
+ GsmClientXSMP *xsmp = (GsmClientXSMP *) client;
+ SmProp *prop = find_property (xsmp, GsmDesktopFile, NULL);
+
+ if (!prop || strcmp (prop->type, SmARRAY8) != 0)
+ return NULL;
+
+ return g_strndup (prop->vals[0].value, prop->vals[0].length);
+}
+
+static char *
+prop_to_command (SmProp *prop)
+{
+ GString *str;
+ int i, j;
+ gboolean need_quotes;
+
+ str = g_string_new (NULL);
+ for (i = 0; i < prop->num_vals; i++)
+ {
+ char *val = prop->vals[i].value;
+
+ need_quotes = FALSE;
+ for (j = 0; j < prop->vals[i].length; j++)
+ {
+ if (!g_ascii_isalnum (val[j]) && !strchr ("-_=:./", val[j]))
+ {
+ need_quotes = TRUE;
+ break;
+ }
+ }
+
+ if (i > 0)
+ g_string_append_c (str, ' ');
+
+ if (!need_quotes)
+ {
+ g_string_append_printf (str, "%.*s", prop->vals[i].length,
+ (char *)prop->vals[i].value);
+ }
+ else
+ {
+ g_string_append_c (str, '\'');
+ while (val < (char *)prop->vals[i].value + prop->vals[i].length)
+ {
+ if (*val == '\'')
+ g_string_append (str, "'\''");
+ else
+ g_string_append_c (str, *val);
+ val++;
+ }
+ g_string_append_c (str, '\'');
+ }
+ }
+
+ return g_string_free (str, FALSE);
+}
+
+static char *
+xsmp_get_restart_command (GsmClient *client)
+{
+ GsmClientXSMP *xsmp = (GsmClientXSMP *) client;
+ SmProp *prop = find_property (xsmp, SmRestartCommand, NULL);
+
+ if (!prop || strcmp (prop->type, SmLISTofARRAY8) != 0)
+ return NULL;
+
+ return prop_to_command (prop);
+}
+
+static char *
+xsmp_get_discard_command (GsmClient *client)
+{
+ GsmClientXSMP *xsmp = (GsmClientXSMP *) client;
+ SmProp *prop = find_property (xsmp, SmDiscardCommand, NULL);
+
+ if (!prop || strcmp (prop->type, SmLISTofARRAY8) != 0)
+ return NULL;
+
+ return prop_to_command (prop);
+}
+
+static gboolean
+xsmp_get_autorestart (GsmClient *client)
+{
+ GsmClientXSMP *xsmp = (GsmClientXSMP *) client;
+ SmProp *prop = find_property (xsmp, SmRestartStyleHint, NULL);
+
+ if (!prop || strcmp (prop->type, SmCARD8) != 0)
+ return FALSE;
+
+ return ((unsigned char *)prop->vals[0].value)[0] == SmRestartImmediately;
+}
+
+static void
+set_description (GsmClientXSMP *client)
+{
+ SmProp *prop = find_property (client, SmProgram, NULL);
+
+ g_free (client->description);
+ if (prop)
+ {
+ client->description = g_strdup_printf ("%p [%.*s %s]", client,
+ prop->vals[0].length,
+ (char *)prop->vals[0].value,
+ client->id);
+ }
+ else if (client->id)
+ client->description = g_strdup_printf ("%p [%s]", client, client->id);
+ else
+ client->description = g_strdup_printf ("%p", client);
+}
+
+void
+gsm_client_xsmp_connect (GsmClientXSMP *client, SmsConn conn,
+ unsigned long *mask_ret, SmsCallbacks *callbacks_ret)
+{
+ client->conn = conn;
+
+ if (client->protocol_timeout)
+ {
+ g_source_remove (client->protocol_timeout);
+ client->protocol_timeout = 0;
+ }
+
+ g_debug ("Initializing client %s", client->description);
+
+ *mask_ret = 0;
+
+ *mask_ret |= SmsRegisterClientProcMask;
+ callbacks_ret->register_client.callback = register_client_callback;
+ callbacks_ret->register_client.manager_data = client;
+
+ *mask_ret |= SmsInteractRequestProcMask;
+ callbacks_ret->interact_request.callback = interact_request_callback;
+ callbacks_ret->interact_request.manager_data = client;
+
+ *mask_ret |= SmsInteractDoneProcMask;
+ callbacks_ret->interact_done.callback = interact_done_callback;
+ callbacks_ret->interact_done.manager_data = client;
+
+ *mask_ret |= SmsSaveYourselfRequestProcMask;
+ callbacks_ret->save_yourself_request.callback = save_yourself_request_callback;
+ callbacks_ret->save_yourself_request.manager_data = client;
+
+ *mask_ret |= SmsSaveYourselfP2RequestProcMask;
+ callbacks_ret->save_yourself_phase2_request.callback = save_yourself_phase2_request_callback;
+ callbacks_ret->save_yourself_phase2_request.manager_data = client;
+
+ *mask_ret |= SmsSaveYourselfDoneProcMask;
+ callbacks_ret->save_yourself_done.callback = save_yourself_done_callback;
+ callbacks_ret->save_yourself_done.manager_data = client;
+
+ *mask_ret |= SmsCloseConnectionProcMask;
+ callbacks_ret->close_connection.callback = close_connection_callback;
+ callbacks_ret->close_connection.manager_data = client;
+
+ *mask_ret |= SmsSetPropertiesProcMask;
+ callbacks_ret->set_properties.callback = set_properties_callback;
+ callbacks_ret->set_properties.manager_data = client;
+
+ *mask_ret |= SmsDeletePropertiesProcMask;
+ callbacks_ret->delete_properties.callback = delete_properties_callback;
+ callbacks_ret->delete_properties.manager_data = client;
+
+ *mask_ret |= SmsGetPropertiesProcMask;
+ callbacks_ret->get_properties.callback = get_properties_callback;
+ callbacks_ret->get_properties.manager_data = client;
+}
diff --git a/toolkit/src/sugar/gsm-client-xsmp.h b/toolkit/src/sugar/gsm-client-xsmp.h
new file mode 100644
index 0000000..46e34a5
--- /dev/null
+++ b/toolkit/src/sugar/gsm-client-xsmp.h
@@ -0,0 +1,70 @@
+/* client-xsmp.h
+ * Copyright (C) 2007 Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser 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., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef __GSM_CLIENT_XSMP_H__
+#define __GSM_CLIENT_XSMP_H__
+
+#include "gsm-client.h"
+
+#include <X11/SM/SMlib.h>
+
+G_BEGIN_DECLS
+
+#define GSM_TYPE_CLIENT_XSMP (gsm_client_xsmp_get_type ())
+#define GSM_CLIENT_XSMP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSM_TYPE_CLIENT_XSMP, GsmClientXSMP))
+#define GSM_CLIENT_XSMP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSM_TYPE_CLIENT_XSMP, GsmClientXSMPClass))
+#define GSM_IS_CLIENT_XSMP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSM_TYPE_CLIENT_XSMP))
+#define GSM_IS_CLIENT_XSMP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSM_TYPE_CLIENT_XSMP))
+#define GSM_CLIENT_XSMP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSM_TYPE_CLIENT_XSMP, GsmClientXSMPClass))
+
+typedef struct _GsmClientXSMP GsmClientXSMP;
+typedef struct _GsmClientXSMPClass GsmClientXSMPClass;
+
+struct _GsmClientXSMP
+{
+ GsmClient parent;
+
+ SmsConn conn;
+ IceConn ice_conn;
+
+ guint watch_id, protocol_timeout;
+
+ int current_save_yourself, next_save_yourself;
+ char *id, *description;
+ GPtrArray *props;
+};
+
+struct _GsmClientXSMPClass
+{
+ GsmClientClass parent_class;
+
+};
+
+GType gsm_client_xsmp_get_type (void) G_GNUC_CONST;
+
+GsmClientXSMP *gsm_client_xsmp_new (IceConn ice_conn);
+
+void gsm_client_xsmp_connect (GsmClientXSMP *client,
+ SmsConn conn,
+ unsigned long *mask_ret,
+ SmsCallbacks *callbacks_ret);
+
+G_END_DECLS
+
+#endif /* __GSM_CLIENT_XSMP_H__ */
diff --git a/toolkit/src/sugar/gsm-client.c b/toolkit/src/sugar/gsm-client.c
new file mode 100644
index 0000000..31554d4
--- /dev/null
+++ b/toolkit/src/sugar/gsm-client.c
@@ -0,0 +1,251 @@
+/* client.c
+ * Copyright (C) 2007 Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser 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., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gsm-client.h"
+
+enum {
+ SAVED_STATE,
+ REQUEST_PHASE2,
+ REQUEST_INTERACTION,
+ INTERACTION_DONE,
+ SAVE_YOURSELF_DONE,
+ DISCONNECTED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (GsmClient, gsm_client, G_TYPE_OBJECT)
+
+static void
+gsm_client_init (GsmClient *client)
+{
+ ;
+}
+
+static void
+gsm_client_class_init (GsmClientClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ signals[SAVED_STATE] =
+ g_signal_new ("saved_state",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GsmClientClass, saved_state),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ signals[REQUEST_PHASE2] =
+ g_signal_new ("request_phase2",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GsmClientClass, request_phase2),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ signals[REQUEST_INTERACTION] =
+ g_signal_new ("request_interaction",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GsmClientClass, request_interaction),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ signals[INTERACTION_DONE] =
+ g_signal_new ("interaction_done",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GsmClientClass, interaction_done),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__BOOLEAN,
+ G_TYPE_NONE,
+ 1, G_TYPE_BOOLEAN);
+
+ signals[SAVE_YOURSELF_DONE] =
+ g_signal_new ("save_yourself_done",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GsmClientClass, save_yourself_done),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ signals[DISCONNECTED] =
+ g_signal_new ("disconnected",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GsmClientClass, disconnected),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+}
+
+const char *
+gsm_client_get_client_id (GsmClient *client)
+{
+ g_return_val_if_fail (GSM_IS_CLIENT (client), NULL);
+
+ return GSM_CLIENT_GET_CLASS (client)->get_client_id (client);
+}
+
+pid_t
+gsm_client_get_pid (GsmClient *client)
+{
+ g_return_val_if_fail (GSM_IS_CLIENT (client), -1);
+
+ return GSM_CLIENT_GET_CLASS (client)->get_pid (client);
+}
+
+char *
+gsm_client_get_desktop_file (GsmClient *client)
+{
+ g_return_val_if_fail (GSM_IS_CLIENT (client), NULL);
+
+ return GSM_CLIENT_GET_CLASS (client)->get_desktop_file (client);
+}
+
+char *
+gsm_client_get_restart_command (GsmClient *client)
+{
+ g_return_val_if_fail (GSM_IS_CLIENT (client), NULL);
+
+ return GSM_CLIENT_GET_CLASS (client)->get_restart_command (client);
+}
+
+char *
+gsm_client_get_discard_command (GsmClient *client)
+{
+ g_return_val_if_fail (GSM_IS_CLIENT (client), NULL);
+
+ return GSM_CLIENT_GET_CLASS (client)->get_discard_command (client);
+}
+
+gboolean
+gsm_client_get_autorestart (GsmClient *client)
+{
+ g_return_val_if_fail (GSM_IS_CLIENT (client), FALSE);
+
+ return GSM_CLIENT_GET_CLASS (client)->get_autorestart (client);
+}
+
+void
+gsm_client_save_state (GsmClient *client)
+{
+ g_return_if_fail (GSM_IS_CLIENT (client));
+}
+
+void
+gsm_client_restart (GsmClient *client, GError **error)
+{
+ g_return_if_fail (GSM_IS_CLIENT (client));
+
+ GSM_CLIENT_GET_CLASS (client)->restart (client, error);
+}
+
+void
+gsm_client_save_yourself (GsmClient *client,
+ gboolean save_state)
+{
+ g_return_if_fail (GSM_IS_CLIENT (client));
+
+ GSM_CLIENT_GET_CLASS (client)->save_yourself (client, save_state);
+}
+
+void
+gsm_client_save_yourself_phase2 (GsmClient *client)
+{
+ g_return_if_fail (GSM_IS_CLIENT (client));
+
+ GSM_CLIENT_GET_CLASS (client)->save_yourself_phase2 (client);
+}
+
+void
+gsm_client_interact (GsmClient *client)
+{
+ g_return_if_fail (GSM_IS_CLIENT (client));
+
+ GSM_CLIENT_GET_CLASS (client)->interact (client);
+}
+
+void
+gsm_client_shutdown_cancelled (GsmClient *client)
+{
+ g_return_if_fail (GSM_IS_CLIENT (client));
+
+ GSM_CLIENT_GET_CLASS (client)->shutdown_cancelled (client);
+}
+
+void
+gsm_client_die (GsmClient *client)
+{
+ g_return_if_fail (GSM_IS_CLIENT (client));
+
+ GSM_CLIENT_GET_CLASS (client)->die (client);
+}
+
+void
+gsm_client_saved_state (GsmClient *client)
+{
+ g_signal_emit (client, signals[SAVED_STATE], 0);
+}
+
+void
+gsm_client_request_phase2 (GsmClient *client)
+{
+ g_signal_emit (client, signals[REQUEST_PHASE2], 0);
+}
+
+void
+gsm_client_request_interaction (GsmClient *client)
+{
+ g_signal_emit (client, signals[REQUEST_INTERACTION], 0);
+}
+
+void
+gsm_client_interaction_done (GsmClient *client, gboolean cancel_shutdown)
+{
+ g_signal_emit (client, signals[INTERACTION_DONE], 0, cancel_shutdown);
+}
+
+void
+gsm_client_save_yourself_done (GsmClient *client)
+{
+ g_signal_emit (client, signals[SAVE_YOURSELF_DONE], 0);
+}
+
+void
+gsm_client_disconnected (GsmClient *client)
+{
+ g_signal_emit (client, signals[DISCONNECTED], 0);
+}
+
diff --git a/toolkit/src/sugar/gsm-client.h b/toolkit/src/sugar/gsm-client.h
new file mode 100644
index 0000000..4cfd7ed
--- /dev/null
+++ b/toolkit/src/sugar/gsm-client.h
@@ -0,0 +1,111 @@
+/* client.h
+ * Copyright (C) 2007 Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser 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., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef __GSM_CLIENT_H__
+#define __GSM_CLIENT_H__
+
+#include <glib-object.h>
+#include <sys/types.h>
+
+G_BEGIN_DECLS
+
+#define GSM_TYPE_CLIENT (gsm_client_get_type ())
+#define GSM_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSM_TYPE_CLIENT, GsmClient))
+#define GSM_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSM_TYPE_CLIENT, GsmClientClass))
+#define GSM_IS_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSM_TYPE_CLIENT))
+#define GSM_IS_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSM_TYPE_CLIENT))
+#define GSM_CLIENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSM_TYPE_CLIENT, GsmClientClass))
+
+typedef struct _GsmClient GsmClient;
+typedef struct _GsmClientClass GsmClientClass;
+
+struct _GsmClient
+{
+ GObject parent;
+
+};
+
+struct _GsmClientClass
+{
+ GObjectClass parent_class;
+
+ /* signals */
+ void (*saved_state) (GsmClient *client);
+
+ void (*request_phase2) (GsmClient *client);
+
+ void (*request_interaction) (GsmClient *client);
+ void (*interaction_done) (GsmClient *client,
+ gboolean cancel_shutdown);
+
+ void (*save_yourself_done) (GsmClient *client);
+
+ void (*disconnected) (GsmClient *client);
+
+ /* virtual methods */
+ const char * (*get_client_id) (GsmClient *client);
+ pid_t (*get_pid) (GsmClient *client);
+ char * (*get_desktop_file) (GsmClient *client);
+ char * (*get_restart_command) (GsmClient *client);
+ char * (*get_discard_command) (GsmClient *client);
+ gboolean (*get_autorestart) (GsmClient *client);
+
+ void (*restart) (GsmClient *client,
+ GError **error);
+ void (*save_yourself) (GsmClient *client,
+ gboolean save_state);
+ void (*save_yourself_phase2) (GsmClient *client);
+ void (*interact) (GsmClient *client);
+ void (*shutdown_cancelled) (GsmClient *client);
+ void (*die) (GsmClient *client);
+};
+
+GType gsm_client_get_type (void) G_GNUC_CONST;
+
+const char *gsm_client_get_client_id (GsmClient *client);
+
+pid_t gsm_client_get_pid (GsmClient *client);
+char *gsm_client_get_desktop_file (GsmClient *client);
+char *gsm_client_get_restart_command (GsmClient *client);
+char *gsm_client_get_discard_command (GsmClient *client);
+gboolean gsm_client_get_autorestart (GsmClient *client);
+
+void gsm_client_save_state (GsmClient *client);
+
+void gsm_client_restart (GsmClient *client,
+ GError **error);
+void gsm_client_save_yourself (GsmClient *client,
+ gboolean save_state);
+void gsm_client_save_yourself_phase2 (GsmClient *client);
+void gsm_client_interact (GsmClient *client);
+void gsm_client_shutdown_cancelled (GsmClient *client);
+void gsm_client_die (GsmClient *client);
+
+/* protected */
+void gsm_client_saved_state (GsmClient *client);
+void gsm_client_request_phase2 (GsmClient *client);
+void gsm_client_request_interaction (GsmClient *client);
+void gsm_client_interaction_done (GsmClient *client,
+ gboolean cancel_shutdown);
+void gsm_client_save_yourself_done (GsmClient *client);
+void gsm_client_disconnected (GsmClient *client);
+
+G_END_DECLS
+
+#endif /* __GSM_CLIENT_H__ */
diff --git a/toolkit/src/sugar/gsm-session.c b/toolkit/src/sugar/gsm-session.c
new file mode 100644
index 0000000..6430a94
--- /dev/null
+++ b/toolkit/src/sugar/gsm-session.c
@@ -0,0 +1,509 @@
+/* session.c
+ * Copyright (C) 2007 Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser 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., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <string.h>
+
+#include "gsm-session.h"
+#include "gsm-app.h"
+#include "gsm-xsmp.h"
+
+GsmSession *global_session;
+
+static void initiate_shutdown (GsmSession *session);
+
+static void session_shutdown (GsmSession *session);
+
+static void client_saved_state (GsmClient *client,
+ gpointer data);
+static void client_request_phase2 (GsmClient *client,
+ gpointer data);
+static void client_request_interaction (GsmClient *client,
+ gpointer data);
+static void client_interaction_done (GsmClient *client,
+ gboolean cancel_shutdown,
+ gpointer data);
+static void client_save_yourself_done (GsmClient *client,
+ gpointer data);
+static void client_disconnected (GsmClient *client,
+ gpointer data);
+
+struct _GsmSession {
+ GObject parent;
+
+ char *name;
+
+ /* Current status */
+ GsmSessionPhase phase;
+ guint timeout;
+ GSList *pending_apps;
+
+ /* SM clients */
+ GSList *clients;
+
+ /* When shutdown starts, all clients are put into shutdown_clients.
+ * If they request phase2, they are moved from shutdown_clients to
+ * phase2_clients. If they request interaction, they are appended
+ * to interact_clients (the first client in interact_clients is
+ * the one currently interacting). If they report that they're done,
+ * they're removed from shutdown_clients/phase2_clients.
+ *
+ * Once shutdown_clients is empty, phase2 starts. Once phase2_clients
+ * is empty, shutdown is complete.
+ */
+ GSList *shutdown_clients;
+ GSList *interact_clients;
+ GSList *phase2_clients;
+
+ /* List of clients which were disconnected due to disabled condition
+ * and shouldn't be automatically restarted */
+ GSList *condition_clients;
+};
+
+struct _GsmSessionClass
+{
+ GObjectClass parent_class;
+
+ void (* shutdown_completed) (GsmSession *client);
+};
+
+enum {
+ SHUTDOWN_COMPLETED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (GsmSession, gsm_session, G_TYPE_OBJECT)
+
+#define GSM_SESSION_PHASE_TIMEOUT 10 /* seconds */
+
+void
+gsm_session_init (GsmSession *session)
+{
+ session->name = NULL;
+ session->clients = NULL;
+ session->condition_clients = NULL;
+}
+
+static void
+gsm_session_class_init (GsmSessionClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ signals[SHUTDOWN_COMPLETED] =
+ g_signal_new ("shutdown_completed",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GsmSessionClass, shutdown_completed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+}
+
+
+/**
+ * gsm_session_set_name:
+ * @session: session instance
+ * @name: name of the session
+ *
+ * Sets the name of a running session.
+ **/
+void
+gsm_session_set_name (GsmSession *session, const char *name)
+{
+ if (session->name)
+ g_free (session->name);
+
+ session->name = g_strdup (name);
+}
+
+static void start_phase (GsmSession *session);
+
+static void
+end_phase (GsmSession *session)
+{
+ g_slist_free (session->pending_apps);
+ session->pending_apps = NULL;
+
+ g_debug ("ending phase %d\n", session->phase);
+
+ session->phase++;
+
+ if (session->phase < GSM_SESSION_PHASE_RUNNING)
+ start_phase (session);
+}
+
+static void
+app_registered (GsmApp *app, gpointer data)
+{
+ GsmSession *session = data;
+
+ session->pending_apps = g_slist_remove (session->pending_apps, app);
+ g_signal_handlers_disconnect_by_func (app, app_registered, session);
+
+ if (!session->pending_apps)
+ {
+ if (session->timeout > 0)
+ {
+ g_source_remove (session->timeout);
+ session->timeout = 0;
+ }
+
+ end_phase (session);
+ }
+}
+
+static gboolean
+phase_timeout (gpointer data)
+{
+ GsmSession *session = data;
+ GSList *a;
+
+ session->timeout = 0;
+
+ for (a = session->pending_apps; a; a = a->next)
+ {
+ g_warning ("Application '%s' failed to register before timeout",
+ gsm_app_get_basename (a->data));
+ g_signal_handlers_disconnect_by_func (a->data, app_registered, session);
+
+ /* FIXME: what if the app was filling in a required slot? */
+ }
+
+ end_phase (session);
+ return FALSE;
+}
+
+static void
+start_phase (GsmSession *session)
+{
+ g_debug ("starting phase %d\n", session->phase);
+
+ g_slist_free (session->pending_apps);
+ session->pending_apps = NULL;
+
+ if (session->pending_apps)
+ {
+ if (session->phase < GSM_SESSION_PHASE_APPLICATION)
+ {
+ session->timeout = g_timeout_add_seconds (GSM_SESSION_PHASE_TIMEOUT,
+ phase_timeout, session);
+ }
+ }
+ else
+ end_phase (session);
+}
+
+void
+gsm_session_start (GsmSession *session)
+{
+ session->phase = GSM_SESSION_PHASE_INITIALIZATION;
+
+ start_phase (session);
+}
+
+GsmSessionPhase
+gsm_session_get_phase (GsmSession *session)
+{
+ return session->phase;
+}
+
+char *
+gsm_session_register_client (GsmSession *session,
+ GsmClient *client,
+ const char *id)
+{
+ GSList *a;
+ char *client_id = NULL;
+
+ /* If we're shutting down, we don't accept any new session
+ clients. */
+ if (session->phase == GSM_SESSION_PHASE_SHUTDOWN)
+ return FALSE;
+
+ if (id == NULL)
+ client_id = gsm_xsmp_generate_client_id ();
+ else
+ {
+ for (a = session->clients; a; a = a->next)
+ {
+ GsmClient *client = GSM_CLIENT (a->data);
+
+ /* We can't have two clients with the same id. */
+ if (!strcmp (id, gsm_client_get_client_id (client)))
+ {
+ return NULL;
+ }
+ }
+
+ client_id = g_strdup (id);
+ }
+
+ g_debug ("Adding new client %s to session", id);
+
+ g_signal_connect (client, "saved_state",
+ G_CALLBACK (client_saved_state), session);
+ g_signal_connect (client, "request_phase2",
+ G_CALLBACK (client_request_phase2), session);
+ g_signal_connect (client, "request_interaction",
+ G_CALLBACK (client_request_interaction), session);
+ g_signal_connect (client, "interaction_done",
+ G_CALLBACK (client_interaction_done), session);
+ g_signal_connect (client, "save_yourself_done",
+ G_CALLBACK (client_save_yourself_done), session);
+ g_signal_connect (client, "disconnected",
+ G_CALLBACK (client_disconnected), session);
+
+ session->clients = g_slist_prepend (session->clients, client);
+
+ /* If it's a brand new client id, we just accept the client*/
+ if (id == NULL)
+ return client_id;
+
+ /* If we're starting up the session, try to match the new client
+ * with one pending apps for the current phase. If not, try to match
+ * with any of the autostarted apps. */
+ if (session->phase < GSM_SESSION_PHASE_APPLICATION)
+ a = session->pending_apps;
+
+ for (; a; a = a->next)
+ {
+ GsmApp *app = GSM_APP (a->data);
+
+ if (!strcmp (client_id, app->client_id))
+ {
+ gsm_app_registered (app);
+ return client_id;
+ }
+ }
+
+ g_free (client_id);
+
+ return NULL;
+}
+
+static void
+client_saved_state (GsmClient *client, gpointer data)
+{
+ /* FIXME */
+}
+
+void
+gsm_session_initiate_shutdown (GsmSession *session)
+{
+ if (session->phase == GSM_SESSION_PHASE_SHUTDOWN)
+ {
+ /* Already shutting down, nothing more to do */
+ return;
+ }
+
+ initiate_shutdown (session);
+}
+
+static void
+session_shutdown_phase2 (GsmSession *session)
+{
+ GSList *cl;
+
+ for (cl = session->phase2_clients; cl; cl = cl->next)
+ gsm_client_save_yourself_phase2 (cl->data);
+}
+
+static void
+session_cancel_shutdown (GsmSession *session)
+{
+ GSList *cl;
+
+ session->phase = GSM_SESSION_PHASE_RUNNING;
+
+ g_slist_free (session->shutdown_clients);
+ session->shutdown_clients = NULL;
+ g_slist_free (session->interact_clients);
+ session->interact_clients = NULL;
+ g_slist_free (session->phase2_clients);
+ session->phase2_clients = NULL;
+
+ for (cl = session->clients; cl; cl = cl->next)
+ gsm_client_shutdown_cancelled (cl->data);
+}
+
+void
+gsm_session_cancel_shutdown (GsmSession *session)
+{
+ if (session == NULL || session->phase != GSM_SESSION_PHASE_SHUTDOWN)
+ {
+ g_warning ("Session is not in shutdown mode");
+ return;
+ }
+
+ session_cancel_shutdown (session);
+}
+
+static void
+initiate_shutdown (GsmSession *session)
+{
+ GSList *cl;
+
+ session->phase = GSM_SESSION_PHASE_SHUTDOWN;
+
+ if (session->clients == NULL)
+ session_shutdown (session);
+
+ for (cl = session->clients; cl; cl = cl->next)
+ {
+ GsmClient *client = GSM_CLIENT (cl->data);
+
+ session->shutdown_clients =
+ g_slist_prepend (session->shutdown_clients, client);
+
+ gsm_client_save_yourself (client, FALSE);
+ }
+}
+
+static void
+session_shutdown (GsmSession *session)
+{
+ GSList *cl;
+
+ /* FIXME: do this in reverse phase order */
+ for (cl = session->clients; cl; cl = cl->next)
+ gsm_client_die (cl->data);
+
+ g_signal_emit (session, signals[SHUTDOWN_COMPLETED], 0);
+}
+
+static void
+client_request_phase2 (GsmClient *client, gpointer data)
+{
+ GsmSession *session = data;
+
+ /* Move the client from shutdown_clients to phase2_clients */
+
+ session->shutdown_clients =
+ g_slist_remove (session->shutdown_clients, client);
+ session->phase2_clients =
+ g_slist_prepend (session->phase2_clients, client);
+}
+
+static void
+client_request_interaction (GsmClient *client, gpointer data)
+{
+ GsmSession *session = data;
+
+ session->interact_clients =
+ g_slist_append (session->interact_clients, client);
+
+ if (!session->interact_clients->next)
+ gsm_client_interact (client);
+}
+
+static void
+client_interaction_done (GsmClient *client, gboolean cancel_shutdown,
+ gpointer data)
+{
+ GsmSession *session = data;
+
+ g_return_if_fail (session->interact_clients &&
+ (GsmClient *)session->interact_clients->data == client);
+
+ if (cancel_shutdown)
+ {
+ session_cancel_shutdown (session);
+ return;
+ }
+
+ /* Remove this client from interact_clients, and if there's another
+ * client waiting to interact, let it interact now.
+ */
+ session->interact_clients =
+ g_slist_remove (session->interact_clients, client);
+ if (session->interact_clients)
+ gsm_client_interact (session->interact_clients->data);
+}
+
+static void
+client_save_yourself_done (GsmClient *client, gpointer data)
+{
+ GsmSession *session = data;
+
+ session->shutdown_clients =
+ g_slist_remove (session->shutdown_clients, client);
+ session->interact_clients =
+ g_slist_remove (session->interact_clients, client);
+ session->phase2_clients =
+ g_slist_remove (session->phase2_clients, client);
+
+ if (session->phase == GSM_SESSION_PHASE_SHUTDOWN &&
+ !session->shutdown_clients)
+ {
+ if (session->phase2_clients)
+ session_shutdown_phase2 (session);
+ else
+ session_shutdown (session);
+ }
+}
+
+static void
+client_disconnected (GsmClient *client, gpointer data)
+{
+ GsmSession *session = data;
+ gboolean is_condition_client = FALSE;
+
+ session->clients =
+ g_slist_remove (session->clients, client);
+ session->shutdown_clients =
+ g_slist_remove (session->shutdown_clients, client);
+ session->interact_clients =
+ g_slist_remove (session->interact_clients, client);
+ session->phase2_clients =
+ g_slist_remove (session->phase2_clients, client);
+
+ if (g_slist_find (session->condition_clients, client))
+ {
+ session->condition_clients =
+ g_slist_remove (session->condition_clients, client);
+
+ is_condition_client = TRUE;
+ }
+
+ if (session->phase != GSM_SESSION_PHASE_SHUTDOWN &&
+ gsm_client_get_autorestart (client) &&
+ !is_condition_client)
+ {
+ GError *error = NULL;
+
+ gsm_client_restart (client, &error);
+
+ if (error)
+ {
+ g_warning ("Error on restarting session client: %s", error->message);
+ g_clear_error (&error);
+ }
+ }
+
+ g_object_unref (client);
+}
+
+GsmSession *
+gsm_session_create_global (void)
+{
+ global_session = GSM_SESSION(g_object_new (GSM_TYPE_SESSION, NULL));
+ return global_session;
+}
diff --git a/toolkit/src/sugar/gsm-session.h b/toolkit/src/sugar/gsm-session.h
new file mode 100644
index 0000000..31d2762
--- /dev/null
+++ b/toolkit/src/sugar/gsm-session.h
@@ -0,0 +1,97 @@
+/* session.h
+ * Copyright (C) 2007 Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser 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., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef __GSM_SESSION_H__
+#define __GSM_SESSION_H__
+
+#include <glib.h>
+#include "gsm-client.h"
+
+G_BEGIN_DECLS
+
+#define GSM_TYPE_SESSION (gsm_session_get_type ())
+#define GSM_SESSION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSM_TYPE_SESSION, GsmSession))
+#define GSM_SESSION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSM_TYPE_SESSION, GsmSessionClass))
+#define GSM_IS_SESSION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSM_TYPE_SESSION))
+#define GSM_IS_SESSION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSM_TYPE_SESSION))
+#define GSM_SESSION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSM_TYPE_SESSION, GsmSessionClass))
+
+typedef struct _GsmSession GsmSession;
+typedef struct _GsmSessionClass GsmSessionClass;
+extern GsmSession *global_session;
+
+typedef enum {
+ /* gsm's own startup/initialization phase */
+ GSM_SESSION_PHASE_STARTUP,
+
+ /* xrandr setup, gnome-settings-daemon, etc */
+ GSM_SESSION_PHASE_INITIALIZATION,
+
+ /* window/compositing managers */
+ GSM_SESSION_PHASE_WINDOW_MANAGER,
+
+ /* apps that will create _NET_WM_WINDOW_TYPE_PANEL windows */
+ GSM_SESSION_PHASE_PANEL,
+
+ /* apps that will create _NET_WM_WINDOW_TYPE_DESKTOP windows */
+ GSM_SESSION_PHASE_DESKTOP,
+
+ /* everything else */
+ GSM_SESSION_PHASE_APPLICATION,
+
+ /* done launching */
+ GSM_SESSION_PHASE_RUNNING,
+
+ /* shutting down */
+ GSM_SESSION_PHASE_SHUTDOWN
+} GsmSessionPhase;
+
+typedef enum {
+ GSM_SESSION_LOGOUT_TYPE_LOGOUT,
+ GSM_SESSION_LOGOUT_TYPE_SHUTDOWN
+} GsmSessionLogoutType;
+
+typedef enum {
+ GSM_SESSION_LOGOUT_MODE_NORMAL,
+ GSM_SESSION_LOGOUT_MODE_NO_CONFIRMATION,
+ GSM_SESSION_LOGOUT_MODE_FORCE
+} GsmSessionLogoutMode;
+
+GType gsm_session_get_type (void) G_GNUC_CONST;
+
+void gsm_session_set_name (GsmSession *session,
+ const char *name);
+
+void gsm_session_start (GsmSession *session);
+
+GsmSessionPhase gsm_session_get_phase (GsmSession *session);
+
+void gsm_session_initiate_shutdown (GsmSession *session);
+
+void gsm_session_cancel_shutdown (GsmSession *session);
+
+char *gsm_session_register_client (GsmSession *session,
+ GsmClient *client,
+ const char *previous_id);
+
+GsmSession *gsm_session_create_global (void);
+
+G_END_DECLS
+
+#endif /* __GSM_SESSION_H__ */
diff --git a/toolkit/src/sugar/gsm-xsmp.c b/toolkit/src/sugar/gsm-xsmp.c
new file mode 100644
index 0000000..aa9dff1
--- /dev/null
+++ b/toolkit/src/sugar/gsm-xsmp.c
@@ -0,0 +1,535 @@
+/* xsmp.c
+ * Copyright (C) 2007 Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser 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., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include "gsm-client-xsmp.h"
+#include "gsm-xsmp.h"
+
+#include <X11/ICE/ICElib.h>
+#include <X11/ICE/ICEutil.h>
+#include <X11/ICE/ICEconn.h>
+#include <X11/SM/SMlib.h>
+
+#ifdef HAVE_X11_XTRANS_XTRANS_H
+/* Get the proto for _IceTransNoListen */
+#define ICE_t
+#define TRANS_SERVER
+#include <X11/Xtrans/Xtrans.h>
+#undef ICE_t
+#undef TRANS_SERVER
+#endif /* HAVE_X11_XTRANS_XTRANS_H */
+
+static IceListenObj *xsmp_sockets;
+static int num_xsmp_sockets, num_local_xsmp_sockets;
+
+static gboolean update_iceauthority (gboolean adding);
+
+static gboolean accept_ice_connection (GIOChannel *source,
+ GIOCondition condition,
+ gpointer data);
+static Status accept_xsmp_connection (SmsConn conn,
+ SmPointer manager_data,
+ unsigned long *mask_ret,
+ SmsCallbacks *callbacks_ret,
+ char **failure_reason_ret);
+
+static void ice_error_handler (IceConn conn,
+ Bool swap,
+ int offending_minor_opcode,
+ unsigned long offending_sequence_num,
+ int error_class,
+ int severity,
+ IcePointer values);
+static void ice_io_error_handler (IceConn conn);
+static void sms_error_handler (SmsConn sms_conn,
+ Bool swap,
+ int offending_minor_opcode,
+ unsigned long offending_sequence_num,
+ int error_class,
+ int severity,
+ IcePointer values);
+/**
+ * gsm_xsmp_init:
+ *
+ * Initializes XSMP. Notably, it creates the XSMP listening socket and
+ * sets the SESSION_MANAGER environment variable to point to it.
+ **/
+char *
+gsm_xsmp_init (void)
+{
+ char error[256];
+ mode_t saved_umask;
+ char *network_id_list;
+ int i;
+
+ /* Set up sane error handlers */
+ IceSetErrorHandler (ice_error_handler);
+ IceSetIOErrorHandler (ice_io_error_handler);
+ SmsSetErrorHandler (sms_error_handler);
+
+ /* Initialize libSM; we pass NULL for hostBasedAuthProc to disable
+ * host-based authentication.
+ */
+ if (!SmsInitialize (PACKAGE, VERSION, accept_xsmp_connection,
+ NULL, NULL, sizeof (error), error))
+ g_error("Could not initialize libSM: %s", error);
+
+#ifdef HAVE_X11_XTRANS_XTRANS_H
+ /* By default, IceListenForConnections will open one socket for each
+ * transport type known to X. We don't want connections from remote
+ * hosts, so for security reasons it would be best if ICE didn't
+ * even open any non-local sockets. So we use an internal ICElib
+ * method to disable them here. Unfortunately, there is no way to
+ * ask X what transport types it knows about, so we're forced to
+ * guess.
+ */
+ _IceTransNoListen ("tcp");
+#endif
+
+ /* Create the XSMP socket. Older versions of IceListenForConnections
+ * have a bug which causes the umask to be set to 0 on certain types
+ * of failures. Probably not an issue on any modern systems, but
+ * we'll play it safe.
+ */
+ saved_umask = umask (0);
+ umask (saved_umask);
+ if (!IceListenForConnections (&num_xsmp_sockets, &xsmp_sockets,
+ sizeof (error), error))
+ g_error ("Could not create ICE listening socket: %s", error);
+ umask (saved_umask);
+
+ /* Find the local sockets in the returned socket list and move them
+ * to the start of the list.
+ */
+ for (i = num_local_xsmp_sockets = 0; i < num_xsmp_sockets; i++)
+ {
+ char *id = IceGetListenConnectionString (xsmp_sockets[i]);
+
+ if (!strncmp (id, "local/", sizeof ("local/") - 1) ||
+ !strncmp (id, "unix/", sizeof ("unix/") - 1))
+ {
+ if (i > num_local_xsmp_sockets)
+ {
+ IceListenObj tmp = xsmp_sockets[i];
+ xsmp_sockets[i] = xsmp_sockets[num_local_xsmp_sockets];
+ xsmp_sockets[num_local_xsmp_sockets] = tmp;
+ }
+ num_local_xsmp_sockets++;
+ }
+ free (id);
+ }
+
+ if (num_local_xsmp_sockets == 0)
+ g_error ("IceListenForConnections did not return a local listener!");
+
+#ifdef HAVE_X11_XTRANS_XTRANS_H
+ if (num_local_xsmp_sockets != num_xsmp_sockets)
+ {
+ /* Xtrans was apparently compiled with support for some
+ * non-local transport besides TCP (which we disabled above); we
+ * won't create IO watches on those extra sockets, so
+ * connections to them will never be noticed, but they're still
+ * there, which is inelegant.
+ *
+ * If the g_warning below is triggering for you and you want to
+ * stop it, the fix is to add additional _IceTransNoListen()
+ * calls above.
+ */
+ network_id_list =
+ IceComposeNetworkIdList (num_xsmp_sockets - num_local_xsmp_sockets,
+ xsmp_sockets + num_local_xsmp_sockets);
+ g_warning ("IceListenForConnections returned %d non-local listeners: %s",
+ num_xsmp_sockets - num_local_xsmp_sockets, network_id_list);
+ free (network_id_list);
+ }
+#endif
+
+ /* Update .ICEauthority with new auth entries for our socket */
+ if (!update_iceauthority (TRUE))
+ {
+ /* FIXME: is this really fatal? Hm... */
+ g_error ("Could not update ICEauthority file %s",
+ IceAuthFileName ());
+ }
+
+ network_id_list = IceComposeNetworkIdList (num_local_xsmp_sockets,
+ xsmp_sockets);
+
+ return network_id_list;
+}
+
+/**
+ * gsm_xsmp_run:
+ *
+ * Sets the XSMP server to start accepting connections.
+ **/
+void
+gsm_xsmp_run (void)
+{
+ GIOChannel *channel;
+ int i;
+
+ for (i = 0; i < num_local_xsmp_sockets; i++)
+ {
+ channel = g_io_channel_unix_new (IceGetListenConnectionNumber (xsmp_sockets[i]));
+ g_io_add_watch (channel, G_IO_IN | G_IO_HUP | G_IO_ERR,
+ accept_ice_connection, xsmp_sockets[i]);
+ g_io_channel_unref (channel);
+ }
+}
+
+/**
+ * gsm_xsmp_shutdown:
+ *
+ * Shuts down the XSMP server and closes the ICE listening socket
+ **/
+void
+gsm_xsmp_shutdown (void)
+{
+ update_iceauthority (FALSE);
+
+ IceFreeListenObjs (num_xsmp_sockets, xsmp_sockets);
+ xsmp_sockets = NULL;
+}
+
+/**
+ * gsm_xsmp_generate_client_id:
+ *
+ * Generates a new XSMP client ID.
+ *
+ * Return value: an XSMP client ID.
+ **/
+char *
+gsm_xsmp_generate_client_id (void)
+{
+ static int sequence = -1;
+ static guint rand1 = 0, rand2 = 0;
+ static pid_t pid = 0;
+ struct timeval tv;
+
+ /* The XSMP spec defines the ID as:
+ *
+ * Version: "1"
+ * Address type and address:
+ * "1" + an IPv4 address as 8 hex digits
+ * "2" + a DECNET address as 12 hex digits
+ * "6" + an IPv6 address as 32 hex digits
+ * Time stamp: milliseconds since UNIX epoch as 13 decimal digits
+ * Process-ID type and process-ID:
+ * "1" + POSIX PID as 10 decimal digits
+ * Sequence number as 4 decimal digits
+ *
+ * XSMP client IDs are supposed to be globally unique: if
+ * SmsGenerateClientID() is unable to determine a network
+ * address for the machine, it gives up and returns %NULL.
+ * GNOME and KDE have traditionally used a fourth address
+ * format in this case:
+ * "0" + 16 random hex digits
+ *
+ * We don't even bother trying SmsGenerateClientID(), since the
+ * user's IP address is probably "192.168.1.*" anyway, so a random
+ * number is actually more likely to be globally unique.
+ */
+
+ if (!rand1)
+ {
+ rand1 = g_random_int ();
+ rand2 = g_random_int ();
+ pid = getpid ();
+ }
+
+ sequence = (sequence + 1) % 10000;
+ gettimeofday (&tv, NULL);
+ return g_strdup_printf ("10%.04x%.04x%.10lu%.3u%.10lu%.4d",
+ rand1, rand2,
+ (unsigned long) tv.tv_sec,
+ (unsigned) tv.tv_usec,
+ (unsigned long) pid,
+ sequence);
+}
+
+/* This is called (by glib via xsmp->ice_connection_watch) when a
+ * connection is first received on the ICE listening socket. (We
+ * expect that the client will then initiate XSMP on the connection;
+ * if it does not, GsmClientXSMP will eventually time out and close
+ * the connection.)
+ *
+ * FIXME: it would probably make more sense to not create a
+ * GsmClientXSMP object until accept_xsmp_connection, below (and to do
+ * the timing-out here in xsmp.c).
+ */
+static gboolean
+accept_ice_connection (GIOChannel *source,
+ GIOCondition condition,
+ gpointer data)
+{
+ IceListenObj listener = data;
+ IceConn ice_conn;
+ IceAcceptStatus status;
+ GsmClientXSMP *client;
+
+ g_debug ("accept_ice_connection()");
+
+ ice_conn = IceAcceptConnection (listener, &status);
+ if (status != IceAcceptSuccess)
+ {
+ g_debug ("IceAcceptConnection returned %d", status);
+ return TRUE;
+ }
+
+ client = gsm_client_xsmp_new (ice_conn);
+ ice_conn->context = client;
+ return TRUE;
+}
+
+/* This is called (by libSM) when XSMP is initiated on an ICE
+ * connection that was already accepted by accept_ice_connection.
+ */
+static Status
+accept_xsmp_connection (SmsConn sms_conn, SmPointer manager_data,
+ unsigned long *mask_ret, SmsCallbacks *callbacks_ret,
+ char **failure_reason_ret)
+{
+ IceConn ice_conn;
+ GsmClientXSMP *client;
+
+ /* FIXME: what about during shutdown but before gsm_xsmp_shutdown? */
+ if (!xsmp_sockets)
+ {
+ g_debug ("In shutdown, rejecting new client");
+
+ *failure_reason_ret =
+ strdup (_("Refusing new client connection because the session is currently being shut down\n"));
+ return FALSE;
+ }
+
+ ice_conn = SmsGetIceConnection (sms_conn);
+ client = ice_conn->context;
+
+ g_return_val_if_fail (client != NULL, TRUE);
+
+ gsm_client_xsmp_connect (client, sms_conn, mask_ret, callbacks_ret);
+ return TRUE;
+}
+
+/* ICEauthority stuff */
+
+/* Various magic numbers stolen from iceauth.c */
+#define GSM_ICE_AUTH_RETRIES 10
+#define GSM_ICE_AUTH_INTERVAL 2 /* 2 seconds */
+#define GSM_ICE_AUTH_LOCK_TIMEOUT 600 /* 10 minutes */
+
+#define GSM_ICE_MAGIC_COOKIE_AUTH_NAME "MIT-MAGIC-COOKIE-1"
+#define GSM_ICE_MAGIC_COOKIE_LEN 16
+
+static IceAuthFileEntry *
+auth_entry_new (const char *protocol, const char *network_id)
+{
+ IceAuthFileEntry *file_entry;
+ IceAuthDataEntry data_entry;
+
+ file_entry = malloc (sizeof (IceAuthFileEntry));
+
+ file_entry->protocol_name = strdup (protocol);
+ file_entry->protocol_data = NULL;
+ file_entry->protocol_data_length = 0;
+ file_entry->network_id = strdup (network_id);
+ file_entry->auth_name = strdup (GSM_ICE_MAGIC_COOKIE_AUTH_NAME);
+ file_entry->auth_data = IceGenerateMagicCookie (GSM_ICE_MAGIC_COOKIE_LEN);
+ file_entry->auth_data_length = GSM_ICE_MAGIC_COOKIE_LEN;
+
+ /* Also create an in-memory copy, which is what the server will
+ * actually use for checking client auth.
+ */
+ data_entry.protocol_name = file_entry->protocol_name;
+ data_entry.network_id = file_entry->network_id;
+ data_entry.auth_name = file_entry->auth_name;
+ data_entry.auth_data = file_entry->auth_data;
+ data_entry.auth_data_length = file_entry->auth_data_length;
+ IceSetPaAuthData (1, &data_entry);
+
+ return file_entry;
+}
+
+static gboolean
+update_iceauthority (gboolean adding)
+{
+ char *filename = IceAuthFileName ();
+ char **our_network_ids;
+ FILE *fp;
+ IceAuthFileEntry *auth_entry;
+ GSList *entries, *e;
+ int i;
+ gboolean ok = FALSE;
+
+ if (IceLockAuthFile (filename, GSM_ICE_AUTH_RETRIES, GSM_ICE_AUTH_INTERVAL,
+ GSM_ICE_AUTH_LOCK_TIMEOUT) != IceAuthLockSuccess)
+ return FALSE;
+
+ our_network_ids = g_malloc (num_local_xsmp_sockets * sizeof (char *));
+ for (i = 0; i < num_local_xsmp_sockets; i++)
+ our_network_ids[i] = IceGetListenConnectionString (xsmp_sockets[i]);
+
+ entries = NULL;
+
+ fp = fopen (filename, "r+");
+ if (fp)
+ {
+ while ((auth_entry = IceReadAuthFileEntry (fp)) != NULL)
+ {
+ /* Skip/delete entries with no network ID (invalid), or with
+ * our network ID; if we're starting up, an entry with our
+ * ID must be a stale entry left behind by an old process,
+ * and if we're shutting down, it won't be valid in the
+ * future, so either way we want to remove it from the list.
+ */
+ if (!auth_entry->network_id)
+ {
+ IceFreeAuthFileEntry (auth_entry);
+ continue;
+ }
+
+ for (i = 0; i < num_local_xsmp_sockets; i++)
+ {
+ if (!strcmp (auth_entry->network_id, our_network_ids[i]))
+ {
+ IceFreeAuthFileEntry (auth_entry);
+ break;
+ }
+ }
+ if (i != num_local_xsmp_sockets)
+ continue;
+
+ entries = g_slist_prepend (entries, auth_entry);
+ }
+
+ rewind (fp);
+ }
+ else
+ {
+ int fd;
+
+ if (g_file_test (filename, G_FILE_TEST_EXISTS))
+ {
+ g_warning ("Unable to read ICE authority file: %s", filename);
+ goto cleanup;
+ }
+
+ fd = open (filename, O_CREAT | O_WRONLY, 0600);
+ fp = fdopen (fd, "w");
+ if (!fp)
+ {
+ g_warning ("Unable to write to ICE authority file: %s", filename);
+ if (fd != -1)
+ close (fd);
+ goto cleanup;
+ }
+ }
+
+ if (adding)
+ {
+ for (i = 0; i < num_local_xsmp_sockets; i++)
+ {
+ entries = g_slist_append (entries,
+ auth_entry_new ("ICE", our_network_ids[i]));
+ entries = g_slist_prepend (entries,
+ auth_entry_new ("XSMP", our_network_ids[i]));
+ }
+ }
+
+ for (e = entries; e; e = e->next)
+ {
+ IceAuthFileEntry *auth_entry = e->data;
+ IceWriteAuthFileEntry (fp, auth_entry);
+ IceFreeAuthFileEntry (auth_entry);
+ }
+ g_slist_free (entries);
+
+ fclose (fp);
+ ok = TRUE;
+
+ cleanup:
+ IceUnlockAuthFile (filename);
+ for (i = 0; i < num_local_xsmp_sockets; i++)
+ free (our_network_ids[i]);
+ g_free (our_network_ids);
+
+ return ok;
+}
+
+/* Error handlers */
+
+static void
+ice_error_handler (IceConn conn, Bool swap, int offending_minor_opcode,
+ unsigned long offending_sequence, int error_class,
+ int severity, IcePointer values)
+{
+ g_debug ("ice_error_handler (%p, %s, %d, %lx, %d, %d)",
+ conn, swap ? "TRUE" : "FALSE", offending_minor_opcode,
+ offending_sequence, error_class, severity);
+
+ if (severity == IceCanContinue)
+ return;
+
+ /* FIXME: the ICElib docs are completely vague about what we're
+ * supposed to do in this case. Need to verify that calling
+ * IceCloseConnection() here is guaranteed to cause neither
+ * free-memory-reads nor leaks.
+ */
+ IceCloseConnection (conn);
+}
+
+static void
+ice_io_error_handler (IceConn conn)
+{
+ g_debug ("ice_io_error_handler (%p)", conn);
+
+ /* We don't need to do anything here; the next call to
+ * IceProcessMessages() for this connection will receive
+ * IceProcessMessagesIOError and we can handle the error there.
+ */
+}
+
+static void
+sms_error_handler (SmsConn conn, Bool swap, int offending_minor_opcode,
+ unsigned long offending_sequence_num, int error_class,
+ int severity, IcePointer values)
+{
+ g_debug ("sms_error_handler (%p, %s, %d, %lx, %d, %d)",
+ conn, swap ? "TRUE" : "FALSE", offending_minor_opcode,
+ offending_sequence_num, error_class, severity);
+
+ /* We don't need to do anything here; if the connection needs to be
+ * closed, libSM will do that itself.
+ */
+}
diff --git a/toolkit/src/sugar/gsm-xsmp.h b/toolkit/src/sugar/gsm-xsmp.h
new file mode 100644
index 0000000..b4b535f
--- /dev/null
+++ b/toolkit/src/sugar/gsm-xsmp.h
@@ -0,0 +1,29 @@
+/* xsmp.h
+ * Copyright (C) 2007 Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser 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., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef __GSM_XSMP_H__
+#define __GSM_XSMP_H__
+
+char *gsm_xsmp_init (void);
+void gsm_xsmp_run (void);
+void gsm_xsmp_shutdown (void);
+
+char *gsm_xsmp_generate_client_id (void);
+
+#endif /* __GSM_XSMP_H__ */
diff --git a/toolkit/src/sugar/network.py b/toolkit/src/sugar/network.py
new file mode 100644
index 0000000..bde8c9f
--- /dev/null
+++ b/toolkit/src/sugar/network.py
@@ -0,0 +1,302 @@
+# Copyright (C) 2006-2007 Red Hat, Inc.
+#
+# 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.
+
+"""
+STABLE.
+"""
+
+import os
+import threading
+import urllib
+import fcntl
+import tempfile
+
+import gobject
+import SimpleHTTPServer
+import SocketServer
+
+
+__authinfos = {}
+
+
+def _add_authinfo(authinfo):
+ __authinfos[threading.currentThread()] = authinfo
+
+
+def get_authinfo():
+ return __authinfos.get(threading.currentThread())
+
+
+def _del_authinfo():
+ del __authinfos[threading.currentThread()]
+
+
+class GlibTCPServer(SocketServer.TCPServer):
+ """GlibTCPServer
+
+ Integrate socket accept into glib mainloop.
+ """
+
+ allow_reuse_address = True
+ request_queue_size = 20
+
+ def __init__(self, server_address, RequestHandlerClass):
+ SocketServer.TCPServer.__init__(self, server_address,
+ RequestHandlerClass)
+ self.socket.setblocking(0) # Set nonblocking
+
+ # Watch the listener socket for data
+ gobject.io_add_watch(self.socket, gobject.IO_IN, self._handle_accept)
+
+ def _handle_accept(self, source, condition):
+ """Process incoming data on the server's socket by doing an accept()
+ via handle_request()."""
+ if not (condition & gobject.IO_IN):
+ return True
+ self.handle_request()
+ return True
+
+ def close_request(self, request):
+ """Called to clean up an individual request."""
+ # let the request be closed by the request handler when its done
+ pass
+
+
+class ChunkedGlibHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
+ """RequestHandler class that integrates with Glib mainloop. It writes
+ the specified file to the client in chunks, returning control to the
+ mainloop between chunks.
+ """
+
+ CHUNK_SIZE = 4096
+
+ def __init__(self, request, client_address, server):
+ self._file = None
+ self._srcid = 0
+ SimpleHTTPServer.SimpleHTTPRequestHandler.__init__(
+ self, request, client_address, server)
+
+ def log_request(self, code='-', size='-'):
+ pass
+
+ def do_GET(self):
+ """Serve a GET request."""
+ self._file = self.send_head()
+ if self._file:
+ self._srcid = gobject.io_add_watch(self.wfile, gobject.IO_OUT |
+ gobject.IO_ERR,
+ self._send_next_chunk)
+ else:
+ self._cleanup()
+
+ def _send_next_chunk(self, source, condition):
+ if condition & gobject.IO_ERR:
+ self._cleanup()
+ return False
+ if not (condition & gobject.IO_OUT):
+ self._cleanup()
+ return False
+ data = self._file.read(self.CHUNK_SIZE)
+ count = os.write(self.wfile.fileno(), data)
+ if count != len(data) or len(data) != self.CHUNK_SIZE:
+ self._cleanup()
+ return False
+ return True
+
+ def _cleanup(self):
+ if self._file:
+ self._file.close()
+ self._file = None
+ if self._srcid > 0:
+ gobject.source_remove(self._srcid)
+ self._srcid = 0
+ if not self.wfile.closed:
+ self.wfile.flush()
+ self.wfile.close()
+ self.rfile.close()
+
+ def finish(self):
+ """Close the sockets when we're done, not before"""
+ pass
+
+ def send_head(self):
+ """Common code for GET and HEAD commands.
+
+ This sends the response code and MIME headers.
+
+ Return value is either a file object (which has to be copied
+ to the outputfile by the caller unless the command was HEAD,
+ and must be closed by the caller under all circumstances), or
+ None, in which case the caller has nothing further to do.
+
+ ** [dcbw] modified to send Content-disposition filename too
+ """
+ path = self.translate_path(self.path)
+ if not path or not os.path.exists(path):
+ self.send_error(404, "File not found")
+ return None
+
+ f = None
+ if os.path.isdir(path):
+ for index in "index.html", "index.htm":
+ index = os.path.join(path, index)
+ if os.path.exists(index):
+ path = index
+ break
+ else:
+ return self.list_directory(path)
+ ctype = self.guess_type(path)
+ try:
+ # Always read in binary mode. Opening files in text mode may cause
+ # newline translations, making the actual size of the content
+ # transmitted *less* than the content-length!
+ f = open(path, 'rb')
+ except IOError:
+ self.send_error(404, "File not found")
+ return None
+ self.send_response(200)
+ self.send_header("Content-type", ctype)
+ self.send_header("Content-Length", str(os.fstat(f.fileno())[6]))
+ self.send_header("Content-Disposition", 'attachment; filename="%s"' %
+ os.path.basename(path))
+ self.end_headers()
+ return f
+
+
+class GlibURLDownloader(gobject.GObject):
+ """Grabs a URL in chunks, returning to the mainloop after each chunk"""
+
+ __gsignals__ = {
+ 'finished': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])),
+ 'error': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ 'progress': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ }
+
+ CHUNK_SIZE = 4096
+
+ def __init__(self, url, destdir=None):
+ self._url = url
+ if not destdir:
+ destdir = tempfile.gettempdir()
+ self._destdir = destdir
+ self._srcid = 0
+ self._fname = None
+ self._outf = None
+ self._suggested_fname = None
+ self._info = None
+ self._written = 0
+ gobject.GObject.__init__(self)
+
+ def start(self, destfile=None, destfd=None):
+ self._info = urllib.urlopen(self._url)
+ self._outf = None
+ self._fname = None
+ if destfd and not destfile:
+ raise ValueError("Must provide destination file too when" \
+ "specifying file descriptor")
+ if destfile:
+ self._suggested_fname = os.path.basename(destfile)
+ self._fname = os.path.abspath(os.path.expanduser(destfile))
+ if destfd:
+ # Use the user-supplied destination file descriptor
+ self._outf = destfd
+ else:
+ self._outf = os.open(self._fname, os.O_RDWR |
+ os.O_TRUNC | os.O_CREAT, 0644)
+ else:
+ fname = self._get_filename_from_headers(self._info.headers)
+ self._suggested_fname = fname
+ garbage_, path = urllib.splittype(self._url)
+ garbage_, path = urllib.splithost(path or "")
+ path, garbage_ = urllib.splitquery(path or "")
+ path, garbage_ = urllib.splitattr(path or "")
+ suffix = os.path.splitext(path)[1]
+ (self._outf, self._fname) = tempfile.mkstemp(suffix=suffix,
+ dir=self._destdir)
+
+ fcntl.fcntl(self._info.fp.fileno(), fcntl.F_SETFD, os.O_NDELAY)
+ self._srcid = gobject.io_add_watch(self._info.fp.fileno(),
+ gobject.IO_IN | gobject.IO_ERR,
+ self._read_next_chunk)
+
+ def cancel(self):
+ if self._srcid == 0:
+ raise RuntimeError("Download already canceled or stopped")
+ self.cleanup(remove=True)
+
+ def _get_filename_from_headers(self, headers):
+ if not headers.has_key("Content-Disposition"):
+ return None
+
+ ftag = "filename="
+ data = headers["Content-Disposition"]
+ fidx = data.find(ftag)
+ if fidx < 0:
+ return None
+ fname = data[fidx+len(ftag):]
+ if fname[0] == '"' or fname[0] == "'":
+ fname = fname[1:]
+ if fname[len(fname)-1] == '"' or fname[len(fname)-1] == "'":
+ fname = fname[:len(fname)-1]
+ return fname
+
+ def _read_next_chunk(self, source, condition):
+ if condition & gobject.IO_ERR:
+ self.cleanup(remove=True)
+ self.emit("error", "Error downloading file.")
+ return False
+ elif not (condition & gobject.IO_IN):
+ # shouldn't get here, but...
+ return True
+
+ try:
+ data = self._info.fp.read(self.CHUNK_SIZE)
+ count = os.write(self._outf, data)
+ self._written += len(data)
+
+ # error writing data to file?
+ if count < len(data):
+ self.cleanup(remove=True)
+ self.emit("error", "Error writing to download file.")
+ return False
+
+ self.emit("progress", self._written)
+
+ # done?
+ if len(data) < self.CHUNK_SIZE:
+ self.cleanup()
+ self.emit("finished", self._fname, self._suggested_fname)
+ return False
+ except Exception, err:
+ self.cleanup(remove=True)
+ self.emit("error", "Error downloading file: %s" % err)
+ return False
+ return True
+
+ def cleanup(self, remove=False):
+ if self._srcid > 0:
+ gobject.source_remove(self._srcid)
+ self._srcid = 0
+ del self._info
+ self._info = None
+ os.close(self._outf)
+ if remove:
+ os.remove(self._fname)
+ self._outf = None
diff --git a/toolkit/src/sugar/presence/Makefile.am b/toolkit/src/sugar/presence/Makefile.am
new file mode 100644
index 0000000..6d87fe7
--- /dev/null
+++ b/toolkit/src/sugar/presence/Makefile.am
@@ -0,0 +1,10 @@
+sugardir = $(pythondir)/sugar/presence
+sugar_PYTHON = \
+ __init__.py \
+ activity.py \
+ buddy.py \
+ connectionmanager.py \
+ sugartubeconn.py \
+ tubeconn.py \
+ presenceservice.py
+
diff --git a/toolkit/src/sugar/presence/__init__.py b/toolkit/src/sugar/presence/__init__.py
new file mode 100644
index 0000000..9756612
--- /dev/null
+++ b/toolkit/src/sugar/presence/__init__.py
@@ -0,0 +1,24 @@
+# Copyright (C) 2006-2007, Red Hat, Inc.
+#
+# 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.
+
+"""Client-code's interface to the PresenceService
+
+Provides a simplified API for accessing the dbus service
+which coordinates native network presence and sharing
+information. This includes both "buddies" and "shared
+activities".
+"""
diff --git a/toolkit/src/sugar/presence/activity.py b/toolkit/src/sugar/presence/activity.py
new file mode 100644
index 0000000..0def0c8
--- /dev/null
+++ b/toolkit/src/sugar/presence/activity.py
@@ -0,0 +1,716 @@
+# Copyright (C) 2007, Red Hat, Inc.
+# Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# 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.
+
+"""UI interface to an activity in the presence service
+
+STABLE.
+"""
+
+import logging
+from functools import partial
+
+import dbus
+from dbus import PROPERTIES_IFACE
+import gobject
+from telepathy.client import Channel
+from telepathy.interfaces import CHANNEL, \
+ CHANNEL_INTERFACE_GROUP, \
+ CHANNEL_TYPE_TUBES, \
+ CHANNEL_TYPE_TEXT, \
+ CONNECTION, \
+ PROPERTIES_INTERFACE
+from telepathy.constants import CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES, \
+ HANDLE_TYPE_ROOM, \
+ HANDLE_TYPE_CONTACT, \
+ PROPERTY_FLAG_WRITE
+
+from sugar.presence.buddy import Buddy
+
+CONN_INTERFACE_ACTIVITY_PROPERTIES = 'org.laptop.Telepathy.ActivityProperties'
+CONN_INTERFACE_BUDDY_INFO = 'org.laptop.Telepathy.BuddyInfo'
+
+_logger = logging.getLogger('sugar.presence.activity')
+
+
+class Activity(gobject.GObject):
+ """UI interface for an Activity in the presence service
+
+ Activities in the presence service represent your and other user's
+ shared activities.
+
+ Properties:
+ id
+ color
+ name
+ type
+ joined
+ """
+ __gsignals__ = {
+ 'buddy-joined': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ 'buddy-left': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ 'new-channel': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ 'joined': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])),
+ }
+
+ __gproperties__ = {
+ 'id': (str, None, None, None, gobject.PARAM_READABLE),
+ 'name': (str, None, None, None, gobject.PARAM_READWRITE),
+ 'tags': (str, None, None, None, gobject.PARAM_READWRITE),
+ 'color': (str, None, None, None, gobject.PARAM_READWRITE),
+ 'type': (str, None, None, None, gobject.PARAM_READABLE),
+ 'private': (bool, None, None, True, gobject.PARAM_READWRITE),
+ 'joined': (bool, None, None, False, gobject.PARAM_READABLE),
+ }
+
+ def __init__(self, account_path, connection, room_handle=None,
+ properties=None):
+ if room_handle is None and properties is None:
+ raise ValueError('Need to pass one of room_handle or properties')
+
+ if properties is None:
+ properties = {}
+
+ gobject.GObject.__init__(self)
+
+ self._account_path = account_path
+ self.telepathy_conn = connection
+ self.telepathy_text_chan = None
+ self.telepathy_tubes_chan = None
+
+ self.room_handle = room_handle
+ self._join_command = None
+ self._share_command = None
+ self._id = properties.get('id', None)
+ self._color = properties.get('color', None)
+ self._name = properties.get('name', None)
+ self._type = properties.get('type', None)
+ self._tags = properties.get('tags', None)
+ self._private = properties.get('private', True)
+ self._joined = properties.get('joined', False)
+ self._channel_self_handle = None
+ self._text_channel_group_flags = 0
+ self._buddies = {}
+
+ self._get_properties_call = None
+ if not self.room_handle is None:
+ self._start_tracking_properties()
+
+ def _start_tracking_properties(self):
+ bus = dbus.SessionBus()
+ self._get_properties_call = bus.call_async(
+ self.telepathy_conn.requested_bus_name,
+ self.telepathy_conn.object_path,
+ CONN_INTERFACE_ACTIVITY_PROPERTIES,
+ 'GetProperties',
+ 'u',
+ (self.room_handle,),
+ reply_handler=self.__got_properties_cb,
+ error_handler=self.__error_handler_cb,
+ utf8_strings=True)
+
+ # As only one Activity instance is needed per activity process,
+ # we can afford listening to ActivityPropertiesChanged like this.
+ self.telepathy_conn.connect_to_signal(
+ 'ActivityPropertiesChanged',
+ self.__activity_properties_changed_cb,
+ dbus_interface=CONN_INTERFACE_ACTIVITY_PROPERTIES)
+
+ def __activity_properties_changed_cb(self, room_handle, properties):
+ _logger.debug('%r: Activity properties changed to %r', self, properties)
+ self._update_properties(properties)
+
+ def __got_properties_cb(self, properties):
+ _logger.debug('__got_properties_cb %r', properties)
+ self._get_properties_call = None
+ self._update_properties(properties)
+
+ def __error_handler_cb(self, error):
+ _logger.debug('__error_handler_cb %r', error)
+
+ def _update_properties(self, new_props):
+ val = new_props.get('name', self._name)
+ if isinstance(val, str) and val != self._name:
+ self._name = val
+ self.notify('name')
+ val = new_props.get('tags', self._tags)
+ if isinstance(val, str) and val != self._tags:
+ self._tags = val
+ self.notify('tags')
+ val = new_props.get('color', self._color)
+ if isinstance(val, str) and val != self._color:
+ self._color = val
+ self.notify('color')
+ val = bool(new_props.get('private', self._private))
+ if val != self._private:
+ self._private = val
+ self.notify('private')
+ val = new_props.get('id', self._id)
+ if isinstance(val, str) and self._id is None:
+ self._id = val
+ self.notify('id')
+ val = new_props.get('type', self._type)
+ if isinstance(val, str) and self._type is None:
+ self._type = val
+ self.notify('type')
+
+ def object_path(self):
+ """Get our dbus object path"""
+ return self._object_path
+
+ def do_get_property(self, pspec):
+ """Retrieve a particular property from our property dictionary"""
+
+ if pspec.name == "joined":
+ return self._joined
+
+ if self._get_properties_call is not None:
+ _logger.debug('%r: Blocking on GetProperties() because someone '
+ 'wants property %s', self, pspec.name)
+ self._get_properties_call.block()
+
+ if pspec.name == "id":
+ return self._id
+ elif pspec.name == "name":
+ return self._name
+ elif pspec.name == "color":
+ return self._color
+ elif pspec.name == "type":
+ return self._type
+ elif pspec.name == "tags":
+ return self._tags
+ elif pspec.name == "private":
+ return self._private
+
+ def do_set_property(self, pspec, val):
+ """Set a particular property in our property dictionary"""
+ # FIXME: need an asynchronous API to set these properties,
+ # particularly 'private'
+
+ if pspec.name == "name":
+ self._name = val
+ elif pspec.name == "color":
+ self._color = val
+ elif pspec.name == "tags":
+ self._tags = val
+ elif pspec.name == "private":
+ self._private = val
+ else:
+ raise ValueError('Unknown property "%s"', pspec.name)
+
+ self._publish_properties()
+
+ def set_private(self, val, reply_handler, error_handler):
+ _logger.debug('set_private %r', val)
+ self._activity.SetProperties({'private': bool(val)},
+ reply_handler=reply_handler,
+ error_handler=error_handler)
+
+ def get_joined_buddies(self):
+ """Retrieve the set of Buddy objects attached to this activity
+
+ returns list of presence Buddy objects that we can successfully
+ create from the buddy object paths that PS has for this activity.
+ """
+ return self._buddies.values()
+
+ def get_buddy_by_handle(self, handle):
+ """Retrieve the Buddy object given a telepathy handle.
+
+ buddy object paths are cached in self._handle_to_buddy_path,
+ so we can get the buddy without calling PS.
+ """
+ object_path = self._handle_to_buddy_path.get(handle, None)
+ if object_path:
+ buddy = self._ps_new_object(object_path)
+ return buddy
+ return None
+
+ def invite(self, buddy, message, response_cb):
+ """Invite the given buddy to join this activity.
+
+ The callback will be called with one parameter: None on success,
+ or an exception on failure.
+ """
+ if not self._joined:
+ raise RuntimeError('Cannot invite a buddy to an activity that is'
+ 'not shared.')
+ self.telepathy_text_chan.AddMembers([buddy.contact_handle], message,
+ dbus_interface=CHANNEL_INTERFACE_GROUP,
+ reply_handler=partial(self.__invite_cb, response_cb),
+ error_handler=partial(self.__invite_cb, response_cb))
+
+ def __invite_cb(self, response_cb, error=None):
+ response_cb(error)
+
+ def set_up_tubes(self, reply_handler, error_handler):
+ raise NotImplementedError()
+
+ def __joined_cb(self, join_command, error):
+ _logger.debug('%r: Join finished %r', self, error)
+ if error is not None:
+ self.emit('joined', error is None, str(error))
+ self.telepathy_text_chan = join_command.text_channel
+ self.telepathy_tubes_chan = join_command.tubes_channel
+ self._channel_self_handle = join_command.channel_self_handle
+ self._text_channel_group_flags = join_command.text_channel_group_flags
+ self._start_tracking_buddies()
+ self._start_tracking_channel()
+
+ def _start_tracking_buddies(self):
+ group = self.telepathy_text_chan[CHANNEL_INTERFACE_GROUP]
+
+ group.GetAllMembers(reply_handler=self.__get_all_members_cb,
+ error_handler=self.__error_handler_cb)
+
+ group.connect_to_signal('MembersChanged',
+ self.__text_channel_members_changed_cb)
+
+ def _start_tracking_channel(self):
+ channel = self.telepathy_text_chan[CHANNEL]
+ channel.connect_to_signal('Closed', self.__text_channel_closed_cb)
+
+ def __get_all_members_cb(self, members, local_pending, remote_pending):
+ _logger.debug('__get_all_members_cb %r %r', members,
+ self._text_channel_group_flags)
+ if self._channel_self_handle in members:
+ members.remove(self._channel_self_handle)
+
+ if not members:
+ return
+
+ self._resolve_handles(members, reply_cb=self._add_initial_buddies)
+
+ def _resolve_handles(self, input_handles, reply_cb):
+ def get_handle_owners_cb(handles):
+ self.telepathy_conn.InspectHandles(HANDLE_TYPE_CONTACT, handles,
+ reply_handler=reply_cb,
+ error_handler=self.__error_handler_cb,
+ dbus_interface=CONNECTION)
+
+ if self._text_channel_group_flags & \
+ CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES:
+
+ group = self.telepathy_text_chan[CHANNEL_INTERFACE_GROUP]
+ group.GetHandleOwners(input_handles,
+ reply_handler=get_handle_owners_cb,
+ error_handler=self.__error_handler_cb)
+ else:
+ get_handle_owners_cb(input_handles)
+
+ def _add_initial_buddies(self, contact_ids):
+ _logger.debug('__add_initial_buddies %r', contact_ids)
+ for contact_id in contact_ids:
+ self._buddies[contact_id] = self._get_buddy(contact_id)
+ # Once we have the initial members, we can finish the join process
+ self._joined = True
+ self.emit('joined', True, None)
+
+ def __text_channel_members_changed_cb(self, message, added, removed,
+ local_pending, remote_pending,
+ actor, reason):
+ _logger.debug('__text_channel_members_changed_cb %r',
+ [added, message, added, removed, local_pending,
+ remote_pending, actor, reason])
+ if self._channel_self_handle in added:
+ added.remove(self._channel_self_handle)
+ if added:
+ self._resolve_handles(added, reply_cb=self._add_buddies)
+
+ if self._channel_self_handle in removed:
+ removed.remove(self._channel_self_handle)
+ if removed:
+ self._resolve_handles(added, reply_cb=self._remove_buddies)
+
+ def _add_buddies(self, contact_ids):
+ for contact_id in contact_ids:
+ if contact_id not in self._buddies:
+ buddy = self._get_buddy(contact_id)
+ self.emit('buddy-joined', buddy)
+ self._buddies[contact_id] = buddy
+
+ def _remove_buddies(self, contact_ids):
+ for contact_id in contact_ids:
+ if contact_id in self._buddies:
+ buddy = self._get_buddy(contact_id)
+ self.emit('buddy-left', buddy)
+ del self._buddies[contact_id]
+
+ def _get_buddy(self, contact_id):
+ if contact_id in self._buddies:
+ return self._buddies[contact_id]
+ else:
+ return Buddy(self._account_path, contact_id)
+
+ def join(self):
+ """Join this activity.
+
+ Emits 'joined' and otherwise does nothing if we're already joined.
+ """
+ if self._join_command is not None:
+ return
+
+ if self._joined:
+ self.emit('joined', True, None)
+ return
+
+ _logger.debug('%r: joining', self)
+
+ self._join_command = _JoinCommand(self.telepathy_conn,
+ self.room_handle)
+ self._join_command.connect('finished', self.__joined_cb)
+ self._join_command.run()
+
+ def share(self, share_activity_cb, share_activity_error_cb):
+ if not self.room_handle is None:
+ raise ValueError('Already have a room handle')
+
+ self._share_command = _ShareCommand(self.telepathy_conn, self._id)
+ self._share_command.connect('finished',
+ partial(self.__shared_cb,
+ share_activity_cb,
+ share_activity_error_cb))
+ self._share_command.run()
+
+ def __shared_cb(self, share_activity_cb, share_activity_error_cb,
+ share_command, error):
+ _logger.debug('%r: Share finished %r', self, error)
+ if error is None:
+ self._joined = True
+ self.room_handle = share_command.room_handle
+ self.telepathy_text_chan = share_command.text_channel
+ self.telepathy_tubes_chan = share_command.tubes_channel
+ self._channel_self_handle = share_command.channel_self_handle
+ self._text_channel_group_flags = \
+ share_command.text_channel_group_flags
+ self._publish_properties()
+ self._start_tracking_properties()
+ self._start_tracking_buddies()
+ self._start_tracking_channel()
+ share_activity_cb(self)
+ else:
+ share_activity_error_cb(self, error)
+
+ def _publish_properties(self):
+ properties = {}
+
+ if self._color is not None:
+ properties['color'] = str(self._color)
+ if self._name is not None:
+ properties['name'] = str(self._name)
+ if self._type is not None:
+ properties['type'] = self._type
+ if self._tags is not None:
+ properties['tags'] = self._tags
+ properties['private'] = self._private
+
+ self.telepathy_conn.SetProperties(
+ self.room_handle,
+ properties,
+ dbus_interface=CONN_INTERFACE_ACTIVITY_PROPERTIES)
+
+ def __share_error_cb(self, share_activity_error_cb, error):
+ logging.debug('%r: Share failed because: %s', self, error)
+ share_activity_error_cb(self, error)
+
+ # GetChannels() wrapper
+
+ def get_channels(self):
+ """Retrieve communications channel descriptions for the activity
+
+ Returns a tuple containing:
+ - the D-Bus well-known service name of the connection
+ (FIXME: this is redundant; in Telepathy it can be derived
+ from that of the connection)
+ - the D-Bus object path of the connection
+ - a list of D-Bus object paths representing the channels
+ associated with this activity
+ """
+ bus_name = self.telepathy_conn.requested_bus_name
+ connection_path = self.telepathy_conn.object_path
+ channels = [self.telepathy_text_chan.object_path,
+ self.telepathy_tubes_chan.object_path]
+
+ _logger.debug('%r: bus name is %s, connection is %s, channels are %r',
+ self, bus_name, connection_path, channels)
+ return bus_name, connection_path, channels
+
+ # Leaving
+ def __text_channel_closed_cb(self):
+ self._joined = False
+ self.emit("joined", False, "left activity")
+
+ def leave(self):
+ """Leave this shared activity"""
+ _logger.debug('%r: leaving', self)
+ self.telepathy_text_chan.Close()
+
+class _BaseCommand(gobject.GObject):
+ __gsignals__ = {
+ 'finished': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([object])),
+ }
+ def __init__(self):
+ gobject.GObject.__init__(self)
+
+ self.text_channel = None
+ self.text_channel_group_flags = None
+ self.tubes_channel = None
+ self.room_handle = None
+ self.channel_self_handle = None
+
+ def run(self):
+ raise NotImplementedError()
+
+
+class _ShareCommand(_BaseCommand):
+ def __init__(self, connection, activity_id):
+ _BaseCommand.__init__(self)
+
+ self._connection = connection
+ self._activity_id = activity_id
+ self._finished = False
+ self._join_command = None
+
+ def run(self):
+ """ TODO: Check we don't need this
+ # We shouldn't have to do this, but Gabble sometimes finds the IRC
+ # transport and goes "that has chatrooms, that'll do nicely". Work
+ # around it til Gabble gets better at finding the MUC service.
+ return '%s@%s' % (activity_id,
+ self._account['fallback-conference-server'])
+ """
+
+ self._connection.RequestHandles(
+ HANDLE_TYPE_ROOM,
+ [self._activity_id],
+ reply_handler=self.__got_handles_cb,
+ error_handler=self.__error_handler_cb,
+ dbus_interface=CONNECTION)
+
+ def __got_handles_cb(self, handles):
+ logging.debug('__got_handles_cb %r', handles)
+ self.room_handle = handles[0]
+
+ self._join_command = _JoinCommand(self._connection, self.room_handle)
+ self._join_command.connect('finished', self.__joined_cb)
+ self._join_command.run()
+
+ def __joined_cb(self, join_command, error):
+ _logger.debug('%r: Join finished %r', self, error)
+ if error is not None:
+ self._finished = True
+ self.emit('finished', error)
+ return
+
+ self.text_channel = join_command.text_channel
+ self.text_channel_group_flags = join_command.text_channel_group_flags
+ self.tubes_channel = join_command.tubes_channel
+
+ self._connection.AddActivity(
+ self._activity_id,
+ self.room_handle,
+ reply_handler=self.__added_activity_cb,
+ error_handler=self.__error_handler_cb,
+ dbus_interface=CONN_INTERFACE_BUDDY_INFO)
+
+ def __added_activity_cb(self):
+ self._finished = True
+ self.emit('finished', None)
+
+ def __error_handler_cb(self, error):
+ self._finished = True
+ self.emit('finished', error)
+
+class _JoinCommand(_BaseCommand):
+ def __init__(self, connection, room_handle):
+ _BaseCommand.__init__(self)
+
+ self._connection = connection
+ self._finished = False
+ self.room_handle = room_handle
+ self._global_self_handle = None
+
+ def run(self):
+ if self._finished:
+ raise RuntimeError('This command has already finished')
+
+ self._connection.Get(CONNECTION, 'SelfHandle',
+ reply_handler=self.__get_self_handle_cb,
+ error_handler=self.__error_handler_cb,
+ dbus_interface=PROPERTIES_IFACE)
+
+ def __get_self_handle_cb(self, handle):
+ self._global_self_handle = handle
+
+ self._connection.RequestChannel(CHANNEL_TYPE_TEXT,
+ HANDLE_TYPE_ROOM, self.room_handle, True,
+ reply_handler=self.__create_text_channel_cb,
+ error_handler=self.__error_handler_cb,
+ dbus_interface=CONNECTION)
+
+ self._connection.RequestChannel(CHANNEL_TYPE_TUBES,
+ HANDLE_TYPE_ROOM, self.room_handle, True,
+ reply_handler=self.__create_tubes_channel_cb,
+ error_handler=self.__error_handler_cb,
+ dbus_interface=CONNECTION)
+
+ def __create_text_channel_cb(self, channel_path):
+ Channel(self._connection.requested_bus_name, channel_path,
+ ready_handler=self.__text_channel_ready_cb)
+
+ def __create_tubes_channel_cb(self, channel_path):
+ Channel(self._connection.requested_bus_name, channel_path,
+ ready_handler=self.__tubes_channel_ready_cb)
+
+ def __error_handler_cb(self, error):
+ self._finished = True
+ self.emit('finished', error)
+
+ def __tubes_channel_ready_cb(self, channel):
+ _logger.debug('%r: Tubes channel %r is ready', self, channel)
+ self.tubes_channel = channel
+ self._tubes_ready()
+
+ def __text_channel_ready_cb(self, channel):
+ _logger.debug('%r: Text channel %r is ready', self, channel)
+ self.text_channel = channel
+ self._tubes_ready()
+
+ def _tubes_ready(self):
+ if self.text_channel is None or \
+ self.tubes_channel is None:
+ return
+
+ _logger.debug('%r: finished setting up tubes', self)
+
+ self._add_self_to_channel()
+
+ def __text_channel_group_flags_changed_cb(self, added, removed):
+ _logger.debug('__text_channel_group_flags_changed_cb %r %r', added,
+ removed)
+ self.text_channel_group_flags |= added
+ self.text_channel_group_flags &= ~removed
+
+ def _add_self_to_channel(self):
+ # FIXME: cope with non-Group channels here if we want to support
+ # non-OLPC-compatible IMs
+
+ group = self.text_channel[CHANNEL_INTERFACE_GROUP]
+
+ def got_all_members(members, local_pending, remote_pending):
+ _logger.debug('got_all_members members %r local_pending %r '
+ 'remote_pending %r', members, local_pending,
+ remote_pending)
+
+ if self.text_channel_group_flags & \
+ CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES:
+ self_handle = self.channel_self_handle
+ else:
+ self_handle = self._global_self_handle
+
+ if self_handle in local_pending:
+ _logger.debug('%r: We are in local pending - entering', self)
+ group.AddMembers([self_handle], '',
+ reply_handler=lambda: None,
+ error_handler=lambda e: self._join_failed_cb(e,
+ 'got_all_members AddMembers'))
+
+ if members:
+ self.__text_channel_members_changed_cb('', members, (),
+ (), (), 0, 0)
+
+ def got_group_flags(flags):
+ self.text_channel_group_flags = flags
+ # by the time we hook this, we need to know the group flags
+ group.connect_to_signal('MembersChanged',
+ self.__text_channel_members_changed_cb)
+
+ # bootstrap by getting the current state. This is where we find
+ # out whether anyone was lying to us in their PEP info
+ group.GetAllMembers(reply_handler=got_all_members,
+ error_handler=self.__error_handler_cb)
+
+ def got_self_handle(channel_self_handle):
+ self.channel_self_handle = channel_self_handle
+ group.connect_to_signal('GroupFlagsChanged',
+ self.__text_channel_group_flags_changed_cb)
+ group.GetGroupFlags(reply_handler=got_group_flags,
+ error_handler=self.__error_handler_cb)
+
+ group.GetSelfHandle(reply_handler=got_self_handle,
+ error_handler=self.__error_handler_cb)
+
+ def __text_channel_members_changed_cb(self, message, added, removed,
+ local_pending, remote_pending,
+ actor, reason):
+ _logger.debug('__text_channel_members_changed_cb added %r removed %r '
+ 'local_pending %r remote_pending %r channel_self_handle '
+ '%r', added, removed, local_pending, remote_pending,
+ self.channel_self_handle)
+
+ if self.text_channel_group_flags & \
+ CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES:
+ self_handle = self.channel_self_handle
+ else:
+ self_handle = self._global_self_handle
+
+ if self_handle in added:
+ if PROPERTIES_INTERFACE not in self.text_channel:
+ self._finished = True
+ self.emit('finished', None)
+ else:
+ self.text_channel[PROPERTIES_INTERFACE].ListProperties(
+ reply_handler=self.__list_properties_cb,
+ error_handler=self.__error_handler_cb)
+
+ def __list_properties_cb(self, prop_specs):
+ # FIXME: invite-only ought to be set on private activities; but
+ # since only the owner can change invite-only, that would break
+ # activity scope changes.
+ props = {
+ 'anonymous': False, # otherwise buddy resolution breaks
+ 'invite-only': False, # anyone who knows about the channel can join
+ 'invite-restricted': False, # so non-owners can invite others
+ 'persistent': False, # vanish when there are no members
+ 'private': True, # don't appear in server room lists
+ }
+ props_to_set = []
+ for ident, name, sig_, flags in prop_specs:
+ value = props.pop(name, None)
+ if value is not None:
+ if flags & PROPERTY_FLAG_WRITE:
+ props_to_set.append((ident, value))
+ # FIXME: else error, but only if we're creating the room?
+ # FIXME: if props is nonempty, then we want to set props that aren't
+ # supported here - raise an error?
+
+ if props_to_set:
+ self.text_channel[PROPERTIES_INTERFACE].SetProperties(
+ props_to_set, reply_handler=self.__set_properties_cb,
+ error_handler=self.__error_handler_cb)
+ else:
+ self._finished = True
+ self.emit('finished', None)
+
+ def __set_properties_cb(self):
+ self._finished = True
+ self.emit('finished', None)
diff --git a/toolkit/src/sugar/presence/buddy.py b/toolkit/src/sugar/presence/buddy.py
new file mode 100644
index 0000000..0a72a36
--- /dev/null
+++ b/toolkit/src/sugar/presence/buddy.py
@@ -0,0 +1,246 @@
+# Copyright (C) 2007, Red Hat, Inc.
+# Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# 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.
+
+"""UI interface to a buddy in the presence service
+
+STABLE.
+"""
+
+import logging
+
+import gobject
+import dbus
+import gconf
+from telepathy.interfaces import CONNECTION, \
+ CONNECTION_INTERFACE_ALIASING, \
+ CONNECTION_INTERFACE_CONTACTS
+from telepathy.constants import HANDLE_TYPE_CONTACT
+
+from sugar.presence.connectionmanager import get_connection_manager
+
+ACCOUNT_MANAGER_SERVICE = 'org.freedesktop.Telepathy.AccountManager'
+CONN_INTERFACE_BUDDY_INFO = 'org.laptop.Telepathy.BuddyInfo'
+
+_logger = logging.getLogger('sugar.presence.buddy')
+
+
+class BaseBuddy(gobject.GObject):
+ """UI interface for a Buddy in the presence service
+
+ Each buddy interface tracks a set of activities and properties
+ that can be queried to provide UI controls for manipulating
+ the presence interface.
+
+ Properties Dictionary:
+ 'key': public key,
+ 'nick': nickname ,
+ 'color': color (XXX what format),
+ 'current-activity': (XXX dbus path?),
+ 'owner': (XXX dbus path?),
+ """
+
+ __gtype_name__ = 'PresenceBaseBuddy'
+
+ __gsignals__ = {
+ 'joined-activity': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ 'left-activity': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ 'property-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ }
+
+ def __init__(self):
+ gobject.GObject.__init__(self)
+
+ self._key = None
+ self._nick = None
+ self._color = None
+ self._current_activity = None
+ self._owner = False
+ self._ip4_address = None
+ self._tags = None
+
+ def get_key(self):
+ return self._key
+
+ def set_key(self, key):
+ self._key = key
+
+ key = gobject.property(type=str, getter=get_key, setter=set_key)
+
+ def get_nick(self):
+ return self._nick
+
+ def set_nick(self, nick):
+ self._nick = nick
+
+ nick = gobject.property(type=str, getter=get_nick, setter=set_nick)
+
+ def get_color(self):
+ return self._color
+
+ def set_color(self, color):
+ self._color = color
+
+ color = gobject.property(type=str, getter=get_color, setter=set_color)
+
+ def get_current_activity(self):
+ if self._current_activity is None:
+ return None
+ for activity in self._activities.values():
+ if activity.props.id == self._current_activity:
+ return activity
+ return None
+
+ current_activity = gobject.property(type=object,
+ getter=get_current_activity)
+
+ def get_owner(self):
+ return self._owner
+
+ def set_owner(self, owner):
+ self._owner = owner
+
+ owner = gobject.property(type=bool, getter=get_owner, setter=set_owner,
+ default=False)
+
+ def get_ip4_address(self):
+ return self._ip4_address
+
+ def set_ip4_address(self, ip4_address):
+ self._ip4_address = ip4_address
+
+ ip4_address = gobject.property(type=str, getter=get_ip4_address,
+ setter=set_ip4_address)
+
+ def get_tags(self):
+ return self._tags
+
+ def set_tags(self, tags):
+ self._tags = tags
+
+ tags = gobject.property(type=str, getter=get_tags, setter=set_tags)
+
+ def object_path(self):
+ """Retrieve our dbus object path"""
+ return None
+
+
+class Buddy(BaseBuddy):
+ __gtype_name__ = 'PresenceBuddy'
+ def __init__(self, account_path, contact_id):
+ _logger.debug('Buddy.__init__')
+ BaseBuddy.__init__(self)
+
+ self._account_path = account_path
+ self.contact_id = contact_id
+ self.contact_handle = None
+
+ connection_manager = get_connection_manager()
+ connection = connection_manager.get_connection(account_path)
+
+ connection_name = connection.object_path.replace('/', '.')[1:]
+
+ bus = dbus.SessionBus()
+ obj = bus.get_object(connection_name, connection.object_path)
+ handles = obj.RequestHandles(HANDLE_TYPE_CONTACT, [self.contact_id],
+ dbus_interface=CONNECTION)
+ self.contact_handle = handles[0]
+
+ self._get_properties_call = bus.call_async(
+ connection_name,
+ connection.object_path,
+ CONN_INTERFACE_BUDDY_INFO,
+ 'GetProperties',
+ 'u',
+ (self.contact_handle,),
+ reply_handler=self.__got_properties_cb,
+ error_handler=self.__error_handler_cb,
+ utf8_strings=True,
+ byte_arrays=True)
+
+ self._get_attributes_call = bus.call_async(
+ connection_name,
+ connection.object_path,
+ CONNECTION_INTERFACE_CONTACTS,
+ 'GetContactAttributes',
+ 'auasb',
+ ([self.contact_handle], [CONNECTION_INTERFACE_ALIASING], False),
+ reply_handler=self.__got_attributes_cb,
+ error_handler=self.__error_handler_cb)
+
+ def __got_properties_cb(self, properties):
+ _logger.debug('__got_properties_cb %r', properties)
+ self._get_properties_call = None
+ self._update_properties(properties)
+
+ def __got_attributes_cb(self, attributes):
+ _logger.debug('__got_attributes_cb %r', attributes)
+ self._get_attributes_call = None
+ self._update_attributes(attributes[self.contact_handle])
+
+ def __error_handler_cb(self, error):
+ _logger.debug('__error_handler_cb %r', error)
+
+ def __properties_changed_cb(self, new_props):
+ _logger.debug('%r: Buddy properties changed to %r', self, new_props)
+ self._update_properties(new_props)
+
+ def _update_properties(self, properties):
+ if 'key' in properties:
+ self.props.key = properties['key']
+ if 'color' in properties:
+ self.props.color = properties['color']
+ if 'current-activity' in properties:
+ self.props.current_activity = properties['current-activity']
+ if 'owner' in properties:
+ self.props.owner = properties['owner']
+ if 'ip4-address' in properties:
+ self.props.ip4_address = properties['ip4-address']
+ if 'tags' in properties:
+ self.props.tags = properties['tags']
+
+ def _update_attributes(self, attributes):
+ nick_key = CONNECTION_INTERFACE_ALIASING + '/alias'
+ if nick_key in attributes:
+ self.props.nick = attributes[nick_key]
+
+ def do_get_property(self, pspec):
+ if pspec.name == 'nick' and self._get_attributes_call is not None:
+ _logger.debug('%r: Blocking on GetContactAttributes() because '
+ 'someone wants property nick', self)
+ self._get_attributes_call.block()
+ elif pspec.name != 'nick' and self._get_properties_call is not None:
+ _logger.debug('%r: Blocking on GetProperties() because someone '
+ 'wants property %s', self, pspec.name)
+ self._get_properties_call.block()
+
+ return BaseBuddy.do_get_property(self, pspec)
+
+
+class Owner(BaseBuddy):
+
+ __gtype_name__ = 'PresenceOwner'
+
+ def __init__(self):
+ BaseBuddy.__init__(self)
+
+ client = gconf.client_get_default()
+ self.props.nick = client.get_string("/desktop/sugar/user/nick")
+ self.props.color = client.get_string("/desktop/sugar/user/color")
diff --git a/toolkit/src/sugar/presence/connectionmanager.py b/toolkit/src/sugar/presence/connectionmanager.py
new file mode 100644
index 0000000..1165c45
--- /dev/null
+++ b/toolkit/src/sugar/presence/connectionmanager.py
@@ -0,0 +1,116 @@
+# Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# 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.
+
+"""
+UNSTABLE. It should really be internal to the sugar.presence package.
+"""
+
+from functools import partial
+
+import dbus
+from dbus import PROPERTIES_IFACE
+from telepathy.interfaces import ACCOUNT, \
+ ACCOUNT_MANAGER, \
+ CONNECTION
+from telepathy.constants import CONNECTION_STATUS_CONNECTED
+
+ACCOUNT_MANAGER_SERVICE = 'org.freedesktop.Telepathy.AccountManager'
+ACCOUNT_MANAGER_PATH = '/org/freedesktop/Telepathy/AccountManager'
+
+class Connection(object):
+ def __init__(self, account_path, connection):
+ self.account_path = account_path
+ self.connection = connection
+ self.connected = False
+
+class ConnectionManager(object):
+ """Track available telepathy connections"""
+
+ def __init__(self):
+ self._connections_per_account = {}
+
+ bus = dbus.SessionBus()
+ obj = bus.get_object(ACCOUNT_MANAGER_SERVICE, ACCOUNT_MANAGER_PATH)
+ account_manager = dbus.Interface(obj, ACCOUNT_MANAGER)
+ account_paths = account_manager.Get(ACCOUNT_MANAGER, 'ValidAccounts',
+ dbus_interface=PROPERTIES_IFACE)
+ for account_path in account_paths:
+ obj = bus.get_object(ACCOUNT_MANAGER_SERVICE, account_path)
+ obj.connect_to_signal('AccountPropertyChanged',
+ partial(self.__account_property_changed_cb, account_path))
+ connection_path = obj.Get(ACCOUNT, 'Connection')
+ if connection_path != '/':
+ self._track_connection(account_path, connection_path)
+
+ def __account_property_changed_cb(self, account_path, properties):
+ if 'Connection' not in properties:
+ return
+ if properties['Connection'] == '/':
+ if account_path in self._connections_per_account:
+ del self._connections_per_account[account_path]
+ else:
+ self._track_connection(account_path, properties['Connection'])
+
+ def _track_connection(self, account_path, connection_path):
+ connection_name = connection_path.replace('/', '.')[1:]
+ bus = dbus.SessionBus()
+ connection = bus.get_object(connection_name, connection_path)
+ connection.connect_to_signal('StatusChanged',
+ partial(self.__status_changed_cb, account_path))
+ self._connections_per_account[account_path] = \
+ Connection(account_path, connection)
+
+ if connection.Get(CONNECTION, 'Status') == CONNECTION_STATUS_CONNECTED:
+ self._connections_per_account[account_path].connected = True
+ else:
+ self._connections_per_account[account_path].connected = False
+
+ def __status_changed_cb(self, account_path, status, reason):
+ if status == CONNECTION_STATUS_CONNECTED:
+ self._connections_per_account[account_path].connected = True
+ else:
+ self._connections_per_account[account_path].connected = False
+
+ def get_preferred_connection(self):
+ best_connection = None, None
+ for account_path, connection in self._connections_per_account.items():
+ if 'salut' in account_path and connection.connected:
+ best_connection = account_path, connection.connection
+ elif 'gabble' in account_path and connection.connected:
+ best_connection = account_path, connection.connection
+ break
+ return best_connection
+
+ def get_connection(self, account_path):
+ return self._connections_per_account[account_path].connection
+
+ def get_connections_per_account(self):
+ return self._connections_per_account
+
+ def get_account_for_connection(self, connection_path):
+ for account_path, connection in self._connections_per_account.items():
+ if connection.connection.object_path == connection_path:
+ return account_path
+ return None
+
+_connection_manager = None
+
+def get_connection_manager():
+ global _connection_manager
+ if not _connection_manager:
+ _connection_manager = ConnectionManager()
+ return _connection_manager
diff --git a/toolkit/src/sugar/presence/presenceservice.py b/toolkit/src/sugar/presence/presenceservice.py
new file mode 100644
index 0000000..326791b
--- /dev/null
+++ b/toolkit/src/sugar/presence/presenceservice.py
@@ -0,0 +1,375 @@
+# Copyright (C) 2007, Red Hat, Inc.
+# Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# 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.
+
+"""
+STABLE.
+"""
+
+import logging
+
+import gobject
+import dbus
+import dbus.exceptions
+import dbus.glib
+from dbus import PROPERTIES_IFACE
+
+from sugar.presence.buddy import Buddy, Owner
+from sugar.presence.activity import Activity
+from sugar.presence.connectionmanager import get_connection_manager
+
+from telepathy.interfaces import ACCOUNT, \
+ ACCOUNT_MANAGER, \
+ CONNECTION
+from telepathy.constants import HANDLE_TYPE_CONTACT
+
+_logger = logging.getLogger('sugar.presence.presenceservice')
+
+ACCOUNT_MANAGER_SERVICE = 'org.freedesktop.Telepathy.AccountManager'
+ACCOUNT_MANAGER_PATH = '/org/freedesktop/Telepathy/AccountManager'
+
+class PresenceService(gobject.GObject):
+ """Provides simplified access to the Telepathy framework to activities"""
+ __gsignals__ = {
+ 'activity-shared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT,
+ gobject.TYPE_PYOBJECT])),
+ }
+
+ def __init__(self):
+ """Initialise the service and attempt to connect to events
+ """
+ gobject.GObject.__init__(self)
+
+ self._activity_cache = None
+ self._buddy_cache = {}
+
+ def _new_object(self, object_path):
+ """Turn new object path into (cached) Buddy/Activity instance
+
+ object_path -- full dbus path of the new object, must be
+ prefixed with either of _PS_BUDDY_OP or _PS_ACTIVITY_OP
+
+ Note that this method is called throughout the class whenever
+ the representation of the object is required, it is not only
+ called when the object is first discovered. The point is to only have
+ _one_ Python object for any D-Bus object represented by an object path,
+ effectively wrapping the D-Bus object in a single Python GObject.
+
+ returns presence Buddy or Activity representation
+ """
+ obj = None
+ try:
+ obj = self._objcache[object_path]
+ _logger.debug('Reused proxy %r', obj)
+ except KeyError:
+ if object_path.startswith(self._PS_BUDDY_OP):
+ obj = Buddy(self._bus, self._new_object,
+ self._del_object, object_path)
+ elif object_path.startswith(self._PS_ACTIVITY_OP):
+ obj = Activity(self._bus, self._new_object,
+ self._del_object, object_path)
+ try:
+ # Pre-fill the activity's ID
+ activity_id = obj.props.id
+ except dbus.exceptions.DBusException:
+ logging.debug('Cannot get the activity ID')
+ else:
+ raise RuntimeError("Unknown object type")
+ self._objcache[object_path] = obj
+ _logger.debug('Created proxy %r', obj)
+ return obj
+
+ def _have_object(self, object_path):
+ return object_path in self._objcache.keys()
+
+ def _del_object(self, object_path):
+ """Fully remove an object from the object cache when
+ it's no longer needed.
+ """
+ del self._objcache[object_path]
+
+ def get(self, object_path):
+ """Return the Buddy or Activity object corresponding to the given
+ D-Bus object path.
+ """
+ return self._new_object(object_path)
+
+ def get_activities(self):
+ """Retrieve set of all activities from service
+
+ returns list of Activity objects for all object paths
+ the service reports exist (using GetActivities)
+ """
+ resp = self._ps.GetActivities()
+ acts = []
+ for item in resp:
+ acts.append(self._new_object(item))
+ return acts
+
+ def _get_activities_cb(self, reply_handler, resp):
+ acts = []
+ for item in resp:
+ acts.append(self._new_object(item))
+
+ reply_handler(acts)
+
+ def _get_activities_error_cb(self, error_handler, e):
+ if error_handler:
+ error_handler(e)
+ else:
+ _logger.warn('Unable to retrieve activity-list from presence '
+ 'service: %s', e)
+
+ def get_activities_async(self, reply_handler=None, error_handler=None):
+ """Retrieve set of all activities from service asyncronously
+ """
+
+ if not reply_handler:
+ logging.error('Function get_activities_async called without' \
+ 'a reply handler. Can not run.')
+ return
+
+ self._ps.GetActivities(
+ reply_handler=lambda resp: \
+ self._get_activities_cb(reply_handler, resp),
+ error_handler=lambda e: \
+ self._get_activities_error_cb(error_handler, e))
+
+ def get_activity(self, activity_id, warn_if_none=True):
+ """Retrieve single Activity object for the given unique id
+
+ activity_id -- unique ID for the activity
+
+ returns single Activity object or None if the activity
+ is not found using GetActivityById on the service
+ """
+ if self._activity_cache is not None:
+ if self._activity_cache.props.id != activity_id:
+ raise RuntimeError('Activities can only access their own shared'
+ 'instance')
+ return self._activity_cache
+ else:
+ connection_manager = get_connection_manager()
+ connections_per_account = \
+ connection_manager.get_connections_per_account()
+ for account_path, connection in connections_per_account.items():
+ if not connection.connected:
+ continue
+ logging.debug("Calling GetActivity on %s", account_path)
+ try:
+ room_handle = connection.connection.GetActivity(activity_id)
+ except dbus.exceptions.DBusException, e:
+ name = 'org.freedesktop.Telepathy.Error.NotAvailable'
+ if e.get_dbus_name() == name:
+ logging.debug("There's no shared activity with the id "
+ "%s", activity_id)
+ else:
+ raise
+ else:
+ activity = Activity(account_path, connection.connection,
+ room_handle=room_handle)
+ self._activity_cache = activity
+ return activity
+
+ return None
+
+ def get_activity_by_handle(self, connection_path, room_handle):
+ if self._activity_cache is not None:
+ if self._activity_cache.room_handle != room_handle:
+ raise RuntimeError('Activities can only access their own shared'
+ 'instance')
+ return self._activity_cache
+ else:
+ connection_manager = get_connection_manager()
+ account_path = \
+ connection_manager.get_account_for_connection(connection_path)
+
+ connection_name = connection_path.replace('/', '.')[1:]
+ bus = dbus.SessionBus()
+ connection = bus.get_object(connection_name, connection_path)
+ activity = Activity(account_path, connection,
+ room_handle=room_handle)
+ self._activity_cache = activity
+ return activity
+
+ def get_buddies(self):
+ """Retrieve set of all buddies from service
+
+ returns list of Buddy objects for all object paths
+ the service reports exist (using GetBuddies)
+ """
+ try:
+ resp = self._ps.GetBuddies()
+ except dbus.exceptions.DBusException:
+ _logger.exception('Unable to retrieve buddy-list from presence '
+ 'service')
+ return []
+ else:
+ buddies = []
+ for item in resp:
+ buddies.append(self._new_object(item))
+ return buddies
+
+ def _get_buddies_cb(self, reply_handler, resp):
+ buddies = []
+ for item in resp:
+ buddies.append(self._new_object(item))
+
+ reply_handler(buddies)
+
+ def _get_buddies_error_cb(self, error_handler, e):
+ if error_handler:
+ error_handler(e)
+ else:
+ _logger.warn('Unable to retrieve buddy-list from presence '
+ 'service: %s', e)
+
+ def get_buddies_async(self, reply_handler=None, error_handler=None):
+ """Retrieve set of all buddies from service asyncronously
+ """
+
+ if not reply_handler:
+ logging.error('Function get_buddies_async called without' \
+ 'a reply handler. Can not run.')
+ return
+
+ self._ps.GetBuddies(
+ reply_handler=lambda resp: \
+ self._get_buddies_cb(reply_handler, resp),
+ error_handler=lambda e: \
+ self._get_buddies_error_cb(error_handler, e))
+
+ def get_buddy(self, account_path, contact_id):
+ if (account_path, contact_id) in self._buddy_cache:
+ return self._buddy_cache[(account_path, contact_id)]
+
+ buddy = Buddy(account_path, contact_id)
+ self._buddy_cache[(account_path, contact_id)] = buddy
+ return buddy
+
+ # DEPRECATED
+ def get_buddy_by_telepathy_handle(self, tp_conn_name, tp_conn_path,
+ handle):
+ """Retrieve single Buddy object for the given public key
+
+ :Parameters:
+ `tp_conn_name` : str
+ The well-known bus name of a Telepathy connection
+ `tp_conn_path` : dbus.ObjectPath
+ The object path of the Telepathy connection
+ `handle` : int or long
+ The handle of a Telepathy contact on that connection,
+ of type HANDLE_TYPE_CONTACT. This may not be a
+ channel-specific handle.
+ :Returns: the Buddy object, or None if the buddy is not found
+ """
+
+ bus = dbus.Bus()
+ obj = bus.get_object(ACCOUNT_MANAGER_SERVICE, ACCOUNT_MANAGER_PATH)
+ account_manager = dbus.Interface(obj, ACCOUNT_MANAGER)
+ account_paths = account_manager.Get(ACCOUNT_MANAGER, 'ValidAccounts',
+ dbus_interface=PROPERTIES_IFACE)
+ for account_path in account_paths:
+ obj = bus.get_object(ACCOUNT_MANAGER_SERVICE, account_path)
+ connection_path = obj.Get(ACCOUNT, 'Connection')
+ if connection_path == tp_conn_path:
+ connection_name = connection_path.replace('/', '.')[1:]
+ connection = bus.get_object(connection_name, connection_path)
+ contact_ids = connection.InspectHandles(HANDLE_TYPE_CONTACT,
+ [handle],
+ dbus_interface=CONNECTION)
+ return self.get_buddy(account_path, contact_ids[0])
+
+ raise ValueError('Unknown buddy in connection %s with handle %d',
+ tp_conn_path, handle)
+
+ def get_owner(self):
+ """Retrieves the laptop Buddy object."""
+ return Owner()
+
+ def __share_activity_cb(self, activity):
+ """Finish sharing the activity
+ """
+ self.emit("activity-shared", True, activity, None)
+
+ def __share_activity_error_cb(self, activity, error):
+ """Notify with GObject event of unsuccessful sharing of activity
+ """
+ self.emit("activity-shared", False, activity, error)
+
+ def share_activity(self, activity, properties=None, private=True):
+ if properties is None:
+ properties = {}
+
+ if 'id' not in properties:
+ properties['id'] = activity.get_id()
+
+ if 'type' not in properties:
+ properties['type'] = activity.get_bundle_id()
+
+ if 'name' not in properties:
+ properties['name'] = activity.metadata.get('title', None)
+
+ if 'color' not in properties:
+ properties['color'] = activity.metadata.get('icon-color', None)
+
+ properties['private'] = private
+
+ if self._activity_cache is not None:
+ raise ValueError('Activity %s is already tracked',
+ activity.get_id())
+
+ connection_manager = get_connection_manager()
+ account_path, connection = \
+ connection_manager.get_preferred_connection()
+ shared_activity = Activity(account_path, connection,
+ properties=properties)
+ self._activity_cache = shared_activity
+
+ """
+ if shared_activity.props.joined:
+ raise RuntimeError('Activity %s is already shared.' %
+ activity.get_id())
+ """
+
+ shared_activity.share(self.__share_activity_cb,
+ self.__share_activity_error_cb)
+
+ def get_preferred_connection(self):
+ """Gets the preferred telepathy connection object that an activity
+ should use when talking directly to telepathy
+
+ returns the bus name and the object path of the Telepathy connection
+ """
+ connection_manager = get_connection_manager()
+ account_path, connection = connection_manager.get_preferred_connection()
+ if connection is None:
+ return None
+ else:
+ return connection.requested_bus_name, connection.object_path
+
+
+_ps = None
+
+
+def get_instance(allow_offline_iface=False):
+ """Retrieve this process' view of the PresenceService"""
+ global _ps
+ if not _ps:
+ _ps = PresenceService()
+ return _ps
diff --git a/toolkit/src/sugar/presence/sugartubeconn.py b/toolkit/src/sugar/presence/sugartubeconn.py
new file mode 100644
index 0000000..954ef67
--- /dev/null
+++ b/toolkit/src/sugar/presence/sugartubeconn.py
@@ -0,0 +1,63 @@
+# Copyright (C) 2008 One Laptop Per Child
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""Subclass of TubeConnection that converts handles to Sugar Buddies
+
+STABLE.
+"""
+
+from telepathy.constants import (
+ CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES)
+
+from sugar.presence.tubeconn import TubeConnection
+from sugar.presence import presenceservice
+
+
+class SugarTubeConnection(TubeConnection):
+ """Subclass of TubeConnection that converts handles to Sugar Buddies"""
+
+ def __new__(cls, conn, tubes_iface, tube_id, address=None,
+ group_iface=None, mainloop=None):
+ self = super(SugarTubeConnection, cls).__new__(
+ cls, conn, tubes_iface, tube_id, address=address,
+ group_iface=group_iface, mainloop=mainloop)
+ self._conn = conn
+ self._group_iface = group_iface
+ return self
+
+ def get_buddy(self, cs_handle):
+ """Retrieve a Buddy object given a telepathy handle.
+
+ cs_handle: A channel-specific CONTACT type handle.
+ returns: sugar.presence Buddy object or None
+ """
+ pservice = presenceservice.get_instance()
+ if self.self_handle == cs_handle:
+ # It's me, just get my global handle
+ handle = self._conn.GetSelfHandle()
+ elif self._group_iface.GetGroupFlags() & \
+ CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES:
+ # The group (channel) has channel specific handles
+ handle = self._group_iface.GetHandleOwners([cs_handle])[0]
+ else:
+ # The group does not have channel specific handles
+ handle = cs_handle
+
+ # deal with failure to get the handle owner
+ if handle == 0:
+ return None
+ return pservice.get_buddy_by_telepathy_handle(
+ self._conn.service_name, self._conn.object_path, handle)
diff --git a/toolkit/src/sugar/presence/test_presence.txt b/toolkit/src/sugar/presence/test_presence.txt
new file mode 100644
index 0000000..d0736a9
--- /dev/null
+++ b/toolkit/src/sugar/presence/test_presence.txt
@@ -0,0 +1,26 @@
+This is a test of presence.
+
+To test this service we will start up a mock dbus library:
+
+ >>> from sugar.testing import mockdbus
+ >>> import dbus
+ >>> pres_service = mockdbus.MockService(
+ ... 'org.laptop.Presence', '/org/laptop/Presence', name='pres')
+ >>> pres_service.install()
+ >>> pres_interface = dbus.Interface(pres_service, 'org.laptop.Presence')
+
+Then we import the library (second, to make sure it connects to our
+mocked system, though the lazy instantiation in get_instance() should
+handle it):
+
+ >>> from sugar.presence import PresenceService
+ >>> ps = PresenceService.get_instance()
+ >>> pres_interface.make_response('getServices', [])
+ >>> ps.get_services()
+ Called pres.org.laptop.Presence:getServices()
+ []
+ >>> pres_interface.make_response('getBuddies', [])
+ >>> ps.get_buddies()
+ Called pres.org.laptop.Presence:getBuddies()
+ []
+
diff --git a/toolkit/src/sugar/presence/tubeconn.py b/toolkit/src/sugar/presence/tubeconn.py
new file mode 100644
index 0000000..88cc729
--- /dev/null
+++ b/toolkit/src/sugar/presence/tubeconn.py
@@ -0,0 +1,114 @@
+# This should eventually land in telepathy-python, so has the same license:
+
+# Copyright (C) 2007 Collabora Ltd. <http://www.collabora.co.uk/>
+#
+# This program 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.1 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+STABLE.
+"""
+
+__all__ = ('TubeConnection', )
+__docformat__ = 'reStructuredText'
+
+
+import logging
+
+from dbus.connection import Connection
+
+
+logger = logging.getLogger('telepathy.tubeconn')
+
+
+class TubeConnection(Connection):
+
+ def __new__(cls, conn, tubes_iface, tube_id, address=None,
+ group_iface=None, mainloop=None):
+ # pylint: disable-msg=W0212
+ # Confused by __new__
+ if address is None:
+ address = tubes_iface.GetDBusTubeAddress(tube_id)
+ self = super(TubeConnection, cls).__new__(cls, address,
+ mainloop=mainloop)
+
+ self._tubes_iface = tubes_iface
+ self.tube_id = tube_id
+ self.participants = {}
+ self.bus_name_to_handle = {}
+ self._mapping_watches = []
+
+ if group_iface is None:
+ method = conn.GetSelfHandle
+ else:
+ method = group_iface.GetSelfHandle
+ method(reply_handler=self._on_get_self_handle_reply,
+ error_handler=self._on_get_self_handle_error)
+
+ return self
+
+ def _on_get_self_handle_reply(self, handle):
+ # pylint: disable-msg=W0201
+ # Confused by __new__
+ self.self_handle = handle
+ match = self._tubes_iface.connect_to_signal('DBusNamesChanged',
+ self._on_dbus_names_changed)
+ self._tubes_iface.GetDBusNames(self.tube_id,
+ reply_handler=self._on_get_dbus_names_reply,
+ error_handler=self._on_get_dbus_names_error)
+ self._dbus_names_changed_match = match
+
+ def _on_get_self_handle_error(self, e):
+ logging.basicConfig()
+ logger.error('GetSelfHandle failed: %s', e)
+
+ def close(self):
+ self._dbus_names_changed_match.remove()
+ self._on_dbus_names_changed(self.tube_id, (), self.participants.keys())
+ super(TubeConnection, self).close()
+
+ def _on_get_dbus_names_reply(self, names):
+ self._on_dbus_names_changed(self.tube_id, names, ())
+
+ def _on_get_dbus_names_error(self, e):
+ logging.basicConfig()
+ logger.error('GetDBusNames failed: %s', e)
+
+ def _on_dbus_names_changed(self, tube_id, added, removed):
+ if tube_id == self.tube_id:
+ for handle, bus_name in added:
+ if handle == self.self_handle:
+ # I've just joined - set my unique name
+ self.set_unique_name(bus_name)
+ self.participants[handle] = bus_name
+ self.bus_name_to_handle[bus_name] = handle
+
+ # call the callback while the removed people are still in
+ # participants, so their bus names are available
+ for callback in self._mapping_watches:
+ callback(added, removed)
+
+ for handle in removed:
+ bus_name = self.participants.pop(handle, None)
+ self.bus_name_to_handle.pop(bus_name, None)
+
+ def watch_participants(self, callback):
+ self._mapping_watches.append(callback)
+ if self.participants:
+ # GetDBusNames already returned: fake a participant add event
+ # immediately
+ added = []
+ for k, v in self.participants.iteritems():
+ added.append((k, v))
+ callback(added, [])
diff --git a/toolkit/src/sugar/profile.py b/toolkit/src/sugar/profile.py
new file mode 100644
index 0000000..1883717
--- /dev/null
+++ b/toolkit/src/sugar/profile.py
@@ -0,0 +1,227 @@
+# Copyright (C) 2006-2007, Red Hat, Inc.
+#
+# 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.
+
+"""User settings/configuration loading.
+
+DEPRECATED. We are using GConf now to store preferences.
+"""
+
+import gconf
+import os
+import logging
+from ConfigParser import ConfigParser
+
+from sugar import env
+from sugar import util
+from sugar.graphics.xocolor import XoColor
+
+
+_profile = None
+
+
+class Profile(object):
+ """Local user's current options/profile information
+
+ User settings were previously stored in an INI-style
+ configuration file. We moved to gconf now. The deprected
+ API is kept around to not break activities still using it.
+
+ The profile is also responsible for loading the user's
+ public and private ssh keys from disk.
+
+ Attributes:
+
+ pubkey -- public ssh key
+ privkey_hash -- SHA has of the child's public key
+ """
+
+ def __init__(self, path):
+ self._pubkey = None
+ self._privkey_hash = None
+
+ self.pubkey = self._load_pubkey()
+ self.privkey_hash = self._hash_private_key()
+
+ def is_valid(self):
+ client = gconf.client_get_default()
+ nick = client.get_string("/desktop/sugar/user/nick")
+ color = client.get_string("/desktop/sugar/user/color")
+
+ return nick is not '' and \
+ color is not '' and \
+ self.pubkey is not None and \
+ self.privkey_hash is not None
+
+ def _load_pubkey(self):
+ key_path = os.path.join(env.get_profile_path(), 'owner.key.pub')
+
+ if not os.path.exists(key_path):
+ return None
+
+ try:
+ f = open(key_path, "r")
+ lines = f.readlines()
+ f.close()
+ except IOError:
+ logging.exception('Error reading public key')
+ return None
+
+ magic = "ssh-dss "
+ for l in lines:
+ l = l.strip()
+ if not l.startswith(magic):
+ continue
+ return l[len(magic):]
+ else:
+ logging.error("Error parsing public key.")
+ return None
+
+ def _hash_private_key(self):
+ key_path = os.path.join(env.get_profile_path(), 'owner.key')
+
+ if not os.path.exists(key_path):
+ return None
+
+ try:
+ f = open(key_path, "r")
+ lines = f.readlines()
+ f.close()
+ except IOError:
+ logging.exception('Error reading private key')
+ return None
+
+ key = ""
+ begin_found = False
+ end_found = False
+ for l in lines:
+ l = l.strip()
+ if l.startswith("-----BEGIN DSA PRIVATE KEY-----"):
+ begin_found = True
+ continue
+ if l.startswith("-----END DSA PRIVATE KEY-----"):
+ end_found = True
+ continue
+ key += l
+ if not (len(key) and begin_found and end_found):
+ logging.error("Error parsing public key.")
+ return None
+
+ # hash it
+ key_hash = util.sha_data(key)
+ return util.printable_hash(key_hash)
+
+ def convert_profile(self):
+ cp = ConfigParser()
+ path = os.path.join(env.get_profile_path(), 'config')
+ cp.read([path])
+
+ client = gconf.client_get_default()
+
+ if cp.has_option('Buddy', 'NickName'):
+ name = cp.get('Buddy', 'NickName')
+ # decode nickname from ascii-safe chars to unicode
+ nick = name.decode("utf-8")
+ client.set_string("/desktop/sugar/user/nick", nick)
+ if cp.has_option('Buddy', 'Color'):
+ color = cp.get('Buddy', 'Color')
+ client.set_string("/desktop/sugar/user/color", color)
+ if cp.has_option('Jabber', 'Server'):
+ server = cp.get('Jabber', 'Server')
+ client.set_string("/desktop/sugar/collaboration/jabber_server",
+ server)
+ if cp.has_option('Date', 'Timezone'):
+ timezone = cp.get('Date', 'Timezone')
+ client.set_string("/desktop/sugar/date/timezone", timezone)
+ if cp.has_option('Frame', 'HotCorners'):
+ delay = float(cp.get('Frame', 'HotCorners'))
+ client.set_int("/desktop/sugar/frame/corner_delay", int(delay))
+ if cp.has_option('Frame', 'WarmEdges'):
+ delay = float(cp.get('Frame', 'WarmEdges'))
+ client.set_int("/desktop/sugar/frame/edge_delay", int(delay))
+ if cp.has_option('Server', 'Backup1'):
+ backup1 = cp.get('Server', 'Backup1')
+ client.set_string("/desktop/sugar/backup_url", backup1)
+ if cp.has_option('Sound', 'Volume'):
+ volume = float(cp.get('Sound', 'Volume'))
+ client.set_int("/desktop/sugar/sound/volume", int(volume))
+ if cp.has_option('Power', 'AutomaticPM'):
+ state = cp.get('Power', 'AutomaticPM')
+ if state.lower() == "true":
+ client.set_bool("/desktop/sugar/power/automatic", True)
+ if cp.has_option('Power', 'ExtremePM'):
+ state = cp.get('Power', 'ExtremePM')
+ if state.lower() == "true":
+ client.set_bool("/desktop/sugar/power/extreme", True)
+ if cp.has_option('Shell', 'FavoritesLayout'):
+ layout = cp.get('Shell', 'FavoritesLayout')
+ client.set_string("/desktop/sugar/desktop/favorites_layout",
+ layout)
+ del cp
+ try:
+ os.unlink(path)
+ except OSError:
+ logging.error('Error removing old profile.')
+
+ def create_debug_file(self):
+ path = os.path.join(os.path.expanduser('~/.sugar'), 'debug')
+ fd = open(path, 'w')
+ text = '# Uncomment the following lines to turn on many' \
+ 'sugar debugging\n'\
+ '# log files and features\n'\
+ '#export LM_DEBUG=net\n' \
+ '#export GABBLE_DEBUG=all\n' \
+ '#export GABBLE_LOGFILE=' \
+ '$HOME/.sugar/$SUGAR_PROFILE/logs/telepathy-gabble.log\n' \
+ '#export SALUT_DEBUG=all\n' \
+ '#export SALUT_LOGFILE=' \
+ '$HOME/.sugar/$SUGAR_PROFILE/logs/telepathy-salut.log\n' \
+ '#export GIBBER_DEBUG=all\n' \
+ '#export WOCKY_DEBUG=all\n' \
+ '#export MC_LOGFILE=' \
+ '$HOME/.sugar/$SUGAR_PROFILE/logs/mission-control.log\n' \
+ '#export MC_DEBUG=all\n' \
+ '#export PRESENCESERVICE_DEBUG=1\n' \
+ '#export SUGAR_LOGGER_LEVEL=debug\n\n' \
+ '# Uncomment the following line to enable core dumps\n' \
+ '#ulimit -c unlimited\n'
+ fd.write(text)
+ fd.close()
+
+
+def get_profile():
+ global _profile
+
+ if not _profile:
+ path = os.path.join(env.get_profile_path(), 'config')
+ _profile = Profile(path)
+
+ return _profile
+
+
+def get_nick_name():
+ client = gconf.client_get_default()
+ return client.get_string("/desktop/sugar/user/nick")
+
+
+def get_color():
+ client = gconf.client_get_default()
+ color = client.get_string("/desktop/sugar/user/color")
+ return XoColor(color)
+
+
+def get_pubkey():
+ return get_profile().pubkey
diff --git a/toolkit/src/sugar/session.py b/toolkit/src/sugar/session.py
new file mode 100644
index 0000000..4ebc590
--- /dev/null
+++ b/toolkit/src/sugar/session.py
@@ -0,0 +1,54 @@
+# Copyright (C) 2008, Red Hat, Inc.
+#
+# 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.
+
+"""
+UNSTABLE. Used only internally by jarabe.
+"""
+
+import os
+
+from sugar import _sugarext
+
+
+class XSMPClient(_sugarext.SMClientXSMP):
+
+ def __init__(self):
+ _sugarext.SMClientXSMP.__init__(self)
+
+
+class SessionManager(object):
+
+ def __init__(self):
+ address = _sugarext.xsmp_init()
+ os.environ['SESSION_MANAGER'] = address
+ _sugarext.xsmp_run()
+
+ self.session = _sugarext.session_create_global()
+
+ def start(self):
+ self.session.start()
+ self.session.connect('shutdown_completed',
+ self.__shutdown_completed_cb)
+
+ def initiate_shutdown(self):
+ self.session.initiate_shutdown()
+
+ def shutdown_completed(self):
+ _sugarext.xsmp_shutdown()
+
+ def __shutdown_completed_cb(self, session):
+ self.shutdown_completed()
diff --git a/toolkit/src/sugar/sexy-icon-entry.c b/toolkit/src/sugar/sexy-icon-entry.c
new file mode 100644
index 0000000..ca35209
--- /dev/null
+++ b/toolkit/src/sugar/sexy-icon-entry.c
@@ -0,0 +1,984 @@
+/*
+ * @file libsexy/sexy-icon-entry.c Entry widget
+ *
+ * @Copyright (C) 2004-2006 Christian Hammond.
+ *
+ * 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.
+ */
+#include <sexy-icon-entry.h>
+#include <string.h>
+#include <gtk/gtk.h>
+
+#define ICON_MARGIN 2
+#define MAX_ICONS 2
+
+#define IS_VALID_ICON_ENTRY_POSITION(pos) \
+ ((pos) == SEXY_ICON_ENTRY_PRIMARY || \
+ (pos) == SEXY_ICON_ENTRY_SECONDARY)
+
+typedef struct
+{
+ GtkImage *icon;
+ gboolean highlight;
+ gboolean hovered;
+ GdkWindow *window;
+
+} SexyIconInfo;
+
+struct _SexyIconEntryPriv
+{
+ SexyIconInfo icons[MAX_ICONS];
+
+ gulong icon_released_id;
+};
+
+enum
+{
+ ICON_PRESSED,
+ ICON_RELEASED,
+ LAST_SIGNAL
+};
+
+static void sexy_icon_entry_class_init(SexyIconEntryClass *klass);
+static void sexy_icon_entry_editable_init(GtkEditableClass *iface);
+static void sexy_icon_entry_init(SexyIconEntry *entry);
+static void sexy_icon_entry_finalize(GObject *obj);
+static void sexy_icon_entry_destroy(GtkObject *obj);
+static void sexy_icon_entry_map(GtkWidget *widget);
+static void sexy_icon_entry_unmap(GtkWidget *widget);
+static void sexy_icon_entry_realize(GtkWidget *widget);
+static void sexy_icon_entry_unrealize(GtkWidget *widget);
+static void sexy_icon_entry_size_request(GtkWidget *widget,
+ GtkRequisition *requisition);
+static void sexy_icon_entry_size_allocate(GtkWidget *widget,
+ GtkAllocation *allocation);
+static gint sexy_icon_entry_expose(GtkWidget *widget, GdkEventExpose *event);
+static gint sexy_icon_entry_enter_notify(GtkWidget *widget,
+ GdkEventCrossing *event);
+static gint sexy_icon_entry_leave_notify(GtkWidget *widget,
+ GdkEventCrossing *event);
+static gint sexy_icon_entry_button_press(GtkWidget *widget,
+ GdkEventButton *event);
+static gint sexy_icon_entry_button_release(GtkWidget *widget,
+ GdkEventButton *event);
+
+static GtkEntryClass *parent_class = NULL;
+static guint signals[LAST_SIGNAL] = {0};
+
+G_DEFINE_TYPE_EXTENDED(SexyIconEntry, sexy_icon_entry, GTK_TYPE_ENTRY,
+ 0,
+ G_IMPLEMENT_INTERFACE(GTK_TYPE_EDITABLE,
+ sexy_icon_entry_editable_init));
+
+static void
+sexy_icon_entry_class_init(SexyIconEntryClass *klass)
+{
+ GObjectClass *gobject_class;
+ GtkObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+ GtkEntryClass *entry_class;
+
+ parent_class = g_type_class_peek_parent(klass);
+
+ gobject_class = G_OBJECT_CLASS(klass);
+ object_class = GTK_OBJECT_CLASS(klass);
+ widget_class = GTK_WIDGET_CLASS(klass);
+ entry_class = GTK_ENTRY_CLASS(klass);
+
+ gobject_class->finalize = sexy_icon_entry_finalize;
+
+ object_class->destroy = sexy_icon_entry_destroy;
+
+ widget_class->map = sexy_icon_entry_map;
+ widget_class->unmap = sexy_icon_entry_unmap;
+ widget_class->realize = sexy_icon_entry_realize;
+ widget_class->unrealize = sexy_icon_entry_unrealize;
+ widget_class->size_request = sexy_icon_entry_size_request;
+ widget_class->size_allocate = sexy_icon_entry_size_allocate;
+ widget_class->expose_event = sexy_icon_entry_expose;
+ widget_class->enter_notify_event = sexy_icon_entry_enter_notify;
+ widget_class->leave_notify_event = sexy_icon_entry_leave_notify;
+ widget_class->button_press_event = sexy_icon_entry_button_press;
+ widget_class->button_release_event = sexy_icon_entry_button_release;
+
+ /**
+ * SexyIconEntry::icon-pressed:
+ * @entry: The entry on which the signal is emitted.
+ * @icon_pos: The position of the clicked icon.
+ * @button: The mouse button clicked.
+ *
+ * The ::icon-pressed signal is emitted when an icon is clicked.
+ */
+ signals[ICON_PRESSED] =
+ g_signal_new("icon_pressed",
+ G_TYPE_FROM_CLASS(gobject_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET(SexyIconEntryClass, icon_pressed),
+ NULL, NULL,
+ gtk_marshal_VOID__INT_INT,
+ G_TYPE_NONE, 2,
+ G_TYPE_INT,
+ G_TYPE_INT);
+
+ /**
+ * SexyIconEntry::icon-released:
+ * @entry: The entry on which the signal is emitted.
+ * @icon_pos: The position of the clicked icon.
+ * @button: The mouse button clicked.
+ *
+ * The ::icon-released signal is emitted on the button release from a
+ * mouse click.
+ */
+ signals[ICON_RELEASED] =
+ g_signal_new("icon_released",
+ G_TYPE_FROM_CLASS(gobject_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET(SexyIconEntryClass, icon_released),
+ NULL, NULL,
+ gtk_marshal_VOID__INT_INT,
+ G_TYPE_NONE, 2,
+ G_TYPE_INT,
+ G_TYPE_INT);
+}
+
+static void
+sexy_icon_entry_editable_init(GtkEditableClass *iface)
+{
+};
+
+static void
+sexy_icon_entry_init(SexyIconEntry *entry)
+{
+ entry->priv = g_new0(SexyIconEntryPriv, 1);
+}
+
+static void
+sexy_icon_entry_finalize(GObject *obj)
+{
+ SexyIconEntry *entry;
+
+ g_return_if_fail(obj != NULL);
+ g_return_if_fail(SEXY_IS_ICON_ENTRY(obj));
+
+ entry = SEXY_ICON_ENTRY(obj);
+
+ g_free(entry->priv);
+
+ if (G_OBJECT_CLASS(parent_class)->finalize)
+ G_OBJECT_CLASS(parent_class)->finalize(obj);
+}
+
+static void
+sexy_icon_entry_destroy(GtkObject *obj)
+{
+ SexyIconEntry *entry;
+
+ entry = SEXY_ICON_ENTRY(obj);
+
+ sexy_icon_entry_set_icon(entry, SEXY_ICON_ENTRY_PRIMARY, NULL);
+ sexy_icon_entry_set_icon(entry, SEXY_ICON_ENTRY_SECONDARY, NULL);
+
+ if (GTK_OBJECT_CLASS(parent_class)->destroy)
+ GTK_OBJECT_CLASS(parent_class)->destroy(obj);
+}
+
+static void
+sexy_icon_entry_map(GtkWidget *widget)
+{
+ if (GTK_WIDGET_REALIZED(widget) && !GTK_WIDGET_MAPPED(widget))
+ {
+ SexyIconEntry *entry = SEXY_ICON_ENTRY(widget);
+ int i;
+
+ GTK_WIDGET_CLASS(parent_class)->map(widget);
+
+ for (i = 0; i < MAX_ICONS; i++)
+ {
+ if (entry->priv->icons[i].icon != NULL)
+ gdk_window_show(entry->priv->icons[i].window);
+ }
+ }
+}
+
+static void
+sexy_icon_entry_unmap(GtkWidget *widget)
+{
+ if (GTK_WIDGET_MAPPED(widget))
+ {
+ SexyIconEntry *entry = SEXY_ICON_ENTRY(widget);
+ int i;
+
+ for (i = 0; i < MAX_ICONS; i++)
+ {
+ if (entry->priv->icons[i].icon != NULL)
+ gdk_window_hide(entry->priv->icons[i].window);
+ }
+
+ GTK_WIDGET_CLASS(parent_class)->unmap(widget);
+ }
+}
+
+static gint
+get_icon_width(SexyIconEntry *entry, SexyIconEntryPosition icon_pos)
+{
+ GtkRequisition requisition;
+ gint menu_icon_width;
+ gint width;
+ SexyIconInfo *icon_info = &entry->priv->icons[icon_pos];
+
+ if (icon_info->icon == NULL)
+ return 0;
+
+ gtk_widget_size_request(GTK_WIDGET(icon_info->icon), &requisition);
+ gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &menu_icon_width, NULL);
+
+ width = MAX(requisition.width, menu_icon_width);
+
+ return width;
+}
+
+static void
+get_borders(SexyIconEntry *entry, gint *xborder, gint *yborder)
+{
+ GtkWidget *widget = GTK_WIDGET(entry);
+ gint focus_width;
+ gboolean interior_focus;
+
+ gtk_widget_style_get(widget,
+ "interior-focus", &interior_focus,
+ "focus-line-width", &focus_width,
+ NULL);
+
+ if (gtk_entry_get_has_frame(GTK_ENTRY(entry)))
+ {
+ *xborder = widget->style->xthickness;
+ *yborder = widget->style->ythickness;
+ }
+ else
+ {
+ *xborder = 0;
+ *yborder = 0;
+ }
+
+ if (!interior_focus)
+ {
+ *xborder += focus_width;
+ *yborder += focus_width;
+ }
+}
+
+static void
+get_text_area_size(SexyIconEntry *entry, GtkAllocation *alloc)
+{
+ GtkWidget *widget = GTK_WIDGET(entry);
+ GtkRequisition requisition;
+ gint xborder, yborder;
+
+ gtk_widget_get_child_requisition(widget, &requisition);
+ get_borders(entry, &xborder, &yborder);
+
+ alloc->x = xborder;
+ alloc->y = yborder;
+ alloc->width = widget->allocation.width - xborder * 2;
+ alloc->height = requisition.height - yborder * 2;
+}
+
+static void
+get_icon_allocation(SexyIconEntry *icon_entry,
+ gboolean left,
+ GtkAllocation *widget_alloc,
+ GtkAllocation *text_area_alloc,
+ GtkAllocation *allocation,
+ SexyIconEntryPosition *icon_pos)
+{
+ gboolean rtl;
+
+ rtl = (gtk_widget_get_direction(GTK_WIDGET(icon_entry)) ==
+ GTK_TEXT_DIR_RTL);
+
+ if (left)
+ *icon_pos = (rtl ? SEXY_ICON_ENTRY_SECONDARY : SEXY_ICON_ENTRY_PRIMARY);
+ else
+ *icon_pos = (rtl ? SEXY_ICON_ENTRY_PRIMARY : SEXY_ICON_ENTRY_SECONDARY);
+
+ allocation->y = text_area_alloc->y;
+ allocation->width = get_icon_width(icon_entry, *icon_pos);
+ allocation->height = text_area_alloc->height;
+
+ if (left)
+ allocation->x = text_area_alloc->x + ICON_MARGIN;
+ else
+ {
+ allocation->x = text_area_alloc->x + text_area_alloc->width -
+ allocation->width - ICON_MARGIN;
+ }
+}
+
+static void
+sexy_icon_entry_realize(GtkWidget *widget)
+{
+ SexyIconEntry *entry = SEXY_ICON_ENTRY(widget);
+ GdkWindowAttr attributes;
+ gint attributes_mask;
+ int i;
+
+ GTK_WIDGET_CLASS(parent_class)->realize(widget);
+
+ attributes.x = 0;
+ attributes.y = 0;
+ attributes.width = 1;
+ attributes.height = 1;
+ attributes.window_type = GDK_WINDOW_CHILD;
+ attributes.wclass = GDK_INPUT_OUTPUT;
+ attributes.visual = gtk_widget_get_visual(widget);
+ attributes.colormap = gtk_widget_get_colormap(widget);
+ attributes.event_mask = gtk_widget_get_events(widget);
+ attributes.event_mask |=
+ (GDK_EXPOSURE_MASK
+ | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
+ | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
+
+ attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
+
+ for (i = 0; i < MAX_ICONS; i++)
+ {
+ SexyIconInfo *icon_info;
+
+ icon_info = &entry->priv->icons[i];
+ icon_info->window = gdk_window_new(widget->window, &attributes,
+ attributes_mask);
+ gdk_window_set_user_data(icon_info->window, widget);
+
+ gdk_window_set_background(icon_info->window,
+ &widget->style->base[GTK_WIDGET_STATE(widget)]);
+ }
+
+ gtk_widget_queue_resize(widget);
+}
+
+static void
+sexy_icon_entry_unrealize(GtkWidget *widget)
+{
+ SexyIconEntry *entry = SEXY_ICON_ENTRY(widget);
+ int i;
+
+ GTK_WIDGET_CLASS(parent_class)->unrealize(widget);
+
+ for (i = 0; i < MAX_ICONS; i++)
+ {
+ SexyIconInfo *icon_info = &entry->priv->icons[i];
+
+ gdk_window_destroy(icon_info->window);
+ icon_info->window = NULL;
+ }
+}
+
+static void
+sexy_icon_entry_size_request(GtkWidget *widget, GtkRequisition *requisition)
+{
+ GtkEntry *gtkentry;
+ SexyIconEntry *entry;
+ gint icon_widths = 0;
+ int i;
+
+ gtkentry = GTK_ENTRY(widget);
+ entry = SEXY_ICON_ENTRY(widget);
+
+ for (i = 0; i < MAX_ICONS; i++)
+ {
+ int icon_width = get_icon_width(entry, i);
+
+ if (icon_width > 0)
+ icon_widths += icon_width + ICON_MARGIN;
+ }
+
+ GTK_WIDGET_CLASS(parent_class)->size_request(widget, requisition);
+
+ if (icon_widths > requisition->width)
+ requisition->width += icon_widths;
+}
+
+static void
+place_windows(SexyIconEntry *icon_entry, GtkAllocation *widget_alloc)
+{
+ SexyIconEntryPosition left_icon_pos;
+ SexyIconEntryPosition right_icon_pos;
+ GtkAllocation left_icon_alloc;
+ GtkAllocation right_icon_alloc;
+ GtkAllocation text_area_alloc;
+
+ get_text_area_size(icon_entry, &text_area_alloc);
+ get_icon_allocation(icon_entry, TRUE, widget_alloc, &text_area_alloc,
+ &left_icon_alloc, &left_icon_pos);
+ get_icon_allocation(icon_entry, FALSE, widget_alloc, &text_area_alloc,
+ &right_icon_alloc, &right_icon_pos);
+
+ if (left_icon_alloc.width > 0)
+ {
+ text_area_alloc.x = left_icon_alloc.x + left_icon_alloc.width +
+ ICON_MARGIN;
+ }
+
+ if (right_icon_alloc.width > 0)
+ text_area_alloc.width -= right_icon_alloc.width + ICON_MARGIN;
+
+ text_area_alloc.width -= text_area_alloc.x;
+
+ gdk_window_move_resize(icon_entry->priv->icons[left_icon_pos].window,
+ left_icon_alloc.x, left_icon_alloc.y,
+ left_icon_alloc.width, left_icon_alloc.height);
+
+ gdk_window_move_resize(icon_entry->priv->icons[right_icon_pos].window,
+ right_icon_alloc.x, right_icon_alloc.y,
+ right_icon_alloc.width, right_icon_alloc.height);
+
+ gdk_window_move_resize(GTK_ENTRY(icon_entry)->text_area,
+ text_area_alloc.x, text_area_alloc.y,
+ text_area_alloc.width, text_area_alloc.height);
+}
+
+static void
+sexy_icon_entry_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
+{
+ g_return_if_fail(SEXY_IS_ICON_ENTRY(widget));
+ g_return_if_fail(allocation != NULL);
+
+ widget->allocation = *allocation;
+
+ GTK_WIDGET_CLASS(parent_class)->size_allocate(widget, allocation);
+
+ if (GTK_WIDGET_REALIZED(widget))
+ place_windows(SEXY_ICON_ENTRY(widget), allocation);
+}
+
+static GdkPixbuf *
+get_pixbuf_from_icon(SexyIconEntry *entry, SexyIconEntryPosition icon_pos)
+{
+ GdkPixbuf *pixbuf = NULL;
+ gchar *stock_id;
+ SexyIconInfo *icon_info = &entry->priv->icons[icon_pos];
+ GtkIconSize size;
+
+ switch (gtk_image_get_storage_type(GTK_IMAGE(icon_info->icon)))
+ {
+ case GTK_IMAGE_PIXBUF:
+ pixbuf = gtk_image_get_pixbuf(GTK_IMAGE(icon_info->icon));
+ g_object_ref(pixbuf);
+ break;
+
+ case GTK_IMAGE_STOCK:
+ gtk_image_get_stock(GTK_IMAGE(icon_info->icon), &stock_id, &size);
+ pixbuf = gtk_widget_render_icon(GTK_WIDGET(entry),
+ stock_id, size, NULL);
+ break;
+
+ default:
+ return NULL;
+ }
+
+ return pixbuf;
+}
+
+/* Kudos to the gnome-panel guys. */
+static void
+colorshift_pixbuf(GdkPixbuf *dest, GdkPixbuf *src, int shift)
+{
+ gint i, j;
+ gint width, height, has_alpha, src_rowstride, dest_rowstride;
+ guchar *target_pixels;
+ guchar *original_pixels;
+ guchar *pix_src;
+ guchar *pix_dest;
+ int val;
+ guchar r, g, b;
+
+ has_alpha = gdk_pixbuf_get_has_alpha(src);
+ width = gdk_pixbuf_get_width(src);
+ height = gdk_pixbuf_get_height(src);
+ src_rowstride = gdk_pixbuf_get_rowstride(src);
+ dest_rowstride = gdk_pixbuf_get_rowstride(dest);
+ original_pixels = gdk_pixbuf_get_pixels(src);
+ target_pixels = gdk_pixbuf_get_pixels(dest);
+
+ for (i = 0; i < height; i++)
+ {
+ pix_dest = target_pixels + i * dest_rowstride;
+ pix_src = original_pixels + i * src_rowstride;
+
+ for (j = 0; j < width; j++)
+ {
+ r = *(pix_src++);
+ g = *(pix_src++);
+ b = *(pix_src++);
+
+ val = r + shift;
+ *(pix_dest++) = CLAMP(val, 0, 255);
+
+ val = g + shift;
+ *(pix_dest++) = CLAMP(val, 0, 255);
+
+ val = b + shift;
+ *(pix_dest++) = CLAMP(val, 0, 255);
+
+ if (has_alpha)
+ *(pix_dest++) = *(pix_src++);
+ }
+ }
+}
+
+static void
+draw_icon(GtkWidget *widget, SexyIconEntryPosition icon_pos)
+{
+ SexyIconEntry *entry = SEXY_ICON_ENTRY(widget);
+ SexyIconInfo *icon_info = &entry->priv->icons[icon_pos];
+ GdkPixbuf *pixbuf;
+ gint x, y, width, height;
+
+ if (icon_info->icon == NULL || !GTK_WIDGET_REALIZED(widget))
+ return;
+
+ if ((pixbuf = get_pixbuf_from_icon(entry, icon_pos)) == NULL)
+ return;
+
+ gdk_drawable_get_size(icon_info->window, &width, &height);
+
+ if (width == 1 || height == 1)
+ {
+ /*
+ * size_allocate hasn't been called yet. These are the default values.
+ */
+ return;
+ }
+
+ if (gdk_pixbuf_get_height(pixbuf) > height)
+ {
+ GdkPixbuf *temp_pixbuf;
+ int scale;
+
+ scale = height - (2 * ICON_MARGIN);
+
+ temp_pixbuf = gdk_pixbuf_scale_simple(pixbuf, scale, scale,
+ GDK_INTERP_BILINEAR);
+
+ g_object_unref(pixbuf);
+
+ pixbuf = temp_pixbuf;
+ }
+
+ x = (width - gdk_pixbuf_get_width(pixbuf)) / 2;
+ y = (height - gdk_pixbuf_get_height(pixbuf)) / 2;
+
+ if (icon_info->hovered)
+ {
+ GdkPixbuf *temp_pixbuf;
+
+ temp_pixbuf = gdk_pixbuf_copy(pixbuf);
+
+ colorshift_pixbuf(temp_pixbuf, pixbuf, 30);
+
+ g_object_unref(pixbuf);
+
+ pixbuf = temp_pixbuf;
+ }
+
+ gdk_draw_pixbuf(icon_info->window, widget->style->black_gc, pixbuf,
+ 0, 0, x, y, -1, -1,
+ GDK_RGB_DITHER_NORMAL, 0, 0);
+
+ g_object_unref(pixbuf);
+}
+
+static gint
+sexy_icon_entry_expose(GtkWidget *widget, GdkEventExpose *event)
+{
+ SexyIconEntry *entry;
+
+ g_return_val_if_fail(SEXY_IS_ICON_ENTRY(widget), FALSE);
+ g_return_val_if_fail(event != NULL, FALSE);
+
+ entry = SEXY_ICON_ENTRY(widget);
+
+ if (GTK_WIDGET_DRAWABLE(widget))
+ {
+ gboolean found = FALSE;
+ int i;
+
+ for (i = 0; i < MAX_ICONS && !found; i++)
+ {
+ SexyIconInfo *icon_info = &entry->priv->icons[i];
+
+ if (event->window == icon_info->window)
+ {
+ gint width;
+ GtkAllocation text_area_alloc;
+
+ get_text_area_size(entry, &text_area_alloc);
+ gdk_drawable_get_size(icon_info->window, &width, NULL);
+
+ gtk_paint_flat_box(widget->style, icon_info->window,
+ GTK_WIDGET_STATE(widget), GTK_SHADOW_NONE,
+ NULL, widget, "entry_bg",
+ 0, 0, width, text_area_alloc.height);
+
+ draw_icon(widget, i);
+
+ found = TRUE;
+ }
+ }
+
+ if (!found)
+ GTK_WIDGET_CLASS(parent_class)->expose_event(widget, event);
+ }
+
+ return FALSE;
+}
+
+static void
+update_icon(GObject *obj, GParamSpec *param, SexyIconEntry *entry)
+{
+ if (param != NULL)
+ {
+ const char *name = g_param_spec_get_name(param);
+
+ if (strcmp(name, "pixbuf") && strcmp(name, "stock") &&
+ strcmp(name, "image") && strcmp(name, "pixmap") &&
+ strcmp(name, "icon_set") && strcmp(name, "pixbuf_animation"))
+ {
+ return;
+ }
+ }
+
+ gtk_widget_queue_resize(GTK_WIDGET(entry));
+}
+
+static gint
+sexy_icon_entry_enter_notify(GtkWidget *widget, GdkEventCrossing *event)
+{
+ SexyIconEntry *entry = SEXY_ICON_ENTRY(widget);
+ int i;
+
+ for (i = 0; i < MAX_ICONS; i++)
+ {
+ if (event->window == entry->priv->icons[i].window)
+ {
+ if (sexy_icon_entry_get_icon_highlight(entry, i))
+ {
+ entry->priv->icons[i].hovered = TRUE;
+
+ update_icon(NULL, NULL, entry);
+
+ break;
+ }
+ }
+ }
+
+ return FALSE;
+}
+
+static gint
+sexy_icon_entry_leave_notify(GtkWidget *widget, GdkEventCrossing *event)
+{
+ SexyIconEntry *entry = SEXY_ICON_ENTRY(widget);
+ int i;
+
+ for (i = 0; i < MAX_ICONS; i++)
+ {
+ if (event->window == entry->priv->icons[i].window)
+ {
+ if (sexy_icon_entry_get_icon_highlight(entry, i))
+ {
+ entry->priv->icons[i].hovered = FALSE;
+
+ update_icon(NULL, NULL, entry);
+
+ break;
+ }
+ }
+ }
+
+ return FALSE;
+}
+
+static gint
+sexy_icon_entry_button_press(GtkWidget *widget, GdkEventButton *event)
+{
+ SexyIconEntry *entry = SEXY_ICON_ENTRY(widget);
+ int i;
+
+ for (i = 0; i < MAX_ICONS; i++)
+ {
+ if (event->window == entry->priv->icons[i].window)
+ {
+ if (event->button == 1 &&
+ sexy_icon_entry_get_icon_highlight(entry, i))
+ {
+ entry->priv->icons[i].hovered = FALSE;
+
+ update_icon(NULL, NULL, entry);
+ }
+
+ g_signal_emit(entry, signals[ICON_PRESSED], 0, i, event->button);
+
+ return TRUE;
+ }
+ }
+
+ if (GTK_WIDGET_CLASS(parent_class)->button_press_event)
+ return GTK_WIDGET_CLASS(parent_class)->button_press_event(widget,
+ event);
+
+ return FALSE;
+}
+
+static gint
+sexy_icon_entry_button_release(GtkWidget *widget, GdkEventButton *event)
+{
+ SexyIconEntry *entry = SEXY_ICON_ENTRY(widget);
+ int i;
+
+ for (i = 0; i < MAX_ICONS; i++)
+ {
+ GdkWindow *icon_window = entry->priv->icons[i].window;
+
+ if (event->window == icon_window)
+ {
+ int width, height;
+ gdk_drawable_get_size(icon_window, &width, &height);
+
+ if (event->button == 1 &&
+ sexy_icon_entry_get_icon_highlight(entry, i) &&
+ event->x >= 0 && event->y >= 0 &&
+ event->x <= width && event->y <= height)
+ {
+ entry->priv->icons[i].hovered = TRUE;
+
+ update_icon(NULL, NULL, entry);
+ }
+
+ g_signal_emit(entry, signals[ICON_RELEASED], 0, i, event->button);
+
+ return TRUE;
+ }
+ }
+
+ if (GTK_WIDGET_CLASS(parent_class)->button_release_event)
+ return GTK_WIDGET_CLASS(parent_class)->button_release_event(widget,
+ event);
+
+ return FALSE;
+}
+
+/**
+ * sexy_icon_entry_new
+ *
+ * Creates a new SexyIconEntry widget.
+ *
+ * Returns a new #SexyIconEntry.
+ */
+GtkWidget *
+sexy_icon_entry_new(void)
+{
+ return GTK_WIDGET(g_object_new(SEXY_TYPE_ICON_ENTRY, NULL));
+}
+
+/**
+ * sexy_icon_entry_set_icon
+ * @entry: A #SexyIconEntry.
+ * @position: Icon position.
+ * @icon: A #GtkImage to set as the icon.
+ *
+ * Sets the icon shown in the entry
+ */
+void
+sexy_icon_entry_set_icon(SexyIconEntry *entry, SexyIconEntryPosition icon_pos,
+ GtkImage *icon)
+{
+ SexyIconInfo *icon_info;
+
+ g_return_if_fail(entry != NULL);
+ g_return_if_fail(SEXY_IS_ICON_ENTRY(entry));
+ g_return_if_fail(IS_VALID_ICON_ENTRY_POSITION(icon_pos));
+ g_return_if_fail(icon == NULL || GTK_IS_IMAGE(icon));
+
+ icon_info = &entry->priv->icons[icon_pos];
+
+ if (icon == icon_info->icon)
+ return;
+
+ if (icon_pos == SEXY_ICON_ENTRY_SECONDARY &&
+ entry->priv->icon_released_id != 0)
+ {
+ g_signal_handler_disconnect(entry, entry->priv->icon_released_id);
+ entry->priv->icon_released_id = 0;
+ }
+
+ if (icon == NULL)
+ {
+ if (icon_info->icon != NULL)
+ {
+ gtk_widget_destroy(GTK_WIDGET(icon_info->icon));
+ icon_info->icon = NULL;
+
+ /*
+ * Explicitly check, as the pointer may become invalidated
+ * during destruction.
+ */
+ if (icon_info->window != NULL && GDK_IS_WINDOW(icon_info->window))
+ gdk_window_hide(icon_info->window);
+ }
+ }
+ else
+ {
+ if (icon_info->window != NULL && icon_info->icon == NULL)
+ gdk_window_show(icon_info->window);
+
+ g_signal_connect(G_OBJECT(icon), "notify",
+ G_CALLBACK(update_icon), entry);
+
+ icon_info->icon = icon;
+ g_object_ref(icon);
+ }
+
+ update_icon(NULL, NULL, entry);
+}
+
+/**
+ * sexy_icon_entry_set_icon_highlight
+ * @entry: A #SexyIconEntry;
+ * @position: Icon position.
+ * @highlight: TRUE if the icon should highlight on mouse-over
+ *
+ * Determines whether the icon will highlight on mouse-over.
+ */
+void
+sexy_icon_entry_set_icon_highlight(SexyIconEntry *entry,
+ SexyIconEntryPosition icon_pos,
+ gboolean highlight)
+{
+ SexyIconInfo *icon_info;
+
+ g_return_if_fail(entry != NULL);
+ g_return_if_fail(SEXY_IS_ICON_ENTRY(entry));
+ g_return_if_fail(IS_VALID_ICON_ENTRY_POSITION(icon_pos));
+
+ icon_info = &entry->priv->icons[icon_pos];
+
+ if (icon_info->highlight == highlight)
+ return;
+
+ icon_info->highlight = highlight;
+}
+
+/**
+ * sexy_icon_entry_get_icon
+ * @entry: A #SexyIconEntry.
+ * @position: Icon position.
+ *
+ * Retrieves the image used for the icon
+ *
+ * Returns: A #GtkImage.
+ */
+GtkImage *
+sexy_icon_entry_get_icon(const SexyIconEntry *entry,
+ SexyIconEntryPosition icon_pos)
+{
+ g_return_val_if_fail(entry != NULL, NULL);
+ g_return_val_if_fail(SEXY_IS_ICON_ENTRY(entry), NULL);
+ g_return_val_if_fail(IS_VALID_ICON_ENTRY_POSITION(icon_pos), NULL);
+
+ return entry->priv->icons[icon_pos].icon;
+}
+
+/**
+ * sexy_icon_entry_get_icon_highlight
+ * @entry: A #SexyIconEntry.
+ * @position: Icon position.
+ *
+ * Retrieves whether entry will highlight the icon on mouseover.
+ *
+ * Returns: TRUE if icon highlights.
+ */
+gboolean
+sexy_icon_entry_get_icon_highlight(const SexyIconEntry *entry,
+ SexyIconEntryPosition icon_pos)
+{
+ g_return_val_if_fail(entry != NULL, FALSE);
+ g_return_val_if_fail(SEXY_IS_ICON_ENTRY(entry), FALSE);
+ g_return_val_if_fail(IS_VALID_ICON_ENTRY_POSITION(icon_pos), FALSE);
+
+ return entry->priv->icons[icon_pos].highlight;
+}
+
+static void
+clear_button_clicked_cb(SexyIconEntry *icon_entry,
+ SexyIconEntryPosition icon_pos,
+ int button)
+{
+ if (icon_pos != SEXY_ICON_ENTRY_SECONDARY || button != 1)
+ return;
+
+ gtk_entry_set_text(GTK_ENTRY(icon_entry), "");
+}
+
+/**
+ * sexy_icon_entry_add_clear_button
+ * @icon_entry: A #SexyIconEntry.
+ *
+ * A convenience function to add a clear button to the end of the entry.
+ * This is useful for search boxes.
+ */
+void
+sexy_icon_entry_add_clear_button(SexyIconEntry *icon_entry)
+{
+ GtkWidget *icon;
+
+ g_return_if_fail(icon_entry != NULL);
+ g_return_if_fail(SEXY_IS_ICON_ENTRY(icon_entry));
+
+ icon = gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_MENU);
+ gtk_widget_show(icon);
+ sexy_icon_entry_set_icon(SEXY_ICON_ENTRY(icon_entry),
+ SEXY_ICON_ENTRY_SECONDARY,
+ GTK_IMAGE(icon));
+ sexy_icon_entry_set_icon_highlight(SEXY_ICON_ENTRY(icon_entry),
+ SEXY_ICON_ENTRY_SECONDARY, TRUE);
+
+ if (icon_entry->priv->icon_released_id != 0)
+ {
+ g_signal_handler_disconnect(icon_entry,
+ icon_entry->priv->icon_released_id);
+ }
+
+ icon_entry->priv->icon_released_id =
+ g_signal_connect(G_OBJECT(icon_entry), "icon_released",
+ G_CALLBACK(clear_button_clicked_cb), NULL);
+}
+
+GType
+sexy_icon_entry_position_get_type (void)
+{
+ static GType etype = 0;
+ if (etype == 0) {
+ static const GEnumValue values[] = {
+ { SEXY_ICON_ENTRY_PRIMARY, "SEXY_ICON_ENTRY_PRIMARY", "primary" },
+ { SEXY_ICON_ENTRY_SECONDARY, "SEXY_ICON_ENTRY_SECONDARY", "secondary" },
+ { 0, NULL, NULL }
+ };
+ etype = g_enum_register_static ("SexyIconEntryPosition", values);
+ }
+ return etype;
+}
+
diff --git a/toolkit/src/sugar/sexy-icon-entry.h b/toolkit/src/sugar/sexy-icon-entry.h
new file mode 100644
index 0000000..eb83fed
--- /dev/null
+++ b/toolkit/src/sugar/sexy-icon-entry.h
@@ -0,0 +1,104 @@
+/*
+ * @file libsexy/sexy-icon-entry.h Entry widget
+ *
+ * @Copyright (C) 2004-2006 Christian Hammond.
+ *
+ * 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.
+ */
+#ifndef _SEXY_ICON_ENTRY_H_
+#define _SEXY_ICON_ENTRY_H_
+
+typedef struct _SexyIconEntry SexyIconEntry;
+typedef struct _SexyIconEntryClass SexyIconEntryClass;
+typedef struct _SexyIconEntryPriv SexyIconEntryPriv;
+
+#include <gtk/gtkentry.h>
+#include <gtk/gtkimage.h>
+
+#define SEXY_TYPE_ICON_ENTRY (sexy_icon_entry_get_type())
+#define SEXY_ICON_ENTRY(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), SEXY_TYPE_ICON_ENTRY, SexyIconEntry))
+#define SEXY_ICON_ENTRY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), SEXY_TYPE_ICON_ENTRY, SexyIconEntryClass))
+#define SEXY_IS_ICON_ENTRY(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), SEXY_TYPE_ICON_ENTRY))
+#define SEXY_IS_ICON_ENTRY_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), SEXY_TYPE_ICON_ENTRY))
+#define SEXY_ICON_ENTRY_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), SEXY_TYPE_ICON_ENTRY, SexyIconEntryClass))
+
+typedef enum
+{
+ SEXY_ICON_ENTRY_PRIMARY,
+ SEXY_ICON_ENTRY_SECONDARY
+
+} SexyIconEntryPosition;
+
+GType sexy_icon_entry_position_get_type(void);
+#define SEXY_TYPE_ICON_ENTRY_POSITION (sexy_icon_entry_position_get_type())
+
+struct _SexyIconEntry
+{
+ GtkEntry parent_object;
+
+ SexyIconEntryPriv *priv;
+
+ void (*gtk_reserved1)(void);
+ void (*gtk_reserved2)(void);
+ void (*gtk_reserved3)(void);
+ void (*gtk_reserved4)(void);
+};
+
+struct _SexyIconEntryClass
+{
+ GtkEntryClass parent_class;
+
+ /* Signals */
+ void (*icon_pressed)(SexyIconEntry *entry, SexyIconEntryPosition icon_pos,
+ int button);
+ void (*icon_released)(SexyIconEntry *entry, SexyIconEntryPosition icon_pos,
+ int button);
+
+ void (*gtk_reserved1)(void);
+ void (*gtk_reserved2)(void);
+ void (*gtk_reserved3)(void);
+ void (*gtk_reserved4)(void);
+};
+
+G_BEGIN_DECLS
+
+GType sexy_icon_entry_get_type(void);
+
+GtkWidget *sexy_icon_entry_new(void);
+
+void sexy_icon_entry_set_icon(SexyIconEntry *entry,
+ SexyIconEntryPosition position,
+ GtkImage *icon);
+
+void sexy_icon_entry_set_icon_highlight(SexyIconEntry *entry,
+ SexyIconEntryPosition position,
+ gboolean highlight);
+
+GtkImage *sexy_icon_entry_get_icon(const SexyIconEntry *entry,
+ SexyIconEntryPosition position);
+
+gboolean sexy_icon_entry_get_icon_highlight(const SexyIconEntry *entry,
+ SexyIconEntryPosition position);
+void sexy_icon_entry_add_clear_button(SexyIconEntry *icon_entry);
+
+G_END_DECLS
+
+#endif /* _SEXY_ICON_ENTRY_H_ */
diff --git a/toolkit/src/sugar/sugar-address-entry.c b/toolkit/src/sugar/sugar-address-entry.c
new file mode 100644
index 0000000..0309880
--- /dev/null
+++ b/toolkit/src/sugar/sugar-address-entry.c
@@ -0,0 +1,576 @@
+/*
+ * Copyright (C) 2006-2007 Red Hat, Inc.
+ *
+ * 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.
+ */
+
+#include <math.h>
+#include <gtk/gtkentry.h>
+
+#include "sugar-address-entry.h"
+
+enum {
+ PROP_0,
+ PROP_PROGRESS
+};
+
+typedef enum {
+ CURSOR_STANDARD,
+ CURSOR_DND
+} CursorType;
+
+static void _gtk_entry_effective_inner_border (GtkEntry *entry,
+ GtkBorder *border);
+static void get_text_area_size (GtkEntry *entry,
+ gint *x,
+ gint *y,
+ gint *width,
+ gint *height);
+
+G_DEFINE_TYPE(SugarAddressEntry, sugar_address_entry, GTK_TYPE_ENTRY)
+
+static GQuark quark_inner_border = 0;
+static const GtkBorder default_inner_border = { 2, 2, 2, 2 };
+
+static void
+draw_insertion_cursor (GtkEntry *entry,
+ GdkRectangle *cursor_location,
+ gboolean is_primary,
+ PangoDirection direction,
+ gboolean draw_arrow)
+{
+ GtkWidget *widget = GTK_WIDGET (entry);
+ GtkTextDirection text_dir;
+
+ if (direction == PANGO_DIRECTION_LTR)
+ text_dir = GTK_TEXT_DIR_LTR;
+ else
+ text_dir = GTK_TEXT_DIR_RTL;
+
+ gtk_draw_insertion_cursor (widget, entry->text_area, NULL,
+ cursor_location,
+ is_primary, text_dir, draw_arrow);
+}
+
+static void
+gtk_entry_get_pixel_ranges (GtkEntry *entry,
+ gint **ranges,
+ gint *n_ranges)
+{
+ gint start_char, end_char;
+
+ if (gtk_editable_get_selection_bounds (GTK_EDITABLE (entry), &start_char, &end_char))
+ {
+ //PangoLayout *layout = gtk_entry_ensure_layout (entry, TRUE);
+ PangoLayout *layout = gtk_entry_get_layout (entry);
+ PangoLayoutLine *line = pango_layout_get_lines (layout)->data;
+ const char *text = pango_layout_get_text (layout);
+ gint start_index = g_utf8_offset_to_pointer (text, start_char) - text;
+ gint end_index = g_utf8_offset_to_pointer (text, end_char) - text;
+ gint real_n_ranges, i;
+
+ pango_layout_line_get_x_ranges (line, start_index, end_index, ranges, &real_n_ranges);
+
+ if (ranges)
+ {
+ gint *r = *ranges;
+
+ for (i = 0; i < real_n_ranges; ++i)
+ {
+ r[2 * i + 1] = (r[2 * i + 1] - r[2 * i]) / PANGO_SCALE;
+ r[2 * i] = r[2 * i] / PANGO_SCALE;
+ }
+ }
+
+ if (n_ranges)
+ *n_ranges = real_n_ranges;
+ }
+ else
+ {
+ if (n_ranges)
+ *n_ranges = 0;
+ if (ranges)
+ *ranges = NULL;
+ }
+}
+
+static void
+gtk_entry_get_cursor_locations (GtkEntry *entry,
+ CursorType type,
+ gint *strong_x,
+ gint *weak_x)
+{
+ if (!entry->visible && !entry->invisible_char)
+ {
+ if (strong_x)
+ *strong_x = 0;
+
+ if (weak_x)
+ *weak_x = 0;
+ }
+ else
+ {
+ //PangoLayout *layout = gtk_entry_ensure_layout (entry, TRUE);
+ PangoLayout *layout = gtk_entry_get_layout (entry);
+ const gchar *text = pango_layout_get_text (layout);
+ PangoRectangle strong_pos, weak_pos;
+ gint index;
+
+ if (type == CURSOR_STANDARD)
+ {
+ index = g_utf8_offset_to_pointer (text, entry->current_pos + entry->preedit_cursor) - text;
+ }
+ else /* type == CURSOR_DND */
+ {
+ index = g_utf8_offset_to_pointer (text, entry->dnd_position) - text;
+
+ if (entry->dnd_position > entry->current_pos)
+ {
+ if (entry->visible)
+ index += entry->preedit_length;
+ else
+ {
+ gint preedit_len_chars = g_utf8_strlen (text, -1) - entry->text_length;
+ index += preedit_len_chars * g_unichar_to_utf8 (entry->invisible_char, NULL);
+ }
+ }
+ }
+
+ pango_layout_get_cursor_pos (layout, index, &strong_pos, &weak_pos);
+
+ if (strong_x)
+ *strong_x = strong_pos.x / PANGO_SCALE;
+
+ if (weak_x)
+ *weak_x = weak_pos.x / PANGO_SCALE;
+ }
+}
+
+static void
+gtk_entry_draw_cursor (GtkEntry *entry,
+ CursorType type)
+{
+ GdkKeymap *keymap = gdk_keymap_get_for_display (gtk_widget_get_display (GTK_WIDGET (entry)));
+ PangoDirection keymap_direction = gdk_keymap_get_direction (keymap);
+
+ if (GTK_WIDGET_DRAWABLE (entry))
+ {
+ GtkWidget *widget = GTK_WIDGET (entry);
+ GdkRectangle cursor_location;
+ gboolean split_cursor;
+
+ GtkBorder inner_border;
+ gint xoffset;
+ gint strong_x, weak_x;
+ gint text_area_height;
+ PangoDirection dir1 = PANGO_DIRECTION_NEUTRAL;
+ PangoDirection dir2 = PANGO_DIRECTION_NEUTRAL;
+ gint x1 = 0;
+ gint x2 = 0;
+
+ _gtk_entry_effective_inner_border (entry, &inner_border);
+
+ xoffset = inner_border.left - entry->scroll_offset;
+
+ gdk_drawable_get_size (entry->text_area, NULL, &text_area_height);
+
+ gtk_entry_get_cursor_locations (entry, type, &strong_x, &weak_x);
+
+ g_object_get (gtk_widget_get_settings (widget),
+ "gtk-split-cursor", &split_cursor,
+ NULL);
+
+ dir1 = entry->resolved_dir;
+
+ if (split_cursor)
+ {
+ x1 = strong_x;
+
+ if (weak_x != strong_x)
+ {
+ dir2 = (entry->resolved_dir == PANGO_DIRECTION_LTR) ? PANGO_DIRECTION_RTL : PANGO_DIRECTION_LTR;
+ x2 = weak_x;
+ }
+ }
+ else
+ {
+ if (keymap_direction == entry->resolved_dir)
+ x1 = strong_x;
+ else
+ x1 = weak_x;
+ }
+
+ cursor_location.x = xoffset + x1;
+ cursor_location.y = inner_border.top;
+ cursor_location.width = 0;
+ cursor_location.height = text_area_height - inner_border.top - inner_border.bottom;
+
+ draw_insertion_cursor (entry,
+ &cursor_location, TRUE, dir1,
+ dir2 != PANGO_DIRECTION_NEUTRAL);
+
+ if (dir2 != PANGO_DIRECTION_NEUTRAL)
+ {
+ cursor_location.x = xoffset + x2;
+ draw_insertion_cursor (entry,
+ &cursor_location, FALSE, dir2,
+ TRUE);
+ }
+ }
+}
+
+static void
+get_layout_position (GtkEntry *entry,
+ gint *x,
+ gint *y)
+{
+ PangoLayout *layout;
+ PangoRectangle logical_rect;
+ gint area_width, area_height;
+ GtkBorder inner_border;
+ gint y_pos;
+ PangoLayoutLine *line;
+
+// layout = gtk_entry_ensure_layout (entry, TRUE);
+ layout = gtk_entry_get_layout(entry);
+
+ get_text_area_size (entry, NULL, NULL, &area_width, &area_height);
+ _gtk_entry_effective_inner_border (entry, &inner_border);
+
+ area_height = PANGO_SCALE * (area_height - inner_border.top - inner_border.bottom);
+
+ line = pango_layout_get_lines (layout)->data;
+ pango_layout_line_get_extents (line, NULL, &logical_rect);
+
+ /* Align primarily for locale's ascent/descent */
+ y_pos = ((area_height - entry->ascent - entry->descent) / 2 +
+ entry->ascent + logical_rect.y);
+
+ /* Now see if we need to adjust to fit in actual drawn string */
+ if (logical_rect.height > area_height)
+ y_pos = (area_height - logical_rect.height) / 2;
+ else if (y_pos < 0)
+ y_pos = 0;
+ else if (y_pos + logical_rect.height > area_height)
+ y_pos = area_height - logical_rect.height;
+
+ y_pos = inner_border.top + y_pos / PANGO_SCALE;
+
+ if (x)
+ *x = inner_border.left - entry->scroll_offset;
+
+ if (y)
+ *y = y_pos;
+}
+
+static void
+_gtk_entry_effective_inner_border (GtkEntry *entry,
+ GtkBorder *border)
+{
+ GtkBorder *tmp_border;
+
+ tmp_border = g_object_get_qdata (G_OBJECT (entry), quark_inner_border);
+
+ if (tmp_border)
+ {
+ *border = *tmp_border;
+ return;
+ }
+
+ gtk_widget_style_get (GTK_WIDGET (entry), "inner-border", &tmp_border, NULL);
+
+ if (tmp_border)
+ {
+ *border = *tmp_border;
+ gtk_border_free (tmp_border);
+ return;
+ }
+
+ *border = default_inner_border;
+}
+
+static void
+gtk_entry_draw_text (GtkEntry *entry)
+{
+ GtkWidget *widget;
+
+ if (!entry->visible && entry->invisible_char == 0)
+ return;
+
+ if (GTK_WIDGET_DRAWABLE (entry))
+ {
+ //PangoLayout *layout = gtk_entry_ensure_layout (entry, TRUE);
+ PangoLayout *layout = gtk_entry_get_layout (entry);
+ cairo_t *cr;
+ gint x, y;
+ gint start_pos, end_pos;
+
+ widget = GTK_WIDGET (entry);
+
+ get_layout_position (entry, &x, &y);
+
+ cr = gdk_cairo_create (entry->text_area);
+
+ cairo_move_to (cr, x, y);
+ gdk_cairo_set_source_color (cr, &widget->style->text [widget->state]);
+ pango_cairo_show_layout (cr, layout);
+
+ if (gtk_editable_get_selection_bounds (GTK_EDITABLE (entry), &start_pos, &end_pos))
+ {
+ gint *ranges;
+ gint n_ranges, i;
+ PangoRectangle logical_rect;
+ GdkColor *selection_color, *text_color;
+ GtkBorder inner_border;
+
+ pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
+ gtk_entry_get_pixel_ranges (entry, &ranges, &n_ranges);
+
+ if (GTK_WIDGET_HAS_FOCUS (entry))
+ {
+ selection_color = &widget->style->base [GTK_STATE_SELECTED];
+ text_color = &widget->style->text [GTK_STATE_SELECTED];
+ }
+ else
+ {
+ selection_color = &widget->style->base [GTK_STATE_ACTIVE];
+ text_color = &widget->style->text [GTK_STATE_ACTIVE];
+ }
+
+ _gtk_entry_effective_inner_border (entry, &inner_border);
+
+ for (i = 0; i < n_ranges; ++i)
+ cairo_rectangle (cr,
+ inner_border.left - entry->scroll_offset + ranges[2 * i],
+ y,
+ ranges[2 * i + 1],
+ logical_rect.height);
+
+ cairo_clip (cr);
+
+ gdk_cairo_set_source_color (cr, selection_color);
+ cairo_paint (cr);
+
+ cairo_move_to (cr, x, y);
+ gdk_cairo_set_source_color (cr, text_color);
+ pango_cairo_show_layout (cr, layout);
+
+ g_free (ranges);
+ }
+
+ cairo_destroy (cr);
+ }
+}
+
+static void
+sugar_address_entry_get_borders (GtkEntry *entry,
+ gint *xborder,
+ gint *yborder)
+{
+ GtkWidget *widget = GTK_WIDGET (entry);
+ gint focus_width;
+ gboolean interior_focus;
+
+ gtk_widget_style_get (widget,
+ "interior-focus", &interior_focus,
+ "focus-line-width", &focus_width,
+ NULL);
+
+ if (entry->has_frame)
+ {
+ *xborder = widget->style->xthickness;
+ *yborder = widget->style->ythickness;
+ }
+ else
+ {
+ *xborder = 0;
+ *yborder = 0;
+ }
+
+ if (!interior_focus)
+ {
+ *xborder += focus_width;
+ *yborder += focus_width;
+ }
+}
+
+static void
+get_text_area_size (GtkEntry *entry,
+ gint *x,
+ gint *y,
+ gint *width,
+ gint *height)
+{
+ gint xborder, yborder;
+ GtkRequisition requisition;
+ GtkWidget *widget = GTK_WIDGET (entry);
+
+ gtk_widget_get_child_requisition (widget, &requisition);
+
+ sugar_address_entry_get_borders (entry, &xborder, &yborder);
+
+ if (x)
+ *x = xborder;
+
+ if (y)
+ *y = yborder;
+
+ if (width)
+ *width = GTK_WIDGET (entry)->allocation.width - xborder * 2;
+
+ if (height)
+ *height = requisition.height - yborder * 2;
+}
+
+static gint
+sugar_address_entry_expose(GtkWidget *widget,
+ GdkEventExpose *event)
+{
+ GtkEntry *entry = GTK_ENTRY (widget);
+ SugarAddressEntry *address_entry = SUGAR_ADDRESS_ENTRY(widget);
+ cairo_t *cr;
+
+ if (entry->text_area == event->window) {
+ gint area_width, area_height;
+
+ get_text_area_size (entry, NULL, NULL, &area_width, &area_height);
+
+ gtk_paint_flat_box (widget->style, entry->text_area,
+ GTK_WIDGET_STATE(widget), GTK_SHADOW_NONE,
+ NULL, widget, "entry_bg",
+ 0, 0, area_width, area_height);
+
+
+ if (address_entry->progress != 0.0 && address_entry->progress != 1.0 &&
+ !GTK_WIDGET_HAS_FOCUS(entry)) {
+ int bar_width = area_width * address_entry->progress;
+ float radius = area_height / 2;
+
+ cr = gdk_cairo_create(entry->text_area);
+ cairo_set_source_rgb(cr, 0xA6 / 255.0, 0xA6 / 255.0, 0xA6 / 255.0);
+
+ cairo_move_to (cr, radius, 0);
+ cairo_arc (cr, bar_width - radius, radius, radius, M_PI * 1.5, M_PI * 2);
+ cairo_arc (cr, bar_width - radius, area_height - radius, radius, 0, M_PI * 0.5);
+ cairo_arc (cr, radius, area_height - radius, radius, M_PI * 0.5, M_PI);
+ cairo_arc (cr, radius, radius, radius, M_PI, M_PI * 1.5);
+
+ cairo_fill(cr);
+ cairo_destroy (cr);
+ }
+
+
+ if ((entry->visible || entry->invisible_char != 0) &&
+ GTK_WIDGET_HAS_FOCUS (widget) &&
+ entry->selection_bound == entry->current_pos && entry->cursor_visible)
+ gtk_entry_draw_cursor (GTK_ENTRY (widget), CURSOR_STANDARD);
+
+ if (entry->dnd_position != -1)
+ gtk_entry_draw_cursor (GTK_ENTRY (widget), CURSOR_DND);
+
+ gtk_entry_draw_text (GTK_ENTRY (widget));
+ } else {
+ GtkWidgetClass *parent_class;
+ parent_class = GTK_WIDGET_CLASS(sugar_address_entry_parent_class);
+ parent_class->expose_event(widget, event);
+ }
+
+ return FALSE;
+}
+
+static void
+sugar_address_entry_set_property(GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SugarAddressEntry *address_entry = SUGAR_ADDRESS_ENTRY(object);
+ GtkEntry *entry = GTK_ENTRY(object);
+
+ switch (prop_id) {
+ case PROP_PROGRESS:
+ address_entry->progress = g_value_get_double(value);
+ if (GTK_WIDGET_REALIZED(entry))
+ gdk_window_invalidate_rect(entry->text_area, NULL, FALSE);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+sugar_address_entry_get_property(GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SugarAddressEntry *entry = SUGAR_ADDRESS_ENTRY(object);
+
+ switch (prop_id) {
+ case PROP_PROGRESS:
+ g_value_set_double(value, entry->progress);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+sugar_address_entry_class_init(SugarAddressEntryClass *klass)
+{
+ GtkWidgetClass *widget_class = (GtkWidgetClass*)klass;
+ GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+
+ widget_class->expose_event = sugar_address_entry_expose;
+
+ gobject_class->set_property = sugar_address_entry_set_property;
+ gobject_class->get_property = sugar_address_entry_get_property;
+
+ quark_inner_border = g_quark_from_static_string ("gtk-entry-inner-border");
+
+ g_object_class_install_property (gobject_class, PROP_PROGRESS,
+ g_param_spec_double("progress",
+ "Progress",
+ "Progress",
+ 0.0, 1.0, 0.0,
+ G_PARAM_READWRITE));
+}
+
+static gboolean
+button_press_event_cb (GtkWidget *widget, GdkEventButton *event)
+{
+ if (event->button == 1 && event->type == GDK_2BUTTON_PRESS) {
+ gtk_editable_select_region(GTK_EDITABLE(widget), 0, -1);
+ gtk_widget_grab_focus(widget);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+sugar_address_entry_init(SugarAddressEntry *entry)
+{
+ entry->progress = 0.0;
+
+ g_signal_connect(entry, "button-press-event",
+ G_CALLBACK(button_press_event_cb), NULL);
+}
diff --git a/toolkit/src/sugar/sugar-address-entry.h b/toolkit/src/sugar/sugar-address-entry.h
new file mode 100644
index 0000000..60c56ab
--- /dev/null
+++ b/toolkit/src/sugar/sugar-address-entry.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2006-2007 Red Hat, Inc.
+ *
+ * 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.
+ */
+
+#ifndef __SUGAR_ADDRESS_ENTRY_H__
+#define __SUGAR_ADDRESS_ENTRY_H__
+
+#include <gtk/gtkentry.h>
+
+G_BEGIN_DECLS
+
+typedef struct _SugarAddressEntry SugarAddressEntry;
+typedef struct _SugarAddressEntryClass SugarAddressEntryClass;
+typedef struct _SugarAddressEntryPrivate SugarAddressEntryPrivate;
+
+#define SUGAR_TYPE_ADDRESS_ENTRY (sugar_address_entry_get_type())
+#define SUGAR_ADDRESS_ENTRY(object) (G_TYPE_CHECK_INSTANCE_CAST((object), SUGAR_TYPE_ADDRESS_ENTRY, SugarAddressEntry))
+#define SUGAR_ADDRESS_ENTRY_CLASS(klass) (G_TYPE_CHACK_CLASS_CAST((klass), SUGAR_TYPE_ADDRESS_ENTRY, SugarAddressEntryClass))
+#define SUGAR_IS_ADDRESS_ENTRY(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), SUGAR_TYPE_ADDRESS_ENTRY))
+#define SUGAR_IS_ADDRESS_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SUGAR_TYPE_ADDRESS_ENTRY))
+#define SUGAR_ADDRESS_ENTRY_GET_CLASS(object) (G_TYPE_INSTANCE_GET_CLASS((object), SUGAR_TYPE_ADDRESS_ENTRY, SugarAddressEntryClass))
+
+struct _SugarAddressEntry {
+ GtkEntry base_instance;
+
+ float progress;
+ char *title;
+ char *address;
+};
+
+struct _SugarAddressEntryClass {
+ GtkEntryClass base_class;
+};
+
+GType sugar_address_entry_get_type (void);
+
+G_END_DECLS
+
+#endif /* __SUGAR_ADDRESS_ENTRY_H__ */
diff --git a/toolkit/src/sugar/sugar-grid.c b/toolkit/src/sugar/sugar-grid.c
new file mode 100644
index 0000000..3fa7de5
--- /dev/null
+++ b/toolkit/src/sugar/sugar-grid.c
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2008, Red Hat, Inc.
+ *
+ * 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.
+ */
+
+#include "sugar-grid.h"
+
+static void sugar_grid_class_init (SugarGridClass *grid_class);
+static void sugar_grid_init (SugarGrid *grid);
+
+
+G_DEFINE_TYPE(SugarGrid, sugar_grid, G_TYPE_OBJECT)
+
+void
+sugar_grid_setup(SugarGrid *grid, gint width, gint height)
+{
+ g_free(grid->weights);
+
+ grid->weights = g_new0(guchar, width * height);
+ grid->width = width;
+ grid->height = height;
+}
+
+static gboolean
+check_bounds(SugarGrid *grid, GdkRectangle *rect)
+{
+ return (grid->weights != NULL &&
+ grid->width >= rect->x + rect->width &&
+ grid->height >= rect->y + rect->height);
+}
+
+void
+sugar_grid_add_weight(SugarGrid *grid, GdkRectangle *rect)
+{
+ int i, k;
+
+ if (!check_bounds(grid, rect)) {
+ g_warning("Trying to add weight outside the grid bounds.");
+ return;
+ }
+
+ for (k = rect->y; k < rect->y + rect->height; k++) {
+ for (i = rect->x; i < rect->x + rect->width; i++) {
+ grid->weights[i + k * grid->width] += 1;
+ }
+ }
+}
+
+void
+sugar_grid_remove_weight(SugarGrid *grid, GdkRectangle *rect)
+{
+ int i, k;
+
+ if (!check_bounds(grid, rect)) {
+ g_warning("Trying to remove weight outside the grid bounds.");
+ return;
+ }
+
+ for (k = rect->y; k < rect->y + rect->height; k++) {
+ for (i = rect->x; i < rect->x + rect->width; i++) {
+ grid->weights[i + k * grid->width] -= 1;
+ }
+ }
+}
+
+guint
+sugar_grid_compute_weight(SugarGrid *grid, GdkRectangle *rect)
+{
+ int i, k, sum = 0;
+
+ if (!check_bounds(grid, rect)) {
+ g_warning("Trying to compute weight outside the grid bounds.");
+ return 0;
+ }
+
+ for (k = rect->y; k < rect->y + rect->height; k++) {
+ for (i = rect->x; i < rect->x + rect->width; i++) {
+ sum += grid->weights[i + k * grid->width];
+ }
+ }
+
+ return sum;
+}
+
+static void
+sugar_grid_finalize(GObject *object)
+{
+ SugarGrid *grid = SUGAR_GRID(object);
+
+ g_free(grid->weights);
+}
+
+static void
+sugar_grid_class_init(SugarGridClass *grid_class)
+{
+ GObjectClass *gobject_class;
+
+ gobject_class = G_OBJECT_CLASS(grid_class);
+ gobject_class->finalize = sugar_grid_finalize;
+}
+
+static void
+sugar_grid_init(SugarGrid *grid)
+{
+ grid->weights = NULL;
+}
diff --git a/toolkit/src/sugar/sugar-grid.h b/toolkit/src/sugar/sugar-grid.h
new file mode 100644
index 0000000..d493a60
--- /dev/null
+++ b/toolkit/src/sugar/sugar-grid.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2008, Red Hat, Inc.
+ *
+ * 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.
+ */
+
+#ifndef __SUGAR_GRID_H__
+#define __SUGAR_GRID_H__
+
+#include <glib-object.h>
+#include <gdk/gdk.h>
+
+G_BEGIN_DECLS
+
+typedef struct _SugarGrid SugarGrid;
+typedef struct _SugarGridClass SugarGridClass;
+
+#define SUGAR_TYPE_GRID (sugar_grid_get_type())
+#define SUGAR_GRID(object) (G_TYPE_CHECK_INSTANCE_CAST((object), SUGAR_TYPE_GRID, SugarGrid))
+#define SUGAR_GRID_CLASS(klass) (G_TYPE_CHACK_CLASS_CAST((klass), SUGAR_TYPE_GRID, SugarGridClass))
+#define SUGAR_IS_GRID(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), SUGAR_TYPE_GRID))
+#define SUGAR_IS_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SUGAR_TYPE_GRID))
+#define SUGAR_GRID_GET_CLASS(object) (G_TYPE_INSTANCE_GET_CLASS((object), SUGAR_TYPE_GRID, SugarGridClass))
+
+struct _SugarGrid {
+ GObject base_instance;
+
+ gint width;
+ gint height;
+ guchar *weights;
+};
+
+struct _SugarGridClass {
+ GObjectClass base_class;
+};
+
+GType sugar_grid_get_type (void);
+void sugar_grid_setup (SugarGrid *grid,
+ gint width,
+ gint height);
+void sugar_grid_add_weight (SugarGrid *grid,
+ GdkRectangle *rect);
+void sugar_grid_remove_weight (SugarGrid *grid,
+ GdkRectangle *rect);
+guint sugar_grid_compute_weight (SugarGrid *grid,
+ GdkRectangle *rect);
+
+G_END_DECLS
+
+#endif /* __SUGAR_GRID_H__ */
diff --git a/toolkit/src/sugar/sugar-key-grabber.c b/toolkit/src/sugar/sugar-key-grabber.c
new file mode 100644
index 0000000..8a00a80
--- /dev/null
+++ b/toolkit/src/sugar/sugar-key-grabber.c
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 2006-2007, Red Hat, Inc.
+ *
+ * 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.
+ */
+
+#include <X11/X.h>
+#include <gdk/gdkscreen.h>
+#include <gdk/gdkx.h>
+#include <gdk/gdk.h>
+
+#include "sugar-key-grabber.h"
+#include "eggaccelerators.h"
+#include "sugar-marshal.h"
+
+/* we exclude shift, GDK_CONTROL_MASK and GDK_MOD1_MASK since we know what
+ these modifiers mean
+ these are the mods whose combinations are bound by the keygrabbing code */
+#define IGNORED_MODS (0x2000 /*Xkb modifier*/ | GDK_LOCK_MASK | \
+ GDK_MOD2_MASK | GDK_MOD3_MASK | GDK_MOD4_MASK | GDK_MOD5_MASK)
+/* these are the ones we actually use for global keys, we always only check
+ * for these set */
+#define USED_MODS (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK)
+
+enum {
+ KEY_PRESSED,
+ KEY_RELEASED,
+ N_SIGNALS
+};
+
+typedef struct {
+ char *key;
+ guint keysym;
+ guint state;
+ guint keycode;
+} Key;
+
+G_DEFINE_TYPE(SugarKeyGrabber, sugar_key_grabber, G_TYPE_OBJECT)
+
+static guint signals[N_SIGNALS];
+
+static void
+free_key_info(Key *key_info)
+{
+ g_free(key_info->key);
+ g_free(key_info);
+}
+
+static void
+sugar_key_grabber_dispose (GObject *object)
+{
+ SugarKeyGrabber *grabber = SUGAR_KEY_GRABBER(object);
+
+ if (grabber->keys) {
+ g_list_foreach(grabber->keys, (GFunc)free_key_info, NULL);
+ g_list_free(grabber->keys);
+ grabber->keys = NULL;
+ }
+}
+
+static void
+sugar_key_grabber_class_init(SugarKeyGrabberClass *grabber_class)
+{
+ GObjectClass *g_object_class = G_OBJECT_CLASS (grabber_class);
+
+ g_object_class->dispose = sugar_key_grabber_dispose;
+
+ signals[KEY_PRESSED] = g_signal_new ("key-pressed",
+ G_TYPE_FROM_CLASS (grabber_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (SugarKeyGrabberClass, key_pressed),
+ NULL, NULL,
+ sugar_marshal_BOOLEAN__UINT_UINT_UINT,
+ G_TYPE_BOOLEAN, 3,
+ G_TYPE_UINT,
+ G_TYPE_UINT,
+ G_TYPE_UINT);
+ signals[KEY_RELEASED] = g_signal_new ("key-released",
+ G_TYPE_FROM_CLASS (grabber_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (SugarKeyGrabberClass, key_released),
+ NULL, NULL,
+ sugar_marshal_BOOLEAN__UINT_UINT_UINT,
+ G_TYPE_BOOLEAN, 3,
+ G_TYPE_UINT,
+ G_TYPE_UINT,
+ G_TYPE_UINT);
+}
+
+char *
+sugar_key_grabber_get_key(SugarKeyGrabber *grabber, guint keycode, guint state)
+{
+ GList *l;
+
+ for (l = grabber->keys; l != NULL; l = l->next) {
+ Key *keyinfo = (Key *)l->data;
+ if ((keyinfo->keycode == keycode) &&
+ ((state & USED_MODS) == keyinfo->state)) {
+ return g_strdup(keyinfo->key);
+ }
+ }
+
+ return NULL;
+}
+
+static GdkFilterReturn
+filter_events(GdkXEvent *xevent, GdkEvent *event, gpointer data)
+{
+ SugarKeyGrabber *grabber = (SugarKeyGrabber *)data;
+ XEvent *xev = (XEvent *)xevent;
+
+ if (xev->type == KeyRelease) {
+ int return_value;
+ g_signal_emit (grabber, signals[KEY_RELEASED], 0, xev->xkey.keycode,
+ xev->xkey.state, xev->xkey.time, &return_value);
+ if(return_value)
+ return GDK_FILTER_REMOVE;
+ }
+
+ if (xev->type == KeyPress) {
+ int return_value;
+ g_signal_emit (grabber, signals[KEY_PRESSED], 0, xev->xkey.keycode,
+ xev->xkey.state, xev->xkey.time, &return_value);
+ if(return_value)
+ return GDK_FILTER_REMOVE;
+ }
+
+ return GDK_FILTER_CONTINUE;
+}
+
+static void
+sugar_key_grabber_init(SugarKeyGrabber *grabber)
+{
+ GdkScreen *screen;
+
+ screen = gdk_screen_get_default();
+ grabber->root = gdk_screen_get_root_window(screen);
+ grabber->keys = NULL;
+
+ gdk_window_add_filter(grabber->root, filter_events, grabber);
+}
+
+/* grab_key and grab_key_real are from
+ * gnome-control-center/gnome-settings-daemon/gnome-settings-multimedia-keys.c
+ */
+
+static void
+grab_key_real (Key *key, GdkWindow *root, gboolean grab, int result)
+{
+ if (grab)
+ XGrabKey (GDK_DISPLAY(), key->keycode, (result | key->state),
+ GDK_WINDOW_XID (root), True, GrabModeAsync, GrabModeAsync);
+ else
+ XUngrabKey(GDK_DISPLAY(), key->keycode, (result | key->state),
+ GDK_WINDOW_XID (root));
+}
+
+#define N_BITS 32
+static void
+grab_key (SugarKeyGrabber *grabber, Key *key, gboolean grab)
+{
+ int indexes[N_BITS];/*indexes of bits we need to flip*/
+ int i, bit, bits_set_cnt;
+ int uppervalue;
+ guint mask_to_traverse = IGNORED_MODS & ~key->state & GDK_MODIFIER_MASK;
+
+ bit = 0;
+ for (i = 0; i < N_BITS; i++) {
+ if (mask_to_traverse & (1<<i))
+ indexes[bit++]=i;
+ }
+
+ bits_set_cnt = bit;
+
+ uppervalue = 1<<bits_set_cnt;
+ for (i = 0; i < uppervalue; i++) {
+ int j, result = 0;
+
+ for (j = 0; j < bits_set_cnt; j++) {
+ if (i & (1<<j))
+ result |= (1<<indexes[j]);
+ }
+
+ grab_key_real (key, grabber->root, grab, result);
+ }
+}
+
+
+void
+sugar_key_grabber_grab_keys(SugarKeyGrabber *grabber, const char **keys)
+{
+ const char **cur = keys;
+ const char *key;
+ Key *keyinfo = NULL;
+ int min_keycodes, max_keycodes;
+
+ XDisplayKeycodes(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()),
+ &min_keycodes, &max_keycodes);
+
+ while (*cur != NULL) {
+ key = *cur;
+ cur += 1;
+
+ keyinfo = g_new0 (Key, 1);
+ keyinfo->key = g_strdup(key);
+
+ if (!egg_accelerator_parse_virtual (key, &keyinfo->keysym,
+ &keyinfo->keycode,
+ &keyinfo->state)) {
+ g_warning ("Invalid key specified: %s", key);
+ continue;
+ }
+
+ if (keyinfo->keycode < min_keycodes || keyinfo->keycode > max_keycodes) {
+ g_warning ("Keycode out of bounds: %d for key %s", keyinfo->keycode, key);
+ continue;
+ }
+
+ gdk_error_trap_push();
+
+ grab_key(grabber, keyinfo, TRUE);
+
+ gdk_flush();
+ gint error_code = gdk_error_trap_pop ();
+ if(!error_code)
+ grabber->keys = g_list_append(grabber->keys, keyinfo);
+ else if(error_code == BadAccess)
+ g_warning ("Grab failed, another application may already have access to key '%s'", key);
+ else if(error_code == BadValue)
+ g_warning ("Grab failed, invalid key %s specified. keysym: %u keycode: %u state: %u",
+ key, keyinfo->keysym, keyinfo->keycode, keyinfo->state);
+ else
+ g_warning ("Grab failed for key '%s' for unknown reason '%d'", key, error_code);
+ }
+}
+
+gboolean
+sugar_key_grabber_is_modifier(SugarKeyGrabber *grabber, guint keycode, guint mask)
+{
+ Display *xdisplay;
+ XModifierKeymap *modmap;
+ gint start, end, i, mod_index;
+ gboolean is_modifier = FALSE;
+
+ xdisplay = gdk_x11_drawable_get_xdisplay(GDK_DRAWABLE (grabber->root));
+
+ modmap = XGetModifierMapping(xdisplay);
+
+ if (mask != -1) {
+ mod_index = 0;
+ mask = mask >> 1;
+ while (mask != 0) {
+ mask = mask >> 1;
+ mod_index += 1;
+ }
+ start = mod_index * modmap->max_keypermod;
+ end = (mod_index + 1) * modmap->max_keypermod;
+ } else {
+ start = 0;
+ end = 8 * modmap->max_keypermod;
+ }
+
+ for (i = start; i < end; i++) {
+ if (keycode == modmap->modifiermap[i]) {
+ is_modifier = TRUE;
+ break;
+ }
+ }
+
+ XFreeModifiermap (modmap);
+
+ return is_modifier;
+}
+
diff --git a/toolkit/src/sugar/sugar-key-grabber.h b/toolkit/src/sugar/sugar-key-grabber.h
new file mode 100644
index 0000000..ab02870
--- /dev/null
+++ b/toolkit/src/sugar/sugar-key-grabber.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2006-2007, Red Hat, Inc.
+ *
+ * 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.
+ */
+
+#ifndef __SUGAR_KEY_GRABBER_H__
+#define __SUGAR_KEY_GRABBER_H__
+
+#include <glib-object.h>
+#include <gdk/gdkwindow.h>
+
+G_BEGIN_DECLS
+
+typedef struct _SugarKeyGrabber SugarKeyGrabber;
+typedef struct _SugarKeyGrabberClass SugarKeyGrabberClass;
+typedef struct _SugarKeyGrabberPrivate SugarKeyGrabberPrivate;
+
+#define SUGAR_TYPE_KEY_GRABBER (sugar_key_grabber_get_type())
+#define SUGAR_KEY_GRABBER(object) (G_TYPE_CHECK_INSTANCE_CAST((object), SUGAR_TYPE_KEY_GRABBER, SugarKeyGrabber))
+#define SUGAR_KEY_GRABBER_CLASS(klass) (G_TYPE_CHACK_CLASS_CAST((klass), SUGAR_TYPE_KEY_GRABBER, SugarKeyGrabberClass))
+#define SUGAR_IS_KEY_GRABBER(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), SUGAR_TYPE_KEY_GRABBER))
+#define SUGAR_IS_KEYGRABBER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SUGAR_TYPE_KEY_GRABBER))
+#define SUGAR_KEY_GRABBER_GET_CLASS(object) (G_TYPE_INSTANCE_GET_CLASS((object), SUGAR_TYPE_KEY_GRABBER, SugarKeyGrabberClass))
+
+struct _SugarKeyGrabber {
+ GObject base_instance;
+
+ GdkWindow *root;
+ GList *keys;
+};
+
+struct _SugarKeyGrabberClass {
+ GObjectClass base_class;
+
+ gboolean (* key_pressed) (SugarKeyGrabber *grabber,
+ guint keycode,
+ guint state);
+ gboolean (* key_released) (SugarKeyGrabber *grabber,
+ guint keycode,
+ guint state);
+};
+
+GType sugar_key_grabber_get_type (void);
+void sugar_key_grabber_grab_keys (SugarKeyGrabber *grabber,
+ const char **keys);
+char *sugar_key_grabber_get_key (SugarKeyGrabber *grabber,
+ guint keycode,
+ guint state);
+gboolean sugar_key_grabber_is_modifier (SugarKeyGrabber *grabber,
+ guint keycode,
+ guint mask);
+
+G_END_DECLS
+
+#endif /* __SUGAR_KEY_GRABBER_H__ */
diff --git a/toolkit/src/sugar/sugar-marshal.list b/toolkit/src/sugar/sugar-marshal.list
new file mode 100644
index 0000000..cb5ec08
--- /dev/null
+++ b/toolkit/src/sugar/sugar-marshal.list
@@ -0,0 +1 @@
+BOOLEAN:UINT,UINT,UINT
diff --git a/toolkit/src/sugar/sugar-menu.c b/toolkit/src/sugar/sugar-menu.c
new file mode 100644
index 0000000..f19dc4b
--- /dev/null
+++ b/toolkit/src/sugar/sugar-menu.c
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2006-2007, Red Hat, Inc.
+ *
+ * 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.
+ */
+
+#include <gtk/gtkwindow.h>
+
+#include "sugar-menu.h"
+
+static void sugar_menu_class_init (SugarMenuClass *menu_class);
+static void sugar_menu_init (SugarMenu *menu);
+
+
+G_DEFINE_TYPE(SugarMenu, sugar_menu, GTK_TYPE_MENU)
+
+void
+sugar_menu_set_active(SugarMenu *menu, gboolean active)
+{
+ GTK_MENU_SHELL(menu)->active = active;
+}
+
+void
+sugar_menu_embed(SugarMenu *menu, GtkContainer *parent)
+{
+ menu->orig_toplevel = GTK_MENU(menu)->toplevel;
+
+ GTK_MENU(menu)->toplevel = gtk_widget_get_toplevel(GTK_WIDGET(parent));
+ gtk_widget_reparent(GTK_WIDGET(menu), GTK_WIDGET(parent));
+}
+
+void
+sugar_menu_unembed(SugarMenu *menu)
+{
+ if (menu->orig_toplevel) {
+ GTK_MENU(menu)->toplevel = menu->orig_toplevel;
+ gtk_widget_reparent(GTK_WIDGET(menu), GTK_WIDGET(menu->orig_toplevel));
+ }
+}
+
+static void
+sugar_menu_class_init(SugarMenuClass *menu_class)
+{
+}
+
+static void
+sugar_menu_init(SugarMenu *menu)
+{
+ menu->orig_toplevel = NULL;
+}
diff --git a/toolkit/src/sugar/sugar-menu.h b/toolkit/src/sugar/sugar-menu.h
new file mode 100644
index 0000000..74ce891
--- /dev/null
+++ b/toolkit/src/sugar/sugar-menu.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2006-2007, Red Hat, Inc.
+ *
+ * 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.
+ */
+
+#ifndef __SUGAR_MENU_H__
+#define __SUGAR_MENU_H__
+
+#include <gtk/gtkmenu.h>
+
+G_BEGIN_DECLS
+
+typedef struct _SugarMenu SugarMenu;
+typedef struct _SugarMenuClass SugarMenuClass;
+
+#define SUGAR_TYPE_MENU (sugar_menu_get_type())
+#define SUGAR_MENU(object) (G_TYPE_CHECK_INSTANCE_CAST((object), SUGAR_TYPE_MENU, SugarMenu))
+#define SUGAR_MENU_CLASS(klass) (G_TYPE_CHACK_CLASS_CAST((klass), SUGAR_TYPE_MENU, SugarMenuClass))
+#define SUGAR_IS_MENU(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), SUGAR_TYPE_MENU))
+#define SUGAR_IS_MENU_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SUGAR_TYPE_MENU))
+#define SUGAR_MENU_GET_CLASS(object) (G_TYPE_INSTANCE_GET_CLASS((object), SUGAR_TYPE_MENU, SugarMenuClass))
+
+struct _SugarMenu {
+ GtkMenu base_instance;
+
+ GtkWidget *orig_toplevel;
+ int min_width;
+};
+
+struct _SugarMenuClass {
+ GtkMenuClass base_class;
+};
+
+GType sugar_menu_get_type (void);
+void sugar_menu_set_active (SugarMenu *menu,
+ gboolean active);
+void sugar_menu_embed (SugarMenu *menu,
+ GtkContainer *parent);
+void sugar_menu_unembed (SugarMenu *menu);
+
+G_END_DECLS
+
+#endif /* __SUGAR_MENU_H__ */
diff --git a/toolkit/src/sugar/util.py b/toolkit/src/sugar/util.py
new file mode 100644
index 0000000..b947c0a
--- /dev/null
+++ b/toolkit/src/sugar/util.py
@@ -0,0 +1,347 @@
+"""Various utility functions"""
+# Copyright (C) 2006-2007 Red Hat, Inc.
+#
+# 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.
+
+"""
+UNSTABLE. We have been adding helpers randomly to this module.
+"""
+
+import os
+import time
+import hashlib
+import random
+import binascii
+import gettext
+import tempfile
+import logging
+import atexit
+import traceback
+
+
+_ = lambda msg: gettext.dgettext('sugar-toolkit', msg)
+
+
+def printable_hash(in_hash):
+ """Convert binary hash data into printable characters."""
+ printable = ""
+ for char in in_hash:
+ printable = printable + binascii.b2a_hex(char)
+ return printable
+
+
+def sha_data(data):
+ """sha1 hash some bytes."""
+ sha_hash = hashlib.sha1()
+ sha_hash.update(data)
+ return sha_hash.digest()
+
+
+def unique_id(data = ''):
+ """Generate a likely-unique ID for whatever purpose
+
+ data -- suffix appended to working data before hashing
+
+ Returns a 40-character string with hexidecimal digits
+ representing an SHA hash of the time, a random digit
+ within a constrained range and the data passed.
+
+ Note: these are *not* crypotographically secure or
+ globally unique identifiers. While they are likely
+ to be unique-enough, no attempt is made to make
+ perfectly unique values.
+ """
+ data_string = "%s%s%s" % (time.time(), random.randint(10000, 100000), data)
+ return printable_hash(sha_data(data_string))
+
+
+ACTIVITY_ID_LEN = 40
+
+
+def is_hex(s):
+ try:
+ int(s, 16)
+ except ValueError:
+ return False
+
+ return True
+
+
+def validate_activity_id(actid):
+ """Validate an activity ID."""
+ if not isinstance(actid, (str, unicode)):
+ return False
+ if len(actid) != ACTIVITY_ID_LEN:
+ return False
+ if not is_hex(actid):
+ return False
+ return True
+
+
+def set_proc_title(title):
+ """Sets the process title so ps and top show more
+ descriptive names. This does not modify argv[0]
+ and only the first 15 characters will be shown.
+
+ title -- the title you wish to change the process
+ title to
+
+ Returns True on success. We don't raise exceptions
+ because if something goes wrong here it is not a big
+ deal as this is intended as a nice thing to have for
+ debugging
+ """
+ try:
+ import ctypes
+ libc = ctypes.CDLL('libc.so.6')
+ libc.prctl(15, str(title), 0, 0, 0)
+
+ return True
+ except Exception:
+ return False
+
+
+class Node(object):
+
+ __slots__ = ['prev', 'next', 'me']
+
+ def __init__(self, prev, me):
+ self.prev = prev
+ self.me = me
+ self.next = None
+
+
+class LRU:
+ """
+ Implementation of a length-limited O(1) LRU queue.
+ Built for and used by PyPE:
+ http://pype.sourceforge.net
+ Copyright 2003 Josiah Carlson.
+ """
+
+ def __init__(self, count, pairs=[]):
+ # pylint: disable-msg=W0102,W0612
+ self.count = max(count, 1)
+ self.d = {}
+ self.first = None
+ self.last = None
+ for key, value in pairs:
+ self[key] = value
+
+ def __contains__(self, obj):
+ return obj in self.d
+
+ def __getitem__(self, obj):
+ a = self.d[obj].me
+ self[a[0]] = a[1]
+ return a[1]
+
+ def __setitem__(self, obj, val):
+ if obj in self.d:
+ del self[obj]
+ nobj = Node(self.last, (obj, val))
+ if self.first is None:
+ self.first = nobj
+ if self.last:
+ self.last.next = nobj
+ self.last = nobj
+ self.d[obj] = nobj
+ if len(self.d) > self.count:
+ if self.first == self.last:
+ self.first = None
+ self.last = None
+ return
+ a = self.first
+ a.next.prev = None
+ self.first = a.next
+ a.next = None
+ del self.d[a.me[0]]
+ del a
+
+ def __delitem__(self, obj):
+ nobj = self.d[obj]
+ if nobj.prev:
+ nobj.prev.next = nobj.next
+ else:
+ self.first = nobj.next
+ if nobj.next:
+ nobj.next.prev = nobj.prev
+ else:
+ self.last = nobj.prev
+ del self.d[obj]
+
+ def __iter__(self):
+ cur = self.first
+ while cur != None:
+ cur2 = cur.next
+ yield cur.me[1]
+ cur = cur2
+
+ def iteritems(self):
+ cur = self.first
+ while cur != None:
+ cur2 = cur.next
+ yield cur.me
+ cur = cur2
+
+ def iterkeys(self):
+ return iter(self.d)
+
+ def itervalues(self):
+ for i_, j in self.iteritems():
+ yield j
+
+ def keys(self):
+ return self.d.keys()
+
+
+units = [['%d year', '%d years', 356 * 24 * 60 * 60],
+ ['%d month', '%d months', 30 * 24 * 60 * 60],
+ ['%d week', '%d weeks', 7 * 24 * 60 * 60],
+ ['%d day', '%d days', 24 * 60 * 60],
+ ['%d hour', '%d hours', 60 * 60],
+ ['%d minute', '%d minutes', 60]]
+
+AND = _(' and ')
+COMMA = _(', ')
+
+# TRANS: Indicating something that just happened, eg. "just now", "moments ago"
+NOW = _('Seconds ago')
+
+# TRANS: Indicating time passed, eg. "[10 day, 5 hours] ago",
+# "[2 minutes] in the past", or "[3 years, 1 month] earlier"
+ELAPSED = _('%s ago')
+
+# Explanation of the following hack:
+# The xgettext utility extracts plural forms by reading the strings included as
+# parameters of ngettext(). As our plurals are not passed to ngettext()
+# straight away because there needs to be a calculation before we know which
+# strings need to be used, then we need to call ngettext() in a fake way so
+# xgettext will pick them up as plurals.
+
+
+def ngettext(singular, plural, n):
+ pass
+
+
+# TRANS: Relative dates (eg. 1 month and 5 days).
+ngettext('%d year', '%d years', 1)
+ngettext('%d month', '%d months', 1)
+ngettext('%d week', '%d weeks', 1)
+ngettext('%d day', '%d days', 1)
+ngettext('%d hour', '%d hours', 1)
+ngettext('%d minute', '%d minutes', 1)
+
+del ngettext
+
+# End of plurals hack
+
+
+# gettext perfs hack (#7959)
+_i18n_timestamps_cache = LRU(60)
+
+
+def timestamp_to_elapsed_string(timestamp, max_levels=2):
+ levels = 0
+ time_period = ''
+ elapsed_seconds = int(time.time() - timestamp)
+
+ for name_singular, name_plural, factor in units:
+ elapsed_units = elapsed_seconds / factor
+ if elapsed_units > 0:
+
+ if levels > 0:
+ time_period += COMMA
+
+ key = ''.join((os.environ['LANG'], name_singular,
+ str(elapsed_units)))
+ if key in _i18n_timestamps_cache:
+ time_period += _i18n_timestamps_cache[key]
+ else:
+ translation = gettext.dngettext('sugar-toolkit',
+ name_singular,
+ name_plural,
+ elapsed_units) % elapsed_units
+ _i18n_timestamps_cache[key] = translation
+ time_period += translation
+
+ elapsed_seconds -= elapsed_units * factor
+
+ if time_period != '':
+ levels += 1
+
+ if levels == max_levels:
+ break
+
+ if levels == 0:
+ return NOW
+
+ return ELAPSED % time_period
+
+
+_tracked_paths = {}
+
+
+class TempFilePath(str):
+
+ def __new__(cls, path=None):
+ if path is None:
+ fd, path = tempfile.mkstemp()
+ os.close(fd)
+ logging.debug('TempFilePath created %r', path)
+
+ if path in _tracked_paths:
+ _tracked_paths[path] += 1
+ else:
+ _tracked_paths[path] = 1
+
+ return str.__new__(cls, path)
+
+ def __del__(self):
+ if _tracked_paths[self] == 1:
+ del _tracked_paths[self]
+
+ if os.path.exists(self):
+ os.unlink(self)
+ logging.debug('TempFilePath deleted %r', self)
+ else:
+ logging.warning('TempFilePath already deleted %r', self)
+ else:
+ _tracked_paths[self] -= 1
+
+
+def _cleanup_temp_files():
+ logging.debug('_cleanup_temp_files')
+ for path in _tracked_paths.keys():
+ try:
+ os.unlink(path)
+ except:
+ logging.error(traceback.format_exc())
+
+atexit.register(_cleanup_temp_files)
+
+
+def format_size(size):
+ if not size:
+ return _('Empty')
+ elif size < 1024:
+ return _('%d B') % size
+ elif size < 1024**2:
+ return _('%d KB') % (size / 1024)
+ elif size < 1024**3:
+ return _('%d MB') % (size / 1024**2)
+ else:
+ return _('%d GB') % (size / 1024**3)
diff --git a/toolkit/src/sugar/wm.py b/toolkit/src/sugar/wm.py
new file mode 100644
index 0000000..418a291
--- /dev/null
+++ b/toolkit/src/sugar/wm.py
@@ -0,0 +1,86 @@
+# Copyright (C) 2007, Red Hat, Inc.
+#
+# 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.
+
+"""
+UNSTABLE. Used only internally by Activity and jarabe.
+"""
+
+import gtk
+import logging
+
+def _property_get_trapped(window, prop, prop_type):
+ gtk.gdk.error_trap_push()
+
+ prop_info = window.property_get(prop, prop_type)
+
+ # We just log a message
+ error = gtk.gdk.error_trap_pop()
+ if error:
+ logging.debug('Received X Error (%i) while getting '
+ 'a property on a window' % error)
+
+ return prop_info
+
+def _property_change_trapped(window, prop, prop_type, format, mode, data):
+ gtk.gdk.error_trap_push()
+
+ window.property_change(prop, prop_type, format, mode, data)
+
+ error = gtk.gdk.error_trap_pop()
+ if error:
+ logging.debug('Received X Error (%i) while setting '
+ 'a property on a window' % error)
+ raise RuntimeError('Received X Error (%i) while setting '
+ 'a property on a window' % error)
+
+
+def get_activity_id(wnck_window):
+ window = gtk.gdk.window_foreign_new(wnck_window.get_xid())
+ prop_info = _property_get_trapped(window, '_SUGAR_ACTIVITY_ID', 'STRING')
+ if prop_info is None:
+ return None
+ else:
+ return prop_info[2]
+
+
+def get_bundle_id(wnck_window):
+ window = gtk.gdk.window_foreign_new(wnck_window.get_xid())
+ prop_info = _property_get_trapped(window, '_SUGAR_BUNDLE_ID', 'STRING')
+ if prop_info is None:
+ return None
+ else:
+ return prop_info[2]
+
+
+def get_sugar_window_type(wnck_window):
+ window = gtk.gdk.window_foreign_new(wnck_window.get_xid())
+ prop_info = _property_get_trapped(window, '_SUGAR_WINDOW_TYPE', 'STRING')
+ if prop_info is None:
+ return None
+ else:
+ return prop_info[2]
+
+
+def set_activity_id(window, activity_id):
+ _property_change_trapped(window, '_SUGAR_ACTIVITY_ID', 'STRING', 8,
+ gtk.gdk.PROP_MODE_REPLACE, activity_id)
+
+
+def set_bundle_id(window, bundle_id):
+ _property_change_trapped(window, '_SUGAR_BUNDLE_ID', 'STRING', 8,
+ gtk.gdk.PROP_MODE_REPLACE, bundle_id)
+
diff --git a/toolkit/tests/graphics/common.py b/toolkit/tests/graphics/common.py
new file mode 100644
index 0000000..2f00099
--- /dev/null
+++ b/toolkit/tests/graphics/common.py
@@ -0,0 +1,55 @@
+# Copyright (C) 2007, Red Hat, Inc.
+#
+# 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 gtk
+
+from sugar.graphics.toolbutton import ToolButton
+
+class Test(gtk.VBox):
+ def __init__(self):
+ gtk.VBox.__init__(self)
+
+class TestPalette(Test):
+ def __init__(self):
+ Test.__init__(self)
+
+ toolbar = gtk.Toolbar()
+
+ self._invoker = ToolButton('go-previous')
+ toolbar.insert(self._invoker, -1)
+ self._invoker.show()
+
+ self.pack_start(toolbar, False)
+ toolbar.show()
+
+ def set_palette(self, palette):
+ self._invoker.set_palette(palette)
+
+class TestRunner(object):
+ def run(self, test):
+ window = gtk.Window()
+ window.connect("destroy", lambda w: gtk.main_quit())
+ window.add(test)
+ test.show()
+
+ window.show()
+
+def main(test):
+ runner = TestRunner()
+ runner.run(test)
+
+ gtk.main()
diff --git a/toolkit/tests/graphics/hipposcalability.py b/toolkit/tests/graphics/hipposcalability.py
new file mode 100644
index 0000000..a5cebcc
--- /dev/null
+++ b/toolkit/tests/graphics/hipposcalability.py
@@ -0,0 +1,50 @@
+import hippo
+import gtk
+import gobject
+
+from sugar.graphics.icon import CanvasIcon
+from sugar.graphics.roundbox import CanvasRoundBox
+
+import common
+
+test = common.Test()
+
+canvas = hippo.Canvas()
+test.pack_start(canvas)
+canvas.show()
+
+scrollbars = hippo.CanvasScrollbars()
+canvas.set_root(scrollbars)
+
+box = hippo.CanvasBox(padding=10, spacing=10)
+scrollbars.set_root(box)
+
+def idle_cb():
+ global countdown
+
+ for i in range(0, 100):
+ entry = hippo.CanvasBox(border=2, border_color=0x000000ff,
+ orientation=hippo.ORIENTATION_HORIZONTAL,
+ padding=10, spacing=10)
+
+ for j in range(0, 3):
+ icon = CanvasIcon(icon_name='go-left')
+ entry.append(icon)
+
+ for j in range(0, 2):
+ text = hippo.CanvasText(text='Text %s %s' % (countdown, j))
+ entry.append(text)
+
+ box.append(entry)
+
+ countdown -= 1
+
+ return countdown > 0
+
+countdown = 1000
+gobject.idle_add(idle_cb)
+
+test.show()
+
+if __name__ == "__main__":
+ common.main(test)
diff --git a/toolkit/tests/graphics/iconcache.py b/toolkit/tests/graphics/iconcache.py
new file mode 100644
index 0000000..b03ecb6
--- /dev/null
+++ b/toolkit/tests/graphics/iconcache.py
@@ -0,0 +1,69 @@
+# Copyright (C) 2007, Red Hat, Inc.
+#
+# 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.
+
+"""
+Test the sugar.graphics.icon.* cache.
+"""
+
+import gtk
+
+from sugar.graphics.icon import Icon
+from sugar.graphics.xocolor import XoColor
+
+import common
+
+test = common.Test()
+
+data = [
+ ['battery-000', '#FF8F00,#FF2B34'],
+ ['battery-010', '#D1A3FF,#00A0FF'],
+ ['battery-020', '#FF8F00,#FF2B34'],
+ ['battery-030', '#00A0FF,#D1A3FF'],
+ ['battery-040', '#AC32FF,#FF2B34'],
+ ['battery-050', '#D1A3FF,#00A0FF'],
+ ['battery-060', '#AC32FF,#FF2B34'],
+ ['battery-070', '#00A0FF,#D1A3FF'],
+ ['battery-080', '#FF8F00,#FF2B34'],
+ ['battery-090', '#D1A3FF,#00A0FF'],
+ ['battery-100', '#AC32FF,#FF2B34']]
+
+def _button_activated_cb(button):
+ import random
+
+ global data
+ random.shuffle(data)
+
+ for i in range(0, len(test.get_children()) - 1):
+ test.get_children()[i].props.icon_name = data[i][0]
+ test.get_children()[i].props.xo_color = XoColor(data[i][1])
+
+for d in data:
+ icon = Icon(icon_name=d[0],
+ icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR,
+ xo_color=XoColor(d[1]))
+ test.pack_start(icon)
+ icon.show()
+
+button = gtk.Button("mec mac")
+test.pack_start(button)
+button.connect('activate', _button_activated_cb)
+button.show()
+
+test.show()
+
+if __name__ == "__main__":
+ common.main(test)
diff --git a/toolkit/tests/graphics/iconwidget.py b/toolkit/tests/graphics/iconwidget.py
new file mode 100644
index 0000000..cacf501
--- /dev/null
+++ b/toolkit/tests/graphics/iconwidget.py
@@ -0,0 +1,87 @@
+# Copyright (C) 2007, Red Hat, Inc.
+#
+# 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.
+
+"""
+Test the sugar.graphics.icon.Icon widget.
+"""
+
+import gtk
+
+from sugar.graphics.icon import Icon
+from sugar.graphics.xocolor import XoColor
+
+import common
+
+test = common.Test()
+
+hbox = gtk.HBox()
+test.pack_start(hbox)
+sensitive_box = gtk.VBox()
+insensitive_box = gtk.VBox()
+
+hbox.pack_start(sensitive_box)
+hbox.pack_start(insensitive_box)
+hbox.show_all()
+
+
+def create_icon_widgets(box, sensitive=True):
+ icon = Icon(icon_name='go-previous')
+ icon.props.icon_size = gtk.ICON_SIZE_LARGE_TOOLBAR
+ box.pack_start(icon)
+ icon.set_sensitive(sensitive)
+ icon.show()
+
+ icon = Icon(icon_name='computer-xo',
+ icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR,
+ xo_color=XoColor())
+ box.pack_start(icon)
+ icon.set_sensitive(sensitive)
+ icon.show()
+
+ icon = Icon(icon_name='battery-000',
+ icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR,
+ badge_name='emblem-busy')
+ box.pack_start(icon)
+ icon.set_sensitive(sensitive)
+ icon.show()
+
+ icon = Icon(icon_name='gtk-new',
+ icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR,
+ badge_name='gtk-cancel')
+ box.pack_start(icon)
+ icon.set_sensitive(sensitive)
+ icon.show()
+
+
+create_icon_widgets(sensitive_box, True)
+create_icon_widgets(insensitive_box, False)
+
+test.show()
+
+# This can be used to test for leaks by setting the LRU cache size
+# in icon.py to 1.
+#def idle_cb():
+# import gc
+# gc.collect()
+# test.queue_draw()
+# return True
+#
+#import gobject
+#gobject.idle_add(idle_cb)
+
+if __name__ == "__main__":
+ common.main(test)
diff --git a/toolkit/tests/graphics/ticket2855.py b/toolkit/tests/graphics/ticket2855.py
new file mode 100644
index 0000000..cc4b3c0
--- /dev/null
+++ b/toolkit/tests/graphics/ticket2855.py
@@ -0,0 +1,59 @@
+# Copyright (C) 2007, Red Hat, Inc.
+#
+# 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.
+
+"""
+Test the style of toggle and radio buttons inside a palette. The buttons
+contains only an icon and should be rendered similarly to the toolbar
+controls. Ticket #2855.
+"""
+
+import gtk
+
+from sugar.graphics.palette import Palette
+from sugar.graphics.icon import Icon
+
+import common
+
+test = common.TestPalette()
+
+palette = Palette('Test radio and toggle')
+test.set_palette(palette)
+
+box = gtk.HBox()
+
+toggle = gtk.ToggleButton()
+
+icon = Icon(icon_name='go-previous', icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR)
+toggle.set_image(icon)
+
+box.pack_start(toggle, False)
+toggle.show()
+
+radio = gtk.RadioButton()
+
+icon = Icon(icon_name='go-next', icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR)
+radio.set_image(icon)
+
+radio.set_mode(False)
+box.pack_start(radio, False)
+radio.show()
+
+palette.set_content(box)
+box.show()
+
+if __name__ == "__main__":
+ common.main(test)
diff --git a/toolkit/tests/graphics/ticket2999.py b/toolkit/tests/graphics/ticket2999.py
new file mode 100644
index 0000000..a7b92d5
--- /dev/null
+++ b/toolkit/tests/graphics/ticket2999.py
@@ -0,0 +1,38 @@
+# 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.
+
+"""
+Spec in ticket #2999.
+"""
+
+import gtk
+
+from sugar.graphics.palette import Palette
+from sugar.graphics.icon import Icon
+
+import common
+
+test = common.Test()
+test.set_border_width(60)
+
+text_view = gtk.TextView()
+text_view.props.buffer.props.text = 'Blah blah blah, blah blah blah.'
+test.pack_start(text_view)
+text_view.show()
+
+if __name__ == "__main__":
+ common.main(test)
diff --git a/toolkit/tests/graphics/ticket3000.py b/toolkit/tests/graphics/ticket3000.py
new file mode 100644
index 0000000..c28b2cb
--- /dev/null
+++ b/toolkit/tests/graphics/ticket3000.py
@@ -0,0 +1,48 @@
+# 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.
+
+"""
+Spec in ticket #3000.
+"""
+
+import gtk
+
+from sugar.graphics.toolbutton import ToolButton
+
+import common
+
+test = common.Test()
+
+toolbar = gtk.Toolbar()
+test.pack_start(toolbar, False)
+toolbar.show()
+
+button = ToolButton('go-previous')
+toolbar.insert(button, -1)
+button.show()
+
+separator = gtk.SeparatorToolItem()
+toolbar.add(separator)
+separator.show()
+
+button = ToolButton('go-next')
+toolbar.insert(button, -1)
+button.show()
+
+
+if __name__ == "__main__":
+ common.main(test)
diff --git a/toolkit/tests/graphics/toolbarpalettes.py b/toolkit/tests/graphics/toolbarpalettes.py
new file mode 100644
index 0000000..608ef57
--- /dev/null
+++ b/toolkit/tests/graphics/toolbarpalettes.py
@@ -0,0 +1,65 @@
+# Copyright (C) 2007, Red Hat, Inc.
+#
+# 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.
+
+"""
+Test palette positioning for toolbar and tray.
+"""
+
+import gtk
+
+from sugar.graphics.tray import HTray, TrayButton
+from sugar.graphics.toolbutton import ToolButton
+
+import common
+
+test = common.Test()
+
+vbox = gtk.VBox()
+
+theme_icons = gtk.icon_theme_get_default().list_icons()
+
+toolbar = gtk.Toolbar()
+vbox.pack_start(toolbar, False)
+toolbar.show()
+
+for i in range(0, 5):
+ button = ToolButton(icon_name=theme_icons[i])
+ button.set_tooltip('Icon %d' % i)
+ toolbar.insert(button, -1)
+ button.show()
+
+content = gtk.Label()
+vbox.pack_start(content)
+content.show()
+
+tray = HTray()
+vbox.pack_start(tray, False)
+tray.show()
+
+for i in range(0, 30):
+ button = TrayButton(icon_name=theme_icons[i])
+ button.set_tooltip('Icon %d' % i)
+ tray.add_item(button)
+ button.show()
+
+test.pack_start(vbox)
+vbox.show()
+
+test.show()
+
+if __name__ == "__main__":
+ common.main(test)
diff --git a/toolkit/tests/graphics/tray.py b/toolkit/tests/graphics/tray.py
new file mode 100644
index 0000000..f589f4e
--- /dev/null
+++ b/toolkit/tests/graphics/tray.py
@@ -0,0 +1,82 @@
+# Copyright (C) 2007, Red Hat, Inc.
+#
+# 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.
+
+"""
+Test the sugar.graphics.icon.Icon widget.
+"""
+
+import gtk
+
+from sugar.graphics.tray import HTray, VTray
+from sugar.graphics.tray import TrayButton, TrayIcon
+
+import common
+
+test = common.Test()
+
+vbox = gtk.VBox()
+
+tray = HTray()
+vbox.pack_start(tray, False)
+tray.show()
+
+theme_icons = gtk.icon_theme_get_default().list_icons()
+
+for i in range(0, 100):
+ button = TrayButton(icon_name=theme_icons[i])
+ tray.add_item(button)
+ button.show()
+
+tray = HTray()
+vbox.pack_start(tray, False)
+tray.show()
+
+for i in range(0, 10):
+ icon = TrayIcon(icon_name=theme_icons[i])
+ tray.add_item(icon)
+ icon.show()
+
+hbox = gtk.HBox()
+
+tray = VTray()
+hbox.pack_start(tray, False)
+tray.show()
+
+for i in range(0, 100):
+ button = TrayButton(icon_name=theme_icons[i])
+ tray.add_item(button)
+ button.show()
+
+tray = VTray()
+hbox.pack_start(tray, False)
+tray.show()
+
+for i in range(0, 4):
+ button = TrayButton(icon_name=theme_icons[i])
+ tray.add_item(button)
+ button.show()
+
+vbox.pack_start(hbox)
+hbox.show()
+
+test.pack_start(vbox)
+vbox.show()
+
+test.show()
+
+if __name__ == "__main__":
+ common.main(test)
diff --git a/toolkit/tests/lib/runall.py b/toolkit/tests/lib/runall.py
new file mode 100644
index 0000000..ae1bb3a
--- /dev/null
+++ b/toolkit/tests/lib/runall.py
@@ -0,0 +1,28 @@
+# Copyright (C) 2007, Red Hat, Inc.
+#
+# 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 unittest
+
+import test_mime
+
+runner = unittest.TextTestRunner()
+loader = unittest.TestLoader()
+
+suite = unittest.TestSuite()
+suite.addTest(loader.loadTestsFromModule(test_mime))
+
+runner.run(suite)
diff --git a/toolkit/tests/lib/test_mime.py b/toolkit/tests/lib/test_mime.py
new file mode 100644
index 0000000..3df0ce6
--- /dev/null
+++ b/toolkit/tests/lib/test_mime.py
@@ -0,0 +1,81 @@
+#!/usr/bin/env python
+
+# Copyright (C) 2006, Red Hat, Inc.
+# Copyright (C) 2007, 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 sys
+import unittest
+
+from sugar import mime
+
+class TestMime(unittest.TestCase):
+ def test_from_file_name(self):
+ self.assertEqual(mime.get_from_file_name('test.pdf'),
+ 'application/pdf')
+
+ def test_choose_most_significant(self):
+ # Mozilla's text in dnd
+ mime_type = mime.choose_most_significant(
+ ['text/plain', 'text/_moz_htmlcontext', 'text/unicode',
+ 'text/html', 'text/_moz_htmlinfo'])
+ self.assertEqual(mime_type, 'text/html')
+
+ # Mozilla's text in c&v
+ mime_type = mime.choose_most_significant(
+ ['text/_moz_htmlcontext', 'STRING', 'text/html', 'text/_moz_htmlinfo',
+ 'text/x-moz-url-priv', 'UTF8_STRING', 'COMPOUND_TEXT'])
+ self.assertEqual(mime_type, 'text/html')
+
+ # Mozilla gif in dnd
+ mime_type = mime.choose_most_significant(
+ ['application/x-moz-file-promise-url',
+ 'application/x-moz-file-promise-dest-filename', 'text/_moz_htmlinfo',
+ 'text/x-moz-url-desc', 'text/_moz_htmlcontext', 'text/x-moz-url-data',
+ 'text/uri-list'])
+ self.assertEqual(mime_type, 'text/uri-list')
+
+ # Mozilla url in dnd
+ mime_type = mime.choose_most_significant(
+ ['text/_moz_htmlcontext', 'text/html', 'text/_moz_htmlinfo',
+ '_NETSCAPE_URL', 'text/x-moz-url', 'text/x-moz-url-desc',
+ 'text/x-moz-url-data', 'text/plain', 'text/unicode'])
+ self.assertEqual(mime_type, 'text/x-moz-url')
+
+ # Abiword text in dnd
+ mime_type = mime.choose_most_significant(
+ ['text/rtf', 'text/uri-list'])
+ self.assertEqual(mime_type, 'text/uri-list')
+
+ # Abiword text in c&v
+ mime_type = mime.choose_most_significant(
+ ['UTF8_STRING', 'STRING', 'text/html', 'TEXT', 'text/rtf',
+ 'COMPOUND_TEXT', 'application/rtf', 'text/plain',
+ 'application/xhtml+xml'])
+ self.assertEqual(mime_type, 'application/rtf')
+
+ # Abiword text in c&v
+ mime_type = mime.choose_most_significant(
+ ['GTK_TEXT_BUFFER_CONTENTS',
+ 'application/x-gtk-text-buffer-rich-text',
+ 'UTF8_STRING', 'COMPOUND_TEXT', 'TEXT', 'STRING',
+ 'text/plain;charset=utf-8', 'text/plain;charset=UTF-8',
+ 'text/plain'])
+ self.assertEqual(mime_type, 'text/plain')
+
+if __name__ == "__main__":
+ unittest.main()
+