Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--AUTHORS14
-rw-r--r--COPYING504
-rw-r--r--Makefile.am13
-rw-r--r--README3
-rwxr-xr-xautogen.sh6
-rw-r--r--configure.ac46
-rw-r--r--m4/gnome-compiler-flags.m4141
-rw-r--r--m4/python.m462
-rw-r--r--po/ChangeLog0
-rw-r--r--po/POTFILES.in6
-rw-r--r--po/POTFILES.skip6
-rw-r--r--po/af.po153
-rw-r--r--po/am.po153
-rw-r--r--po/ar.po564
-rw-r--r--po/ay.po153
-rw-r--r--po/bg.po153
-rw-r--r--po/bi.po153
-rw-r--r--po/bn.po153
-rw-r--r--po/bn_IN.po153
-rw-r--r--po/ca.po153
-rw-r--r--po/cs.po160
-rw-r--r--po/de.po152
-rw-r--r--po/dz.po153
-rw-r--r--po/el.po153
-rw-r--r--po/en.po153
-rw-r--r--po/es.po630
-rw-r--r--po/fa.po153
-rw-r--r--po/fa_AF.po153
-rw-r--r--po/ff.po153
-rw-r--r--po/fr.po189
-rw-r--r--po/gu.po153
-rw-r--r--po/ha.po153
-rw-r--r--po/he.po153
-rw-r--r--po/hi.po153
-rw-r--r--po/ht.po153
-rw-r--r--po/hu.po153
-rw-r--r--po/ig.po153
-rw-r--r--po/is.po153
-rw-r--r--po/it.po194
-rw-r--r--po/ja.po149
-rw-r--r--po/km.po153
-rw-r--r--po/ko.po153
-rw-r--r--po/mk.po153
-rw-r--r--po/ml.po153
-rw-r--r--po/mn.po190
-rw-r--r--po/mr.po152
-rw-r--r--po/mvo.po153
-rw-r--r--po/nb.po152
-rw-r--r--po/ne.po154
-rw-r--r--po/nl.po189
-rw-r--r--po/pa.po153
-rw-r--r--po/pap.po153
-rw-r--r--po/pis.po153
-rw-r--r--po/pl.po153
-rw-r--r--po/ps.po153
-rw-r--r--po/pseudo.po153
-rw-r--r--po/pt.po153
-rw-r--r--po/pt_BR.po153
-rw-r--r--po/qu.po153
-rw-r--r--po/ro.po153
-rw-r--r--po/ru.po161
-rw-r--r--po/rw.po154
-rw-r--r--po/sd.po153
-rw-r--r--po/si.po152
-rw-r--r--po/sk.po160
-rw-r--r--po/sl.po202
-rw-r--r--po/sugar-toolkit.pot154
-rw-r--r--po/sv.po152
-rw-r--r--po/sw.po152
-rw-r--r--po/te.po154
-rw-r--r--po/th.po153
-rw-r--r--po/tpi.po153
-rw-r--r--po/tr.po153
-rw-r--r--po/ur.po152
-rw-r--r--po/vi.po153
-rw-r--r--po/wa.po153
-rw-r--r--po/yo.po153
-rw-r--r--po/zh_CN.po153
-rw-r--r--po/zh_TW.po146
-rw-r--r--src/Makefile.am1
-rw-r--r--src/sugar/Makefile.am87
-rw-r--r--src/sugar/_sugarext.defs416
-rw-r--r--src/sugar/_sugarext.override81
-rw-r--r--src/sugar/_sugarextmodule.c50
-rw-r--r--src/sugar/acme-volume-alsa.c317
-rw-r--r--src/sugar/acme-volume-alsa.h47
-rw-r--r--src/sugar/acme-volume.c127
-rw-r--r--src/sugar/acme-volume.h63
-rw-r--r--src/sugar/activity/Makefile.am10
-rw-r--r--src/sugar/activity/__init__.py55
-rw-r--r--src/sugar/activity/__init__py0
-rw-r--r--src/sugar/activity/activity.py1048
-rw-r--r--src/sugar/activity/activityfactory.py343
-rw-r--r--src/sugar/activity/activityhandle.py70
-rw-r--r--src/sugar/activity/activityservice.py82
-rw-r--r--src/sugar/activity/bundlebuilder.py398
-rw-r--r--src/sugar/activity/main.py140
-rw-r--r--src/sugar/activity/namingalert.py320
-rw-r--r--src/sugar/bundle/Makefile.am6
-rw-r--r--src/sugar/bundle/__init__.py16
-rw-r--r--src/sugar/bundle/activitybundle.py375
-rw-r--r--src/sugar/bundle/bundle.py199
-rw-r--r--src/sugar/bundle/contentbundle.py220
-rw-r--r--src/sugar/datastore/Makefile.am5
-rw-r--r--src/sugar/datastore/__init__.py16
-rw-r--r--src/sugar/datastore/datastore.py254
-rw-r--r--src/sugar/datastore/dbus_helpers.py104
-rw-r--r--src/sugar/eggaccelerators.c702
-rw-r--r--src/sugar/eggaccelerators.h89
-rw-r--r--src/sugar/eggdesktopfile.c1437
-rw-r--r--src/sugar/eggdesktopfile.h156
-rw-r--r--src/sugar/eggsmclient-private.h56
-rw-r--r--src/sugar/eggsmclient-xsmp.c1359
-rw-r--r--src/sugar/eggsmclient.c392
-rw-r--r--src/sugar/eggsmclient.h112
-rw-r--r--src/sugar/env.py60
-rw-r--r--src/sugar/graphics/Makefile.am27
-rw-r--r--src/sugar/graphics/__init__.py18
-rw-r--r--src/sugar/graphics/alert.py436
-rw-r--r--src/sugar/graphics/animator.py148
-rw-r--r--src/sugar/graphics/canvastextview.py39
-rw-r--r--src/sugar/graphics/colorbutton.py526
-rw-r--r--src/sugar/graphics/combobox.py168
-rw-r--r--src/sugar/graphics/entry.py39
-rw-r--r--src/sugar/graphics/icon.py872
-rw-r--r--src/sugar/graphics/iconentry.py106
-rw-r--r--src/sugar/graphics/menuitem.py94
-rw-r--r--src/sugar/graphics/notebook.py150
-rw-r--r--src/sugar/graphics/objectchooser.py130
-rw-r--r--src/sugar/graphics/palette.py1124
-rw-r--r--src/sugar/graphics/palettegroup.py95
-rw-r--r--src/sugar/graphics/panel.py27
-rw-r--r--src/sugar/graphics/radiotoolbutton.py180
-rw-r--r--src/sugar/graphics/roundbox.py70
-rw-r--r--src/sugar/graphics/style.py133
-rw-r--r--src/sugar/graphics/toggletoolbutton.py89
-rw-r--r--src/sugar/graphics/toolbox.py101
-rw-r--r--src/sugar/graphics/toolbutton.py158
-rw-r--r--src/sugar/graphics/toolcombobox.py63
-rw-r--r--src/sugar/graphics/tray.py461
-rw-r--r--src/sugar/graphics/window.py216
-rw-r--r--src/sugar/graphics/xocolor.py259
-rw-r--r--src/sugar/gsm-app.c396
-rw-r--r--src/sugar/gsm-app.h70
-rw-r--r--src/sugar/gsm-client-xsmp.c828
-rw-r--r--src/sugar/gsm-client-xsmp.h70
-rw-r--r--src/sugar/gsm-client.c251
-rw-r--r--src/sugar/gsm-client.h111
-rw-r--r--src/sugar/gsm-session.c497
-rw-r--r--src/sugar/gsm-session.h95
-rw-r--r--src/sugar/gsm-xsmp.c535
-rw-r--r--src/sugar/gsm-xsmp.h29
-rw-r--r--src/sugar/network.py297
-rw-r--r--src/sugar/presence/Makefile.am9
-rw-r--r--src/sugar/presence/__init__.py24
-rw-r--r--src/sugar/presence/activity.py410
-rw-r--r--src/sugar/presence/buddy.py239
-rw-r--r--src/sugar/presence/presenceservice.py609
-rw-r--r--src/sugar/presence/sugartubeconn.py63
-rw-r--r--src/sugar/presence/test_presence.txt26
-rw-r--r--src/sugar/presence/tubeconn.py114
-rw-r--r--src/sugar/profile.py216
-rw-r--r--src/sugar/session.py50
-rw-r--r--src/sugar/sexy-icon-entry.c984
-rw-r--r--src/sugar/sexy-icon-entry.h104
-rw-r--r--src/sugar/sugar-address-entry.c576
-rw-r--r--src/sugar/sugar-address-entry.h54
-rw-r--r--src/sugar/sugar-grid.c120
-rw-r--r--src/sugar/sugar-grid.h63
-rw-r--r--src/sugar/sugar-key-grabber.c265
-rw-r--r--src/sugar/sugar-key-grabber.h69
-rw-r--r--src/sugar/sugar-marshal.list1
-rw-r--r--src/sugar/sugar-menu.c63
-rw-r--r--src/sugar/sugar-menu.h57
-rw-r--r--src/sugar/util.py293
-rw-r--r--src/sugar/wm.py46
-rw-r--r--tests/graphics/common.py55
-rw-r--r--tests/graphics/hipposcalability.py50
-rw-r--r--tests/graphics/iconcache.py69
-rw-r--r--tests/graphics/iconwidget.py87
-rw-r--r--tests/graphics/ticket2855.py59
-rw-r--r--tests/graphics/ticket2999.py38
-rw-r--r--tests/graphics/ticket3000.py48
-rw-r--r--tests/graphics/toolbarpalettes.py65
-rw-r--r--tests/graphics/tray.py82
-rw-r--r--tests/lib/runall.py28
-rw-r--r--tests/lib/test_mime.py81
187 files changed, 35538 insertions, 0 deletions
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..8cd5dac
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,14 @@
+Marco Pesenti Gritti <mpg@redhat.com>
+Dan Williams <dcbw@redhat.com>
+Tomeu Vizoso <tomeu@tomeuvizoso.net>
+Dan Winship <dwinship@redhat.com>
+Benjamin Berg <benjamin@sipsolutions.net>
+Eduardo Silva <edsiper@gmail.com>
+Simon Schampijer <simon@schampijer.de>
+Bert Freudenberg <bert@freudenbergs.de>
+Guillaume Desmottes <guillaume.desmottes@collabora.co.uk>
+Dafydd Harries <daf@rhydd.org>
+John (J5) Palmieri <johnp@redhat.com>
+Morgan Collett <morgan.collett@gmail.com>
+Simon McVittie <simon.mcvittie@collabora.co.uk>
+Owen Williams <owen@ywwg.com>
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..5ab7695
--- /dev/null
+++ b/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/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..b62b8cc
--- /dev/null
+++ b/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/README b/README
new file mode 100644
index 0000000..0c5cbce
--- /dev/null
+++ b/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/autogen.sh b/autogen.sh
new file mode 100755
index 0000000..3d12f8f
--- /dev/null
+++ b/autogen.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+export ACLOCAL="aclocal -I m4"
+
+intltoolize
+autoreconf -i
+./configure "$@"
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..8637262
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,46 @@
+AC_INIT([sugar-toolkit],[0.83.4],[],[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])
+
+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 bn bn_IN ca de dz el en es fa fa_AF ff fr gu ha hi ht ig is it ja km ko mk ml mn mr mvo nb ne nl pa pap pis pl ps pt pt_BR qu ro ru rw sd si sl te th tpi tr ur vi 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/m4/gnome-compiler-flags.m4 b/m4/gnome-compiler-flags.m4
new file mode 100644
index 0000000..b9db2fd
--- /dev/null
+++ b/m4/gnome-compiler-flags.m4
@@ -0,0 +1,141 @@
+dnl GNOME_COMPILE_WARNINGS
+dnl Turn on many useful compiler warnings
+dnl For now, only works on GCC
+AC_DEFUN([GNOME_COMPILE_WARNINGS],[
+ dnl ******************************
+ dnl More compiler warnings
+ dnl ******************************
+
+ AC_ARG_ENABLE(compile-warnings,
+ AC_HELP_STRING([--enable-compile-warnings=@<:@no/minimum/yes/maximum/error@:>@],
+ [Turn on compiler warnings]),,
+ [enable_compile_warnings="m4_default([$1],[yes])"])
+
+ warnCFLAGS=
+ if test "x$GCC" != xyes; then
+ enable_compile_warnings=no
+ fi
+
+ warning_flags=
+ realsave_CFLAGS="$CFLAGS"
+
+ case "$enable_compile_warnings" in
+ no)
+ warning_flags=
+ ;;
+ minimum)
+ warning_flags="-Wall"
+ ;;
+ yes)
+ warning_flags="-Wall -Wmissing-prototypes"
+ ;;
+ maximum|error)
+ warning_flags="-Wall -Wmissing-prototypes -Wnested-externs -Wpointer-arith"
+ CFLAGS="$warning_flags $CFLAGS"
+ for option in -Wno-sign-compare; do
+ SAVE_CFLAGS="$CFLAGS"
+ CFLAGS="$CFLAGS $option"
+ AC_MSG_CHECKING([whether gcc understands $option])
+ AC_TRY_COMPILE([], [],
+ has_option=yes,
+ has_option=no,)
+ CFLAGS="$SAVE_CFLAGS"
+ AC_MSG_RESULT($has_option)
+ if test $has_option = yes; then
+ warning_flags="$warning_flags $option"
+ fi
+ unset has_option
+ unset SAVE_CFLAGS
+ done
+ unset option
+ if test "$enable_compile_warnings" = "error" ; then
+ warning_flags="$warning_flags -Werror"
+ fi
+ ;;
+ *)
+ AC_MSG_ERROR(Unknown argument '$enable_compile_warnings' to --enable-compile-warnings)
+ ;;
+ esac
+ CFLAGS="$realsave_CFLAGS"
+ AC_MSG_CHECKING(what warning flags to pass to the C compiler)
+ AC_MSG_RESULT($warning_flags)
+
+ AC_ARG_ENABLE(iso-c,
+ AC_HELP_STRING([--enable-iso-c],
+ [Try to warn if code is not ISO C ]),,
+ [enable_iso_c=no])
+
+ AC_MSG_CHECKING(what language compliance flags to pass to the C compiler)
+ complCFLAGS=
+ if test "x$enable_iso_c" != "xno"; then
+ if test "x$GCC" = "xyes"; then
+ case " $CFLAGS " in
+ *[\ \ ]-ansi[\ \ ]*) ;;
+ *) complCFLAGS="$complCFLAGS -ansi" ;;
+ esac
+ case " $CFLAGS " in
+ *[\ \ ]-pedantic[\ \ ]*) ;;
+ *) complCFLAGS="$complCFLAGS -pedantic" ;;
+ esac
+ fi
+ fi
+ AC_MSG_RESULT($complCFLAGS)
+
+ WARN_CFLAGS="$warning_flags $complCFLAGS"
+ AC_SUBST(WARN_CFLAGS)
+])
+
+dnl For C++, do basically the same thing.
+
+AC_DEFUN([GNOME_CXX_WARNINGS],[
+ AC_ARG_ENABLE(cxx-warnings,
+ AC_HELP_STRING([--enable-cxx-warnings=@<:@no/minimum/yes@:>@]
+ [Turn on compiler warnings.]),,
+ [enable_cxx_warnings="m4_default([$1],[minimum])"])
+
+ AC_MSG_CHECKING(what warning flags to pass to the C++ compiler)
+ warnCXXFLAGS=
+ if test "x$GXX" != xyes; then
+ enable_cxx_warnings=no
+ fi
+ if test "x$enable_cxx_warnings" != "xno"; then
+ if test "x$GXX" = "xyes"; then
+ case " $CXXFLAGS " in
+ *[\ \ ]-Wall[\ \ ]*) ;;
+ *) warnCXXFLAGS="-Wall -Wno-unused" ;;
+ esac
+
+ ## -W is not all that useful. And it cannot be controlled
+ ## with individual -Wno-xxx flags, unlike -Wall
+ if test "x$enable_cxx_warnings" = "xyes"; then
+ warnCXXFLAGS="$warnCXXFLAGS -Wshadow -Woverloaded-virtual"
+ fi
+ fi
+ fi
+ AC_MSG_RESULT($warnCXXFLAGS)
+
+ AC_ARG_ENABLE(iso-cxx,
+ AC_HELP_STRING([--enable-iso-cxx],
+ [Try to warn if code is not ISO C++ ]),,
+ [enable_iso_cxx=no])
+
+ AC_MSG_CHECKING(what language compliance flags to pass to the C++ compiler)
+ complCXXFLAGS=
+ if test "x$enable_iso_cxx" != "xno"; then
+ if test "x$GXX" = "xyes"; then
+ case " $CXXFLAGS " in
+ *[\ \ ]-ansi[\ \ ]*) ;;
+ *) complCXXFLAGS="$complCXXFLAGS -ansi" ;;
+ esac
+
+ case " $CXXFLAGS " in
+ *[\ \ ]-pedantic[\ \ ]*) ;;
+ *) complCXXFLAGS="$complCXXFLAGS -pedantic" ;;
+ esac
+ fi
+ fi
+ AC_MSG_RESULT($complCXXFLAGS)
+
+ WARN_CXXFLAGS="$CXXFLAGS $warnCXXFLAGS $complCXXFLAGS"
+ AC_SUBST(WARN_CXXFLAGS)
+])
diff --git a/m4/python.m4 b/m4/python.m4
new file mode 100644
index 0000000..e1c5266
--- /dev/null
+++ b/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/po/ChangeLog b/po/ChangeLog
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/po/ChangeLog
diff --git a/po/POTFILES.in b/po/POTFILES.in
new file mode 100644
index 0000000..488cb27
--- /dev/null
+++ b/po/POTFILES.in
@@ -0,0 +1,6 @@
+src/sugar/activity/activity.py
+src/sugar/activity/namingalert.py
+src/sugar/graphics/alert.py
+src/sugar/graphics/colorbutton.py
+src/sugar/graphics/objectchooser.py
+src/sugar/util.py
diff --git a/po/POTFILES.skip b/po/POTFILES.skip
new file mode 100644
index 0000000..a656b5a
--- /dev/null
+++ b/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/po/af.po b/po/af.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/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/po/am.po b/po/am.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/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/po/ar.po b/po/ar.po
new file mode 100644
index 0000000..f4e9b3e
--- /dev/null
+++ b/po/ar.po
@@ -0,0 +1,564 @@
+# translation of sugar.po to Arabic
+# Khaled Hosny <khaledhosny@eglug.org>, 2007, 2008.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+msgid ""
+msgstr ""
+"Project-Id-Version: sugar\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: 2008-08-07 17:03-0400\n"
+"Last-Translator: Khaled Hosny <khaledhosny@eglug.org>\n"
+"Language-Team: Arabic <doc@arabeyes.org>\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.1.0rc2\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:120
+msgid "Share with:"
+msgstr "شارِك مع:"
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr "خاص"
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+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/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] "%.sسنة"
+msgstr[2] "%.sسنتين"
+msgstr[3] "%d سنوات"
+msgstr[4] "%d سنة"
+msgstr[5] "%d سنة"
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] "%d شهر"
+msgstr[1] "%.0sشهر"
+msgstr[2] "%.0sشهرين"
+msgstr[3] "%d شهور"
+msgstr[4] "%d شهرا"
+msgstr[5] "%d شهر"
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] "%d أسبوع"
+msgstr[1] "%.0sأسبوع"
+msgstr[2] "%.0sأسبوعين"
+msgstr[3] "%d أسابيع"
+msgstr[4] "%d أسبوعا"
+msgstr[5] "%d أسبوع"
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] "%d يوم"
+msgstr[1] "%.0sيوم"
+msgstr[2] "%.0sيومين"
+msgstr[3] "%d أيام"
+msgstr[4] "%d يوما"
+msgstr[5] "%d يوم"
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] "%d ساعة"
+msgstr[1] "%.0sساعة"
+msgstr[2] "%.0sساعتين"
+msgstr[3] "%d ساعات"
+msgstr[4] "%d ساعة"
+msgstr[5] "%d ساعة"
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] "%d دقيقة"
+msgstr[1] "%.0sدقيقة"
+msgstr[2] "%.0sدقيقتين"
+msgstr[3] "%d دقائق"
+msgstr[4] "%d دقيقة"
+msgstr[5] "%d دقيقة"
+
+#~ 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 "حسنا"
+
+#~ msgid "%d second"
+#~ msgstr "%d ثانية"
diff --git a/po/ay.po b/po/ay.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/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/po/bg.po b/po/bg.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/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/po/bi.po b/po/bi.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/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/po/bn.po b/po/bn.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/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/po/bn_IN.po b/po/bn_IN.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/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/po/ca.po b/po/ca.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/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/po/cs.po b/po/cs.po
new file mode 100644
index 0000000..bf097b4
--- /dev/null
+++ b/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/po/de.po b/po/de.po
new file mode 100644
index 0000000..1fb7f11
--- /dev/null
+++ b/po/de.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.
+# Markus Schlager <m.slg@gmx.de>, 2008.
+msgid ""
+msgstr ""
+"Project-Id-Version: sugar-toolkit\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: 2008-09-19 10:32-0400\n"
+"Last-Translator: Markus Schlager <m.slg@gmx.de>\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 "Teilen mit:"
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr "Privat"
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr "Meine Umgebung"
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr "Behalten"
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr "Stopp"
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr "Rückgängig"
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr "Wiederholen"
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr "Kopieren"
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr "Einfügen"
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr "Aktivität"
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr "%s Aktivität"
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr "Fehler beim Speichern"
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr "Fehler beim Speichern: Alle Änderungen gehen verloren"
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr "Nicht stoppen"
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr "Trotzdem stoppen"
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr "Abbrechen"
+
+#: ../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 "Weitermachen"
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr " und "
+
+#: ../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 "vor Sekunden"
+
+#. 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 "vor %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 Jahr"
+msgstr[1] "%d Jahre"
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] "%d Monat"
+msgstr[1] "%d Monate"
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] "%d Woche"
+msgstr[1] "%d Wochen"
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] "%d Tag"
+msgstr[1] "%d Tage"
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] "%d Stunde"
+msgstr[1] "%d Stunden"
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] "%d Minute"
+msgstr[1] "%d Minuten"
diff --git a/po/dz.po b/po/dz.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/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/po/el.po b/po/el.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/po/el.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/po/en.po b/po/en.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/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/po/es.po b/po/es.po
new file mode 100644
index 0000000..0a9126f
--- /dev/null
+++ b/po/es.po
@@ -0,0 +1,630 @@
+# 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: 2008-06-24 00:07+0530\n"
+"PO-Revision-Date: 2008-07-20 14:39-0400\n"
+"Last-Translator: Chema Q <jameson.quinn@gmail.com>\n"
+"Language-Team: Fedora Spanish <fedora-trans-es@redhat.com>\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"
+"X-Poedit-Language: Spanish\n"
+"X-Poedit-SourceCharset: utf-8\n"
+"X-Poedit-Basepath: .\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr "Compartir con:"
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr "Privado"
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr "Mi Vecindario"
+
+# 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/activity.py:130
+msgid "Keep"
+msgstr "Guardar"
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr "Parar"
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr "Deshacer"
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr "Rehacer"
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr "Copiar"
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr "Pegar"
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr "Actividad"
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr "Actividad %s"
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr "Error al guardar"
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr "Error al guardar: todos los cambios se perderán"
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr "No detener"
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr "Detener de todas formas"
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr "Cancelar"
+
+#: ../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 "Continuar"
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr " y "
+
+#: ../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 "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"
+#: ../src/sugar/util.py:189
+#, 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).
+#: ../src/sugar/util.py:202
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] "%d año"
+msgstr[1] "%d años"
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] "%d mes"
+msgstr[1] "%d meses"
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] "%d semana"
+msgstr[1] "%d semanas"
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] "%d día"
+msgstr[1] "%d días"
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] "%d hora"
+msgstr[1] "%d horas"
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] "%d minuto"
+msgstr[1] "%d minutos"
+
+#~ 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/po/fa.po b/po/fa.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/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/po/fa_AF.po b/po/fa_AF.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/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/po/ff.po b/po/ff.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/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/po/fr.po b/po/fr.po
new file mode 100644
index 0000000..d60935b
--- /dev/null
+++ b/po/fr.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 16:47-0500\n"
+"Last-Translator: samy boutayeb <s.boutayeb@free.fr>\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 "Partager avec:"
+
+#: ../src/sugar/activity/activity.py:125
+msgid "Private"
+msgstr "Privé"
+
+#: ../src/sugar/activity/activity.py:126
+msgid "My Neighborhood"
+msgstr "Mon voisinage"
+
+#: ../src/sugar/activity/activity.py:133
+#: ../src/sugar/activity/namingalert.py:65
+msgid "Keep"
+msgstr "Conserver"
+
+#: ../src/sugar/activity/activity.py:144
+msgid "Stop"
+msgstr "Arrêter"
+
+#: ../src/sugar/activity/activity.py:258
+msgid "Undo"
+msgstr "Annuler"
+
+#: ../src/sugar/activity/activity.py:263
+msgid "Redo"
+msgstr "Répéter"
+
+#: ../src/sugar/activity/activity.py:273
+msgid "Copy"
+msgstr "Copier"
+
+#: ../src/sugar/activity/activity.py:278
+msgid "Paste"
+msgstr "Coller"
+
+#: ../src/sugar/activity/activity.py:304
+msgid "Activity"
+msgstr "Activité"
+
+#: ../src/sugar/activity/activity.py:542
+#, python-format
+msgid "%s Activity"
+msgstr "Activité %s"
+
+#: ../src/sugar/activity/activity.py:910
+msgid "Keep error"
+msgstr "Erreur d'enregistrement"
+
+#: ../src/sugar/activity/activity.py:911
+msgid "Keep error: all changes will be lost"
+msgstr "Erreur d'enregistrement : toutes les modifications seront perdues"
+
+#: ../src/sugar/activity/activity.py:914
+msgid "Don't stop"
+msgstr "Ne pas arrêter"
+
+#: ../src/sugar/activity/activity.py:917
+msgid "Stop anyway"
+msgstr "Arrêter quand même"
+
+#: ../src/sugar/activity/namingalert.py:60
+msgid "Name this entry"
+msgstr "Donner un nom à cette entrée"
+
+#: ../src/sugar/activity/namingalert.py:248
+msgid "Untitled"
+msgstr "Sans titre"
+
+#: ../src/sugar/activity/namingalert.py:255
+msgid "Description:"
+msgstr "Description :"
+
+#: ../src/sugar/activity/namingalert.py:279
+msgid "Tags:"
+msgstr "Étiquettes :"
+
+#: ../src/sugar/graphics/alert.py:288 ../src/sugar/graphics/alert.py:367
+msgid "Cancel"
+msgstr "Annuler"
+
+#: ../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 "Continuer"
+
+#: ../src/sugar/graphics/colorbutton.py:49
+msgid "Choose a color"
+msgstr "Choisir une couleur"
+
+#: ../src/sugar/graphics/colorbutton.py:262
+msgid "Red"
+msgstr "Rouge"
+
+#: ../src/sugar/graphics/colorbutton.py:264
+msgid "Green"
+msgstr "Vert"
+
+#: ../src/sugar/graphics/colorbutton.py:266
+msgid "Blue"
+msgstr "Bleu"
+
+#: ../src/sugar/util.py:194
+msgid " and "
+msgstr " et "
+
+#: ../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 "A 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:202
+#, 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:215
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] "%d an"
+msgstr[1] "%d ans"
+
+#: ../src/sugar/util.py:216
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] "%d mois"
+msgstr[1] "%d mois"
+
+#: ../src/sugar/util.py:217
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] "%d semaine"
+msgstr[1] "%d semaines"
+
+#: ../src/sugar/util.py:218
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] "%d jour"
+msgstr[1] "%d jours"
+
+#: ../src/sugar/util.py:219
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] "%d heure"
+msgstr[1] "%d heures"
+
+#: ../src/sugar/util.py:220
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] "%d minute"
+msgstr[1] "%d minutes"
diff --git a/po/gu.po b/po/gu.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/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/po/ha.po b/po/ha.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/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/po/he.po b/po/he.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/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/po/hi.po b/po/hi.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/po/hi.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/po/ht.po b/po/ht.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/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/po/hu.po b/po/hu.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/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/po/ig.po b/po/ig.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/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/po/is.po b/po/is.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/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/po/it.po b/po/it.po
new file mode 100644
index 0000000..b829f1b
--- /dev/null
+++ b/po/it.po
@@ -0,0 +1,194 @@
+# 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 16:32-0500\n"
+"Last-Translator: Carlo Falciola <cfalciola@yahoo.it>\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 "Condividi con:"
+
+#: ../src/sugar/activity/activity.py:125
+msgid "Private"
+msgstr "Privato"
+
+#: ../src/sugar/activity/activity.py:126
+msgid "My Neighborhood"
+msgstr "I miei vicini"
+
+#: ../src/sugar/activity/activity.py:133
+#: ../src/sugar/activity/namingalert.py:65
+msgid "Keep"
+msgstr "Memorizza"
+
+#: ../src/sugar/activity/activity.py:144
+msgid "Stop"
+msgstr "Chiudi"
+
+#: ../src/sugar/activity/activity.py:258
+msgid "Undo"
+msgstr "Annulla"
+
+#: ../src/sugar/activity/activity.py:263
+msgid "Redo"
+msgstr "Ripeti"
+
+#: ../src/sugar/activity/activity.py:273
+msgid "Copy"
+msgstr "Copia"
+
+#: ../src/sugar/activity/activity.py:278
+msgid "Paste"
+msgstr "Incolla"
+
+#: ../src/sugar/activity/activity.py:304
+msgid "Activity"
+msgstr "Attività"
+
+#: ../src/sugar/activity/activity.py:542
+#, python-format
+msgid "%s Activity"
+msgstr "Attività %s "
+
+#: ../src/sugar/activity/activity.py:910
+msgid "Keep error"
+msgstr "Errore di memorizzazione"
+
+#: ../src/sugar/activity/activity.py:911
+msgid "Keep error: all changes will be lost"
+msgstr "Errore di memorizzazione: tutte le modifiche saranno perse"
+
+#: ../src/sugar/activity/activity.py:914
+msgid "Don't stop"
+msgstr "Non Fermare"
+
+#: ../src/sugar/activity/activity.py:917
+msgid "Stop anyway"
+msgstr "Ferma comunque"
+
+#: ../src/sugar/activity/namingalert.py:60
+msgid "Name this entry"
+msgstr "Dai un nome a questo oggetto"
+
+#: ../src/sugar/activity/namingalert.py:248
+msgid "Untitled"
+msgstr "Senza nome"
+
+#: ../src/sugar/activity/namingalert.py:255
+msgid "Description:"
+msgstr "Descrizione:"
+
+#: ../src/sugar/activity/namingalert.py:279
+msgid "Tags:"
+msgstr "Etichette:"
+
+#: ../src/sugar/graphics/alert.py:288 ../src/sugar/graphics/alert.py:367
+msgid "Cancel"
+msgstr "Cancella"
+
+#: ../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 "Continua"
+
+#: ../src/sugar/graphics/colorbutton.py:49
+msgid "Choose a color"
+msgstr "Scegli un colore"
+
+#: ../src/sugar/graphics/colorbutton.py:262
+msgid "Red"
+msgstr "Rosso"
+
+#: ../src/sugar/graphics/colorbutton.py:264
+msgid "Green"
+msgstr "Verde"
+
+#: ../src/sugar/graphics/colorbutton.py:266
+msgid "Blue"
+msgstr "Blu"
+
+#: ../src/sugar/util.py:194
+msgid " and "
+msgstr " e "
+
+#: ../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 "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:202
+#, 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:215
+#, 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:216
+#, 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:217
+#, 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:218
+#, 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:219
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] "%d ora"
+msgstr[1] "%d ore"
+
+#: ../src/sugar/util.py:220
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] "%d minuto"
+msgstr[1] "%d minuti"
diff --git a/po/ja.po b/po/ja.po
new file mode 100644
index 0000000..29ffda2
--- /dev/null
+++ b/po/ja.po
@@ -0,0 +1,149 @@
+# 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-10-10 12:45+0530\n"
+"Last-Translator: \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"
+"Plural-Forms: nplurals=1; plural=0;\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 年"
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] "%d 月"
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] "%d 週"
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] "%d 日"
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] "%d 時間"
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] "%d 分"
+
diff --git a/po/km.po b/po/km.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/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/po/ko.po b/po/ko.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/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/po/mk.po b/po/mk.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/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/po/ml.po b/po/ml.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/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/po/mn.po b/po/mn.po
new file mode 100644
index 0000000..45d4018
--- /dev/null
+++ b/po/mn.po
@@ -0,0 +1,190 @@
+# 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: 2008-06-27 06:28-0400\n"
+"Last-Translator: Odontsetseg Bat-Erdene <obat-erdene@suffolk.edu>\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
+#, fuzzy
+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/po/mr.po b/po/mr.po
new file mode 100644
index 0000000..aee6e3b
--- /dev/null
+++ b/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/po/mvo.po b/po/mvo.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/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/po/nb.po b/po/nb.po
new file mode 100644
index 0000000..3e958e1
--- /dev/null
+++ b/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/po/ne.po b/po/ne.po
new file mode 100644
index 0000000..42719d5
--- /dev/null
+++ b/po/ne.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-31 03:41-0400\n"
+"Last-Translator: Bibek Kafle <oxese.eax@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 "%ऽ कृयाकलाप"
+
+#: ../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
+#, fuzzy
+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
+#, fuzzy
+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/po/nl.po b/po/nl.po
new file mode 100644
index 0000000..1e5493c
--- /dev/null
+++ b/po/nl.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:04-0500\n"
+"Last-Translator: Myckel Habets <myckel@sdf.lonestar.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 "Delen met:"
+
+#: ../src/sugar/activity/activity.py:125
+msgid "Private"
+msgstr "Privé"
+
+#: ../src/sugar/activity/activity.py:126
+msgid "My Neighborhood"
+msgstr "Mijn omgeving"
+
+#: ../src/sugar/activity/activity.py:133
+#: ../src/sugar/activity/namingalert.py:65
+msgid "Keep"
+msgstr "Behouden"
+
+#: ../src/sugar/activity/activity.py:144
+msgid "Stop"
+msgstr "Stop"
+
+#: ../src/sugar/activity/activity.py:258
+msgid "Undo"
+msgstr "Ongedaan maken"
+
+#: ../src/sugar/activity/activity.py:263
+msgid "Redo"
+msgstr "Herhalen"
+
+#: ../src/sugar/activity/activity.py:273
+msgid "Copy"
+msgstr "Kopiëren"
+
+#: ../src/sugar/activity/activity.py:278
+msgid "Paste"
+msgstr "Plakken"
+
+#: ../src/sugar/activity/activity.py:304
+msgid "Activity"
+msgstr "Activiteit"
+
+#: ../src/sugar/activity/activity.py:542
+#, python-format
+msgid "%s Activity"
+msgstr "%s activiteit"
+
+#: ../src/sugar/activity/activity.py:910
+msgid "Keep error"
+msgstr "Bewaarfout"
+
+#: ../src/sugar/activity/activity.py:911
+msgid "Keep error: all changes will be lost"
+msgstr "Bewaarfout: alle veranderingen zijn verloren gegaan"
+
+#: ../src/sugar/activity/activity.py:914
+msgid "Don't stop"
+msgstr "Niet stoppen"
+
+#: ../src/sugar/activity/activity.py:917
+msgid "Stop anyway"
+msgstr "Toch stoppen"
+
+#: ../src/sugar/activity/namingalert.py:60
+msgid "Name this entry"
+msgstr "Benoem deze ingang"
+
+#: ../src/sugar/activity/namingalert.py:248
+msgid "Untitled"
+msgstr "Naamloos"
+
+#: ../src/sugar/activity/namingalert.py:255
+msgid "Description:"
+msgstr "Beschrijving:"
+
+#: ../src/sugar/activity/namingalert.py:279
+msgid "Tags:"
+msgstr "Labels:"
+
+#: ../src/sugar/graphics/alert.py:288 ../src/sugar/graphics/alert.py:367
+msgid "Cancel"
+msgstr "Annuleren"
+
+#: ../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 "Doorgaan"
+
+#: ../src/sugar/graphics/colorbutton.py:49
+msgid "Choose a color"
+msgstr "Kies een kleur"
+
+#: ../src/sugar/graphics/colorbutton.py:262
+msgid "Red"
+msgstr "Rood"
+
+#: ../src/sugar/graphics/colorbutton.py:264
+msgid "Green"
+msgstr "Groen"
+
+#: ../src/sugar/graphics/colorbutton.py:266
+msgid "Blue"
+msgstr "Blauw"
+
+#: ../src/sugar/util.py:194
+msgid " and "
+msgstr " en "
+
+#: ../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 "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:202
+#, 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:215
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] "%d jaar"
+msgstr[1] "%d jaren"
+
+#: ../src/sugar/util.py:216
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] "%d maand"
+msgstr[1] "%d maanden"
+
+#: ../src/sugar/util.py:217
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] "%d week"
+msgstr[1] "%d weken"
+
+#: ../src/sugar/util.py:218
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] "%d dag"
+msgstr[1] "%d dagen"
+
+#: ../src/sugar/util.py:219
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] "%d uur"
+msgstr[1] "%d uren"
+
+#: ../src/sugar/util.py:220
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] "%d minuut"
+msgstr[1] "%d minuten"
diff --git a/po/pa.po b/po/pa.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/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/po/pap.po b/po/pap.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/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/po/pis.po b/po/pis.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/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/po/pl.po b/po/pl.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/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/po/ps.po b/po/ps.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/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/po/pseudo.po b/po/pseudo.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/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/po/pt.po b/po/pt.po
new file mode 100644
index 0000000..dc1c4d9
--- /dev/null
+++ b/po/pt.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-08-25 17:58-0400\n"
+"Last-Translator: Eduardo H. Silva <HoboPrimate@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"
+"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 "Partilhar com:"
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr "Privado"
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr "Minha Vizinhança"
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr "Guardar"
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr "Parar"
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr "Desfazer"
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr "Refazer"
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr "Copiar"
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr "Colar"
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr "Actividade"
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr "Actividade %s"
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr "Erro ao guardar"
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr "Erro ao guardar: todas as mudanças serão perdidas"
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr "Não parar"
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr "Parar mesmo assim"
+
+#: ../src/sugar/graphics/alert.py:166 ../src/sugar/graphics/alert.py:209
+msgid "Cancel"
+msgstr "Cancelar"
+
+#: ../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 "Continuar"
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr " e "
+
+#: ../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 "Segundos atrás"
+
+#. 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 atrá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 ano"
+msgstr[1] "%d anos"
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] "%d mês"
+msgstr[1] "%d meses"
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] "%d semana"
+msgstr[1] "%d semanas"
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] "%d dia"
+msgstr[1] "%d dias"
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] "%d hora"
+msgstr[1] "%d horas"
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] "%d minuto"
+msgstr[1] "%d minutos"
diff --git a/po/pt_BR.po b/po/pt_BR.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/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/po/qu.po b/po/qu.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/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/po/ro.po b/po/ro.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/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/po/ru.po b/po/ru.po
new file mode 100644
index 0000000..c86e0fb
--- /dev/null
+++ b/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/po/rw.po b/po/rw.po
new file mode 100644
index 0000000..567ed5b
--- /dev/null
+++ b/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/po/sd.po b/po/sd.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/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/po/si.po b/po/si.po
new file mode 100644
index 0000000..c6892fb
--- /dev/null
+++ b/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/po/sk.po b/po/sk.po
new file mode 100644
index 0000000..bf097b4
--- /dev/null
+++ b/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/po/sl.po b/po/sl.po
new file mode 100644
index 0000000..a7c0405
--- /dev/null
+++ b/po/sl.po
@@ -0,0 +1,202 @@
+# 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: 2008-07-11 00:46-0400\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 ""
+
+#: ../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 "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 ""
+
+#: ../src/sugar/graphics/colorbutton.py:262
+#, fuzzy
+msgid "Red"
+msgstr "Ponovi"
+
+#: ../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 " 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/po/sugar-toolkit.pot b/po/sugar-toolkit.pot
new file mode 100644
index 0000000..aad88d1
--- /dev/null
+++ b/po/sugar-toolkit.pot
@@ -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.
+#
+#, 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=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\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/po/sv.po b/po/sv.po
new file mode 100644
index 0000000..c0cc91c
--- /dev/null
+++ b/po/sv.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-10-08 05:46-0400\n"
+"Last-Translator: Mattias Ohlsson <mattias_oh@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: Translate Toolkit 1.1.1rc4\n"
+
+#: ../src/sugar/activity/activity.py:120
+msgid "Share with:"
+msgstr "Dela med:"
+
+#: ../src/sugar/activity/activity.py:122
+msgid "Private"
+msgstr "Mig själv"
+
+#: ../src/sugar/activity/activity.py:123
+msgid "My Neighborhood"
+msgstr "Mina grannar"
+
+#: ../src/sugar/activity/activity.py:130
+msgid "Keep"
+msgstr "Spara"
+
+#: ../src/sugar/activity/activity.py:136
+msgid "Stop"
+msgstr "Avsluta"
+
+#: ../src/sugar/activity/activity.py:251
+msgid "Undo"
+msgstr "Ångra"
+
+#: ../src/sugar/activity/activity.py:256
+msgid "Redo"
+msgstr "Återställ"
+
+#: ../src/sugar/activity/activity.py:266
+msgid "Copy"
+msgstr "Kopiera"
+
+#: ../src/sugar/activity/activity.py:271
+msgid "Paste"
+msgstr "Klistra in"
+
+#: ../src/sugar/activity/activity.py:297
+msgid "Activity"
+msgstr "Aktivitet"
+
+#: ../src/sugar/activity/activity.py:469
+#, python-format
+msgid "%s Activity"
+msgstr "(%s)aktivitet"
+
+#: ../src/sugar/activity/activity.py:856
+msgid "Keep error"
+msgstr "Sparfel"
+
+#: ../src/sugar/activity/activity.py:857
+msgid "Keep error: all changes will be lost"
+msgstr "Sparfel: alla ändringar kommer att förloras"
+
+#: ../src/sugar/activity/activity.py:860
+msgid "Don't stop"
+msgstr "Avsluta inte"
+
+#: ../src/sugar/activity/activity.py:863
+msgid "Stop anyway"
+msgstr "Stäng utan att spara"
+
+#: ../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 "Ok"
+
+#: ../src/sugar/graphics/alert.py:219
+msgid "Continue"
+msgstr "Fortsätt"
+
+#: ../src/sugar/util.py:181
+msgid " and "
+msgstr " och "
+
+#: ../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 "sekunder sedan"
+
+#. 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 gammalt"
+
+#. 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ånad"
+msgstr[1] "%d månader"
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] "%d vecka"
+msgstr[1] "%d veckor"
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] "%d dag"
+msgstr[1] "%d dagar"
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] "%d timme"
+msgstr[1] "%d timmar"
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] "%d minut"
+msgstr[1] "%d minuter"
diff --git a/po/sw.po b/po/sw.po
new file mode 100644
index 0000000..2c06ab5
--- /dev/null
+++ b/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/po/te.po b/po/te.po
new file mode 100644
index 0000000..bc632b1
--- /dev/null
+++ b/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/po/th.po b/po/th.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/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/po/tpi.po b/po/tpi.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/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/po/tr.po b/po/tr.po
new file mode 100644
index 0000000..7cd7da5
--- /dev/null
+++ b/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/po/ur.po b/po/ur.po
new file mode 100644
index 0000000..66ec1ce
--- /dev/null
+++ b/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/po/vi.po b/po/vi.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/po/vi.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/po/wa.po b/po/wa.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/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/po/yo.po b/po/yo.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/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/po/zh_CN.po b/po/zh_CN.po
new file mode 100644
index 0000000..93a56e3
--- /dev/null
+++ b/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/po/zh_TW.po b/po/zh_TW.po
new file mode 100644
index 0000000..ab47f70
--- /dev/null
+++ b/po/zh_TW.po
@@ -0,0 +1,146 @@
+# 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 07:24-0400\n"
+"Last-Translator: Yuan Chao <yuanchao@gmail.com>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Pootle 1.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 年"
+
+#: ../src/sugar/util.py:203
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] "%d 個月"
+
+#: ../src/sugar/util.py:204
+#, python-format
+msgid "%d week"
+msgid_plural "%d weeks"
+msgstr[0] "%d 週"
+
+#: ../src/sugar/util.py:205
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] "%d 天"
+
+#: ../src/sugar/util.py:206
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] "%d 小時"
+
+#: ../src/sugar/util.py:207
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] "%d 分鐘"
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..4fa44db
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = sugar
diff --git a/src/sugar/Makefile.am b/src/sugar/Makefile.am
new file mode 100644
index 0000000..fb87bf6
--- /dev/null
+++ b/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/src/sugar/_sugarext.defs b/src/sugar/_sugarext.defs
new file mode 100644
index 0000000..a6befa4
--- /dev/null
+++ b/src/sugar/_sugarext.defs
@@ -0,0 +1,416 @@
+;; -*- 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 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/src/sugar/_sugarext.override b/src/sugar/_sugarext.override
new file mode 100644
index 0000000..6b768bb
--- /dev/null
+++ b/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/src/sugar/_sugarextmodule.c b/src/sugar/_sugarextmodule.c
new file mode 100644
index 0000000..1bb8545
--- /dev/null
+++ b/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/src/sugar/acme-volume-alsa.c b/src/sugar/acme-volume-alsa.c
new file mode 100644
index 0000000..42bbf4e
--- /dev/null
+++ b/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/src/sugar/acme-volume-alsa.h b/src/sugar/acme-volume-alsa.h
new file mode 100644
index 0000000..b179a24
--- /dev/null
+++ b/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/src/sugar/acme-volume.c b/src/sugar/acme-volume.c
new file mode 100644
index 0000000..09ae1d2
--- /dev/null
+++ b/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/src/sugar/acme-volume.h b/src/sugar/acme-volume.h
new file mode 100644
index 0000000..ec5ee3d
--- /dev/null
+++ b/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/src/sugar/activity/Makefile.am b/src/sugar/activity/Makefile.am
new file mode 100644
index 0000000..91f6ea8
--- /dev/null
+++ b/src/sugar/activity/Makefile.am
@@ -0,0 +1,10 @@
+sugardir = $(pythondir)/sugar/activity
+sugar_PYTHON = \
+ __init__.py \
+ activity.py \
+ activityfactory.py \
+ activityhandle.py \
+ activityservice.py \
+ bundlebuilder.py \
+ main.py \
+ namingalert.py \ No newline at end of file
diff --git a/src/sugar/activity/__init__.py b/src/sugar/activity/__init__.py
new file mode 100644
index 0000000..8d3ef2b
--- /dev/null
+++ b/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/src/sugar/activity/__init__py b/src/sugar/activity/__init__py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/sugar/activity/__init__py
diff --git a/src/sugar/activity/activity.py b/src/sugar/activity/activity.py
new file mode 100644
index 0000000..d2ba278
--- /dev/null
+++ b/src/sugar/activity/activity.py
@@ -0,0 +1,1048 @@
+"""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
+#
+# 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
+import traceback
+import gconf
+
+import gtk
+import gobject
+import dbus
+import dbus.service
+import cjson
+
+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.toolbox import Toolbox
+from sugar.graphics.toolbutton import ToolButton
+from sugar.graphics.toolcombobox import ToolComboBox
+from sugar.graphics.alert import Alert
+from sugar.graphics.icon import Icon
+from sugar.graphics.xocolor import XoColor
+from sugar.datastore import datastore
+from sugar.session import XSMPClient
+from sugar import wm
+
+_ = 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'
+
+class ActivityToolbar(gtk.Toolbar):
+ """The Activity toolbar with the Journal entry title, sharing,
+ Keep and Stop buttons
+
+ All activities should have this toolbar. It is easiest to add it to your
+ Activity by using the ActivityToolbox.
+ """
+ def __init__(self, activity):
+ gtk.Toolbar.__init__(self)
+
+ self._activity = activity
+ self._updating_share = False
+
+ activity.connect('shared', self.__activity_shared_cb)
+ activity.connect('joined', self.__activity_shared_cb)
+ activity.connect('notify::max_participants',
+ self.__max_participants_changed_cb)
+
+ if activity.metadata:
+ self.title = gtk.Entry()
+ self.title.set_size_request(int(gtk.gdk.screen_width() / 3), -1)
+ self.title.set_text(activity.metadata['title'])
+ self.title.connect('changed', self.__title_changed_cb)
+ self._add_widget(self.title)
+
+ activity.metadata.connect('updated', self.__jobject_updated_cb)
+
+ separator = gtk.SeparatorToolItem()
+ separator.props.draw = False
+ separator.set_expand(True)
+ self.insert(separator, -1)
+ separator.show()
+
+ self.share = ToolComboBox(label_text=_('Share with:'))
+ self.share.combo.connect('changed', self.__share_changed_cb)
+ self.share.combo.append_item(SCOPE_PRIVATE, _('Private'), 'zoom-home')
+ self.share.combo.append_item(SCOPE_NEIGHBORHOOD, _('My Neighborhood'),
+ 'zoom-neighborhood')
+ self.insert(self.share, -1)
+ self.share.show()
+
+ self._update_share()
+
+ self.keep = ToolButton(tooltip=_('Keep'))
+ client = gconf.client_get_default()
+ color = XoColor(client.get_string('/desktop/sugar/user/color'))
+ keep_icon = Icon(icon_name='document-save', xo_color=color)
+ self.keep.set_icon_widget(keep_icon)
+ keep_icon.show()
+ self.keep.props.accelerator = '<Ctrl>S'
+ self.keep.connect('clicked', self.__keep_clicked_cb)
+ self.insert(self.keep, -1)
+ self.keep.show()
+
+ self.stop = ToolButton('activity-stop', tooltip=_('Stop'))
+ self.stop.props.accelerator = '<Ctrl>Q'
+ self.stop.connect('clicked', self.__stop_clicked_cb)
+ self.insert(self.stop, -1)
+ self.stop.show()
+
+ self._update_title_sid = None
+
+ def _update_share(self):
+ self._updating_share = True
+
+ if self._activity.props.max_participants == 1:
+ self.share.hide()
+
+ if self._activity.get_shared():
+ self.share.set_sensitive(False)
+ self.share.combo.set_active(1)
+ else:
+ self.share.set_sensitive(True)
+ self.share.combo.set_active(0)
+
+ self._updating_share = False
+
+ def __share_changed_cb(self, combo):
+ if self._updating_share:
+ return
+
+ model = self.share.combo.get_model()
+ it = self.share.combo.get_active_iter()
+ (scope, ) = model.get(it, 0)
+ if scope == SCOPE_NEIGHBORHOOD:
+ self._activity.share()
+
+ def __keep_clicked_cb(self, button):
+ self._activity.copy()
+
+ def __stop_clicked_cb(self, button):
+ self._activity.close()
+
+ def __jobject_updated_cb(self, jobject):
+ self.title.set_text(jobject['title'])
+
+ def __title_changed_cb(self, entry):
+ if not self._update_title_sid:
+ self._update_title_sid = gobject.timeout_add_seconds(
+ 1, self.__update_title_cb)
+
+ def __update_title_cb(self):
+ title = self.title.get_text()
+
+ self._activity.metadata['title'] = title
+ self._activity.metadata['title_set_by_user'] = '1'
+ self._activity.save()
+
+ shared_activity = self._activity.get_shared_activity()
+ if shared_activity:
+ shared_activity.props.name = title
+
+ self._update_title_sid = None
+ return False
+
+ 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 __activity_shared_cb(self, activity):
+ self._update_share()
+
+ def __max_participants_changed_cb(self, activity, pspec):
+ self._update_share()
+
+class EditToolbar(gtk.Toolbar):
+ """Provides the standard edit toolbar for Activities.
+
+ Members:
+ undo -- the undo button
+ redo -- the redo button
+ copy -- the copy button
+ paste -- the paste button
+ separator -- A separator between undo/redo and copy/paste
+
+ This class only provides the 'edit' buttons in a standard layout,
+ your activity will need to either hide buttons which make no sense for your
+ Activity, or you need to connect the button events to your own callbacks:
+
+ ## Example from Read.activity:
+ # Create the edit toolbar:
+ self._edit_toolbar = EditToolbar(self._view)
+ # Hide undo and redo, they're not needed
+ self._edit_toolbar.undo.props.visible = False
+ self._edit_toolbar.redo.props.visible = False
+ # Hide the separator too:
+ self._edit_toolbar.separator.props.visible = False
+
+ # As long as nothing is selected, copy needs to be insensitive:
+ self._edit_toolbar.copy.set_sensitive(False)
+ # When the user clicks the button, call _edit_toolbar_copy_cb()
+ self._edit_toolbar.copy.connect('clicked', self._edit_toolbar_copy_cb)
+
+ # Add the edit toolbar:
+ toolbox.add_toolbar(_('Edit'), self._edit_toolbar)
+ # And make it visible:
+ self._edit_toolbar.show()
+ """
+ def __init__(self):
+ gtk.Toolbar.__init__(self)
+
+ self.undo = ToolButton('edit-undo')
+ self.undo.set_tooltip(_('Undo'))
+ self.insert(self.undo, -1)
+ self.undo.show()
+
+ self.redo = ToolButton('edit-redo')
+ self.redo.set_tooltip(_('Redo'))
+ 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 = ToolButton('edit-copy')
+ self.copy.set_tooltip(_('Copy'))
+ self.insert(self.copy, -1)
+ self.copy.show()
+
+ self.paste = ToolButton('edit-paste')
+ self.paste.set_tooltip(_('Paste'))
+ 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_toolbox(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
+
+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)
+
+ # 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._pservice = presenceservice.get_instance()
+ self.shared_activity = None
+ self._share_id = 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._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']
+
+ # handle activity share/join
+ mesh_instance = self._pservice.get_activity(self._activity_id,
+ warn_if_none=False)
+ 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)
+
+ if handle.object_id is None and create_jobject:
+ logging.debug('Creating a jobject.')
+ self._jobject = datastore.create()
+ title = _('%s Activity') % get_bundle_name()
+ self._jobject.metadata['title'] = title
+ self.set_title(self._jobject.metadata['title'])
+ self._jobject.metadata['title_set_by_user'] = '0'
+ self._jobject.metadata['activity'] = self.get_bundle_id()
+ self._jobject.metadata['activity_id'] = self.get_id()
+ self._jobject.metadata['keep'] = '0'
+ self._jobject.metadata['preview'] = ''
+ self._jobject.metadata['share-scope'] = SCOPE_PRIVATE
+ 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')
+ self._jobject.metadata['icon-color'] = icon_color
+
+ self._jobject.file_path = ''
+ # Cannot call datastore.write async for creates:
+ # https://dev.laptop.org/ticket/3071
+ datastore.write(self._jobject)
+
+ 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 set_canvas(self, canvas):
+ """Sets the 'work area' of your activity with the canvas of your choice.
+
+ One commonly used canvas is gtk.ScrolledWindow
+ """
+ Window.set_canvas(self, canvas)
+ canvas.connect('map', self.__canvas_map_cb)
+
+ def __session_quit_requested_cb(self, session):
+ self._quit_requested = True
+
+ if not self._prepare_close():
+ session.will_quit(self, False)
+ elif not self._updating_jobject:
+ session.will_quit(self, True)
+
+ def __session_quit_cb(self, client):
+ self._complete_close()
+
+ def __canvas_map_cb(self, canvas):
+ if self._jobject and self._jobject.file_path:
+ self.read_file(self._jobject.file_path)
+
+ 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
+ logging.debug("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):
+
+ 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)
+
+ try:
+ file_path = os.path.join(self.get_activity_root(), 'instance',
+ '%i' % time.time())
+ self.write_file(file_path)
+ self._owns_file = True
+ self._jobject.file_path = file_path
+ except NotImplementedError:
+ logging.debug('Activity.write_file is not implemented.')
+
+ # 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):
+ 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"""
+ self.shared_activity.disconnect(self._join_id)
+ self._join_id = None
+ if not success:
+ logging.debug("Failed to join activity: %s" % err)
+ return
+
+ self.present()
+ 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):
+ self._pservice.disconnect(self._share_id)
+ self._share_id = None
+ 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:
+ buddy_key = self._invites_queue.pop()
+ buddy = self._pservice.get_buddy(buddy_key)
+ if buddy:
+ self.shared_activity.invite(
+ buddy, '', self._invite_response_cb)
+ else:
+ logging.error('Cannot invite %s, no such buddy.' % buddy_key)
+
+ def invite(self, buddy_key):
+ """Invite a buddy to join this Activity.
+
+ Side Effects:
+ Calls self.share(True) to privately share the activity if it wasn't
+ shared before.
+ """
+ self._invites_queue.append(buddy_key)
+
+ if (self.shared_activity is None
+ 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))
+ self._share_id = self._pservice.connect("activity-shared",
+ self.__share_cb)
+ self._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.present()
+
+ 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)
+
+ 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 Exception:
+ logging.info(traceback.format_exc())
+ self._show_keep_failed_dialog()
+ return False
+
+ if self.shared_activity:
+ self.shared_activity.leave()
+
+ self._closing = True
+
+ return True
+
+ def _complete_close(self):
+ self._cleanup_jobject()
+ self.destroy()
+
+ # 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.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()
+
+ 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)
+
+_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/src/sugar/activity/activityfactory.py b/src/sugar/activity/activityfactory.py
new file mode 100644
index 0000000..e92314d
--- /dev/null
+++ b/src/sugar/activity/activityfactory.py
@@ -0,0 +1,343 @@
+# 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.
+
+"""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 dbus
+import gobject
+
+from sugar.presence import presenceservice
+from sugar.activity.activityhandle import ActivityHandle
+from sugar import util
+from sugar import env
+
+from errno import EEXIST, ENOSPC
+
+import os
+
+_SHELL_SERVICE = "org.laptop.Shell"
+_SHELL_PATH = "/org/laptop/Shell"
+_SHELL_IFACE = "org.laptop.Shell"
+
+_DS_SERVICE = "org.laptop.sugar.DataStore"
+_DS_INTERFACE = "org.laptop.sugar.DataStore"
+_DS_PATH = "/org/laptop/sugar/DataStore"
+
+_ACTIVITY_FACTORY_INTERFACE = "org.laptop.ActivityFactory"
+
+_RAINBOW_SERVICE_NAME = "org.laptop.security.Rainbow"
+_RAINBOW_ACTIVITY_FACTORY_PATH = "/"
+_RAINBOW_ACTIVITY_FACTORY_INTERFACE = "org.laptop.security.Rainbow"
+
+# 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"""
+ pservice = presenceservice.get_instance()
+
+ # create a new unique activity ID
+ i = 0
+ act_id = None
+ while i < 10:
+ act_id = util.unique_id()
+ i += 1
+
+ # check through network activities
+ found = False
+ activities = pservice.get_activities()
+ for act in activities:
+ if act_id == act.props.id:
+ found = True
+ break
+ if not found:
+ return act_id
+ raise RuntimeError("Cannot generate unique activity id.")
+
+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']
+ #environ['RAINBOW_STRACE_LOG'] = '1'
+
+ if activity.get_path().startswith(env.get_user_activities_path()):
+ environ['SUGAR_LOCALEDIR'] = os.path.join(activity.get_path(), 'locale')
+
+ if activity.get_bundle_id() in [ 'org.laptop.WebActivity',
+ 'org.laptop.GmailActivity',
+ 'org.laptop.WikiBrowseActivity'
+ ]:
+ environ['RAINBOW_CONSTANT_UID'] = 'yes'
+
+ return environ
+
+def get_command(activity, activity_id=None, object_id=None, uri=None):
+ 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 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_SYNC | 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
+
+ self._use_rainbow = os.path.exists('/etc/olpc-security')
+ if self._service_name in [ 'org.laptop.JournalActivity',
+ 'org.laptop.Terminal',
+ 'org.laptop.Log',
+ 'org.laptop.Analyze'
+ ]:
+ self._use_rainbow = False
+
+ 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 = dbus.Interface(
+ bus.get_object(_DS_SERVICE, _DS_PATH), _DS_INTERFACE)
+ datastore.find({ 'activity_id': self._handle.activity_id }, [],
+ reply_handler=self._find_object_reply_handler,
+ error_handler=self._find_object_error_handler,
+ byte_arrays=True)
+ 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)
+
+ if not self._use_rainbow:
+ # use gobject spawn functionality, so that zombies are
+ # automatically reaped by the gobject event loop.
+ def child_setup():
+ # clone logfile.fileno() onto stdout/stderr
+ os.dup2(log_file.fileno(), 1)
+ os.dup2(log_file.fileno(), 2)
+ # close all other fds
+ _close_fds()
+ # we need to sanitize and str-ize the various bits which
+ # dbus gives us.
+ gobject.spawn_async([str(s) for s in command],
+ envp=['%s=%s' % (k, str(v))
+ for k, v in environ.items()],
+ working_directory=str(self._bundle.get_path()),
+ child_setup=child_setup,
+ flags=(gobject.SPAWN_SEARCH_PATH |
+ gobject.SPAWN_LEAVE_DESCRIPTORS_OPEN))
+ log_file.close()
+ else:
+ log_file.close()
+ system_bus = dbus.SystemBus()
+ factory = system_bus.get_object(_RAINBOW_SERVICE_NAME,
+ _RAINBOW_ACTIVITY_FACTORY_PATH)
+ factory.CreateActivity(
+ log_path,
+ environ,
+ command,
+ environ['SUGAR_BUNDLE_PATH'],
+ environ['SUGAR_BUNDLE_ID'],
+ timeout=30,
+ reply_handler=self._create_reply_handler,
+ error_handler=self._create_error_handler,
+ dbus_interface=_RAINBOW_ACTIVITY_FACTORY_INTERFACE)
+
+ 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._create_activity()
+
+ def _find_object_error_handler(self, err):
+ logging.error("Datastore find failed %s" % err)
+ self._create_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)
diff --git a/src/sugar/activity/activityhandle.py b/src/sugar/activity/activityhandle.py
new file mode 100644
index 0000000..f255fd5
--- /dev/null
+++ b/src/sugar/activity/activityhandle.py
@@ -0,0 +1,70 @@
+# 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
+ ):
+ """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)
+ """
+ self.activity_id = activity_id
+ self.object_id = object_id
+ self.uri = uri
+
+ def get_dict(self):
+ """Retrieve our settings as a dictionary"""
+ result = { 'activity_id' : self.activity_id }
+ 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'),
+ )
+ return result
diff --git a/src/sugar/activity/activityservice.py b/src/sugar/activity/activityservice.py
new file mode 100644
index 0000000..ff806f3
--- /dev/null
+++ b/src/sugar/activity/activityservice.py
@@ -0,0 +1,82 @@
+# 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 Invite(self, buddy_key):
+ self._activity.invite(buddy_key)
+
+ @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/src/sugar/activity/bundlebuilder.py b/src/sugar/activity/bundlebuilder.py
new file mode 100644
index 0000000..ab3679b
--- /dev/null
+++ b/src/sugar/activity/bundlebuilder.py
@@ -0,0 +1,398 @@
+# 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 = []
+
+ 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_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)
+ if git_ls.wait():
+ # Fall back to filtered list
+ return list_files(self.config.source_dir,
+ IGNORE_DIRS, IGNORE_FILES)
+
+ return [path.strip() for path in git_ls.stdout.readlines()]
+
+ 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_dummy, dirs_dummy, files in os.walk(config.source_dir):
+ for file_name in files:
+ if file_name.endswith('.py'):
+ python_files.append(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/src/sugar/activity/main.py b/src/sugar/activity/main.py
new file mode 100644
index 0000000..0295bf9
--- /dev/null
+++ b/src/sugar/activity/main.py
@@ -0,0 +1,140 @@
+# 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
+
+from sugar.activity import activityhandle
+from sugar.bundle.activitybundle import ActivityBundle
+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')
+ (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())
+
+ locale_path = None
+ if 'SUGAR_LOCALEDIR' in os.environ:
+ locale_path = os.environ['SUGAR_LOCALEDIR']
+
+ gettext.bindtextdomain(bundle.get_bundle_id(), 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)
+
+ 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/src/sugar/activity/namingalert.py b/src/sugar/activity/namingalert.py
new file mode 100644
index 0000000..724d76a
--- /dev/null
+++ b/src/sugar/activity/namingalert.py
@@ -0,0 +1,320 @@
+# 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 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.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)
+
+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)
+
+ activity_icon = self._create_activity_icon()
+ header.append(activity_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_activity_icon(self):
+ activity_bundle = ActivityBundle(self._bundle_path)
+ activity_icon = CanvasIcon(file_name=activity_bundle.get_icon())
+ if self._activity.metadata.has_key('icon-color') and \
+ self._activity.metadata['icon-color']:
+ activity_icon.props.xo_color = XoColor( \
+ self._activity.metadata['icon-color'])
+ return activity_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/src/sugar/bundle/Makefile.am b/src/sugar/bundle/Makefile.am
new file mode 100644
index 0000000..f1af791
--- /dev/null
+++ b/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/src/sugar/bundle/__init__.py b/src/sugar/bundle/__init__.py
new file mode 100644
index 0000000..85ebced
--- /dev/null
+++ b/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/src/sugar/bundle/activitybundle.py b/src/sugar/bundle/activitybundle.py
new file mode 100644
index 0000000..eb35307
--- /dev/null
+++ b/src/sugar/bundle/activitybundle.py
@@ -0,0 +1,375 @@
+# 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
+
+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._icon = None
+ self._bundle_id = None
+ self._mime_types = None
+ self._show_launcher = True
+ 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)
+
+ 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'):
+ 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'):
+ 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, '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._name = cp.get(section, 'name')
+
+ 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._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
+
+ # 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
+ def get_icon(self):
+ """Get the activity icon name"""
+ 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(self._icon)
+ 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_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)
+
+ 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)
+ os.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'
+ if os.path.isfile(svg_file):
+ os.symlink(svg_file,
+ os.path.join(installed_icons_dir,
+ os.path.basename(svg_file)))
+ if os.path.isfile(info_file):
+ os.symlink(info_file,
+ os.path.join(installed_icons_dir,
+ os.path.basename(info_file)))
+ return install_path
+
+ def uninstall(self, install_path, force=False):
+ 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)
diff --git a/src/sugar/bundle/bundle.py b/src/sugar/bundle/bundle.py
new file mode 100644
index 0000000..a1b2686
--- /dev/null
+++ b/src/sugar/bundle/bundle.py
@@ -0,0 +1,199 @@
+# 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
+
+ if os.path.isdir(self._path):
+ self._zip_file = None
+ else:
+ 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/src/sugar/bundle/contentbundle.py b/src/sugar/bundle/contentbundle.py
new file mode 100644
index 0000000..a95ed61
--- /dev/null
+++ b/src/sugar/bundle/contentbundle.py
@@ -0,0 +1,220 @@
+# 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 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
+
+ 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, 'host_version'):
+ version = cp.get(section, 'host_version')
+ try:
+ if int(version) != 1:
+ raise MalformedBundleException(
+ 'Content bundle %s has unknown host_version number %s' %
+ (self._path, version))
+ except ValueError:
+ raise MalformedBundleException(
+ 'Content bundle %s has invalid host_version number %s' %
+ (self._path, version))
+
+ 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, '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'
+
+ 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 is_installed(self):
+ if self._zip_file is None:
+ return True
+ elif os.path.isdir(self.get_root_dir()):
+ return True
+ else:
+ return False
+
+ def install(self):
+ self._unzip(env.get_user_library_path())
+ self._run_indexer()
+
+ 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/src/sugar/datastore/Makefile.am b/src/sugar/datastore/Makefile.am
new file mode 100644
index 0000000..a5f16b7
--- /dev/null
+++ b/src/sugar/datastore/Makefile.am
@@ -0,0 +1,5 @@
+sugardir = $(pythondir)/sugar/datastore
+sugar_PYTHON = \
+ __init__.py \
+ dbus_helpers.py \
+ datastore.py
diff --git a/src/sugar/datastore/__init__.py b/src/sugar/datastore/__init__.py
new file mode 100644
index 0000000..bdb658b
--- /dev/null
+++ b/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/src/sugar/datastore/datastore.py b/src/sugar/datastore/datastore.py
new file mode 100644
index 0000000..80d5936
--- /dev/null
+++ b/src/sugar/datastore/datastore.py
@@ -0,0 +1,254 @@
+# 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 time
+from datetime import datetime
+import os
+
+import gobject
+
+from sugar.datastore import dbus_helpers
+from sugar import mime
+
+class DSMetadata(gobject.GObject):
+ __gsignals__ = {
+ 'updated': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([]))
+ }
+
+ def __init__(self, props=None):
+ gobject.GObject.__init__(self)
+ if not props:
+ self._props = {}
+ else:
+ self._props = props
+
+ default_keys = ['activity', 'activity_id',
+ 'mime_type', 'title_set_by_user']
+ for key in default_keys:
+ if not self._props.has_key(key):
+ self._props[key] = ''
+
+ def __getitem__(self, key):
+ return self._props[key]
+
+ def __setitem__(self, key, value):
+ if not self._props.has_key(key) or self._props[key] != value:
+ self._props[key] = value
+ self.emit('updated')
+
+ def __delitem__(self, key):
+ del self._props[key]
+
+ def __contains__(self, key):
+ return self._props.__contains__(key)
+
+ def has_key(self, key):
+ return self._props.has_key(key)
+
+ def keys(self):
+ return self._props.keys()
+
+ def get_dictionary(self):
+ return self._props
+
+ def copy(self):
+ return DSMetadata(self._props.copy())
+
+ def get(self, key, default=None):
+ if self._props.has_key(key):
+ return self._props[key]
+ else:
+ return default
+
+class DSObject(object):
+ def __init__(self, object_id, metadata=None, file_path=None):
+ self.object_id = object_id
+ self._metadata = metadata
+ self._file_path = file_path
+ self._destroyed = False
+ self._owns_file = False
+
+ def get_metadata(self):
+ if self._metadata is None and not self.object_id is None:
+ metadata = DSMetadata(dbus_helpers.get_properties(self.object_id))
+ 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(dbus_helpers.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)
+
+def get(object_id):
+ logging.debug('datastore.get')
+ metadata = dbus_helpers.get_properties(object_id)
+
+ ds_object = DSObject(object_id, DSMetadata(metadata), None)
+ # TODO: register the object for updates
+ return ds_object
+
+def create():
+ metadata = DSMetadata()
+ metadata['mtime'] = datetime.now().isoformat()
+ metadata['timestamp'] = int(time.time())
+ return DSObject(object_id=None, metadata=metadata, file_path=None)
+
+def write(ds_object, update_mtime=True, transfer_ownership=False,
+ reply_handler=None, error_handler=None, timeout=-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:
+ dbus_helpers.update(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 = dbus_helpers.create(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):
+ logging.debug('datastore.delete')
+ dbus_helpers.delete(object_id)
+
+def find(query, sorting=None, limit=None, offset=None, properties=None,
+ reply_handler=None, error_handler=None):
+
+ query = query.copy()
+
+ if properties is None:
+ properties = []
+
+ if sorting:
+ query['order_by'] = sorting
+ if limit:
+ query['limit'] = limit
+ if offset:
+ query['offset'] = offset
+
+ props_list, total_count = dbus_helpers.find(query, properties,
+ reply_handler, error_handler)
+
+ objects = []
+ for props in props_list:
+ object_id = props['uid']
+ del props['uid']
+
+ ds_object = DSObject(object_id, DSMetadata(props), None)
+ objects.append(ds_object)
+
+ return objects, total_count
+
+def copy(jobject, mount_point):
+
+ new_jobject = jobject.copy()
+ new_jobject.metadata['mountpoint'] = mount_point
+
+ if jobject.metadata.has_key('title'):
+ filename = jobject.metadata['title']
+
+ if jobject.metadata.has_key('mime_type'):
+ mime_type = jobject.metadata['mime_type']
+ extension = mime.get_primary_extension(mime_type)
+ if extension:
+ filename += '.' + extension
+
+ new_jobject.metadata['suggested_filename'] = filename
+
+ # this will cause the file be retrieved from the DS
+ new_jobject.file_path = jobject.file_path
+
+ write(new_jobject)
+
+def mount(uri, options, timeout=-1):
+ return dbus_helpers.mount(uri, options, timeout=timeout)
+
+def unmount(mount_point_id):
+ dbus_helpers.unmount(mount_point_id)
+
+def mounts():
+ return dbus_helpers.mounts()
+
+def complete_indexing():
+ return dbus_helpers.complete_indexing()
+
+def get_unique_values(key):
+ return dbus_helpers.get_unique_values(key)
diff --git a/src/sugar/datastore/dbus_helpers.py b/src/sugar/datastore/dbus_helpers.py
new file mode 100644
index 0000000..9115382
--- /dev/null
+++ b/src/sugar/datastore/dbus_helpers.py
@@ -0,0 +1,104 @@
+# Copyright (C) 2006-2007 Red Hat, Inc.
+# 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.
+
+"""
+UNSTABLE. Should be internal to the datastore module.
+"""
+
+import logging
+
+import dbus
+import dbus.glib
+
+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)
+ return _data_store
+
+def create(properties, filename, transfer_ownership=False):
+ object_id = _get_data_store().create(dbus.Dictionary(properties), filename,
+ transfer_ownership)
+ logging.debug('dbus_helpers.create: ' + object_id)
+ return object_id
+
+def update(uid, properties, filename, transfer_ownership=False,
+ reply_handler=None, error_handler=None, timeout=-1):
+ debug_props = properties.copy()
+ if debug_props.has_key("preview"):
+ debug_props["preview"] = "<omitted>"
+ logging.debug('dbus_helpers.update: %s, %s, %s, %s' %
+ (uid, filename, debug_props, 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 delete(uid):
+ logging.debug('dbus_helpers.delete: %r' % uid)
+ _get_data_store().delete(uid)
+
+def get_properties(uid):
+ logging.debug('dbus_helpers.get_properties: %s' % uid)
+ return _get_data_store().get_properties(uid, byte_arrays=True)
+
+def get_filename(uid):
+ filename = _get_data_store().get_filename(uid)
+ logging.debug('dbus_helpers.get_filename: %s, %s' % (uid, filename))
+ return filename
+
+def find(query, properties, reply_handler, error_handler):
+ logging.debug('dbus_helpers.find: %r %r' % (query, properties))
+ if reply_handler and error_handler:
+ return _get_data_store().find(query, properties,
+ reply_handler=reply_handler, error_handler=error_handler,
+ byte_arrays=True)
+ else:
+ return _get_data_store().find(query, properties, byte_arrays=True)
+
+def mount(uri, options, timeout=-1):
+ return _get_data_store().mount(uri, options, timeout=timeout)
+
+def unmount(mount_point_id):
+ _get_data_store().unmount(mount_point_id)
+
+def mounts():
+ return _get_data_store().mounts()
+
+def get_unique_values(key):
+ return _get_data_store().get_uniquevaluesfor(
+ key, dbus.Dictionary({}, signature='ss'))
+
+def complete_indexing():
+ return _get_data_store().complete_indexing()
+
diff --git a/src/sugar/eggaccelerators.c b/src/sugar/eggaccelerators.c
new file mode 100644
index 0000000..0a39d51
--- /dev/null
+++ b/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/src/sugar/eggaccelerators.h b/src/sugar/eggaccelerators.h
new file mode 100644
index 0000000..d2276d2
--- /dev/null
+++ b/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/src/sugar/eggdesktopfile.c b/src/sugar/eggdesktopfile.c
new file mode 100644
index 0000000..eb28b9d
--- /dev/null
+++ b/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/src/sugar/eggdesktopfile.h b/src/sugar/eggdesktopfile.h
new file mode 100644
index 0000000..270aec8
--- /dev/null
+++ b/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/src/sugar/eggsmclient-private.h b/src/sugar/eggsmclient-private.h
new file mode 100644
index 0000000..d2958c9
--- /dev/null
+++ b/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/src/sugar/eggsmclient-xsmp.c b/src/sugar/eggsmclient-xsmp.c
new file mode 100644
index 0000000..13eb5d5
--- /dev/null
+++ b/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/src/sugar/eggsmclient.c b/src/sugar/eggsmclient.c
new file mode 100644
index 0000000..86036f9
--- /dev/null
+++ b/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/src/sugar/eggsmclient.h b/src/sugar/eggsmclient.h
new file mode 100644
index 0000000..52d85de
--- /dev/null
+++ b/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/src/sugar/env.py b/src/sugar/env.py
new file mode 100644
index 0000000..91e91d3
--- /dev/null
+++ b/src/sugar/env.py
@@ -0,0 +1,60 @@
+"""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/src/sugar/graphics/Makefile.am b/src/sugar/graphics/Makefile.am
new file mode 100644
index 0000000..c4d5e61
--- /dev/null
+++ b/src/sugar/graphics/Makefile.am
@@ -0,0 +1,27 @@
+sugardir = $(pythondir)/sugar/graphics
+sugar_PYTHON = \
+ __init__.py \
+ alert.py \
+ animator.py \
+ canvastextview.py \
+ combobox.py \
+ colorbutton.py \
+ entry.py \
+ icon.py \
+ iconentry.py \
+ menuitem.py \
+ notebook.py \
+ objectchooser.py \
+ radiotoolbutton.py \
+ palette.py \
+ palettegroup.py \
+ panel.py \
+ roundbox.py \
+ style.py \
+ toggletoolbutton.py \
+ toolbox.py \
+ toolbutton.py \
+ toolcombobox.py \
+ tray.py \
+ window.py \
+ xocolor.py
diff --git a/src/sugar/graphics/__init__.py b/src/sugar/graphics/__init__.py
new file mode 100644
index 0000000..1e7e0f9
--- /dev/null
+++ b/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/src/sugar/graphics/alert.py b/src/sugar/graphics/alert.py
new file mode 100644
index 0000000..2c3c4ca
--- /dev/null
+++ b/src/sugar/graphics/alert.py
@@ -0,0 +1,436 @@
+"""
+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
+#
+# 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 _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/src/sugar/graphics/animator.py b/src/sugar/graphics/animator.py
new file mode 100644
index 0000000..5d5b355
--- /dev/null
+++ b/src/sugar/graphics/animator.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.
+
+"""
+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/src/sugar/graphics/canvastextview.py b/src/sugar/graphics/canvastextview.py
new file mode 100644
index 0000000..481248d
--- /dev/null
+++ b/src/sugar/graphics/canvastextview.py
@@ -0,0 +1,39 @@
+# 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/src/sugar/graphics/colorbutton.py b/src/sugar/graphics/colorbutton.py
new file mode 100644
index 0000000..44f9f69
--- /dev/null
+++ b/src/sugar/graphics/colorbutton.py
@@ -0,0 +1,526 @@
+# 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:
+ self._palette.popup(immediate=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)
+
+ # Drag and Drop
+ def __drag_begin_cb(self, widget, context):
+ 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)
+
+# 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.)
+class ColorToolButton(gtk.ToolItem):
+ __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/src/sugar/graphics/combobox.py b/src/sugar/graphics/combobox.py
new file mode 100644
index 0000000..4e094ab
--- /dev/null
+++ b/src/sugar/graphics/combobox.py
@@ -0,0 +1,168 @@
+# 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/src/sugar/graphics/entry.py b/src/sugar/graphics/entry.py
new file mode 100644
index 0000000..62975da
--- /dev/null
+++ b/src/sugar/graphics/entry.py
@@ -0,0 +1,39 @@
+# 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/src/sugar/graphics/icon.py b/src/sugar/graphics/icon.py
new file mode 100644
index 0000000..1608bac
--- /dev/null
+++ b/src/sugar/graphics/icon.py
@@ -0,0 +1,872 @@
+# 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.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):
+ return (self.icon_name, self.file_name, self.fill_color,
+ self.stroke_color, self.badge_name, self.width, self.height,
+ 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)
+ surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
+
+ context = cairo.Context(surface)
+ 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()
+
+ gobject.GObject.__init__(self, **kwargs)
+
+ 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 __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(0, 0, -1, -1)
+
+ 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(0, 0, -1, -1)
+
+ 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(0, 0, -1, -1)
+
+ 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_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=int, 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):
+ self.emit_activated()
+ return True
+
+ 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))
+
+ palette = property(get_palette, set_palette)
+
+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
diff --git a/src/sugar/graphics/iconentry.py b/src/sugar/graphics/iconentry.py
new file mode 100644
index 0000000..df38b9e
--- /dev/null
+++ b/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/src/sugar/graphics/menuitem.py b/src/sugar/graphics/menuitem.py
new file mode 100644
index 0000000..a357d78
--- /dev/null
+++ b/src/sugar/graphics/menuitem.py
@@ -0,0 +1,94 @@
+# 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=0,
+ 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/src/sugar/graphics/notebook.py b/src/sugar/graphics/notebook.py
new file mode 100644
index 0000000..4965b24
--- /dev/null
+++ b/src/sugar/graphics/notebook.py
@@ -0,0 +1,150 @@
+# 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/src/sugar/graphics/objectchooser.py b/src/sugar/graphics/objectchooser.py
new file mode 100644
index 0000000..fb3703d
--- /dev/null
+++ b/src/sugar/graphics/objectchooser.py
@@ -0,0 +1,130 @@
+# 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/src/sugar/graphics/palette.py b/src/sugar/graphics/palette.py
new file mode 100644
index 0000000..de5b8e0
--- /dev/null
+++ b/src/sugar/graphics/palette.py
@@ -0,0 +1,1124 @@
+# Copyright (C) 2007, Eduardo Silva <edsiper@gmail.com>
+# 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
+import hippo
+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 import _sugarext
+
+# Helper function to find the gap position and size of widget a
+def _calculate_gap(a, b):
+ # 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 Palette(gtk.Window):
+ PRIMARY = 0
+ SECONDARY = 1
+
+ __gtype_name__ = 'SugarPalette'
+
+ __gsignals__ = {
+ 'popup' : (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([])),
+ 'popdown' : (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([]))
+ }
+
+ # DEPRECATED: label is passed with the primary-text property, accel_path
+ # is set via the invoker property, and menu_after_content is not used
+ def __init__(self, label=None, accel_path=None, menu_after_content=False,
+ text_maxlen=0, **kwargs):
+
+ self.palette_state = self.PRIMARY
+
+ self._primary_text = None
+ self._secondary_text = None
+ self._icon = None
+ self._icon_visible = True
+ self._group_id = None
+
+ 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.zoom(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_MIDDLE)
+
+ 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._popup_anim = animator.Animator(.5, 10)
+ self._popup_anim.add(_PopupAnimation(self))
+
+ self._secondary_anim = animator.Animator(2.0, 10)
+ self._secondary_anim.add(_SecondaryAnimation(self))
+
+ self._popdown_anim = animator.Animator(0.6, 10)
+ self._popdown_anim.add(_PopdownAnimation(self))
+
+ # we init after initializing all of our containers
+ 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)
+
+ primary_box.set_size_request(-1, style.zoom(style.GRID_CELL_SIZE)
+ - 2 * self.get_border_width())
+
+
+ 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._alignment = None
+ self._old_alloc = None
+ self._full_request = [0, 0]
+ self._cursor_x = 0
+ self._cursor_y = 0
+ self._invoker = None
+ self._group_id = None
+ self._up = False
+ self._menu_box = None
+ self._content = None
+ self._invoker_hids = []
+
+ self.set_group_id("default")
+
+ # 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('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 __menu_item_inserted_cb(self, menu):
+ self._update_separators()
+
+ def __destroy_cb(self, palette):
+ self.set_group_id(None)
+
+ # 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 _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 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 is_up(self):
+ return self._up
+
+ 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 = rectangle.width
+ height = rectangle.height
+
+ return gtk.gdk.Rectangle(x, y, width, height)
+
+ 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))
+ if hasattr(invoker.props, 'widget'):
+ self._update_accel_widget()
+ logging.debug(('Setup widget', invoker.props.widget))
+ self._invoker_hids.append(self._invoker.connect(
+ 'notify::widget', self._invoker_widget_changed_cb))
+
+ def get_invoker(self):
+ return self._invoker
+
+ invoker = gobject.property(type=object,
+ getter=get_invoker,
+ setter=set_invoker)
+
+ 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):
+ 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)
+
+ self._icon = icon
+ self._icon.props.icon_size = gtk.ICON_SIZE_LARGE_TOOLBAR
+ self._icon_box.pack_start(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 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 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)
+
+ # 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,
+ style.zoom(style.GRID_CELL_SIZE * 2),
+ label_width,
+ self._full_request[0])
+
+ 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_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.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
+ 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 _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 popup(self, immediate=False, state=None):
+ logging.debug('Palette.popup immediate %r' % immediate)
+
+ if state is None:
+ state = self.PRIMARY
+ self.set_state(state)
+
+ if self._invoker is not None:
+ self._update_full_request()
+ self._alignment = self._invoker.get_alignment(self._full_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.show()
+
+ self._secondary_anim.start()
+
+ def popdown(self, immediate=False):
+ logging.debug('Palette.popdown immediate %r' % immediate)
+ self._popup_anim.stop()
+
+ self._mouse_detector.stop()
+
+ if not immediate:
+ self._popdown_anim.start()
+ else:
+ self.hide()
+
+ def set_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
+
+ 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 _invoker_widget_changed_cb(self, invoker, spec):
+ self._update_accel_widget()
+
+ def _invoker_mouse_enter_cb(self, invoker):
+ self._mouse_detector.start()
+
+ def _invoker_mouse_leave_cb(self, invoker):
+ self._mouse_detector.stop()
+ self.popdown()
+
+ def _invoker_right_click_cb(self, invoker):
+ self.popup(immediate=True, state=self.SECONDARY)
+
+ def __enter_notify_event_cb(self, widget, event):
+ if event.detail != gtk.gdk.NOTIFY_INFERIOR and \
+ event.mode == gtk.gdk.CROSSING_NORMAL:
+ self._popdown_anim.stop()
+ self._secondary_anim.start()
+
+ def __leave_notify_event_cb(self, widget, event):
+ if event.detail != gtk.gdk.NOTIFY_INFERIOR and \
+ event.mode == gtk.gdk.CROSSING_NORMAL:
+ self.popdown()
+
+ def __show_cb(self, widget):
+ self.menu.set_active(True)
+
+ self._invoker.notify_popup()
+
+ self._up = True
+ self.emit('popup')
+
+ def __hide_cb(self, widget):
+ logging.debug('__hide_cb')
+ self.menu.set_active(False)
+
+ self._secondary_anim.stop()
+
+ if self._invoker:
+ self._invoker.notify_popdown()
+
+ self._up = False
+ self.emit('popdown')
+
+
+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 _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.show()
+
+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_state(Palette.SECONDARY)
+
+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.hide()
+
+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)
+ return self._get_position_for_alignment(alignment, palette_dim)
+
+ 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]
+
+ else:
+ 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:
+ 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:
+ 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
diff --git a/src/sugar/graphics/palettegroup.py b/src/sugar/graphics/palettegroup.py
new file mode 100644
index 0000000..e95b5aa
--- /dev/null
+++ b/src/sugar/graphics/palettegroup.py
@@ -0,0 +1,95 @@
+# 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
+
+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):
+ 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/src/sugar/graphics/panel.py b/src/sugar/graphics/panel.py
new file mode 100644
index 0000000..bc48db8
--- /dev/null
+++ b/src/sugar/graphics/panel.py
@@ -0,0 +1,27 @@
+# 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/src/sugar/graphics/radiotoolbutton.py b/src/sugar/graphics/radiotoolbutton.py
new file mode 100644
index 0000000..11f962d
--- /dev/null
+++ b/src/sugar/graphics/radiotoolbutton.py
@@ -0,0 +1,180 @@
+# 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/src/sugar/graphics/roundbox.py b/src/sugar/graphics/roundbox.py
new file mode 100644
index 0000000..28644bc
--- /dev/null
+++ b/src/sugar/graphics/roundbox.py
@@ -0,0 +1,70 @@
+# 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/src/sugar/graphics/style.py b/src/sugar/graphics/style.py
new file mode 100644
index 0000000..16b78ed
--- /dev/null
+++ b/src/sugar/graphics/style.py
@@ -0,0 +1,133 @@
+# 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
+
+_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)
+
+FONT_SIZE = zoom(7)
+FONT_NORMAL = Font('Bitstream Vera Sans %d' % FONT_SIZE)
+FONT_BOLD = Font('Bitstream Vera Sans bold %d' % 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')
+
+PALETTE_CURSOR_DISTANCE = zoom(10)
diff --git a/src/sugar/graphics/toggletoolbutton.py b/src/sugar/graphics/toggletoolbutton.py
new file mode 100644
index 0000000..9bb2f58
--- /dev/null
+++ b/src/sugar/graphics/toggletoolbutton.py
@@ -0,0 +1,89 @@
+# 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/src/sugar/graphics/toolbox.py b/src/sugar/graphics/toolbox.py
new file mode 100644
index 0000000..1cfbe93
--- /dev/null
+++ b/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/src/sugar/graphics/toolbutton.py b/src/sugar/graphics/toolbutton.py
new file mode 100644
index 0000000..6205b8a
--- /dev/null
+++ b/src/sugar/graphics/toolbutton.py
@@ -0,0 +1,158 @@
+# 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.connect('clicked', self.__button_clicked_cb)
+ 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 __button_clicked_cb(self, widget):
+ if self.palette:
+ self.palette.popdown(True)
+
diff --git a/src/sugar/graphics/toolcombobox.py b/src/sugar/graphics/toolcombobox.py
new file mode 100644
index 0000000..16e14bd
--- /dev/null
+++ b/src/sugar/graphics/toolcombobox.py
@@ -0,0 +1,63 @@
+# 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/src/sugar/graphics/tray.py b/src/sugar/graphics/tray.py
new file mode 100644
index 0000000..5df7170
--- /dev/null
+++ b/src/sugar/graphics/tray.py
@@ -0,0 +1,461 @@
+# 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/src/sugar/graphics/window.py b/src/sugar/graphics/window.py
new file mode 100644
index 0000000..1ad2bca
--- /dev/null
+++ b/src/sugar/graphics/window.py
@@ -0,0 +1,216 @@
+# 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
+
+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('window-state-event', self.__window_state_event_cb)
+ self.connect('key-press-event', self.__key_press_cb)
+
+ self.toolbox = 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.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)
+
+ def set_canvas(self, canvas):
+ if self.canvas:
+ self._event_box.remove(self.canvas)
+
+ if canvas:
+ self._event_box.add(canvas)
+
+ self.canvas = canvas
+
+ def set_toolbox(self, toolbox):
+ if self.toolbox:
+ self._vbox.remove(self.toolbox)
+
+ self._vbox.pack_start(toolbox, False)
+ self._vbox.reorder_child(toolbox, 0)
+
+ self.toolbox = toolbox
+
+ 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.toolbox 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.toolbox 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 __window_state_event_cb(self, window, event):
+ if not (event.changed_mask & gtk.gdk.WINDOW_STATE_FULLSCREEN):
+ return False
+
+ if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
+ if self.toolbox is not None:
+ self.toolbox.hide()
+ if self.tray is not None:
+ self.tray.hide()
+
+ self._is_fullscreen = True
+ if self.props.enable_fullscreen_mode:
+ self._unfullscreen_button.show()
+
+ else:
+ if self.toolbox is not None:
+ self.toolbox.show()
+ if self.tray is not None:
+ self.tray.show()
+
+ self._is_fullscreen = False
+ if self.props.enable_fullscreen_mode:
+ self._unfullscreen_button.hide()
+
+ def __key_press_cb(self, widget, event):
+ key = gtk.gdk.keyval_name(event.keyval)
+ if event.state & gtk.gdk.MOD1_MASK:
+ if 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 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)
+
diff --git a/src/sugar/graphics/xocolor.py b/src/sugar/graphics/xocolor.py
new file mode 100644
index 0000000..beb9565
--- /dev/null
+++ b/src/sugar/graphics/xocolor.py
@@ -0,0 +1,259 @@
+# 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
+
+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 or not is_valid(color_string):
+ 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/src/sugar/gsm-app.c b/src/sugar/gsm-app.c
new file mode 100644
index 0000000..3a63bac
--- /dev/null
+++ b/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/src/sugar/gsm-app.h b/src/sugar/gsm-app.h
new file mode 100644
index 0000000..32ee62d
--- /dev/null
+++ b/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/src/sugar/gsm-client-xsmp.c b/src/sugar/gsm-client-xsmp.c
new file mode 100644
index 0000000..04c7de4
--- /dev/null
+++ b/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/src/sugar/gsm-client-xsmp.h b/src/sugar/gsm-client-xsmp.h
new file mode 100644
index 0000000..46e34a5
--- /dev/null
+++ b/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/src/sugar/gsm-client.c b/src/sugar/gsm-client.c
new file mode 100644
index 0000000..31554d4
--- /dev/null
+++ b/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/src/sugar/gsm-client.h b/src/sugar/gsm-client.h
new file mode 100644
index 0000000..3e4e751
--- /dev/null
+++ b/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/src/sugar/gsm-session.c b/src/sugar/gsm-session.c
new file mode 100644
index 0000000..0fe2fb5
--- /dev/null
+++ b/src/sugar/gsm-session.c
@@ -0,0 +1,497 @@
+/* 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);
+}
+
+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/src/sugar/gsm-session.h b/src/sugar/gsm-session.h
new file mode 100644
index 0000000..d4880a9
--- /dev/null
+++ b/src/sugar/gsm-session.h
@@ -0,0 +1,95 @@
+/* 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);
+
+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/src/sugar/gsm-xsmp.c b/src/sugar/gsm-xsmp.c
new file mode 100644
index 0000000..aa9dff1
--- /dev/null
+++ b/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/src/sugar/gsm-xsmp.h b/src/sugar/gsm-xsmp.h
new file mode 100644
index 0000000..b4b535f
--- /dev/null
+++ b/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/src/sugar/network.py b/src/sugar/network.py
new file mode 100644
index 0000000..0e25d73
--- /dev/null
+++ b/src/sugar/network.py
@@ -0,0 +1,297 @@
+# 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/src/sugar/presence/Makefile.am b/src/sugar/presence/Makefile.am
new file mode 100644
index 0000000..0c4368b
--- /dev/null
+++ b/src/sugar/presence/Makefile.am
@@ -0,0 +1,9 @@
+sugardir = $(pythondir)/sugar/presence
+sugar_PYTHON = \
+ __init__.py \
+ activity.py \
+ buddy.py \
+ sugartubeconn.py \
+ tubeconn.py \
+ presenceservice.py
+
diff --git a/src/sugar/presence/__init__.py b/src/sugar/presence/__init__.py
new file mode 100644
index 0000000..1136c19
--- /dev/null
+++ b/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/src/sugar/presence/activity.py b/src/sugar/presence/activity.py
new file mode 100644
index 0000000..dc02aa1
--- /dev/null
+++ b/src/sugar/presence/activity.py
@@ -0,0 +1,410 @@
+# 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.
+
+"""UI interface to an activity in the presence service
+
+STABLE.
+"""
+
+import logging
+
+import dbus
+import gobject
+import telepathy
+
+_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),
+ }
+
+ _PRESENCE_SERVICE = "org.laptop.Sugar.Presence"
+ _ACTIVITY_DBUS_INTERFACE = "org.laptop.Sugar.Presence.Activity"
+
+ def __init__(self, bus, new_obj_cb, del_obj_cb, object_path):
+ """Initialse the activity interface, connecting to service"""
+ gobject.GObject.__init__(self)
+ self._telepathy_room_handle = None
+ self._object_path = object_path
+ self._ps_new_object = new_obj_cb
+ self._ps_del_object = del_obj_cb
+ bobj = bus.get_object(self._PRESENCE_SERVICE, object_path)
+ self._activity = dbus.Interface(bobj, self._ACTIVITY_DBUS_INTERFACE)
+ self._activity.connect_to_signal('BuddyHandleJoined',
+ self._buddy_handle_joined_cb)
+ self._activity.connect_to_signal('BuddyLeft',
+ self._buddy_left_cb)
+ self._activity.connect_to_signal('NewChannel', self._new_channel_cb)
+ self._activity.connect_to_signal('PropertiesChanged',
+ self._properties_changed_cb,
+ utf8_strings=True)
+ # FIXME: this *would* just use a normal proxy call, but I want the
+ # pending call object so I can block on it, and normal proxy methods
+ # don't return those as of dbus-python 0.82.1; so do it the hard way
+ self._get_properties_call = bus.call_async(self._PRESENCE_SERVICE,
+ object_path, self._ACTIVITY_DBUS_INTERFACE, 'GetProperties',
+ '', (), self._get_properties_reply_cb,
+ self._get_properties_error_cb, utf8_strings=True)
+
+ self._id = None
+ self._color = None
+ self._name = None
+ self._type = None
+ self._tags = None
+ self._private = True
+ self._joined = False
+ # Cache for get_buddy_by_handle, maps handles to buddy object paths
+ self._handle_to_buddy_path = {}
+ self._buddy_path_to_handle = {}
+
+ # Set up by set_up_tubes()
+ self.telepathy_conn = None
+ self.telepathy_tubes_chan = None
+ self.telepathy_text_chan = None
+ self._telepathy_room = None
+
+ def __repr__(self):
+ return ('<proxy for %s at %x>' % (self._object_path, id(self)))
+
+ def _get_properties_reply_cb(self, new_props):
+ self._get_properties_call = None
+ _logger.debug('%r: initial GetProperties returned', self)
+ self._properties_changed_cb(new_props)
+
+ def _get_properties_error_cb(self, e):
+ self._get_properties_call = None
+ # FIXME: do something with the error
+ _logger.warning('%r: Error doing initial GetProperties: %s', self, e)
+
+ def _properties_changed_cb(self, new_props):
+ _logger.debug('%r: Activity properties changed to %r', 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
+
+ # FIXME: need an asynchronous API to set these properties, particularly
+ # 'private'
+ def do_set_property(self, pspec, val):
+ """Set a particular property in our property dictionary"""
+ if pspec.name == "name":
+ self._activity.SetProperties({'name': val})
+ self._name = val
+ elif pspec.name == "color":
+ self._activity.SetProperties({'color': val})
+ self._color = val
+ elif pspec.name == "tags":
+ self._activity.SetProperties({'tags': val})
+ self._tags = val
+ elif pspec.name == "private":
+ self._activity.SetProperties({'private': val})
+ self._private = val
+
+ def set_private(self, val, reply_handler, error_handler):
+ self._activity.SetProperties({'private': bool(val)},
+ reply_handler=reply_handler,
+ error_handler=error_handler)
+
+ def _emit_buddy_joined_signal(self, object_path):
+ """Generate buddy-joined GObject signal with presence Buddy object"""
+ self.emit('buddy-joined', self._ps_new_object(object_path))
+ return False
+
+ def _buddy_handle_joined_cb(self, object_path, handle):
+ _logger.debug('%r: buddy %s joined with handle %u', self, object_path,
+ handle)
+ gobject.idle_add(self._emit_buddy_joined_signal, object_path)
+ self._handle_to_buddy_path[handle] = object_path
+ self._buddy_path_to_handle[object_path] = handle
+
+ def _emit_buddy_left_signal(self, object_path):
+ """Generate buddy-left GObject signal with presence Buddy object
+
+ XXX note use of _ps_new_object instead of _ps_del_object here
+ """
+ self.emit('buddy-left', self._ps_new_object(object_path))
+ return False
+
+ def _buddy_left_cb(self, object_path):
+ _logger.debug('%r: buddy %s left', self, object_path)
+ gobject.idle_add(self._emit_buddy_left_signal, object_path)
+ handle = self._buddy_path_to_handle.pop(object_path, None)
+ if handle:
+ self._handle_to_buddy_path.pop(handle, None)
+
+ def _emit_new_channel_signal(self, object_path):
+ """Generate new-channel GObject signal with channel object path
+
+ New telepathy-python communications channel has been opened
+ """
+ self.emit('new-channel', object_path)
+ return False
+
+ def _new_channel_cb(self, object_path):
+ _logger.debug('%r: new channel created at %s', self, object_path)
+ gobject.idle_add(self._emit_new_channel_signal, object_path)
+
+ 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.
+ """
+ resp = self._activity.GetJoinedBuddies()
+ buddies = []
+ for item in resp:
+ try:
+ buddies.append(self._ps_new_object(item))
+ except dbus.DBusException:
+ _logger.debug(
+ 'get_joined_buddies failed to get buddy object for %r',
+ item)
+ return buddies
+
+ 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.
+ """
+ op = buddy.object_path()
+ _logger.debug('%r: inviting %s', self, op)
+ self._activity.Invite(op, message,
+ reply_handler=lambda: response_cb(None),
+ error_handler=response_cb)
+
+ # Joining and sharing (FIXME: sharing is actually done elsewhere)
+
+ def set_up_tubes(self, reply_handler, error_handler):
+
+ chans = []
+
+ def tubes_ready():
+ if self.telepathy_text_chan is None or \
+ self.telepathy_tubes_chan is None:
+ return
+
+ _logger.debug('%r: finished setting up tubes', self)
+ reply_handler()
+
+ def tubes_chan_ready(chan):
+ _logger.debug('%r: Tubes channel %r is ready', self, chan)
+ self.telepathy_tubes_chan = chan
+ tubes_ready()
+
+ def text_chan_ready(chan):
+ _logger.debug('%r: Text channel %r is ready', self, chan)
+ self.telepathy_text_chan = chan
+ tubes_ready()
+
+ def conn_ready(conn):
+ _logger.debug('%r: Connection %r is ready', self, conn)
+ self.telepathy_conn = conn
+ found_text_channel = False
+ found_tubes_channel = False
+
+ for chan_path, chan_iface, handle_type, handle_ in chans:
+ if handle_type != telepathy.HANDLE_TYPE_ROOM:
+ return
+
+ if chan_iface == telepathy.CHANNEL_TYPE_TEXT:
+ telepathy.client.Channel(
+ conn.service_name, chan_path,
+ ready_handler=text_chan_ready,
+ error_handler=error_handler)
+ found_text_channel = True
+
+ elif chan_iface == telepathy.CHANNEL_TYPE_TUBES:
+ telepathy.client.Channel(
+ conn.service_name, chan_path,
+ ready_handler=tubes_chan_ready,
+ error_handler=error_handler)
+ found_tubes_channel = True
+
+ if not found_text_channel:
+ error_handler(AssertionError("Presence Service didn't create "
+ "a chatroom"))
+ elif not found_tubes_channel:
+ error_handler(AssertionError("Presence Service didn't create "
+ "tubes channel"))
+
+ def channels_listed(bus_name, conn_path, channels):
+ _logger.debug('%r: Connection on %s at %s, channels: %r',
+ self, bus_name, conn_path, channels)
+
+ # can't use assignment for this due to Python scoping
+ chans.extend(channels)
+
+ telepathy.client.Connection(bus_name, conn_path,
+ ready_handler=conn_ready,
+ error_handler=error_handler)
+
+
+ self._activity.ListChannels(reply_handler=channels_listed,
+ error_handler=error_handler)
+
+ def _join_cb(self):
+ _logger.debug('%r: Join finished', self)
+ self._joined = True
+ self.emit("joined", True, None)
+
+ def _join_error_cb(self, err):
+ _logger.debug('%r: Join failed because: %s', self, err)
+ self.emit("joined", False, str(err))
+
+ def join(self):
+ """Join this activity.
+
+ Emits 'joined' and otherwise does nothing if we're already joined.
+ """
+ if self._joined:
+ self.emit("joined", True, None)
+ return
+
+ _logger.debug('%r: joining', self)
+
+ def joined():
+ self.set_up_tubes(reply_handler=self._join_cb,
+ error_handler=self._join_error_cb)
+
+ self._activity.Join(reply_handler=joined,
+ error_handler=self._join_error_cb)
+
+ # 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, connection, channels) = self._activity.GetChannels()
+ _logger.debug('%r: bus name is %s, connection is %s, channels are %r',
+ self, bus_name, connection, channels)
+ return bus_name, connection, channels
+
+ # Leaving
+
+ def _leave_cb(self):
+ """Callback for async action of leaving shared activity."""
+ self.emit("joined", False, "left activity")
+
+ def _leave_error_cb(self, err):
+ """Callback for error in async leaving of shared activity."""
+ _logger.debug('Failed to leave activity: %s', err)
+
+ def leave(self):
+ """Leave this shared activity"""
+ _logger.debug('%r: leaving', self)
+ self._joined = False
+ self._activity.Leave(reply_handler=self._leave_cb,
+ error_handler=self._leave_error_cb)
diff --git a/src/sugar/presence/buddy.py b/src/sugar/presence/buddy.py
new file mode 100644
index 0000000..fab23d2
--- /dev/null
+++ b/src/sugar/presence/buddy.py
@@ -0,0 +1,239 @@
+# 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.
+
+"""UI interface to a buddy in the presence service
+
+STABLE.
+"""
+
+import gobject
+import gtk
+import dbus
+
+class Buddy(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?),
+ 'icon': (XXX pixel data for an icon?)
+ See __gproperties__
+ """
+ __gsignals__ = {
+ 'icon-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([])),
+ '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])),
+ }
+
+ __gproperties__ = {
+ 'key' : (str, None, None, None, gobject.PARAM_READABLE),
+ 'icon' : (str, None, None, None, gobject.PARAM_READABLE),
+ 'nick' : (str, None, None, None, gobject.PARAM_READABLE),
+ 'color' : (str, None, None, None, gobject.PARAM_READABLE),
+ 'current-activity' : (object, None, None, gobject.PARAM_READABLE),
+ 'owner' : (bool, None, None, False, gobject.PARAM_READABLE),
+ 'ip4-address' : (str, None, None, None, gobject.PARAM_READABLE)
+ }
+
+ _PRESENCE_SERVICE = "org.laptop.Sugar.Presence"
+ _BUDDY_DBUS_INTERFACE = "org.laptop.Sugar.Presence.Buddy"
+
+ def __init__(self, bus, new_obj_cb, del_obj_cb, object_path):
+ """Initialise the reference to the buddy
+
+ bus -- dbus bus object
+ new_obj_cb -- callback to call when this buddy joins an activity
+ del_obj_cb -- callback to call when this buddy leaves an activity
+ object_path -- path to the buddy object
+ """
+ gobject.GObject.__init__(self)
+ self._object_path = object_path
+ self._ps_new_object = new_obj_cb
+ self._ps_del_object = del_obj_cb
+ self._properties = {}
+ self._activities = {}
+
+ bobj = bus.get_object(self._PRESENCE_SERVICE, object_path)
+ self._buddy = dbus.Interface(bobj, self._BUDDY_DBUS_INTERFACE)
+
+ self._icon_changed_signal = self._buddy.connect_to_signal(
+ 'IconChanged', self._icon_changed_cb, byte_arrays=True)
+ self._joined_activity_signal = self._buddy.connect_to_signal(
+ 'JoinedActivity', self._joined_activity_cb)
+ self._left_activity_signal = self._buddy.connect_to_signal(
+ 'LeftActivity', self._left_activity_cb)
+ self._property_changed_signal = self._buddy.connect_to_signal(
+ 'PropertyChanged', self._property_changed_cb)
+
+ self._properties = self._get_properties_helper()
+
+ activities = self._buddy.GetJoinedActivities()
+ for op in activities:
+ self._activities[op] = self._ps_new_object(op)
+ self._icon = None
+
+ def destroy(self):
+ self._icon_changed_signal.remove()
+ self._joined_activity_signal.remove()
+ self._left_activity_signal.remove()
+ self._property_changed_signal.remove()
+
+ def _get_properties_helper(self):
+ """Retrieve the Buddy's property dictionary from the service object
+ """
+ props = self._buddy.GetProperties(byte_arrays=True)
+ if not props:
+ return {}
+ return props
+
+ def do_get_property(self, pspec):
+ """Retrieve a particular property from our property dictionary
+
+ pspec -- XXX some sort of GTK specifier object with attributes
+ including 'name', 'active' and 'icon-name'
+ """
+ if pspec.name == "key":
+ return self._properties["key"]
+ elif pspec.name == "nick":
+ return self._properties["nick"]
+ elif pspec.name == "color":
+ return self._properties["color"]
+ elif pspec.name == "current-activity":
+ if not self._properties.has_key("current-activity"):
+ return None
+ curact = self._properties["current-activity"]
+ if not len(curact):
+ return None
+ for activity in self._activities.values():
+ if activity.props.id == curact:
+ return activity
+ return None
+ elif pspec.name == "owner":
+ return self._properties["owner"]
+ elif pspec.name == "icon":
+ if not self._icon:
+ self._icon = str(self._buddy.GetIcon(byte_arrays=True))
+ return self._icon
+ elif pspec.name == "ip4-address":
+ # IPv4 address will go away quite soon
+ if not self._properties.has_key("ip4-address"):
+ return None
+ return self._properties["ip4-address"]
+
+ def object_path(self):
+ """Retrieve our dbus object path"""
+ return self._object_path
+
+ def _emit_icon_changed_signal(self, bytes):
+ """Emit GObject signal when icon has changed"""
+ self._icon = str(bytes)
+ self.emit('icon-changed')
+ return False
+
+ def _icon_changed_cb(self, icon_data):
+ """Handle dbus signal by emitting a GObject signal"""
+ gobject.idle_add(self._emit_icon_changed_signal, icon_data)
+
+ def _emit_joined_activity_signal(self, object_path):
+ """Emit activity joined signal with Activity object"""
+ self.emit('joined-activity', self._ps_new_object(object_path))
+ return False
+
+ def _joined_activity_cb(self, object_path):
+ """Handle dbus signal by emitting a GObject signal
+
+ Stores the activity in activities dictionary as well
+ """
+ if not self._activities.has_key(object_path):
+ self._activities[object_path] = self._ps_new_object(object_path)
+ gobject.idle_add(self._emit_joined_activity_signal, object_path)
+
+ def _emit_left_activity_signal(self, object_path):
+ """Emit activity left signal with Activity object
+
+ XXX this calls self._ps_new_object instead of self._ps_del_object,
+ which would seem to be the incorrect callback?
+ """
+ self.emit('left-activity', self._ps_new_object(object_path))
+ return False
+
+ def _left_activity_cb(self, object_path):
+ """Handle dbus signal by emitting a GObject signal
+
+ Also removes from the activities dictionary
+ """
+ if self._activities.has_key(object_path):
+ del self._activities[object_path]
+ gobject.idle_add(self._emit_left_activity_signal, object_path)
+
+ def _handle_property_changed_signal(self, prop_list):
+ """Emit property-changed signal with property dictionary
+
+ Generates a property-changed signal with the results of
+ _get_properties_helper()
+ """
+ self._properties = self._get_properties_helper()
+ # FIXME: don't leak unexposed property names
+ self.emit('property-changed', prop_list)
+ return False
+
+ def _property_changed_cb(self, prop_list):
+ """Handle dbus signal by emitting a GObject signal"""
+ gobject.idle_add(self._handle_property_changed_signal, prop_list)
+
+ def get_icon_pixbuf(self):
+ """Retrieve Buddy's icon as a GTK pixel buffer
+
+ XXX Why aren't the icons coming in as SVG?
+ """
+ if self.props.icon and len(self.props.icon):
+ pbl = gtk.gdk.PixbufLoader()
+ pbl.write(self.props.icon)
+ pbl.close()
+ return pbl.get_pixbuf()
+ else:
+ return None
+
+ def get_joined_activities(self):
+ """Retrieve the set of all activities which this buddy has joined
+
+ Uses the GetJoinedActivities method on the service
+ object to produce object paths, wraps each in an
+ Activity object.
+
+ returns list of presence Activity objects
+ """
+ try:
+ resp = self._buddy.GetJoinedActivities()
+ except dbus.exceptions.DBusException:
+ return []
+ acts = []
+ for item in resp:
+ acts.append(self._ps_new_object(item))
+ return acts
diff --git a/src/sugar/presence/presenceservice.py b/src/sugar/presence/presenceservice.py
new file mode 100644
index 0000000..a7fd1a4
--- /dev/null
+++ b/src/sugar/presence/presenceservice.py
@@ -0,0 +1,609 @@
+# 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.
+
+"""UI class to access system-level presence object
+
+STABLE.
+"""
+
+import logging
+import traceback
+
+import dbus
+import dbus.exceptions
+import dbus.glib
+import gobject
+
+from sugar.presence.buddy import Buddy
+from sugar.presence.activity import Activity
+
+
+DBUS_SERVICE = "org.laptop.Sugar.Presence"
+DBUS_INTERFACE = "org.laptop.Sugar.Presence"
+DBUS_PATH = "/org/laptop/Sugar/Presence"
+
+_logger = logging.getLogger('sugar.presence.presenceservice')
+
+
+class PresenceService(gobject.GObject):
+ """UI-side interface to the dbus presence service
+
+ This class provides UI programmers with simplified access
+ to the dbus service of the same name. It allows for observing
+ various events from the presence service as GObject events,
+ as well as some basic introspection queries.
+ """
+ __gsignals__ = {
+ 'buddy-appeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ 'buddy-disappeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ # parameters: (activity: Activity, inviter: Buddy, message: unicode)
+ 'activity-invitation': (gobject.SIGNAL_RUN_FIRST, None, ([object]*3)),
+ 'private-invitation': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT,
+ gobject.TYPE_PYOBJECT, str])),
+ 'activity-appeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ 'activity-disappeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ 'activity-shared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT,
+ gobject.TYPE_PYOBJECT]))
+ }
+
+ _PS_BUDDY_OP = DBUS_PATH + "/Buddies/"
+ _PS_ACTIVITY_OP = DBUS_PATH + "/Activities/"
+
+
+ def __init__(self, allow_offline_iface=True):
+ """Initialise the service and attempt to connect to events
+ """
+ gobject.GObject.__init__(self)
+ self._objcache = {}
+ self._joined = None
+
+ # Get a connection to the session bus
+ self._bus = dbus.SessionBus()
+ self._bus.add_signal_receiver(self._name_owner_changed_cb,
+ signal_name="NameOwnerChanged",
+ dbus_interface="org.freedesktop.DBus")
+
+ # attempt to load the interface to the service...
+ self._allow_offline_iface = allow_offline_iface
+ self._get_ps()
+
+ def _name_owner_changed_cb(self, name, old, new):
+ if name != DBUS_SERVICE:
+ return
+ if (old and len(old)) and (not new and not len(new)):
+ # PS went away, clear out PS dbus service wrapper
+ self._ps_ = None
+ elif (not old and not len(old)) and (new and len(new)):
+ # PS started up
+ self._get_ps()
+
+ _ps_ = None
+ def _get_ps(self):
+ """Retrieve dbus interface to PresenceService
+
+ Also registers for updates from various dbus events on the
+ interface.
+
+ If unable to retrieve the interface, we will temporarily
+ return an _OfflineInterface object to allow the calling
+ code to continue functioning as though it had accessed a
+ real presence service.
+
+ If successful, caches the presence service interface
+ for use by other methods and returns that interface
+ """
+ if not self._ps_:
+ try:
+ # NOTE: We need to follow_name_owner_changes here
+ # because we can not connect to a signal unless
+ # we follow the changes or we start the service
+ # before we connect. Starting the service here
+ # causes a major bottleneck during startup
+ ps = dbus.Interface(
+ self._bus.get_object(DBUS_SERVICE,
+ DBUS_PATH,
+ follow_name_owner_changes=True),
+ DBUS_INTERFACE
+ )
+ except dbus.exceptions.DBusException, err:
+ _logger.error(
+ """Failure retrieving %r interface from
+ the D-BUS service %r %r: %s""",
+ DBUS_INTERFACE, DBUS_SERVICE, DBUS_PATH, err
+ )
+ if self._allow_offline_iface:
+ return _OfflineInterface()
+ raise RuntimeError("Failed to connect to the presence service.")
+ else:
+ self._ps_ = ps
+ ps.connect_to_signal('BuddyAppeared',
+ self._buddy_appeared_cb)
+ ps.connect_to_signal('BuddyDisappeared',
+ self._buddy_disappeared_cb)
+ ps.connect_to_signal('ActivityAppeared',
+ self._activity_appeared_cb)
+ ps.connect_to_signal('ActivityDisappeared',
+ self._activity_disappeared_cb)
+ ps.connect_to_signal('ActivityInvitation',
+ self._activity_invitation_cb)
+ ps.connect_to_signal('PrivateInvitation',
+ self._private_invitation_cb)
+ return self._ps_
+
+ _ps = property(
+ _get_ps, None, None,
+ """DBUS interface to the PresenceService
+ (services/presence/presenceservice)"""
+ )
+
+ 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 _emit_buddy_appeared_signal(self, object_path):
+ """Emit GObject event with presence.buddy.Buddy object"""
+ self.emit('buddy-appeared', self._new_object(object_path))
+ return False
+
+ def _buddy_appeared_cb(self, op):
+ """Callback for dbus event (forwards to method to emit GObject event)"""
+ gobject.idle_add(self._emit_buddy_appeared_signal, op)
+
+ def _emit_buddy_disappeared_signal(self, object_path):
+ """Emit GObject event with presence.buddy.Buddy object"""
+ # Don't try to create a new object here if needed; it will probably
+ # fail anyway because the object has already been destroyed in the PS
+ if self._have_object(object_path):
+ obj = self._objcache[object_path]
+ self.emit('buddy-disappeared', obj)
+
+ # We cannot maintain the object in the cache because that would keep
+ # a lot of objects from being collected. That includes UI objects
+ # due to signals using strong references.
+ # If we want to cache some despite the memory usage increase,
+ # we could use a LRU cache limited to some value.
+ del self._objcache[object_path]
+ obj.destroy()
+
+ return False
+
+ def _buddy_disappeared_cb(self, object_path):
+ """Callback for dbus event (forwards to method to emit GObject event)"""
+ gobject.idle_add(self._emit_buddy_disappeared_signal, object_path)
+
+ def _emit_activity_invitation_signal(self, activity_path, buddy_path,
+ message):
+ """Emit GObject event with presence.activity.Activity object"""
+ self.emit('activity-invitation', self._new_object(activity_path),
+ self._new_object(buddy_path), unicode(message))
+ return False
+
+ def _activity_invitation_cb(self, activity_path, buddy_path, message):
+ """Callback for dbus event (forwards to method to emit GObject event)"""
+ gobject.idle_add(self._emit_activity_invitation_signal, activity_path,
+ buddy_path, message)
+
+ def _emit_private_invitation_signal(self, bus_name, connection,
+ channel, chan_type):
+ """Emit GObject event with bus_name, connection and channel"""
+ self.emit('private-invitation', bus_name, connection,
+ channel, chan_type)
+ return False
+
+ def _private_invitation_cb(self, bus_name, connection, channel, chan_type):
+ """Callback for dbus event (forwards to method to emit GObject event)"""
+ gobject.idle_add(self._emit_private_invitation_signal, bus_name,
+ connection, channel, chan_type)
+
+ def _emit_activity_appeared_signal(self, object_path):
+ """Emit GObject event with presence.activity.Activity object"""
+ self.emit('activity-appeared', self._new_object(object_path))
+ return False
+
+ def _activity_appeared_cb(self, object_path):
+ """Callback for dbus event (forwards to method to emit GObject event)"""
+ gobject.idle_add(self._emit_activity_appeared_signal, object_path)
+
+ def _emit_activity_disappeared_signal(self, object_path):
+ """Emit GObject event with presence.activity.Activity object"""
+ self.emit('activity-disappeared', self._new_object(object_path))
+ return False
+
+ def _activity_disappeared_cb(self, object_path):
+ """Callback for dbus event (forwards to method to emit GObject event)"""
+ gobject.idle_add(self._emit_activity_disappeared_signal, 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)
+ """
+ try:
+ resp = self._ps.GetActivities()
+ except dbus.exceptions.DBusException, err:
+ _logger.warn(
+ """Unable to retrieve activity list from presence service: %s"""
+ % err
+ )
+ return []
+ else:
+ 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
+ """
+ try:
+ act_op = self._ps.GetActivityById(activity_id)
+ except dbus.exceptions.DBusException, err:
+ if warn_if_none:
+ _logger.warn("Unable to retrieve activity handle for %r from "
+ "presence service: %s", activity_id, err)
+ return None
+ return self._new_object(act_op)
+
+ 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, err:
+ _logger.warn(
+ """Unable to retrieve buddy-list from presence service: %s"""
+ % err
+ )
+ 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, key):
+ """Retrieve single Buddy object for the given public key
+
+ key -- buddy's public encryption key
+
+ returns single Buddy object or None if the activity
+ is not found using GetBuddyByPublicKey on the
+ service
+ """
+ try:
+ buddy_op = self._ps.GetBuddyByPublicKey(dbus.ByteArray(key))
+ except dbus.exceptions.DBusException, err:
+ _logger.warn(
+ """Unable to retrieve buddy handle
+ for %r from presence service: %s"""
+ % key, err
+ )
+ return None
+ return self._new_object(buddy_op)
+
+ 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
+ """
+ try:
+ buddy_op = self._ps.GetBuddyByTelepathyHandle(tp_conn_name,
+ tp_conn_path,
+ handle)
+ except dbus.exceptions.DBusException, err:
+ _logger.warn('Unable to retrieve buddy handle for handle %u at '
+ 'conn %s:%s from presence service: %s',
+ handle, tp_conn_name, tp_conn_path, err)
+ return None
+ return self._new_object(buddy_op)
+
+ def get_owner(self):
+ """Retrieves the laptop "owner" Buddy object."""
+ try:
+ owner_op = self._ps.GetOwner()
+ except dbus.exceptions.DBusException, err:
+ _logger.warn(
+ """Unable to retrieve local user/owner
+ from presence service: %s"""
+ % err
+ )
+ raise RuntimeError("Could not get owner object.")
+ return self._new_object(owner_op)
+
+ def _share_activity_cb(self, activity, op):
+ """Finish sharing the activity
+ """
+ # FIXME find a better way to shutup pylint
+ psact = self._new_object(op)
+ psact._joined = True
+ _logger.debug('%r: Just shared, setting up tubes', activity)
+ psact.set_up_tubes(reply_handler=lambda:
+ self.emit("activity-shared", True, psact, None),
+ error_handler=lambda e:
+ self._share_activity_error_cb(activity, e))
+
+ def _share_activity_error_cb(self, activity, err):
+ """Notify with GObject event of unsuccessful sharing of activity"""
+ _logger.debug("Error sharing activity %s: %s" %
+ (activity.get_id(), err))
+ self.emit("activity-shared", False, None, err)
+
+ def share_activity(self, activity, properties=None, private=True):
+ """Ask presence service to ask the activity to share itself publicly.
+
+ Uses the AdvertiseActivity method on the service to ask for the
+ sharing of the given activity. Arranges to emit activity-shared
+ event with:
+
+ (success, Activity, err)
+
+ on success/failure.
+
+ returns None
+ """
+ actid = activity.get_id()
+
+ if properties is None:
+ properties = {}
+
+ # Ensure the activity is not already shared/joined
+ for obj in self._objcache.values():
+ if not isinstance(object, Activity):
+ continue
+ if obj.props.id == actid or obj.props.joined:
+ raise RuntimeError("Activity %s is already shared." %
+ actid)
+
+ atype = activity.get_bundle_id()
+ name = activity.props.title
+ properties['private'] = bool(private)
+ self._ps.ShareActivity(actid, atype, name, properties,
+ reply_handler=lambda op: \
+ self._share_activity_cb(activity, op),
+ error_handler=lambda e: \
+ self._share_activity_error_cb(activity, e))
+
+ 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"""
+
+ try:
+ bus_name, object_path = self._ps.GetPreferredConnection()
+ except dbus.exceptions.DBusException:
+ logging.error(traceback.format_exc())
+ return None
+
+ return bus_name, object_path
+
+class _OfflineInterface( object ):
+ """Offline-presence-service interface
+
+ Used to mimic the behaviour of a real PresenceService sufficiently
+ to avoid crashing client code that expects the given interface.
+
+ XXX we could likely return a "MockOwner" object reasonably
+ easily, but would it be worth it?
+ """
+ def raiseException( self, *args, **named ):
+ """Raise dbus.exceptions.DBusException"""
+ raise dbus.exceptions.DBusException(
+ """PresenceService Interface not available"""
+ )
+ GetActivities = raiseException
+ GetActivityById = raiseException
+ GetBuddies = raiseException
+ GetBuddyByPublicKey = raiseException
+ GetOwner = raiseException
+ GetPreferredConnection = raiseException
+ def ShareActivity(
+ self, actid, atype, name, properties,
+ reply_handler, error_handler,
+ ):
+ """Pretend to share and fail..."""
+ exc = IOError(
+ """Unable to share activity as PresenceService
+ is not currenly available"""
+ )
+ return error_handler( exc )
+
+class _MockPresenceService(gobject.GObject):
+ """Test fixture allowing testing of items that use PresenceService
+
+ See PresenceService for usage and purpose
+ """
+ __gsignals__ = {
+ 'buddy-appeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ 'buddy-disappeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ 'activity-invitation': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ 'private-invitation': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT,
+ gobject.TYPE_PYOBJECT])),
+ 'activity-appeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT])),
+ 'activity-disappeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_PYOBJECT]))
+ }
+
+ def __init__(self):
+ gobject.GObject.__init__(self)
+
+ def get_activities(self):
+ return []
+
+ def get_activity(self, activity_id):
+ return None
+
+ def get_buddies(self):
+ return []
+
+ def get_buddy(self, key):
+ return None
+
+ def get_owner(self):
+ return None
+
+ def share_activity(self, activity, properties=None):
+ return None
+
+_ps = None
+def get_instance(allow_offline_iface=False):
+ """Retrieve this process' view of the PresenceService"""
+ global _ps
+ if not _ps:
+ _ps = PresenceService(allow_offline_iface)
+ return _ps
+
diff --git a/src/sugar/presence/sugartubeconn.py b/src/sugar/presence/sugartubeconn.py
new file mode 100644
index 0000000..954ef67
--- /dev/null
+++ b/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/src/sugar/presence/test_presence.txt b/src/sugar/presence/test_presence.txt
new file mode 100644
index 0000000..d0736a9
--- /dev/null
+++ b/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/src/sugar/presence/tubeconn.py b/src/sugar/presence/tubeconn.py
new file mode 100644
index 0000000..8606db6
--- /dev/null
+++ b/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):
+
+ # pylint: disable-msg=W0212
+ # Confused by __new__
+ def __new__(cls, conn, tubes_iface, tube_id, address=None,
+ group_iface=None, mainloop=None):
+ 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
+
+ # pylint: disable-msg=W0201
+ # Confused by __new__
+ def _on_get_self_handle_reply(self, handle):
+ 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/src/sugar/profile.py b/src/sugar/profile.py
new file mode 100644
index 0000000..cce45a7
--- /dev/null
+++ b/src/sugar/profile.py
@@ -0,0 +1,216 @@
+# 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
+
+ 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')
+ try:
+ f = open(key_path, "r")
+ lines = f.readlines()
+ f.close()
+ except IOError, e:
+ logging.error("Error reading public key: %s" % e)
+ 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 _get_pubkey(self):
+ # load on-demand.
+ if not self._pubkey:
+ self._pubkey = self._load_pubkey()
+ return self._pubkey
+
+ def _hash_private_key(self):
+ key_path = os.path.join(env.get_profile_path(), 'owner.key')
+ try:
+ f = open(key_path, "r")
+ lines = f.readlines()
+ f.close()
+ except IOError, e:
+ logging.error("Error reading private key: %s" % e)
+ return None
+
+ key = ""
+ for l in lines:
+ l = l.strip()
+ if l.startswith("-----BEGIN DSA PRIVATE KEY-----"):
+ continue
+ if l.startswith("-----END DSA PRIVATE KEY-----"):
+ continue
+ key += l
+ if not len(key):
+ logging.error("Error parsing public key.")
+ return None
+
+ # hash it
+ key_hash = util.sha_data(key)
+ return util.printable_hash(key_hash)
+
+ def _get_privkey_hash(self):
+ # load on-demand.
+ if not self._privkey_hash:
+ self._privkey_hash = self._hash_private_key()
+ return self._privkey_hash
+
+ privkey_hash = property(_get_privkey_hash)
+ pubkey = property(_get_pubkey)
+
+ 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=~/.sugar/default/logs/telepathy-gabble.log\n' \
+ '#export SALUT_DEBUG=all\n' \
+ '#export' \
+ 'SALUT_LOGFILE=~/.sugar/default/logs/telepathy-salut.log\n' \
+ '#export GIBBER_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/src/sugar/session.py b/src/sugar/session.py
new file mode 100644
index 0000000..0978be8
--- /dev/null
+++ b/src/sugar/session.py
@@ -0,0 +1,50 @@
+# 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/src/sugar/sexy-icon-entry.c b/src/sugar/sexy-icon-entry.c
new file mode 100644
index 0000000..ca35209
--- /dev/null
+++ b/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/src/sugar/sexy-icon-entry.h b/src/sugar/sexy-icon-entry.h
new file mode 100644
index 0000000..eb83fed
--- /dev/null
+++ b/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/src/sugar/sugar-address-entry.c b/src/sugar/sugar-address-entry.c
new file mode 100644
index 0000000..c7555e0
--- /dev/null
+++ b/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/src/sugar/sugar-address-entry.h b/src/sugar/sugar-address-entry.h
new file mode 100644
index 0000000..60c56ab
--- /dev/null
+++ b/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/src/sugar/sugar-grid.c b/src/sugar/sugar-grid.c
new file mode 100644
index 0000000..3fa7de5
--- /dev/null
+++ b/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/src/sugar/sugar-grid.h b/src/sugar/sugar-grid.h
new file mode 100644
index 0000000..d493a60
--- /dev/null
+++ b/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/src/sugar/sugar-key-grabber.c b/src/sugar/sugar-key-grabber.c
new file mode 100644
index 0000000..89b743f
--- /dev/null
+++ b/src/sugar/sugar-key-grabber.c
@@ -0,0 +1,265 @@
+/*
+ * 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,
+ G_TYPE_BOOLEAN, 2,
+ 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,
+ G_TYPE_BOOLEAN, 2,
+ 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, &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, &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;
+
+ 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)
+{
+ char **cur = keys;
+ char *key;
+ Key *keyinfo;
+
+ gdk_error_trap_push();
+
+ while (*cur != NULL) {
+ key = *cur;
+ cur += 1;
+
+ keyinfo = g_new0 (Key, 1);
+ keyinfo->key = g_strdup(key);
+
+ egg_accelerator_parse_virtual (key, &keyinfo->keysym,
+ &keyinfo->keycode, &keyinfo->state);
+
+ grab_key(grabber, keyinfo, TRUE);
+
+ grabber->keys = g_list_append(grabber->keys, keyinfo);
+ }
+
+ gdk_flush();
+ gdk_error_trap_push();
+}
+
+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/src/sugar/sugar-key-grabber.h b/src/sugar/sugar-key-grabber.h
new file mode 100644
index 0000000..ab02870
--- /dev/null
+++ b/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/src/sugar/sugar-marshal.list b/src/sugar/sugar-marshal.list
new file mode 100644
index 0000000..41ce620
--- /dev/null
+++ b/src/sugar/sugar-marshal.list
@@ -0,0 +1 @@
+BOOLEAN:UINT,UINT
diff --git a/src/sugar/sugar-menu.c b/src/sugar/sugar-menu.c
new file mode 100644
index 0000000..f19dc4b
--- /dev/null
+++ b/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/src/sugar/sugar-menu.h b/src/sugar/sugar-menu.h
new file mode 100644
index 0000000..74ce891
--- /dev/null
+++ b/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/src/sugar/util.py b/src/sugar/util.py
new file mode 100644
index 0000000..d375b87
--- /dev/null
+++ b/src/sugar/util.py
@@ -0,0 +1,293 @@
+"""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 sha
+import random
+import binascii
+import gettext
+import tempfile
+import logging
+
+_ = 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 = sha.new()
+ 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.
+ """
+ # pylint: disable-msg=W0102,W0612
+ def __init__(self, count, pairs=[]):
+ 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
+
diff --git a/src/sugar/wm.py b/src/sugar/wm.py
new file mode 100644
index 0000000..4ec9a12
--- /dev/null
+++ b/src/sugar/wm.py
@@ -0,0 +1,46 @@
+# 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
+
+def get_activity_id(wnck_window):
+ window = gtk.gdk.window_foreign_new(wnck_window.get_xid())
+ prop_info = window.property_get('_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 = window.property_get('_SUGAR_BUNDLE_ID', 'STRING')
+ if prop_info is None:
+ return None
+ else:
+ return prop_info[2]
+
+def set_activity_id(window, activity_id):
+ window.property_change('_SUGAR_ACTIVITY_ID', 'STRING', 8,
+ gtk.gdk.PROP_MODE_REPLACE, activity_id)
+
+def set_bundle_id(window, bundle_id):
+ window.property_change('_SUGAR_BUNDLE_ID', 'STRING', 8,
+ gtk.gdk.PROP_MODE_REPLACE, bundle_id)
diff --git a/tests/graphics/common.py b/tests/graphics/common.py
new file mode 100644
index 0000000..2f00099
--- /dev/null
+++ b/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/tests/graphics/hipposcalability.py b/tests/graphics/hipposcalability.py
new file mode 100644
index 0000000..a5cebcc
--- /dev/null
+++ b/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/tests/graphics/iconcache.py b/tests/graphics/iconcache.py
new file mode 100644
index 0000000..b03ecb6
--- /dev/null
+++ b/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/tests/graphics/iconwidget.py b/tests/graphics/iconwidget.py
new file mode 100644
index 0000000..cacf501
--- /dev/null
+++ b/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/tests/graphics/ticket2855.py b/tests/graphics/ticket2855.py
new file mode 100644
index 0000000..cc4b3c0
--- /dev/null
+++ b/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/tests/graphics/ticket2999.py b/tests/graphics/ticket2999.py
new file mode 100644
index 0000000..a7b92d5
--- /dev/null
+++ b/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/tests/graphics/ticket3000.py b/tests/graphics/ticket3000.py
new file mode 100644
index 0000000..c28b2cb
--- /dev/null
+++ b/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/tests/graphics/toolbarpalettes.py b/tests/graphics/toolbarpalettes.py
new file mode 100644
index 0000000..608ef57
--- /dev/null
+++ b/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/tests/graphics/tray.py b/tests/graphics/tray.py
new file mode 100644
index 0000000..f589f4e
--- /dev/null
+++ b/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/tests/lib/runall.py b/tests/lib/runall.py
new file mode 100644
index 0000000..ae1bb3a
--- /dev/null
+++ b/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/tests/lib/test_mime.py b/tests/lib/test_mime.py
new file mode 100644
index 0000000..3df0ce6
--- /dev/null
+++ b/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()
+