Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMarco Pesenti Gritti <marco@marcopg.org>2010-08-22 02:45:24 (GMT)
committer Marco Pesenti Gritti <marco@marcopg.org>2010-08-22 02:45:24 (GMT)
commit415b048faad644549d270817b5fb8910c2752bdb (patch)
treec870d507728fa86f99d8acfb70564686ee8026d1
parent323478579d1ef91fd3c994b3576beb99fc53a741 (diff)
parentd156ff56a8f63dba184fe0984986bd4f39c206dd (diff)
Merge toolkit module in a subdirectory
-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.m462
-rw-r--r--toolkit/po/.gitignore4
-rw-r--r--toolkit/po/ChangeLog0
-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
211 files changed, 41257 insertions, 0 deletions
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/toolkit/m4/python.m4 b/toolkit/m4/python.m4
new file mode 100644
index 0000000..e1c5266
--- /dev/null
+++ b/toolkit/m4/python.m4
@@ -0,0 +1,62 @@
+## this one is commonly used with AM_PATH_PYTHONDIR ...
+dnl AM_CHECK_PYMOD(MODNAME [,SYMBOL [,ACTION-IF-FOUND [,ACTION-IF-NOT-FOUND]]])
+dnl Check if a module containing a given symbol is visible to python.
+AC_DEFUN([AM_CHECK_PYMOD],
+[AC_REQUIRE([AM_PATH_PYTHON])
+py_mod_var=`echo $1['_']$2 | sed 'y%./+-%__p_%'`
+AC_MSG_CHECKING(for ifelse([$2],[],,[$2 in ])python module $1)
+AC_CACHE_VAL(py_cv_mod_$py_mod_var, [
+ifelse([$2],[], [prog="
+import sys
+try:
+ import $1
+except ImportError:
+ sys.exit(1)
+except:
+ sys.exit(0)
+sys.exit(0)"], [prog="
+import $1
+$1.$2"])
+if $PYTHON -c "$prog" 1>&AC_FD_CC 2>&AC_FD_CC
+ then
+ eval "py_cv_mod_$py_mod_var=yes"
+ else
+ eval "py_cv_mod_$py_mod_var=no"
+ fi
+])
+py_val=`eval "echo \`echo '$py_cv_mod_'$py_mod_var\`"`
+if test "x$py_val" != xno; then
+ AC_MSG_RESULT(yes)
+ ifelse([$3], [],, [$3
+])dnl
+else
+ AC_MSG_RESULT(no)
+ ifelse([$4], [],, [$4
+])dnl
+fi
+])
+
+dnl a macro to check for ability to create python extensions
+dnl AM_CHECK_PYTHON_HEADERS([ACTION-IF-POSSIBLE], [ACTION-IF-NOT-POSSIBLE])
+dnl function also defines PYTHON_INCLUDES
+AC_DEFUN([AM_CHECK_PYTHON_HEADERS],
+[AC_REQUIRE([AM_PATH_PYTHON])
+AC_MSG_CHECKING(for headers required to compile python extensions)
+dnl deduce PYTHON_INCLUDES
+py_prefix=`$PYTHON -c "import sys; print sys.prefix"`
+py_exec_prefix=`$PYTHON -c "import sys; print sys.exec_prefix"`
+PYTHON_INCLUDES="-I${py_prefix}/include/python${PYTHON_VERSION}"
+if test "$py_prefix" != "$py_exec_prefix"; then
+ PYTHON_INCLUDES="$PYTHON_INCLUDES -I${py_exec_prefix}/include/python${PYTHON_VERSION}"
+fi
+AC_SUBST(PYTHON_INCLUDES)
+dnl check if the headers exist:
+save_CPPFLAGS="$CPPFLAGS"
+CPPFLAGS="$CPPFLAGS $PYTHON_INCLUDES"
+AC_TRY_CPP([#include <Python.h>],dnl
+[AC_MSG_RESULT(found)
+$1],dnl
+[AC_MSG_RESULT(not found)
+$2])
+CPPFLAGS="$save_CPPFLAGS"
+])
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/toolkit/po/ChangeLog b/toolkit/po/ChangeLog
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ 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()
+