Imported Upstream version 3.27.0 upstream/3.27.0
authorDongHun Kwak <dh0128.kwak@samsung.com>
Tue, 30 Oct 2018 01:29:43 +0000 (10:29 +0900)
committerDongHun Kwak <dh0128.kwak@samsung.com>
Tue, 30 Oct 2018 01:29:43 +0000 (10:29 +0900)
92 files changed:
AUTHORS [deleted file]
ChangeLog
MANIFEST.in [new file with mode: 0644]
Makefile.am
Makefile.in
NEWS
PKG-INFO
PKG-INFO.in
README.rst [moved from README with 74% similarity]
configure
configure.ac
examples/Makefile.am
examples/Makefile.in
examples/demo/demo.py [new file with mode: 0755]
examples/demo/demos/Css/__init__.py [new file with mode: 0644]
examples/demo/demos/Css/css_accordion.py [new file with mode: 0644]
examples/demo/demos/Css/css_basics.py [new file with mode: 0644]
examples/demo/demos/Css/css_multiplebgs.py [new file with mode: 0644]
examples/demo/demos/Entry/__init__.py [new file with mode: 0644]
examples/demo/demos/Entry/entry_buffer.py [new file with mode: 0644]
examples/demo/demos/Entry/entry_completion.py [new file with mode: 0644]
examples/demo/demos/Entry/search_entry.py [new file with mode: 0644]
examples/demo/demos/IconView/__init__.py [new file with mode: 0644]
examples/demo/demos/IconView/iconviewbasics.py [new file with mode: 0644]
examples/demo/demos/IconView/iconviewedit.py [new file with mode: 0644]
examples/demo/demos/TreeView/__init__.py [new file with mode: 0644]
examples/demo/demos/TreeView/liststore.py [new file with mode: 0644]
examples/demo/demos/TreeView/treemodel_filelist.py [new file with mode: 0644]
examples/demo/demos/TreeView/treemodel_filetree.py [new file with mode: 0644]
examples/demo/demos/TreeView/treemodel_large.py [new file with mode: 0644]
examples/demo/demos/__init__.py [new file with mode: 0644]
examples/demo/demos/appwindow.py [new file with mode: 0644]
examples/demo/demos/assistant.py [new file with mode: 0644]
examples/demo/demos/builder.py [new file with mode: 0644]
examples/demo/demos/button_box.py [new file with mode: 0644]
examples/demo/demos/clipboard.py [new file with mode: 0644]
examples/demo/demos/colorselector.py [new file with mode: 0644]
examples/demo/demos/combobox.py [new file with mode: 0644]
examples/demo/demos/data/alphatest.png [new file with mode: 0644]
examples/demo/demos/data/apple-red.png [new file with mode: 0644]
examples/demo/demos/data/background.jpg [new file with mode: 0644]
examples/demo/demos/data/brick.png [new file with mode: 0644]
examples/demo/demos/data/brick2.png [new file with mode: 0644]
examples/demo/demos/data/css_accordion.css [new file with mode: 0644]
examples/demo/demos/data/css_basics.css [new file with mode: 0644]
examples/demo/demos/data/css_multiplebgs.css [new file with mode: 0644]
examples/demo/demos/data/cssview.css [new file with mode: 0644]
examples/demo/demos/data/demo.gresource [new file with mode: 0644]
examples/demo/demos/data/demo.gresource.xml [new file with mode: 0644]
examples/demo/demos/data/demo.ui [new file with mode: 0644]
examples/demo/demos/data/floppybuddy.gif [new file with mode: 0644]
examples/demo/demos/data/gnome-applets.png [new file with mode: 0644]
examples/demo/demos/data/gnome-calendar.png [new file with mode: 0644]
examples/demo/demos/data/gnome-foot.png [new file with mode: 0644]
examples/demo/demos/data/gnome-fs-directory.png [new file with mode: 0644]
examples/demo/demos/data/gnome-fs-regular.png [new file with mode: 0644]
examples/demo/demos/data/gnome-gimp.png [new file with mode: 0644]
examples/demo/demos/data/gnome-gmush.png [new file with mode: 0644]
examples/demo/demos/data/gnome-gsame.png [new file with mode: 0644]
examples/demo/demos/data/gnu-keys.png [new file with mode: 0644]
examples/demo/demos/data/gtk-logo-rgb.gif [new file with mode: 0644]
examples/demo/demos/data/reset.css [new file with mode: 0644]
examples/demo/demos/dialogs.py [new file with mode: 0644]
examples/demo/demos/drawingarea.py [new file with mode: 0644]
examples/demo/demos/expander.py [new file with mode: 0644]
examples/demo/demos/flowbox.py [new file with mode: 0755]
examples/demo/demos/images.py [new file with mode: 0644]
examples/demo/demos/infobars.py [new file with mode: 0644]
examples/demo/demos/links.py [new file with mode: 0644]
examples/demo/demos/menus.py [new file with mode: 0644]
examples/demo/demos/pickers.py [new file with mode: 0644]
examples/demo/demos/pixbuf.py [new file with mode: 0644]
examples/demo/demos/printing.py [new file with mode: 0644]
examples/demo/demos/rotatedtext.py [new file with mode: 0644]
examples/demo/demos/test.py [new file with mode: 0644]
gi/_ossighelper.py [new file with mode: 0644]
gi/overrides/GLib.py
gi/overrides/Gio.py
gi/overrides/Gtk.py
m4/ax_code_coverage.m4
m4/ax_compiler_flags_cflags.m4
m4/glib-2.0.m4
pygobject-3.0-uninstalled.pc.in [deleted file]
pygobject.doap
setup.py [changed mode: 0755->0644]
tests/Makefile.am
tests/Makefile.in
tests/test_everything.py
tests/test_gi.py
tests/test_ossig.py [new file with mode: 0644]
tests/test_overrides_gtk.py
tools/pygi-convert.sh [moved from pygi-convert.sh with 100% similarity]

diff --git a/AUTHORS b/AUTHORS
deleted file mode 100644 (file)
index 76b1091..0000000
--- a/AUTHORS
+++ /dev/null
@@ -1,11 +0,0 @@
-Original Authors:
-James Henstridge <james@daa.com.au>
-Johan Dahlin <johan@gnome.org>
-
-Current Maintainers:
-Ignacio Casal Quinteiro <icq@gnome.org>
-Martin Pitt <martinpitt@gnome.org>
-Paolo Borelli <pborelli@gnome.org>
-Sebastian Pölsterl <sebp@k-d-w.org>
-Simon Feltman <s.feltman@gmail.com>
-Tomeu Vizoso <tomeu.vizoso@collabora.co.uk>
index 23d0994..c302ac3 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,4 +1,500 @@
-commit 17b4ba8707a8c6c24cd52e59a1f107dccfa9fd55
+commit eb2ff4362878d0e348c67c606a32e8d332e2454d
+Author: Christoph Reiter <creiter@src.gnome.org>
+Date:   Fri Dec 8 16:56:10 2017 +0100
+
+    demo: pep8 fixes
+
+    The demo app now gets analyzed by pep8 since it was moved to the
+    examples.
+
+ examples/demo/demos/Css/css_accordion.py           |    1 +
+ examples/demo/demos/Entry/entry_buffer.py          |    1 +
+ examples/demo/demos/Entry/entry_completion.py      |    1 +
+ examples/demo/demos/Entry/search_entry.py          |    1 +
+ examples/demo/demos/IconView/iconviewbasics.py     |    1 +
+ examples/demo/demos/IconView/iconviewedit.py       |    1 +
+ examples/demo/demos/TreeView/liststore.py          |    2 +
+ examples/demo/demos/TreeView/treemodel_filelist.py |    1 +
+ examples/demo/demos/TreeView/treemodel_filetree.py |    1 +
+ examples/demo/demos/appwindow.py                   |    1 +
+ examples/demo/demos/assistant.py                   |    1 +
+ examples/demo/demos/builder.py                     |    1 +
+ examples/demo/demos/button_box.py                  |    1 +
+ examples/demo/demos/clipboard.py                   |    1 +
+ examples/demo/demos/colorselector.py               |    1 +
+ examples/demo/demos/combobox.py                    |    1 +
+ examples/demo/demos/dialogs.py                     |    1 +
+ examples/demo/demos/drawingarea.py                 |   11 +-
+ examples/demo/demos/expander.py                    |    1 +
+ examples/demo/demos/flowbox.py                     | 1332
+ ++++++++++----------
+ examples/demo/demos/images.py                      |    1 +
+ examples/demo/demos/infobars.py                    |    1 +
+ examples/demo/demos/links.py                       |    1 +
+ examples/demo/demos/menus.py                       |    1 +
+ examples/demo/demos/pickers.py                     |    1 +
+ examples/demo/demos/pixbuf.py                      |    1 +
+ examples/demo/demos/printing.py                    |    1 +
+ examples/demo/demos/rotatedtext.py                 |    1 +
+ 28 files changed, 700 insertions(+), 670 deletions(-)
+
+commit af9ddf322f7cf377996b8fa29906bd4b254dc001
+Author: Christoph Reiter <creiter@src.gnome.org>
+Date:   Fri Dec 8 16:40:33 2017 +0100
+
+    Fix ctypes.PyDLL construction under Windows
+
+    We require the ctypes.pythonapi interface but can't use the global one
+    since we have to change it which could potentially break other users
+    of that interface.
+
+    Turns out simply passing None to PyDLL() only works on Unix to
+    load the
+    CPython library. Instead copy the logic from the ctypes module in the
+    stdlib.
+
+    https://bugzilla.gnome.org/show_bug.cgi?id=622084
+
+ gi/_ossighelper.py | 13 ++++++++++++-
+ 1 file changed, 12 insertions(+), 1 deletion(-)
+
+commit d19aca693950c0edb02b226db8bcf81a5304870e
+Author: Christoph Reiter <creiter@src.gnome.org>
+Date:   Fri Dec 8 14:40:58 2017 +0100
+
+    configure.ac: Error out in case autoconf-archive isn't installed
+
+    Check whether the AX_IS_RELEASE macro is defined and if not print
+    a proper error message.
+
+    https://bugzilla.gnome.org/show_bug.cgi?id=784428
+
+ configure.ac | 3 +++
+ 1 file changed, 3 insertions(+)
+
+commit 26001a6ae42f8e10ed4c44bc9414e219b946cee0
+Author: Christoph Reiter <creiter@src.gnome.org>
+Date:   Wed Dec 6 18:37:45 2017 +0100
+
+    Move pygi-convert.sh into tools
+
+ MANIFEST.in                              | 2 +-
+ Makefile.am                              | 2 +-
+ pygi-convert.sh => tools/pygi-convert.sh | 0
+ 3 files changed, 2 insertions(+), 2 deletions(-)
+
+commit dd1507be6c198319304342b73c8ec1ec2a102145
+Author: Christoph Reiter <creiter@src.gnome.org>
+Date:   Wed Dec 6 18:26:13 2017 +0100
+
+    README: Convert to reST
+
+ MANIFEST.in          |  2 +-
+ Makefile.am          |  3 ++-
+ README => README.rst | 56
+ ++++++++++++++++++++++++++++++++++------------------
+ 3 files changed, 40 insertions(+), 21 deletions(-)
+
+commit e4a52b9f99667df98806d0c4ee78c2909ead27e1
+Author: Christoph Reiter <creiter@src.gnome.org>
+Date:   Wed Dec 6 17:53:51 2017 +0100
+
+    demo: Move demo into examples and dist it
+
+    We might want to install it in the future, but until then at least put
+    it into the tarball.
+
+    https://bugzilla.gnome.org/show_bug.cgi?id=735918
+
+ MANIFEST.in                                                 |   3 +--
+ examples/Makefile.am                                        |   7 ++++++-
+ {demo => examples/demo}/demo.py                             |   0
+ {demo => examples/demo}/demos/Css/__init__.py               |   0
+ {demo => examples/demo}/demos/Css/css_accordion.py          |   0
+ {demo => examples/demo}/demos/Css/css_basics.py             |   0
+ {demo => examples/demo}/demos/Css/css_multiplebgs.py        |   0
+ {demo => examples/demo}/demos/Entry/__init__.py             |   0
+ {demo => examples/demo}/demos/Entry/entry_buffer.py         |   0
+ {demo => examples/demo}/demos/Entry/entry_completion.py     |   0
+ {demo => examples/demo}/demos/Entry/search_entry.py         |   0
+ {demo => examples/demo}/demos/IconView/__init__.py          |   0
+ {demo => examples/demo}/demos/IconView/iconviewbasics.py    |   0
+ {demo => examples/demo}/demos/IconView/iconviewedit.py      |   0
+ {demo => examples/demo}/demos/TreeView/__init__.py          |   0
+ {demo => examples/demo}/demos/TreeView/liststore.py         |   0
+ .../demo}/demos/TreeView/treemodel_filelist.py              |   0
+ .../demo}/demos/TreeView/treemodel_filetree.py              |   0
+ {demo => examples/demo}/demos/TreeView/treemodel_large.py   |   0
+ {demo => examples/demo}/demos/__init__.py                   |   0
+ {demo => examples/demo}/demos/appwindow.py                  |   0
+ {demo => examples/demo}/demos/assistant.py                  |   0
+ {demo => examples/demo}/demos/builder.py                    |   0
+ {demo => examples/demo}/demos/button_box.py                 |   0
+ {demo => examples/demo}/demos/clipboard.py                  |   0
+ {demo => examples/demo}/demos/colorselector.py              |   0
+ {demo => examples/demo}/demos/combobox.py                   |   0
+ {demo => examples/demo}/demos/data/alphatest.png            | Bin
+ {demo => examples/demo}/demos/data/apple-red.png            | Bin
+ {demo => examples/demo}/demos/data/background.jpg           | Bin
+ {demo => examples/demo}/demos/data/brick.png                | Bin
+ {demo => examples/demo}/demos/data/brick2.png               | Bin
+ {demo => examples/demo}/demos/data/css_accordion.css        |   0
+ {demo => examples/demo}/demos/data/css_basics.css           |   0
+ {demo => examples/demo}/demos/data/css_multiplebgs.css      |   0
+ {demo => examples/demo}/demos/data/cssview.css              |   0
+ {demo => examples/demo}/demos/data/demo.gresource           | Bin
+ {demo => examples/demo}/demos/data/demo.gresource.xml       |   0
+ {demo => examples/demo}/demos/data/demo.ui                  |   0
+ {demo => examples/demo}/demos/data/floppybuddy.gif          | Bin
+ {demo => examples/demo}/demos/data/gnome-applets.png        | Bin
+ {demo => examples/demo}/demos/data/gnome-calendar.png       | Bin
+ {demo => examples/demo}/demos/data/gnome-foot.png           | Bin
+ {demo => examples/demo}/demos/data/gnome-fs-directory.png   | Bin
+ {demo => examples/demo}/demos/data/gnome-fs-regular.png     | Bin
+ {demo => examples/demo}/demos/data/gnome-gimp.png           | Bin
+ {demo => examples/demo}/demos/data/gnome-gmush.png          | Bin
+ {demo => examples/demo}/demos/data/gnome-gsame.png          | Bin
+ {demo => examples/demo}/demos/data/gnu-keys.png             | Bin
+ {demo => examples/demo}/demos/data/gtk-logo-rgb.gif         | Bin
+ {demo => examples/demo}/demos/data/reset.css                |   0
+ {demo => examples/demo}/demos/dialogs.py                    |   0
+ {demo => examples/demo}/demos/drawingarea.py                |   0
+ {demo => examples/demo}/demos/expander.py                   |   0
+ {demo => examples/demo}/demos/flowbox.py                    |   0
+ {demo => examples/demo}/demos/images.py                     |   0
+ {demo => examples/demo}/demos/infobars.py                   |   0
+ {demo => examples/demo}/demos/links.py                      |   0
+ {demo => examples/demo}/demos/menus.py                      |   0
+ {demo => examples/demo}/demos/pickers.py                    |   0
+ {demo => examples/demo}/demos/pixbuf.py                     |   0
+ {demo => examples/demo}/demos/printing.py                   |   0
+ {demo => examples/demo}/demos/rotatedtext.py                |   0
+ {demo => examples/demo}/demos/test.py                       |   0
+ 64 files changed, 7 insertions(+), 3 deletions(-)
+
+commit f8ff3b5e0e769e6db1509426af28728129780529
+Author: Gian Mario Tagliaretti <gianmt@gnome.org>
+Date:   Thu Sep 4 08:51:10 2014 +0100
+
+    demo: Add new Gtk.FlowBox example
+
+    Added a new example for the Gtk.FlowBox class added in GTK+ 3.12.
+    This example additionally makes use of Gtk.HeaderBar.
+
+    https://bugzilla.gnome.org/show_bug.cgi?id=735918
+
+ demo/demos/flowbox.py | 750
+ ++++++++++++++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 750 insertions(+)
+
+commit 3f4a176c77a8026949c04d85d0a822554c4c661c
+Author: Simon Feltman <sfeltman@src.gnome.org>
+Date:   Tue Sep 2 10:53:29 2014 -0700
+
+    demo: Use HeaderBar for main app window
+
+    https://bugzilla.gnome.org/show_bug.cgi?id=735918
+
+ demo/demo.py | 23 +++++++++++------------
+ 1 file changed, 11 insertions(+), 12 deletions(-)
+
+commit 995f1e72eed0970d56d871ed8452c61f92a40696
+Author: Simon Feltman <sfeltman@src.gnome.org>
+Date:   Tue Sep 2 11:10:31 2014 -0700
+
+    demo: PyFlakes and PEP8 fixes
+
+    Since PyFlakes and PEP8 are not currently run on the demo due to it
+    not being packaged, manually check and fix this for now.
+
+    https://bugzilla.gnome.org/show_bug.cgi?id=735918
+
+ demo/demo.py                         | 2 +-
+ demo/demos/Entry/entry_buffer.py     | 2 +-
+ demo/demos/Entry/entry_completion.py | 2 +-
+ demo/demos/clipboard.py              | 6 +-----
+ demo/demos/combobox.py               | 2 +-
+ 5 files changed, 5 insertions(+), 9 deletions(-)
+
+commit 5d2ce69ed51aec62872ebb2a129c2b8a6b4256af
+Author: Simon Feltman <sfeltman@src.gnome.org>
+Date:   Tue Sep 2 11:01:55 2014 -0700
+
+    demo: Rename gtk-demo.py to demo.py
+
+    Remove GTK+ specificity which also allows the demo app to be used as
+    one of the demos itself.
+
+    https://bugzilla.gnome.org/show_bug.cgi?id=735918
+
+ demo/{gtk-demo.py => demo.py} | 0
+ 1 file changed, 0 insertions(+), 0 deletions(-)
+
+commit 7e48d91a5e1f180cbe4adea41bd41939c26c77b0
+Author: Simon Feltman <sfeltman@src.gnome.org>
+Date:   Mon Sep 1 22:04:29 2014 -0700
+
+    demo: Rename demos/gtk-demo to simply demo
+
+    Move the entire "pygobject/demos/gtk-demo" folder to "pygobject/demo".
+    Since we only have a single demo app which should be used for all
+    platform demos we can remove the diectory obfuscation.
+
+    https://bugzilla.gnome.org/show_bug.cgi?id=735918
+
+ {demos/gtk-demo => demo}/demos/Css/__init__.py              |   0
+ {demos/gtk-demo => demo}/demos/Css/css_accordion.py         |   0
+ {demos/gtk-demo => demo}/demos/Css/css_basics.py            |   0
+ {demos/gtk-demo => demo}/demos/Css/css_multiplebgs.py       |   0
+ {demos/gtk-demo => demo}/demos/Entry/__init__.py            |   0
+ {demos/gtk-demo => demo}/demos/Entry/entry_buffer.py        |   0
+ {demos/gtk-demo => demo}/demos/Entry/entry_completion.py    |   0
+ {demos/gtk-demo => demo}/demos/Entry/search_entry.py        |   0
+ {demos/gtk-demo => demo}/demos/IconView/__init__.py         |   0
+ {demos/gtk-demo => demo}/demos/IconView/iconviewbasics.py   |   0
+ {demos/gtk-demo => demo}/demos/IconView/iconviewedit.py     |   0
+ {demos/gtk-demo => demo}/demos/TreeView/__init__.py         |   0
+ {demos/gtk-demo => demo}/demos/TreeView/liststore.py        |   0
+ .../gtk-demo => demo}/demos/TreeView/treemodel_filelist.py  |   0
+ .../gtk-demo => demo}/demos/TreeView/treemodel_filetree.py  |   0
+ {demos/gtk-demo => demo}/demos/TreeView/treemodel_large.py  |   0
+ {demos/gtk-demo => demo}/demos/__init__.py                  |   0
+ {demos/gtk-demo => demo}/demos/appwindow.py                 |   0
+ {demos/gtk-demo => demo}/demos/assistant.py                 |   0
+ {demos/gtk-demo => demo}/demos/builder.py                   |   0
+ {demos/gtk-demo => demo}/demos/button_box.py                |   0
+ {demos/gtk-demo => demo}/demos/clipboard.py                 |   0
+ {demos/gtk-demo => demo}/demos/colorselector.py             |   0
+ {demos/gtk-demo => demo}/demos/combobox.py                  |   0
+ {demos/gtk-demo => demo}/demos/data/alphatest.png           | Bin
+ {demos/gtk-demo => demo}/demos/data/apple-red.png           | Bin
+ {demos/gtk-demo => demo}/demos/data/background.jpg          | Bin
+ {demos/gtk-demo => demo}/demos/data/brick.png               | Bin
+ {demos/gtk-demo => demo}/demos/data/brick2.png              | Bin
+ {demos/gtk-demo => demo}/demos/data/css_accordion.css       |   0
+ {demos/gtk-demo => demo}/demos/data/css_basics.css          |   0
+ {demos/gtk-demo => demo}/demos/data/css_multiplebgs.css     |   0
+ {demos/gtk-demo => demo}/demos/data/cssview.css             |   0
+ {demos/gtk-demo => demo}/demos/data/demo.gresource          | Bin
+ {demos/gtk-demo => demo}/demos/data/demo.gresource.xml      |   0
+ {demos/gtk-demo => demo}/demos/data/demo.ui                 |   0
+ {demos/gtk-demo => demo}/demos/data/floppybuddy.gif         | Bin
+ {demos/gtk-demo => demo}/demos/data/gnome-applets.png       | Bin
+ {demos/gtk-demo => demo}/demos/data/gnome-calendar.png      | Bin
+ {demos/gtk-demo => demo}/demos/data/gnome-foot.png          | Bin
+ {demos/gtk-demo => demo}/demos/data/gnome-fs-directory.png  | Bin
+ {demos/gtk-demo => demo}/demos/data/gnome-fs-regular.png    | Bin
+ {demos/gtk-demo => demo}/demos/data/gnome-gimp.png          | Bin
+ {demos/gtk-demo => demo}/demos/data/gnome-gmush.png         | Bin
+ {demos/gtk-demo => demo}/demos/data/gnome-gsame.png         | Bin
+ {demos/gtk-demo => demo}/demos/data/gnu-keys.png            | Bin
+ {demos/gtk-demo => demo}/demos/data/gtk-logo-rgb.gif        | Bin
+ {demos/gtk-demo => demo}/demos/data/reset.css               |   0
+ {demos/gtk-demo => demo}/demos/dialogs.py                   |   0
+ {demos/gtk-demo => demo}/demos/drawingarea.py               |   0
+ {demos/gtk-demo => demo}/demos/expander.py                  |   0
+ {demos/gtk-demo => demo}/demos/images.py                    |   0
+ {demos/gtk-demo => demo}/demos/infobars.py                  |   0
+ {demos/gtk-demo => demo}/demos/links.py                     |   0
+ {demos/gtk-demo => demo}/demos/menus.py                     |   0
+ {demos/gtk-demo => demo}/demos/pickers.py                   |   0
+ {demos/gtk-demo => demo}/demos/pixbuf.py                    |   0
+ {demos/gtk-demo => demo}/demos/printing.py                  |   0
+ {demos/gtk-demo => demo}/demos/rotatedtext.py               |   0
+ {demos/gtk-demo => demo}/demos/test.py                      |   0
+ {demos/gtk-demo => demo}/gtk-demo.py                        |   0
+ 61 files changed, 0 insertions(+), 0 deletions(-)
+
+commit 1ea36ccfc755693b9ce4cf28be549704bf5370d3
+Author: Christoph Reiter <creiter@src.gnome.org>
+Date:   Wed Dec 6 16:54:54 2017 +0100
+
+    Remove AUTHORS file
+
+    No need to duplicate that information over and over.
+    It's in the README and .doap file still
+
+ AUTHORS     | 11 -----------
+ MANIFEST.in |  1 -
+ README      |  4 ++--
+ 3 files changed, 2 insertions(+), 14 deletions(-)
+
+commit 9462873823a5459276400c0d6cf11e084b681751
+Author: Christoph Reiter <creiter@src.gnome.org>
+Date:   Wed Dec 6 15:55:54 2017 +0100
+
+    Remove pre-commit.hook
+
+    It was obviously not used in some time as the git command doesn't
+    exist anymore.
+    If we ever get per pull/merge request CI we can look into bringing
+    something
+    like this back.
+
+ MANIFEST.in     |  1 -
+ pre-commit.hook | 39 ---------------------------------------
+ 2 files changed, 40 deletions(-)
+
+commit 3e455944f5835c750911d3178a0607201f23f1a8
+Author: Christoph Reiter <creiter@src.gnome.org>
+Date:   Mon Dec 4 15:33:00 2017 +0100
+
+    setup.py: Port to distutils/setuptools
+
+    Instead of wrapping autotools add a proper setuptools based build
+    system.
+    Compared to the autotools one this does not install .pc files
+    or headers
+    and does not allow running tests.
+
+    It uses pkg-config for discovering dependencies and explictely
+    searches
+    for .pc files in the Python prefix so that pycairo installations in a
+    virtualenv are discovered. When using MSVC, pkg-config is skipped and
+    it is assumend that INCLUDE and LIB is properly set up.
+
+    Version information and requirements are parsed from configure.ac,
+    package
+    metadata is parsed from PKG-INFO.in.
+
+    Also adds a "setup.py distcheck" command which makes sure all
+    tracked files
+    end up in the tarball and that the tarball builds (no tests are
+    run atm).
+
+    https://bugzilla.gnome.org/show_bug.cgi?id=789211
+
+ .gitignore   |   1 +
+ MANIFEST.in  |  20 +++
+ Makefile.am  |   3 +-
+ PKG-INFO.in  |   8 +-
+ configure.ac |   3 +-
+ setup.py     | 409
+ +++++++++++++++++++++++++++++++++++++++++++++++------------
+ 6 files changed, 359 insertions(+), 85 deletions(-)
+
+commit 58f677bfaa0f117465a9e2146c5d83768b5a76ac
+Author: Christoph Reiter <creiter@src.gnome.org>
+Date:   Fri Nov 24 13:11:26 2017 +0100
+
+    Install a default SIGINT handler for functions which start an
+    event loop
+
+    Currently ctrl+c on a program blocked on Gtk.main() will raise
+    an exception
+    but not return control. While it's easy to set up the proper signal
+    handling and
+    stop the event loop or execute some other application shutdown code
+    it's nice to have a good default behaviour for small
+    prototypes/examples
+    or when testing some code in an interactive console.
+
+    This adds a context manager which registers a SIGINT handler only
+    in case
+    the default Python signal handler is active and restores the
+    original handle
+    afterwards. Since signal handlers registered through
+    g_unix_signal_add()
+    are not detected by Python's signal module we use PyOS_getsig()
+    through ctypes
+    to detect if the signal handler is changed from outside.
+
+    In case of nested event loops, all of them will be aborted.
+    In case an event loop is started in a thread, nothing will happen.
+
+    The context manager is used in the overrides for Gtk.main(),
+    Gtk.Dialog.run(),
+    Gio.Application.run() and GLib.MainLoop.run()
+
+    This also fixes GLib.MainLoop.run() replacing a non-default signal
+    handler
+    and not restoring the default one:
+        https://bugzilla.gnome.org/show_bug.cgi?id=698623
+
+    https://bugzilla.gnome.org/show_bug.cgi?id=622084
+
+ gi/_ossighelper.py   | 115
+ +++++++++++++++++++++++++++++++++++++++++++++++++++
+ gi/overrides/GLib.py |  31 +++-----------
+ gi/overrides/Gio.py  |   7 ++--
+ gi/overrides/Gtk.py  |  12 +++---
+ tests/test_ossig.py  |  73 +++++++++++++++++++++++++++++++-
+ 5 files changed, 203 insertions(+), 35 deletions(-)
+
+commit a321f6e9d8f5b8e779892eab4ce759b60ff98e39
+Author: Christoph Reiter <creiter@src.gnome.org>
+Date:   Fri Nov 17 20:05:24 2017 +0100
+
+    Make Python OS signal handlers run when an event loop is idling
+
+    When Python receives a signal such as SIGINT it sets a flag and
+    will execute
+    the registered signal handler on the next call to
+    PyErr_CheckSignals().
+    In case the main thread is blocked by an idling event loop (say
+    Gtk.main()
+    or Gtk.Dialog.run()) the check never happens and the signal handler
+    will not get executed.
+
+    To work around the issue use signal.set_wakeup_fd() to wake up
+    the active
+    event loop when a signal is received, which will invoke a Python
+    callback
+    which will lead to the signal handler being executed.
+
+    This patch enables it in overrides for Gtk.main(), Gtk.Dialog.run(),
+    Gio.Application.run() and GLib.MainLoop.run().
+
+    Works on Unix, and on Windows with Python 3.5+.
+
+    With this fix in place it is possible to have a cross platform way to
+    react to SIGINT (GLib.unix_signal_add() worked, but not on Windows),
+    for example:
+
+        signal.signal(signal.SIGINT, lambda *args: Gtk.main_quit())
+        Gtk.main()
+
+    https://bugzilla.gnome.org/show_bug.cgi?id=622084
+
+ Makefile.am          |   3 +-
+ gi/_ossighelper.py   | 137
+ +++++++++++++++++++++++++++++++++++++++++++++++++++
+ gi/overrides/GLib.py |   4 +-
+ gi/overrides/Gio.py  |  12 +++++
+ gi/overrides/Gtk.py  |  14 ++++++
+ tests/Makefile.am    |   1 +
+ tests/test_ossig.py  | 102 ++++++++++++++++++++++++++++++++++++++
+ 7 files changed, 271 insertions(+), 2 deletions(-)
+
+commit 46a9dade170127006df98d44b1a9fb2035ada86b
+Author: Christoph Reiter <creiter@src.gnome.org>
+Date:   Fri Nov 24 17:40:11 2017 +0100
+
+    Drop Python 3.3 support
+
+    It's EOL and not used much (https://hynek.me/articles/python3-2016/)
+
+    https://bugzilla.gnome.org/show_bug.cgi?id=790787
+
+ configure.ac | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+commit bd165405754ba44dea12fd3f31b841b5c8ba0f1a
+Author: Sander Sweers <infirit@gmail.com>
+Date:   Sun Aug 20 22:44:15 2017 +0200
+
+    Drop set_value usage in Gtk.List/TreeStore.set override
+
+    this causes multiple updates to the store each emitting a signal.
+
+    https://bugzilla.gnome.org/show_bug.cgi?id=790346
+
+ gi/overrides/Gtk.py         | 48
+ ++++++++++++++++++++++++---------------------
+ tests/test_overrides_gtk.py | 12 ++++++++++++
+ 2 files changed, 38 insertions(+), 22 deletions(-)
+
+commit 5f63b8c626eb7f27de346dac2b66f07794e61b07
 Author: Christoph Reiter <creiter@src.gnome.org>
 Date:   Thu Oct 26 17:24:02 2017 +0200
 
@@ -31,7 +527,7 @@ Date:   Thu Oct 26 17:24:02 2017 +0200
  tests/test_signal.py  | 29 ++++++++++++++++++++++++++++-
  2 files changed, 32 insertions(+), 3 deletions(-)
 
-commit b4bf1b9d936e021b1645c069c2e0a3062cfab62b
+commit f13b0d6e0d97e21aa2a4553a036362dec9d0c4cb
 Author: Daniel Colascione <dancol@dancol.org>
 Date:   Tue Oct 24 14:42:43 2017 +0200
 
@@ -62,7 +558,7 @@ Date:   Tue Oct 24 14:42:43 2017 +0200
  gi/gimodule.c | 2 +-
  1 file changed, 1 insertion(+), 1 deletion(-)
 
-commit 1136f385d6080297bd57715b749c67f5e7208ba2
+commit 280e80c54e6f2c91638195b92736f5c2b34bbbbd
 Author: Christoph Reiter <creiter@src.gnome.org>
 Date:   Thu Oct 26 09:35:09 2017 +0200
 
@@ -77,7 +573,7 @@ Date:   Thu Oct 26 09:35:09 2017 +0200
  tests/test_gi.py | 1 -
  1 file changed, 1 deletion(-)
 
-commit a37687d3d8bdc42aea63e551401e6686c926c556
+commit 5f41add624990255dfebf2d726af946599f7bcf6
 Author: Christoph Reiter <creiter@src.gnome.org>
 Date:   Mon Oct 23 12:41:45 2017 +0200
 
@@ -94,7 +590,7 @@ Date:   Mon Oct 23 12:41:45 2017 +0200
  tests/test_gi.py                |  5 +++++
  3 files changed, 45 insertions(+)
 
-commit 44a852191a67bc7ef76202412a0102de46eb26f0
+commit 0695d234b0e3e9b0f15a7548816c5c4c96a41ab5
 Author: Philippe Renon <philippe_renon@yahoo.fr>
 Date:   Thu Aug 31 16:39:08 2017 +0200
 
@@ -109,7 +605,7 @@ Date:   Thu Aug 31 16:39:08 2017 +0200
  gi/pygi-enum-marshal.c | 8 ++++----
  1 file changed, 4 insertions(+), 4 deletions(-)
 
-commit fa4330df4e26bb9f77a5cf081d3cc40c342709b9
+commit 1bf7d2cddcd24f619a268d0af85d7919d72bacba
 Author: Christoph Reiter <creiter@src.gnome.org>
 Date:   Sun Oct 22 17:59:17 2017 +0200
 
@@ -126,7 +622,16 @@ Date:   Sun Oct 22 17:59:17 2017 +0200
  tests/test_gi.py                |  6 ++++++
  4 files changed, 32 insertions(+)
 
-commit 5f0f3b330cfa1eb11db4f376d141445847cb9d16
+commit e502d0097f28e6c65d3d5120230fb428aabbc083
+Author: Christoph Reiter <creiter@src.gnome.org>
+Date:   Fri Oct 20 09:48:07 2017 +0200
+
+    pygobject.doap: Add myself as maintainer
+
+ pygobject.doap | 7 +++++++
+ 1 file changed, 7 insertions(+)
+
+commit 37db51220668238e4870b7220b496803d942691b
 Author: James Clarke <jrtc27@jrtc27.com>
 Date:   Fri Oct 13 18:04:45 2017 +0100
 
@@ -146,7 +651,7 @@ Date:   Fri Oct 13 18:04:45 2017 +0100
  gi/pygi-closure.c | 40 +++++++++++++++++++++-------------------
  1 file changed, 21 insertions(+), 19 deletions(-)
 
-commit d831decad9e8fdb449518997dee1a5eaa21e0313
+commit 3c791a5d4b17d647a531de04469d04f77fce0548
 Author: Christoph Reiter <creiter@src.gnome.org>
 Date:   Fri Oct 13 19:24:01 2017 +0200
 
@@ -161,11 +666,58 @@ Date:   Fri Oct 13 19:24:01 2017 +0200
  Makefile.am | 7 +++++++
  1 file changed, 7 insertions(+)
 
-commit 5b61ac3f2a66d93110642f43bec4f2a4e656681a
+commit c4995493c233225b1fa5a3e5406e78d02e3b86e6
+Author: Christoph Reiter <creiter@src.gnome.org>
+Date:   Fri Oct 13 17:22:02 2017 +0200
+
+    Drop pygobject-3.0-uninstalled.pc file
+
+    Like glib/gtk+ did in 2011:
+    https://git.gnome.org/browse/glib/commit/?id=306aa62ea5fa4d3a57bca419afcc159f9509b476
+
+ configure.ac                    |  1 -
+ pygobject-3.0-uninstalled.pc.in | 12 ------------
+ 2 files changed, 13 deletions(-)
+
+commit 3363049be9d8cd277b33127427694588598aa6dd
+Author: Christoph Reiter <creiter@src.gnome.org>
+Date:   Fri Oct 13 16:08:01 2017 +0200
+
+    tests: Windows fix
+
+    some fallout from fd5f2a09ce48329d2df191eca9a0cea926ddfb5b
+
+ tests/test_everything.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+commit fd5f2a09ce48329d2df191eca9a0cea926ddfb5b
+Author: Christoph Reiter <creiter@src.gnome.org>
+Date:   Fri Oct 13 15:29:40 2017 +0200
+
+    tests: some more C locale fixes
+
+    see f40df0a09803ec9c729d842f1f83c6d56aebac22
+
+ tests/test_everything.py | 8 +++++++-
+ tests/test_gi.py         | 6 ++++--
+ 2 files changed, 11 insertions(+), 3 deletions(-)
+
+commit f40df0a09803ec9c729d842f1f83c6d56aebac22
+Author: Christoph Reiter <creiter@src.gnome.org>
+Date:   Fri Oct 13 14:32:44 2017 +0200
+
+    tests: Make the test suite pass with the C locale
+
+    Some filename tests assumed a Unicode locale, skip those.
+
+ tests/test_gi.py | 29 +++++++++++++++++++++++++----
+ 1 file changed, 25 insertions(+), 4 deletions(-)
+
+commit 857fb023886476988d99d35c92894cdbc1bbeab2
 Author: Christoph Reiter <creiter@src.gnome.org>
-Date:   Thu Oct 12 18:58:04 2017 +0200
+Date:   Tue Sep 12 08:35:12 2017 +0200
 
-    configure.ac: version bump to 3.26.1
+    configure.ac: post-release version bump to 3.27.0
 
  configure.ac | 2 +-
  1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644 (file)
index 0000000..b9f067e
--- /dev/null
@@ -0,0 +1,17 @@
+include *.am
+include autogen.sh
+include configure.ac
+include COPYING
+include HACKING
+include *.in
+include INSTALL
+include m4/introspection.m4
+include m4/python.m4
+include NEWS
+include tools/pygi-convert.sh
+include pygobject.doap
+include README.rst
+recursive-include examples *.py *.am *.png *.css *.ui *.gif *.gresource *.jpg *.xml
+recursive-include gi *.am *.h
+recursive-include pygtkcompat *.am
+recursive-include tests *.py *.c *.h *.xml *.supp *nouppera *.am
index c3ba1a8..643aefc 100644 (file)
@@ -17,10 +17,12 @@ EXTRA_DIST = \
        pygobject-$(PLATFORM_VERSION).pc.in \
        PKG-INFO \
        PKG-INFO.in \
-       pygi-convert.sh \
+       tools/pygi-convert.sh \
        m4/python.m4 \
        m4/introspection.m4 \
-       setup.py
+       setup.py \
+       MANIFEST.in \
+       README.rst
 
 MAINTAINERCLEANFILES = \
        $(srcdir)/INSTALL \
@@ -63,7 +65,8 @@ nobase_pyexec_PYTHON = \
        gi/_propertyhelper.py \
        gi/_signalhelper.py \
        gi/_option.py \
-       gi/_error.py
+       gi/_error.py \
+       gi/_ossighelper.py
 
 # if we build in a separate tree, we need to symlink the *.py files from the
 # source tree; Python does not accept the extensions and modules in different
index 08ce9e1..9190734 100644 (file)
@@ -117,8 +117,7 @@ am__CONFIG_DISTCLEAN_FILES = config.status config.cache config.log \
  configure.lineno config.status.lineno
 mkinstalldirs = $(install_sh) -d
 CONFIG_HEADER = config.h
-CONFIG_CLEAN_FILES = pygobject-3.0.pc pygobject-3.0-uninstalled.pc \
-       PKG-INFO
+CONFIG_CLEAN_FILES = pygobject-3.0.pc PKG-INFO
 CONFIG_CLEAN_VPATH_FILES =
 am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
 am__vpath_adj = case $$p in \
@@ -213,11 +212,9 @@ CTAGS = ctags
 CSCOPE = cscope
 DIST_SUBDIRS = $(SUBDIRS)
 am__DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/PKG-INFO.in \
-       $(srcdir)/config.h.in \
-       $(srcdir)/pygobject-3.0-uninstalled.pc.in \
-       $(srcdir)/pygobject-3.0.pc.in AUTHORS COPYING ChangeLog \
-       INSTALL NEWS README compile config.guess config.sub install-sh \
-       ltmain.sh missing py-compile
+       $(srcdir)/config.h.in $(srcdir)/pygobject-3.0.pc.in COPYING \
+       ChangeLog INSTALL NEWS compile config.guess config.sub \
+       install-sh ltmain.sh missing py-compile
 DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
 distdir = $(PACKAGE)-$(VERSION)
 top_distdir = $(distdir)
@@ -454,10 +451,12 @@ EXTRA_DIST = \
        pygobject-$(PLATFORM_VERSION).pc.in \
        PKG-INFO \
        PKG-INFO.in \
-       pygi-convert.sh \
+       tools/pygi-convert.sh \
        m4/python.m4 \
        m4/introspection.m4 \
-       setup.py
+       setup.py \
+       MANIFEST.in \
+       README.rst
 
 MAINTAINERCLEANFILES = \
        $(srcdir)/INSTALL \
@@ -500,7 +499,8 @@ nobase_pyexec_PYTHON = \
        gi/_propertyhelper.py \
        gi/_signalhelper.py \
        gi/_option.py \
-       gi/_error.py
+       gi/_error.py \
+       gi/_ossighelper.py
 
 
 # pkg-config files
@@ -564,8 +564,6 @@ distclean-hdr:
        -rm -f config.h stamp-h1
 pygobject-3.0.pc: $(top_builddir)/config.status $(srcdir)/pygobject-3.0.pc.in
        cd $(top_builddir) && $(SHELL) ./config.status $@
-pygobject-3.0-uninstalled.pc: $(top_builddir)/config.status $(srcdir)/pygobject-3.0-uninstalled.pc.in
-       cd $(top_builddir) && $(SHELL) ./config.status $@
 PKG-INFO: $(top_builddir)/config.status $(srcdir)/PKG-INFO.in
        cd $(top_builddir) && $(SHELL) ./config.status $@
 
diff --git a/NEWS b/NEWS
index 9070fd9..b663a0a 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,13 +1,36 @@
-3.26.1  27-Oct-2017
+3.27.0  08-Dec-2017
+        - demo: pep8 fixes (Christoph Reiter)
+        - Fix ctypes.PyDLL construction under Windows (Christoph Reiter) (#622084)
+        - configure.ac: Error out in case autoconf-archive isn't installed (Christoph Reiter) (#784428)
+        - Move pygi-convert.sh into tools (Christoph Reiter)
+        - README: Convert to reST (Christoph Reiter)
+        - demo: Move demo into examples and dist it (Christoph Reiter) (#735918)
+        - demo: Add new Gtk.FlowBox example (Gian Mario Tagliaretti) (#735918)
+        - demo: Use HeaderBar for main app window (Simon Feltman) (#735918)
+        - demo: PyFlakes and PEP8 fixes (Simon Feltman) (#735918)
+        - demo: Rename gtk-demo.py to demo.py (Simon Feltman) (#735918)
+        - demo: Rename demos/gtk-demo to simply demo (Simon Feltman) (#735918)
+        - Remove AUTHORS file (Christoph Reiter)
+        - Remove pre-commit.hook (Christoph Reiter)
+        - setup.py: Port to distutils/setuptools (Christoph Reiter) (#789211)
+        - Install a default SIGINT handler for functions which start an event loop (Christoph Reiter) (#622084)
+        - Make Python OS signal handlers run when an event loop is idling (Christoph Reiter) (#622084)
+        - Drop Python 3.3 support (Christoph Reiter) (#790787)
+        - Drop set_value usage in Gtk.List/TreeStore.set override (Sander Sweers) (#790346)
         - pygobject-object: Fix Python GC collecting a ref cycle too early (Christoph Reiter) (#731501)
         - Fix potential uninitialized memory access during GC (Daniel Colascione) (#786872)
         - test: revert parts of the previous test as it's broken on 32 bit builds (Christoph Reiter) (#786948)
         - flags: Add testcase for bug 786948 (Christoph Reiter) (#786948)
         - fix potential overflow when marshalling flags from py interface (Philippe Renon) (#786948)
         - to_py_array: Properly handle enum array items (Christoph Reiter) (#788890)
+        - pygobject.doap: Add myself as maintainer (Christoph Reiter)
         - closure: Fix unaligned and out-of-bounds access (James Clarke) (#788894)
         - build: Fix not installing .egg-info file (Christoph Reiter) (#777719)
-        - configure.ac: version bump to 3.26.1 (Christoph Reiter)
+        - Drop pygobject-3.0-uninstalled.pc file (Christoph Reiter)
+        - tests: Windows fix (Christoph Reiter)
+        - tests: some more C locale fixes (Christoph Reiter)
+        - tests: Make the test suite pass with the C locale (Christoph Reiter)
+        - configure.ac: post-release version bump to 3.27.0 (Christoph Reiter)
 
 3.26.0  12-Sep-2017
         - configure.ac: pre-release version bump to 3.26.0 (Christoph Reiter)
index ff24db0..279cc40 100644 (file)
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,15 +1,15 @@
 Metadata-Version: 1.0
-Name: PyGObject
-Version: 3.26.1
-Summary: Python bindings for GObject
-Home-page: http://www.pygtk.org/
+Name: pygobject
+Version: 3.27.0
+Summary: Python bindings for GObject Introspection
+Home-page: https://pygobject.readthedocs.io
 Author: James Henstridge
 Author-email: james@daa.com.au
 Maintainer: Simon Feltman
 Maintainer-email: sfeltman@src.gnome.org
 License: GNU LGPL
-Download-url: ftp://ftp.gnome.org/pub/GNOME/sources/pygobject/3.26/pygobject-3.26.1.tar.gz
-Description: Python bindings for GLib and GObject
+Download-url: ftp://ftp.gnome.org/pub/GNOME/sources/pygobject/3.27/pygobject-3.27.0.tar.gz
+Description: Python bindings for GObject Introspection
 Platform: POSIX, Windows
 Classifier: Development Status :: 5 - Production/Stable
 Classifier: Environment :: Linux
index 651dabe..bebaf08 100644 (file)
@@ -1,15 +1,15 @@
 Metadata-Version: 1.0
-Name: PyGObject
+Name: pygobject
 Version: @VERSION@
-Summary: Python bindings for GObject
-Home-page: http://www.pygtk.org/
+Summary: Python bindings for GObject Introspection
+Home-page: https://pygobject.readthedocs.io
 Author: James Henstridge
 Author-email: james@daa.com.au
 Maintainer: Simon Feltman
 Maintainer-email: sfeltman@src.gnome.org
 License: GNU LGPL
 Download-url: ftp://ftp.gnome.org/pub/GNOME/sources/pygobject/@PYGOBJECT_MAJOR_VERSION@.@PYGOBJECT_MINOR_VERSION@/pygobject-@VERSION@.tar.gz
-Description: Python bindings for GLib and GObject
+Description: Python bindings for GObject Introspection
 Platform: POSIX, Windows
 Classifier: Development Status :: 5 - Production/Stable
 Classifier: Environment :: Linux
similarity index 74%
rename from README
rename to README.rst
index eb7ec8a..7eb7c8b 100644 (file)
--- a/README
@@ -1,15 +1,6 @@
+=========
 PyGObject
-=====
-Original authors:   James Henstridge <james@daa.com.au>
-                    Johan Dahlin <johan@gnome.org>
-
-Current maintainers:  Tomeu Vizoso <tomeu.vizoso@collabora.co.uk>
-                      Martin Pitt <martinpitt@gnome.org>
-                      Paolo Borelli <pborelli@gnome.org>
-                      Ignacio Casal Quinteiro <icq@gnome.org>
-                      Sebastian Pölsterl <sebp@k-d-w.org>
-                     Simon Feltman <sfeltman@gnome.org>
-
+=========
 
 This archive contains bindings for the GLib, and GObject,
 to be used in Python. It is a fairly complete set of bindings,
@@ -19,7 +10,8 @@ of the simpler programs you could write).
 
 If you have any enhancements or bug reports, please file them in
 bugzilla at:
-  http://bugzilla.gnome.org/enter_bug.cgi?product=pygobject
+
+    http://bugzilla.gnome.org/enter_bug.cgi?product=pygobject
 
 If you have a patch, file the bug first and then use the "create new
 attachment" link on the bug's info page.  My preferred format for
@@ -27,11 +19,13 @@ patches is unified diff format (ie. diff -u).  Please don't send me
 diffs which don't have any context, as these make it very difficult to
 see what the patch does.
 
+
 New Versions
 ============
 
 New versions of this package can be found at:
-  http://ftp.gnome.org/pub/GNOME/sources/pygobject/
+
+    http://ftp.gnome.org/pub/GNOME/sources/pygobject/
 
 
 Mailing list
@@ -42,14 +36,17 @@ You can subscribe to it through the web interface:
 
   https://mail.gnome.org/mailman/listinfo/python-hackers-list/
 
+
 Requirements
 ============
+
   * C compiler (GCC and MSVC supported)
   * Python 2.7 or higher
   * Glib/Gio 2.38.0 or higher
   * gobject-introspection 1.46.0 or higher
   * libffi (optional)
 
+
 Copyright Information
 =====================
 
@@ -57,11 +54,12 @@ This software is covered by the GNU Lesser General Public Licence
 (version 2.1, or if you choose, a later version).  Basically just don't
 say you wrote bits you didn't.
 
+
 Compilation
 ===========
 
 PyGObject uses the standard autotools for the build infrastructure.  To
-build, it should be as simple as running:
+build, it should be as simple as running::
 
     $ ./configure --prefix=<prefix where python is installed>
     $ make
@@ -71,11 +69,11 @@ By default, configure searches for a few well-known Python interpreter
 names, such as "python3", "python2", "python2.7", or "python".  If your
 Python interpreter isn't in the path, or is not called "python", you can
 configure pygobject to build against that with --with-python=<path> or
-setting the PYTHON environment variable:
+setting the PYTHON environment variable::
 
-   $ ./configure --with-python=python3
-   $ PYTHON=python3.2 ./configure
-   $ ./configure --with-python=~/my-patched-python/python
+    $ ./configure --with-python=python3
+    $ PYTHON=python3.2 ./configure
+    $ ./configure --with-python=~/my-patched-python/python
 
 If configure can't find GTK+, you may need to set the PKG_CONFIG_PATH
 environment variable to help it find the libraries.
@@ -88,6 +86,7 @@ is installed you'll need to set the PYTHONPATH variable to the
 $prefix/lib/pythonX.Y/site-packages directory created by
 the PyGObject installation.
 
+
 Tests
 =====
 
@@ -102,7 +101,26 @@ Getting Help
 If you have questions about programming with PyGObject, you might want to
 check the documentation on
 
-  https://live.gnome.org/PyGObject/
+    https://live.gnome.org/PyGObject/
 
 If that does not help, send a message to the mailing list (information on
 subscribing is above), or join #python on irc.gnome.org.
+
+
+Authors
+=======
+
+Original authors:
+    * James Henstridge <james@daa.com.au>
+    * Johan Dahlin <johan@gnome.org>
+
+Current maintainers:
+    * Tomeu Vizoso <tomeu.vizoso@collabora.co.uk>
+    * Martin Pitt <martinpitt@gnome.org>
+    * Paolo Borelli <pborelli@gnome.org>
+    * Ignacio Casal Quinteiro <icq@gnome.org>
+    * Sebastian Pölsterl <sebp@k-d-w.org>
+    * Simon Feltman <sfeltman@gnome.org>
+    * Christoph Reiter <reiter.christoph@gmail.com>
+
+See the NEWS file and the git history for a list of all contributors.
index 752e07a..50ea09e 100755 (executable)
--- a/configure
+++ b/configure
@@ -1,6 +1,6 @@
 #! /bin/sh
 # Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.69 for pygobject 3.26.1.
+# Generated by GNU Autoconf 2.69 for pygobject 3.27.0.
 #
 # Report bugs to <http://bugzilla.gnome.org/enter_bug.cgi?product=pygobject>.
 #
@@ -591,8 +591,8 @@ MAKEFLAGS=
 # Identity of this package.
 PACKAGE_NAME='pygobject'
 PACKAGE_TARNAME='pygobject'
-PACKAGE_VERSION='3.26.1'
-PACKAGE_STRING='pygobject 3.26.1'
+PACKAGE_VERSION='3.27.0'
+PACKAGE_STRING='pygobject 3.27.0'
 PACKAGE_BUGREPORT='http://bugzilla.gnome.org/enter_bug.cgi?product=pygobject'
 PACKAGE_URL='https://wiki.gnome.org/Projects/PyGObject/'
 
@@ -1419,7 +1419,7 @@ if test "$ac_init_help" = "long"; then
   # Omit some internal or obsolete options to make the list less imposing.
   # This message is too long to be a string in the A/UX 3.1 sh.
   cat <<_ACEOF
-\`configure' configures pygobject 3.26.1 to adapt to many kinds of systems.
+\`configure' configures pygobject 3.27.0 to adapt to many kinds of systems.
 
 Usage: $0 [OPTION]... [VAR=VALUE]...
 
@@ -1490,7 +1490,7 @@ fi
 
 if test -n "$ac_init_help"; then
   case $ac_init_help in
-     short | recursive ) echo "Configuration of pygobject 3.26.1:";;
+     short | recursive ) echo "Configuration of pygobject 3.27.0:";;
    esac
   cat <<\_ACEOF
 
@@ -1635,7 +1635,7 @@ fi
 test -n "$ac_init_help" && exit $ac_status
 if $ac_init_version; then
   cat <<\_ACEOF
-pygobject configure 3.26.1
+pygobject configure 3.27.0
 generated by GNU Autoconf 2.69
 
 Copyright (C) 2012 Free Software Foundation, Inc.
@@ -1913,7 +1913,7 @@ cat >config.log <<_ACEOF
 This file contains any messages produced by compilers while
 running configure, to aid debugging if configure makes a mistake.
 
-It was created by pygobject $as_me 3.26.1, which was
+It was created by pygobject $as_me 3.27.0, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   $ $0 $@
@@ -2265,6 +2265,9 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
 
 
+
+
+
         # $is_release = ($minor_version is even)
         minor_version=`echo "$PACKAGE_VERSION" | sed 's/[^.][^.]*.\([^.][^.]*\).*/\1/'`
         if test "$(( $minor_version % 2 ))" -ne 0; then :
@@ -2283,14 +2286,14 @@ $as_echo "#define PYGOBJECT_MAJOR_VERSION 3" >>confdefs.h
 PYGOBJECT_MAJOR_VERSION=3
 
 
-$as_echo "#define PYGOBJECT_MINOR_VERSION 26" >>confdefs.h
+$as_echo "#define PYGOBJECT_MINOR_VERSION 27" >>confdefs.h
 
-PYGOBJECT_MINOR_VERSION=26
+PYGOBJECT_MINOR_VERSION=27
 
 
-$as_echo "#define PYGOBJECT_MICRO_VERSION 1" >>confdefs.h
+$as_echo "#define PYGOBJECT_MICRO_VERSION 0" >>confdefs.h
 
-PYGOBJECT_MICRO_VERSION=1
+PYGOBJECT_MICRO_VERSION=0
 
 
 ac_config_headers="$ac_config_headers config.h"
@@ -2810,7 +2813,7 @@ fi
 
 # Define the identity of the package.
  PACKAGE='pygobject'
- VERSION='3.26.1'
+ VERSION='3.27.0'
 
 
 cat >>confdefs.h <<_ACEOF
 
 # if building for python 3 make sure we have the minimum version supported
 if test $build_py3k = true ; then
-  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $PYTHON >= 3.3" >&5
-$as_echo_n "checking for $PYTHON >= 3.3... " >&6; }
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $PYTHON >= 3.4" >&5
+$as_echo_n "checking for $PYTHON >= 3.4... " >&6; }
   prog="import sys
 # split strings by '.' and convert to numeric.  Append some zeros
 # because we need at least 4 digits for the hex conversion.
 # map returns an iterator in Python 3.0 and a list in 2.x
-minver = list(map(int, '3.3'.split('.'))) + [0, 0, 0]
+minver = list(map(int, '3.4'.split('.'))) + [0, 0, 0]
 minverhex = 0
 # xrange is not present in Python 3.0 and range returns an iterator
 for i in list(range(0, 4)): minverhex = (minverhex << 8) + minver[i]
@@ -13892,7 +13895,7 @@ else
 #include <stdlib.h>
 
 int
-main (void)
+main ()
 {
   unsigned int major, minor, micro;
 
@@ -15008,7 +15011,7 @@ done
 
 
 
-for flag in              -Wall             -Wextra             -Wundef             -Wnested-externs             -Wwrite-strings             -Wpointer-arith             -Wmissing-declarations             -Wmissing-prototypes             -Wstrict-prototypes             -Wredundant-decls             -Wno-unused-parameter             -Wno-missing-field-initializers             -Wdeclaration-after-statement             -Wformat=2             -Wold-style-definition             -Wcast-align             -Wformat-nonliteral             -Wformat-security             -Wsign-compare             -Wstrict-aliasing             -Wshadow             -Winline             -Wpacked             -Wmissing-format-attribute             -Wmissing-noreturn             -Winit-self             -Wredundant-decls             -Wmissing-include-dirs             -Wunused-but-set-variable             -Warray-bounds             -Wimplicit-function-declaration             -Wreturn-type             -Wswitch-enum             -Wswitch-default             -Wduplicated-cond             -Wduplicated-branches             -Wlogical-op             -Wrestrict             -Wnull-dereference             -Wjump-misses-init             -Wdouble-promotion                                                                ; do
+for flag in              -Wall             -Wextra             -Wundef             -Wnested-externs             -Wwrite-strings             -Wpointer-arith             -Wmissing-declarations             -Wmissing-prototypes             -Wstrict-prototypes             -Wredundant-decls             -Wno-unused-parameter             -Wno-missing-field-initializers             -Wdeclaration-after-statement             -Wformat=2             -Wold-style-definition             -Wcast-align             -Wformat-nonliteral             -Wformat-security             -Wsign-compare             -Wstrict-aliasing             -Wshadow             -Winline             -Wpacked             -Wmissing-format-attribute             -Wmissing-noreturn             -Winit-self             -Wredundant-decls             -Wmissing-include-dirs             -Wunused-but-set-variable             -Warray-bounds             -Wimplicit-function-declaration             -Wreturn-type             -Wswitch-enum             -Wswitch-default                                                                ; do
   as_CACHEVAR=`$as_echo "ax_cv_check_cflags_$ax_compiler_flags_test_$flag" | $as_tr_sh`
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts $flag" >&5
 $as_echo_n "checking whether C compiler accepts $flag... " >&6; }
@@ -16330,12 +16333,9 @@ CODE_COVERAGE_LCOV_RMOPTS ?= $(CODE_COVERAGE_LCOV_RMOPTS_DEFAULT)
 CODE_COVERAGE_GENHTML_OPTIONS_DEFAULT ?=\
 $(if $(CODE_COVERAGE_BRANCH_COVERAGE),\
 --rc genhtml_branch_coverage=$(CODE_COVERAGE_BRANCH_COVERAGE))
-CODE_COVERAGE_GENHTML_OPTIONS ?= $(CODE_COVERAGE_GENHTML_OPTIONS_DEFAULT)
+CODE_COVERAGE_GENHTML_OPTIONS ?= $(CODE_COVERAGE_GENHTML_OPTIONS_DEFAULTS)
 CODE_COVERAGE_IGNORE_PATTERN ?=
 
-GITIGNOREFILES ?=
-GITIGNOREFILES += $(CODE_COVERAGE_OUTPUT_FILE) $(CODE_COVERAGE_OUTPUT_DIRECTORY)
-
 code_coverage_v_lcov_cap = $(code_coverage_v_lcov_cap_$(V))
 code_coverage_v_lcov_cap_ = $(code_coverage_v_lcov_cap_$(AM_DEFAULT_VERBOSITY))
 code_coverage_v_lcov_cap_0 = @echo "  LCOV   --capture"\
@@ -16365,6 +16365,9 @@ code-coverage-capture-hook:
 
 '"$CODE_COVERAGE_RULES_CLEAN"'
 
+GITIGNOREFILES ?=
+GITIGNOREFILES += $(CODE_COVERAGE_OUTPUT_FILE) $(CODE_COVERAGE_OUTPUT_DIRECTORY)
+
 A''M_DISTCHECK_CONFIGURE_FLAGS ?=
 A''M_DISTCHECK_CONFIGURE_FLAGS += --disable-code-coverage
 
@@ -16375,7 +16378,7 @@ A''M_DISTCHECK_CONFIGURE_FLAGS += --disable-code-coverage
 
 
 
-ac_config_files="$ac_config_files Makefile pygobject-3.0.pc pygobject-3.0-uninstalled.pc gi/Makefile gi/repository/Makefile gi/overrides/Makefile examples/Makefile tests/Makefile pygtkcompat/Makefile PKG-INFO"
+ac_config_files="$ac_config_files Makefile pygobject-3.0.pc gi/Makefile gi/repository/Makefile gi/overrides/Makefile examples/Makefile tests/Makefile pygtkcompat/Makefile PKG-INFO"
 
 cat >confcache <<\_ACEOF
 # This file is a shell script that caches the results of configure
@@ -16931,7 +16934,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
 # report actual input values of CONFIG_FILES etc. instead of their
 # values after options handling.
 ac_log="
-This file was extended by pygobject $as_me 3.26.1, which was
+This file was extended by pygobject $as_me 3.27.0, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
@@ -16998,7 +17001,7 @@ _ACEOF
 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
 ac_cs_version="\\
-pygobject config.status 3.26.1
+pygobject config.status 3.27.0
 configured by $0, generated by GNU Autoconf 2.69,
   with options \\"\$ac_cs_config\\"
 
@@ -17417,7 +17420,6 @@ do
     "libtool") CONFIG_COMMANDS="$CONFIG_COMMANDS libtool" ;;
     "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;;
     "pygobject-3.0.pc") CONFIG_FILES="$CONFIG_FILES pygobject-3.0.pc" ;;
-    "pygobject-3.0-uninstalled.pc") CONFIG_FILES="$CONFIG_FILES pygobject-3.0-uninstalled.pc" ;;
     "gi/Makefile") CONFIG_FILES="$CONFIG_FILES gi/Makefile" ;;
     "gi/repository/Makefile") CONFIG_FILES="$CONFIG_FILES gi/repository/Makefile" ;;
     "gi/overrides/Makefile") CONFIG_FILES="$CONFIG_FILES gi/overrides/Makefile" ;;
index 3ad8da1..cdecad2 100644 (file)
@@ -13,12 +13,12 @@ AC_PREREQ([2.68])
 #   $ ./configure --with-python=~/my-patched-python/python
 
 m4_define(python_min_ver, 2.7)
-m4_define(python3_min_ver, 3.3)
+m4_define(python3_min_ver, 3.4)
 
 dnl the pygobject version number
 m4_define(pygobject_major_version, 3)
-m4_define(pygobject_minor_version, 26)
-m4_define(pygobject_micro_version, 1)
+m4_define(pygobject_minor_version, 27)
+m4_define(pygobject_micro_version, 0)
 m4_define(pygobject_version, pygobject_major_version.pygobject_minor_version.pygobject_micro_version)
 
 dnl versions of packages we require ...
@@ -26,10 +26,14 @@ m4_define(introspection_required_version, 1.46.0)
 m4_define(pycairo_required_version, 1.11.1)
 m4_define(glib_required_version, 2.38.0)
 m4_define(gio_required_version, 2.38.0)
+m4_define(libffi_required_version, 3.0)
 
 AC_INIT([pygobject],[pygobject_version],
        [http://bugzilla.gnome.org/enter_bug.cgi?product=pygobject],
        [pygobject],[https://wiki.gnome.org/Projects/PyGObject/])
+
+m4_ifndef([AX_IS_RELEASE], [AC_MSG_ERROR(['autoconf-archive' missing])])
+
 AX_IS_RELEASE([minor-version])
 AC_CONFIG_MACRO_DIR([m4])
 AC_CONFIG_SRCDIR([gi/gimodule.c])
@@ -131,7 +135,7 @@ PYTHON_VALGRIND_SUPP=`$PYTHON -c "import sys; sys.stdout.write('python' + sys.ve
 AC_SUBST([PYTHON_VALGRIND_SUPP])
 
 dnl libffi
-PKG_CHECK_MODULES(FFI, libffi >= 3.0)
+PKG_CHECK_MODULES(FFI, libffi >= libffi_required_version)
 LIBFFI_PC=libffi
 
 AC_SUBST(FFI_CFLAGS)
@@ -194,7 +198,6 @@ AX_CODE_COVERAGE()
 AC_CONFIG_FILES(
   Makefile
   pygobject-3.0.pc
-  pygobject-3.0-uninstalled.pc
   gi/Makefile
   gi/repository/Makefile
   gi/overrides/Makefile
index af9f3d7..c023cc0 100644 (file)
@@ -1 +1,6 @@
-EXTRA_DIST = properties.py signal.py option.py cairo-demo.py
+EXTRA_DIST = \
+       properties.py \
+       signal.py \
+       option.py \
+       cairo-demo.py \
+       demo
index 7b22613..54843be 100644 (file)
@@ -311,7 +311,13 @@ target_alias = @target_alias@
 top_build_prefix = @top_build_prefix@
 top_builddir = @top_builddir@
 top_srcdir = @top_srcdir@
-EXTRA_DIST = properties.py signal.py option.py cairo-demo.py
+EXTRA_DIST = \
+       properties.py \
+       signal.py \
+       option.py \
+       cairo-demo.py \
+       demo
+
 all: all-am
 
 .SUFFIXES:
diff --git a/examples/demo/demo.py b/examples/demo/demo.py
new file mode 100755 (executable)
index 0000000..d67935d
--- /dev/null
@@ -0,0 +1,356 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.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.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 St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+
+import codecs
+import os
+import sys
+import textwrap
+
+from gi.repository import GLib, GObject, Pango, GdkPixbuf, Gtk, Gio
+
+try:
+    from gi.repository import GtkSource
+    GtkSource  # PyFlakes
+except ImportError:
+    GtkSource = None
+
+
+DEMOROOTDIR = os.path.abspath(os.path.dirname(__file__))
+DEMOCODEDIR = os.path.join(DEMOROOTDIR, 'demos')
+sys.path.insert(0, DEMOROOTDIR)
+
+
+class Demo(GObject.GObject):
+    __gtype_name__ = 'GtkDemo'
+
+    def __init__(self, title, module, filename):
+        super(Demo, self).__init__()
+
+        self.title = title
+        self.module = module
+        self.filename = filename
+
+    @classmethod
+    def new_from_file(cls, path):
+        relpath = os.path.relpath(path, DEMOROOTDIR)
+        packagename = os.path.dirname(relpath).replace(os.sep, '.')
+        modulename = os.path.basename(relpath)[0:-3]
+
+        package = __import__(packagename, globals(), locals(), [modulename], 0)
+        module = getattr(package, modulename)
+
+        try:
+            return cls(module.title, module, path)
+        except AttributeError as e:
+            raise AttributeError('(%s): %s' % (path, e.message))
+
+
+class DemoTreeStore(Gtk.TreeStore):
+    __gtype_name__ = 'GtkDemoTreeStore'
+
+    def __init__(self, *args):
+        super(DemoTreeStore, self).__init__(str, Demo, Pango.Style)
+
+        self._parent_nodes = {}
+
+        for filename in self._list_dir(DEMOCODEDIR):
+            fullpath = os.path.join(DEMOCODEDIR, filename)
+            initfile = os.path.join(os.path.dirname(fullpath), '__init__.py')
+
+            if fullpath != initfile and os.path.isfile(initfile) and fullpath.endswith('.py'):
+                parentname = os.path.dirname(os.path.relpath(fullpath, DEMOCODEDIR))
+
+                if parentname:
+                    parent = self._get_parent_node(parentname)
+                else:
+                    parent = None
+
+                demo = Demo.new_from_file(fullpath)
+                self.append(parent, (demo.title, demo, Pango.Style.NORMAL))
+
+    def _list_dir(self, path):
+        demo_file_list = []
+
+        for filename in os.listdir(path):
+            fullpath = os.path.join(path, filename)
+
+            if os.path.isdir(fullpath):
+                demo_file_list.extend(self._list_dir(fullpath))
+            elif os.path.isfile(fullpath):
+                demo_file_list.append(fullpath)
+
+        return sorted(demo_file_list, key=str.lower)
+
+    def _get_parent_node(self, name):
+        if name not in self._parent_nodes.keys():
+            node = self.append(None, (name, None, Pango.Style.NORMAL))
+            self._parent_nodes[name] = node
+
+        return self._parent_nodes[name]
+
+
+class GtkDemoApp(Gtk.Application):
+    __gtype_name__ = 'GtkDemoWindow'
+
+    def __init__(self):
+        super(GtkDemoApp, self).__init__(application_id='org.gnome.pygobject.gtkdemo')
+
+        # Use a GResource to hold the CSS files. Resource bundles are created by
+        # the glib-compile-resources program shipped with Glib which takes an xml
+        # file that describes the bundle, and a set of files that the xml
+        # references. These are combined into a binary resource bundle.
+        base_path = os.path.abspath(os.path.dirname(__file__))
+        resource_path = os.path.join(base_path, 'demos/data/demo.gresource')
+        resource = Gio.Resource.load(resource_path)
+
+        # FIXME: method register() should be without the underscore
+        # FIXME: see https://bugzilla.gnome.org/show_bug.cgi?id=684319
+        # Once the resource has been globally registered it can be used
+        # throughout the application.
+        resource._register()
+
+    def on_activate(self, app):
+        self.window = Gtk.ApplicationWindow.new(self)
+        self.window.set_title('PyGObject GTK+ Code Demos')
+        self.window.set_default_size(600, 400)
+        self.setup_default_icon()
+
+        self.header_bar = Gtk.HeaderBar(show_close_button=True,
+                                        subtitle='Foobar')
+        self.window.set_titlebar(self.header_bar)
+
+        stack = Gtk.Stack(transition_type=Gtk.StackTransitionType.SLIDE_LEFT_RIGHT,
+                          homogeneous=True)
+        switcher = Gtk.StackSwitcher(stack=stack, halign=Gtk.Align.CENTER)
+
+        self.header_bar.set_custom_title(switcher)
+
+        hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL,
+                       homogeneous=False,
+                       spacing=0)
+        self.window.add(hbox)
+
+        tree = self.create_tree()
+        hbox.pack_start(child=tree, expand=False, fill=False, padding=0)
+        hbox.pack_start(child=stack, expand=True, fill=True, padding=0)
+
+        text_widget, info_buffer = self.create_text_view()
+        stack.add_titled(text_widget, name='info', title='Info')
+
+        self.info_buffer = info_buffer
+        self.info_buffer.create_tag('title', font='Sans 18')
+
+        text_widget, self.source_buffer = self.create_source_view()
+        stack.add_titled(text_widget, name='source', title='Source')
+
+        self.window.show_all()
+
+        self.selection_cb(self.tree_view.get_selection(),
+                          self.tree_view.get_model())
+
+    def find_file(self, base=''):
+        dir = os.path.join(DEMOCODEDIR, 'data')
+        logo_file = os.path.join(dir, 'gtk-logo-rgb.gif')
+        base_file = os.path.join(dir, base)
+
+        if (GLib.file_test(logo_file, GLib.FileTest.EXISTS) and
+                GLib.file_test(base_file, GLib.FileTest.EXISTS)):
+            return base_file
+        else:
+            filename = os.path.join(DEMOCODEDIR, base)
+
+            if GLib.file_test(filename, GLib.FileTest.EXISTS):
+                return filename
+
+            # can't find the file
+            raise IOError('Cannot find demo data file "%s"' % base)
+
+    def setup_default_icon(self):
+        filename = self.find_file('gtk-logo-rgb.gif')
+        pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename)
+        transparent = pixbuf.add_alpha(True, 0xff, 0xff, 0xff)
+        list = []
+        list.append(transparent)
+        Gtk.Window.set_default_icon_list(list)
+
+    def selection_cb(self, selection, model):
+        sel = selection.get_selected()
+        if sel == ():
+            return
+
+        treeiter = sel[1]
+        title = model.get_value(treeiter, 0)
+        demo = model.get_value(treeiter, 1)
+
+        if demo is None:
+            return
+
+        # Split into paragraphs based on double newlines and use
+        # textwrap to strip out all other formatting whitespace
+        description = ''
+        for paragraph in demo.module.description.split('\n\n'):
+            description += '\n'.join(textwrap.wrap(paragraph, 99999))
+            description += '\n\n'  # Add paragraphs back in
+
+        f = codecs.open(demo.filename, 'rU', 'utf-8')
+        code = f.read()
+        f.close()
+
+        # output and style the title
+        (start, end) = self.info_buffer.get_bounds()
+        self.info_buffer.delete(start, end)
+        (start, end) = self.source_buffer.get_bounds()
+        self.source_buffer.delete(start, end)
+
+        start = self.info_buffer.get_iter_at_offset(0)
+        end = start.copy()
+        self.info_buffer.insert(end, title)
+        start = end.copy()
+        start.backward_chars(len(title))
+        self.info_buffer.apply_tag_by_name('title', start, end)
+        self.info_buffer.insert(end, '\n')
+
+        # output the description
+        self.info_buffer.insert(end, description)
+
+        # output the code
+        start = self.source_buffer.get_iter_at_offset(0)
+        end = start.copy()
+        self.source_buffer.insert(end, code)
+
+    def row_activated_cb(self, view, path, col, store):
+        iter = store.get_iter(path)
+        demo = store.get_value(iter, 1)
+
+        if demo is not None:
+            store.set_value(iter, 2, Pango.Style.ITALIC)
+            try:
+                demo.module.main(self)
+            finally:
+                store.set_value(iter, 2, Pango.Style.NORMAL)
+
+    def create_tree(self):
+        tree_store = DemoTreeStore()
+        tree_view = Gtk.TreeView()
+        self.tree_view = tree_view
+        tree_view.set_model(tree_store)
+        selection = tree_view.get_selection()
+        selection.set_mode(Gtk.SelectionMode.BROWSE)
+        tree_view.set_size_request(200, -1)
+
+        cell = Gtk.CellRendererText()
+        column = Gtk.TreeViewColumn(title='Widget (double click for demo)',
+                                    cell_renderer=cell,
+                                    text=0,
+                                    style=2)
+
+        first_iter = tree_store.get_iter_first()
+        if first_iter is not None:
+            selection.select_iter(first_iter)
+
+        selection.connect('changed', self.selection_cb, tree_store)
+        tree_view.connect('row_activated', self.row_activated_cb, tree_store)
+
+        tree_view.append_column(column)
+
+        tree_view.expand_all()
+        tree_view.set_headers_visible(False)
+        scrolled_window = Gtk.ScrolledWindow(hadjustment=None,
+                                             vadjustment=None)
+        scrolled_window.set_policy(Gtk.PolicyType.NEVER,
+                                   Gtk.PolicyType.AUTOMATIC)
+
+        scrolled_window.add(tree_view)
+
+        label = Gtk.Label(label='Widget (double click for demo)')
+
+        box = Gtk.Notebook()
+        box.append_page(scrolled_window, label)
+
+        tree_view.grab_focus()
+
+        return box
+
+    def create_scrolled_window(self):
+        scrolled_window = Gtk.ScrolledWindow(hadjustment=None,
+                                             vadjustment=None)
+        scrolled_window.set_policy(Gtk.PolicyType.AUTOMATIC,
+                                   Gtk.PolicyType.AUTOMATIC)
+        scrolled_window.set_shadow_type(Gtk.ShadowType.IN)
+        return scrolled_window
+
+    def create_text_view(self):
+        text_view = Gtk.TextView()
+        buffer = Gtk.TextBuffer()
+
+        text_view.set_buffer(buffer)
+        text_view.set_editable(False)
+        text_view.set_cursor_visible(False)
+
+        scrolled_window = self.create_scrolled_window()
+        scrolled_window.add(text_view)
+
+        text_view.set_wrap_mode(Gtk.WrapMode.WORD)
+        text_view.set_pixels_above_lines(2)
+        text_view.set_pixels_below_lines(2)
+
+        return scrolled_window, buffer
+
+    def create_source_view(self):
+        font_desc = Pango.FontDescription('monospace 11')
+
+        if GtkSource:
+            lang_mgr = GtkSource.LanguageManager()
+            lang = lang_mgr.get_language('python')
+
+            buffer = GtkSource.Buffer()
+            buffer.set_language(lang)
+            buffer.set_highlight_syntax(True)
+
+            view = GtkSource.View()
+            view.set_buffer(buffer)
+            view.set_show_line_numbers(True)
+
+            scrolled_window = self.create_scrolled_window()
+            scrolled_window.add(view)
+
+        else:
+            scrolled_window, buffer = self.create_text_view()
+            view = scrolled_window.get_child()
+
+        view.modify_font(font_desc)
+        view.set_wrap_mode(Gtk.WrapMode.NONE)
+        return scrolled_window, buffer
+
+    def run(self, argv):
+        self.connect('activate', self.on_activate)
+        return super(GtkDemoApp, self).run(argv)
+
+
+def main(argv):
+    """Entry point for demo manager"""
+    app = GtkDemoApp()
+    return app.run(argv)
+
+
+if __name__ == '__main__':
+    SystemExit(main(sys.argv))
diff --git a/examples/demo/demos/Css/__init__.py b/examples/demo/demos/Css/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/examples/demo/demos/Css/css_accordion.py b/examples/demo/demos/Css/css_accordion.py
new file mode 100644 (file)
index 0000000..2b7cddc
--- /dev/null
@@ -0,0 +1,80 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2013 Gian Mario Tagliaretti <gianmt@gnome.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.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 St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "CSS Accordion"
+description = """
+A simple accordion demo written using CSS transitions and multiple backgrounds.
+"""
+
+
+from gi.repository import Gtk, Gio
+
+
+class CSSAccordionApp:
+    def __init__(self):
+        window = Gtk.Window()
+        window.set_title('CSS Accordion')
+        window.set_default_size(600, 300)
+        window.set_border_width(10)
+        window.connect('destroy', Gtk.main_quit)
+
+        hbox = Gtk.Box(homogeneous=False, spacing=2,
+                       orientation=Gtk.Orientation.HORIZONTAL)
+        hbox.set_halign(Gtk.Align.CENTER)
+        hbox.set_valign(Gtk.Align.CENTER)
+        window.add(hbox)
+
+        for label in ('This', 'Is', 'A', 'CSS', 'Accordion', ':-)'):
+            hbox.add(Gtk.Button(label=label))
+
+        bytes = Gio.resources_lookup_data("/css_accordion/css_accordion.css", 0)
+
+        provider = Gtk.CssProvider()
+        provider.load_from_data(bytes.get_data())
+
+        self.apply_css(window, provider)
+
+        window.show_all()
+
+    def apply_css(self, widget, provider):
+        Gtk.StyleContext.add_provider(widget.get_style_context(),
+                                      provider,
+                                      Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
+
+        if isinstance(widget, Gtk.Container):
+            widget.forall(self.apply_css, provider)
+
+
+def main(demoapp=None):
+    CSSAccordionApp()
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    import os
+    base_path = os.path.abspath(os.path.dirname(__file__))
+    resource_path = os.path.join(base_path, '../data/demo.gresource')
+    resource = Gio.Resource.load(resource_path)
+
+    # FIXME: method register() should be without the underscore
+    # FIXME: see https://bugzilla.gnome.org/show_bug.cgi?id=684319
+    resource._register()
+    main()
diff --git a/examples/demo/demos/Css/css_basics.py b/examples/demo/demos/Css/css_basics.py
new file mode 100644 (file)
index 0000000..18c3d12
--- /dev/null
@@ -0,0 +1,148 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2013 Gian Mario Tagliaretti <gianmt@gnome.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.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 St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "CSS Basics"
+description = """
+Gtk themes are written using CSS. Every widget is build of multiple items
+that you can style very similarly to a regular website.
+"""
+
+import os
+from gi.repository import Gtk, Gdk, Pango, Gio, GLib
+
+
+class CSSBasicsApp:
+    def __init__(self, demoapp):
+        self.demoapp = demoapp
+        #: Store the last successful parsing of the css so we can revert
+        #: this in case of an error.
+        self.last_good_text = ''
+        #: Set when we receive a parsing-error callback. This is needed
+        #: to handle logic after a parsing-error callback which does not raise
+        #: an exception with provider.load_from_data()
+        self.last_error_code = 0
+
+        self.window = Gtk.Window()
+        self.window.set_title('CSS Basics')
+        self.window.set_default_size(400, 300)
+        self.window.set_border_width(10)
+        self.window.connect('destroy', lambda w: Gtk.main_quit())
+
+        self.infobar = Gtk.InfoBar()
+        self.infolabel = Gtk.Label()
+        self.infobar.get_content_area().pack_start(self.infolabel, False, False, 0)
+        self.infobar.set_message_type(Gtk.MessageType.WARNING)
+
+        scrolled = Gtk.ScrolledWindow()
+        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
+        box.pack_start(scrolled, expand=True, fill=True, padding=0)
+        box.pack_start(self.infobar, expand=False, fill=True, padding=0)
+        self.window.add(box)
+
+        provider = Gtk.CssProvider()
+
+        buffer = Gtk.TextBuffer()
+        buffer.create_tag(tag_name="warning", underline=Pango.Underline.SINGLE)
+        buffer.create_tag(tag_name="error", underline=Pango.Underline.ERROR)
+        buffer.connect("changed", self.css_text_changed, provider)
+
+        provider.connect("parsing-error", self.show_parsing_error, buffer)
+
+        textview = Gtk.TextView()
+        textview.set_buffer(buffer)
+        scrolled.add(textview)
+
+        bytes = Gio.resources_lookup_data("/css_basics/css_basics.css", 0)
+        buffer.set_text(bytes.get_data().decode('utf-8'))
+
+        self.apply_css(self.window, provider)
+        self.window.show_all()
+        self.infobar.hide()
+
+    def apply_css(self, widget, provider):
+        Gtk.StyleContext.add_provider(widget.get_style_context(),
+                                      provider,
+                                      Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
+
+        if isinstance(widget, Gtk.Container):
+            widget.forall(self.apply_css, provider)
+
+    def show_parsing_error(self, provider, section, error, buffer):
+        start = buffer.get_iter_at_line_index(section.get_start_line(),
+                                              section.get_start_position())
+
+        end = buffer.get_iter_at_line_index(section.get_end_line(),
+                                            section.get_end_position())
+
+        if error.code == Gtk.CssProviderError.DEPRECATED:
+            tag_name = "warning"
+        else:
+            tag_name = "error"
+        self.last_error_code = error.code
+
+        self.infolabel.set_text(error.message)
+        self.infobar.show_all()
+
+        buffer.apply_tag_by_name(tag_name, start, end)
+
+    def css_text_changed(self, buffer, provider):
+        start = buffer.get_start_iter()
+        end = buffer.get_end_iter()
+        buffer.remove_all_tags(start, end)
+
+        text = buffer.get_text(start, end, False).encode('utf-8')
+
+        # Ignore CSS errors as they are shown by highlighting
+        try:
+            provider.load_from_data(text)
+        except GLib.GError as e:
+            if e.domain != 'gtk-css-provider-error-quark':
+                raise e
+
+        # If the parsing-error callback is ever run (even in the case of warnings)
+        # load the last good css text that ran without any warnings. Otherwise
+        # we may have a discrepancy in "last_good_text" vs the current buffer
+        # causing section.get_start_position() to give back an invalid position
+        # for the editor buffer.
+        if self.last_error_code:
+            provider.load_from_data(self.last_good_text)
+            self.last_error_code = 0
+        else:
+            self.last_good_text = text
+            self.infobar.hide()
+
+        Gtk.StyleContext.reset_widgets(Gdk.Screen.get_default())
+
+
+def main(demoapp=None):
+    CSSBasicsApp(demoapp)
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    base_path = os.path.abspath(os.path.dirname(__file__))
+    resource_path = os.path.join(base_path, '../data/demo.gresource')
+    resource = Gio.Resource.load(resource_path)
+
+    # FIXME: method register() should be without the underscore
+    # FIXME: see https://bugzilla.gnome.org/show_bug.cgi?id=684319
+    resource._register()
+    main()
diff --git a/examples/demo/demos/Css/css_multiplebgs.py b/examples/demo/demos/Css/css_multiplebgs.py
new file mode 100644 (file)
index 0000000..9e1b011
--- /dev/null
@@ -0,0 +1,187 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2013 Gian Mario Tagliaretti <gianmt@gnome.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.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 St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "CSS Theming/Multiple Backgrounds"
+description = """
+Gtk themes are written using CSS. Every widget is build of multiple items
+that you can style very similarly to a regular website.
+"""
+
+from gi.repository import Gtk, Gdk, Pango, Gio, GLib
+
+
+class CSSMultiplebgsApp:
+    def __init__(self, demoapp):
+        self.demoapp = demoapp
+        #: Store the last successful parsing of the css so we can revert
+        #: this in case of an error.
+        self.last_good_text = ''
+        #: Set when we receive a parsing-error callback. This is needed
+        #: to handle logic after a parsing-error callback which does not raise
+        #: an exception with provider.load_from_data()
+        self.last_error_code = 0
+
+        self.window = Gtk.Window()
+        self.window.set_title('CSS Multiplebgs')
+        self.window.set_default_size(400, 300)
+        self.window.set_border_width(10)
+        self.window.connect('destroy', lambda w: Gtk.main_quit())
+
+        overlay = Gtk.Overlay()
+        overlay.add_events(Gdk.EventMask.ENTER_NOTIFY_MASK |
+                           Gdk.EventMask.LEAVE_NOTIFY_MASK |
+                           Gdk.EventMask.POINTER_MOTION_MASK)
+
+        self.infobar = Gtk.InfoBar()
+        self.infolabel = Gtk.Label()
+        self.infobar.get_content_area().pack_start(self.infolabel, False, False, 0)
+        self.infobar.set_message_type(Gtk.MessageType.WARNING)
+
+        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
+        box.pack_start(overlay, expand=True, fill=True, padding=0)
+        box.pack_start(self.infobar, expand=False, fill=True, padding=0)
+        self.window.add(box)
+
+        canvas = Gtk.DrawingArea()
+        canvas.set_name("canvas")
+        canvas.connect("draw", self.drawing_area_draw)
+        overlay.add(canvas)
+
+        button = Gtk.Button()
+        button.add_events(Gdk.EventMask.ENTER_NOTIFY_MASK |
+                          Gdk.EventMask.LEAVE_NOTIFY_MASK |
+                          Gdk.EventMask.POINTER_MOTION_MASK)
+        button.set_name("bricks-button")
+        button.set_halign(Gtk.Align.CENTER)
+        button.set_valign(Gtk.Align.CENTER)
+        button.set_size_request(250, 84)
+        overlay.add_overlay(button)
+
+        paned = Gtk.Paned(orientation=Gtk.Orientation.VERTICAL)
+        overlay.add_overlay(paned)
+
+        # We need a filler so we get a handle
+        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
+        paned.add(box)
+
+        buffer = Gtk.TextBuffer()
+        buffer.create_tag(tag_name="warning", underline=Pango.Underline.SINGLE)
+        buffer.create_tag(tag_name="error", underline=Pango.Underline.ERROR)
+
+        provider = Gtk.CssProvider()
+
+        buffer.connect("changed", self.css_text_changed, provider)
+        provider.connect("parsing-error", self.show_parsing_error, buffer)
+
+        textview = Gtk.TextView()
+        textview.set_buffer(buffer)
+
+        scrolled = Gtk.ScrolledWindow()
+        scrolled.add(textview)
+        paned.add(scrolled)
+
+        bytes = Gio.resources_lookup_data("/css_multiplebgs/css_multiplebgs.css", 0)
+        buffer.set_text(bytes.get_data().decode('utf-8'))
+
+        self.apply_css(self.window, provider)
+        self.window.show_all()
+        self.infobar.hide()
+
+    def drawing_area_draw(self, widget, cairo_t):
+        context = widget.get_style_context()
+        Gtk.render_background(context, cairo_t, 0, 0,
+                              widget.get_allocated_width(),
+                              widget.get_allocated_height())
+
+        Gtk.render_frame(context, cairo_t, 0, 0,
+                         widget.get_allocated_width(),
+                         widget.get_allocated_height())
+
+    def apply_css(self, widget, provider):
+        Gtk.StyleContext.add_provider(widget.get_style_context(),
+                                      provider,
+                                      Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
+
+        if isinstance(widget, Gtk.Container):
+            widget.forall(self.apply_css, provider)
+
+    def show_parsing_error(self, provider, section, error, buffer):
+        start = buffer.get_iter_at_line_index(section.get_start_line(),
+                                              section.get_start_position())
+
+        end = buffer.get_iter_at_line_index(section.get_end_line(),
+                                            section.get_end_position())
+
+        if error.code == Gtk.CssProviderError.DEPRECATED:
+            tag_name = "warning"
+        else:
+            tag_name = "error"
+        self.last_error_code = error.code
+
+        self.infolabel.set_text(error.message)
+        self.infobar.show_all()
+
+        buffer.apply_tag_by_name(tag_name, start, end)
+
+    def css_text_changed(self, buffer, provider):
+        start = buffer.get_start_iter()
+        end = buffer.get_end_iter()
+        buffer.remove_all_tags(start, end)
+
+        text = buffer.get_text(start, end, False).encode('utf-8')
+
+        # Ignore CSS errors as they are shown by highlighting
+        try:
+            provider.load_from_data(text)
+        except GLib.GError as e:
+            if e.domain != 'gtk-css-provider-error-quark':
+                raise e
+
+        # If the parsing-error callback is ever run (even in the case of warnings)
+        # load the last good css text that ran without any warnings. Otherwise
+        # we may have a discrepancy in "last_good_text" vs the current buffer
+        # causing section.get_start_position() to give back an invalid position
+        # for the editor buffer.
+        if self.last_error_code:
+            provider.load_from_data(self.last_good_text)
+            self.last_error_code = 0
+        else:
+            self.last_good_text = text
+            self.infobar.hide()
+
+        Gtk.StyleContext.reset_widgets(Gdk.Screen.get_default())
+
+
+def main(demoapp=None):
+    CSSMultiplebgsApp(demoapp)
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    import os
+    base_path = os.path.abspath(os.path.dirname(__file__))
+    resource_path = os.path.join(base_path, '../data/demo.gresource')
+    resource = Gio.Resource.load(resource_path)
+
+    # FIXME: method register() should be without the underscore
+    # FIXME: see https://bugzilla.gnome.org/show_bug.cgi?id=684319
+    resource._register()
+    main()
diff --git a/examples/demo/demos/Entry/__init__.py b/examples/demo/demos/Entry/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/examples/demo/demos/Entry/entry_buffer.py b/examples/demo/demos/Entry/entry_buffer.py
new file mode 100644 (file)
index 0000000..f0c04a4
--- /dev/null
@@ -0,0 +1,72 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.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.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 St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Entry Buffer"
+description = """
+Gtk.EntryBuffer provides the text content in a Gtk.Entry.
+"""
+
+
+from gi.repository import Gtk
+
+
+class EntryBufferApp:
+    def __init__(self):
+        self.window = Gtk.Dialog(title='Gtk.EntryBuffer')
+        self.window.add_buttons(Gtk.STOCK_CLOSE, Gtk.ResponseType.NONE)
+        self.window.connect('response', self.destroy)
+        self.window.connect('destroy', lambda x: Gtk.main_quit())
+        self.window.set_resizable(False)
+
+        vbox = Gtk.VBox(homogeneous=False, spacing=0)
+        self.window.get_content_area().pack_start(vbox, True, True, 0)
+        vbox.set_border_width(5)
+
+        label = Gtk.Label()
+        label.set_markup('Entries share a buffer. Typing in one is reflected in the other.')
+        vbox.pack_start(label, False, False, 0)
+
+        # create a buffer
+        buffer = Gtk.EntryBuffer()
+
+        # create our first entry
+        entry = Gtk.Entry(buffer=buffer)
+        vbox.pack_start(entry, False, False, 0)
+
+        # create the second entry
+        entry = Gtk.Entry(buffer=buffer)
+        entry.set_visibility(False)
+        vbox.pack_start(entry, False, False, 0)
+
+        self.window.show_all()
+
+    def destroy(self, *args):
+        self.window.destroy()
+        Gtk.main_quit()
+
+
+def main(demoapp=None):
+    EntryBufferApp()
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/Entry/entry_completion.py b/examples/demo/demos/Entry/entry_completion.py
new file mode 100644 (file)
index 0000000..107c45a
--- /dev/null
@@ -0,0 +1,86 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.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.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 St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Entry Completion"
+description = """
+Gtk.EntryCompletion provides a mechanism for adding support for
+completion in Gtk.Entry.
+"""
+
+
+from gi.repository import Gtk
+
+
+class EntryBufferApp:
+    def __init__(self):
+        self.window = Gtk.Dialog(title='Gtk.EntryCompletion')
+        self.window.add_buttons(Gtk.STOCK_CLOSE, Gtk.ResponseType.NONE)
+        self.window.connect('response', self.destroy)
+        self.window.connect('destroy', lambda x: Gtk.main_quit())
+        self.window.set_resizable(False)
+
+        vbox = Gtk.VBox(homogeneous=False, spacing=0)
+        self.window.get_content_area().pack_start(vbox, True, True, 0)
+        vbox.set_border_width(5)
+
+        label = Gtk.Label()
+        label.set_markup('Completion demo, try writing <b>total</b> or <b>gnome</b> for example.')
+        vbox.pack_start(label, False, False, 0)
+
+        # create our entry
+        entry = Gtk.Entry()
+        vbox.pack_start(entry, False, False, 0)
+
+        # create the completion object
+        completion = Gtk.EntryCompletion()
+
+        # assign the completion to the entry
+        entry.set_completion(completion)
+
+        # create tree model and use it as the completion model
+        completion_model = self.create_completion_model()
+        completion.set_model(completion_model)
+
+        completion.set_text_column(0)
+
+        self.window.show_all()
+
+    def create_completion_model(self):
+        store = Gtk.ListStore(str)
+
+        store.append(['GNOME'])
+        store.append(['total'])
+        store.append(['totally'])
+
+        return store
+
+    def destroy(self, *args):
+        self.window.destroy()
+        Gtk.main_quit()
+
+
+def main(demoapp=None):
+    EntryBufferApp()
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/Entry/search_entry.py b/examples/demo/demos/Entry/search_entry.py
new file mode 100644 (file)
index 0000000..793b81a
--- /dev/null
@@ -0,0 +1,254 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.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.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 St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Search Entry"
+description = """GtkEntry allows to display icons and progress information.
+This demo shows how to use these features in a search entry.
+"""
+
+from gi.repository import Gtk, GObject
+
+(PIXBUF_COL,
+ TEXT_COL) = range(2)
+
+
+class SearchboxApp:
+    def __init__(self, demoapp):
+        self.demoapp = demoapp
+
+        self.window = Gtk.Dialog(title='Search Entry')
+        self.window.add_buttons(Gtk.STOCK_CLOSE, Gtk.ResponseType.NONE)
+
+        self.window.connect('response', lambda x, y: self.window.destroy())
+        self.window.connect('destroy', Gtk.main_quit)
+
+        content_area = self.window.get_content_area()
+
+        vbox = Gtk.VBox(spacing=5)
+        content_area.pack_start(vbox, True, True, 0)
+        vbox.set_border_width(5)
+
+        label = Gtk.Label()
+        label.set_markup('Search entry demo')
+        vbox.pack_start(label, False, False, 0)
+
+        hbox = Gtk.HBox(homogeneous=False, spacing=10)
+        hbox.set_border_width(0)
+        vbox.pack_start(hbox, True, True, 0)
+
+        # Create our entry
+        entry = Gtk.Entry()
+        hbox.pack_start(entry, False, False, 0)
+
+        # Create the find and cancel buttons
+        notebook = Gtk.Notebook()
+        self.notebook = notebook
+        notebook.set_show_tabs(False)
+        notebook.set_show_border(False)
+        hbox.pack_start(notebook, False, False, 0)
+
+        find_button = Gtk.Button(label='Find')
+        find_button.connect('clicked', self.start_search, entry)
+        notebook.append_page(find_button, None)
+        find_button.show()
+
+        cancel_button = Gtk.Button(label='Cancel')
+        cancel_button.connect('clicked', self.stop_search, entry)
+        notebook.append_page(cancel_button, None)
+        cancel_button.show()
+
+        # Set up the search icon
+        self.search_by_name(None, entry)
+
+        # Set up the clear icon
+        entry.set_icon_from_stock(Gtk.EntryIconPosition.SECONDARY,
+                                  Gtk.STOCK_CLEAR)
+        self.text_changed_cb(entry, None, find_button)
+
+        entry.connect('notify::text', self.text_changed_cb, find_button)
+
+        entry.connect('activate', self.activate_cb)
+
+        # Create the menu
+        menu = self.create_search_menu(entry)
+        entry.connect('icon-press', self.icon_press_cb, menu)
+
+        # FIXME: this should take None for the detach callback
+        #        but our callback implementation does not allow
+        #        it yet, so we pass in a noop callback
+        menu.attach_to_widget(entry, self.detach)
+
+        # add accessible alternatives for icon functionality
+        entry.connect('populate-popup', self.entry_populate_popup)
+
+        self.window.show_all()
+
+    def detach(self, *args):
+        pass
+
+    def show_find_button(self):
+        self.notebook.set_current_page(0)
+
+    def show_cancel_button(self):
+        self.notebook.set_current_page(1)
+
+    def search_progress(self, entry):
+        entry.progress_pulse()
+        return True
+
+    def search_progress_done(self, entry):
+        entry.set_progress_fraction(0.0)
+
+    def finish_search(self, button, entry):
+        self.show_find_button()
+        GObject.source_remove(self.search_progress_id)
+        self.search_progress_done(entry)
+        self.search_progress_id = 0
+
+        return False
+
+    def start_search_feedback(self, entry):
+        self.search_progress_id = GObject.timeout_add(100,
+                                                      self.search_progress,
+                                                      entry)
+
+        return False
+
+    def start_search(self, button, entry):
+        self.show_cancel_button()
+        self.search_progress_id = GObject.timeout_add_seconds(1,
+                                                              self.start_search_feedback,
+                                                              entry)
+        self.finish_search_id = GObject.timeout_add_seconds(15,
+                                                            self.finish_search,
+                                                            button)
+
+    def stop_search(self, button, entry):
+        GObject.source_remove(self.finish_search_id)
+        self.finish_search(button, entry)
+
+    def clear_entry_swapped(self, widget, entry):
+        self.clear_entry(entry)
+
+    def clear_entry(self, entry):
+        entry.set_text('')
+
+    def search_by_name(self, item, entry):
+        entry.set_icon_from_stock(Gtk.EntryIconPosition.PRIMARY,
+                                  Gtk.STOCK_FIND)
+        entry.set_icon_tooltip_text(Gtk.EntryIconPosition.PRIMARY,
+                                    'Search by name\n' +
+                                    'Click here to change the search type')
+
+    def search_by_description(self, item, entry):
+        entry.set_icon_from_stock(Gtk.EntryIconPosition.PRIMARY,
+                                  Gtk.STOCK_EDIT)
+        entry.set_icon_tooltip_text(Gtk.EntryIconPosition.PRIMARY,
+                                    'Search by description\n' +
+                                    'Click here to change the search type')
+
+    def search_by_file(self, item, entry):
+        entry.set_icon_from_stock(Gtk.EntryIconPosition.PRIMARY,
+                                  Gtk.STOCK_OPEN)
+        entry.set_icon_tooltip_text(Gtk.EntryIconPosition.PRIMARY,
+                                    'Search by file name\n' +
+                                    'Click here to change the search type')
+
+    def create_search_menu(self, entry):
+        menu = Gtk.Menu()
+
+        item = Gtk.ImageMenuItem.new_with_mnemonic('Search by _name')
+        image = Gtk.Image.new_from_stock(Gtk.STOCK_FIND, Gtk.IconSize.MENU)
+        item.set_image(image)
+        item.set_always_show_image(True)
+        item.connect('activate', self.search_by_name, entry)
+        menu.append(item)
+
+        item = Gtk.ImageMenuItem.new_with_mnemonic('Search by _description')
+        image = Gtk.Image.new_from_stock(Gtk.STOCK_EDIT, Gtk.IconSize.MENU)
+        item.set_image(image)
+        item.set_always_show_image(True)
+        item.connect('activate', self.search_by_description, entry)
+        menu.append(item)
+
+        item = Gtk.ImageMenuItem.new_with_mnemonic('Search by _file name')
+        image = Gtk.Image.new_from_stock(Gtk.STOCK_OPEN, Gtk.IconSize.MENU)
+        item.set_image(image)
+        item.set_always_show_image(True)
+        item.connect('activate', self.search_by_name, entry)
+        menu.append(item)
+
+        menu.show_all()
+
+        return menu
+
+    def icon_press_cb(self, entry, position, event, menu):
+        if position == Gtk.EntryIconPosition.PRIMARY:
+            menu.popup(None, None, None, None,
+                       event.button, event.time)
+        else:
+            self.clear_entry(entry)
+
+    def text_changed_cb(self, entry, pspec, button):
+        has_text = entry.get_text_length() > 0
+        entry.set_icon_sensitive(Gtk.EntryIconPosition.SECONDARY, has_text)
+        button.set_sensitive(has_text)
+
+    def activate_cb(self, entry, button):
+        if self.search_progress_id != 0:
+            return
+        self.start_search(button, entry)
+
+    def search_entry_destroyed(self, widget):
+        if self.finish_search_id != 0:
+            GObject.source_remove(self.finish_search_id)
+        if self.search_progress_id != 0:
+            GObject.source_remove(self.search_progress_id)
+
+        self.window = None
+
+    def entry_populate_popup(self, entry, menu):
+        has_text = entry.get_text_length() > 0
+
+        item = Gtk.SeparatorMenuItem()
+        item.show()
+        menu.append(item)
+
+        item = Gtk.MenuItem.new_with_mnemonic("C_lear")
+        item.show()
+        item.connect('activate', self.clear_entry_swapped, entry)
+        menu.append(item)
+        item.set_sensitive(has_text)
+
+        search_menu = self.create_search_menu(entry)
+        item = Gtk.MenuItem.new_with_label('Search by')
+        item.show()
+        item.set_submenu(search_menu)
+        menu.append(item)
+
+
+def main(demoapp=None):
+    SearchboxApp(demoapp)
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/IconView/__init__.py b/examples/demo/demos/IconView/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/examples/demo/demos/IconView/iconviewbasics.py b/examples/demo/demos/IconView/iconviewbasics.py
new file mode 100644 (file)
index 0000000..8cb71a8
--- /dev/null
@@ -0,0 +1,221 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.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.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 St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Icon View Basics"
+description = """The GtkIconView widget is used to display and manipulate
+icons. It uses a GtkTreeModel for data storage, so the list store example might
+be helpful. We also use the Gio.File API to get the icons for each file type.
+"""
+
+
+import os
+
+from gi.repository import GLib, Gio, GdkPixbuf, Gtk
+
+
+class IconViewApp:
+    (COL_PATH,
+     COL_DISPLAY_NAME,
+     COL_PIXBUF,
+     COL_IS_DIRECTORY,
+     NUM_COLS) = range(5)
+
+    def __init__(self, demoapp):
+        self.pixbuf_lookup = {}
+
+        self.demoapp = demoapp
+
+        self.window = Gtk.Window()
+        self.window.set_title('Gtk.IconView demo')
+        self.window.set_default_size(650, 400)
+        self.window.connect('destroy', Gtk.main_quit)
+
+        vbox = Gtk.VBox()
+        self.window.add(vbox)
+
+        tool_bar = Gtk.Toolbar()
+        vbox.pack_start(tool_bar, False, False, 0)
+
+        up_button = Gtk.ToolButton(stock_id=Gtk.STOCK_GO_UP)
+        up_button.set_is_important(True)
+        up_button.set_sensitive(False)
+        tool_bar.insert(up_button, -1)
+
+        home_button = Gtk.ToolButton(stock_id=Gtk.STOCK_HOME)
+        home_button.set_is_important(True)
+        tool_bar.insert(home_button, -1)
+
+        sw = Gtk.ScrolledWindow()
+        sw.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
+        sw.set_policy(Gtk.PolicyType.AUTOMATIC,
+                      Gtk.PolicyType.AUTOMATIC)
+
+        vbox.pack_start(sw, True, True, 0)
+
+        # create the store and fill it with content
+        self.parent_dir = '/'
+        store = self.create_store()
+        self.fill_store(store)
+
+        icon_view = Gtk.IconView(model=store)
+        icon_view.set_selection_mode(Gtk.SelectionMode.MULTIPLE)
+        sw.add(icon_view)
+
+        # connect to the 'clicked' signal of the "Up" tool button
+        up_button.connect('clicked', self.up_clicked, store)
+
+        # connect to the 'clicked' signal of the "home" tool button
+        home_button.connect('clicked', self.home_clicked, store)
+
+        self.up_button = up_button
+        self.home_button = home_button
+
+        # we now set which model columns that correspond to the text
+        # and pixbuf of each item
+        icon_view.set_text_column(self.COL_DISPLAY_NAME)
+        icon_view.set_pixbuf_column(self.COL_PIXBUF)
+
+        # connect to the "item-activated" signal
+        icon_view.connect('item-activated', self.item_activated, store)
+        icon_view.grab_focus()
+
+        self.window.show_all()
+
+    def sort_func(self, store, a_iter, b_iter, user_data):
+        (a_name, a_is_dir) = store.get(a_iter,
+                                       self.COL_DISPLAY_NAME,
+                                       self.COL_IS_DIRECTORY)
+
+        (b_name, b_is_dir) = store.get(b_iter,
+                                       self.COL_DISPLAY_NAME,
+                                       self.COL_IS_DIRECTORY)
+
+        if a_name is None:
+            a_name = ''
+
+        if b_name is None:
+            b_name = ''
+
+        if (not a_is_dir) and b_is_dir:
+            return 1
+        elif a_is_dir and (not b_is_dir):
+            return -1
+        elif a_name > b_name:
+            return 1
+        elif a_name < b_name:
+            return -1
+        else:
+            return 0
+
+    def up_clicked(self, item, store):
+        self.parent_dir = os.path.split(self.parent_dir)[0]
+        self.fill_store(store)
+        # de-sensitize the up button if we are at the root
+        self.up_button.set_sensitive(self.parent_dir != '/')
+
+    def home_clicked(self, item, store):
+        self.parent_dir = GLib.get_home_dir()
+        self.fill_store(store)
+
+        # Sensitize the up button
+        self.up_button.set_sensitive(True)
+
+    def item_activated(self, icon_view, tree_path, store):
+        iter_ = store.get_iter(tree_path)
+        (path, is_dir) = store.get(iter_, self.COL_PATH, self.COL_IS_DIRECTORY)
+        if not is_dir:
+            return
+
+        self.parent_dir = path
+        self.fill_store(store)
+
+        self.up_button.set_sensitive(True)
+
+    def create_store(self):
+        store = Gtk.ListStore(str, str, GdkPixbuf.Pixbuf, bool)
+
+        # set sort column and function
+        store.set_default_sort_func(self.sort_func)
+        store.set_sort_column_id(-1, Gtk.SortType.ASCENDING)
+
+        return store
+
+    def file_to_icon_pixbuf(self, path):
+        pixbuf = None
+
+        # get the theme icon
+        f = Gio.file_new_for_path(path)
+        info = f.query_info(Gio.FILE_ATTRIBUTE_STANDARD_ICON,
+                            Gio.FileQueryInfoFlags.NONE,
+                            None)
+        gicon = info.get_icon()
+
+        # check to see if it is an image format we support
+        for format in GdkPixbuf.Pixbuf.get_formats():
+            for mime_type in format.get_mime_types():
+                content_type = Gio.content_type_from_mime_type(mime_type)
+                if content_type is not None:
+                    break
+
+            format_gicon = Gio.content_type_get_icon(content_type)
+            if format_gicon.equal(gicon):
+                gicon = f.icon_new()
+                break
+
+        if gicon in self.pixbuf_lookup:
+            return self.pixbuf_lookup[gicon]
+
+        if isinstance(gicon, Gio.ThemedIcon):
+            names = gicon.get_names()
+            icon_theme = Gtk.IconTheme.get_default()
+            for name in names:
+                try:
+                    pixbuf = icon_theme.load_icon(name, 64, 0)
+                    break
+                except GLib.GError:
+                    pass
+
+            self.pixbuf_lookup[gicon] = pixbuf
+
+        elif isinstance(gicon, Gio.FileIcon):
+            icon_file = gicon.get_file()
+            path = icon_file.get_path()
+            pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(path, 72, 72)
+            self.pixbuf_lookup[gicon] = pixbuf
+
+        return pixbuf
+
+    def fill_store(self, store):
+        store.clear()
+        for name in os.listdir(self.parent_dir):
+            path = os.path.join(self.parent_dir, name)
+            is_dir = os.path.isdir(path)
+            pixbuf = self.file_to_icon_pixbuf(path)
+            store.append((path, name, pixbuf, is_dir))
+
+
+def main(demoapp=None):
+    IconViewApp(demoapp)
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/IconView/iconviewedit.py b/examples/demo/demos/IconView/iconviewedit.py
new file mode 100644 (file)
index 0000000..85dfa93
--- /dev/null
@@ -0,0 +1,99 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.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.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 St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Editing and Drag-and-Drop"
+description = """The GtkIconView widget supports Editing and Drag-and-Drop.
+This example also demonstrates using the generic GtkCellLayout interface to set
+up cell renderers in an icon view.
+"""
+
+from gi.repository import Gtk, Gdk, GdkPixbuf
+
+
+class IconviewEditApp:
+    COL_TEXT = 0
+    NUM_COLS = 1
+
+    def __init__(self):
+        self.window = Gtk.Window()
+        self.window.set_title('Editing and Drag-and-Drop')
+        self.window.set_border_width(8)
+        self.window.connect('destroy', Gtk.main_quit)
+
+        store = Gtk.ListStore(str)
+        colors = ['Red', 'Green', 'Blue', 'Yellow']
+        store.clear()
+        for c in colors:
+            store.append([c])
+
+        icon_view = Gtk.IconView(model=store)
+        icon_view.set_selection_mode(Gtk.SelectionMode.SINGLE)
+        icon_view.set_item_orientation(Gtk.Orientation.HORIZONTAL)
+        icon_view.set_columns(2)
+        icon_view.set_reorderable(True)
+
+        renderer = Gtk.CellRendererPixbuf()
+        icon_view.pack_start(renderer, True)
+        icon_view.set_cell_data_func(renderer,
+                                     self.set_cell_color,
+                                     None)
+
+        renderer = Gtk.CellRendererText()
+        icon_view.pack_start(renderer, True)
+        renderer.props.editable = True
+        renderer.connect('edited', self.edited, icon_view)
+        icon_view.add_attribute(renderer, 'text', self.COL_TEXT)
+
+        self.window.add(icon_view)
+
+        self.window.show_all()
+
+    def set_cell_color(self, cell_layout, cell, tree_model, iter_, icon_view):
+
+        # FIXME return single element instead of tuple
+        text = tree_model.get(iter_, self.COL_TEXT)[0]
+        color = Gdk.color_parse(text)
+        pixel = 0
+        if color is not None:
+            pixel = ((color.red >> 8) << 24 |
+                     (color.green >> 8) << 16 |
+                     (color.blue >> 8) << 8)
+
+        pixbuf = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, False, 8, 24, 24)
+        pixbuf.fill(pixel)
+
+        cell.props.pixbuf = pixbuf
+
+    def edited(self, cell, path_string, text, icon_view):
+        model = icon_view.get_model()
+        path = Gtk.TreePath(path_string)
+
+        iter_ = model.get_iter(path)
+        model.set_row(iter_, [text])
+
+
+def main(demoapp=None):
+    IconviewEditApp()
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/TreeView/__init__.py b/examples/demo/demos/TreeView/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/examples/demo/demos/TreeView/liststore.py b/examples/demo/demos/TreeView/liststore.py
new file mode 100644 (file)
index 0000000..4b3daa1
--- /dev/null
@@ -0,0 +1,212 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.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.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 St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "List Store"
+description = """
+The GtkListStore is used to store data in list form, to be used later on by a
+GtkTreeView to display it. This demo builds a simple GtkListStore and displays
+it. See the Stock Browser demo for a more advanced example.
+"""
+
+
+from gi.repository import Gtk, GObject, GLib
+
+
+class Bug:
+    def __init__(self, is_fixed, number, severity, description):
+        self.is_fixed = is_fixed
+        self.number = number
+        self.severity = severity
+        self.description = description
+
+
+# initial data we use to fill in the store
+data = [Bug(False, 60482, "Normal", "scrollable notebooks and hidden tabs"),
+        Bug(False, 60620, "Critical", "gdk_window_clear_area (gdkwindow-win32.c) is not thread-safe"),
+        Bug(False, 50214, "Major", "Xft support does not clean up correctly"),
+        Bug(True, 52877, "Major", "GtkFileSelection needs a refresh method. "),
+        Bug(False, 56070, "Normal", "Can't click button after setting in sensitive"),
+        Bug(True, 56355, "Normal", "GtkLabel - Not all changes propagate correctly"),
+        Bug(False, 50055, "Normal", "Rework width/height computations for TreeView"),
+        Bug(False, 58278, "Normal", "gtk_dialog_set_response_sensitive () doesn't work"),
+        Bug(False, 55767, "Normal", "Getters for all setters"),
+        Bug(False, 56925, "Normal", "Gtkcalender size"),
+        Bug(False, 56221, "Normal", "Selectable label needs right-click copy menu"),
+        Bug(True, 50939, "Normal", "Add shift clicking to GtkTextView"),
+        Bug(False, 6112, "Enhancement", "netscape-like collapsable toolbars"),
+        Bug(False, 1, "Normal", "First bug :=)")]
+
+
+class ListStoreApp:
+    (COLUMN_FIXED,
+     COLUMN_NUMBER,
+     COLUMN_SEVERITY,
+     COLUMN_DESCRIPTION,
+     COLUMN_PULSE,
+     COLUMN_ICON,
+     COLUMN_ACTIVE,
+     COLUMN_SENSITIVE,
+     NUM_COLUMNS) = range(9)
+
+    def __init__(self):
+        self.window = Gtk.Window()
+        self.window.set_title('Gtk.ListStore Demo')
+        self.window.connect('destroy', Gtk.main_quit)
+
+        vbox = Gtk.VBox(spacing=8)
+        self.window.add(vbox)
+
+        label = Gtk.Label(label='This is the bug list (note: not based on real data, it would be nice to have a nice ODBC interface to bugzilla or so, though).')
+        vbox.pack_start(label, False, False, 0)
+
+        sw = Gtk.ScrolledWindow()
+        sw.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
+        sw.set_policy(Gtk.PolicyType.NEVER,
+                      Gtk.PolicyType.AUTOMATIC)
+        vbox.pack_start(sw, True, True, 0)
+
+        self.create_model()
+        treeview = Gtk.TreeView(model=self.model)
+        treeview.set_rules_hint(True)
+        treeview.set_search_column(self.COLUMN_DESCRIPTION)
+        sw.add(treeview)
+
+        self.add_columns(treeview)
+
+        self.window.set_default_size(280, 250)
+        self.window.show_all()
+
+        self.window.connect('delete-event', self.window_closed)
+        self.timeout = GLib.timeout_add(80, self.spinner_timeout)
+
+    def window_closed(self, window, event):
+        if self.timeout != 0:
+            GLib.source_remove(self.timeout)
+
+    def spinner_timeout(self):
+        if self.model is None:
+            return False
+
+        iter_ = self.model.get_iter_first()
+        pulse = self.model.get(iter_, self.COLUMN_PULSE)[0]
+        if pulse == 999999999:
+            pulse = 0
+        else:
+            pulse += 1
+
+        self.model.set_value(iter_, self.COLUMN_PULSE, pulse)
+        self.model.set_value(iter_, self.COLUMN_ACTIVE, True)
+
+        return True
+
+    def create_model(self):
+        self.model = Gtk.ListStore(bool,
+                                   GObject.TYPE_INT,
+                                   str,
+                                   str,
+                                   GObject.TYPE_INT,
+                                   str,
+                                   bool,
+                                   bool)
+
+        col = 0
+        for bug in data:
+            if col == 1 or col == 3:
+                icon_name = 'battery-critical-charging-symbolic'
+            else:
+                icon_name = ''
+            if col == 3:
+                is_sensitive = False
+            else:
+                is_sensitive = True
+
+            self.model.append([bug.is_fixed,
+                               bug.number,
+                               bug.severity,
+                               bug.description,
+                               0,
+                               icon_name,
+                               False,
+                               is_sensitive])
+            col += 1
+
+    def add_columns(self, treeview):
+        model = treeview.get_model()
+
+        # column for is_fixed toggle
+        renderer = Gtk.CellRendererToggle()
+        renderer.connect('toggled', self.is_fixed_toggled, model)
+
+        column = Gtk.TreeViewColumn("Fixed?", renderer,
+                                    active=self.COLUMN_FIXED)
+        column.set_fixed_width(50)
+        column.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
+        treeview.append_column(column)
+
+        # column for severities
+        renderer = Gtk.CellRendererText()
+        column = Gtk.TreeViewColumn("Severity", renderer,
+                                    text=self.COLUMN_SEVERITY)
+        column.set_sort_column_id(self.COLUMN_SEVERITY)
+        treeview.append_column(column)
+
+        # column for description
+        renderer = Gtk.CellRendererText()
+        column = Gtk.TreeViewColumn("Description", renderer,
+                                    text=self.COLUMN_DESCRIPTION)
+        column.set_sort_column_id(self.COLUMN_DESCRIPTION)
+        treeview.append_column(column)
+
+        # column for spinner
+        renderer = Gtk.CellRendererSpinner()
+        column = Gtk.TreeViewColumn("Spinning", renderer,
+                                    pulse=self.COLUMN_PULSE,
+                                    active=self.COLUMN_ACTIVE)
+        column.set_sort_column_id(self.COLUMN_PULSE)
+        treeview.append_column(column)
+
+        # column for symbolic icon
+        renderer = Gtk.CellRendererPixbuf()
+        renderer.props.follow_state = True
+        column = Gtk.TreeViewColumn("Symbolic icon", renderer,
+                                    icon_name=self.COLUMN_ICON,
+                                    sensitive=self.COLUMN_SENSITIVE)
+        column.set_sort_column_id(self.COLUMN_ICON)
+        treeview.append_column(column)
+
+    def is_fixed_toggled(self, cell, path_str, model):
+        # get toggled iter
+        iter_ = model.get_iter(path_str)
+        is_fixed = model.get_value(iter_, self.COLUMN_FIXED)
+
+        # do something with value
+        is_fixed ^= 1
+
+        model.set_value(iter_, self.COLUMN_FIXED, is_fixed)
+
+
+def main(demoapp=None):
+    ListStoreApp()
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/TreeView/treemodel_filelist.py b/examples/demo/demos/TreeView/treemodel_filelist.py
new file mode 100644 (file)
index 0000000..f011a47
--- /dev/null
@@ -0,0 +1,235 @@
+#!/usr/bin/env python
+
+title = "File List (GenericTreeModel)"
+description = """
+This is a file list demo which makes use of the GenericTreeModel python
+implementation of the Gtk.TreeModel interface. This demo shows what methods
+need to be overridden to provide a valid TreeModel to a TreeView.
+"""
+
+import os
+import stat
+import time
+
+import pygtkcompat
+pygtkcompat.enable()
+pygtkcompat.enable_gtk('3.0')
+
+import gtk
+
+
+folderxpm = [
+    "17 16 7 1",
+    "  c #000000",
+    ". c #808000",
+    "X c yellow",
+    "o c #808080",
+    "O c #c0c0c0",
+    "+ c white",
+    "@ c None",
+    "@@@@@@@@@@@@@@@@@",
+    "@@@@@@@@@@@@@@@@@",
+    "@@+XXXX.@@@@@@@@@",
+    "@+OOOOOO.@@@@@@@@",
+    "@+OXOXOXOXOXOXO. ",
+    "@+XOXOXOXOXOXOX. ",
+    "@+OXOXOXOXOXOXO. ",
+    "@+XOXOXOXOXOXOX. ",
+    "@+OXOXOXOXOXOXO. ",
+    "@+XOXOXOXOXOXOX. ",
+    "@+OXOXOXOXOXOXO. ",
+    "@+XOXOXOXOXOXOX. ",
+    "@+OOOOOOOOOOOOO. ",
+    "@                ",
+    "@@@@@@@@@@@@@@@@@",
+    "@@@@@@@@@@@@@@@@@"
+    ]
+folderpb = gtk.gdk.pixbuf_new_from_xpm_data(folderxpm)
+
+filexpm = [
+    "12 12 3 1",
+    "  c #000000",
+    ". c #ffff04",
+    "X c #b2c0dc",
+    "X        XXX",
+    "X ...... XXX",
+    "X ......   X",
+    "X .    ... X",
+    "X ........ X",
+    "X .   .... X",
+    "X ........ X",
+    "X .     .. X",
+    "X ........ X",
+    "X .     .. X",
+    "X ........ X",
+    "X          X"
+    ]
+filepb = gtk.gdk.pixbuf_new_from_xpm_data(filexpm)
+
+
+class FileListModel(gtk.GenericTreeModel):
+    __gtype_name__ = 'DemoFileListModel'
+
+    column_types = (gtk.gdk.Pixbuf, str, int, str, str)
+    column_names = ['Name', 'Size', 'Mode', 'Last Changed']
+
+    def __init__(self, dname=None):
+        gtk.GenericTreeModel.__init__(self)
+        self._sort_column_id = 0
+        self._sort_order = gtk.SORT_ASCENDING
+
+        if not dname:
+            self.dirname = os.path.expanduser('~')
+        else:
+            self.dirname = os.path.abspath(dname)
+        self.files = ['..'] + [f for f in os.listdir(self.dirname)]
+        return
+
+    def get_pathname(self, path):
+        filename = self.files[path[0]]
+        return os.path.join(self.dirname, filename)
+
+    def is_folder(self, path):
+        filename = self.files[path[0]]
+        pathname = os.path.join(self.dirname, filename)
+        filestat = os.stat(pathname)
+        if stat.S_ISDIR(filestat.st_mode):
+            return True
+        return False
+
+    def get_column_names(self):
+        return self.column_names[:]
+
+    #
+    # GenericTreeModel Implementation
+    #
+    def on_get_flags(self):
+        return 0  # gtk.TREE_MODEL_ITERS_PERSIST
+
+    def on_get_n_columns(self):
+        return len(self.column_types)
+
+    def on_get_column_type(self, n):
+        return self.column_types[n]
+
+    def on_get_iter(self, path):
+        return self.files[path[0]]
+
+    def on_get_path(self, rowref):
+        return self.files.index(rowref)
+
+    def on_get_value(self, rowref, column):
+        fname = os.path.join(self.dirname, rowref)
+        try:
+            filestat = os.stat(fname)
+        except OSError:
+            return None
+        mode = filestat.st_mode
+        if column is 0:
+            if stat.S_ISDIR(mode):
+                return folderpb
+            else:
+                return filepb
+        elif column is 1:
+            return rowref
+        elif column is 2:
+            return filestat.st_size
+        elif column is 3:
+            return oct(stat.S_IMODE(mode))
+        return time.ctime(filestat.st_mtime)
+
+    def on_iter_next(self, rowref):
+        try:
+            i = self.files.index(rowref) + 1
+            return self.files[i]
+        except IndexError:
+            return None
+
+    def on_iter_children(self, rowref):
+        if rowref:
+            return None
+        return self.files[0]
+
+    def on_iter_has_child(self, rowref):
+        return False
+
+    def on_iter_n_children(self, rowref):
+        if rowref:
+            return 0
+        return len(self.files)
+
+    def on_iter_nth_child(self, rowref, n):
+        if rowref:
+            return None
+        try:
+            return self.files[n]
+        except IndexError:
+            return None
+
+    def on_iter_parent(child):
+        return None
+
+
+class GenericTreeModelExample:
+    def delete_event(self, widget, event, data=None):
+        gtk.main_quit()
+        return False
+
+    def __init__(self):
+        # Create a new window
+        self.window = gtk.Window(type=gtk.WINDOW_TOPLEVEL)
+
+        self.window.set_size_request(300, 200)
+
+        self.window.connect("delete_event", self.delete_event)
+
+        self.listmodel = FileListModel()
+
+        # create the TreeView
+        self.treeview = gtk.TreeView()
+
+        self.tvcolumns = []
+
+        # create the TreeViewColumns to display the data
+        for n, name in enumerate(self.listmodel.get_column_names()):
+            if n == 0:
+                cellpb = gtk.CellRendererPixbuf()
+                col = gtk.TreeViewColumn(name, cellpb, pixbuf=0)
+                cell = gtk.CellRendererText()
+                col.pack_start(cell, False)
+                col.add_attribute(cell, 'text', 1)
+            else:
+                cell = gtk.CellRendererText()
+                col = gtk.TreeViewColumn(name, cell, text=n + 1)
+            if n == 1:
+                cell.set_property('xalign', 1.0)
+
+            self.treeview.append_column(col)
+
+        self.treeview.connect('row-activated', self.open_file)
+
+        self.scrolledwindow = gtk.ScrolledWindow()
+        self.scrolledwindow.add(self.treeview)
+        self.window.add(self.scrolledwindow)
+        self.treeview.set_model(self.listmodel)
+        self.window.set_title(self.listmodel.dirname)
+        self.window.show_all()
+
+    def open_file(self, treeview, path, column):
+        model = treeview.get_model()
+        if model.is_folder(path):
+            pathname = model.get_pathname(path)
+            new_model = FileListModel(pathname)
+            self.window.set_title(new_model.dirname)
+            treeview.set_model(new_model)
+        return
+
+
+def main(demoapp=None):
+    demo = GenericTreeModelExample()
+    demo
+    gtk.main()
+
+
+if __name__ == "__main__":
+    main()
diff --git a/examples/demo/demos/TreeView/treemodel_filetree.py b/examples/demo/demos/TreeView/treemodel_filetree.py
new file mode 100644 (file)
index 0000000..3b43190
--- /dev/null
@@ -0,0 +1,280 @@
+#!/usr/bin/env python
+
+title = "File Tree (GenericTreeModel)"
+description = """
+This is a file list demo which makes use of the GenericTreeModel python
+implementation of the Gtk.TreeModel interface. This demo shows what methods
+need to be overridden to provide a valid TreeModel to a TreeView.
+"""
+
+import os
+import stat
+import time
+from collections import OrderedDict
+
+import pygtkcompat
+pygtkcompat.enable_gtk('3.0')
+
+import gtk
+
+
+folderxpm = [
+    "17 16 7 1",
+    "  c #000000",
+    ". c #808000",
+    "X c yellow",
+    "o c #808080",
+    "O c #c0c0c0",
+    "+ c white",
+    "@ c None",
+    "@@@@@@@@@@@@@@@@@",
+    "@@@@@@@@@@@@@@@@@",
+    "@@+XXXX.@@@@@@@@@",
+    "@+OOOOOO.@@@@@@@@",
+    "@+OXOXOXOXOXOXO. ",
+    "@+XOXOXOXOXOXOX. ",
+    "@+OXOXOXOXOXOXO. ",
+    "@+XOXOXOXOXOXOX. ",
+    "@+OXOXOXOXOXOXO. ",
+    "@+XOXOXOXOXOXOX. ",
+    "@+OXOXOXOXOXOXO. ",
+    "@+XOXOXOXOXOXOX. ",
+    "@+OOOOOOOOOOOOO. ",
+    "@                ",
+    "@@@@@@@@@@@@@@@@@",
+    "@@@@@@@@@@@@@@@@@"
+    ]
+folderpb = gtk.gdk.pixbuf_new_from_xpm_data(folderxpm)
+
+filexpm = [
+    "12 12 3 1",
+    "  c #000000",
+    ". c #ffff04",
+    "X c #b2c0dc",
+    "X        XXX",
+    "X ...... XXX",
+    "X ......   X",
+    "X .    ... X",
+    "X ........ X",
+    "X .   .... X",
+    "X ........ X",
+    "X .     .. X",
+    "X ........ X",
+    "X .     .. X",
+    "X ........ X",
+    "X          X"
+    ]
+filepb = gtk.gdk.pixbuf_new_from_xpm_data(filexpm)
+
+
+class FileTreeModel(gtk.GenericTreeModel):
+    __gtype_name__ = 'DemoFileTreeModel'
+
+    column_types = (gtk.gdk.Pixbuf, str, int, str, str)
+    column_names = ['Name', 'Size', 'Mode', 'Last Changed']
+
+    def __init__(self, dname=None):
+        gtk.GenericTreeModel.__init__(self)
+        if not dname:
+            self.dirname = os.path.expanduser('~')
+        else:
+            self.dirname = os.path.abspath(dname)
+        self.files = self.build_file_dict(self.dirname)
+        return
+
+    def build_file_dict(self, dirname):
+        """
+        :Returns:
+            A dictionary containing the files in the given dirname keyed by filename.
+            If the child filename is a sub-directory, the dict value is a dict.
+            Otherwise it will be None.
+        """
+        d = OrderedDict()
+        for fname in os.listdir(dirname):
+            try:
+                filestat = os.stat(os.path.join(dirname, fname))
+            except OSError:
+                d[fname] = None
+            else:
+                d[fname] = OrderedDict() if stat.S_ISDIR(filestat.st_mode) else None
+
+        return d
+
+    def get_node_from_treepath(self, path):
+        """
+        :Returns:
+            The node stored at the given tree path in local storage.
+        """
+        # TreePaths are a series of integer indices so just iterate through them
+        # and index values by each integer since we are using an OrderedDict
+        if path is None:
+            path = []
+        node = self.files
+        for index in path:
+            node = list(node.values())[index]
+        return node
+
+    def get_node_from_filepath(self, filepath):
+        """
+        :Returns:
+            The node stored at the given file path in local storage.
+        """
+        if not filepath:
+            return self.files
+        node = self.files
+        for key in filepath.split(os.path.sep):
+            node = node[key]
+        return node
+
+    def get_column_names(self):
+        return self.column_names[:]
+
+    #
+    # GenericTreeModel Implementation
+    #
+
+    def on_get_flags(self):
+        return 0
+
+    def on_get_n_columns(self):
+        return len(self.column_types)
+
+    def on_get_column_type(self, n):
+        return self.column_types[n]
+
+    def on_get_path(self, relpath):
+        path = []
+        node = self.files
+        for key in relpath.split(os.path.sep):
+            path.append(list(node.keys()).index(key))
+            node = node[key]
+        return path
+
+    def on_get_value(self, relpath, column):
+        fname = os.path.join(self.dirname, relpath)
+        try:
+            filestat = os.stat(fname)
+        except OSError:
+            return None
+        mode = filestat.st_mode
+        if column is 0:
+            if stat.S_ISDIR(mode):
+                return folderpb
+            else:
+                return filepb
+        elif column is 1:
+            return os.path.basename(relpath)
+        elif column is 2:
+            return filestat.st_size
+        elif column is 3:
+            return oct(stat.S_IMODE(mode))
+        return time.ctime(filestat.st_mtime)
+
+    def on_get_iter(self, path):
+        filepath = ''
+        value = self.files
+        for index in path:
+            filepath = os.path.join(filepath, list(value.keys())[index])
+            value = list(value.values())[index]
+        return filepath
+
+    def on_iter_next(self, filepath):
+        parent_path, child_path = os.path.split(filepath)
+        parent = self.get_node_from_filepath(parent_path)
+
+        # Index of filepath within its parents child list
+        sibling_names = list(parent.keys())
+        index = sibling_names.index(child_path)
+        try:
+            return os.path.join(parent_path, sibling_names[index + 1])
+        except IndexError:
+            return None
+
+    def on_iter_children(self, filepath):
+        if filepath:
+            children = list(self.get_node_from_filepath(filepath).keys())
+            if children:
+                return os.path.join(filepath, children[0])
+        elif self.files:
+            return list(self.files.keys())[0]
+
+        return None
+
+    def on_iter_has_child(self, filepath):
+        return bool(self.get_node_from_filepath(filepath))
+
+    def on_iter_n_children(self, filepath):
+        return len(self.get_node_from_filepath(filepath))
+
+    def on_iter_nth_child(self, filepath, n):
+        try:
+            child = list(self.get_node_from_filepath(filepath).keys())[n]
+            if filepath:
+                return os.path.join(filepath, child)
+            else:
+                return child
+        except IndexError:
+            return None
+
+    def on_iter_parent(self, filepath):
+        return os.path.dirname(filepath)
+
+    def on_ref_node(self, filepath):
+        value = self.get_node_from_filepath(filepath)
+        if value is not None:
+            value.update(self.build_file_dict(os.path.join(self.dirname, filepath)))
+
+    def on_unref_node(self, filepath):
+        pass
+
+
+class GenericTreeModelExample:
+    def delete_event(self, widget, event, data=None):
+        gtk.main_quit()
+        return False
+
+    def __init__(self):
+        # Create a new window
+        self.window = gtk.Window(type=gtk.WINDOW_TOPLEVEL)
+        self.window.set_size_request(300, 200)
+        self.window.connect("delete_event", self.delete_event)
+
+        self.listmodel = FileTreeModel()
+
+        # create the TreeView
+        self.treeview = gtk.TreeView()
+
+        # create the TreeViewColumns to display the data
+        column_names = self.listmodel.get_column_names()
+        self.tvcolumn = [None] * len(column_names)
+        cellpb = gtk.CellRendererPixbuf()
+        self.tvcolumn[0] = gtk.TreeViewColumn(column_names[0],
+                                              cellpb, pixbuf=0)
+        cell = gtk.CellRendererText()
+        self.tvcolumn[0].pack_start(cell, False)
+        self.tvcolumn[0].add_attribute(cell, 'text', 1)
+        self.treeview.append_column(self.tvcolumn[0])
+        for n in range(1, len(column_names)):
+            cell = gtk.CellRendererText()
+            if n == 1:
+                cell.set_property('xalign', 1.0)
+            self.tvcolumn[n] = gtk.TreeViewColumn(column_names[n],
+                                                  cell, text=n + 1)
+            self.treeview.append_column(self.tvcolumn[n])
+
+        self.scrolledwindow = gtk.ScrolledWindow()
+        self.scrolledwindow.add(self.treeview)
+        self.window.add(self.scrolledwindow)
+        self.treeview.set_model(self.listmodel)
+        self.window.set_title(self.listmodel.dirname)
+        self.window.show_all()
+
+
+def main(demoapp=None):
+    demo = GenericTreeModelExample()
+    demo
+    gtk.main()
+
+
+if __name__ == "__main__":
+    main()
diff --git a/examples/demo/demos/TreeView/treemodel_large.py b/examples/demo/demos/TreeView/treemodel_large.py
new file mode 100644 (file)
index 0000000..b129521
--- /dev/null
@@ -0,0 +1,143 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# pygobject - Python bindings for the GObject library
+# Copyright (C) 2014 Simon Feltman
+#
+# 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, see <http://www.gnu.org/licenses/>.
+
+title = "Tree Model with Large Data"
+description = """
+Implementation of the Gtk.TreeModel interface to create a custom model.
+The demo uses a fake data store (it is not backed by a Python list) and is for
+the purpose of showing how to override the TreeModel interfaces virtual methods.
+"""
+
+from gi.repository import GObject
+from gi.repository import GLib
+from gi.repository import Gtk
+
+
+class Model(GObject.Object, Gtk.TreeModel):
+    columns_types = (str, str)
+    item_count = 100000
+    item_data = 'abcdefghijklmnopqrstuvwxyz'
+
+    def __init__(self):
+        super(Model, self).__init__()
+
+    def do_get_flags(self):
+        return Gtk.TreeModelFlags.LIST_ONLY
+
+    def do_get_n_columns(self):
+        return len(self.columns_types)
+
+    def do_get_column_type(self, n):
+        return self.columns_types[n]
+
+    def do_get_iter(self, path):
+        # Return False and an empty iter when out of range
+        index = path.get_indices()[0]
+        if index < 0 or index >= self.item_count:
+            return False, None
+
+        it = Gtk.TreeIter()
+        it.user_data = index
+        return True, it
+
+    def do_get_path(self, it):
+        return Gtk.TreePath([it.user_data])
+
+    def do_get_value(self, it, column):
+        if column == 0:
+            return str(it.user_data)
+        elif column == 1:
+            return self.item_data
+
+    def do_iter_next(self, it):
+        # Return False if there is not a next item
+        next = it.user_data + 1
+        if next >= self.item_count:
+            return False
+
+        # Set the iters data and return True
+        it.user_data = next
+        return True
+
+    def do_iter_previous(self, it):
+        prev = it.user_data - 1
+        if prev < 0:
+            return False
+
+        it.user_data = prev
+        return True
+
+    def do_iter_children(self, parent):
+        # If parent is None return the first item
+        if parent is None:
+            it = Gtk.TreeIter()
+            it.user_data = 0
+            return True, it
+        return False, None
+
+    def do_iter_has_child(self, it):
+        return it is None
+
+    def do_iter_n_children(self, it):
+        # If iter is None, return the number of top level nodes
+        if it is None:
+            return self.item_count
+        return 0
+
+    def do_iter_nth_child(self, parent, n):
+        if parent is not None or n >= self.item_count:
+            return False, None
+        elif parent is None:
+            # If parent is None, return the nth iter
+            it = Gtk.TreeIter()
+            it.user_data = n
+            return True, it
+
+    def do_iter_parent(self, child):
+        return False, None
+
+
+def main(demoapp=None):
+    model = Model()
+    # Use fixed-height-mode to get better model load and display performance.
+    view = Gtk.TreeView(fixed_height_mode=True, headers_visible=False)
+    column = Gtk.TreeViewColumn()
+    column.props.sizing = Gtk.TreeViewColumnSizing.FIXED
+
+    renderer1 = Gtk.CellRendererText()
+    renderer2 = Gtk.CellRendererText()
+    column.pack_start(renderer1, expand=True)
+    column.pack_start(renderer2, expand=True)
+    column.add_attribute(renderer1, 'text', 0)
+    column.add_attribute(renderer2, 'text', 1)
+    view.append_column(column)
+
+    scrolled = Gtk.ScrolledWindow()
+    scrolled.add(view)
+
+    window = Gtk.Window(title=title)
+    window.set_size_request(480, 640)
+    window.add(scrolled)
+    window.show_all()
+    GLib.timeout_add(10, lambda *args: view.set_model(model))
+    return window
+
+
+if __name__ == "__main__":
+    window = main()
+    window.connect('destroy', Gtk.main_quit)
+    Gtk.main()
diff --git a/examples/demo/demos/__init__.py b/examples/demo/demos/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/examples/demo/demos/appwindow.py b/examples/demo/demos/appwindow.py
new file mode 100644 (file)
index 0000000..893ecc0
--- /dev/null
@@ -0,0 +1,408 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.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.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 St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Application main window"
+description = """
+Demonstrates a typical application window with menubar, toolbar, statusbar.
+"""
+
+import os
+
+from gi.repository import GdkPixbuf, Gtk
+
+
+infobar = None
+window = None
+messagelabel = None
+_demoapp = None
+
+
+def widget_destroy(widget, button):
+    widget.destroy()
+
+
+def activate_action(action, user_data=None):
+    global window
+
+    name = action.get_name()
+    _type = type(action)
+    if name == 'DarkTheme':
+        value = action.get_active()
+        settings = Gtk.Settings.get_default()
+        settings.set_property('gtk-application-prefer-dark-theme', value)
+        return
+
+    dialog = Gtk.MessageDialog(message_type=Gtk.MessageType.INFO,
+                               buttons=Gtk.ButtonsType.CLOSE,
+                               text='You activated action: "%s" of type %s' % (name, _type))
+
+    # FIXME: this should be done in the constructor
+    dialog.set_transient_for(window)
+    dialog.connect('response', widget_destroy)
+    dialog.show()
+
+
+def activate_radio_action(action, current, user_data=None):
+    global infobar
+    global messagelabel
+
+    name = current.get_name()
+    _type = type(current)
+    active = current.get_active()
+    value = current.get_current_value()
+    if active:
+        text = 'You activated radio action: "%s" of type %s.\n Current value: %d' % (name, _type, value)
+        messagelabel.set_text(text)
+        infobar.set_message_type(Gtk.MessageType(value))
+        infobar.show()
+
+
+def update_statusbar(buffer, statusbar):
+    statusbar.pop(0)
+    count = buffer.get_char_count()
+
+    iter = buffer.get_iter_at_mark(buffer.get_insert())
+    row = iter.get_line()
+    col = iter.get_line_offset()
+    msg = 'Cursor at row %d column %d - %d chars in document' % (row, col, count)
+
+    statusbar.push(0, msg)
+
+
+def mark_set_callback(buffer, new_location, mark, data):
+    update_statusbar(buffer, data)
+
+
+def about_cb(widget, user_data=None):
+    global window
+
+    authors = ['John (J5) Palmieri',
+               'Tomeu Vizoso',
+               'and many more...']
+
+    documentors = ['David Malcolm',
+                   'Zack Goldberg',
+                   'and many more...']
+
+    license = """
+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 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.
+"""
+    dirname = os.path.abspath(os.path.dirname(__file__))
+    filename = os.path.join(dirname, 'data', 'gtk-logo-rgb.gif')
+    pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename)
+    transparent = pixbuf.add_alpha(True, 0xff, 0xff, 0xff)
+
+    about = Gtk.AboutDialog(parent=window,
+                            program_name='GTK+ Code Demos',
+                            version='0.1',
+                            copyright='(C) 2010 The PyGI Team',
+                            license=license,
+                            website='http://live.gnome.org/PyGI',
+                            comments='Program to demonstrate PyGI functions.',
+                            authors=authors,
+                            documenters=documentors,
+                            logo=transparent,
+                            title='About GTK+ Code Demos')
+
+    about.connect('response', widget_destroy)
+    about.show()
+
+
+action_entries = (
+    ("FileMenu", None, "_File"),                # name, stock id, label
+    ("OpenMenu", None, "_Open"),                # name, stock id, label
+    ("PreferencesMenu", None, "_Preferences"),  # name, stock id, label
+    ("ColorMenu", None, "_Color"),              # name, stock id, label
+    ("ShapeMenu", None, "_Shape"),              # name, stock id, label
+    ("HelpMenu", None, "_Help"),                # name, stock id, label
+    ("New", Gtk.STOCK_NEW,                      # name, stock id
+     "_New", "<control>N",                      # label, accelerator
+     "Create a new file",                       # tooltip
+     activate_action),
+    ("File1", None,                             # name, stock id
+     "File1", None,                             # label, accelerator
+     "Open first file",                         # tooltip
+     activate_action),
+    ("Save", Gtk.STOCK_SAVE,                    # name, stock id
+     "_Save", "<control>S",                     # label, accelerator
+     "Save current file",                       # tooltip
+     activate_action),
+    ("SaveAs", Gtk.STOCK_SAVE,                  # name, stock id
+     "Save _As...", None,                       # label, accelerator
+     "Save to a file",                          # tooltip
+     activate_action),
+    ("Quit", Gtk.STOCK_QUIT,                    # name, stock id
+     "_Quit", "<control>Q",                     # label, accelerator
+     "Quit",                                    # tooltip
+     activate_action),
+    ("About", None,                             # name, stock id
+     "_About", "<control>A",                    # label, accelerator
+     "About",                                   # tooltip
+     about_cb),
+    ("Logo", "demo-gtk-logo",                   # name, stock id
+     None, None,                                # label, accelerator
+     "GTK+",                                    # tooltip
+     activate_action),
+)
+
+toggle_action_entries = (
+    ("Bold", Gtk.STOCK_BOLD,                    # name, stock id
+     "_Bold", "<control>B",                     # label, accelerator
+     "Bold",                                    # tooltip
+     activate_action,
+     True),                                     # is_active
+    ("DarkTheme", None,                         # name, stock id
+     "_Prefer Dark Theme", None,                # label, accelerator
+     "Prefer Dark Theme",                       # tooltip
+     activate_action,
+     False),                                    # is_active
+)
+
+(COLOR_RED,
+ COLOR_GREEN,
+ COLOR_BLUE) = range(3)
+
+color_action_entries = (
+    ("Red", None,                               # name, stock id
+     "_Red", "<control>R",                      # label, accelerator
+     "Blood", COLOR_RED),                       # tooltip, value
+    ("Green", None,                             # name, stock id
+     "_Green", "<control>G",                    # label, accelerator
+     "Grass", COLOR_GREEN),                     # tooltip, value
+    ("Blue", None,                              # name, stock id
+     "_Blue", "<control>B",                     # label, accelerator
+     "Sky", COLOR_BLUE),                        # tooltip, value
+)
+
+(SHAPE_SQUARE,
+ SHAPE_RECTANGLE,
+ SHAPE_OVAL) = range(3)
+
+shape_action_entries = (
+    ("Square", None,                            # name, stock id
+     "_Square", "<control>S",                   # label, accelerator
+     "Square", SHAPE_SQUARE),                   # tooltip, value
+    ("Rectangle", None,                         # name, stock id
+     "_Rectangle", "<control>R",                # label, accelerator
+     "Rectangle", SHAPE_RECTANGLE),             # tooltip, value
+    ("Oval", None,                              # name, stock id
+     "_Oval", "<control>O",                     # label, accelerator
+     "Egg", SHAPE_OVAL),                        # tooltip, value
+)
+
+ui_info = """
+<ui>
+  <menubar name='MenuBar'>
+    <menu action='FileMenu'>
+      <menuitem action='New'/>
+      <menuitem action='Open'/>
+      <menuitem action='Save'/>
+      <menuitem action='SaveAs'/>
+      <separator/>
+      <menuitem action='Quit'/>
+    </menu>
+    <menu action='PreferencesMenu'>
+      <menuitem action='DarkTheme'/>
+      <menu action='ColorMenu'>
+    <menuitem action='Red'/>
+    <menuitem action='Green'/>
+    <menuitem action='Blue'/>
+      </menu>
+      <menu action='ShapeMenu'>
+        <menuitem action='Square'/>
+        <menuitem action='Rectangle'/>
+        <menuitem action='Oval'/>
+      </menu>
+      <menuitem action='Bold'/>
+    </menu>
+    <menu action='HelpMenu'>
+      <menuitem action='About'/>
+    </menu>
+  </menubar>
+  <toolbar name='ToolBar'>
+    <toolitem action='Open'>
+      <menu action='OpenMenu'>
+        <menuitem action='File1'/>
+      </menu>
+    </toolitem>
+    <toolitem action='Quit'/>
+    <separator action='Sep1'/>
+    <toolitem action='Logo'/>
+  </toolbar>
+</ui>
+"""
+
+
+def _quit(*args):
+    Gtk.main_quit()
+
+
+def register_stock_icons():
+    """
+    This function registers our custom toolbar icons, so they can be themed.
+    It's totally optional to do this, you could just manually insert icons
+    and have them not be themeable, especially if you never expect people
+    to theme your app.
+    """
+    '''
+    item = Gtk.StockItem()
+    item.stock_id = 'demo-gtk-logo'
+    item.label = '_GTK!'
+    item.modifier = 0
+    item.keyval = 0
+    item.translation_domain = None
+
+    Gtk.stock_add(item, 1)
+    '''
+    global _demoapp
+
+    factory = Gtk.IconFactory()
+    factory.add_default()
+
+    if _demoapp is None:
+        filename = os.path.join('data', 'gtk-logo-rgb.gif')
+    else:
+        filename = _demoapp.find_file('gtk-logo-rgb.gif')
+
+    pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename)
+    transparent = pixbuf.add_alpha(True, 0xff, 0xff, 0xff)
+    icon_set = Gtk.IconSet.new_from_pixbuf(transparent)
+
+    factory.add('demo-gtk-logo', icon_set)
+
+
+class ToolMenuAction(Gtk.Action):
+    __gtype_name__ = "GtkToolMenuAction"
+
+    def do_create_tool_item(self):
+        return Gtk.MenuToolButton()
+
+
+def main(demoapp=None):
+    global infobar
+    global window
+    global messagelabel
+    global _demoapp
+
+    _demoapp = demoapp
+
+    register_stock_icons()
+
+    window = Gtk.Window()
+    window.set_title('Application Window')
+    window.set_icon_name('gtk-open')
+    window.connect_after('destroy', _quit)
+    table = Gtk.Table(n_rows=1,
+                      n_columns=5,
+                      homogeneous=False)
+    window.add(table)
+
+    action_group = Gtk.ActionGroup(name='AppWindowActions')
+    open_action = ToolMenuAction(name='Open',
+                                 stock_id=Gtk.STOCK_OPEN,
+                                 label='_Open',
+                                 tooltip='Open a file')
+
+    action_group.add_action(open_action)
+    action_group.add_actions(action_entries)
+    action_group.add_toggle_actions(toggle_action_entries)
+    action_group.add_radio_actions(color_action_entries,
+                                   COLOR_RED,
+                                   activate_radio_action)
+    action_group.add_radio_actions(shape_action_entries,
+                                   SHAPE_SQUARE,
+                                   activate_radio_action)
+
+    merge = Gtk.UIManager()
+    merge.insert_action_group(action_group, 0)
+    window.add_accel_group(merge.get_accel_group())
+
+    merge.add_ui_from_string(ui_info)
+
+    bar = merge.get_widget('/MenuBar')
+    bar.show()
+    table.attach(bar, 0, 1, 0, 1,
+                 Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
+                 0, 0, 0)
+
+    bar = merge.get_widget('/ToolBar')
+    bar.show()
+    table.attach(bar, 0, 1, 1, 2,
+                 Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
+                 0, 0, 0)
+
+    infobar = Gtk.InfoBar()
+    infobar.set_no_show_all(True)
+    messagelabel = Gtk.Label()
+    messagelabel.show()
+    infobar.get_content_area().pack_start(messagelabel, True, True, 0)
+    infobar.add_button(Gtk.STOCK_OK, Gtk.ResponseType.OK)
+    infobar.connect('response', lambda a, b: Gtk.Widget.hide(a))
+
+    table.attach(infobar, 0, 1, 2, 3,
+                 Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
+                 0, 0, 0)
+
+    sw = Gtk.ScrolledWindow(hadjustment=None,
+                            vadjustment=None)
+    sw.set_shadow_type(Gtk.ShadowType.IN)
+    table.attach(sw, 0, 1, 3, 4,
+                 Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
+                 Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
+                 0, 0)
+
+    contents = Gtk.TextView()
+    contents.grab_focus()
+    sw.add(contents)
+
+    # Create statusbar
+    statusbar = Gtk.Statusbar()
+    table.attach(statusbar, 0, 1, 4, 5,
+                 Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
+                 0, 0, 0)
+
+    # show text widget info in the statusbar
+    buffer = contents.get_buffer()
+    buffer.connect('changed', update_statusbar, statusbar)
+    buffer.connect('mark_set', mark_set_callback, statusbar)
+
+    update_statusbar(buffer, statusbar)
+
+    window.set_default_size(200, 200)
+    window.show_all()
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/assistant.py b/examples/demo/demos/assistant.py
new file mode 100644 (file)
index 0000000..9e729e9
--- /dev/null
@@ -0,0 +1,135 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.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.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 St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Assistant"
+description = """
+Demonstrates a sample multistep assistant. Assistants are used to divide
+an operation into several simpler sequential steps, and to guide the user
+through these steps.
+"""
+
+
+from gi.repository import Gtk
+
+
+class AssistantApp:
+    def __init__(self):
+        self.assistant = Gtk.Assistant()
+        self.assistant.set_default_size(-1, 300)
+
+        self.create_page1()
+        self.create_page2()
+        self.create_page3()
+
+        self.assistant.connect('cancel', self.on_close_cancel)
+        self.assistant.connect('close', self.on_close_cancel)
+        self.assistant.connect('apply', self.on_apply)
+        self.assistant.connect('prepare', self.on_prepare)
+
+        self.assistant.show()
+
+    def on_close_cancel(self, assistant):
+        assistant.destroy()
+        Gtk.main_quit()
+
+    def on_apply(self, assistant):
+        # apply changes here; this is a fictional example so just do
+        # nothing here
+        pass
+
+    def on_prepare(self, assistant, page):
+        current_page = assistant.get_current_page()
+        n_pages = assistant.get_n_pages()
+        title = 'Sample assistant (%d of %d)' % (current_page + 1, n_pages)
+        assistant.set_title(title)
+
+    def on_entry_changed(self, widget):
+        page_number = self.assistant.get_current_page()
+        current_page = self.assistant.get_nth_page(page_number)
+        text = widget.get_text()
+
+        if text:
+            self.assistant.set_page_complete(current_page, True)
+        else:
+            self.assistant.set_page_complete(current_page, False)
+
+    def create_page1(self):
+        box = Gtk.HBox(homogeneous=False,
+                       spacing=12)
+        box.set_border_width(12)
+        label = Gtk.Label(label='You must fill out this entry to continue:')
+        box.pack_start(label, False, False, 0)
+
+        entry = Gtk.Entry()
+        box.pack_start(entry, True, True, 0)
+        entry.connect('changed', self.on_entry_changed)
+
+        box.show_all()
+        self.assistant.append_page(box)
+        self.assistant.set_page_title(box, 'Page 1')
+        self.assistant.set_page_type(box, Gtk.AssistantPageType.INTRO)
+
+        pixbuf = self.assistant.render_icon(Gtk.STOCK_DIALOG_INFO,
+                                            Gtk.IconSize.DIALOG,
+                                            None)
+
+        self.assistant.set_page_header_image(box, pixbuf)
+
+    def create_page2(self):
+        box = Gtk.VBox(homogeneous=False,
+                       spacing=12)
+        box.set_border_width(12)
+
+        checkbutton = Gtk.CheckButton(label='This is optional data, you may continue even if you do not check this')
+        box.pack_start(checkbutton, False, False, 0)
+
+        box.show_all()
+
+        self.assistant.append_page(box)
+        self.assistant.set_page_complete(box, True)
+        self.assistant.set_page_title(box, 'Page 2')
+
+        pixbuf = self.assistant.render_icon(Gtk.STOCK_DIALOG_INFO,
+                                            Gtk.IconSize.DIALOG,
+                                            None)
+        self.assistant.set_page_header_image(box, pixbuf)
+
+    def create_page3(self):
+        label = Gtk.Label(label='This is a confirmation page, press "Apply" to apply changes')
+        label.show()
+        self.assistant.append_page(label)
+        self.assistant.set_page_complete(label, True)
+        self.assistant.set_page_title(label, 'Confirmation')
+        self.assistant.set_page_type(label, Gtk.AssistantPageType.CONFIRM)
+
+        pixbuf = self.assistant.render_icon(Gtk.STOCK_DIALOG_INFO,
+                                            Gtk.IconSize.DIALOG,
+                                            None)
+        self.assistant.set_page_header_image(label, pixbuf)
+
+
+def main(demoapp=None):
+    AssistantApp()
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/builder.py b/examples/demo/demos/builder.py
new file mode 100644 (file)
index 0000000..47e09a4
--- /dev/null
@@ -0,0 +1,65 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.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.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 St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Builder"
+description = """
+Demonstrates an interface loaded from a XML description.
+"""
+
+
+import os
+
+from gi.repository import Gtk
+
+
+class BuilderApp:
+    def __init__(self, demoapp):
+        self.demoapp = demoapp
+
+        self.builder = Gtk.Builder()
+        if demoapp is None:
+            filename = os.path.join('data', 'demo.ui')
+        else:
+            filename = demoapp.find_file('demo.ui')
+
+        self.builder.add_from_file(filename)
+        self.builder.connect_signals(self)
+
+        window = self.builder.get_object('window1')
+        window.connect('destroy', lambda x: Gtk.main_quit())
+        window.show_all()
+
+    def about_activate(self, action):
+        about_dlg = self.builder.get_object('aboutdialog1')
+        about_dlg.run()
+        about_dlg.hide()
+
+    def quit_activate(self, action):
+        Gtk.main_quit()
+
+
+def main(demoapp=None):
+    BuilderApp(demoapp)
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/button_box.py b/examples/demo/demos/button_box.py
new file mode 100644 (file)
index 0000000..be94984
--- /dev/null
@@ -0,0 +1,122 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.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.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 St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Button Boxes"
+description = """
+The Button Box widgets are used to arrange buttons with padding.
+"""
+
+
+from gi.repository import Gtk
+
+
+class ButtonBoxApp:
+    def __init__(self):
+        window = Gtk.Window()
+        window.set_title('Button Boxes')
+        window.connect('destroy', lambda x: Gtk.main_quit())
+        window.set_border_width(10)
+
+        main_vbox = Gtk.VBox(homogeneous=False, spacing=0)
+        window.add(main_vbox)
+
+        frame_horz = Gtk.Frame(label='Horizontal Button Boxes')
+        main_vbox.pack_start(frame_horz, True, True, 10)
+
+        vbox = Gtk.VBox(homogeneous=False, spacing=0)
+        vbox.set_border_width(10)
+        frame_horz.add(vbox)
+
+        vbox.pack_start(
+            self.create_bbox(True, 'Spread', 40, Gtk.ButtonBoxStyle.SPREAD),
+            True, True, 0)
+
+        vbox.pack_start(
+            self.create_bbox(True, 'Edge', 40, Gtk.ButtonBoxStyle.EDGE),
+            True, True, 5)
+
+        vbox.pack_start(
+            self.create_bbox(True, 'Start', 40, Gtk.ButtonBoxStyle.START),
+            True, True, 5)
+
+        vbox.pack_start(
+            self.create_bbox(True, 'End', 40, Gtk.ButtonBoxStyle.END),
+            True, True, 5)
+
+        frame_vert = Gtk.Frame(label='Vertical Button Boxes')
+        main_vbox.pack_start(frame_vert, True, True, 10)
+
+        hbox = Gtk.HBox(homogeneous=False, spacing=0)
+        hbox.set_border_width(10)
+        frame_vert.add(hbox)
+
+        hbox.pack_start(
+            self.create_bbox(False, 'Spread', 30, Gtk.ButtonBoxStyle.SPREAD),
+            True, True, 0)
+
+        hbox.pack_start(
+            self.create_bbox(False, 'Edge', 30, Gtk.ButtonBoxStyle.EDGE),
+            True, True, 5)
+
+        hbox.pack_start(
+            self.create_bbox(False, 'Start', 30, Gtk.ButtonBoxStyle.START),
+            True, True, 5)
+
+        hbox.pack_start(
+            self.create_bbox(False, 'End', 30, Gtk.ButtonBoxStyle.END),
+            True, True, 5)
+
+        window.show_all()
+
+    def create_bbox(self, is_horizontal, title, spacing, layout):
+        frame = Gtk.Frame(label=title)
+
+        if is_horizontal:
+            bbox = Gtk.HButtonBox()
+        else:
+            bbox = Gtk.VButtonBox()
+
+        bbox.set_border_width(5)
+        frame.add(bbox)
+
+        bbox.set_layout(layout)
+        bbox.set_spacing(spacing)
+
+        # FIXME: GtkButton consturctor should take a stock_id
+        button = Gtk.Button.new_from_stock(Gtk.STOCK_OK)
+        bbox.add(button)
+
+        button = Gtk.Button.new_from_stock(Gtk.STOCK_CANCEL)
+        bbox.add(button)
+
+        button = Gtk.Button.new_from_stock(Gtk.STOCK_HELP)
+        bbox.add(button)
+
+        return frame
+
+
+def main(demoapp=None):
+    ButtonBoxApp()
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/clipboard.py b/examples/demo/demos/clipboard.py
new file mode 100644 (file)
index 0000000..5a88828
--- /dev/null
@@ -0,0 +1,228 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.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.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 St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Clipboard"
+description = """
+GtkClipboard is used for clipboard handling. This demo shows how to
+copy and paste text to and from the clipboard.
+
+It also shows how to transfer images via the clipboard or via
+drag-and-drop, and how to make clipboard contents persist after
+the application exits. Clipboard persistence requires a clipboard
+manager to run.
+"""
+
+
+from gi.repository import Gtk, Gdk
+
+
+class ClipboardApp:
+    def __init__(self):
+        self.window = Gtk.Window()
+        self.window.set_title('Clipboard demo')
+        self.window.connect('destroy', lambda w: Gtk.main_quit())
+
+        vbox = Gtk.VBox(homogeneous=False, spacing=0)
+        vbox.set_border_width(8)
+        self.window.add(vbox)
+
+        label = Gtk.Label(label='"Copy" will copy the text\nin the entry to the clipboard')
+        vbox.pack_start(label, False, False, 0)
+
+        hbox = Gtk.HBox(homogeneous=False, spacing=4)
+        hbox.set_border_width(8)
+        vbox.pack_start(hbox, False, False, 0)
+
+        # create first entry
+        entry = Gtk.Entry()
+        hbox.pack_start(entry, True, True, 0)
+
+        # create button
+        button = Gtk.Button.new_from_stock(Gtk.STOCK_COPY)
+        hbox.pack_start(button, False, False, 0)
+        button.connect('clicked', self.copy_button_clicked, entry)
+
+        label = Gtk.Label(label='"Paste" will paste the text from the clipboard to the entry')
+        vbox.pack_start(label, False, False, 0)
+
+        hbox = Gtk.HBox(homogeneous=False, spacing=4)
+        hbox.set_border_width(8)
+        vbox.pack_start(hbox, False, False, 0)
+
+        # create secondary entry
+        entry = Gtk.Entry()
+        hbox.pack_start(entry, True, True, 0)
+        # create button
+        button = Gtk.Button.new_from_stock(Gtk.STOCK_PASTE)
+        hbox.pack_start(button, False, False, 0)
+        button.connect('clicked', self.paste_button_clicked, entry)
+
+        label = Gtk.Label(label='Images can be transferred via the clipboard, too')
+        vbox.pack_start(label, False, False, 0)
+
+        hbox = Gtk.HBox(homogeneous=False, spacing=4)
+        hbox.set_border_width(8)
+        vbox.pack_start(hbox, False, False, 0)
+
+        # create the first image
+        image = Gtk.Image(stock=Gtk.STOCK_DIALOG_WARNING,
+                          icon_size=Gtk.IconSize.BUTTON)
+
+        ebox = Gtk.EventBox()
+        ebox.add(image)
+        hbox.add(ebox)
+
+        # make ebox a drag source
+        ebox.drag_source_set(Gdk.ModifierType.BUTTON1_MASK,
+                             None, Gdk.DragAction.COPY)
+        ebox.drag_source_add_image_targets()
+        ebox.connect('drag-begin', self.drag_begin, image)
+        ebox.connect('drag-data-get', self.drag_data_get, image)
+
+        # accept drops on ebox
+        ebox.drag_dest_set(Gtk.DestDefaults.ALL,
+                           None, Gdk.DragAction.COPY)
+        ebox.drag_dest_add_image_targets()
+        ebox.connect('drag-data-received', self.drag_data_received, image)
+
+        # context menu on ebox
+        ebox.connect('button-press-event', self.button_press, image)
+
+        # create the second image
+        image = Gtk.Image(stock=Gtk.STOCK_STOP,
+                          icon_size=Gtk.IconSize.BUTTON)
+
+        ebox = Gtk.EventBox()
+        ebox.add(image)
+        hbox.add(ebox)
+
+        # make ebox a drag source
+        ebox.drag_source_set(Gdk.ModifierType.BUTTON1_MASK,
+                             None, Gdk.DragAction.COPY)
+        ebox.drag_source_add_image_targets()
+        ebox.connect('drag-begin', self.drag_begin, image)
+        ebox.connect('drag-data-get', self.drag_data_get, image)
+
+        # accept drops on ebox
+        ebox.drag_dest_set(Gtk.DestDefaults.ALL,
+                           None, Gdk.DragAction.COPY)
+        ebox.drag_dest_add_image_targets()
+        ebox.connect('drag-data-received', self.drag_data_received, image)
+
+        # context menu on ebox
+        ebox.connect('button-press-event', self.button_press, image)
+
+        # tell the clipboard manager to make data persistent
+        # FIXME: Allow sending strings a Atoms and convert in PyGI
+        atom = Gdk.atom_intern('CLIPBOARD', True)
+        clipboard = Gtk.Clipboard.get(atom)
+        clipboard.set_can_store(None)
+
+        self.window.show_all()
+
+    def copy_button_clicked(self, button, entry):
+        # get the default clipboard
+        atom = Gdk.atom_intern('CLIPBOARD', True)
+        clipboard = entry.get_clipboard(atom)
+
+        # set the clipboard's text
+        # FIXME: don't require passing length argument
+        clipboard.set_text(entry.get_text(), -1)
+
+    def paste_received(self, clipboard, text, entry):
+        if text is not None:
+            entry.set_text(text)
+
+    def paste_button_clicked(self, button, entry):
+        # get the default clipboard
+        atom = Gdk.atom_intern('CLIPBOARD', True)
+        clipboard = entry.get_clipboard(atom)
+
+        # set the clipboard's text
+        clipboard.request_text(self.paste_received, entry)
+
+    def get_image_pixbuf(self, image):
+        # FIXME: We should hide storage types in an override
+        storage_type = image.get_storage_type()
+        if storage_type == Gtk.ImageType.PIXBUF:
+            return image.get_pixbuf()
+        elif storage_type == Gtk.ImageType.STOCK:
+            (stock_id, size) = image.get_stock()
+            return image.render_icon(stock_id, size, None)
+
+        return None
+
+    def drag_begin(self, widget, context, data):
+        pixbuf = self.get_image_pixbuf(data)
+        Gtk.drag_set_icon_pixbuf(context, pixbuf, -2, -2)
+
+    def drag_data_get(self, widget, context, selection_data, info, time, data):
+        pixbuf = self.get_image_pixbuf(data)
+        selection_data.set_pixbuf(pixbuf)
+
+    def drag_data_received(self, widget, context, x, y, selection_data, info, time, data):
+        if selection_data.get_length() > 0:
+            pixbuf = selection_data.get_pixbuf()
+            data.set_from_pixbuf(pixbuf)
+
+    def copy_image(self, item, data):
+        # get the default clipboard
+        atom = Gdk.atom_intern('CLIPBOARD', True)
+        clipboard = Gtk.Clipboard.get(atom)
+        pixbuf = self.get_image_pixbuf(data)
+
+        clipboard.set_image(pixbuf)
+
+    def paste_image(self, item, data):
+        # get the default clipboard
+        atom = Gdk.atom_intern('CLIPBOARD', True)
+        clipboard = Gtk.Clipboard.get(atom)
+        pixbuf = clipboard.wait_for_image()
+
+        if pixbuf is not None:
+            data.set_from_pixbuf(pixbuf)
+
+    def button_press(self, widget, event, data):
+        if event.button != 3:
+            return False
+
+        self.menu = Gtk.Menu()
+
+        item = Gtk.ImageMenuItem.new_from_stock(Gtk.STOCK_COPY, None)
+        item.connect('activate', self.copy_image, data)
+        item.show()
+        self.menu.append(item)
+
+        item = Gtk.ImageMenuItem.new_from_stock(Gtk.STOCK_PASTE, None)
+        item.connect('activate', self.paste_image, data)
+        item.show()
+        self.menu.append(item)
+
+        self.menu.popup(None, None, None, None, event.button, event.time)
+
+
+def main(demoapp=None):
+    ClipboardApp()
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/colorselector.py b/examples/demo/demos/colorselector.py
new file mode 100644 (file)
index 0000000..d05ca52
--- /dev/null
@@ -0,0 +1,112 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.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.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 St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Color Selector"
+description = """
+ GtkColorSelection lets the user choose a color. GtkColorSelectionDialog is
+ a prebuilt dialog containing a GtkColorSelection.
+ """
+
+
+from gi.repository import Gtk, Gdk
+
+
+class ColorSelectorApp:
+    def __init__(self):
+        # FIXME: we should allow Gdk.Color to be allocated without parameters
+        #        Also color doesn't seem to work
+        self.color = Gdk.RGBA()
+        self.color.red = 0
+        self.color.blue = 1
+        self.color.green = 0
+        self.color.alpha = 1
+
+        self.window = Gtk.Window()
+        self.window.set_title('Color Selection')
+        self.window.set_border_width(8)
+        self.window.connect('destroy', lambda w: Gtk.main_quit())
+
+        vbox = Gtk.VBox(homogeneous=False,
+                        spacing=8)
+        vbox.set_border_width(8)
+        self.window.add(vbox)
+
+        # create color swatch area
+        frame = Gtk.Frame()
+        frame.set_shadow_type(Gtk.ShadowType.IN)
+        vbox.pack_start(frame, True, True, 0)
+
+        self.da = Gtk.DrawingArea()
+        self.da.connect('draw', self.draw_cb)
+
+        # set a minimum size
+        self.da.set_size_request(200, 200)
+        # set the color
+        self.da.override_background_color(0, self.color)
+        frame.add(self.da)
+
+        alignment = Gtk.Alignment(xalign=1.0,
+                                  yalign=0.5,
+                                  xscale=0.0,
+                                  yscale=0.0)
+
+        button = Gtk.Button(label='_Change the above color',
+                            use_underline=True)
+        alignment.add(button)
+        vbox.pack_start(alignment, False, False, 0)
+
+        button.connect('clicked', self.change_color_cb)
+
+        self.window.show_all()
+
+    def draw_cb(self, widget, cairo_ctx):
+        style = widget.get_style_context()
+        bg_color = style.get_background_color(0)
+        Gdk.cairo_set_source_rgba(cairo_ctx, bg_color)
+        cairo_ctx.paint()
+
+        return True
+
+    def change_color_cb(self, button):
+        dialog = Gtk.ColorSelectionDialog(title='Changing color')
+        dialog.set_transient_for(self.window)
+
+        colorsel = dialog.get_color_selection()
+        colorsel.set_previous_rgba(self.color)
+        colorsel.set_current_rgba(self.color)
+        colorsel.set_has_palette(True)
+
+        response = dialog.run()
+
+        if response == Gtk.ResponseType.OK:
+            self.color = colorsel.get_current_rgba()
+            self.da.override_background_color(0, self.color)
+
+        dialog.destroy()
+
+
+def main(demoapp=None):
+    ColorSelectorApp()
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/combobox.py b/examples/demo/demos/combobox.py
new file mode 100644 (file)
index 0000000..36034ba
--- /dev/null
@@ -0,0 +1,319 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.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.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 St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Combo boxes"
+description = """
+The ComboBox widget allows to select one option out of a list.
+The ComboBoxEntry additionally allows the user to enter a value
+that is not in the list of options.
+
+How the options are displayed is controlled by cell renderers.
+ """
+
+
+from gi.repository import Gtk, Gdk, GdkPixbuf, GLib, GObject
+
+
+(PIXBUF_COL,
+ TEXT_COL) = range(2)
+
+
+class MaskEntry(Gtk.Entry):
+    __gtype_name__ = 'MaskEntry'
+
+    def __init__(self, mask=None):
+        self.mask = mask
+        super(MaskEntry, self).__init__()
+
+        self.connect('changed', self.changed_cb)
+
+        self.error_color = Gdk.RGBA()
+        self.error_color.red = 1.0
+        self.error_color.green = 0.9
+        self.error_color_blue = 0.9
+        self.error_color.alpha = 1.0
+
+        # workaround since override_color doesn't accept None yet
+        style_ctx = self.get_style_context()
+        self.normal_color = style_ctx.get_color(0)
+
+    def set_background(self):
+        if self.mask:
+            if not GLib.regex_match_simple(self.mask,
+                                           self.get_text(), 0, 0):
+                self.override_color(0, self.error_color)
+                return
+
+        self.override_color(0, self.normal_color)
+
+    def changed_cb(self, entry):
+        self.set_background()
+
+
+class ComboboxApp:
+    def __init__(self, demoapp):
+        self.demoapp = demoapp
+
+        self.window = Gtk.Window()
+        self.window.set_title('Combo boxes')
+        self.window.set_border_width(10)
+        self.window.connect('destroy', lambda w: Gtk.main_quit())
+
+        vbox = Gtk.VBox(homogeneous=False, spacing=2)
+        self.window.add(vbox)
+
+        frame = Gtk.Frame(label='Some stock icons')
+        vbox.pack_start(frame, False, False, 0)
+
+        box = Gtk.VBox(homogeneous=False, spacing=0)
+        box.set_border_width(5)
+        frame.add(box)
+
+        model = self.create_stock_icon_store()
+        combo = Gtk.ComboBox(model=model)
+        box.add(combo)
+
+        renderer = Gtk.CellRendererPixbuf()
+        combo.pack_start(renderer, False)
+
+        # FIXME: override set_attributes
+        combo.add_attribute(renderer, 'pixbuf', PIXBUF_COL)
+        combo.set_cell_data_func(renderer, self.set_sensitive, None)
+
+        renderer = Gtk.CellRendererText()
+        combo.pack_start(renderer, True)
+        combo.add_attribute(renderer, 'text', TEXT_COL)
+        combo.set_cell_data_func(renderer, self.set_sensitive, None)
+
+        combo.set_row_separator_func(self.is_separator, None)
+        combo.set_active(0)
+
+        # a combobox demonstrating trees
+        frame = Gtk.Frame(label='Where are we ?')
+        vbox.pack_start(frame, False, False, 0)
+
+        box = Gtk.VBox(homogeneous=False, spacing=0)
+        box.set_border_width(5)
+        frame.add(box)
+
+        model = self.create_capital_store()
+        combo = Gtk.ComboBox(model=model)
+        box.add(combo)
+
+        renderer = Gtk.CellRendererText()
+        combo.pack_start(renderer, True)
+        combo.add_attribute(renderer, 'text', 0)
+        combo.set_cell_data_func(renderer, self.is_capital_sensistive, None)
+
+        # FIXME: make new_from_indices work
+        #        make constructor take list or string of indices
+        path = Gtk.TreePath.new_from_string('0:8')
+        treeiter = model.get_iter(path)
+        combo.set_active_iter(treeiter)
+
+        # A GtkComboBoxEntry with validation.
+
+        frame = Gtk.Frame(label='Editable')
+        vbox.pack_start(frame, False, False, 0)
+
+        box = Gtk.VBox(homogeneous=False, spacing=0)
+        box.set_border_width(5)
+        frame.add(box)
+
+        combo = Gtk.ComboBoxText.new_with_entry()
+        self.fill_combo_entry(combo)
+        box.add(combo)
+
+        entry = MaskEntry(mask='^([0-9]*|One|Two|2\302\275|Three)$')
+
+        Gtk.Container.remove(combo, combo.get_child())
+        combo.add(entry)
+
+        # A combobox with string IDs
+
+        frame = Gtk.Frame(label='String IDs')
+        vbox.pack_start(frame, False, False, 0)
+
+        box = Gtk.VBox(homogeneous=False, spacing=0)
+        box.set_border_width(5)
+        frame.add(box)
+
+        # FIXME: model is not setup when constructing Gtk.ComboBoxText()
+        #        so we call new() - Gtk should fix this to setup the model
+        #        in __init__, not in the constructor
+        combo = Gtk.ComboBoxText.new()
+        combo.append('never', 'Not visible')
+        combo.append('when-active', 'Visible when active')
+        combo.append('always', 'Always visible')
+        box.add(combo)
+
+        entry = Gtk.Entry()
+
+        combo.bind_property('active-id',
+                            entry, 'text',
+                            GObject.BindingFlags.BIDIRECTIONAL)
+
+        box.add(entry)
+        self.window.show_all()
+
+    def strip_underscore(self, s):
+        return s.replace('_', '')
+
+    def create_stock_icon_store(self):
+        stock_id = (Gtk.STOCK_DIALOG_WARNING,
+                    Gtk.STOCK_STOP,
+                    Gtk.STOCK_NEW,
+                    Gtk.STOCK_CLEAR,
+                    None,
+                    Gtk.STOCK_OPEN)
+
+        cellview = Gtk.CellView()
+        store = Gtk.ListStore(GdkPixbuf.Pixbuf, str)
+
+        for id in stock_id:
+            if id is not None:
+                pixbuf = cellview.render_icon(id, Gtk.IconSize.BUTTON, None)
+                item = Gtk.stock_lookup(id)
+                label = self.strip_underscore(item.label)
+                store.append((pixbuf, label))
+            else:
+                store.append((None, 'separator'))
+
+        return store
+
+    def set_sensitive(self, cell_layout, cell, tree_model, treeiter, data):
+        """
+        A GtkCellLayoutDataFunc that demonstrates how one can control
+        sensitivity of rows. This particular function does nothing
+        useful and just makes the second row insensitive.
+        """
+
+        path = tree_model.get_path(treeiter)
+        indices = path.get_indices()
+
+        sensitive = not(indices[0] == 1)
+
+        cell.set_property('sensitive', sensitive)
+
+    def is_separator(self, model, treeiter, data):
+        """
+        A GtkTreeViewRowSeparatorFunc that demonstrates how rows can be
+        rendered as separators. This particular function does nothing
+        useful and just turns the fourth row into a separator.
+        """
+
+        path = model.get_path(treeiter)
+
+        indices = path.get_indices()
+        result = (indices[0] == 4)
+
+        return result
+
+    def create_capital_store(self):
+        capitals = (
+            {'group': 'A - B', 'capital': None},
+            {'group': None, 'capital': 'Albany'},
+            {'group': None, 'capital': 'Annapolis'},
+            {'group': None, 'capital': 'Atlanta'},
+            {'group': None, 'capital': 'Augusta'},
+            {'group': None, 'capital': 'Austin'},
+            {'group': None, 'capital': 'Baton Rouge'},
+            {'group': None, 'capital': 'Bismarck'},
+            {'group': None, 'capital': 'Boise'},
+            {'group': None, 'capital': 'Boston'},
+            {'group': 'C - D', 'capital': None},
+            {'group': None, 'capital': 'Carson City'},
+            {'group': None, 'capital': 'Charleston'},
+            {'group': None, 'capital': 'Cheyeene'},
+            {'group': None, 'capital': 'Columbia'},
+            {'group': None, 'capital': 'Columbus'},
+            {'group': None, 'capital': 'Concord'},
+            {'group': None, 'capital': 'Denver'},
+            {'group': None, 'capital': 'Des Moines'},
+            {'group': None, 'capital': 'Dover'},
+            {'group': 'E - J', 'capital': None},
+            {'group': None, 'capital': 'Frankfort'},
+            {'group': None, 'capital': 'Harrisburg'},
+            {'group': None, 'capital': 'Hartford'},
+            {'group': None, 'capital': 'Helena'},
+            {'group': None, 'capital': 'Honolulu'},
+            {'group': None, 'capital': 'Indianapolis'},
+            {'group': None, 'capital': 'Jackson'},
+            {'group': None, 'capital': 'Jefferson City'},
+            {'group': None, 'capital': 'Juneau'},
+            {'group': 'K - O', 'capital': None},
+            {'group': None, 'capital': 'Lansing'},
+            {'group': None, 'capital': 'Lincon'},
+            {'group': None, 'capital': 'Little Rock'},
+            {'group': None, 'capital': 'Madison'},
+            {'group': None, 'capital': 'Montgomery'},
+            {'group': None, 'capital': 'Montpelier'},
+            {'group': None, 'capital': 'Nashville'},
+            {'group': None, 'capital': 'Oklahoma City'},
+            {'group': None, 'capital': 'Olympia'},
+            {'group': 'P - S', 'capital': None},
+            {'group': None, 'capital': 'Phoenix'},
+            {'group': None, 'capital': 'Pierre'},
+            {'group': None, 'capital': 'Providence'},
+            {'group': None, 'capital': 'Raleigh'},
+            {'group': None, 'capital': 'Richmond'},
+            {'group': None, 'capital': 'Sacramento'},
+            {'group': None, 'capital': 'Salem'},
+            {'group': None, 'capital': 'Salt Lake City'},
+            {'group': None, 'capital': 'Santa Fe'},
+            {'group': None, 'capital': 'Springfield'},
+            {'group': None, 'capital': 'St. Paul'},
+            {'group': 'T - Z', 'capital': None},
+            {'group': None, 'capital': 'Tallahassee'},
+            {'group': None, 'capital': 'Topeka'},
+            {'group': None, 'capital': 'Trenton'}
+        )
+
+        parent = None
+
+        store = Gtk.TreeStore(str)
+
+        for item in capitals:
+            if item['group']:
+                parent = store.append(None, (item['group'],))
+            elif item['capital']:
+                store.append(parent, (item['capital'],))
+
+        return store
+
+    def is_capital_sensistive(self, cell_layout, cell, tree_model, treeiter, data):
+        sensitive = not tree_model.iter_has_child(treeiter)
+        cell.set_property('sensitive', sensitive)
+
+    def fill_combo_entry(self, entry):
+        entry.append_text('One')
+        entry.append_text('Two')
+        entry.append_text('2\302\275')
+        entry.append_text('Three')
+
+
+def main(demoapp=None):
+    ComboboxApp(demoapp)
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/data/alphatest.png b/examples/demo/demos/data/alphatest.png
new file mode 100644 (file)
index 0000000..eb5885f
Binary files /dev/null and b/examples/demo/demos/data/alphatest.png differ
diff --git a/examples/demo/demos/data/apple-red.png b/examples/demo/demos/data/apple-red.png
new file mode 100644 (file)
index 0000000..b0a24e9
Binary files /dev/null and b/examples/demo/demos/data/apple-red.png differ
diff --git a/examples/demo/demos/data/background.jpg b/examples/demo/demos/data/background.jpg
new file mode 100644 (file)
index 0000000..86c006a
Binary files /dev/null and b/examples/demo/demos/data/background.jpg differ
diff --git a/examples/demo/demos/data/brick.png b/examples/demo/demos/data/brick.png
new file mode 100644 (file)
index 0000000..d413cd2
Binary files /dev/null and b/examples/demo/demos/data/brick.png differ
diff --git a/examples/demo/demos/data/brick2.png b/examples/demo/demos/data/brick2.png
new file mode 100644 (file)
index 0000000..cfcd079
Binary files /dev/null and b/examples/demo/demos/data/brick2.png differ
diff --git a/examples/demo/demos/data/css_accordion.css b/examples/demo/demos/data/css_accordion.css
new file mode 100644 (file)
index 0000000..a243427
--- /dev/null
@@ -0,0 +1,52 @@
+@import url("resource://css_accordion/reset.css");
+
+* {
+    transition-property: color, background-color, border-color, background-image, padding, border-width;
+    transition-duration: 1s;
+
+    font: Sans 20px;
+}
+
+GtkWindow {
+    background: linear-gradient(153deg, #151515, #151515 5px, transparent 5px) 0 0,
+                linear-gradient(333deg, #151515, #151515 5px, transparent 5px) 10px 5px,
+                linear-gradient(153deg, #222, #222 5px, transparent 5px) 0 5px,
+                linear-gradient(333deg, #222, #222 5px, transparent 5px) 10px 10px,
+                linear-gradient(90deg, #1b1b1b, #1b1b1b 10px, transparent 10px),
+                linear-gradient(#1d1d1d, #1d1d1d 25%, #1a1a1a 25%, #1a1a1a 50%, transparent 50%, transparent 75%, #242424 75%, #242424);
+    background-color: #131313;
+    background-size: 20px 20px;
+}
+
+.button {
+    color: black;
+    background-color: #bbb;
+    border-style: solid;
+    border-width: 2px 0 2px 2px;
+    border-color: #333;
+
+    padding: 12px 4px;
+}
+
+.button:first-child {
+    border-radius: 5px 0 0 5px;
+}
+
+.button:last-child {
+    border-radius: 0 5px 5px 0;
+    border-width: 2px;
+}
+
+.button:hover {
+    padding: 12px 48px;
+    background-color: #4870bc;
+}
+
+.button *:hover {
+    color: white;
+}
+
+.button:hover:active,
+.button:active {
+    background-color: #993401;
+}
diff --git a/examples/demo/demos/data/css_basics.css b/examples/demo/demos/data/css_basics.css
new file mode 100644 (file)
index 0000000..62dba7a
--- /dev/null
@@ -0,0 +1,22 @@
+/* You can edit the text in this window to change the
+ * appearance of this Window.
+ * Be careful, if you screw it up, nothing might be visible
+ * anymore. :)
+ */
+
+/* This CSS resets all properties to their defaults values
+ *    and overrides all user settings and the theme in use */
+@import url("resource://css_basics/reset.css");
+
+/* Set a very futuristic style by default */
+* {
+  color: green;
+  font-family: Monospace;
+  border: 1px solid;
+}
+
+/* Make sure selections are visible */
+:selected {
+  background-color: darkGreen;
+  color: black;
+}
diff --git a/examples/demo/demos/data/css_multiplebgs.css b/examples/demo/demos/data/css_multiplebgs.css
new file mode 100644 (file)
index 0000000..eb9d4d6
--- /dev/null
@@ -0,0 +1,136 @@
+/* You can edit the text in this window to change the
+ * appearance of this Window.
+ * Be careful, if you screw it up, nothing might be visible
+ * anymore. :)
+ */
+
+/* This CSS resets all properties to their defaults values
+ *    and overrides all user settings and the theme in use */
+@import url("resource://css_multiplebgs/reset.css");
+@import url("resource://css_multiplebgs/cssview.css");
+
+#canvas {
+    transition-property: background-color, background-image;
+    transition-duration: 0.5s;
+
+    background-color: #4870bc;
+}
+
+/* The gradients below are adapted versions of Lea Verou's CSS3 patterns,
+ * licensed under the MIT license:
+ * Copyright (c) 2011 Lea Verou, http://lea.verou.me/
+ *
+ * See https://github.com/LeaVerou/CSS3-Patterns-Gallery
+ */
+
+/**********
+ * Bricks *
+ **********/
+/*
+@define-color brick_hi #d42;
+@define-color brick_lo #b42;
+@define-color brick_hi_backdrop #888;
+@define-color brick_lo_backdrop #999;
+
+#canvas {
+    background-color: #999;
+    background-image: linear-gradient(205deg, @brick_lo, @brick_lo 23px, transparent 23px),
+                      linear-gradient(25deg, @brick_hi, @brick_hi 23px, transparent 23px),
+                      linear-gradient(205deg, @brick_lo, @brick_lo 23px, transparent 23px),
+                      linear-gradient(25deg, @brick_hi, @brick_hi 23px, transparent 23px);
+    background-size: 58px 58px;
+    background-position: 0px 6px, 4px 31px, 29px 35px, 34px 2px;
+}
+
+#canvas:backdrop {
+    background-color: #444;
+    background-image: linear-gradient(205deg, @brick_lo_backdrop, @brick_lo_backdrop 23px, transparent 23px),
+                      linear-gradient(25deg, @brick_hi_backdrop, @brick_hi_backdrop 23px, transparent 23px),
+                     linear-gradient(205deg, @brick_lo_backdrop, @brick_lo_backdrop 23px, transparent 23px),
+                     linear-gradient(25deg, @brick_hi_backdrop, @brick_hi_backdrop 23px, transparent 23px);
+    background-size: 58px 58px;
+    background-position: 0px 6px, 4px 31px, 29px 35px, 34px 2px;
+}
+*/
+
+/*
+#bricks-button {
+    background-color: #eef;
+    background-image: -gtk-scaled(url('resource://css_multiplebgs/brick.png'),url('resource://css_multiplebgs/brick2.png'));
+    background-repeat: no-repeat;
+    background-position: center;
+}
+
+*/
+/**********
+ * Tartan *
+ **********/
+/*
+@define-color tartan_bg #662e2c;
+@define-color tartan_bg_backdrop #333;
+
+#canvas {
+    background-color: @tartan_bg;
+    background-image: repeating-linear-gradient(transparent, transparent 50px, rgba(0,0,0,.4) 50px,
+                                                rgba(0,0,0,.4) 53px, transparent 53px, transparent 63px,
+                                                rgba(0,0,0,.4) 63px, rgba(0,0,0,.4) 66px, transparent 66px,
+                                                transparent 116px, rgba(0,0,0,.5) 116px, rgba(0,0,0,.5) 166px,
+                                                rgba(255,255,255,.2) 166px, rgba(255,255,255,.2) 169px, rgba(0,0,0,.5) 169px,
+                                                rgba(0,0,0,.5) 179px, rgba(255,255,255,.2) 179px, rgba(255,255,255,.2) 182px,
+                                                rgba(0,0,0,.5) 182px, rgba(0,0,0,.5) 232px, transparent 232px),
+                      repeating-linear-gradient(90deg, transparent, transparent 50px, rgba(0,0,0,.4) 50px, rgba(0,0,0,.4) 53px,
+                                                transparent 53px, transparent 63px, rgba(0,0,0,.4) 63px, rgba(0,0,0,.4) 66px,
+                                                transparent 66px, transparent 116px, rgba(0,0,0,.5) 116px, rgba(0,0,0,.5) 166px,
+                                                rgba(255,255,255,.2) 166px, rgba(255,255,255,.2) 169px, rgba(0,0,0,.5) 169px,
+                                                rgba(0,0,0,.5) 179px, rgba(255,255,255,.2) 179px, rgba(255,255,255,.2) 182px,
+                                                rgba(0,0,0,.5) 182px, rgba(0,0,0,.5) 232px, transparent 232px),
+                      repeating-linear-gradient(-55deg, transparent, transparent 1px, rgba(0,0,0,.2) 1px, rgba(0,0,0,.2) 4px,
+                                                transparent 4px, transparent 19px, rgba(0,0,0,.2) 19px,
+                                                rgba(0,0,0,.2) 24px, transparent 24px, transparent 51px, rgba(0,0,0,.2) 51px,
+                                                rgba(0,0,0,.2) 54px, transparent 54px, transparent 74px);
+}
+
+#canvas:backdrop {
+    background-color: @tartan_bg_backdrop;
+}
+*/
+
+/***********
+ * Stripes *
+ ***********/
+
+/*
+@define-color base_bg #4870bc;
+@define-color backdrop_bg #555;
+
+#canvas {
+  background-color: @base_bg;
+  background-image: linear-gradient(to left, transparent, rgba(255,255,255,.07) 50%, transparent 50%),
+                    linear-gradient(to left, transparent, rgba(255,255,255,.13) 50%, transparent 50%),
+                    linear-gradient(to left, transparent, transparent 50%, rgba(255,255,255,.17) 50%),
+                    linear-gradient(to left, transparent, transparent 50%, rgba(255,255,255,.19) 50%);
+  background-size: 29px, 59px, 73px, 109px;
+}
+
+#canvas:backdrop {
+  background-color: @backdrop_bg;
+}
+*/
+
+/***************
+ * Lined Paper *
+ ***************/
+/*
+#canvas {
+    background-color: #fff;
+    background-image: linear-gradient(90deg, transparent 79px, alpha(#f98195, 0.40) 79px, #f98195 80px, alpha(#f98195, 0.40) 81px, transparent 81px),
+                      linear-gradient(alpha(#77c5cf, 0.60), alpha(#77c5cf, 0.60) 1px, transparent 1px);
+    background-size: 100% 36px;
+}
+
+#canvas:backdrop {
+    background-color: #f1f2f4;
+    background-image: linear-gradient(90deg, transparent 79px, alpha(#999, 0.40) 79px, #999 80px, alpha(#999, 0.40) 81px, transparent 81px),
+                      linear-gradient(alpha(#bbb, 0.60), alpha(#bbb, 0.60) 1px, transparent 1px);
+}
+*/
diff --git a/examples/demo/demos/data/cssview.css b/examples/demo/demos/data/cssview.css
new file mode 100644 (file)
index 0000000..5060c39
--- /dev/null
@@ -0,0 +1,41 @@
+/* Make the text editor has a nice style */
+.view {
+  color: #2e3436;
+  font: Monospace;
+  background-color: alpha(white, 0.30);
+}
+
+.view:selected {
+  color: white;
+  background-color: #4a90d9;
+}
+
+.scrollbar.trough,
+.scrollbars-junction {
+  background-color: alpha(white, 0.80);
+}
+
+.scrollbar.slider {
+  border-width: 3px;
+  border-style: solid;
+  border-radius: 10px;
+  border-color: transparent;
+  background-clip: padding-box;
+  background-color: #999;
+}
+
+.scrollbar.slider:prelight {
+  background-color: #555;
+}
+
+.pane-separator {
+  background-color: alpha(white, 0.80);
+  background-image: linear-gradient(transparent, transparent 1px, #999 1px, #999 4px, transparent 4px);
+  background-size: 40px auto;
+  background-repeat: no-repeat;
+  background-position: center;
+}
+
+.pane-separator:prelight {
+  background-image: linear-gradient(transparent, transparent 1px, #555 1px, #555 4px, transparent 4px);
+}
diff --git a/examples/demo/demos/data/demo.gresource b/examples/demo/demos/data/demo.gresource
new file mode 100644 (file)
index 0000000..e19d822
Binary files /dev/null and b/examples/demo/demos/data/demo.gresource differ
diff --git a/examples/demo/demos/data/demo.gresource.xml b/examples/demo/demos/data/demo.gresource.xml
new file mode 100644 (file)
index 0000000..866769f
--- /dev/null
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+  <gresource prefix="/css_accordion">
+    <file>css_accordion.css</file>
+    <file>reset.css</file>
+  </gresource>
+  <gresource prefix="/css_basics">
+    <file>css_basics.css</file>
+    <file>reset.css</file>
+  </gresource>
+  <gresource prefix="/css_multiplebgs">
+    <file>css_multiplebgs.css</file>
+    <file>brick.png</file>
+    <file>brick2.png</file>
+    <file>cssview.css</file>
+    <file>reset.css</file>
+  </gresource>
+</gresources>
diff --git a/examples/demo/demos/data/demo.ui b/examples/demo/demos/data/demo.ui
new file mode 100644 (file)
index 0000000..57dd232
--- /dev/null
@@ -0,0 +1,258 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<interface domain="gtk20">
+    <object class="GtkListStore" id="liststore1">
+      <columns>
+        <column type="gchararray"/>
+        <column type="gchararray"/>
+        <column type="gint"/>
+        <column type="gchararray"/>
+      </columns>
+      <data>
+        <row>
+          <col id="0" translatable="yes">John</col>
+          <col id="1" translatable="yes">Doe</col>
+          <col id="2">25</col>
+          <col id="3" translatable="yes">This is the John Doe row</col>
+        </row>
+        <row>
+          <col id="0" translatable="yes">Mary</col>
+          <col id="1" translatable="yes">Unknown</col>
+          <col id="2">50</col>
+          <col id="3" translatable="yes">This is the Mary Unknown row</col>
+        </row>
+      </data>
+    </object>
+    <object class="GtkUIManager" id="uimanager">
+        <child>
+            <object class="GtkActionGroup" id="DefaultActions">
+                <child>
+                    <object class="GtkAction" id="Copy">
+                        <property name="name">Copy</property>
+                        <property name="tooltip" translatable="yes">Copy selected object into the clipboard</property>
+                        <property name="stock_id">gtk-copy</property>
+                    </object>
+                </child>
+                <child>
+                    <object class="GtkAction" id="Cut">
+                        <property name="name">Cut</property>
+                        <property name="tooltip" translatable="yes">Cut selected object into the clipboard</property>
+                        <property name="stock_id">gtk-cut</property>
+                    </object>
+                </child>
+                <child>
+                    <object class="GtkAction" id="EditMenu">
+                        <property name="name">EditMenu</property>
+                        <property name="label" translatable="yes">_Edit</property>
+                    </object>
+                </child>
+                <child>
+                    <object class="GtkAction" id="FileMenu">
+                        <property name="name">FileMenu</property>
+                        <property name="label" translatable="yes">_File</property>
+                    </object>
+                </child>
+                <child>
+                    <object class="GtkAction" id="New">
+                        <property name="name">New</property>
+                        <property name="tooltip" translatable="yes">Create a new file</property>
+                        <property name="stock_id">gtk-new</property>
+                    </object>
+                </child>
+                <child>
+                    <object class="GtkAction" id="Open">
+                        <property name="name">Open</property>
+                        <property name="tooltip" translatable="yes">Open a file</property>
+                        <property name="stock_id">gtk-open</property>
+                    </object>
+                </child>
+                <child>
+                    <object class="GtkAction" id="Paste">
+                        <property name="name">Paste</property>
+                        <property name="tooltip" translatable="yes">Paste object from the Clipboard</property>
+                        <property name="stock_id">gtk-paste</property>
+                    </object>
+                </child>
+                <child>
+                    <object class="GtkAction" id="Quit">
+                        <property name="name">Quit</property>
+                        <property name="tooltip" translatable="yes">Quit the program</property>
+                        <property name="stock_id">gtk-quit</property>
+                        <signal handler="quit_activate" name="activate"/>
+                    </object>
+                </child>
+                <child>
+                    <object class="GtkAction" id="Save">
+                        <property name="name">Save</property>
+                        <property name="is_important">True</property>
+                        <property name="tooltip" translatable="yes">Save a file</property>
+                        <property name="stock_id">gtk-save</property>
+                    </object>
+                </child>
+                <child>
+                    <object class="GtkAction" id="SaveAs">
+                        <property name="name">SaveAs</property>
+                        <property name="tooltip" translatable="yes">Save with a different name</property>
+                        <property name="stock_id">gtk-save-as</property>
+                    </object>
+                </child>
+                <child>
+                    <object class="GtkAction" id="HelpMenu">
+                        <property name="name">HelpMenu</property>
+                        <property name="label" translatable="yes">_Help</property>
+                    </object>
+                </child>
+                <child>
+                    <object class="GtkAction" id="About">
+                        <property name="name">About</property>
+                        <property name="stock_id">gtk-about</property>
+                        <signal handler="about_activate" name="activate"/>
+                    </object>
+                    <accelerator key="F1"/>
+                </child>
+            </object>
+        </child>
+        <ui>
+          <menubar name="menubar1">
+            <menu action="FileMenu" name="FileMenu">
+              <menuitem action="New" name="New"/>
+              <menuitem action="Open" name="Open"/>
+              <menuitem action="Save" name="Save"/>
+              <menuitem action="SaveAs" name="SaveAs"/>
+              <separator/>
+              <menuitem action="Quit" name="Quit"/>
+            </menu>
+            <menu action="EditMenu">
+              <menuitem action="Copy" name="Copy"/>
+              <menuitem action="Cut" name="Cut"/>
+              <menuitem action="Paste" name="Paste"/>
+            </menu>
+            <menu action="HelpMenu" name="HelpMenu">
+              <menuitem action="About" name="About"/>
+            </menu>
+          </menubar>
+          <toolbar name="toolbar1">
+            <toolitem action="New" name="New"/>
+            <toolitem action="Open" name="Open"/>
+            <toolitem action="Save" name="Save"/>
+            <separator/>
+            <toolitem action="Copy" name="Copy"/>
+            <toolitem action="Cut" name="Cut"/>
+            <toolitem action="Paste" name="Paste"/>
+          </toolbar>
+        </ui>
+    </object>
+    <object class="GtkAboutDialog" id="aboutdialog1">
+        <property name="program-name" translatable="yes">GtkBuilder demo</property>
+         <accessibility>
+            <relation target="window1" type="subwindow-of"/>
+        </accessibility>
+    </object>
+    <object class="GtkWindow" id="window1">
+        <property name="default_height">250</property>
+        <property name="default_width">440</property>
+        <property name="title">GtkBuilder demo</property>
+        <child>
+            <object class="GtkVBox" id="vbox1">
+                <property name="visible">True</property>
+                <child>
+                    <object constructor="uimanager" class="GtkMenuBar" id="menubar1">
+                        <property name="visible">True</property>
+                       <child internal-child="accessible">
+                           <object class="AtkObject" id="a11y-menubar">
+                               <property name="AtkObject::accessible-name">The menubar</property>
+                           </object>
+                       </child>
+                   </object>
+                    <packing>
+                        <property name="expand">False</property>
+                    </packing>
+                </child>
+                <child>
+                    <object constructor="uimanager" class="GtkToolbar" id="toolbar1">
+                        <property name="visible">True</property>
+                       <child internal-child="accessible">
+                           <object class="AtkObject" id="a11y-toolbar">
+                               <property name="AtkObject::accessible-name">The toolbar</property>
+                           </object>
+                       </child>
+                    </object>
+                    <packing>
+                        <property name="expand">False</property>
+                        <property name="position">1</property>
+                    </packing>
+                </child>
+                <child>
+                    <object class="GtkScrolledWindow" id="scrolledwindow1">
+                      <property name="hscrollbar_policy">automatic</property>
+                      <property name="shadow_type">in</property>
+                      <property name="visible">True</property>
+                      <property name="vscrollbar_policy">automatic</property>
+                      <child>
+                        <object class="GtkTreeView" id="treeview1">
+                          <property name="visible">True</property>
+                          <property name="model">liststore1</property>
+                          <property name="tooltip-column">3</property>
+                         <child internal-child="accessible">
+                             <object class="AtkObject" id="a11y-treeview">
+                                 <property name="AtkObject::accessible-name">Name list</property>
+                                  <property name="AtkObject::accessible-description">
+                                    A list of person with name, surname and age columns
+                                  </property>
+                             </object>
+                         </child>
+                          <child>
+                            <object class="GtkTreeViewColumn" id="column1">
+                              <property name="title">Name</property>
+                              <child>
+                                <object class="GtkCellRendererText" id="renderer1"/>
+                                <attributes>
+                                  <attribute name="text">0</attribute>
+                                </attributes>
+                              </child>
+                            </object>
+                          </child>
+                          <child>
+                            <object class="GtkTreeViewColumn" id="column2">
+                              <property name="title">Surname</property>
+                              <child>
+                                <object class="GtkCellRendererText" id="renderer2"/>
+                                <attributes>
+                                  <attribute name="text">1</attribute>
+                                </attributes>
+                              </child>
+                            </object>
+                          </child>
+                          <child>
+                            <object class="GtkTreeViewColumn" id="column3">
+                              <property name="title">Age</property>
+                              <child>
+                                <object class="GtkCellRendererText" id="renderer3"/>
+                                <attributes>
+                                  <attribute name="text">2</attribute>
+                                </attributes>
+                              </child>
+                            </object>
+                          </child>
+                        </object>
+                      </child>
+                   <accessibility>
+                       <action action_name="move-cursor" description="Move the cursor to select another person."/>
+                   </accessibility>
+                    </object>
+                    <packing>
+                        <property name="position">2</property>
+                    </packing>
+                </child>
+                <child>
+                    <object class="GtkStatusbar" id="statusbar1">
+                        <property name="visible">True</property>
+                    </object>
+                    <packing>
+                        <property name="expand">False</property>
+                        <property name="position">3</property>
+                    </packing>
+                </child>
+            </object>
+        </child>
+    </object>
+</interface>
diff --git a/examples/demo/demos/data/floppybuddy.gif b/examples/demo/demos/data/floppybuddy.gif
new file mode 100644 (file)
index 0000000..ac986c8
Binary files /dev/null and b/examples/demo/demos/data/floppybuddy.gif differ
diff --git a/examples/demo/demos/data/gnome-applets.png b/examples/demo/demos/data/gnome-applets.png
new file mode 100644 (file)
index 0000000..8d3549e
Binary files /dev/null and b/examples/demo/demos/data/gnome-applets.png differ
diff --git a/examples/demo/demos/data/gnome-calendar.png b/examples/demo/demos/data/gnome-calendar.png
new file mode 100644 (file)
index 0000000..889f329
Binary files /dev/null and b/examples/demo/demos/data/gnome-calendar.png differ
diff --git a/examples/demo/demos/data/gnome-foot.png b/examples/demo/demos/data/gnome-foot.png
new file mode 100644 (file)
index 0000000..0476658
Binary files /dev/null and b/examples/demo/demos/data/gnome-foot.png differ
diff --git a/examples/demo/demos/data/gnome-fs-directory.png b/examples/demo/demos/data/gnome-fs-directory.png
new file mode 100644 (file)
index 0000000..05921a6
Binary files /dev/null and b/examples/demo/demos/data/gnome-fs-directory.png differ
diff --git a/examples/demo/demos/data/gnome-fs-regular.png b/examples/demo/demos/data/gnome-fs-regular.png
new file mode 100644 (file)
index 0000000..0f5019c
Binary files /dev/null and b/examples/demo/demos/data/gnome-fs-regular.png differ
diff --git a/examples/demo/demos/data/gnome-gimp.png b/examples/demo/demos/data/gnome-gimp.png
new file mode 100644 (file)
index 0000000..f6bbc6d
Binary files /dev/null and b/examples/demo/demos/data/gnome-gimp.png differ
diff --git a/examples/demo/demos/data/gnome-gmush.png b/examples/demo/demos/data/gnome-gmush.png
new file mode 100644 (file)
index 0000000..0a4b0d0
Binary files /dev/null and b/examples/demo/demos/data/gnome-gmush.png differ
diff --git a/examples/demo/demos/data/gnome-gsame.png b/examples/demo/demos/data/gnome-gsame.png
new file mode 100644 (file)
index 0000000..01c0611
Binary files /dev/null and b/examples/demo/demos/data/gnome-gsame.png differ
diff --git a/examples/demo/demos/data/gnu-keys.png b/examples/demo/demos/data/gnu-keys.png
new file mode 100644 (file)
index 0000000..58a3377
Binary files /dev/null and b/examples/demo/demos/data/gnu-keys.png differ
diff --git a/examples/demo/demos/data/gtk-logo-rgb.gif b/examples/demo/demos/data/gtk-logo-rgb.gif
new file mode 100644 (file)
index 0000000..63c622b
Binary files /dev/null and b/examples/demo/demos/data/gtk-logo-rgb.gif differ
diff --git a/examples/demo/demos/data/reset.css b/examples/demo/demos/data/reset.css
new file mode 100644 (file)
index 0000000..1c27a8e
--- /dev/null
@@ -0,0 +1,68 @@
+/* @import this colorsheet to get the default values for every property.
+ * This is useful when writing special CSS tests that should not be
+ * inluenced by themes - not even the default ones.
+ * Keep in mind that the output will be very ugly and not look like
+ * anything GTK.
+ * Also, when adding new style properties, please add them here.
+ */
+
+* {
+  color: inherit;
+  font-size: inherit;
+  background-color: initial;
+  font-family: inherit;
+  font-style: inherit;
+  font-variant: inherit;
+  font-weight: inherit;
+  text-shadow: inherit;
+  icon-shadow: inherit;
+  box-shadow: initial;
+  margin-top: initial;
+  margin-left: initial;
+  margin-bottom: initial;
+  margin-right: initial;
+  padding-top: initial;
+  padding-left: initial;
+  padding-bottom: initial;
+  padding-right: initial;
+  border-top-style: initial;
+  border-top-width: initial;
+  border-left-style: initial;
+  border-left-width: initial;
+  border-bottom-style: initial;
+  border-bottom-width: initial;
+  border-right-style: initial;
+  border-right-width: initial;
+  border-top-left-radius: initial;
+  border-top-right-radius: initial;
+  border-bottom-right-radius: initial;
+  border-bottom-left-radius: initial;
+  outline-style: initial;
+  outline-width: initial;
+  outline-offset: initial;
+  background-clip: initial;
+  background-origin: initial;
+  background-size: initial;
+  background-position: initial;
+  border-top-color: initial;
+  border-right-color: initial;
+  border-bottom-color: initial;
+  border-left-color: initial;
+  outline-color:  initial;
+  background-repeat: initial;
+  background-image: initial;
+  border-image-source: initial;
+  border-image-repeat: initial;
+  border-image-slice: initial;
+  border-image-width: initial;
+  transition-property: initial;
+  transition-duration: initial;
+  transition-timing-function: initial;
+  transition-delay: initial;
+  engine: initial;
+  gtk-key-bindings: initial;
+
+  -GtkWidget-focus-line-width: 0;
+  -GtkWidget-focus-padding: 0;
+  -GtkNotebook-initial-gap: 0;
+}
diff --git a/examples/demo/demos/dialogs.py b/examples/demo/demos/dialogs.py
new file mode 100644 (file)
index 0000000..47d6822
--- /dev/null
@@ -0,0 +1,156 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.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.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 St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Dialog and Message Boxes"
+description = """
+Dialog widgets are used to pop up a transient window for user feedback.
+"""
+
+from gi.repository import Gtk
+
+
+class DialogsApp:
+    def __init__(self):
+        self.dialog_counter = 1
+
+        self.window = Gtk.Window(title="Dialogs")
+        self.window.set_border_width(8)
+        self.window.connect('destroy', Gtk.main_quit)
+
+        frame = Gtk.Frame(label="Dialogs")
+        self.window.add(frame)
+
+        vbox = Gtk.VBox(spacing=8)
+        vbox.set_border_width(8)
+        frame.add(vbox)
+
+        # Standard message dialog
+        hbox = Gtk.HBox(spacing=8)
+        vbox.pack_start(hbox, False, False, 0)
+        button = Gtk.Button.new_with_mnemonic("_Message Dialog")
+
+        button.connect('clicked',
+                       self._message_dialog_clicked)
+        hbox.pack_start(button, False, False, 0)
+
+        vbox.pack_start(Gtk.HSeparator(),
+                        False, False, 0)
+
+        # Interactive dialog
+        hbox = Gtk.HBox(spacing=8)
+        vbox.pack_start(hbox, False, False, 0)
+        vbox2 = Gtk.VBox(spacing=0)
+        button = Gtk.Button.new_with_mnemonic("_Interactive Dialog")
+
+        button.connect('clicked',
+                       self._interactive_dialog_clicked)
+        hbox.pack_start(vbox2, False, False, 0)
+        vbox2.pack_start(button, False, False, 0)
+
+        table = Gtk.Table(n_rows=2, n_columns=2, homogeneous=False)
+        table.set_row_spacings(4)
+        table.set_col_spacings(4)
+        hbox.pack_start(table, False, False, 0)
+
+        label = Gtk.Label.new_with_mnemonic("_Entry 1")
+        table.attach_defaults(label, 0, 1, 0, 1)
+
+        self.entry1 = Gtk.Entry()
+        table.attach_defaults(self.entry1, 1, 2, 0, 1)
+        label.set_mnemonic_widget(self.entry1)
+
+        label = Gtk.Label.new_with_mnemonic("E_ntry 2")
+
+        table.attach_defaults(label, 0, 1, 1, 2)
+
+        self.entry2 = Gtk.Entry()
+        table.attach_defaults(self.entry2, 1, 2, 1, 2)
+        label.set_mnemonic_widget(self.entry2)
+
+        self.window.show_all()
+
+    def _interactive_dialog_clicked(self, button):
+        dialog = Gtk.Dialog(title='Interactive Dialog',
+                            transient_for=self.window,
+                            modal=True,
+                            destroy_with_parent=True)
+        dialog.add_buttons(Gtk.STOCK_OK, Gtk.ResponseType.OK,
+                           "_Non-stock Button", Gtk.ResponseType.CANCEL)
+
+        content_area = dialog.get_content_area()
+        hbox = Gtk.HBox(spacing=8)
+        hbox.set_border_width(8)
+        content_area.pack_start(hbox, False, False, 0)
+
+        stock = Gtk.Image(stock=Gtk.STOCK_DIALOG_QUESTION,
+                          icon_size=Gtk.IconSize.DIALOG)
+
+        hbox.pack_start(stock, False, False, 0)
+
+        table = Gtk.Table(n_rows=2, n_columns=2, homogeneous=False)
+        table.set_row_spacings(4)
+        table.set_col_spacings(4)
+        hbox.pack_start(table, True, True, 0)
+        label = Gtk.Label.new_with_mnemonic("_Entry 1")
+        table.attach_defaults(label, 0, 1, 0, 1)
+        local_entry1 = Gtk.Entry()
+        local_entry1.set_text(self.entry1.get_text())
+        table.attach_defaults(local_entry1, 1, 2, 0, 1)
+        label.set_mnemonic_widget(local_entry1)
+
+        label = Gtk.Label.new_with_mnemonic("E_ntry 2")
+        table.attach_defaults(label, 0, 1, 1, 2)
+
+        local_entry2 = Gtk.Entry()
+        local_entry2.set_text(self.entry2.get_text())
+        table.attach_defaults(local_entry2, 1, 2, 1, 2)
+        label.set_mnemonic_widget(local_entry2)
+
+        hbox.show_all()
+
+        response = dialog.run()
+        if response == Gtk.ResponseType.OK:
+            self.entry1.set_text(local_entry1.get_text())
+            self.entry2.set_text(local_entry2.get_text())
+
+        dialog.destroy()
+
+    def _message_dialog_clicked(self, button):
+        dialog = Gtk.MessageDialog(transient_for=self.window,
+                                   modal=True,
+                                   destroy_with_parent=True,
+                                   message_type=Gtk.MessageType.INFO,
+                                   buttons=Gtk.ButtonsType.OK,
+                                   text="This message box has been popped up the following\nnumber of times:")
+        dialog.format_secondary_text('%d' % self.dialog_counter)
+        dialog.run()
+
+        self.dialog_counter += 1
+        dialog.destroy()
+
+
+def main(demoapp=None):
+    DialogsApp()
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/drawingarea.py b/examples/demo/demos/drawingarea.py
new file mode 100644 (file)
index 0000000..b04c41d
--- /dev/null
@@ -0,0 +1,207 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.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.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 St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Drawing Area"
+description = """
+GtkDrawingArea is a blank area where you can draw custom displays
+of various kinds.
+
+This demo has two drawing areas. The checkerboard area shows
+how you can just draw something; all you have to do is write
+a signal handler for expose_event, as shown here.
+
+The "scribble" area is a bit more advanced, and shows how to handle
+events such as button presses and mouse motion. Click the mouse
+and drag in the scribble area to draw squiggles. Resize the window
+to clear the area.
+"""
+
+
+import cairo
+
+from gi.repository import Gtk, Gdk
+
+
+class DrawingAreaApp:
+    def __init__(self):
+        self.sureface = None
+
+        window = Gtk.Window()
+        window.set_title(title)
+        window.connect('destroy', lambda x: Gtk.main_quit())
+        window.set_border_width(8)
+
+        vbox = Gtk.VBox(homogeneous=False, spacing=8)
+        window.add(vbox)
+
+        # create checkerboard area
+        label = Gtk.Label()
+        label.set_markup('<u>Checkerboard pattern</u>')
+        vbox.pack_start(label, False, False, 0)
+
+        frame = Gtk.Frame()
+        frame.set_shadow_type(Gtk.ShadowType.IN)
+        vbox.pack_start(frame, True, True, 0)
+
+        da = Gtk.DrawingArea()
+        da.set_size_request(100, 100)
+        frame.add(da)
+        da.connect('draw', self.checkerboard_draw_event)
+
+        # create scribble area
+        label = Gtk.Label()
+        label.set_markup('<u>Scribble area</u>')
+        vbox.pack_start(label, False, False, 0)
+
+        frame = Gtk.Frame()
+        frame.set_shadow_type(Gtk.ShadowType.IN)
+        vbox.pack_start(frame, True, True, 0)
+
+        da = Gtk.DrawingArea()
+        da.set_size_request(100, 100)
+        frame.add(da)
+        da.connect('draw', self.scribble_draw_event)
+        da.connect('configure-event', self.scribble_configure_event)
+
+        # event signals
+        da.connect('motion-notify-event', self.scribble_motion_notify_event)
+        da.connect('button-press-event', self.scribble_button_press_event)
+
+        # Ask to receive events the drawing area doesn't normally
+        # subscribe to
+        da.set_events(da.get_events() |
+                      Gdk.EventMask.LEAVE_NOTIFY_MASK |
+                      Gdk.EventMask.BUTTON_PRESS_MASK |
+                      Gdk.EventMask.POINTER_MOTION_MASK |
+                      Gdk.EventMask.POINTER_MOTION_HINT_MASK)
+
+        window.show_all()
+
+    def checkerboard_draw_event(self, da, cairo_ctx):
+
+        # At the start of a draw handler, a clip region has been set on
+        # the Cairo context, and the contents have been cleared to the
+        # widget's background color. The docs for
+        # gdk_window_begin_paint_region() give more details on how this
+        # works.
+        check_size = 10
+        spacing = 2
+
+        xcount = 0
+        i = spacing
+        width = da.get_allocated_width()
+        height = da.get_allocated_height()
+
+        while i < width:
+            j = spacing
+            ycount = xcount % 2  # start with even/odd depending on row
+            while j < height:
+                if ycount % 2:
+                    cairo_ctx.set_source_rgb(0.45777, 0, 0.45777)
+                else:
+                    cairo_ctx.set_source_rgb(1, 1, 1)
+                # If we're outside the clip this will do nothing.
+                cairo_ctx.rectangle(i, j,
+                                    check_size,
+                                    check_size)
+                cairo_ctx.fill()
+
+                j += check_size + spacing
+                ycount += 1
+
+            i += check_size + spacing
+            xcount += 1
+
+        return True
+
+    def scribble_draw_event(self, da, cairo_ctx):
+
+        cairo_ctx.set_source_surface(self.surface, 0, 0)
+        cairo_ctx.paint()
+
+        return False
+
+    def draw_brush(self, widget, x, y):
+        update_rect = Gdk.Rectangle()
+        update_rect.x = x - 3
+        update_rect.y = y - 3
+        update_rect.width = 6
+        update_rect.height = 6
+
+        # paint to the surface where we store our state
+        cairo_ctx = cairo.Context(self.surface)
+
+        Gdk.cairo_rectangle(cairo_ctx, update_rect)
+        cairo_ctx.fill()
+
+        widget.get_window().invalidate_rect(update_rect, False)
+
+    def scribble_configure_event(self, da, event):
+
+        allocation = da.get_allocation()
+        self.surface = da.get_window().create_similar_surface(cairo.CONTENT_COLOR,
+                                                              allocation.width,
+                                                              allocation.height)
+
+        cairo_ctx = cairo.Context(self.surface)
+        cairo_ctx.set_source_rgb(1, 1, 1)
+        cairo_ctx.paint()
+
+        return True
+
+    def scribble_motion_notify_event(self, da, event):
+        if self.surface is None:  # paranoia check, in case we haven't gotten a configure event
+            return False
+
+        # This call is very important; it requests the next motion event.
+        # If you don't call gdk_window_get_pointer() you'll only get
+        # a single motion event. The reason is that we specified
+        # GDK_POINTER_MOTION_HINT_MASK to gtk_widget_set_events().
+        # If we hadn't specified that, we could just use event->x, event->y
+        # as the pointer location. But we'd also get deluged in events.
+        # By requesting the next event as we handle the current one,
+        # we avoid getting a huge number of events faster than we
+        # can cope.
+
+        (window, x, y, state) = event.window.get_pointer()
+
+        if state & Gdk.ModifierType.BUTTON1_MASK:
+            self.draw_brush(da, x, y)
+
+        return True
+
+    def scribble_button_press_event(self, da, event):
+        if self.surface is None:  # paranoia check, in case we haven't gotten a configure event
+            return False
+
+        if event.button == 1:
+            self.draw_brush(da, event.x, event.y)
+
+        return True
+
+
+def main(demoapp=None):
+    DrawingAreaApp()
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/expander.py b/examples/demo/demos/expander.py
new file mode 100644 (file)
index 0000000..0ec149e
--- /dev/null
@@ -0,0 +1,62 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.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.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 St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Expander"
+description = """
+GtkExpander allows to provide additional content that is initially hidden.
+This is also known as "disclosure triangle".
+"""
+
+from gi.repository import Gtk
+
+
+class ExpanderApp:
+    def __init__(self):
+        self.window = Gtk.Dialog(title="GtkExpander")
+        self.window.add_buttons(Gtk.STOCK_CLOSE, Gtk.ResponseType.NONE)
+        self.window.set_resizable(False)
+        self.window.connect('response', lambda window, x: window.destroy())
+        self.window.connect('destroy', Gtk.main_quit)
+
+        content_area = self.window.get_content_area()
+        vbox = Gtk.VBox(spacing=5)
+        content_area.pack_start(vbox, True, True, 0)
+        vbox.set_border_width(5)
+
+        label = Gtk.Label(label='Expander demo. Click on the triangle for details.')
+        vbox.pack_start(label, True, True, 0)
+
+        expander = Gtk.Expander(label='Details')
+        vbox.pack_start(expander, False, False, 0)
+
+        label = Gtk.Label(label='Details can be shown or hidden')
+        expander.add(label)
+
+        self.window.show_all()
+
+
+def main(demoapp=None):
+    ExpanderApp()
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/flowbox.py b/examples/demo/demos/flowbox.py
new file mode 100755 (executable)
index 0000000..0485b7c
--- /dev/null
@@ -0,0 +1,752 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2014 Gian Mario Tagliaretti <gianmt@gnome.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.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 St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "FlowBox"
+description = """
+A FlowBox allows flexible and responsive grids which reflow as needed and
+support sorting and filtering. The children of a GtkFlowBox are regular widgets.
+"""
+
+from gi.repository import Gtk, Gdk
+
+
+class FlowBoxApp:
+    def __init__(self):
+        window = Gtk.Window()
+        window.connect('destroy', lambda x: Gtk.main_quit())
+        window.set_border_width(10)
+        window.set_default_size(600, 400)
+
+        header = Gtk.HeaderBar(title="Flow Box")
+        header.set_subtitle("Sample FlowBox app")
+        header.props.show_close_button = True
+
+        window.set_titlebar(header)
+
+        scrolled = Gtk.ScrolledWindow()
+        scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
+
+        flowbox = Gtk.FlowBox()
+        flowbox.set_valign(Gtk.Align.START)
+        flowbox.set_max_children_per_line(30)
+        flowbox.set_selection_mode(Gtk.SelectionMode.NONE)
+
+        self.create_flowbox(flowbox)
+
+        scrolled.add(flowbox)
+
+        window.add(scrolled)
+        window.show_all()
+
+    def color_swatch_new(self, str_color):
+        rgba = Gdk.RGBA()
+        rgba.parse(str_color)
+
+        text_rgba = Gdk.RGBA()  # default is white
+        if max(rgba.red, rgba.green, rgba.blue) > 0.6:
+            text_rgba.parse('black')
+
+        label = Gtk.Label(label=str_color)
+        label.override_background_color(0, rgba)
+        label.override_color(0, text_rgba)
+        return label
+
+    def create_flowbox(self, flowbox):
+        colors = [
+            'AliceBlue',
+            'AntiqueWhite',
+            'AntiqueWhite1',
+            'AntiqueWhite2',
+            'AntiqueWhite3',
+            'AntiqueWhite4',
+            'aqua',
+            'aquamarine',
+            'aquamarine1',
+            'aquamarine2',
+            'aquamarine3',
+            'aquamarine4',
+            'azure',
+            'azure1',
+            'azure2',
+            'azure3',
+            'azure4',
+            'beige',
+            'bisque',
+            'bisque1',
+            'bisque2',
+            'bisque3',
+            'bisque4',
+            'black',
+            'BlanchedAlmond',
+            'blue',
+            'blue1',
+            'blue2',
+            'blue3',
+            'blue4',
+            'BlueViolet',
+            'brown',
+            'brown1',
+            'brown2',
+            'brown3',
+            'brown4',
+            'burlywood',
+            'burlywood1',
+            'burlywood2',
+            'burlywood3',
+            'burlywood4',
+            'CadetBlue',
+            'CadetBlue1',
+            'CadetBlue2',
+            'CadetBlue3',
+            'CadetBlue4',
+            'chartreuse',
+            'chartreuse1',
+            'chartreuse2',
+            'chartreuse3',
+            'chartreuse4',
+            'chocolate',
+            'chocolate1',
+            'chocolate2',
+            'chocolate3',
+            'chocolate4',
+            'coral',
+            'coral1',
+            'coral2',
+            'coral3',
+            'coral4',
+            'CornflowerBlue',
+            'cornsilk',
+            'cornsilk1',
+            'cornsilk2',
+            'cornsilk3',
+            'cornsilk4',
+            'crimson',
+            'cyan',
+            'cyan1',
+            'cyan2',
+            'cyan3',
+            'cyan4',
+            'DarkBlue',
+            'DarkCyan',
+            'DarkGoldenrod',
+            'DarkGoldenrod1',
+            'DarkGoldenrod2',
+            'DarkGoldenrod3',
+            'DarkGoldenrod4',
+            'DarkGray',
+            'DarkGreen',
+            'DarkGrey',
+            'DarkKhaki',
+            'DarkMagenta',
+            'DarkOliveGreen',
+            'DarkOliveGreen1',
+            'DarkOliveGreen2',
+            'DarkOliveGreen3',
+            'DarkOliveGreen4',
+            'DarkOrange',
+            'DarkOrange1',
+            'DarkOrange2',
+            'DarkOrange3',
+            'DarkOrange4',
+            'DarkOrchid',
+            'DarkOrchid1',
+            'DarkOrchid2',
+            'DarkOrchid3',
+            'DarkOrchid4',
+            'DarkRed',
+            'DarkSalmon',
+            'DarkSeaGreen',
+            'DarkSeaGreen1',
+            'DarkSeaGreen2',
+            'DarkSeaGreen3',
+            'DarkSeaGreen4',
+            'DarkSlateBlue',
+            'DarkSlateGray',
+            'DarkSlateGray1',
+            'DarkSlateGray2',
+            'DarkSlateGray3',
+            'DarkSlateGray4',
+            'DarkSlateGrey',
+            'DarkTurquoise',
+            'DarkViolet',
+            'DeepPink',
+            'DeepPink1',
+            'DeepPink2',
+            'DeepPink3',
+            'DeepPink4',
+            'DeepSkyBlue',
+            'DeepSkyBlue1',
+            'DeepSkyBlue2',
+            'DeepSkyBlue3',
+            'DeepSkyBlue4',
+            'DimGray',
+            'DimGrey',
+            'DodgerBlue',
+            'DodgerBlue1',
+            'DodgerBlue2',
+            'DodgerBlue3',
+            'DodgerBlue4',
+            'firebrick',
+            'firebrick1',
+            'firebrick2',
+            'firebrick3',
+            'firebrick4',
+            'FloralWhite',
+            'ForestGreen',
+            'fuchsia',
+            'gainsboro',
+            'GhostWhite',
+            'gold',
+            'gold1',
+            'gold2',
+            'gold3',
+            'gold4',
+            'goldenrod',
+            'goldenrod1',
+            'goldenrod2',
+            'goldenrod3',
+            'goldenrod4',
+            'gray',
+            'gray0',
+            'gray1',
+            'gray2',
+            'gray3',
+            'gray4',
+            'gray5',
+            'gray6',
+            'gray7',
+            'gray8',
+            'gray9',
+            'gray10',
+            'gray11',
+            'gray12',
+            'gray13',
+            'gray14',
+            'gray15',
+            'gray16',
+            'gray17',
+            'gray18',
+            'gray19',
+            'gray20',
+            'gray21',
+            'gray22',
+            'gray23',
+            'gray24',
+            'gray25',
+            'gray26',
+            'gray27',
+            'gray28',
+            'gray29',
+            'gray30',
+            'gray31',
+            'gray32',
+            'gray33',
+            'gray34',
+            'gray35',
+            'gray36',
+            'gray37',
+            'gray38',
+            'gray39',
+            'gray40',
+            'gray41',
+            'gray42',
+            'gray43',
+            'gray44',
+            'gray45',
+            'gray46',
+            'gray47',
+            'gray48',
+            'gray49',
+            'gray50',
+            'gray51',
+            'gray52',
+            'gray53',
+            'gray54',
+            'gray55',
+            'gray56',
+            'gray57',
+            'gray58',
+            'gray59',
+            'gray60',
+            'gray61',
+            'gray62',
+            'gray63',
+            'gray64',
+            'gray65',
+            'gray66',
+            'gray67',
+            'gray68',
+            'gray69',
+            'gray70',
+            'gray71',
+            'gray72',
+            'gray73',
+            'gray74',
+            'gray75',
+            'gray76',
+            'gray77',
+            'gray78',
+            'gray79',
+            'gray80',
+            'gray81',
+            'gray82',
+            'gray83',
+            'gray84',
+            'gray85',
+            'gray86',
+            'gray87',
+            'gray88',
+            'gray89',
+            'gray90',
+            'gray91',
+            'gray92',
+            'gray93',
+            'gray94',
+            'gray95',
+            'gray96',
+            'gray97',
+            'gray98',
+            'gray99',
+            'gray100',
+            'green',
+            'green1',
+            'green2',
+            'green3',
+            'green4',
+            'GreenYellow',
+            'grey',
+            'grey0',
+            'grey1',
+            'grey2',
+            'grey3',
+            'grey4',
+            'grey5',
+            'grey6',
+            'grey7',
+            'grey8',
+            'grey9',
+            'grey10',
+            'grey11',
+            'grey12',
+            'grey13',
+            'grey14',
+            'grey15',
+            'grey16',
+            'grey17',
+            'grey18',
+            'grey19',
+            'grey20',
+            'grey21',
+            'grey22',
+            'grey23',
+            'grey24',
+            'grey25',
+            'grey26',
+            'grey27',
+            'grey28',
+            'grey29',
+            'grey30',
+            'grey31',
+            'grey32',
+            'grey33',
+            'grey34',
+            'grey35',
+            'grey36',
+            'grey37',
+            'grey38',
+            'grey39',
+            'grey40',
+            'grey41',
+            'grey42',
+            'grey43',
+            'grey44',
+            'grey45',
+            'grey46',
+            'grey47',
+            'grey48',
+            'grey49',
+            'grey50',
+            'grey51',
+            'grey52',
+            'grey53',
+            'grey54',
+            'grey55',
+            'grey56',
+            'grey57',
+            'grey58',
+            'grey59',
+            'grey60',
+            'grey61',
+            'grey62',
+            'grey63',
+            'grey64',
+            'grey65',
+            'grey66',
+            'grey67',
+            'grey68',
+            'grey69',
+            'grey70',
+            'grey71',
+            'grey72',
+            'grey73',
+            'grey74',
+            'grey75',
+            'grey76',
+            'grey77',
+            'grey78',
+            'grey79',
+            'grey80',
+            'grey81',
+            'grey82',
+            'grey83',
+            'grey84',
+            'grey85',
+            'grey86',
+            'grey87',
+            'grey88',
+            'grey89',
+            'grey90',
+            'grey91',
+            'grey92',
+            'grey93',
+            'grey94',
+            'grey95',
+            'grey96',
+            'grey97',
+            'grey98',
+            'grey99',
+            'grey100',
+            'honeydew',
+            'honeydew1',
+            'honeydew2',
+            'honeydew3',
+            'honeydew4',
+            'HotPink',
+            'HotPink1',
+            'HotPink2',
+            'HotPink3',
+            'HotPink4',
+            'IndianRed',
+            'IndianRed1',
+            'IndianRed2',
+            'IndianRed3',
+            'IndianRed4',
+            'indigo',
+            'ivory',
+            'ivory1',
+            'ivory2',
+            'ivory3',
+            'ivory4',
+            'khaki',
+            'khaki1',
+            'khaki2',
+            'khaki3',
+            'khaki4',
+            'lavender',
+            'LavenderBlush',
+            'LavenderBlush1',
+            'LavenderBlush2',
+            'LavenderBlush3',
+            'LavenderBlush4',
+            'LawnGreen',
+            'LemonChiffon',
+            'LemonChiffon1',
+            'LemonChiffon2',
+            'LemonChiffon3',
+            'LemonChiffon4',
+            'LightBlue',
+            'LightBlue1',
+            'LightBlue2',
+            'LightBlue3',
+            'LightBlue4',
+            'LightCoral',
+            'LightCyan',
+            'LightCyan1',
+            'LightCyan2',
+            'LightCyan3',
+            'LightCyan4',
+            'LightGoldenrod',
+            'LightGoldenrod1',
+            'LightGoldenrod2',
+            'LightGoldenrod3',
+            'LightGoldenrod4',
+            'LightGoldenrodYellow',
+            'LightGray',
+            'LightGreen',
+            'LightGrey',
+            'LightPink',
+            'LightPink1',
+            'LightPink2',
+            'LightPink3',
+            'LightPink4',
+            'LightSalmon',
+            'LightSalmon1',
+            'LightSalmon2',
+            'LightSalmon3',
+            'LightSalmon4',
+            'LightSeaGreen',
+            'LightSkyBlue',
+            'LightSkyBlue1',
+            'LightSkyBlue2',
+            'LightSkyBlue3',
+            'LightSkyBlue4',
+            'LightSlateBlue',
+            'LightSlateGray',
+            'LightSlateGrey',
+            'LightSteelBlue',
+            'LightSteelBlue1',
+            'LightSteelBlue2',
+            'LightSteelBlue3',
+            'LightSteelBlue4',
+            'LightYellow',
+            'LightYellow1',
+            'LightYellow2',
+            'LightYellow3',
+            'LightYellow4',
+            'lime',
+            'LimeGreen',
+            'linen',
+            'magenta',
+            'magenta1',
+            'magenta2',
+            'magenta3',
+            'magenta4',
+            'maroon',
+            'maroon1',
+            'maroon2',
+            'maroon3',
+            'maroon4',
+            'MediumAquamarine',
+            'MediumBlue',
+            'MediumOrchid',
+            'MediumOrchid1',
+            'MediumOrchid2',
+            'MediumOrchid3',
+            'MediumOrchid4',
+            'MediumPurple',
+            'MediumPurple1',
+            'MediumPurple2',
+            'MediumPurple3',
+            'MediumPurple4',
+            'MediumSeaGreen',
+            'MediumSlateBlue',
+            'MediumSpringGreen',
+            'MediumTurquoise',
+            'MediumVioletRed',
+            'MidnightBlue',
+            'MintCream',
+            'MistyRose',
+            'MistyRose1',
+            'MistyRose2',
+            'MistyRose3',
+            'MistyRose4',
+            'moccasin',
+            'NavajoWhite',
+            'NavajoWhite1',
+            'NavajoWhite2',
+            'NavajoWhite3',
+            'NavajoWhite4',
+            'navy',
+            'NavyBlue',
+            'OldLace',
+            'olive',
+            'OliveDrab',
+            'OliveDrab1',
+            'OliveDrab2',
+            'OliveDrab3',
+            'OliveDrab4',
+            'orange',
+            'orange1',
+            'orange2',
+            'orange3',
+            'orange4',
+            'OrangeRed',
+            'OrangeRed1',
+            'OrangeRed2',
+            'OrangeRed3',
+            'OrangeRed4',
+            'orchid',
+            'orchid1',
+            'orchid2',
+            'orchid3',
+            'orchid4',
+            'PaleGoldenrod',
+            'PaleGreen',
+            'PaleGreen1',
+            'PaleGreen2',
+            'PaleGreen3',
+            'PaleGreen4',
+            'PaleTurquoise',
+            'PaleTurquoise1',
+            'PaleTurquoise2',
+            'PaleTurquoise3',
+            'PaleTurquoise4',
+            'PaleVioletRed',
+            'PaleVioletRed1',
+            'PaleVioletRed2',
+            'PaleVioletRed3',
+            'PaleVioletRed4',
+            'PapayaWhip',
+            'PeachPuff',
+            'PeachPuff1',
+            'PeachPuff2',
+            'PeachPuff3',
+            'PeachPuff4',
+            'peru',
+            'pink',
+            'pink1',
+            'pink2',
+            'pink3',
+            'pink4',
+            'plum',
+            'plum1',
+            'plum2',
+            'plum3',
+            'plum4',
+            'PowderBlue',
+            'purple',
+            'purple1',
+            'purple2',
+            'purple3',
+            'purple4',
+            'red',
+            'red1',
+            'red2',
+            'red3',
+            'red4',
+            'RosyBrown',
+            'RosyBrown1',
+            'RosyBrown2',
+            'RosyBrown3',
+            'RosyBrown4',
+            'RoyalBlue',
+            'RoyalBlue1',
+            'RoyalBlue2',
+            'RoyalBlue3',
+            'RoyalBlue4',
+            'SaddleBrown',
+            'salmon',
+            'salmon1',
+            'salmon2',
+            'salmon3',
+            'salmon4',
+            'SandyBrown',
+            'SeaGreen',
+            'SeaGreen1',
+            'SeaGreen2',
+            'SeaGreen3',
+            'SeaGreen4',
+            'seashell',
+            'seashell1',
+            'seashell2',
+            'seashell3',
+            'seashell4',
+            'sienna',
+            'sienna1',
+            'sienna2',
+            'sienna3',
+            'sienna4',
+            'silver',
+            'SkyBlue',
+            'SkyBlue1',
+            'SkyBlue2',
+            'SkyBlue3',
+            'SkyBlue4',
+            'SlateBlue',
+            'SlateBlue1',
+            'SlateBlue2',
+            'SlateBlue3',
+            'SlateBlue4',
+            'SlateGray',
+            'SlateGray1',
+            'SlateGray2',
+            'SlateGray3',
+            'SlateGray4',
+            'SlateGrey',
+            'snow',
+            'snow1',
+            'snow2',
+            'snow3',
+            'snow4',
+            'SpringGreen',
+            'SpringGreen1',
+            'SpringGreen2',
+            'SpringGreen3',
+            'SpringGreen4',
+            'SteelBlue',
+            'SteelBlue1',
+            'SteelBlue2',
+            'SteelBlue3',
+            'SteelBlue4',
+            'tan',
+            'tan1',
+            'tan2',
+            'tan3',
+            'tan4',
+            'teal',
+            'thistle',
+            'thistle1',
+            'thistle2',
+            'thistle3',
+            'thistle4',
+            'tomato',
+            'tomato1',
+            'tomato2',
+            'tomato3',
+            'tomato4',
+            'turquoise',
+            'turquoise1',
+            'turquoise2',
+            'turquoise3',
+            'turquoise4',
+            'violet',
+            'VioletRed',
+            'VioletRed1',
+            'VioletRed2',
+            'VioletRed3',
+            'VioletRed4',
+            'wheat',
+            'wheat1',
+            'wheat2',
+            'wheat3',
+            'wheat4',
+            'white',
+            'WhiteSmoke',
+            'yellow',
+            'yellow1',
+            'yellow2',
+            'yellow3',
+            'yellow4',
+            'YellowGreen',
+        ]
+
+        for color in colors:
+            button = self.color_swatch_new(color)
+            flowbox.add(button)
+
+
+def main(demoapp=None):
+    FlowBoxApp()
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/images.py b/examples/demo/demos/images.py
new file mode 100644 (file)
index 0000000..80c99af
--- /dev/null
@@ -0,0 +1,305 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.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.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 St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Images"
+description = """GtkImage is used to display an image; the image can be in a
+number of formats. Typically, you load an image into a GdkPixbuf, then display
+the pixbuf. This demo code shows some of the more obscure cases, in the simple
+case a call to gtk_image_new_from_file() is all you need.
+"""
+
+import os
+from os import path
+
+from gi.repository import Gtk, Gdk, GdkPixbuf, GLib, Gio, GObject
+
+
+class ImagesApp:
+    def __init__(self):
+        self.pixbuf_loader = None
+        self.image_stream = None
+
+        self.window = Gtk.Window(title="Images")
+        self.window.connect('destroy', self.cleanup_cb)
+        self.window.set_border_width(8)
+
+        vbox = Gtk.VBox(spacing=8)
+        vbox.set_border_width(8)
+        self.window.add(vbox)
+
+        label = Gtk.Label()
+        label.set_markup('<u>Image loaded from file</u>')
+        vbox.pack_start(label, False, False, 0)
+
+        frame = Gtk.Frame()
+        frame.set_shadow_type(Gtk.ShadowType.IN)
+
+        # The alignment keeps the frame from growing when users resize
+        # the window
+        align = Gtk.Alignment(xalign=0.5,
+                              yalign=0.5,
+                              xscale=0,
+                              yscale=0)
+        align.add(frame)
+        vbox.pack_start(align, False, False, 0)
+
+        self.base_path = os.path.abspath(os.path.dirname(__file__))
+        self.base_path = os.path.join(self.base_path, 'data')
+        filename = os.path.join(self.base_path, 'gtk-logo-rgb.gif')
+        pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename)
+        transparent = pixbuf.add_alpha(True, 0xff, 0xff, 0xff)
+        image = Gtk.Image.new_from_pixbuf(transparent)
+        frame.add(image)
+
+        # Animation
+
+        label = Gtk.Label()
+        label.set_markup('<u>Animation loaded from file</u>')
+        vbox.pack_start(label, False, False, 0)
+
+        frame = Gtk.Frame()
+        frame.set_shadow_type(Gtk.ShadowType.IN)
+
+        # The alignment keeps the frame from growing when users resize
+        # the window
+        align = Gtk.Alignment(xalign=0.5,
+                              yalign=0.5,
+                              xscale=0,
+                              yscale=0)
+        align.add(frame)
+        vbox.pack_start(align, False, False, 0)
+
+        img_path = path.join(self.base_path, 'floppybuddy.gif')
+        image = Gtk.Image.new_from_file(img_path)
+        frame.add(image)
+
+        # Symbolic icon
+
+        label = Gtk.Label()
+        label.set_markup('<u>Symbolic themed icon</u>')
+        vbox.pack_start(label, False, False, 0)
+
+        frame = Gtk.Frame()
+        frame.set_shadow_type(Gtk.ShadowType.IN)
+
+        # The alignment keeps the frame from growing when users resize
+        # the window
+        align = Gtk.Alignment(xalign=0.5,
+                              yalign=0.5,
+                              xscale=0,
+                              yscale=0)
+        align.add(frame)
+        vbox.pack_start(align, False, False, 0)
+
+        gicon = Gio.ThemedIcon.new_with_default_fallbacks('battery-caution-charging-symbolic')
+        image = Gtk.Image.new_from_gicon(gicon, Gtk.IconSize.DIALOG)
+        frame.add(image)
+
+        # progressive
+
+        label = Gtk.Label()
+        label.set_markup('<u>Progressive image loading</u>')
+        vbox.pack_start(label, False, False, 0)
+
+        frame = Gtk.Frame()
+        frame.set_shadow_type(Gtk.ShadowType.IN)
+
+        # The alignment keeps the frame from growing when users resize
+        # the window
+        align = Gtk.Alignment(xalign=0.5,
+                              yalign=0.5,
+                              xscale=0,
+                              yscale=0)
+        align.add(frame)
+        vbox.pack_start(align, False, False, 0)
+
+        image = Gtk.Image.new_from_pixbuf(None)
+        frame.add(image)
+
+        self.start_progressive_loading(image)
+
+        # Sensistivity control
+        button = Gtk.ToggleButton.new_with_mnemonic('_Insensitive')
+        button.connect('toggled', self.toggle_sensitivity_cb, vbox)
+        vbox.pack_start(button, False, False, 0)
+
+        self.window.show_all()
+
+    def toggle_sensitivity_cb(self, button, container):
+        widget_list = container.get_children()
+        for w in widget_list:
+            if w != button:
+                w.set_sensitive(not button.get_active())
+
+        return True
+
+    def progressive_timeout(self, image):
+        # This shows off fully-paranoid error handling, so looks scary.
+        # You could factor out the error handling code into a nice separate
+        # function to make things nicer.
+
+        if self.image_stream:
+            try:
+                buf = self.image_stream.read(256)
+            except IOError as e:
+                dialog = Gtk.MessageDialog(self.window,
+                                           Gtk.DialogFlags.DESTROY_WITH_PARENT,
+                                           Gtk.MessageType.ERROR,
+                                           Gtk.ButtonsType.CLOSE,
+                                           "Failure reading image file 'alphatest.png': %s" % (str(e), ))
+
+                self.image_stream.close()
+                self.image_stream = None
+                self.load_timeout = 0
+
+                dialog.show()
+                dialog.connect('response', lambda x, y: dialog.destroy())
+
+                return False  # uninstall the timeout
+
+            try:
+                self.pixbuf_loader.write(buf)
+
+            except GObject.GError as e:
+                dialog = Gtk.MessageDialog(self.window,
+                                           Gtk.DialogFlags.DESTROY_WITH_PARENT,
+                                           Gtk.MessageType.ERROR,
+                                           Gtk.ButtonsType.CLOSE,
+                                           e.message)
+
+                self.image_stream.close()
+                self.image_stream = None
+                self.load_timeout = 0
+
+                dialog.show()
+                dialog.connect('response', lambda x, y: dialog.destroy())
+
+                return False  # uninstall the timeout
+
+            if len(buf) < 256:  # eof
+                self.image_stream.close()
+                self.image_stream = None
+
+                # Errors can happen on close, e.g. if the image
+                # file was truncated we'll know on close that
+                # it was incomplete.
+                try:
+                    self.pixbuf_loader.close()
+                except GObject.GError as e:
+                    dialog = Gtk.MessageDialog(self.window,
+                                               Gtk.DialogFlags.DESTROY_WITH_PARENT,
+                                               Gtk.MessageType.ERROR,
+                                               Gtk.ButtonsType.CLOSE,
+                                               'Failed to load image: %s' % e.message)
+
+                    self.load_timeout = 0
+
+                    dialog.show()
+                    dialog.connect('response', lambda x, y: dialog.destroy())
+
+                    return False  # uninstall the timeout
+        else:
+            img_path = path.join(self.base_path, 'alphatest.png')
+            try:
+                self.image_stream = open(img_path, 'rb')
+            except IOError as e:
+                dialog = Gtk.MessageDialog(self.window,
+                                           Gtk.DialogFlags.DESTROY_WITH_PARENT,
+                                           Gtk.MessageType.ERROR,
+                                           Gtk.ButtonsType.CLOSE,
+                                           str(e))
+                self.load_timeout = 0
+                dialog.show()
+                dialog.connect('response', lambda x, y: dialog.destroy())
+
+                return False  # uninstall the timeout
+
+            if self.pixbuf_loader:
+                try:
+                    self.pixbuf_loader.close()
+                except GObject.GError:
+                    pass
+                self.pixbuf_loader = None
+
+            self.pixbuf_loader = GdkPixbuf.PixbufLoader()
+
+            self.pixbuf_loader.connect('area-prepared',
+                                       self.progressive_prepared_callback,
+                                       image)
+
+            self.pixbuf_loader.connect('area-updated',
+                                       self.progressive_updated_callback,
+                                       image)
+        # leave timeout installed
+        return True
+
+    def progressive_prepared_callback(self, loader, image):
+        pixbuf = loader.get_pixbuf()
+        # Avoid displaying random memory contents, since the pixbuf
+        # isn't filled in yet.
+        pixbuf.fill(0xaaaaaaff)
+        image.set_from_pixbuf(pixbuf)
+
+    def progressive_updated_callback(self, loader, x, y, width, height, image):
+        # We know the pixbuf inside the GtkImage has changed, but the image
+        # itself doesn't know this; so queue a redraw.  If we wanted to be
+        # really efficient, we could use a drawing area or something
+        # instead of a GtkImage, so we could control the exact position of
+        # the pixbuf on the display, then we could queue a draw for only
+        # the updated area of the image.
+        image.queue_draw()
+
+    def start_progressive_loading(self, image):
+        # This is obviously totally contrived (we slow down loading
+        # on purpose to show how incremental loading works).
+        # The real purpose of incremental loading is the case where
+        # you are reading data from a slow source such as the network.
+        # The timeout simply simulates a slow data source by inserting
+        # pauses in the reading process.
+
+        self.load_timeout = Gdk.threads_add_timeout(150,
+                                                    GLib.PRIORITY_DEFAULT_IDLE,
+                                                    self.progressive_timeout,
+                                                    image)
+
+    def cleanup_cb(self, widget):
+        if self.load_timeout:
+            GLib.source_remove(self.load_timeout)
+
+        if self.pixbuf_loader:
+            try:
+                self.pixbuf_loader.close()
+            except GObject.GError:
+                pass
+
+        if self.image_stream:
+            self.image_stream.close()
+
+        Gtk.main_quit()
+
+
+def main(demoapp=None):
+    ImagesApp()
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/infobars.py b/examples/demo/demos/infobars.py
new file mode 100644 (file)
index 0000000..b8a1dc7
--- /dev/null
@@ -0,0 +1,103 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.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.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 St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Info Bars"
+description = """
+Info bar widgets are used to report important messages to the user.
+"""
+
+from gi.repository import Gtk
+
+
+class InfobarApp:
+    def __init__(self):
+        self.window = Gtk.Window()
+        self.window.set_title('Info Bars')
+        self.window.set_border_width(8)
+        self.window.connect('destroy', Gtk.main_quit)
+
+        vbox = Gtk.VBox(spacing=0)
+        self.window.add(vbox)
+
+        bar = Gtk.InfoBar()
+        vbox.pack_start(bar, False, False, 0)
+        bar.set_message_type(Gtk.MessageType.INFO)
+        label = Gtk.Label(label='This is an info bar with message type Gtk.MessageType.INFO')
+        bar.get_content_area().pack_start(label, False, False, 0)
+
+        bar = Gtk.InfoBar()
+        vbox.pack_start(bar, False, False, 0)
+        bar.set_message_type(Gtk.MessageType.WARNING)
+        label = Gtk.Label(label='This is an info bar with message type Gtk.MessageType.WARNING')
+        bar.get_content_area().pack_start(label, False, False, 0)
+
+        bar = Gtk.InfoBar()
+        bar.add_button(Gtk.STOCK_OK, Gtk.ResponseType.OK)
+        bar.connect('response', self.on_bar_response)
+        vbox.pack_start(bar, False, False, 0)
+        bar.set_message_type(Gtk.MessageType.QUESTION)
+        label = Gtk.Label(label='This is an info bar with message type Gtk.MessageType.QUESTION')
+        bar.get_content_area().pack_start(label, False, False, 0)
+
+        bar = Gtk.InfoBar()
+        vbox.pack_start(bar, False, False, 0)
+        bar.set_message_type(Gtk.MessageType.ERROR)
+        label = Gtk.Label(label='This is an info bar with message type Gtk.MessageType.ERROR')
+        bar.get_content_area().pack_start(label, False, False, 0)
+
+        bar = Gtk.InfoBar()
+        vbox.pack_start(bar, False, False, 0)
+        bar.set_message_type(Gtk.MessageType.OTHER)
+        label = Gtk.Label(label='This is an info bar with message type Gtk.MessageType.OTHER')
+        bar.get_content_area().pack_start(label, False, False, 0)
+
+        frame = Gtk.Frame(label="Info bars")
+        vbox.pack_start(frame, False, False, 8)
+
+        vbox2 = Gtk.VBox(spacing=8)
+        vbox2.set_border_width(8)
+        frame.add(vbox2)
+
+        # Standard message dialog
+        label = Gtk.Label(label='An example of different info bars')
+        vbox2.pack_start(label, False, False, 0)
+
+        self.window.show_all()
+
+    def on_bar_response(self, info_bar, response_id):
+        dialog = Gtk.MessageDialog(transient_for=self.window,
+                                   modal=True,
+                                   destroy_with_parent=True,
+                                   message_type=Gtk.MessageType.INFO,
+                                   buttons=Gtk.ButtonsType.OK,
+                                   text='You clicked on an info bar')
+        dialog.format_secondary_text('Your response has id %d' % response_id)
+        dialog.run()
+        dialog.destroy()
+
+
+def main(demoapp=None):
+    InfobarApp()
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/links.py b/examples/demo/demos/links.py
new file mode 100644 (file)
index 0000000..c6d331d
--- /dev/null
@@ -0,0 +1,77 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.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.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 St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Links"
+description = """
+GtkLabel can show hyperlinks. The default action is to call gtk_show_uri() on
+their URI, but it is possible to override this with a custom handler.
+"""
+
+from gi.repository import Gtk
+
+
+class LinksApp:
+    def __init__(self):
+        self.window = Gtk.Window()
+        self.window.set_title('Links')
+        self.window.set_border_width(12)
+        self.window.connect('destroy', Gtk.main_quit)
+
+        label = Gtk.Label(label="""Some <a href="http://en.wikipedia.org/wiki/Text"
+title="plain text">text</a> may be marked up
+as hyperlinks, which can be clicked
+or activated via <a href="keynav">keynav</a>""")
+
+        label.set_use_markup(True)
+        label.connect("activate-link", self.activate_link)
+        self.window.add(label)
+        label.show()
+
+        self.window.show()
+
+    def activate_link(self, label, uri):
+        if uri == 'keynav':
+            parent = label.get_toplevel()
+            markup = """The term <i>keynav</i> is a shorthand for
+keyboard navigation and refers to the process of using
+a program (exclusively) via keyboard input."""
+            dialog = Gtk.MessageDialog(transient_for=parent,
+                                       destroy_with_parent=True,
+                                       message_type=Gtk.MessageType.INFO,
+                                       buttons=Gtk.ButtonsType.OK,
+                                       text=markup,
+                                       use_markup=True)
+            dialog.present()
+            dialog.connect('response', self.response_cb)
+
+            return True
+
+    def response_cb(self, dialog, response_id):
+        dialog.destroy()
+
+
+def main(demoapp=None):
+    LinksApp()
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/menus.py b/examples/demo/demos/menus.py
new file mode 100644 (file)
index 0000000..7cf2e57
--- /dev/null
@@ -0,0 +1,134 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.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.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 St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Menus"
+description = """There are several widgets involved in displaying menus. The
+GtkMenuBar widget is a menu bar, which normally appears horizontally at the top
+of an application, but can also be layed out vertically. The GtkMenu widget is
+the actual menu that pops up. Both GtkMenuBar and GtkMenu are subclasses of
+GtkMenuShell; a GtkMenuShell contains menu items (GtkMenuItem). Each menu item
+contains text and/or images and can be selected by the user. There are several
+kinds of menu item, including plain GtkMenuItem, GtkCheckMenuItem which can be
+checked/unchecked, GtkRadioMenuItem which is a check menu item that's in a
+mutually exclusive group, GtkSeparatorMenuItem which is a separator bar,
+GtkTearoffMenuItem which allows a GtkMenu to be torn off, and GtkImageMenuItem
+which can place a GtkImage or other widget next to the menu text. A GtkMenuItem
+can have a submenu, which is simply a GtkMenu to pop up when the menu item is
+selected. Typically, all menu items in a menu bar have submenus. GtkUIManager
+provides a higher-level interface for creating menu bars and menus; while you
+can construct menus manually, most people don't do that. There's a separate demo
+for GtkUIManager.
+"""
+
+from gi.repository import Gtk
+
+
+class MenusApp:
+    def __init__(self):
+        self.window = Gtk.Window()
+        self.window.set_title('Menus')
+        self.window.connect('destroy', Gtk.main_quit)
+
+        accel_group = Gtk.AccelGroup()
+        self.window.add_accel_group(accel_group)
+        self.window.set_border_width(0)
+
+        box = Gtk.HBox()
+        self.window.add(box)
+
+        box1 = Gtk.VBox()
+        box.add(box1)
+
+        menubar = Gtk.MenuBar()
+        box1.pack_start(menubar, False, True, 0)
+
+        menuitem = Gtk.MenuItem(label='test\nline2')
+        menuitem.set_submenu(self.create_menu(3, True))
+        menubar.append(menuitem)
+
+        menuitem = Gtk.MenuItem(label='foo')
+        menuitem.set_submenu(self.create_menu(4, True))
+        menuitem.set_right_justified(True)
+        menubar.append(menuitem)
+
+        box2 = Gtk.VBox(spacing=10)
+        box2.set_border_width(10)
+        box1.pack_start(box2, False, True, 0)
+
+        button = Gtk.Button(label='Flip')
+        button.connect('clicked', self.change_orientation, menubar)
+        box2.pack_start(button, True, True, 0)
+
+        button = Gtk.Button(label='Close')
+        button.connect('clicked', lambda x: self.window.destroy())
+        box2.pack_start(button, True, True, 0)
+        button.set_can_default(True)
+
+        self.window.show_all()
+
+    def create_menu(self, depth, tearoff):
+        if depth < 1:
+            return None
+
+        menu = Gtk.Menu()
+
+        if tearoff:
+            menuitem = Gtk.TearoffMenuItem()
+            menu.append(menuitem)
+
+        i = 0
+        j = 1
+        while i < 5:
+            label = 'item %2d - %d' % (depth, j)
+            # we should be adding this to a group but the group API
+            # isn't bindable - we need something more like the
+            # Gtk.RadioAction API
+            menuitem = Gtk.RadioMenuItem(label=label)
+            menu.append(menuitem)
+
+            if i == 3:
+                menuitem.set_sensitive(False)
+
+            menuitem.set_submenu(self.create_menu(depth - 1, True))
+
+            i += 1
+            j += 1
+
+        return menu
+
+    def change_orientation(self, button, menubar):
+        parent = menubar.get_parent()
+        orientation = parent.get_orientation()
+        parent.set_orientation(1 - orientation)
+
+        if orientation == Gtk.Orientation.VERTICAL:
+            menubar.props.pack_direction = Gtk.PackDirection.TTB
+        else:
+            menubar.props.pack_direction = Gtk.PackDirection.LTR
+
+
+def main(demoapp=None):
+    MenusApp()
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/pickers.py b/examples/demo/demos/pickers.py
new file mode 100644 (file)
index 0000000..d8a9c75
--- /dev/null
@@ -0,0 +1,77 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.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.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 St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Pickers"
+description = """These widgets are mainly intended for use in preference
+dialogs. They allow to select colors, fonts, files and directories.
+"""
+
+from gi.repository import Gtk
+
+
+class PickersApp:
+    def __init__(self):
+        self.window = Gtk.Window(title='Pickers')
+        self.window.connect('destroy', Gtk.main_quit)
+        self.window.set_border_width(10)
+
+        table = Gtk.Table(n_rows=4, n_columns=2, homogeneous=False)
+        table.set_col_spacing(0, 10)
+        table.set_row_spacings(3)
+        self.window.add(table)
+        table.set_border_width(10)
+
+        label = Gtk.Label(label='Color:')
+        label.set_alignment(0.0, 0.5)
+        picker = Gtk.ColorButton()
+        table.attach_defaults(label, 0, 1, 0, 1)
+        table.attach_defaults(picker, 1, 2, 0, 1)
+
+        label = Gtk.Label(label='Font:')
+        label.set_alignment(0.0, 0.5)
+        picker = Gtk.FontButton()
+        table.attach_defaults(label, 0, 1, 1, 2)
+        table.attach_defaults(picker, 1, 2, 1, 2)
+
+        label = Gtk.Label(label='File:')
+        label.set_alignment(0.0, 0.5)
+        picker = Gtk.FileChooserButton.new('Pick a File',
+                                           Gtk.FileChooserAction.OPEN)
+        table.attach_defaults(label, 0, 1, 2, 3)
+        table.attach_defaults(picker, 1, 2, 2, 3)
+
+        label = Gtk.Label(label='Folder:')
+        label.set_alignment(0.0, 0.5)
+        picker = Gtk.FileChooserButton.new('Pick a Folder',
+                                           Gtk.FileChooserAction.SELECT_FOLDER)
+        table.attach_defaults(label, 0, 1, 3, 4)
+        table.attach_defaults(picker, 1, 2, 3, 4)
+
+        self.window.show_all()
+
+
+def main(demoapp=None):
+    PickersApp()
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/pixbuf.py b/examples/demo/demos/pixbuf.py
new file mode 100644 (file)
index 0000000..c213584
--- /dev/null
@@ -0,0 +1,184 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.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.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 St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Pixbufs"
+description = """A GdkPixbuf represents an image, normally in RGB or RGBA
+format. Pixbufs are normally used to load files from disk and perform image
+scaling. It also shows off how to use GtkDrawingArea to do a simple animation.
+Look at the Image demo for additional pixbuf usage examples.
+"""
+
+from gi.repository import Gtk, Gdk, GdkPixbuf, GLib
+import os
+import math
+
+
+class PixbufApp:
+    FRAME_DELAY = 50
+    BACKGROUND_NAME = "background.jpg"
+    CYCLE_LEN = 60
+
+    def __init__(self):
+        self.background_width = 0
+        self.background_height = 0
+        self.background_pixbuf = None
+        self.frame = None
+        self.frame_num = 0
+        self.pixbufs = []
+        self.image_names = [
+            "apple-red.png",
+            "gnome-applets.png",
+            "gnome-calendar.png",
+            "gnome-foot.png",
+            "gnome-gmush.png",
+            "gnome-gimp.png",
+            "gnome-gsame.png",
+            "gnu-keys.png"
+        ]
+
+        self.window = Gtk.Window(title="Pixbufs")
+        self.window.set_resizable(False)
+        self.window.connect('destroy', self.cleanup_cb)
+
+        try:
+            self.load_pixbufs()
+        except GLib.Error as e:
+            dialog = Gtk.MessageDialog(None,
+                                       0,
+                                       Gtk.MessageType.ERROR,
+                                       Gtk.ButtonsType.CLOSE,
+                                       e.message)
+
+            dialog.run()
+            dialog.destroy()
+            Gtk.main_quit()
+
+        self.window.set_size_request(self.background_width,
+                                     self.background_height)
+        self.frame = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB,
+                                          False,
+                                          8,
+                                          self.background_width,
+                                          self.background_height)
+        self.da = Gtk.DrawingArea()
+        self.da.connect('draw', self.draw_cb)
+        self.window.add(self.da)
+        self.timeout_id = GLib.timeout_add(self.FRAME_DELAY, self.timeout_cb)
+        self.window.show_all()
+
+    def load_pixbufs(self):
+        base_path = os.path.abspath(os.path.dirname(__file__))
+        base_path = os.path.join(base_path, 'data')
+        img_path = os.path.join(base_path, self.BACKGROUND_NAME)
+        self.background_pixbuf = GdkPixbuf.Pixbuf.new_from_file(img_path)
+        self.background_height = self.background_pixbuf.get_height()
+        self.background_width = self.background_pixbuf.get_width()
+
+        for img_name in self.image_names:
+            img_path = os.path.join(base_path, img_name)
+            self.pixbufs.append(GdkPixbuf.Pixbuf.new_from_file(img_path))
+
+    def draw_cb(self, da, cairo_ctx):
+        Gdk.cairo_set_source_pixbuf(cairo_ctx, self.frame, 0, 0)
+        cairo_ctx.paint()
+
+        return True
+
+    def timeout_cb(self):
+        self.background_pixbuf.copy_area(0, 0,
+                                         self.background_width,
+                                         self.background_height,
+                                         self.frame,
+                                         0, 0)
+
+        f = float(self.frame_num % self.CYCLE_LEN) / self.CYCLE_LEN
+
+        xmid = self.background_width / 2.0
+        ymid = self.background_height / 2.0
+        radius = min(xmid, ymid) / 2.0
+
+        r1 = Gdk.Rectangle()
+        r2 = Gdk.Rectangle()
+
+        i = 0
+        for pb in self.pixbufs:
+            i += 1
+            ang = 2.0 * math.pi * i / len(self.pixbufs) - f * 2.0 * math.pi
+
+            iw = pb.get_width()
+            ih = pb.get_height()
+
+            r = radius + (radius / 3.0) * math.sin(f * 2.0 * math.pi)
+
+            xpos = math.floor(xmid + r * math.cos(ang) - iw / 2.0 + 0.5)
+            ypos = math.floor(ymid + r * math.sin(ang) - ih / 2.0 + 0.5)
+
+            if i & 1:
+                k = math.sin(f * 2.0 * math.pi)
+            else:
+                k = math.cos(f * 2.0 * math.pi)
+
+            k = 2.0 * k * k
+            k = max(0.25, k)
+
+            r1.x = xpos
+            r1.y = ypos
+            r1.width = iw * k
+            r1.height = ih * k
+
+            r2.x = 0
+            r2.y = 0
+            r2.width = self.background_width
+            r2.height = self.background_height
+
+            success, dest = Gdk.rectangle_intersect(r1, r2)
+            if success:
+                alpha = 0
+                if i & 1:
+                    alpha = max(127, math.fabs(255 * math.sin(f * 2.0 * math.pi)))
+                else:
+                    alpha = max(127, math.fabs(255 * math.cos(f * 2.0 * math.pi)))
+
+                pb.composite(self.frame,
+                             dest.x, dest.y,
+                             dest.width, dest.height,
+                             xpos, ypos,
+                             k, k,
+                             GdkPixbuf.InterpType.NEAREST,
+                             alpha)
+
+        self.da.queue_draw()
+
+        self.frame_num += 1
+        return True
+
+    def cleanup_cb(self, widget):
+        GLib.source_remove(self.timeout_id)
+        Gtk.main_quit()
+
+
+def main(demoapp=None):
+    PixbufApp()
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/printing.py b/examples/demo/demos/printing.py
new file mode 100644 (file)
index 0000000..6e68037
--- /dev/null
@@ -0,0 +1,180 @@
+#!/usr/bin/env python
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.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.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 St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Printing"
+description = """
+GtkPrintOperation offers a simple API to support printing in a cross-platform way.
+"""
+
+from gi.repository import Gtk, GLib, Pango, PangoCairo
+import math
+import os
+
+
+class PrintingApp:
+    HEADER_HEIGHT = 10 * 72 / 25.4
+    HEADER_GAP = 3 * 72 / 25.4
+
+    def __init__(self):
+        self.operation = Gtk.PrintOperation()
+        print_data = {'filename': os.path.abspath(__file__),
+                      'font_size': 12.0,
+                      'lines_per_page': 0,
+                      'lines': None,
+                      'num_lines': 0,
+                      'num_pages': 0
+                     }
+
+        self.operation.connect('begin-print', self.begin_print, print_data)
+        self.operation.connect('draw-page', self.draw_page, print_data)
+        self.operation.connect('end-print', self.end_print, print_data)
+
+        self.operation.set_use_full_page(False)
+        self.operation.set_unit(Gtk.Unit.POINTS)
+        self.operation.set_embed_page_setup(True)
+
+        settings = Gtk.PrintSettings()
+
+        dir = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_DOCUMENTS)
+        if dir is None:
+            dir = GLib.get_home_dir()
+        if settings.get(Gtk.PRINT_SETTINGS_OUTPUT_FILE_FORMAT) == 'ps':
+            ext = '.ps'
+        elif settings.get(Gtk.PRINT_SETTINGS_OUTPUT_FILE_FORMAT) == 'svg':
+            ext = '.svg'
+        else:
+            ext = '.pdf'
+
+        uri = "file://%s/gtk-demo%s" % (dir, ext)
+        settings.set(Gtk.PRINT_SETTINGS_OUTPUT_URI, uri)
+        self.operation.set_print_settings(settings)
+
+    def run(self, parent=None):
+        result = self.operation.run(Gtk.PrintOperationAction.PRINT_DIALOG, parent)
+
+        if result == Gtk.PrintOperationResult.ERROR:
+            message = self.operation.get_error()
+
+            dialog = Gtk.MessageDialog(parent,
+                                       0,
+                                       Gtk.MessageType.ERROR,
+                                       Gtk.ButtonsType.CLOSE,
+                                       message)
+
+            dialog.run()
+            dialog.destroy()
+
+        Gtk.main_quit()
+
+    def begin_print(self, operation, print_ctx, print_data):
+        height = print_ctx.get_height() - self.HEADER_HEIGHT - self.HEADER_GAP
+        print_data['lines_per_page'] = \
+            math.floor(height / print_data['font_size'])
+
+        file_path = print_data['filename']
+        if not os.path.isfile(file_path):
+            file_path = os.path.join('demos', file_path)
+            if not os.path.isfile:
+                raise Exception("file not found: " % (file_path, ))
+
+        # in reality you should most likely not read the entire
+        # file into a buffer
+        source_file = open(file_path, 'r')
+        s = source_file.read()
+        print_data['lines'] = s.split('\n')
+
+        print_data['num_lines'] = len(print_data['lines'])
+        print_data['num_pages'] = \
+            (print_data['num_lines'] - 1) / print_data['lines_per_page'] + 1
+
+        operation.set_n_pages(print_data['num_pages'])
+
+    def draw_page(self, operation, print_ctx, page_num, print_data):
+        cr = print_ctx.get_cairo_context()
+        width = print_ctx.get_width()
+
+        cr.rectangle(0, 0, width, self.HEADER_HEIGHT)
+        cr.set_source_rgb(0.8, 0.8, 0.8)
+        cr.fill_preserve()
+
+        cr.set_source_rgb(0, 0, 0)
+        cr.set_line_width(1)
+        cr.stroke()
+
+        layout = print_ctx.create_pango_layout()
+        desc = Pango.FontDescription('sans 14')
+        layout.set_font_description(desc)
+
+        layout.set_text(print_data['filename'], -1)
+        (text_width, text_height) = layout.get_pixel_size()
+
+        if text_width > width:
+            layout.set_width(width)
+            layout.set_ellipsize(Pango.EllipsizeMode.START)
+            (text_width, text_height) = layout.get_pixel_size()
+
+        cr.move_to((width - text_width) / 2,
+                   (self.HEADER_HEIGHT - text_height) / 2)
+        PangoCairo.show_layout(cr, layout)
+
+        page_str = "%d/%d" % (page_num + 1, print_data['num_pages'])
+        layout.set_text(page_str, -1)
+
+        layout.set_width(-1)
+        (text_width, text_height) = layout.get_pixel_size()
+        cr.move_to(width - text_width - 4,
+                   (self.HEADER_HEIGHT - text_height) / 2)
+        PangoCairo.show_layout(cr, layout)
+
+        layout = print_ctx.create_pango_layout()
+
+        desc = Pango.FontDescription('monospace')
+        desc.set_size(print_data['font_size'] * Pango.SCALE)
+        layout.set_font_description(desc)
+
+        cr.move_to(0, self.HEADER_HEIGHT + self.HEADER_GAP)
+        lines_pp = int(print_data['lines_per_page'])
+        num_lines = print_data['num_lines']
+        data_lines = print_data['lines']
+        font_size = print_data['font_size']
+        line = page_num * lines_pp
+
+        for i in range(lines_pp):
+            if line >= num_lines:
+                break
+
+            layout.set_text(data_lines[line], -1)
+            PangoCairo.show_layout(cr, layout)
+            cr.rel_move_to(0, font_size)
+            line += 1
+
+    def end_print(self, operation, print_ctx, print_data):
+        pass
+
+
+def main(demoapp=None):
+    app = PrintingApp()
+    GLib.idle_add(app.run, demoapp.window)
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/rotatedtext.py b/examples/demo/demos/rotatedtext.py
new file mode 100644 (file)
index 0000000..d47b1cf
--- /dev/null
@@ -0,0 +1,199 @@
+#!/usr/bin/env python
+# coding=utf-8
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+#
+# Copyright (C) 2010 Red Hat, Inc., John (J5) Palmieri <johnp@redhat.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.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 St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+title = "Rotated Text"
+description = """This demo shows how to use PangoCairo to draw rotated and
+transformed text.  The right pane shows a rotated GtkLabel widget. In both
+cases, a custom PangoCairo shape renderer is installed to draw a red heard using
+cairo drawing operations instead of the Unicode heart character.
+"""
+
+from gi.repository import Gtk, Pango, PangoCairo, Gdk
+import cairo
+import sys
+import math
+
+# Python 2 and 3 handle UTF8 differently
+if sys.version_info < (3, 0):
+    BYTES_TEXT = "I \xe2\x99\xa5 GTK+"
+    UTF8_TEXT = unicode(BYTES_TEXT, 'UTF-8')
+    BYTES_HEART = "\xe2\x99\xa5"
+    HEART = unicode(BYTES_HEART, 'UTF-8')
+else:
+    UTF8_TEXT = "I ♥ GTK+"
+    BYTES_TEXT = bytes(UTF8_TEXT, 'utf-8')
+    HEART = "♥"
+    BYTES_HEART = bytes(HEART, 'utf-8')
+
+
+class RotatedTextApp:
+    RADIUS = 150
+    N_WORDS = 5
+    FONT = "Serif 18"
+
+    def __init__(self):
+
+        white = Gdk.RGBA()
+        white.red = 1.0
+        white.green = 1.0
+        white.blue = 1.0
+        white.alpha = 1.0
+
+        self.window = Gtk.Window(title="Rotated Text")
+        self.window.set_default_size(4 * self.RADIUS, 2 * self.RADIUS)
+        self.window.connect('destroy', Gtk.main_quit)
+
+        box = Gtk.HBox()
+        box.set_homogeneous(True)
+        self.window.add(box)
+
+        # add a drawing area
+        da = Gtk.DrawingArea()
+        box.add(da)
+
+        # override the background color from the theme
+        da.override_background_color(0, white)
+
+        da.connect('draw', self.rotated_text_draw)
+
+        label = Gtk.Label(label=UTF8_TEXT)
+        box.add(label)
+        label.set_angle(45)
+
+        # Setup some fancy stuff on the label
+        layout = label.get_layout()
+
+        PangoCairo.context_set_shape_renderer(layout.get_context(),
+                                              self.fancy_shape_renderer,
+                                              None)
+        attrs = self.create_fancy_attr_list_for_layout(layout)
+        label.set_attributes(attrs)
+
+        self.window.show_all()
+
+    def fancy_shape_renderer(self, cairo_ctx, attr, do_path):
+        x, y = cairo_ctx.get_current_point()
+        cairo_ctx.translate(x, y)
+
+        cairo_ctx.scale(float(attr.inc_rect.width) / Pango.SCALE,
+                        float(attr.inc_rect.height) / Pango.SCALE)
+
+        if int(attr.data) == 0x2665:  # U+2665 BLACK HEART SUIT
+            cairo_ctx.move_to(0.5, 0.0)
+            cairo_ctx.line_to(0.9, -0.4)
+            cairo_ctx.curve_to(1.1, -0.8, 0.5, -0.9, 0.5, -0.5)
+            cairo_ctx.curve_to(0.5, -0.9, -0.1, -0.8, 0.1, -0.4)
+            cairo_ctx.close_path()
+
+        if not do_path:
+            cairo_ctx.set_source_rgb(1.0, 0.0, 0.0)
+            cairo_ctx.fill()
+
+    def create_fancy_attr_list_for_layout(self, layout):
+        pango_ctx = layout.get_context()
+        metrics = pango_ctx.get_metrics(layout.get_font_description(),
+                                        None)
+        ascent = metrics.get_ascent()
+
+        logical_rect = Pango.Rectangle()
+        logical_rect.x = 0
+        logical_rect.width = ascent
+        logical_rect.y = -ascent
+        logical_rect.height = ascent
+
+        # Set fancy shape attributes for all hearts
+        attrs = Pango.AttrList()
+
+        # FIXME: attr_shape_new_with_data isn't introspectable
+        '''
+        ink_rect = logical_rect
+        p = BYTES_TEXT.find(BYTES_HEART)
+        while (p != -1):
+            attr = Pango.attr_shape_new_with_data(ink_rect,
+                                                  logical_rect,
+                                                  ord(HEART),
+                                                  None)
+            attr.start_index = p
+            attr.end_index = p + len(BYTES_HEART)
+            p = UTF8_TEXT.find(HEART, attr.end_index)
+
+        attrs.insert(attr)
+        '''
+        return attrs
+
+    def rotated_text_draw(self, da, cairo_ctx):
+        # Create a cairo context and set up a transformation matrix so that the user
+        # space coordinates for the centered square where we draw are [-RADIUS, RADIUS],
+        # [-RADIUS, RADIUS].
+        # We first center, then change the scale.
+        width = da.get_allocated_width()
+        height = da.get_allocated_height()
+        device_radius = min(width, height) / 2.0
+        cairo_ctx.translate(
+            device_radius + (width - 2 * device_radius) / 2,
+            device_radius + (height - 2 * device_radius) / 2)
+        cairo_ctx.scale(device_radius / self.RADIUS,
+                        device_radius / self.RADIUS)
+
+        # Create a subtle gradient source and use it.
+        pattern = cairo.LinearGradient(-self.RADIUS, -self.RADIUS, self.RADIUS, self.RADIUS)
+        pattern.add_color_stop_rgb(0.0, 0.5, 0.0, 0.0)
+        pattern.add_color_stop_rgb(1.0, 0.0, 0.0, 0.5)
+        cairo_ctx.set_source(pattern)
+
+        # Create a PangoContext and set up our shape renderer
+        context = da.create_pango_context()
+        PangoCairo.context_set_shape_renderer(context,
+                                              self.fancy_shape_renderer,
+                                              None)
+
+        # Create a PangoLayout, set the text, font, and attributes */
+        layout = Pango.Layout(context=context)
+        layout.set_text(UTF8_TEXT, -1)
+        desc = Pango.FontDescription(self.FONT)
+        layout.set_font_description(desc)
+
+        attrs = self.create_fancy_attr_list_for_layout(layout)
+        layout.set_attributes(attrs)
+
+        # Draw the layout N_WORDS times in a circle */
+        for i in range(self.N_WORDS):
+            # Inform Pango to re-layout the text with the new transformation matrix
+            PangoCairo.update_layout(cairo_ctx, layout)
+
+            width, height = layout.get_pixel_size()
+            cairo_ctx.move_to(-width / 2, -self.RADIUS * 0.9)
+            PangoCairo.show_layout(cairo_ctx, layout)
+
+            # Rotate for the next turn
+            cairo_ctx.rotate(math.pi * 2 / self.N_WORDS)
+
+        return False
+
+
+def main(demoapp=None):
+    RotatedTextApp()
+    Gtk.main()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/examples/demo/demos/test.py b/examples/demo/demos/test.py
new file mode 100644 (file)
index 0000000..4bd7684
--- /dev/null
@@ -0,0 +1,16 @@
+title = "Test Demo"
+description = "Dude this is a test"
+
+
+from gi.repository import Gtk
+
+
+def _quit(*args):
+    Gtk.main_quit()
+
+
+def main(demoapp=None):
+    window = Gtk.Window()
+    window.connect_after('destroy', _quit)
+    window.show_all()
+    Gtk.main()
diff --git a/gi/_ossighelper.py b/gi/_ossighelper.py
new file mode 100644 (file)
index 0000000..2c72134
--- /dev/null
@@ -0,0 +1,263 @@
+# -*- coding: utf-8 -*-
+# Copyright 2017 Christoph Reiter
+#
+# 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, see <http://www.gnu.org/licenses/>.
+
+from __future__ import print_function
+
+import os
+import sys
+import socket
+import signal
+import ctypes
+import threading
+from contextlib import closing, contextmanager
+
+
+def ensure_socket_not_inheritable(sock):
+    """Ensures that the socket is not inherited by child processes
+
+    Raises:
+        EnvironmentError
+        NotImplementedError: With Python <3.4 on Windows
+    """
+
+    if hasattr(sock, "set_inheritable"):
+        sock.set_inheritable(False)
+    else:
+        try:
+            import fcntl
+        except ImportError:
+            raise NotImplementedError(
+                "Not implemented for older Python on Windows")
+        else:
+            fd = sock.fileno()
+            flags = fcntl.fcntl(fd, fcntl.F_GETFD)
+            fcntl.fcntl(fd, fcntl.F_SETFD, flags | fcntl.FD_CLOEXEC)
+
+
+_wakeup_fd_is_active = False
+"""Since we can't check if set_wakeup_fd() is already used for nested event
+loops without introducing a race condition we keep track of it globally.
+"""
+
+
+@contextmanager
+def wakeup_on_signal():
+    """A decorator for functions which create a glib event loop to keep
+    Python signal handlers working while the event loop is idling.
+
+    In case an OS signal is received will wake the default event loop up
+    shortly so that any registered Python signal handlers registered through
+    signal.signal() can run.
+
+    Works on Windows but needs Python 3.5+.
+
+    In case the wrapped function is not called from the main thread it will be
+    called as is and it will not wake up the default loop for signals.
+    """
+
+    global _wakeup_fd_is_active
+
+    if _wakeup_fd_is_active:
+        yield
+        return
+
+    from gi.repository import GLib
+
+    # On Windows only Python 3.5+ supports passing sockets to set_wakeup_fd
+    set_wakeup_fd_supports_socket = (
+        os.name != "nt" or sys.version_info[:2] >= (3, 5))
+    # On Windows only Python 3 has an implementation of socketpair()
+    has_socketpair = hasattr(socket, "socketpair")
+
+    if not has_socketpair or not set_wakeup_fd_supports_socket:
+        yield
+        return
+
+    read_socket, write_socket = socket.socketpair()
+    with closing(read_socket), closing(write_socket):
+
+        for sock in [read_socket, write_socket]:
+            sock.setblocking(False)
+            ensure_socket_not_inheritable(sock)
+
+        try:
+            orig_fd = signal.set_wakeup_fd(write_socket.fileno())
+        except ValueError:
+            # Raised in case this is not the main thread -> give up.
+            yield
+            return
+        else:
+            _wakeup_fd_is_active = True
+
+        def signal_notify(source, condition):
+            if condition & GLib.IO_IN:
+                try:
+                    return bool(read_socket.recv(1))
+                except EnvironmentError as e:
+                    print(e)
+                    return False
+                return True
+            else:
+                return False
+
+        try:
+            if os.name == "nt":
+                channel = GLib.IOChannel.win32_new_socket(
+                    read_socket.fileno())
+            else:
+                channel = GLib.IOChannel.unix_new(read_socket.fileno())
+
+            source_id = GLib.io_add_watch(
+                channel,
+                GLib.PRIORITY_DEFAULT,
+                (GLib.IOCondition.IN | GLib.IOCondition.HUP |
+                 GLib.IOCondition.NVAL | GLib.IOCondition.ERR),
+                signal_notify)
+            try:
+                yield
+            finally:
+                GLib.source_remove(source_id)
+        finally:
+            write_fd = signal.set_wakeup_fd(orig_fd)
+            if write_fd != write_socket.fileno():
+                # Someone has called set_wakeup_fd while func() was active,
+                # so let's re-revert again.
+                signal.set_wakeup_fd(write_fd)
+            _wakeup_fd_is_active = False
+
+
+def create_pythonapi():
+    # We need our own instance of ctypes.pythonapi so we don't modify the
+    # global shared one. Adapted from the ctypes source.
+    if os.name == "nt":
+        return ctypes.PyDLL("python dll", None, sys.dllhandle)
+    elif sys.platform == "cygwin":
+        return ctypes.PyDLL("libpython%d.%d.dll" % sys.version_info[:2])
+    else:
+        return ctypes.PyDLL(None)
+
+
+pydll = create_pythonapi()
+PyOS_getsig = pydll.PyOS_getsig
+PyOS_getsig.restype = ctypes.c_void_p
+PyOS_getsig.argtypes = [ctypes.c_int]
+
+# We save the signal pointer so we can detect if glib has changed the
+# signal handler behind Python's back (GLib.unix_signal_add)
+if signal.getsignal(signal.SIGINT) is signal.default_int_handler:
+    startup_sigint_ptr = PyOS_getsig(signal.SIGINT)
+else:
+    # Something has set the handler before import, we can't get a ptr
+    # for the default handler so make sure the pointer will never match.
+    startup_sigint_ptr = -1
+
+
+def sigint_handler_is_default():
+    """Returns if on SIGINT the default Python handler would be called"""
+
+    return (signal.getsignal(signal.SIGINT) is signal.default_int_handler and
+            PyOS_getsig(signal.SIGINT) == startup_sigint_ptr)
+
+
+@contextmanager
+def sigint_handler_set_and_restore_default(handler):
+    """Context manager for saving/restoring the SIGINT handler default state.
+
+    Will only restore the default handler again if the handler is not changed
+    while the context is active.
+    """
+
+    assert sigint_handler_is_default()
+
+    signal.signal(signal.SIGINT, handler)
+    sig_ptr = PyOS_getsig(signal.SIGINT)
+    try:
+        yield
+    finally:
+        if signal.getsignal(signal.SIGINT) is handler and \
+                PyOS_getsig(signal.SIGINT) == sig_ptr:
+            signal.signal(signal.SIGINT, signal.default_int_handler)
+
+
+def is_main_thread():
+    """Returns True in case the function is called from the main thread"""
+
+    return threading.current_thread().name == "MainThread"
+
+
+_callback_stack = []
+_sigint_called = False
+
+
+@contextmanager
+def register_sigint_fallback(callback):
+    """Installs a SIGINT signal handler in case the default Python one is
+    active which calls 'callback' in case the signal occurs.
+
+    Only does something if called from the main thread.
+
+    In case of nested context managers the signal handler will be only
+    installed once and the callbacks will be called in the reverse order
+    of their registration.
+
+    The old signal handler will be restored in case no signal handler is
+    registered while the context is active.
+    """
+
+    # To handle multiple levels of event loops we need to call the last
+    # callback first, wait until the inner most event loop returns control
+    # and only then call the next callback, and so on... until we
+    # reach the outer most which manages the signal handler and raises
+    # in the end
+
+    global _callback_stack, _sigint_called
+
+    if not is_main_thread():
+        yield
+        return
+
+    if not sigint_handler_is_default():
+        if _callback_stack:
+            # This is an inner event loop, append our callback
+            # to the stack so the parent context can call it.
+            _callback_stack.append(callback)
+            try:
+                yield
+            finally:
+                if _sigint_called:
+                    _callback_stack.pop()()
+        else:
+            # There is a signal handler set by the user, just do nothing
+            yield
+        return
+
+    _sigint_called = False
+
+    def sigint_handler(sig_num, frame):
+        global _callback_stack, _sigint_called
+
+        if _sigint_called:
+            return
+        _sigint_called = True
+        _callback_stack.pop()()
+
+    _callback_stack.append(callback)
+    with sigint_handler_set_and_restore_default(sigint_handler):
+        try:
+            yield
+        finally:
+            if _sigint_called:
+                signal.default_int_handler()
index 372d6d4..52b6af3 100644 (file)
 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
 # USA
 
-import signal
 import warnings
 import sys
 import socket
 
+from .._ossighelper import wakeup_on_signal, register_sigint_fallback
 from ..module import get_introspection_module
 from .._gi import (variant_type_from_string, source_new,
                    source_set_callback, io_channel_read)
@@ -560,32 +560,13 @@ class MainLoop(GLib.MainLoop):
     def __new__(cls, context=None):
         return GLib.MainLoop.new(context, False)
 
-    # Retain classic pygobject behaviour of quitting main loops on SIGINT
     def __init__(self, context=None):
-        def _handler(loop):
-            loop.quit()
-            loop._quit_by_sigint = True
-            # We handle signal deletion in __del__, return True so GLib
-            # doesn't do the deletion for us.
-            return True
-
-        if sys.platform != 'win32':
-            # compatibility shim, keep around until we depend on glib 2.36
-            if hasattr(GLib, 'unix_signal_add'):
-                fn = GLib.unix_signal_add
-            else:
-                fn = GLib.unix_signal_add_full
-            self._signal_source = fn(GLib.PRIORITY_DEFAULT, signal.SIGINT, _handler, self)
-
-    def __del__(self):
-        if hasattr(self, '_signal_source'):
-            GLib.source_remove(self._signal_source)
+        pass
 
     def run(self):
-        super(MainLoop, self).run()
-        if hasattr(self, '_quit_by_sigint'):
-            # caught by _main_loop_sigint_handler()
-            raise KeyboardInterrupt
+        with register_sigint_fallback(self.quit):
+            with wakeup_on_signal():
+                super(MainLoop, self).run()
 
 
 MainLoop = override(MainLoop)
index cdb3ccb..5ab23fc 100644 (file)
@@ -20,6 +20,7 @@
 
 import warnings
 
+from .._ossighelper import wakeup_on_signal, register_sigint_fallback
 from ..overrides import override, deprecated_init
 from ..module import get_introspection_module
 from gi import PyGIWarning
@@ -33,6 +34,18 @@ Gio = get_introspection_module('Gio')
 __all__ = []
 
 
+class Application(Gio.Application):
+
+    def run(self, *args, **kwargs):
+        with register_sigint_fallback(self.quit):
+            with wakeup_on_signal():
+                return Gio.Application.run(self, *args, **kwargs)
+
+
+Application = override(Application)
+__all__.append('Application')
+
+
 class VolumeMonitor(Gio.VolumeMonitor):
 
     def __init__(self, *args, **kwargs):
index 08d2612..c495fd1 100644 (file)
@@ -24,6 +24,7 @@ import sys
 import warnings
 
 from gi.repository import GObject
+from .._ossighelper import wakeup_on_signal, register_sigint_fallback
 from ..overrides import override, strip_boolean_result, deprecated_init
 from ..module import get_introspection_module
 from gi import PyGIDeprecationWarning
@@ -543,6 +544,11 @@ class Dialog(Gtk.Dialog, Container):
         if add_buttons:
             self.add_buttons(*add_buttons)
 
+    def run(self, *args, **kwargs):
+        with register_sigint_fallback(self.destroy):
+            with wakeup_on_signal():
+                return Gtk.Dialog.run(self, *args, **kwargs)
+
     action_area = property(lambda dialog: dialog.get_action_area())
     vbox = property(lambda dialog: dialog.get_content_area())
 
@@ -1007,28 +1013,30 @@ class ListStore(Gtk.ListStore, TreeModel, TreeSortable):
         Gtk.ListStore.set_value(self, treeiter, column, value)
 
     def set(self, treeiter, *args):
-
-        def _set_lists(columns, values):
-            if len(columns) != len(values):
+        def _set_lists(cols, vals):
+            if len(cols) != len(vals):
                 raise TypeError('The number of columns do not match the number of values')
-            for col_num, val in zip(columns, values):
+
+            columns = []
+            values = []
+            for col_num, value in zip(cols, vals):
                 if not isinstance(col_num, int):
                     raise TypeError('TypeError: Expected integer argument for column.')
-                self.set_value(treeiter, col_num, val)
+
+                columns.append(col_num)
+                values.append(self._convert_value(col_num, value))
+
+            Gtk.ListStore.set(self, treeiter, columns, values)
 
         if args:
             if isinstance(args[0], int):
-                columns = args[::2]
-                values = args[1::2]
-                _set_lists(columns, values)
+                _set_lists(args[::2], args[1::2])
             elif isinstance(args[0], (tuple, list)):
                 if len(args) != 2:
                     raise TypeError('Too many arguments')
                 _set_lists(args[0], args[1])
             elif isinstance(args[0], dict):
-                columns = args[0].keys()
-                values = args[0].values()
-                _set_lists(columns, values)
+                _set_lists(list(args[0]), args[0].values())
             else:
                 raise TypeError('Argument list must be in the form of (column, value, ...), ((columns,...), (values, ...)) or {column: value}.  No -1 termination is needed.')
 
@@ -1269,28 +1277,30 @@ class TreeStore(Gtk.TreeStore, TreeModel, TreeSortable):
         Gtk.TreeStore.set_value(self, treeiter, column, value)
 
     def set(self, treeiter, *args):
-
-        def _set_lists(columns, values):
-            if len(columns) != len(values):
+        def _set_lists(cols, vals):
+            if len(cols) != len(vals):
                 raise TypeError('The number of columns do not match the number of values')
-            for col_num, val in zip(columns, values):
+
+            columns = []
+            values = []
+            for col_num, value in zip(cols, vals):
                 if not isinstance(col_num, int):
                     raise TypeError('TypeError: Expected integer argument for column.')
-                self.set_value(treeiter, col_num, val)
+
+                columns.append(col_num)
+                values.append(self._convert_value(col_num, value))
+
+            Gtk.TreeStore.set(self, treeiter, columns, values)
 
         if args:
             if isinstance(args[0], int):
-                columns = args[::2]
-                values = args[1::2]
-                _set_lists(columns, values)
+                _set_lists(args[::2], args[1::2])
             elif isinstance(args[0], (tuple, list)):
                 if len(args) != 2:
                     raise TypeError('Too many arguments')
                 _set_lists(args[0], args[1])
             elif isinstance(args[0], dict):
-                columns = args[0].keys()
-                values = args[0].values()
-                _set_lists(columns, values)
+                _set_lists(args[0].keys(), args[0].values())
             else:
                 raise TypeError('Argument list must be in the form of (column, value, ...), ((columns,...), (values, ...)) or {column: value}.  No -1 termination is needed.')
 
@@ -1590,6 +1600,16 @@ def main_quit(*args):
     _Gtk_main_quit()
 
 
+_Gtk_main = Gtk.main
+
+
+@override(Gtk.main)
+def main(*args, **kwargs):
+    with register_sigint_fallback(Gtk.main_quit):
+        with wakeup_on_signal():
+            return _Gtk_main(*args, **kwargs)
+
+
 if Gtk._version in ("2.0", "3.0"):
     stock_lookup = strip_boolean_result(Gtk.stock_lookup)
     __all__.append('stock_lookup')
index 6484f03..0934a44 100644 (file)
@@ -21,7 +21,7 @@
 #   Test also for gcov program and create GCOV variable that could be
 #   substituted.
 #
-#   Note that all optimization flags in CFLAGS must be disabled when code
+#   Note that all optimisation flags in CFLAGS must be disabled when code
 #   coverage is enabled.
 #
 #   Usage example:
@@ -75,7 +75,7 @@
 #   You should have received a copy of the GNU Lesser General Public License
 #   along with this program. If not, see <https://www.gnu.org/licenses/>.
 
-#serial 25
+#serial 21
 
 AC_DEFUN([AX_CODE_COVERAGE],[
        dnl Check for --enable-code-coverage
@@ -218,12 +218,9 @@ CODE_COVERAGE_LCOV_RMOPTS ?= $(CODE_COVERAGE_LCOV_RMOPTS_DEFAULT)
 CODE_COVERAGE_GENHTML_OPTIONS_DEFAULT ?=\
 $(if $(CODE_COVERAGE_BRANCH_COVERAGE),\
 --rc genhtml_branch_coverage=$(CODE_COVERAGE_BRANCH_COVERAGE))
-CODE_COVERAGE_GENHTML_OPTIONS ?= $(CODE_COVERAGE_GENHTML_OPTIONS_DEFAULT)
+CODE_COVERAGE_GENHTML_OPTIONS ?= $(CODE_COVERAGE_GENHTML_OPTIONS_DEFAULTS)
 CODE_COVERAGE_IGNORE_PATTERN ?=
 
-GITIGNOREFILES ?=
-GITIGNOREFILES += $(CODE_COVERAGE_OUTPUT_FILE) $(CODE_COVERAGE_OUTPUT_DIRECTORY)
-
 code_coverage_v_lcov_cap = $(code_coverage_v_lcov_cap_$(V))
 code_coverage_v_lcov_cap_ = $(code_coverage_v_lcov_cap_$(AM_DEFAULT_VERBOSITY))
 code_coverage_v_lcov_cap_0 = @echo "  LCOV   --capture"\
@@ -253,6 +250,9 @@ code-coverage-capture-hook:
 
 '"$CODE_COVERAGE_RULES_CLEAN"'
 
+GITIGNOREFILES ?=
+GITIGNOREFILES += $(CODE_COVERAGE_OUTPUT_FILE) $(CODE_COVERAGE_OUTPUT_DIRECTORY)
+
 A''M_DISTCHECK_CONFIGURE_FLAGS ?=
 A''M_DISTCHECK_CONFIGURE_FLAGS += --disable-code-coverage
 
index 9767e6a..aeb16e3 100644 (file)
 # LICENSE
 #
 #   Copyright (c) 2014, 2015 Philip Withnall <philip@tecnocode.co.uk>
-#   Copyright (c) 2017 Reini Urban <rurban@cpan.org>
 #
 #   Copying and distribution of this file, with or without modification, are
 #   permitted in any medium without royalty provided the copyright notice
 #   and this notice are preserved.  This file is offered as-is, without any
 #   warranty.
 
-#serial 15
+#serial 14
 
 AC_DEFUN([AX_COMPILER_FLAGS_CFLAGS],[
     AC_REQUIRE([AC_PROG_SED])
@@ -101,13 +100,6 @@ AC_DEFUN([AX_COMPILER_FLAGS_CFLAGS],[
             -Wreturn-type dnl
             -Wswitch-enum dnl
             -Wswitch-default dnl
-            -Wduplicated-cond dnl
-            -Wduplicated-branches dnl
-            -Wlogical-op dnl
-            -Wrestrict dnl
-            -Wnull-dereference dnl
-            -Wjump-misses-init dnl
-            -Wdouble-promotion dnl
             $4 dnl
             $5 dnl
             $6 dnl
index 4b19019..d8f03d4 100644 (file)
@@ -1,9 +1,6 @@
 # Configure paths for GLIB
 # Owen Taylor     1997-2001
 
-# Increment this whenever this file is changed.
-#serial 1
-
 dnl AM_PATH_GLIB_2_0([MINIMUM-VERSION, [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND [, MODULES]]]])
 dnl Test for GLIB, and define GLIB_CFLAGS and GLIB_LIBS, if gmodule, gobject,
 dnl gthread, or gio is specified in MODULES, pass to pkg-config
@@ -93,7 +90,7 @@ dnl
 #include <stdlib.h>
 
 int 
-main (void)
+main ()
 {
   unsigned int major, minor, micro;
 
diff --git a/pygobject-3.0-uninstalled.pc.in b/pygobject-3.0-uninstalled.pc.in
deleted file mode 100644 (file)
index 4cec178..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-# you can use the --variable=pygobjectincludedir argument to
-# pkg-config to get this value. You might want to use this to
-# install additional headers.
-pygobjectincludedir=${pcfiledir}/gi/_gobject
-overridesdir=${pcfiledir}/gi/overrides
-
-Name: PyGObject
-Description: Python bindings for GObject
-Requires: gobject-2.0
-Requires.private: @LIBFFI_PC@
-Version: @VERSION@
-Cflags: -I${pcfiledir}/gi/_gobject
index d366a2a..63f184f 100644 (file)
@@ -64,4 +64,11 @@ PyGObject now dynamically accesses any GObject libraries that uses GObject Intro
       <gnome:userid>sfeltman</gnome:userid>
     </foaf:Person>
   </maintainer>
+  <maintainer>
+    <foaf:Person>
+      <foaf:name>Christoph Reiter</foaf:name>
+      <foaf:mbox rdf:resource="mailto:creiter@src.gnome.org" />
+      <gnome:userid>creiter</gnome:userid>
+    </foaf:Person>
+  </maintainer>
 </Project>
old mode 100755 (executable)
new mode 100644 (file)
index 0da9ed9..af3aa5c
--- a/setup.py
+++ b/setup.py
 #!/usr/bin/env python
+# Copyright 2017 Christoph Reiter <reiter.christoph@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.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 St, Fifth Floor, Boston, MA  02110-1301
+# USA
 
+"""
+ATTENTION DISTRO PACKAGERS: This is not a valid replacement for autotools.
+It does not install headers, pkgconfig files and does not support running
+tests. Its main use case atm is installation in virtualenvs and via pip.
+"""
 
+import io
 import os
 import re
-import subprocess
 import sys
+import errno
+import subprocess
+import tarfile
+from email import parser
+
+from setuptools import setup, find_packages
+from distutils.core import Extension, Distribution
+from distutils.ccompiler import new_compiler
+from distutils import dir_util
+
+
+def get_command_class(name):
+    # Returns the right class for either distutils or setuptools
+    return Distribution({}).get_command_class(name)
+
+
+def get_pycairo_pkg_config_name():
+    return "py3cairo" if sys.version_info[0] == 3 else "pycairo"
+
 
-from distutils.command.build import build as orig_build
-from setuptools.command.build_ext import build_ext as orig_build_ext
-from setuptools.command.build_py import build_py as orig_build_py
-from setuptools import setup, Extension
+def get_version_requirement(conf_dir, pkg_config_name):
+    """Given a pkg-config module name gets the minimum version required"""
 
+    if pkg_config_name in ["cairo", "cairo-gobject"]:
+        return "0"
 
-with open("configure.ac", "r") as h:
-    version = ".".join(re.findall("pygobject_[^\s]+_version,\s*(\d+)\)", h.read()))
+    mapping = {
+        "gobject-introspection-1.0": "introspection",
+        "glib-2.0": "glib",
+        "gio-2.0": "gio",
+        get_pycairo_pkg_config_name(): "pycairo",
+        "libffi": "libffi",
+    }
+    assert pkg_config_name in mapping
 
+    configure_ac = os.path.join(conf_dir, "configure.ac")
+    with io.open(configure_ac, "r", encoding="utf-8") as h:
+        text = h.read()
+        conf_name = mapping[pkg_config_name]
+        res = re.findall(
+            r"%s_required_version,\s*([\d\.]+)\)" % conf_name, text)
+        assert len(res) == 1
+        return res[0]
 
-def makedirs(dirpath):
-    """Safely make directories
 
-    By default, os.makedirs fails if the directory already exists.
+def parse_versions(conf_dir):
+    configure_ac = os.path.join(conf_dir, "configure.ac")
+    with io.open(configure_ac, "r", encoding="utf-8") as h:
+        version = re.findall(r"pygobject_[^\s]+_version,\s*(\d+)\)", h.read())
+        assert len(version) == 3
 
-    Python 3.2 introduced the `exist_ok` argument, but we can't use it because
-    we want to keep supporting Python 2 for some time.
+    versions = {
+        "PYGOBJECT_MAJOR_VERSION": version[0],
+        "PYGOBJECT_MINOR_VERSION": version[1],
+        "PYGOBJECT_MICRO_VERSION": version[2],
+        "VERSION": ".".join(version),
+    }
+    return versions
+
+
+def parse_pkg_info(conf_dir):
+    """Returns an email.message.Message instance containing the content
+    of the PKG-INFO file. The version info is parsed from configure.ac
     """
-    import errno
 
-    try:
-        os.makedirs(dirpath)
+    versions = parse_versions(conf_dir)
+
+    pkg_info = os.path.join(conf_dir, "PKG-INFO.in")
+    with io.open(pkg_info, "r", encoding="utf-8") as h:
+        text = h.read()
+        for key, value in versions.items():
+            text = text.replace("@%s@" % key, value)
+
+    p = parser.Parser()
+    message = p.parse(io.StringIO(text))
+    return message
+
+
+def _run_pkg_config(args):
+    command = ["pkg-config"] + args
+
+    # Add $prefix/share/pkgconfig to PKG_CONFIG_PATH so we use the right
+    # pycairo in case we are in a virtualenv
+    env = dict(os.environ)
+    paths = env.get("PKG_CONFIG_PATH", "").split(os.pathsep)
+    paths = [p for p in paths if p]
+    paths.insert(0, os.path.join(sys.prefix, "share", "pkgconfig"))
+    env["PKG_CONFIG_PATH"] = os.pathsep.join(paths)
 
+    try:
+        return subprocess.check_output(command, env=env)
     except OSError as e:
-        if e.errno == errno.EEXIST:
-            return
+        if e.errno == errno.ENOENT:
+            raise SystemExit(
+                "%r not found.\nArguments: %r" % (command[0], command))
+        raise SystemExit(e)
+    except subprocess.CalledProcessError as e:
+        raise SystemExit(e)
+
+
+def pkg_config_version_check(pkg, version):
+    _run_pkg_config([
+        "--print-errors",
+        "--exists",
+        '%s >= %s' % (pkg, version),
+    ])
 
-        raise
 
+def pkg_config_parse(opt, pkg):
+    ret = _run_pkg_config([opt, pkg])
+    output = ret.decode()
+    opt = opt[-2:]
+    return [x.lstrip(opt) for x in output.split()]
 
-class Build(orig_build):
-    """Dummy version of distutils build which runs an Autotools build system
-    instead.
+
+du_sdist = get_command_class("sdist")
+
+
+class distcheck(du_sdist):
+    """Creates a tarball and does some additional sanity checks such as
+    checking if the tarballs includes all files and builds.
     """
+
+    def _check_manifest(self):
+        # make sure MANIFEST.in includes all tracked files
+        assert self.get_archive_files()
+
+        if subprocess.call(["git", "status"],
+                           stdout=subprocess.PIPE,
+                           stderr=subprocess.PIPE) != 0:
+            return
+
+        included_files = self.filelist.files
+        assert included_files
+
+        process = subprocess.Popen(
+            ["git", "ls-tree", "-r", "HEAD", "--name-only"],
+            stdout=subprocess.PIPE, universal_newlines=True)
+        out, err = process.communicate()
+        assert process.returncode == 0
+
+        tracked_files = out.splitlines()
+        for ignore in [".gitignore"]:
+            tracked_files.remove(ignore)
+
+        diff = set(tracked_files) - set(included_files)
+        assert not diff, (
+            "Not all tracked files included in tarball, check MANIFEST.in",
+            diff)
+
+    def _check_dist(self):
+        # make sure the tarball builds
+        assert self.get_archive_files()
+
+        distcheck_dir = os.path.join(self.dist_dir, "distcheck")
+        if os.path.exists(distcheck_dir):
+            dir_util.remove_tree(distcheck_dir)
+        self.mkpath(distcheck_dir)
+
+        archive = self.get_archive_files()[0]
+        tfile = tarfile.open(archive, "r:gz")
+        tfile.extractall(distcheck_dir)
+        tfile.close()
+
+        name = self.distribution.get_fullname()
+        extract_dir = os.path.join(distcheck_dir, name)
+
+        old_pwd = os.getcwd()
+        os.chdir(extract_dir)
+        try:
+            self.spawn([sys.executable, "setup.py", "build"])
+            self.spawn([sys.executable, "setup.py", "install",
+                        "--root", "../prefix", "--record", "../log.txt"])
+        finally:
+            os.chdir(old_pwd)
+
     def run(self):
-        srcdir = os.getcwd()
-        builddir = os.path.join(srcdir, self.build_temp)
-        makedirs(builddir)
-        configure = os.path.join(srcdir, 'configure')
-
-        if not os.path.exists(configure):
-            configure = os.path.join(srcdir, 'autogen.sh')
-
-        subprocess.check_call([
-                configure,
-                'PYTHON=%s' % sys.executable,
-                # Put the documentation, etc. out of the way: we only want
-                # the Python code and extensions
-                '--prefix=' + os.path.join(builddir, 'prefix'),
-            ],
-            cwd=builddir)
-        make_args = [
-            'pythondir=%s' % os.path.join(srcdir, self.build_lib),
-            'pyexecdir=%s' % os.path.join(srcdir, self.build_lib),
-        ]
-        subprocess.check_call(['make', '-C', builddir] + make_args)
-        subprocess.check_call(['make', '-C', builddir, 'install'] + make_args)
-
-
-class BuildExt(orig_build_ext):
-    def run(self):
-        pass
+        du_sdist.run(self)
+        self._check_manifest()
+        self._check_dist()
+
+
+du_build_ext = get_command_class("build_ext")
+
+
+class build_ext(du_build_ext):
+
+    def initialize_options(self):
+        du_build_ext.initialize_options(self)
+        self.compiler_type = None
+
+    def finalize_options(self):
+        du_build_ext.finalize_options(self)
+        self.compiler_type = new_compiler(compiler=self.compiler).compiler_type
+
+    def _write_config_h(self):
+        script_dir = os.path.dirname(os.path.realpath(__file__))
+        target = os.path.join(script_dir, "config.h")
+        versions = parse_versions(script_dir)
+        with io.open(target, 'w', encoding="utf-8") as h:
+            h.write("""
+/* Configuration header created by setup.py - do not edit */
+#ifndef _CONFIG_H
+#define _CONFIG_H 1
+
+#define PYGOBJECT_MAJOR_VERSION %(PYGOBJECT_MAJOR_VERSION)s
+#define PYGOBJECT_MINOR_VERSION %(PYGOBJECT_MINOR_VERSION)s
+#define PYGOBJECT_MICRO_VERSION %(PYGOBJECT_MICRO_VERSION)s
+#define VERSION "%(VERSION)s"
+
+#endif /* _CONFIG_H */
+""" % versions)
 
+    def _setup_extensions(self):
+        ext = {e.name: e for e in self.extensions}
+        script_dir = os.path.dirname(os.path.realpath(__file__))
+
+        msvc_libraries = {
+            "glib-2.0": ["glib-2.0"],
+            "gio-2.0": ["gio-2.0", "gobject-2.0", "glib-2.0"],
+            "gobject-introspection-1.0":
+                ["girepository-1.0", "gobject-2.0", "glib-2.0"],
+            get_pycairo_pkg_config_name(): ["cairo"],
+            "cairo": ["cairo"],
+            "cairo-gobject":
+                ["cairo-gobject", "cairo", "gobject-2.0", "glib-2.0"],
+            "libffi": ["ffi"],
+        }
+
+        def add_dependency(ext, name):
+            fallback_libs = msvc_libraries[name]
+
+            if self.compiler_type == "msvc":
+                # assume that INCLUDE and LIB contains the right paths
+                ext.libraries += fallback_libs
+
+                # The PyCairo header is installed in a subdir of the
+                # Python installation that we are building for, so
+                # deduce that include path here, and use it
+                ext.include_dirs += [
+                    os.path.join(sys.prefix, "include", "pycairo")]
+            else:
+                min_version = get_version_requirement(script_dir, name)
+                pkg_config_version_check(name, min_version)
+                ext.include_dirs += pkg_config_parse("--cflags-only-I", name)
+                ext.library_dirs += pkg_config_parse("--libs-only-L", name)
+                ext.libraries += pkg_config_parse("--libs-only-l", name)
+
+        gi_ext = ext["gi._gi"]
+        add_dependency(gi_ext, "glib-2.0")
+        add_dependency(gi_ext, "gio-2.0")
+        add_dependency(gi_ext, "gobject-introspection-1.0")
+        add_dependency(gi_ext, "libffi")
+
+        gi_cairo_ext = ext["gi._gi_cairo"]
+        add_dependency(gi_cairo_ext, "glib-2.0")
+        add_dependency(gi_cairo_ext, "gio-2.0")
+        add_dependency(gi_cairo_ext, "gobject-introspection-1.0")
+        add_dependency(gi_cairo_ext, "libffi")
+        add_dependency(gi_cairo_ext, "cairo")
+        add_dependency(gi_cairo_ext, "cairo-gobject")
+        add_dependency(gi_cairo_ext, get_pycairo_pkg_config_name())
 
-class BuildPy(orig_build_py):
     def run(self):
-        pass
-
-
-setup(
-    name='pygobject',
-    version=version,
-    description='Python bindings for GObject Introspection',
-    maintainer='The pygobject maintainers',
-    maintainer_email='http://mail.gnome.org/mailman/listinfo/python-hackers-list',
-    download_url='http://download.gnome.org/sources/pygobject/',
-    url='https://wiki.gnome.org/Projects/PyGObject',
-    packages=['gi', 'pygtkcompat'],
-    ext_modules=[
-        Extension(
-            '_gi', sources=['gi/gimodule.c'])
+        self._write_config_h()
+        self._setup_extensions()
+        du_build_ext.run(self)
+
+
+def main():
+    script_dir = os.path.dirname(os.path.realpath(__file__))
+    pkginfo = parse_pkg_info(script_dir)
+    gi_dir = os.path.join(script_dir, "gi")
+
+    sources = [
+        os.path.join("gi", n) for n in os.listdir(gi_dir)
+        if os.path.splitext(n)[-1] == ".c"
+    ]
+    cairo_sources = [os.path.join("gi", "pygi-foreign-cairo.c")]
+    for s in cairo_sources:
+        sources.remove(s)
+
+    gi_ext = Extension(
+        name='gi._gi',
+        sources=sources,
+        include_dirs=[script_dir, gi_dir],
+        define_macros=[("HAVE_CONFIG_H", None)],
+    )
+
+    gi_cairo_ext = Extension(
+        name='gi._gi_cairo',
+        sources=cairo_sources,
+        include_dirs=[script_dir, gi_dir],
+        define_macros=[("HAVE_CONFIG_H", None)],
+    )
+
+    setup(
+        name=pkginfo["Name"],
+        version=pkginfo["Version"],
+        description=pkginfo["Summary"],
+        url=pkginfo["Home-page"],
+        author=pkginfo["Author"],
+        author_email=pkginfo["Author-email"],
+        maintainer=pkginfo["Maintainer"],
+        maintainer_email=pkginfo["Maintainer-email"],
+        license=pkginfo["License"],
+        download_url=pkginfo["Download-url"],
+        long_description=pkginfo["Description"],
+        platforms=pkginfo.get_all("Platform"),
+        classifiers=pkginfo.get_all("Classifier"),
+        packages=find_packages(script_dir),
+        ext_modules=[
+            gi_ext,
+            gi_cairo_ext,
         ],
-    license='LGPL',
-    classifiers=[
-        'Development Status :: 5 - Production/Stable',
-        'License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+)',
-        'Programming Language :: C',
-        'Programming Language :: Python :: 2',
-        'Programming Language :: Python :: 3',
-        'Programming Language :: Python :: Implementation :: CPython',
-    ],
-    cmdclass={
-        'build': Build,
-        'build_py': BuildPy,
-        'build_ext': BuildExt,
-    },
-)
+        cmdclass={
+            "build_ext": build_ext,
+            "distcheck": distcheck,
+        },
+        install_requires=[
+            "pycairo>=%s" % get_version_requirement(
+                script_dir, get_pycairo_pkg_config_name()),
+        ],
+    )
+
+
+if __name__ == "__main__":
+    main()
index ff10433..aef0528 100644 (file)
@@ -144,6 +144,7 @@ EXTRA_DIST = \
        test_docstring.py \
        test_repository.py \
        test_resulttuple.py \
+       test_ossig.py \
        compat_test_pygtk.py \
        gi/__init__.py \
        gi/overrides/__init__.py \
index f070538..d7e307b 100644 (file)
@@ -493,6 +493,7 @@ EXTRA_DIST = \
        test_docstring.py \
        test_repository.py \
        test_resulttuple.py \
+       test_ossig.py \
        compat_test_pygtk.py \
        gi/__init__.py \
        gi/overrides/__init__.py \
index 0378781..ac97e05 100644 (file)
@@ -7,6 +7,7 @@ import traceback
 import ctypes
 import warnings
 import sys
+import os
 
 from gi.repository import Regress as Everything
 from gi.repository import GObject
@@ -19,6 +20,7 @@ try:
 except:
     Gtk = None
 
+from compathelper import PY3
 from helper import capture_exceptions
 
 
@@ -246,7 +248,11 @@ class TestEverything(unittest.TestCase):
         self.assertEqual(Everything.test_utf8_inout(const_str), noconst_str)
 
     def test_filename_return(self):
-        self.assertEqual(Everything.test_filename_return(), ['åäö', '/etc/fstab'])
+        if PY3 and os.name != "nt":
+            result = [os.fsdecode(b'\xc3\xa5\xc3\xa4\xc3\xb6'), '/etc/fstab']
+        else:
+            result = ['åäö', '/etc/fstab']
+        self.assertEqual(Everything.test_filename_return(), result)
 
     def test_int_out_utf8(self):
         # returns g_utf8_strlen() in out argument
index d1b0cfd..39aaf0c 100644 (file)
@@ -695,6 +695,13 @@ class TestFilename(unittest.TestCase):
     @unittest.skipIf(os.name == "nt", "fixme")
     def test_filename_in(self):
         fname = os.path.join(self.workdir, u'testäø.txt')
+
+        try:
+            os.path.exists(fname)
+        except ValueError:
+            # non-unicode fs encoding
+            return
+
         self.assertRaises(GLib.GError, GLib.file_get_contents, fname)
 
         with open(fname.encode('UTF-8'), 'wb') as f:
@@ -711,8 +718,15 @@ class TestFilename(unittest.TestCase):
     @unittest.skipIf(os.name == "nt", "fixme")
     def test_filename_out(self):
         self.assertRaises(GLib.GError, GLib.Dir.make_tmp, 'test')
+        name = 'testäø.XXXXXX'
+
+        try:
+            os.path.exists(name)
+        except ValueError:
+            # non-unicode fs encoding
+            return
 
-        dirname = GLib.Dir.make_tmp('testäø.XXXXXX')
+        dirname = GLib.Dir.make_tmp(name)
         self.assertTrue(os.path.sep + 'testäø.' in dirname, dirname)
         self.assertTrue(os.path.isdir(dirname))
         os.rmdir(dirname)
@@ -868,7 +882,16 @@ class TestFilename(unittest.TestCase):
         if os.name != "nt":
             paths.append((wdb, b"\xff\xfe-5"))
 
+        def valid_path(p):
+            try:
+                os.path.exists(p)
+            except ValueError:
+                return False
+            return True
+
         for (d, path) in paths:
+            if not valid_path(path):
+                continue
             path = os.path.join(d, path)
             with open(path, "wb"):
                 self.assertTrue(GIMarshallingTests.filename_exists(path))
diff --git a/tests/test_ossig.py b/tests/test_ossig.py
new file mode 100644 (file)
index 0000000..bf218b8
--- /dev/null
@@ -0,0 +1,173 @@
+# -*- coding: utf-8 -*-
+# Copyright 2017 Christoph Reiter
+#
+# 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, see <http://www.gnu.org/licenses/>.
+
+import os
+import signal
+import unittest
+import threading
+from contextlib import contextmanager
+
+from gi.repository import Gtk, Gio, GLib
+from gi._ossighelper import wakeup_on_signal, register_sigint_fallback
+
+
+class TestOverridesWakeupOnAlarm(unittest.TestCase):
+
+    @contextmanager
+    def _run_with_timeout(self, timeout, abort_func):
+        failed = []
+
+        def fail():
+            abort_func()
+            failed.append(1)
+            return True
+
+        fail_id = GLib.timeout_add(timeout, fail)
+        try:
+            yield
+        finally:
+            GLib.source_remove(fail_id)
+        self.assertFalse(failed)
+
+    def test_basic(self):
+        self.assertEqual(signal.set_wakeup_fd(-1), -1)
+        with wakeup_on_signal():
+            pass
+        self.assertEqual(signal.set_wakeup_fd(-1), -1)
+
+    def test_in_thread(self):
+        failed = []
+
+        def target():
+            try:
+                with wakeup_on_signal():
+                    pass
+            except:
+                failed.append(1)
+
+        t = threading.Thread(target=target)
+        t.start()
+        t.join(5)
+        self.assertFalse(failed)
+
+    @unittest.skipIf(os.name == "nt", "not on Windows")
+    def test_glib_mainloop(self):
+        loop = GLib.MainLoop()
+        signal.signal(signal.SIGALRM, lambda *args: loop.quit())
+        GLib.idle_add(signal.setitimer, signal.ITIMER_REAL, 0.001)
+
+        with self._run_with_timeout(2000, loop.quit):
+            loop.run()
+
+    @unittest.skipIf(os.name == "nt", "not on Windows")
+    def test_gio_application(self):
+        app = Gio.Application()
+        signal.signal(signal.SIGALRM, lambda *args: app.quit())
+        GLib.idle_add(signal.setitimer, signal.ITIMER_REAL, 0.001)
+
+        with self._run_with_timeout(2000, app.quit):
+            app.hold()
+            app.connect("activate", lambda *args: None)
+            app.run()
+
+    @unittest.skipIf(os.name == "nt", "not on Windows")
+    def test_gtk_main(self):
+        signal.signal(signal.SIGALRM, lambda *args: Gtk.main_quit())
+        GLib.idle_add(signal.setitimer, signal.ITIMER_REAL, 0.001)
+
+        with self._run_with_timeout(2000, Gtk.main_quit):
+            Gtk.main()
+
+    @unittest.skipIf(os.name == "nt", "not on Windows")
+    def test_gtk_dialog_run(self):
+        w = Gtk.Window()
+        d = Gtk.Dialog(transient_for=w)
+        signal.signal(signal.SIGALRM, lambda *args: d.destroy())
+        GLib.idle_add(signal.setitimer, signal.ITIMER_REAL, 0.001)
+
+        with self._run_with_timeout(2000, d.destroy):
+            d.run()
+
+
+class TestSigintFallback(unittest.TestCase):
+
+    def setUp(self):
+        self.assertEqual(
+            signal.getsignal(signal.SIGINT), signal.default_int_handler)
+
+    def tearDown(self):
+        self.assertEqual(
+            signal.getsignal(signal.SIGINT), signal.default_int_handler)
+
+    def test_replace_handler_and_restore_nested(self):
+        with register_sigint_fallback(lambda: None):
+            new_handler = signal.getsignal(signal.SIGINT)
+            self.assertNotEqual(new_handler, signal.default_int_handler)
+            with register_sigint_fallback(lambda: None):
+                self.assertTrue(signal.getsignal(signal.SIGINT) is new_handler)
+        self.assertEqual(
+            signal.getsignal(signal.SIGINT), signal.default_int_handler)
+
+    def test_no_replace_if_not_default(self):
+        new_handler = lambda *args: None
+        signal.signal(signal.SIGINT, new_handler)
+        try:
+            with register_sigint_fallback(lambda: None):
+                self.assertTrue(signal.getsignal(signal.SIGINT) is new_handler)
+                with register_sigint_fallback(lambda: None):
+                    self.assertTrue(
+                        signal.getsignal(signal.SIGINT) is new_handler)
+            self.assertTrue(signal.getsignal(signal.SIGINT) is new_handler)
+        finally:
+            signal.signal(signal.SIGINT, signal.default_int_handler)
+
+    def test_noop_in_threads(self):
+        failed = []
+
+        def target():
+            try:
+                with register_sigint_fallback(lambda: None):
+                    with register_sigint_fallback(lambda: None):
+                        self.assertTrue(
+                            signal.getsignal(signal.SIGINT) is
+                            signal.default_int_handler)
+            except:
+                failed.append(1)
+
+        t = threading.Thread(target=target)
+        t.start()
+        t.join(5)
+        self.assertFalse(failed)
+
+    @unittest.skipIf(os.name == "nt", "not on Windows")
+    def test_no_replace_if_set_by_glib(self):
+        id_ = GLib.unix_signal_add(
+            GLib.PRIORITY_DEFAULT, signal.SIGINT, lambda *args: None)
+        try:
+            # signal.getsignal() doesn't pick up that unix_signal_add()
+            # has changed the handler, but we should anyway.
+            self.assertEqual(
+                signal.getsignal(signal.SIGINT), signal.default_int_handler)
+            with register_sigint_fallback(lambda: None):
+                self.assertEqual(
+                    signal.getsignal(signal.SIGINT),
+                    signal.default_int_handler)
+            self.assertEqual(
+                signal.getsignal(signal.SIGINT), signal.default_int_handler)
+        finally:
+            GLib.source_remove(id_)
+            signal.signal(signal.SIGINT, signal.SIG_DFL)
+            signal.signal(signal.SIGINT, signal.default_int_handler)
index 61b7dc0..769e439 100644 (file)
@@ -1080,6 +1080,12 @@ class TestTreeModel(unittest.TestCase):
         tree_store.insert(None, 1)
         self.assertEqual(signals, ['row-inserted'])
 
+        # One set one signal
+        signals.pop()
+        tree_iter = tree_store.append(None, (10, False))
+        tree_store.set(tree_iter, (0, 1), (20, True))
+        self.assertEqual(signals, ['row-inserted', 'row-changed'])
+
     def test_list_store(self):
         class TestPyObject(object):
             pass
@@ -1337,6 +1343,12 @@ class TestTreeModel(unittest.TestCase):
         list_store.insert(1)
         self.assertEqual(signals, ['row-inserted'])
 
+        # One set one signal
+        signals.pop()
+        tree_iter = list_store.append((10, False))
+        list_store.set(tree_iter, (0, 1), (20, True))
+        self.assertEqual(signals, ['row-inserted', 'row-changed'])
+
     def test_tree_path(self):
         p1 = Gtk.TreePath()
         p2 = Gtk.TreePath.new_first()
similarity index 100%
rename from pygi-convert.sh
rename to tools/pygi-convert.sh