summaryrefslogtreecommitdiffstats
path: root/subprojects/libgd
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/libgd')
-rw-r--r--subprojects/libgd/Makefile.am232
-rw-r--r--subprojects/libgd/README123
-rw-r--r--subprojects/libgd/libgd.doap33
-rw-r--r--subprojects/libgd/libgd.m4140
-rw-r--r--subprojects/libgd/libgd/gd-icon-utils.c394
-rw-r--r--subprojects/libgd/libgd/gd-icon-utils.h49
-rw-r--r--subprojects/libgd/libgd/gd-main-box-child.c186
-rw-r--r--subprojects/libgd/libgd/gd-main-box-child.h54
-rw-r--r--subprojects/libgd/libgd/gd-main-box-generic.c491
-rw-r--r--subprojects/libgd/libgd/gd-main-box-generic.h73
-rw-r--r--subprojects/libgd/libgd/gd-main-box-item.c242
-rw-r--r--subprojects/libgd/libgd/gd-main-box-item.h55
-rw-r--r--subprojects/libgd/libgd/gd-main-box.c548
-rw-r--r--subprojects/libgd/libgd/gd-main-box.h61
-rw-r--r--subprojects/libgd/libgd/gd-main-icon-box-child.c439
-rw-r--r--subprojects/libgd/libgd/gd-main-icon-box-child.h43
-rw-r--r--subprojects/libgd/libgd/gd-main-icon-box-icon.c366
-rw-r--r--subprojects/libgd/libgd/gd-main-icon-box-icon.h40
-rw-r--r--subprojects/libgd/libgd/gd-main-icon-box.c1042
-rw-r--r--subprojects/libgd/libgd/gd-main-icon-box.h44
-rw-r--r--subprojects/libgd/libgd/gd-main-icon-view.c434
-rw-r--r--subprojects/libgd/libgd/gd-main-icon-view.h72
-rw-r--r--subprojects/libgd/libgd/gd-main-list-view.c346
-rw-r--r--subprojects/libgd/libgd/gd-main-list-view.h80
-rw-r--r--subprojects/libgd/libgd/gd-main-view-generic.c330
-rw-r--r--subprojects/libgd/libgd/gd-main-view-generic.h117
-rw-r--r--subprojects/libgd/libgd/gd-main-view.c1160
-rw-r--r--subprojects/libgd/libgd/gd-main-view.h65
-rw-r--r--subprojects/libgd/libgd/gd-margin-container.c379
-rw-r--r--subprojects/libgd/libgd/gd-margin-container.h75
-rw-r--r--subprojects/libgd/libgd/gd-notification.c875
-rw-r--r--subprojects/libgd/libgd/gd-notification.h67
-rw-r--r--subprojects/libgd/libgd/gd-styled-text-renderer.c124
-rw-r--r--subprojects/libgd/libgd/gd-styled-text-renderer.h79
-rw-r--r--subprojects/libgd/libgd/gd-tagged-entry.c1242
-rw-r--r--subprojects/libgd/libgd/gd-tagged-entry.h117
-rw-r--r--subprojects/libgd/libgd/gd-toggle-pixbuf-renderer.c271
-rw-r--r--subprojects/libgd/libgd/gd-toggle-pixbuf-renderer.h75
-rw-r--r--subprojects/libgd/libgd/gd-two-lines-renderer.c616
-rw-r--r--subprojects/libgd/libgd/gd-two-lines-renderer.h75
-rw-r--r--subprojects/libgd/libgd/gd-types-catalog.c123
-rw-r--r--subprojects/libgd/libgd/gd-types-catalog.h31
-rw-r--r--subprojects/libgd/libgd/gd.h81
-rw-r--r--subprojects/libgd/libgd/meson.build202
-rw-r--r--subprojects/libgd/meson.build24
-rw-r--r--subprojects/libgd/meson_options.txt25
-rw-r--r--subprojects/libgd/meson_readme.md88
-rw-r--r--subprojects/libgd/test-tagged-entry-2.c131
-rw-r--r--subprojects/libgd/test-tagged-entry.c111
49 files changed, 12070 insertions, 0 deletions
diff --git a/subprojects/libgd/Makefile.am b/subprojects/libgd/Makefile.am
new file mode 100644
index 0000000..5b7ded7
--- /dev/null
+++ b/subprojects/libgd/Makefile.am
@@ -0,0 +1,232 @@
+AUTOMAKE_OPTIONS=subdir-objects
+NULL =
+CLEANFILES =
+MAINTAINERCLEANFILES =
+EXTRA_DIST =
+noinst_DATA =
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir) \
+ -DPREFIX=\"$(prefix)\" \
+ -DLIBDIR=\"$(libdir)\" \
+ -DG_LOG_DOMAIN=\"libgd\" \
+ -DG_DISABLE_DEPRECATED \
+ $(LIBGD_CFLAGS) \
+ $(NULL)
+
+noinst_PROGRAMS =
+
+if LIBGD_STATIC
+noinst_LTLIBRARIES = libgd.la
+else
+pkglib_LTLIBRARIES = libgd.la
+endif
+
+libgd_la_LIBADD = $(LIBGD_LIBS) $(LIBM)
+libgd_la_LDFLAGS = -avoid-version
+libgd_la_SOURCES = libgd/gd.h
+nodist_libgd_la_SOURCES =
+
+catalog_sources = \
+ libgd/gd-types-catalog.c \
+ libgd/gd-types-catalog.h \
+ $(NULL)
+
+nodist_libgd_la_SOURCES += $(catalog_sources)
+EXTRA_DIST += $(catalog_sources)
+
+if LIBGD_GTK_HACKS
+gtk_hacks_sources = \
+ libgd/gd-icon-utils.c \
+ libgd/gd-icon-utils.h \
+ $(NULL)
+
+nodist_libgd_la_SOURCES += $(gtk_hacks_sources)
+EXTRA_DIST += $(gtk_hacks_sources)
+endif
+
+if LIBGD__BOX_COMMON
+box_common_sources = \
+ libgd/gd-main-box-child.c \
+ libgd/gd-main-box-child.h \
+ libgd/gd-main-box-generic.c \
+ libgd/gd-main-box-generic.h \
+ libgd/gd-main-box-item.c \
+ libgd/gd-main-box-item.h \
+ $(NULL)
+
+nodist_libgd_la_SOURCES += $(box_common_sources)
+EXTRA_DIST += $(box_common_sources)
+endif
+
+if LIBGD_MAIN_ICON_BOX
+main_icon_box_sources = \
+ libgd/gd-main-icon-box.c \
+ libgd/gd-main-icon-box.h \
+ libgd/gd-main-icon-box-child.c \
+ libgd/gd-main-icon-box-child.h \
+ libgd/gd-main-icon-box-icon.c \
+ libgd/gd-main-icon-box-icon.h \
+ $(NULL)
+
+nodist_libgd_la_SOURCES += $(main_icon_box_sources)
+EXTRA_DIST += $(main_icon_box_sources)
+endif
+
+if LIBGD_MAIN_BOX
+main_box_sources = \
+ libgd/gd-main-box.c \
+ libgd/gd-main-box.h \
+ $(NULL)
+
+nodist_libgd_la_SOURCES += $(main_box_sources)
+EXTRA_DIST += $(main_box_sources)
+endif
+
+if LIBGD__VIEW_COMMON
+view_common_sources = \
+ libgd/gd-main-view-generic.c \
+ libgd/gd-main-view-generic.h \
+ libgd/gd-styled-text-renderer.c \
+ libgd/gd-styled-text-renderer.h \
+ libgd/gd-two-lines-renderer.c \
+ libgd/gd-two-lines-renderer.h \
+ $(NULL)
+
+nodist_libgd_la_SOURCES += $(view_common_sources)
+EXTRA_DIST += $(view_common_sources)
+endif
+
+if LIBGD_MAIN_ICON_VIEW
+main_icon_view_sources = \
+ libgd/gd-main-icon-view.c \
+ libgd/gd-main-icon-view.h \
+ libgd/gd-toggle-pixbuf-renderer.c \
+ libgd/gd-toggle-pixbuf-renderer.h \
+ $(NULL)
+
+nodist_libgd_la_SOURCES += $(main_icon_view_sources)
+EXTRA_DIST += $(main_icon_view_sources)
+endif
+
+if LIBGD_MAIN_LIST_VIEW
+main_list_view_sources = \
+ libgd/gd-main-list-view.c \
+ libgd/gd-main-list-view.h \
+ $(NULL)
+
+nodist_libgd_la_SOURCES += $(main_list_view_sources)
+EXTRA_DIST += $(main_list_view_sources)
+endif
+
+if LIBGD_MAIN_VIEW
+main_view_sources = \
+ libgd/gd-main-view.c \
+ libgd/gd-main-view.h \
+ $(NULL)
+
+nodist_libgd_la_SOURCES += $(main_view_sources)
+EXTRA_DIST += $(main_view_sources)
+endif
+
+if LIBGD_MARGIN_CONTAINER
+margin_container_sources = \
+ libgd/gd-margin-container.c \
+ libgd/gd-margin-container.h \
+ $(NULL)
+
+nodist_libgd_la_SOURCES += $(margin_container_sources)
+EXTRA_DIST += $(margin_container_sources)
+endif
+
+if LIBGD_NOTIFICATION
+notification_sources = \
+ libgd/gd-notification.c \
+ libgd/gd-notification.h \
+ $(NULL)
+
+nodist_libgd_la_SOURCES += $(notification_sources)
+EXTRA_DIST += $(notification_sources)
+endif
+
+if LIBGD_TAGGED_ENTRY
+tagged_entry_sources = \
+ libgd/gd-tagged-entry.c \
+ libgd/gd-tagged-entry.h \
+ $(NULL)
+
+nodist_libgd_la_SOURCES += $(tagged_entry_sources)
+EXTRA_DIST += $(tagged_entry_sources)
+
+noinst_PROGRAMS += \
+ test-tagged-entry \
+ test-tagged-entry-2 \
+ $(null)
+
+test_tagged_entry_SOURCES = \
+ test-tagged-entry.c \
+ $(NULL)
+test_tagged_entry_LDADD = \
+ $(LIBGD_LIBS) \
+ libgd.la \
+ $(NULL)
+
+test_tagged_entry_2_SOURCES = \
+ test-tagged-entry-2.c \
+ $(NULL)
+test_tagged_entry_2_LDADD = \
+ $(LIBGD_LIBS) \
+ libgd.la \
+ $(NULL)
+endif
+
+if LIBGD_GIR
+include $(INTROSPECTION_MAKEFILE)
+INTROSPECTION_GIRS = Gd-1.0.gir
+
+Gd-1.0.gir: libgd.la Makefile
+Gd_1_0_gir_NAMESPACE = Gd
+Gd_1_0_gir_VERSION = 1.0
+Gd_1_0_gir_LIBS = libgd.la
+Gd_1_0_gir_CFLAGS = $(AM_CPPFLAGS)
+Gd_1_0_gir_SCANNERFLAGS = \
+ --warn-all \
+ --symbol-prefix=gd \
+ --identifier-prefix=Gd \
+ --c-include="libgd/gd.h" \
+ $(NULL)
+Gd_1_0_gir_INCLUDES = $(LIBGD_GIR_INCLUDES)
+Gd_1_0_gir_FILES = $(nodist_libgd_la_SOURCES)
+
+if LIBGD_STATIC
+noinst_DATA += $(srcdir)/Gd-1.0.gir
+EXTRA_DIST += $(srcdir)/Gd-1.0.gir
+MAINTAINERCLEANFILES += $(srcdir)/Gd-1.0.gir
+else
+girdir= $(pkgdatadir)/gir-1.0
+typelibdir= $(pkglibdir)/girepository-1.0
+
+gir_DATA = $(INTROSPECTION_GIRS)
+typelib_DATA = $(gir_DATA:.gir=.typelib)
+
+CLEANFILES += $(gir_DATA) $(typelib_DATA)
+endif
+endif
+
+if LIBGD_VAPI
+VAPIS = $(srcdir)/gd-1.0.vapi
+
+$(srcdir)/gd-1.0.vapi: $(srcdir)/Gd-1.0.gir
+ $(AM_V_GEN)$(VAPIGEN) \
+ --library gd-1.0 \
+ --pkg gtk+-3.0 \
+ $<
+#This 'touch' is a workaround for vapigen not touching the dest file if
+#its content hasn't changed, which causes the rule to generate the .vapi
+#file to always trigger
+ @touch $@
+
+noinst_DATA += $(VAPIS)
+EXTRA_DIST += $(VAPIS)
+MAINTAINERCLEANFILES += $(VAPIS)
+endif
diff --git a/subprojects/libgd/README b/subprojects/libgd/README
new file mode 100644
index 0000000..fb8c88d
--- /dev/null
+++ b/subprojects/libgd/README
@@ -0,0 +1,123 @@
+=====
+libgd
+=====
+
+Introduction
+============
+
+libgd is a library used by various GNOME 3 styled applications.
+However, it is not a typical library, since it doesn't guarantee
+API/ABI stability, nor does it has official releases tarballs. Only
+the files actually used by your project will be shipped with its
+tarball. Only the necessary dependencies will be checked during
+configure time and used at runtime.
+
+Each application can configure libgd depending on its needs and will
+be able to either link dynamically (privately) or statically link with
+a specific development version.
+
+GObject Introspection based bindings generation such as Javascript or
+Vala are also supported.
+
+More Background
+---------------
+
+libgd originates from the GNOME Documents project (written by Cosimo
+Cecchi), which was one of the first application to follow the novel
+GNOME 3 application design.
+
+Since other applications have similar needs, it makes sense to try to
+reuse and improve the existing work. However, the design being not
+frozen, and the code being not yet matured enough and proven, it is
+not possible for the developers to guarantee API and propose the
+addition to the respective projects (Gtk+, or GLib for example) right
+now. Sharing the code allows to experiment, discuss and test together
+before proposing it upstream.
+
+Traditionally, this problem is solved by copying often outdated
+snippets of code around (due to no API/ABI guarantee), often not
+centralized (libegg).
+
+In the past, there used to be some common aging GNOME application
+libraries above Gtk+ which have been slowly deprecated in favour of
+Gtk+ (gnomeui and friends).
+
+All approaches have pros and cons. A configurable git submodule
+has the following advantages:
+
+- no direct code copying necessary because API/ABI breakage (history
+ is preserved etc..)
+- code is shared and maintained in a common project
+- you can stick to a particular upstream version, or branch off your
+ own version easily if needed (hopefully you find your way back upstream)
+- update the submodule version when your project is ready
+- the libgd options should help you to configure a library to suit
+ your needs, taking care of some of autofoo stuff for you
+
+Usage
+=====
+
+In order to use libgd, an application using autotools needs to:
+
+1. from the top-level project directory, add the submodule:
+ - git submodule add git://git.gnome.org/libgd
+
+2. in autogen.sh, it is recommended to add before autoreconf call:
+ - git submodule update --init --recursive
+
+3. in top-level Makefile.am:
+ - add -I libgd to ACLOCAL_AMFLAGS
+ - add libgd to SUBDIRS, before the project src directory
+
+4. in project configure.ac:
+ - add LIBGD_INIT([list-of-options]) after your project
+ dependencies checks
+ - add libgd/Makefile to AC_CONFIG_FILES
+
+5. from your program Makefile.am, you may now for example:
+ - link with $(top_builddir)/libgd/libgd.la, and include
+ <libgd/gd.h> (adjust your AM_CPPFLAGS as necessary)
+
+You may be interested to look at the commit switching GNOME Boxes from
+private libgd usage to the libgd submodule:
+
+http://git.gnome.org/browse/gnome-boxes/commit/?id=395652458d8b311a25ecb27cc42287602a122b1f
+
+Note for example that the submodule url is "../libgd", which is a
+better alternative for projects hosted on git.gnome.org. It will allow
+the submodule update to reuse the address and the credentials given to
+the toplevel project.
+
+LIBGD_INIT options
+==================
+
+- gtk-hacks
+
+- main-icon-view
+
+- margin-container
+
+- notification
+
+- static
+
+- tagged-entry
+
+- vapi
+
+- gir
+
+How to modify or add an API?
+============================
+
+
+TODO
+====
+
+- add translation support
+- add some form of build test
+- document: options, modification process
+- eventually add documentation generation
+- some licensing check
+- more modularity (not all in libgd.m4/Makefile.am)
+- CSS styling and data: shared only with gnome-themes-standard?
diff --git a/subprojects/libgd/libgd.doap b/subprojects/libgd/libgd.doap
new file mode 100644
index 0000000..944e083
--- /dev/null
+++ b/subprojects/libgd/libgd.doap
@@ -0,0 +1,33 @@
+<Project xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
+ xmlns:foaf="http://xmlns.com/foaf/0.1/"
+ xmlns:gnome="http://api.gnome.org/doap-extensions#"
+ xmlns="http://usefulinc.com/ns/doap#">
+
+ <name xml:lang="en">libgd</name>
+ <shortdesc xml:lang="en">A common GNOME 3 applications submodule</shortdesc>
+ <description>No long description yet</description>
+
+<!--
+ <homepage rdf:resource="http://live.gnome.org/Libgd" />
+ <bug-database rdf:resource="http://bugzilla.gnome.org/browse.cgi?product=libgd"/>
+-->
+
+ <category rdf:resource="http://api.gnome.org/doap-extensions#platform" />
+
+ <maintainer>
+ <foaf:Person>
+ <foaf:name>Cosimo Cecchi</foaf:name>
+ <foaf:mbox rdf:resource="mailto:cosimoc@gnome.org" />
+ <gnome:userid>cosimoc</gnome:userid>
+ </foaf:Person>
+ </maintainer>
+ <maintainer>
+ <foaf:Person>
+ <foaf:name>Marc-André Lureau</foaf:name>
+ <foaf:mbox rdf:resource="mailto:malureau@gnome.org" />
+ <gnome:userid>malureau</gnome:userid>
+ </foaf:Person>
+ </maintainer>
+
+</Project>
diff --git a/subprojects/libgd/libgd.m4 b/subprojects/libgd/libgd.m4
new file mode 100644
index 0000000..b71e344
--- /dev/null
+++ b/subprojects/libgd/libgd.m4
@@ -0,0 +1,140 @@
+dnl The option stuff below is based on the similar code from Automake
+
+# _LIBGD_MANGLE_OPTION(NAME)
+# -------------------------
+# Convert NAME to a valid m4 identifier, by replacing invalid characters
+# with underscores, and prepend the _LIBGD_OPTION_ suffix to it.
+AC_DEFUN([_LIBGD_MANGLE_OPTION],
+[[_LIBGD_OPTION_]m4_bpatsubst($1, [[^a-zA-Z0-9_]], [_])])
+
+# _LIBGD_SET_OPTION(NAME)
+# ----------------------
+# Set option NAME. If NAME begins with a digit, treat it as a requested
+# Guile version number, and define _LIBGD_GUILE_VERSION to that number.
+# Otherwise, define the option using _LIBGD_MANGLE_OPTION.
+AC_DEFUN([_LIBGD_SET_OPTION],
+[m4_define(_LIBGD_MANGLE_OPTION([$1]), 1)])
+
+# _LIBGD_SET_OPTIONS(OPTIONS)
+# ----------------------------------
+# OPTIONS is a space-separated list of libgd options.
+AC_DEFUN([_LIBGD_SET_OPTIONS],
+[m4_foreach_w([_LIBGD_Option], [$1], [_LIBGD_SET_OPTION(_LIBGD_Option)])])
+
+# _LIBGD_IF_OPTION_SET(NAME,IF-SET,IF-NOT-SET)
+# -------------------------------------------
+# Check if option NAME is set.
+AC_DEFUN([_LIBGD_IF_OPTION_SET],
+[m4_ifset(_LIBGD_MANGLE_OPTION([$1]),[$2],[$3])])
+
+dnl LIBGD_INIT([OPTIONS], [DIR])
+dnl ----------------------------
+dnl OPTIONS A whitespace-seperated list of options.
+dnl DIR libgd submodule directory (defaults to 'libgd')
+AC_DEFUN([LIBGD_INIT], [
+ _LIBGD_SET_OPTIONS([$1])
+ AC_SUBST([LIBGD_MODULE_DIR],[m4_if([$2],,[libgd],[$2])])
+
+ AC_REQUIRE([LT_INIT])
+ AC_REQUIRE([AC_CHECK_LIBM])
+ AC_SUBST(LIBM)
+ LIBGD_MODULES="gtk+-3.0 >= 3.7.10"
+ LIBGD_GIR_INCLUDES="Gtk-3.0"
+ LIBGD_SOURCES=""
+
+ AM_CONDITIONAL([LIBGD_STATIC],[_LIBGD_IF_OPTION_SET([static],[true],[false])])
+
+ # main-box:
+ AM_CONDITIONAL([LIBGD_MAIN_BOX],[_LIBGD_IF_OPTION_SET([main-box],[true],[false])])
+ _LIBGD_IF_OPTION_SET([main-box],[
+ _LIBGD_SET_OPTION([main-icon-box])
+ AC_DEFINE([LIBGD_MAIN_BOX], [1], [Description])
+ ])
+
+ # main-icon-box:
+ AM_CONDITIONAL([LIBGD_MAIN_ICON_BOX],[_LIBGD_IF_OPTION_SET([main-icon-box],[true],[false])])
+ _LIBGD_IF_OPTION_SET([main-icon-box],[
+ _LIBGD_SET_OPTION([_box-common])
+ _LIBGD_SET_OPTION([gtk-hacks])
+ AC_DEFINE([LIBGD_MAIN_ICON_BOX], [1], [Description])
+ ])
+
+ # main-view:
+ AM_CONDITIONAL([LIBGD_MAIN_VIEW],[_LIBGD_IF_OPTION_SET([main-view],[true],[false])])
+ _LIBGD_IF_OPTION_SET([main-view],[
+ _LIBGD_SET_OPTION([main-icon-view])
+ _LIBGD_SET_OPTION([main-list-view])
+ _LIBGD_SET_OPTION([gtk-hacks])
+ AC_DEFINE([LIBGD_MAIN_VIEW], [1], [Description])
+ ])
+
+ # main-icon-view:
+ AM_CONDITIONAL([LIBGD_MAIN_ICON_VIEW],[_LIBGD_IF_OPTION_SET([main-icon-view],[true],[false])])
+ _LIBGD_IF_OPTION_SET([main-icon-view],[
+ _LIBGD_SET_OPTION([_view-common])
+ AC_DEFINE([LIBGD_MAIN_ICON_VIEW], [1], [Description])
+ ])
+
+ # main-list-view:
+ AM_CONDITIONAL([LIBGD_MAIN_LIST_VIEW],[_LIBGD_IF_OPTION_SET([main-list-view],[true],[false])])
+ _LIBGD_IF_OPTION_SET([main-list-view],[
+ _LIBGD_SET_OPTION([_view-common])
+ AC_DEFINE([LIBGD_MAIN_LIST_VIEW], [1], [Description])
+ ])
+
+ # margin-container:
+ AM_CONDITIONAL([LIBGD_MARGIN_CONTAINER],[_LIBGD_IF_OPTION_SET([margin-container],[true],[false])])
+ _LIBGD_IF_OPTION_SET([margin-container],[
+ AC_DEFINE([LIBGD_MARGIN_CONTAINER], [1], [Description])
+ ])
+
+ # notification:
+ AM_CONDITIONAL([LIBGD_NOTIFICATION],[_LIBGD_IF_OPTION_SET([notification],[true],[false])])
+ _LIBGD_IF_OPTION_SET([notification],[
+ AC_DEFINE([LIBGD_NOTIFICATION], [1], [Description])
+ ])
+
+ # tagged-entry: Gtk+ widget
+ AM_CONDITIONAL([LIBGD_TAGGED_ENTRY],[_LIBGD_IF_OPTION_SET([tagged-entry],[true],[false])])
+ _LIBGD_IF_OPTION_SET([tagged-entry],[
+ AC_DEFINE([LIBGD_TAGGED_ENTRY], [1], [Description])
+ ])
+
+ # vapi: vala bindings support
+ AM_CONDITIONAL([LIBGD_VAPI],[ _LIBGD_IF_OPTION_SET([vapi],[true],[false])])
+ _LIBGD_IF_OPTION_SET([vapi],[
+ _LIBGD_SET_OPTION([gir])
+ dnl check for vapigen
+ AC_PATH_PROG(VAPIGEN, vapigen, no)
+ AS_IF([test x$VAPIGEN = "xno"],
+ [AC_MSG_ERROR([Cannot find the "vapigen compiler in your PATH])])
+ ])
+
+ # gir: gobject introspection support
+ AM_CONDITIONAL([LIBGD_GIR],[ _LIBGD_IF_OPTION_SET([gir],[true],[false])])
+ _LIBGD_IF_OPTION_SET([gir],[
+ GOBJECT_INTROSPECTION_REQUIRE([0.9.6])
+ ])
+
+ # gtk-hacks: collection of Gtk+ hacks and workarounds
+ AM_CONDITIONAL([LIBGD_GTK_HACKS],[_LIBGD_IF_OPTION_SET([gtk-hacks],[true],[false])])
+ _LIBGD_IF_OPTION_SET([gtk-hacks],[
+ AC_DEFINE([LIBGD_GTK_HACKS], [1], [Description])
+ ])
+
+ # _box-common:
+ AM_CONDITIONAL([LIBGD__BOX_COMMON],[_LIBGD_IF_OPTION_SET([_box-common],[true],[false])])
+ _LIBGD_IF_OPTION_SET([_box-common],[
+ AC_DEFINE([LIBGD__BOX_COMMON], [1], [Description])
+ ])
+
+ # _view-common:
+ AM_CONDITIONAL([LIBGD__VIEW_COMMON],[_LIBGD_IF_OPTION_SET([_view-common],[true],[false])])
+ _LIBGD_IF_OPTION_SET([_view-common],[
+ AC_DEFINE([LIBGD__VIEW_COMMON], [1], [Description])
+ ])
+
+ PKG_CHECK_MODULES(LIBGD, [ $LIBGD_MODULES ])
+ AC_SUBST(LIBGD_GIR_INCLUDES)
+ AC_SUBST(LIBGD_SOURCES)
+])
diff --git a/subprojects/libgd/libgd/gd-icon-utils.c b/subprojects/libgd/libgd/gd-icon-utils.c
new file mode 100644
index 0000000..6c9dd40
--- /dev/null
+++ b/subprojects/libgd/libgd/gd-icon-utils.c
@@ -0,0 +1,394 @@
+/*
+ * Copyright (c) 2011, 2012, 2015, 2016 Red Hat, Inc.
+ *
+ * Gnome Documents is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * Gnome Documents is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with Gnome Documents; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Cosimo Cecchi <cosimoc@redhat.com>
+ *
+ */
+
+#include "gd-icon-utils.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <string.h>
+#include <math.h>
+
+#define _BG_MIN_SIZE 20
+#define _EMBLEM_MIN_SIZE 8
+
+/**
+ * gd_copy_image_surface:
+ * @surface:
+ *
+ * Returns: (transfer full):
+ */
+cairo_surface_t *
+gd_copy_image_surface (cairo_surface_t *surface)
+{
+ cairo_surface_t *copy = NULL;
+ cairo_t *cr;
+ gdouble scale_x;
+ gdouble scale_y;
+
+ copy = cairo_surface_create_similar_image (surface, CAIRO_FORMAT_ARGB32,
+ cairo_image_surface_get_width (surface),
+ cairo_image_surface_get_height (surface));
+ cairo_surface_get_device_scale (surface, &scale_x, &scale_y);
+ cairo_surface_set_device_scale (copy, scale_x, scale_y);
+
+ cr = cairo_create (copy);
+ cairo_set_source_surface (cr, surface, 0, 0);
+ cairo_paint (cr);
+ cairo_destroy (cr);
+
+ return copy;
+}
+
+/**
+ * gd_create_surface_with_counter:
+ * @widget:
+ * @base:
+ * @number:
+ *
+ * Returns: (transfer full):
+ */
+cairo_surface_t *
+gd_create_surface_with_counter (GtkWidget *widget, cairo_surface_t *base, gint number)
+{
+ GtkStyleContext *context;
+ cairo_t *cr, *emblem_cr;
+ cairo_surface_t *emblem_surface;
+ cairo_surface_t *surface;
+ gint height;
+ gint height_scaled;
+ gint width;
+ gint width_scaled;
+ gint layout_width, layout_height;
+ gint emblem_size;
+ gint emblem_size_scaled;
+ gdouble scale;
+ gdouble scale_x;
+ gdouble scale_y;
+ gchar *str;
+ PangoLayout *layout;
+ PangoAttrList *attr_list;
+ PangoAttribute *attr;
+ PangoFontDescription *desc;
+ GdkRGBA color;
+
+ context = gtk_widget_get_style_context (GTK_WIDGET (widget));
+ gtk_style_context_save (context);
+ gtk_style_context_add_class (context, "documents-counter");
+
+ width_scaled = cairo_image_surface_get_width (base);
+ height_scaled = cairo_image_surface_get_height (base);
+ cairo_surface_get_device_scale (base, &scale_x, &scale_y);
+
+ width = width_scaled / (gint) floor (scale_x),
+ height = height_scaled / (gint) floor (scale_y);
+
+ surface = cairo_surface_create_similar_image (base, CAIRO_FORMAT_ARGB32,
+ width_scaled, height_scaled);
+ cairo_surface_set_device_scale (surface, scale_x, scale_y);
+
+ cr = cairo_create (surface);
+ cairo_set_source_surface (cr, base, 0, 0);
+ cairo_paint (cr);
+
+ emblem_size_scaled = MIN (width_scaled / 2, height_scaled / 2);
+ emblem_size = MIN (width / 2, height / 2);
+
+ emblem_surface = cairo_surface_create_similar_image (base, CAIRO_FORMAT_ARGB32,
+ emblem_size_scaled, emblem_size_scaled);
+ cairo_surface_set_device_scale (emblem_surface, scale_x, scale_y);
+
+ emblem_cr = cairo_create (emblem_surface);
+ gtk_render_background (context, emblem_cr,
+ 0, 0, emblem_size, emblem_size);
+
+ if (number > 99)
+ number = 99;
+ if (number < -99)
+ number = -99;
+
+ str = g_strdup_printf ("%d", number);
+ layout = gtk_widget_create_pango_layout (GTK_WIDGET (widget), str);
+ g_free (str);
+
+ pango_layout_get_pixel_size (layout, &layout_width, &layout_height);
+
+ /* scale the layout to be 0.5 of the size still available for drawing */
+ scale = (emblem_size * 0.50) / (MAX (layout_width, layout_height));
+ attr_list = pango_attr_list_new ();
+
+ attr = pango_attr_scale_new (scale);
+ pango_attr_list_insert (attr_list, attr);
+ pango_layout_set_attributes (layout, attr_list);
+
+ gtk_style_context_get (context, GTK_STATE_FLAG_NORMAL, "font", &desc, NULL);
+ pango_layout_set_font_description (layout, desc);
+ pango_font_description_free (desc);
+
+ gtk_style_context_get_color (context, 0, &color);
+ gdk_cairo_set_source_rgba (emblem_cr, &color);
+
+ /* update these values */
+ pango_layout_get_pixel_size (layout, &layout_width, &layout_height);
+
+ cairo_move_to (emblem_cr,
+ emblem_size / 2 - layout_width / 2,
+ emblem_size / 2 - layout_height / 2);
+
+ pango_cairo_show_layout (emblem_cr, layout);
+
+ g_object_unref (layout);
+ pango_attr_list_unref (attr_list);
+ cairo_destroy (emblem_cr);
+
+ cairo_set_source_surface (cr, emblem_surface,
+ width - emblem_size, height - emblem_size);
+ cairo_paint (cr);
+ cairo_destroy (cr);
+
+ cairo_surface_destroy (emblem_surface);
+ gtk_style_context_restore (context);
+
+ return surface;
+}
+
+/**
+ * gd_create_symbolic_icon_for_scale:
+ * @name:
+ * @base_size:
+ * @scale:
+ *
+ * Returns: (transfer full):
+ */
+GIcon *
+gd_create_symbolic_icon_for_scale (const gchar *name,
+ gint base_size,
+ gint scale)
+{
+ gchar *symbolic_name;
+ GIcon *icon, *retval = NULL;
+ cairo_surface_t *icon_surface;
+ cairo_surface_t *surface;
+ cairo_t *cr;
+ GtkStyleContext *style;
+ GtkWidgetPath *path;
+ GdkPixbuf *pixbuf;
+ GtkIconTheme *theme;
+ GtkIconInfo *info;
+ gint bg_size;
+ gint emblem_size;
+ gint total_size;
+ gint total_size_scaled;
+
+ total_size = base_size / 2;
+ total_size_scaled = total_size * scale;
+
+ bg_size = MAX (total_size / 2, _BG_MIN_SIZE);
+ emblem_size = MAX (bg_size - 8, _EMBLEM_MIN_SIZE);
+
+ surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, total_size_scaled, total_size_scaled);
+ cairo_surface_set_device_scale (surface, (gdouble) scale, (gdouble) scale);
+ cr = cairo_create (surface);
+
+ style = gtk_style_context_new ();
+
+ path = gtk_widget_path_new ();
+ gtk_widget_path_append_type (path, GTK_TYPE_ICON_VIEW);
+ gtk_style_context_set_path (style, path);
+ gtk_widget_path_unref (path);
+
+ gtk_style_context_add_class (style, "documents-icon-bg");
+
+ gtk_render_background (style, cr, (total_size - bg_size) / 2, (total_size - bg_size) / 2, bg_size, bg_size);
+
+ symbolic_name = g_strconcat (name, "-symbolic", NULL);
+ icon = g_themed_icon_new_with_default_fallbacks (symbolic_name);
+ g_free (symbolic_name);
+
+ theme = gtk_icon_theme_get_default();
+ info = gtk_icon_theme_lookup_by_gicon_for_scale (theme, icon, emblem_size, scale,
+ GTK_ICON_LOOKUP_FORCE_SIZE);
+ g_object_unref (icon);
+
+ if (info == NULL)
+ goto out;
+
+ pixbuf = gtk_icon_info_load_symbolic_for_context (info, style, NULL, NULL);
+ g_object_unref (info);
+
+ if (pixbuf == NULL)
+ goto out;
+
+ icon_surface = gdk_cairo_surface_create_from_pixbuf (pixbuf, scale, NULL);
+ g_object_unref (pixbuf);
+
+ gtk_render_icon_surface (style, cr, icon_surface, (total_size - emblem_size) / 2, (total_size - emblem_size) / 2);
+ cairo_surface_destroy (icon_surface);
+
+ retval = G_ICON (gdk_pixbuf_get_from_surface (surface, 0, 0, total_size_scaled, total_size_scaled));
+
+ out:
+ g_object_unref (style);
+ cairo_surface_destroy (surface);
+ cairo_destroy (cr);
+
+ return retval;
+}
+
+/**
+ * gd_create_symbolic_icon:
+ * @name:
+ * @base_size:
+ *
+ * Returns: (transfer full):
+ */
+GIcon *
+gd_create_symbolic_icon (const gchar *name,
+ gint base_size)
+{
+ return gd_create_symbolic_icon_for_scale (name, base_size, 1);
+}
+
+/**
+ * gd_embed_surface_in_frame:
+ * @source_image:
+ * @frame_image_url:
+ * @slice_width:
+ * @border_width:
+ *
+ * Returns: (transfer full):
+ */
+cairo_surface_t *
+gd_embed_surface_in_frame (cairo_surface_t *source_image,
+ const gchar *frame_image_url,
+ GtkBorder *slice_width,
+ GtkBorder *border_width)
+{
+ cairo_surface_t *surface;
+ cairo_t *cr;
+ int source_width, source_height;
+ gchar *css_str;
+ GtkCssProvider *provider;
+ GtkStyleContext *context;
+ GError *error = NULL;
+ GtkWidgetPath *path;
+ gdouble scale_x, scale_y;
+
+ cairo_surface_get_device_scale (source_image, &scale_x, &scale_y);
+
+ source_width = cairo_image_surface_get_width (source_image) / (gint) floor (scale_x),
+ source_height = cairo_image_surface_get_height (source_image) / (gint) floor (scale_y);
+
+ css_str = g_strdup_printf (".embedded-image { border-image: url(\"%s\") %d %d %d %d / %dpx %dpx %dpx %dpx }",
+ frame_image_url,
+ slice_width->top, slice_width->right, slice_width->bottom, slice_width->left,
+ border_width->top, border_width->right, border_width->bottom, border_width->left);
+ provider = gtk_css_provider_new ();
+ gtk_css_provider_load_from_data (provider, css_str, -1, &error);
+
+ if (error != NULL)
+ {
+ g_warning ("Unable to create the thumbnail frame image: %s", error->message);
+ g_error_free (error);
+ g_free (css_str);
+
+ return g_object_ref (source_image);
+ }
+
+ surface = cairo_surface_create_similar (source_image,
+ CAIRO_CONTENT_COLOR_ALPHA,
+ source_width, source_height);
+ cr = cairo_create (surface);
+
+ context = gtk_style_context_new ();
+ path = gtk_widget_path_new ();
+ gtk_widget_path_append_type (path, GTK_TYPE_ICON_VIEW);
+
+ gtk_style_context_set_path (context, path);
+ gtk_style_context_add_provider (context,
+ GTK_STYLE_PROVIDER (provider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+
+ cairo_save (cr);
+ cairo_rectangle (cr,
+ border_width->left,
+ border_width->top,
+ source_width - border_width->left - border_width->right,
+ source_height - border_width->top - border_width->bottom);
+ cairo_clip (cr);
+ gtk_render_icon_surface (context, cr,
+ source_image,
+ 0, 0);
+ cairo_restore (cr);
+
+ gtk_style_context_save (context);
+ gtk_style_context_add_class (context, "embedded-image");
+
+ gtk_render_frame (context, cr,
+ 0, 0,
+ source_width, source_height);
+
+ gtk_style_context_restore (context);
+ cairo_destroy (cr);
+
+ gtk_widget_path_unref (path);
+ g_object_unref (provider);
+ g_object_unref (context);
+ g_free (css_str);
+
+ return surface;
+}
+
+/**
+ * gd_embed_image_in_frame:
+ * @source_image:
+ * @frame_image_url:
+ * @slice_width:
+ * @border_width:
+ *
+ * Returns: (transfer full):
+ */
+GdkPixbuf *
+gd_embed_image_in_frame (GdkPixbuf *source_image,
+ const gchar *frame_image_url,
+ GtkBorder *slice_width,
+ GtkBorder *border_width)
+{
+ cairo_surface_t *surface, *embedded_surface;
+ GdkPixbuf *retval;
+
+ surface = gdk_cairo_surface_create_from_pixbuf (source_image,
+ 0, NULL);
+
+ /* Force the device scale to 1.0, since pixbufs are always in unscaled
+ * dimensions.
+ */
+ cairo_surface_set_device_scale (surface, 1.0, 1.0);
+ embedded_surface = gd_embed_surface_in_frame (surface, frame_image_url,
+ slice_width, border_width);
+ retval = gdk_pixbuf_get_from_surface (embedded_surface,
+ 0, 0,
+ cairo_image_surface_get_width (embedded_surface),
+ cairo_image_surface_get_height (embedded_surface));
+
+ cairo_surface_destroy (embedded_surface);
+ cairo_surface_destroy (surface);
+
+ return retval;
+}
diff --git a/subprojects/libgd/libgd/gd-icon-utils.h b/subprojects/libgd/libgd/gd-icon-utils.h
new file mode 100644
index 0000000..12f4f06
--- /dev/null
+++ b/subprojects/libgd/libgd/gd-icon-utils.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2011, 2012, 2015, 2016 Red Hat, Inc.
+ *
+ * Gnome Documents is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * Gnome Documents is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with Gnome Documents; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Cosimo Cecchi <cosimoc@redhat.com>
+ *
+ */
+
+#ifndef __GD_CREATE_SYMBOLIC_ICON_H__
+#define __GD_CREATE_SYMBOLIC_ICON_H__
+
+#include <cairo.h>
+#include <gtk/gtk.h>
+
+cairo_surface_t *gd_copy_image_surface (cairo_surface_t *surface);
+
+cairo_surface_t *gd_create_surface_with_counter (GtkWidget *widget,
+ cairo_surface_t *base,
+ gint number);
+
+GIcon *gd_create_symbolic_icon (const gchar *name,
+ gint base_size);
+GIcon *gd_create_symbolic_icon_for_scale (const gchar *name,
+ gint base_size,
+ gint scale);
+
+GdkPixbuf *gd_embed_image_in_frame (GdkPixbuf *source_image,
+ const gchar *frame_image_url,
+ GtkBorder *slice_width,
+ GtkBorder *border_width);
+cairo_surface_t *gd_embed_surface_in_frame (cairo_surface_t *source_image,
+ const gchar *frame_image_url,
+ GtkBorder *slice_width,
+ GtkBorder *border_width);
+
+#endif /* __GD_CREATE_SYMBOLIC_ICON_H__ */
diff --git a/subprojects/libgd/libgd/gd-main-box-child.c b/subprojects/libgd/libgd/gd-main-box-child.c
new file mode 100644
index 0000000..9a1118d
--- /dev/null
+++ b/subprojects/libgd/libgd/gd-main-box-child.c
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2016, 2017 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Debarshi Ray <debarshir@gnome.org>
+ *
+ */
+
+#include "gd-main-box-child.h"
+
+G_DEFINE_INTERFACE (GdMainBoxChild, gd_main_box_child, GTK_TYPE_WIDGET)
+
+static void
+gd_main_box_child_default_init (GdMainBoxChildInterface *iface)
+{
+ GParamSpec *pspec;
+
+ /**
+ * GdMainBoxChild:item:
+ *
+ * A #GdMainBoxItem that is rendered by the #GdMainBoxChild widget.
+ */
+ pspec = g_param_spec_object ("item",
+ "Item",
+ "An item that is rendered by the widget",
+ GD_TYPE_MAIN_BOX_ITEM,
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_EXPLICIT_NOTIFY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS);
+ g_object_interface_install_property (iface, pspec);
+
+ /**
+ * GdMainBoxChild:selection-mode:
+ *
+ * Whether the #GdMainBoxChild widget is in selection mode.
+ */
+ pspec = g_param_spec_boolean ("selection-mode",
+ "Selection mode",
+ "Whether the child is in selection mode",
+ FALSE,
+ G_PARAM_CONSTRUCT |
+ G_PARAM_EXPLICIT_NOTIFY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS);
+ g_object_interface_install_property (iface, pspec);
+
+ /**
+ * GdMainBoxChild:show-primary-text:
+ *
+ * Whether the #GdMainBoxChild widget is going to show the
+ * primary-text of its item.
+ */
+ pspec = g_param_spec_boolean ("show-primary-text",
+ "Show Primary Text",
+ "Whether the item's primary-text is going to be shown",
+ FALSE,
+ G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_interface_install_property (iface, pspec);
+
+ /**
+ * GdMainBoxChild:show-secondary-text:
+ *
+ * Whether the #GdMainBoxChild widget is going to show the
+ * secondary-text of its item.
+ */
+ pspec = g_param_spec_boolean ("show-secondary-text",
+ "Show Secondary Text",
+ "Whether the item's secondary-text is going to be shown",
+ FALSE,
+ G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_interface_install_property (iface, pspec);
+}
+
+/**
+ * gd_main_box_child_get_item:
+ * @self:
+ *
+ * Returns: (transfer none): The #GdMainBoxItem
+ */
+GdMainBoxItem *
+gd_main_box_child_get_item (GdMainBoxChild *self)
+{
+ GdMainBoxChildInterface *iface;
+
+ g_return_val_if_fail (GD_IS_MAIN_BOX_CHILD (self), NULL);
+
+ iface = GD_MAIN_BOX_CHILD_GET_IFACE (self);
+
+ return (* iface->get_item) (self);
+}
+
+/**
+ * gd_main_box_child_get_index:
+ * @self:
+ *
+ * Returns: (transfer none): The index
+ */
+gint
+gd_main_box_child_get_index (GdMainBoxChild *self)
+{
+ GdMainBoxChildInterface *iface;
+
+ g_return_val_if_fail (GD_IS_MAIN_BOX_CHILD (self), -1);
+
+ iface = GD_MAIN_BOX_CHILD_GET_IFACE (self);
+
+ return (* iface->get_index) (self);
+}
+
+/**
+ * gd_main_box_child_get_selected:
+ * @self:
+ *
+ * Returns: (transfer none): Whether @self is selected
+ */
+gboolean
+gd_main_box_child_get_selected (GdMainBoxChild *self)
+{
+ GdMainBoxChildInterface *iface;
+
+ g_return_val_if_fail (GD_IS_MAIN_BOX_CHILD (self), FALSE);
+
+ iface = GD_MAIN_BOX_CHILD_GET_IFACE (self);
+
+ return (* iface->get_selected) (self);
+}
+
+/**
+ * gd_main_box_child_get_selection_mode:
+ * @self:
+ *
+ * Returns: (transfer none): Whether @self is in selection mode
+ */
+gboolean
+gd_main_box_child_get_selection_mode (GdMainBoxChild *self)
+{
+ gboolean selection_mode;
+
+ g_return_val_if_fail (GD_IS_MAIN_BOX_CHILD (self), FALSE);
+
+ g_object_get (self, "selection-mode", &selection_mode, NULL);
+ return selection_mode;
+}
+
+/**
+ * gd_main_box_child_set_selected:
+ * @self:
+ * @selected:
+ */
+void
+gd_main_box_child_set_selected (GdMainBoxChild *self, gboolean selected)
+{
+ GdMainBoxChildInterface *iface;
+
+ g_return_if_fail (GD_IS_MAIN_BOX_CHILD (self));
+
+ iface = GD_MAIN_BOX_CHILD_GET_IFACE (self);
+
+ return (* iface->set_selected) (self, selected);
+}
+
+/**
+ * gd_main_box_child_set_selection_mode:
+ * @self:
+ * @selection_mode:
+ */
+void
+gd_main_box_child_set_selection_mode (GdMainBoxChild *self, gboolean selection_mode)
+{
+ g_return_if_fail (GD_IS_MAIN_BOX_CHILD (self));
+ g_object_set (self, "selection-mode", selection_mode, NULL);
+}
diff --git a/subprojects/libgd/libgd/gd-main-box-child.h b/subprojects/libgd/libgd/gd-main-box-child.h
new file mode 100644
index 0000000..b2600a6
--- /dev/null
+++ b/subprojects/libgd/libgd/gd-main-box-child.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2016 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Debarshi Ray <debarshir@gnome.org>
+ *
+ */
+
+#ifndef __GD_MAIN_BOX_CHILD_H__
+#define __GD_MAIN_BOX_CHILD_H__
+
+#include <gtk/gtk.h>
+
+#include "gd-main-box-item.h"
+
+G_BEGIN_DECLS
+
+#define GD_TYPE_MAIN_BOX_CHILD gd_main_box_child_get_type()
+G_DECLARE_INTERFACE (GdMainBoxChild, gd_main_box_child, GD, MAIN_BOX_CHILD, GtkWidget)
+
+struct _GdMainBoxChildInterface
+{
+ GTypeInterface base_iface;
+
+ /* vtable */
+ gint (* get_index) (GdMainBoxChild *self);
+ GdMainBoxItem * (* get_item) (GdMainBoxChild *self);
+ gboolean (* get_selected) (GdMainBoxChild *self);
+ void (* set_selected) (GdMainBoxChild *self, gboolean selected);
+};
+
+gint gd_main_box_child_get_index (GdMainBoxChild *self);
+GdMainBoxItem * gd_main_box_child_get_item (GdMainBoxChild *self);
+gboolean gd_main_box_child_get_selected (GdMainBoxChild *self);
+gboolean gd_main_box_child_get_selection_mode (GdMainBoxChild *self);
+void gd_main_box_child_set_selected (GdMainBoxChild *self, gboolean selected);
+void gd_main_box_child_set_selection_mode (GdMainBoxChild *self, gboolean selection_mode);
+
+G_END_DECLS
+
+#endif /* __GD_MAIN_BOX_CHILD_H__ */
diff --git a/subprojects/libgd/libgd/gd-main-box-generic.c b/subprojects/libgd/libgd/gd-main-box-generic.c
new file mode 100644
index 0000000..0d18764
--- /dev/null
+++ b/subprojects/libgd/libgd/gd-main-box-generic.c
@@ -0,0 +1,491 @@
+/*
+ * Copyright (c) 2016, 2017 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Debarshi Ray <debarshir@gnome.org>
+ *
+ */
+
+#include "gd-main-box-generic.h"
+#include "gd-main-box-item.h"
+
+enum
+{
+ ITEM_ACTIVATED,
+ SELECTION_CHANGED,
+ SELECTION_MODE_REQUEST,
+ NUM_SIGNALS
+};
+
+static guint signals[NUM_SIGNALS] = { 0, };
+
+G_DEFINE_INTERFACE (GdMainBoxGeneric, gd_main_box_generic, GTK_TYPE_WIDGET)
+
+static void
+gd_main_box_generic_mark_range_as_selected (GdMainBoxGeneric *self, gint first_element, gint last_element)
+{
+ gint i;
+
+ if (first_element > last_element)
+ {
+ gint tmp;
+
+ tmp = first_element;
+ first_element = last_element;
+ last_element = tmp;
+ }
+
+ for (i = first_element; i <= last_element; i++)
+ {
+ GdMainBoxChild *child;
+
+ child = gd_main_box_generic_get_child_at_index (self, i);
+ gd_main_box_generic_select_child (self, child);
+ }
+}
+
+static void
+gd_main_box_generic_select_range (GdMainBoxGeneric *self, GdMainBoxChild *child)
+{
+ GListModel *model;
+ const gchar *last_selected_id;
+ gint index;
+ gint other_index = -1;
+ guint n_items;
+
+ model = gd_main_box_generic_get_model (self);
+ n_items = g_list_model_get_n_items (model);
+
+ last_selected_id = gd_main_box_generic_get_last_selected_id (self);
+ index = gd_main_box_child_get_index (child);
+
+ if (last_selected_id != NULL)
+ {
+ guint i;
+
+ for (i = 0; i < n_items; i++)
+ {
+ GdMainBoxItem *item;
+ const gchar *id;
+
+ item = GD_MAIN_BOX_ITEM (g_list_model_get_object (model, i));
+ id = gd_main_box_item_get_id (item);
+
+ if (g_strcmp0 (id, last_selected_id) == 0)
+ {
+ other_index = (gint) i;
+ g_object_unref (item);
+ break;
+ }
+
+ g_object_unref (item);
+ }
+ }
+
+ if (other_index == -1)
+ {
+ gint i;
+
+ for (i = index - 1; i >= 0; i--)
+ {
+ GdMainBoxChild *other;
+
+ other = gd_main_box_generic_get_child_at_index (self, i);
+ if (gd_main_box_child_get_selected (other))
+ {
+ other_index = i;
+ break;
+ }
+ }
+ }
+
+ if (other_index == -1)
+ {
+ gint i;
+
+ for (i = index + 1; i < (gint) n_items; i++)
+ {
+ GdMainBoxChild *other;
+
+ other = gd_main_box_generic_get_child_at_index (self, i);
+ if (gd_main_box_child_get_selected (other))
+ {
+ other_index = i;
+ break;
+ }
+ }
+ }
+
+ if (other_index == -1)
+ gd_main_box_generic_select_child (self, child);
+ else
+ gd_main_box_generic_mark_range_as_selected (self, index, other_index);
+}
+
+static void
+gd_main_box_generic_default_init (GdMainBoxGenericInterface *iface)
+{
+ GParamSpec *pspec;
+
+ /**
+ * GdMainBoxGeneric:last-selected-id:
+ *
+ * A unique ID to identify the #GdMainBoxItem object that was most
+ * recently selected.
+ */
+ pspec = g_param_spec_string ("last-selected-id",
+ "ID",
+ "A unique ID to identify the most recently selected item",
+ NULL,
+ G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_interface_install_property (iface, pspec);
+
+ /**
+ * GdMainBoxGeneric:model:
+ *
+ * A #GListModel that is rendered by the #GdMainBoxGeneric widget.
+ */
+ pspec = g_param_spec_object ("model",
+ "Model",
+ "A model that is rendered by the widget",
+ G_TYPE_LIST_MODEL,
+ G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_interface_install_property (iface, pspec);
+
+ /**
+ * GdMainBoxGeneric:gd-selection-mode:
+ *
+ * Whether the #GdMainBoxGeneric widget is in selection mode.
+ */
+ pspec = g_param_spec_boolean ("gd-selection-mode",
+ "Selection Mode",
+ "Whether the widget is in selection mode",
+ FALSE,
+ G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_interface_install_property (iface, pspec);
+
+ /**
+ * GdMainBoxGeneric:show-primary-text:
+ *
+ * Whether the #GdMainBoxGeneric widget is going to show the
+ * primary-text of each #GdMainBoxItem.
+ */
+ pspec = g_param_spec_boolean ("show-primary-text",
+ "Show Primary Text",
+ "Whether each GdMainBoxItem's primary-text is going to be shown",
+ FALSE,
+ G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_interface_install_property (iface, pspec);
+
+ /**
+ * GdMainBoxGeneric:show-secondary-text:
+ *
+ * Whether the #GdMainBoxGeneric widget is going to show the
+ * secondary-text of each #GdMainBoxItem.
+ */
+ pspec = g_param_spec_boolean ("show-secondary-text",
+ "Show Secondary Text",
+ "Whether each GdMainBoxItem's secondary-text is going to be shown",
+ FALSE,
+ G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_interface_install_property (iface, pspec);
+
+ signals[ITEM_ACTIVATED] = g_signal_new ("item-activated",
+ GD_TYPE_MAIN_BOX_GENERIC,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL,
+ NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1,
+ GD_TYPE_MAIN_BOX_CHILD);
+
+ signals[SELECTION_CHANGED] = g_signal_new ("selection-changed",
+ GD_TYPE_MAIN_BOX_GENERIC,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL,
+ NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ signals[SELECTION_MODE_REQUEST] = g_signal_new ("selection-mode-request",
+ GD_TYPE_MAIN_BOX_GENERIC,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL,
+ NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+}
+
+/**
+ * gd_main_box_generic_get_child_at_index:
+ * @self:
+ * @index:
+ *
+ * Returns: (transfer none): The child at @index.
+ */
+GdMainBoxChild *
+gd_main_box_generic_get_child_at_index (GdMainBoxGeneric *self, gint index)
+{
+ GdMainBoxGenericInterface *iface;
+
+ g_return_val_if_fail (GD_IS_MAIN_BOX_GENERIC (self), NULL);
+
+ iface = GD_MAIN_BOX_GENERIC_GET_IFACE (self);
+
+ return (* iface->get_child_at_index) (self, index);
+}
+
+/**
+ * gd_main_box_generic_get_last_selected_id:
+ * @self:
+ *
+ * Returns: (transfer none): The ID of the most recently selected #GdMainBoxItem.
+ */
+const gchar *
+gd_main_box_generic_get_last_selected_id (GdMainBoxGeneric *self)
+{
+ GdMainBoxGenericInterface *iface;
+
+ g_return_val_if_fail (GD_IS_MAIN_BOX_GENERIC (self), NULL);
+
+ iface = GD_MAIN_BOX_GENERIC_GET_IFACE (self);
+
+ return (* iface->get_last_selected_id) (self);
+}
+
+/**
+ * gd_main_box_generic_get_model:
+ * @self:
+ *
+ * Returns: (transfer none): The associated model
+ */
+GListModel *
+gd_main_box_generic_get_model (GdMainBoxGeneric *self)
+{
+ GdMainBoxGenericInterface *iface;
+
+ g_return_val_if_fail (GD_IS_MAIN_BOX_GENERIC (self), NULL);
+
+ iface = GD_MAIN_BOX_GENERIC_GET_IFACE (self);
+
+ return (* iface->get_model) (self);
+}
+
+/**
+ * gd_main_box_generic_get_selected_children:
+ * @self:
+ *
+ * Returns: (element-type GdMainBoxChild) (transfer container): The
+ * selected children
+ */
+GList *
+gd_main_box_generic_get_selected_children (GdMainBoxGeneric *self)
+{
+ GdMainBoxGenericInterface *iface;
+
+ g_return_val_if_fail (GD_IS_MAIN_BOX_GENERIC (self), NULL);
+
+ iface = GD_MAIN_BOX_GENERIC_GET_IFACE (self);
+
+ return (* iface->get_selected_children) (self);
+}
+
+/**
+ * gd_main_box_generic_get_selection_mode:
+ * @self:
+ *
+ * Returns: (transfer none): Whether @self is in selection mode
+ */
+gboolean
+gd_main_box_generic_get_selection_mode (GdMainBoxGeneric *self)
+{
+ gboolean selection_mode;
+
+ g_return_val_if_fail (GD_IS_MAIN_BOX_GENERIC (self), FALSE);
+
+ g_object_get (self, "gd-selection-mode", &selection_mode, NULL);
+ return selection_mode;
+}
+
+/**
+ * gd_main_box_generic_get_show_primary_text:
+ * @self:
+ *
+ * Returns: (transfer none): Whether @self is going to show the
+ * primary-text of each #GdMainBoxItem
+ */
+gboolean
+gd_main_box_generic_get_show_primary_text (GdMainBoxGeneric *self)
+{
+ gboolean show_primary_text;
+
+ g_return_val_if_fail (GD_IS_MAIN_BOX_GENERIC (self), FALSE);
+
+ g_object_get (self, "show-primary-text", &show_primary_text, NULL);
+ return show_primary_text;
+}
+
+/**
+ * gd_main_box_generic_get_show_secondary_text:
+ * @self:
+ *
+ * Returns: (transfer none): Whether @self is going to show the
+ * secondary-text of each #GdMainBoxItem
+ */
+gboolean
+gd_main_box_generic_get_show_secondary_text (GdMainBoxGeneric *self)
+{
+ gboolean show_secondary_text;
+
+ g_return_val_if_fail (GD_IS_MAIN_BOX_GENERIC (self), FALSE);
+
+ g_object_get (self, "show-secondary-text", &show_secondary_text, NULL);
+ return show_secondary_text;
+}
+
+void
+gd_main_box_generic_select_all (GdMainBoxGeneric *self)
+{
+ GdMainBoxGenericInterface *iface;
+
+ g_return_if_fail (GD_IS_MAIN_BOX_GENERIC (self));
+
+ iface = GD_MAIN_BOX_GENERIC_GET_IFACE (self);
+
+ (* iface->select_all) (self);
+}
+
+void
+gd_main_box_generic_select_child (GdMainBoxGeneric *self, GdMainBoxChild *child)
+{
+ GdMainBoxGenericInterface *iface;
+
+ g_return_if_fail (GD_IS_MAIN_BOX_GENERIC (self));
+ g_return_if_fail (GD_IS_MAIN_BOX_CHILD (child));
+
+ iface = GD_MAIN_BOX_GENERIC_GET_IFACE (self);
+
+ (* iface->select_child) (self, child);
+}
+
+/**
+ * gd_main_box_generic_set_model:
+ * @self:
+ * @model: (allow-none):
+ *
+ */
+void
+gd_main_box_generic_set_model (GdMainBoxGeneric *self, GListModel *model)
+{
+ g_return_if_fail (GD_IS_MAIN_BOX_GENERIC (self));
+ g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model));
+
+ g_object_set (self, "model", model, NULL);
+}
+
+/**
+ * gd_main_box_generic_set_selection_mode:
+ * @self:
+ * @selection_mode:
+ *
+ */
+void
+gd_main_box_generic_set_selection_mode (GdMainBoxGeneric *self, gboolean selection_mode)
+{
+ g_return_if_fail (GD_IS_MAIN_BOX_GENERIC (self));
+ g_object_set (self, "gd-selection-mode", selection_mode, NULL);
+}
+
+/**
+ * gd_main_box_generic_set_show_primary_text:
+ * @self:
+ * @show_primary_text:
+ *
+ */
+void
+gd_main_box_generic_set_show_primary_text (GdMainBoxGeneric *self, gboolean show_primary_text)
+{
+ g_return_if_fail (GD_IS_MAIN_BOX_GENERIC (self));
+ g_object_set (self, "show-primary-text", show_primary_text, NULL);
+}
+
+/**
+ * gd_main_box_generic_set_show_secondary_text:
+ * @self:
+ * @show_secondary_text:
+ *
+ */
+void
+gd_main_box_generic_set_show_secondary_text (GdMainBoxGeneric *self, gboolean show_secondary_text)
+{
+ g_return_if_fail (GD_IS_MAIN_BOX_GENERIC (self));
+ g_object_set (self, "show-secondary-text", show_secondary_text, NULL);
+}
+
+void
+gd_main_box_generic_unselect_all (GdMainBoxGeneric *self)
+{
+ GdMainBoxGenericInterface *iface;
+
+ g_return_if_fail (GD_IS_MAIN_BOX_GENERIC (self));
+
+ iface = GD_MAIN_BOX_GENERIC_GET_IFACE (self);
+
+ (* iface->unselect_all) (self);
+}
+
+void
+gd_main_box_generic_unselect_child (GdMainBoxGeneric *self, GdMainBoxChild *child)
+{
+ GdMainBoxGenericInterface *iface;
+
+ g_return_if_fail (GD_IS_MAIN_BOX_GENERIC (self));
+ g_return_if_fail (GD_IS_MAIN_BOX_CHILD (child));
+
+ iface = GD_MAIN_BOX_GENERIC_GET_IFACE (self);
+
+ (* iface->unselect_child) (self, child);
+}
+
+void
+gd_main_box_generic_toggle_selection_for_child (GdMainBoxGeneric *self,
+ GdMainBoxChild *child,
+ gboolean select_range)
+{
+ GListModel *model;
+
+ model = gd_main_box_generic_get_model (self);
+ if (model == NULL)
+ return;
+
+ if (gd_main_box_child_get_selected (child))
+ {
+ gd_main_box_generic_unselect_child (self, child);
+ }
+ else
+ {
+ if (select_range)
+ gd_main_box_generic_select_range (self, child);
+ else
+ gd_main_box_generic_select_child (self, child);
+ }
+}
diff --git a/subprojects/libgd/libgd/gd-main-box-generic.h b/subprojects/libgd/libgd/gd-main-box-generic.h
new file mode 100644
index 0000000..2c9c805
--- /dev/null
+++ b/subprojects/libgd/libgd/gd-main-box-generic.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2016, 2017 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Debarshi Ray <debarshir@gnome.org>
+ *
+ */
+
+#ifndef __GD_MAIN_BOX_GENERIC_H__
+#define __GD_MAIN_BOX_GENERIC_H__
+
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+#include "gd-main-box-child.h"
+
+G_BEGIN_DECLS
+
+#define GD_TYPE_MAIN_BOX_GENERIC gd_main_box_generic_get_type()
+G_DECLARE_INTERFACE (GdMainBoxGeneric, gd_main_box_generic, GD, MAIN_BOX_GENERIC, GtkWidget)
+
+struct _GdMainBoxGenericInterface
+{
+ GTypeInterface base_iface;
+
+ /* vtable */
+ GdMainBoxChild * (* get_child_at_index) (GdMainBoxGeneric *self, gint index);
+ const gchar * (* get_last_selected_id) (GdMainBoxGeneric *self);
+ GListModel * (* get_model) (GdMainBoxGeneric *self);
+ GList * (* get_selected_children) (GdMainBoxGeneric *self);
+ void (* select_all) (GdMainBoxGeneric *self);
+ void (* select_child) (GdMainBoxGeneric *self, GdMainBoxChild *child);
+ void (* unselect_all) (GdMainBoxGeneric *self);
+ void (* unselect_child) (GdMainBoxGeneric *self, GdMainBoxChild *child);
+};
+
+GdMainBoxChild * gd_main_box_generic_get_child_at_index (GdMainBoxGeneric *self, gint index);
+const gchar * gd_main_box_generic_get_last_selected_id (GdMainBoxGeneric *self);
+GListModel * gd_main_box_generic_get_model (GdMainBoxGeneric *self);
+GList * gd_main_box_generic_get_selected_children (GdMainBoxGeneric *self);
+gboolean gd_main_box_generic_get_selection_mode (GdMainBoxGeneric *self);
+gboolean gd_main_box_generic_get_show_primary_text (GdMainBoxGeneric *self);
+gboolean gd_main_box_generic_get_show_secondary_text (GdMainBoxGeneric *self);
+void gd_main_box_generic_select_all (GdMainBoxGeneric *self);
+void gd_main_box_generic_select_child (GdMainBoxGeneric *self, GdMainBoxChild *child);
+void gd_main_box_generic_set_model (GdMainBoxGeneric *self, GListModel *model);
+void gd_main_box_generic_set_selection_mode (GdMainBoxGeneric *self, gboolean selection_mode);
+void gd_main_box_generic_set_show_primary_text (GdMainBoxGeneric *self, gboolean show_primary_text);
+void gd_main_box_generic_set_show_secondary_text (GdMainBoxGeneric *self,
+ gboolean show_secondary_text);
+void gd_main_box_generic_unselect_all (GdMainBoxGeneric *self);
+void gd_main_box_generic_unselect_child (GdMainBoxGeneric *self, GdMainBoxChild *child);
+
+void gd_main_box_generic_toggle_selection_for_child (GdMainBoxGeneric *self,
+ GdMainBoxChild *child,
+ gboolean select_range);
+
+G_END_DECLS
+
+#endif /* __GD_MAIN_BOX_GENERIC_H__ */
diff --git a/subprojects/libgd/libgd/gd-main-box-item.c b/subprojects/libgd/libgd/gd-main-box-item.c
new file mode 100644
index 0000000..042b0e4
--- /dev/null
+++ b/subprojects/libgd/libgd/gd-main-box-item.c
@@ -0,0 +1,242 @@
+/*
+ * Copyright (c) 2016 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Debarshi Ray <debarshir@gnome.org>
+ *
+ */
+
+#include <cairo-gobject.h>
+
+#include "gd-main-box-item.h"
+
+G_DEFINE_INTERFACE (GdMainBoxItem, gd_main_box_item, G_TYPE_OBJECT)
+
+static void
+gd_main_box_item_default_init (GdMainBoxItemInterface *iface)
+{
+ GParamSpec *pspec;
+
+ /**
+ * GdMainBoxItem:id:
+ *
+ * A unique ID to identify the #GdMainBoxItem object.
+ */
+ pspec = g_param_spec_string ("id",
+ "ID",
+ "A unique ID to identify the item",
+ NULL,
+ G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_interface_install_property (iface, pspec);
+
+ /**
+ * GdMainBoxItem:uri:
+ *
+ * A URI corresponding to the #GdMainBoxItem object.
+ */
+ pspec = g_param_spec_string ("uri",
+ "URI",
+ "A URI corresponding to the item",
+ NULL,
+ G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_interface_install_property (iface, pspec);
+
+ /**
+ * GdMainBoxItem:primary-text:
+ *
+ * Some text to describe the #GdMainBoxItem object.
+ */
+ pspec = g_param_spec_string ("primary-text",
+ "Primary Text",
+ "Some text to describe the item",
+ NULL,
+ G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_interface_install_property (iface, pspec);
+
+ /**
+ * GdMainBoxItem:secondary-text:
+ *
+ * Some additional text to describe the #GdMainBoxItem object.
+ */
+ pspec = g_param_spec_string ("secondary-text",
+ "Secondary Text",
+ "Some additional text to describe the item",
+ NULL,
+ G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_interface_install_property (iface, pspec);
+
+ /**
+ * GdMainBoxItem:icon:
+ *
+ * An icon to visually identify the #GdMainBoxItem object.
+ */
+ pspec = g_param_spec_boxed ("icon",
+ "Icon",
+ "An icon to visually identify the item",
+ CAIRO_GOBJECT_TYPE_SURFACE,
+ G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_interface_install_property (iface, pspec);
+
+ /**
+ * GdMainBoxItem:mtime:
+ *
+ * The time when the #GdMainBoxItem object was last modified.
+ */
+ pspec = g_param_spec_int64 ("mtime",
+ "Modification time",
+ "The time when the item was last modified",
+ -1,
+ G_MAXINT64,
+ -1,
+ G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_interface_install_property (iface, pspec);
+
+ /**
+ * GdMainBoxItem:pulse:
+ *
+ * Whether to show a progress indicator.
+ */
+ pspec = g_param_spec_boolean ("pulse",
+ "Pulse",
+ "Whether to show a progress indicator",
+ FALSE,
+ G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ g_object_interface_install_property (iface, pspec);
+}
+
+/**
+ * gd_main_box_item_get_id:
+ * @self:
+ *
+ * Returns: (transfer none): The ID
+ */
+const gchar *
+gd_main_box_item_get_id (GdMainBoxItem *self)
+{
+ GdMainBoxItemInterface *iface;
+
+ g_return_val_if_fail (GD_IS_MAIN_BOX_ITEM (self), NULL);
+
+ iface = GD_MAIN_BOX_ITEM_GET_IFACE (self);
+
+ return (* iface->get_id) (self);
+}
+
+/**
+ * gd_main_box_item_get_uri:
+ * @self:
+ *
+ * Returns: (transfer none): The URI
+ */
+const gchar *
+gd_main_box_item_get_uri (GdMainBoxItem *self)
+{
+ GdMainBoxItemInterface *iface;
+
+ g_return_val_if_fail (GD_IS_MAIN_BOX_ITEM (self), NULL);
+
+ iface = GD_MAIN_BOX_ITEM_GET_IFACE (self);
+
+ return (* iface->get_uri) (self);
+}
+
+/**
+ * gd_main_box_item_get_primary_text:
+ * @self:
+ *
+ * Returns: (transfer none): The primary text
+ */
+const gchar *
+gd_main_box_item_get_primary_text (GdMainBoxItem *self)
+{
+ GdMainBoxItemInterface *iface;
+
+ g_return_val_if_fail (GD_IS_MAIN_BOX_ITEM (self), NULL);
+
+ iface = GD_MAIN_BOX_ITEM_GET_IFACE (self);
+
+ return (* iface->get_primary_text) (self);
+}
+
+/**
+ * gd_main_box_item_get_secondary_text:
+ * @self:
+ *
+ * Returns: (transfer none): The secondary text
+ */
+const gchar *
+gd_main_box_item_get_secondary_text (GdMainBoxItem *self)
+{
+ GdMainBoxItemInterface *iface;
+
+ g_return_val_if_fail (GD_IS_MAIN_BOX_ITEM (self), NULL);
+
+ iface = GD_MAIN_BOX_ITEM_GET_IFACE (self);
+
+ return (* iface->get_secondary_text) (self);
+}
+
+/**
+ * gd_main_box_item_get_icon:
+ * @self:
+ *
+ * Returns: (transfer none): The icon
+ */
+cairo_surface_t *
+gd_main_box_item_get_icon (GdMainBoxItem *self)
+{
+ GdMainBoxItemInterface *iface;
+
+ g_return_val_if_fail (GD_IS_MAIN_BOX_ITEM (self), NULL);
+
+ iface = GD_MAIN_BOX_ITEM_GET_IFACE (self);
+
+ return (* iface->get_icon) (self);
+}
+
+/**
+ * gd_main_box_item_get_mtime:
+ * @self:
+ *
+ * Returns: (transfer none): The modification time
+ */
+gint64
+gd_main_box_item_get_mtime (GdMainBoxItem *self)
+{
+ gint64 mtime;
+
+ g_return_val_if_fail (GD_IS_MAIN_BOX_ITEM (self), -1);
+
+ g_object_get (self, "mtime", &mtime, NULL);
+ return mtime;
+}
+
+/**
+ * gd_main_box_item_get_pulse:
+ * @self:
+ *
+ * Returns: (transfer none): Whether to show a progress indicator
+ */
+gboolean
+gd_main_box_item_get_pulse (GdMainBoxItem *self)
+{
+ gboolean pulse;
+
+ g_return_val_if_fail (GD_IS_MAIN_BOX_ITEM (self), FALSE);
+
+ g_object_get (self, "pulse", &pulse, NULL);
+ return pulse;
+}
diff --git a/subprojects/libgd/libgd/gd-main-box-item.h b/subprojects/libgd/libgd/gd-main-box-item.h
new file mode 100644
index 0000000..e37cb07
--- /dev/null
+++ b/subprojects/libgd/libgd/gd-main-box-item.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2016 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Debarshi Ray <debarshir@gnome.org>
+ *
+ */
+
+#ifndef __GD_MAIN_BOX_ITEM_H__
+#define __GD_MAIN_BOX_ITEM_H__
+
+#include <cairo.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GD_TYPE_MAIN_BOX_ITEM gd_main_box_item_get_type()
+G_DECLARE_INTERFACE (GdMainBoxItem, gd_main_box_item, GD, MAIN_BOX_ITEM, GObject)
+
+struct _GdMainBoxItemInterface
+{
+ GTypeInterface base_iface;
+
+ /* vtable */
+ const gchar * (* get_id) (GdMainBoxItem *self);
+ const gchar * (* get_uri) (GdMainBoxItem *self);
+ const gchar * (* get_primary_text) (GdMainBoxItem *self);
+ const gchar * (* get_secondary_text) (GdMainBoxItem *self);
+ cairo_surface_t * (* get_icon) (GdMainBoxItem *self);
+};
+
+const gchar * gd_main_box_item_get_id (GdMainBoxItem *self);
+const gchar * gd_main_box_item_get_uri (GdMainBoxItem *self);
+const gchar * gd_main_box_item_get_primary_text (GdMainBoxItem *self);
+const gchar * gd_main_box_item_get_secondary_text (GdMainBoxItem *self);
+cairo_surface_t * gd_main_box_item_get_icon (GdMainBoxItem *self);
+gint64 gd_main_box_item_get_mtime (GdMainBoxItem *self);
+gboolean gd_main_box_item_get_pulse (GdMainBoxItem *self);
+
+G_END_DECLS
+
+#endif /* __GD_MAIN_BOX_ITEM_H__ */
diff --git a/subprojects/libgd/libgd/gd-main-box.c b/subprojects/libgd/libgd/gd-main-box.c
new file mode 100644
index 0000000..be87b1d
--- /dev/null
+++ b/subprojects/libgd/libgd/gd-main-box.c
@@ -0,0 +1,548 @@
+/*
+ * Copyright (c) 2016, 2017 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Debarshi Ray <debarshir@gnome.org>
+ *
+ */
+
+#include "gd-main-box.h"
+#include "gd-main-box-child.h"
+#include "gd-main-box-generic.h"
+#include "gd-main-icon-box.h"
+
+#define MAIN_BOX_TYPE_INITIAL -1
+
+typedef struct _GdMainBoxPrivate GdMainBoxPrivate;
+
+struct _GdMainBoxPrivate
+{
+ GListModel *model;
+ GdMainBoxType current_type;
+ GtkWidget *current_box;
+ GtkWidget *frame;
+ gboolean selection_mode;
+ gboolean show_primary_text;
+ gboolean show_secondary_text;
+};
+
+enum
+{
+ PROP_BOX_TYPE = 1,
+ PROP_SELECTION_MODE,
+ PROP_SHOW_PRIMARY_TEXT,
+ PROP_SHOW_SECONDARY_TEXT,
+ PROP_MODEL,
+ NUM_PROPERTIES
+};
+
+enum
+{
+ ITEM_ACTIVATED,
+ SELECTION_CHANGED,
+ SELECTION_MODE_REQUEST,
+ NUM_SIGNALS
+};
+
+static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
+static guint signals[NUM_SIGNALS] = { 0, };
+
+G_DEFINE_TYPE_WITH_PRIVATE (GdMainBox, gd_main_box, GTK_TYPE_BIN)
+
+static void
+gd_main_box_activate_item_for_child (GdMainBox *self, GdMainBoxChild *child)
+{
+ GdMainBoxPrivate *priv;
+ GdMainBoxItem *item;
+
+ priv = gd_main_box_get_instance_private (self);
+
+ if (priv->model == NULL)
+ return;
+
+ item = gd_main_box_child_get_item (child);
+ if (item == NULL)
+ return;
+
+ g_signal_emit (self, signals[ITEM_ACTIVATED], 0, item);
+}
+
+static void
+gd_main_box_apply_selection_mode (GdMainBox *self)
+{
+ GdMainBoxPrivate *priv;
+
+ priv = gd_main_box_get_instance_private (self);
+
+ gd_main_box_generic_set_selection_mode (GD_MAIN_BOX_GENERIC (priv->current_box), priv->selection_mode);
+
+ if (!priv->selection_mode)
+ {
+ if (priv->model != NULL)
+ gd_main_box_unselect_all (self);
+ }
+}
+
+static void
+gd_main_box_item_activated_cb (GdMainBox *self, GdMainBoxChild *child)
+{
+ GdMainBoxPrivate *priv;
+
+ priv = gd_main_box_get_instance_private (self);
+
+ if (!priv->selection_mode)
+ gd_main_box_activate_item_for_child (self, child);
+}
+
+static void
+gd_main_box_selection_changed_cb (GdMainBox *self)
+{
+ g_signal_emit (self, signals[SELECTION_CHANGED], 0);
+}
+
+static void
+gd_main_box_selection_mode_request_cb (GdMainBox *self)
+{
+ g_signal_emit (self, signals[SELECTION_MODE_REQUEST], 0);
+}
+
+static void
+gd_main_box_rebuild (GdMainBox *self)
+{
+ GdMainBoxPrivate *priv;
+
+ priv = gd_main_box_get_instance_private (self);
+
+ if (priv->current_box != NULL)
+ gtk_widget_destroy (priv->current_box);
+
+ switch (priv->current_type)
+ {
+ case GD_MAIN_BOX_ICON:
+ priv->current_box = gd_main_icon_box_new ();
+ break;
+
+ case GD_MAIN_BOX_LIST:
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ gtk_widget_set_hexpand (priv->current_box, TRUE);
+ gtk_widget_set_valign (priv->current_box, GTK_ALIGN_START);
+ g_object_bind_property (self, "show-primary-text",
+ priv->current_box, "show-primary-text",
+ G_BINDING_SYNC_CREATE);
+ g_object_bind_property (self, "show-secondary-text",
+ priv->current_box, "show-secondary-text",
+ G_BINDING_SYNC_CREATE);
+ gtk_container_add (GTK_CONTAINER (priv->frame), priv->current_box);
+
+ g_signal_connect_swapped (priv->current_box,
+ "item-activated",
+ G_CALLBACK (gd_main_box_item_activated_cb),
+ self);
+ g_signal_connect_swapped (priv->current_box,
+ "selection-changed",
+ G_CALLBACK (gd_main_box_selection_changed_cb),
+ self);
+ g_signal_connect_swapped (priv->current_box,
+ "selection-mode-request",
+ G_CALLBACK (gd_main_box_selection_mode_request_cb),
+ self);
+
+ gd_main_box_generic_set_model (GD_MAIN_BOX_GENERIC (priv->current_box), priv->model);
+ gd_main_box_apply_selection_mode (self);
+
+ gtk_widget_show_all (GTK_WIDGET (self));
+}
+
+static void
+gd_main_box_dispose (GObject *obj)
+{
+ GdMainBox *self = GD_MAIN_BOX (obj);
+ GdMainBoxPrivate *priv;
+
+ priv = gd_main_box_get_instance_private (self);
+
+ g_clear_object (&priv->model);
+
+ G_OBJECT_CLASS (gd_main_box_parent_class)->dispose (obj);
+}
+
+static void
+gd_main_box_init (GdMainBox *self)
+{
+ GdMainBoxPrivate *priv;
+ GtkStyleContext *context;
+
+ priv = gd_main_box_get_instance_private (self);
+
+ priv->frame = gtk_frame_new (NULL);
+ context = gtk_widget_get_style_context (priv->frame);
+ gtk_style_context_add_class (context, "content-view");
+ gtk_container_add (GTK_CONTAINER (self), priv->frame);
+
+ /* so that we get constructed with the right view even at startup */
+ priv->current_type = MAIN_BOX_TYPE_INITIAL;
+}
+
+static void
+gd_main_box_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
+{
+ GdMainBox *self = GD_MAIN_BOX (object);
+
+ switch (property_id)
+ {
+ case PROP_BOX_TYPE:
+ g_value_set_int (value, gd_main_box_get_box_type (self));
+ break;
+ case PROP_SELECTION_MODE:
+ g_value_set_boolean (value, gd_main_box_get_selection_mode (self));
+ break;
+ case PROP_SHOW_PRIMARY_TEXT:
+ g_value_set_boolean (value, gd_main_box_get_show_primary_text (self));
+ break;
+ case PROP_SHOW_SECONDARY_TEXT:
+ g_value_set_boolean (value, gd_main_box_get_show_secondary_text (self));
+ break;
+ case PROP_MODEL:
+ g_value_set_object (value, gd_main_box_get_model (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gd_main_box_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
+{
+ GdMainBox *self = GD_MAIN_BOX (object);
+
+ switch (property_id)
+ {
+ case PROP_BOX_TYPE:
+ gd_main_box_set_box_type (self, g_value_get_int (value));
+ break;
+ case PROP_SELECTION_MODE:
+ gd_main_box_set_selection_mode (self, g_value_get_boolean (value));
+ break;
+ case PROP_SHOW_PRIMARY_TEXT:
+ gd_main_box_set_show_primary_text (self, g_value_get_boolean (value));
+ break;
+ case PROP_SHOW_SECONDARY_TEXT:
+ gd_main_box_set_show_secondary_text (self, g_value_get_boolean (value));
+ break;
+ case PROP_MODEL:
+ gd_main_box_set_model (self, g_value_get_object (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gd_main_box_class_init (GdMainBoxClass *klass)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+
+ oclass->get_property = gd_main_box_get_property;
+ oclass->set_property = gd_main_box_set_property;
+ oclass->dispose = gd_main_box_dispose;
+
+ properties[PROP_BOX_TYPE] = g_param_spec_int ("box-type",
+ "Box type",
+ "Box type",
+ GD_MAIN_BOX_ICON,
+ GD_MAIN_BOX_LIST,
+ GD_MAIN_BOX_ICON,
+ G_PARAM_EXPLICIT_NOTIFY |
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_MODEL] = g_param_spec_object ("model",
+ "Model",
+ "The GListModel",
+ G_TYPE_LIST_MODEL,
+ G_PARAM_EXPLICIT_NOTIFY |
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_SELECTION_MODE] = g_param_spec_boolean ("selection-mode",
+ "Selection mode",
+ "Whether the view is in selection mode",
+ FALSE,
+ G_PARAM_EXPLICIT_NOTIFY |
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_SHOW_PRIMARY_TEXT] = g_param_spec_boolean ("show-primary-text",
+ "Show primary text",
+ "Whether each GdMainBoxItem's primary-text is going "
+ "to be shown",
+ FALSE,
+ G_PARAM_EXPLICIT_NOTIFY |
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_SHOW_SECONDARY_TEXT] = g_param_spec_boolean ("show-secondary-text",
+ "Show secondary text",
+ "Whether each GdMainBoxItem's secondary-text is "
+ "going to be shown",
+ FALSE,
+ G_PARAM_EXPLICIT_NOTIFY |
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS);
+
+ signals[ITEM_ACTIVATED] = g_signal_new ("item-activated",
+ GD_TYPE_MAIN_BOX,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL,
+ NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE,
+ 1,
+ GD_TYPE_MAIN_BOX_ITEM);
+
+ signals[SELECTION_CHANGED] = g_signal_new ("selection-changed",
+ GD_TYPE_MAIN_BOX,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL,
+ NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ signals[SELECTION_MODE_REQUEST] = g_signal_new ("selection-mode-request",
+ GD_TYPE_MAIN_BOX,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL,
+ NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ g_object_class_install_properties (oclass, NUM_PROPERTIES, properties);
+}
+
+GtkWidget *
+gd_main_box_new (GdMainBoxType type)
+{
+ return g_object_new (GD_TYPE_MAIN_BOX, "box-type", type, NULL);
+}
+
+GdMainBoxType
+gd_main_box_get_box_type (GdMainBox *self)
+{
+ GdMainBoxPrivate *priv;
+
+ priv = gd_main_box_get_instance_private (self);
+ return priv->current_type;
+}
+
+void
+gd_main_box_set_box_type (GdMainBox *self, GdMainBoxType type)
+{
+ GdMainBoxPrivate *priv;
+
+ priv = gd_main_box_get_instance_private (self);
+
+ if (type == priv->current_type)
+ return;
+
+ priv->current_type = type;
+ gd_main_box_rebuild (self);
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_BOX_TYPE]);
+}
+
+gboolean
+gd_main_box_get_selection_mode (GdMainBox *self)
+{
+ GdMainBoxPrivate *priv;
+
+ priv = gd_main_box_get_instance_private (self);
+ return priv->selection_mode;
+}
+
+gboolean
+gd_main_box_get_show_primary_text (GdMainBox *self)
+{
+ GdMainBoxPrivate *priv;
+
+ priv = gd_main_box_get_instance_private (self);
+ return priv->show_primary_text;
+}
+
+gboolean
+gd_main_box_get_show_secondary_text (GdMainBox *self)
+{
+ GdMainBoxPrivate *priv;
+
+ priv = gd_main_box_get_instance_private (self);
+ return priv->show_secondary_text;
+}
+
+void
+gd_main_box_set_selection_mode (GdMainBox *self, gboolean selection_mode)
+{
+ GdMainBoxPrivate *priv;
+
+ priv = gd_main_box_get_instance_private (self);
+
+ if (selection_mode == priv->selection_mode)
+ return;
+
+ priv->selection_mode = selection_mode;
+ gd_main_box_apply_selection_mode (self);
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SELECTION_MODE]);
+}
+
+void
+gd_main_box_set_show_primary_text (GdMainBox *self, gboolean show_primary_text)
+{
+ GdMainBoxPrivate *priv;
+
+ priv = gd_main_box_get_instance_private (self);
+
+ if (show_primary_text == priv->show_primary_text)
+ return;
+
+ priv->show_primary_text = show_primary_text;
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SHOW_PRIMARY_TEXT]);
+}
+
+void
+gd_main_box_set_show_secondary_text (GdMainBox *self, gboolean show_secondary_text)
+{
+ GdMainBoxPrivate *priv;
+
+ priv = gd_main_box_get_instance_private (self);
+
+ if (show_secondary_text == priv->show_secondary_text)
+ return;
+
+ priv->show_secondary_text = show_secondary_text;
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SHOW_SECONDARY_TEXT]);
+}
+
+/**
+ * gd_main_box_get_model:
+ * @self:
+ *
+ * Returns: (transfer none):
+ */
+GListModel *
+gd_main_box_get_model (GdMainBox *self)
+{
+ GdMainBoxPrivate *priv;
+
+ priv = gd_main_box_get_instance_private (self);
+ return priv->model;
+}
+
+/**
+ * gd_main_box_set_model:
+ * @self:
+ * @model: (allow-none):
+ *
+ */
+void
+gd_main_box_set_model (GdMainBox *self, GListModel *model)
+{
+ GdMainBoxPrivate *priv;
+
+ priv = gd_main_box_get_instance_private (self);
+
+ if (!g_set_object (&priv->model, model))
+ return;
+
+ gd_main_box_generic_set_model (GD_MAIN_BOX_GENERIC (priv->current_box), priv->model);
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]);
+}
+
+/**
+ * gd_main_box_get_generic_box:
+ * @self:
+ *
+ * Returns: (transfer none):
+ */
+GtkWidget *
+gd_main_box_get_generic_box (GdMainBox *self)
+{
+ GdMainBoxPrivate *priv;
+
+ priv = gd_main_box_get_instance_private (self);
+ return priv->current_box;
+}
+
+/**
+ * gd_main_box_get_selection:
+ * @self:
+ *
+ * Returns: (element-type GdMainBoxItem) (transfer full):
+ */
+GList *
+gd_main_box_get_selection (GdMainBox *self)
+{
+ GdMainBoxPrivate *priv;
+ GList *l;
+ GList *selected_children;
+ GList *selection = NULL;
+
+ priv = gd_main_box_get_instance_private (self);
+
+ selected_children = gd_main_box_generic_get_selected_children (GD_MAIN_BOX_GENERIC (priv->current_box));
+ for (l = selected_children; l != NULL; l = l->next)
+ {
+ GdMainBoxChild *child = GD_MAIN_BOX_CHILD (l->data);
+ GdMainBoxItem *item;
+
+ item = gd_main_box_child_get_item (child);
+ selection = g_list_prepend (selection, g_object_ref (item));
+ }
+
+ selection = g_list_reverse (selection);
+ g_list_free (selected_children);
+ return selection;
+}
+
+void
+gd_main_box_select_all (GdMainBox *self)
+{
+ GdMainBoxPrivate *priv;
+
+ priv = gd_main_box_get_instance_private (self);
+ gd_main_box_generic_select_all (GD_MAIN_BOX_GENERIC (priv->current_box));
+}
+
+void
+gd_main_box_unselect_all (GdMainBox *self)
+{
+ GdMainBoxPrivate *priv;
+
+ priv = gd_main_box_get_instance_private (self);
+ gd_main_box_generic_unselect_all (GD_MAIN_BOX_GENERIC (priv->current_box));
+}
diff --git a/subprojects/libgd/libgd/gd-main-box.h b/subprojects/libgd/libgd/gd-main-box.h
new file mode 100644
index 0000000..1ff8038
--- /dev/null
+++ b/subprojects/libgd/libgd/gd-main-box.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2016, 2017 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Debarshi Ray <debarshir@gnome.org>
+ *
+ */
+
+#ifndef __GD_MAIN_BOX_H__
+#define __GD_MAIN_BOX_H__
+
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GD_TYPE_MAIN_BOX gd_main_box_get_type()
+G_DECLARE_DERIVABLE_TYPE (GdMainBox, gd_main_box, GD, MAIN_BOX, GtkBin)
+
+typedef enum
+{
+ GD_MAIN_BOX_ICON,
+ GD_MAIN_BOX_LIST
+} GdMainBoxType;
+
+struct _GdMainBoxClass
+{
+ GtkBinClass parent_class;
+};
+
+GtkWidget * gd_main_box_new (GdMainBoxType type);
+GdMainBoxType gd_main_box_get_box_type (GdMainBox *self);
+GListModel * gd_main_box_get_model (GdMainBox *self);
+GList * gd_main_box_get_selection (GdMainBox *self);
+gboolean gd_main_box_get_selection_mode (GdMainBox *self);
+gboolean gd_main_box_get_show_primary_text (GdMainBox *self);
+gboolean gd_main_box_get_show_secondary_text (GdMainBox *self);
+void gd_main_box_select_all (GdMainBox *self);
+void gd_main_box_set_box_type (GdMainBox *self, GdMainBoxType type);
+void gd_main_box_set_model (GdMainBox *self, GListModel *model);
+void gd_main_box_set_selection_mode (GdMainBox *self, gboolean selection_mode);
+void gd_main_box_set_show_primary_text (GdMainBox *self, gboolean show_primary_text);
+void gd_main_box_set_show_secondary_text (GdMainBox *self, gboolean show_secondary_text);
+void gd_main_box_unselect_all (GdMainBox *self);
+
+G_END_DECLS
+
+#endif /* __GD_MAIN_BOX_H__ */
diff --git a/subprojects/libgd/libgd/gd-main-icon-box-child.c b/subprojects/libgd/libgd/gd-main-icon-box-child.c
new file mode 100644
index 0000000..3e24694
--- /dev/null
+++ b/subprojects/libgd/libgd/gd-main-icon-box-child.c
@@ -0,0 +1,439 @@
+/*
+ * Copyright (c) 2016, 2017 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Debarshi Ray <debarshir@gnome.org>
+ *
+ */
+
+#include "gd-main-box-child.h"
+#include "gd-main-icon-box-child.h"
+#include "gd-main-icon-box-icon.h"
+
+#include <gio/gio.h>
+#include <glib.h>
+
+typedef struct _GdMainIconBoxChildPrivate GdMainIconBoxChildPrivate;
+
+struct _GdMainIconBoxChildPrivate
+{
+ GdMainBoxItem *item;
+ GtkWidget *check_button;
+ gboolean selection_mode;
+ gboolean show_primary_text;
+ gboolean show_secondary_text;
+};
+
+enum
+{
+ PROP_ITEM = 1,
+ PROP_SELECTION_MODE,
+ PROP_SHOW_PRIMARY_TEXT,
+ PROP_SHOW_SECONDARY_TEXT,
+ NUM_PROPERTIES
+};
+
+static void gd_main_box_child_interface_init (GdMainBoxChildInterface *iface);
+G_DEFINE_TYPE_WITH_CODE (GdMainIconBoxChild, gd_main_icon_box_child, GTK_TYPE_FLOW_BOX_CHILD,
+ G_ADD_PRIVATE (GdMainIconBoxChild)
+ G_IMPLEMENT_INTERFACE (GD_TYPE_MAIN_BOX_CHILD, gd_main_box_child_interface_init))
+
+static gboolean
+gd_main_icon_box_child_bind_text_to_visible (GBinding *binding,
+ const GValue *from_value,
+ GValue *to_value,
+ gpointer user_data)
+{
+ gboolean to_visible;
+ const gchar *from_text;
+
+ from_text = g_value_get_string (from_value);
+ to_visible = from_text != NULL && from_text[0] != '\0' ? TRUE : FALSE;
+ g_value_set_boolean (to_value, to_visible);
+ return TRUE;
+}
+
+static void
+gd_main_icon_box_check_button_toggled (GdMainIconBoxChild *self)
+{
+ GdMainIconBoxChildPrivate *priv;
+ GtkWidget *parent;
+
+ priv = gd_main_icon_box_child_get_instance_private (self);
+
+ parent = gtk_widget_get_parent (GTK_WIDGET (self));
+ if (!GTK_IS_FLOW_BOX (parent))
+ return;
+
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->check_button)))
+ gtk_flow_box_select_child (GTK_FLOW_BOX (parent), GTK_FLOW_BOX_CHILD (self));
+ else
+ gtk_flow_box_unselect_child (GTK_FLOW_BOX (parent), GTK_FLOW_BOX_CHILD (self));
+}
+
+static void
+gd_main_icon_box_child_update_layout (GdMainIconBoxChild *self)
+{
+ GdMainIconBoxChildPrivate *priv;
+ GtkWidget *grid;
+ GtkWidget *icon;
+ GtkWidget *overlay;
+
+ priv = gd_main_icon_box_child_get_instance_private (self);
+
+ gtk_container_foreach (GTK_CONTAINER (self), (GtkCallback) gtk_widget_destroy, NULL);
+
+ grid = gtk_grid_new ();
+ gtk_widget_set_valign (grid, GTK_ALIGN_CENTER);
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (grid), GTK_ORIENTATION_VERTICAL);
+ gtk_container_add (GTK_CONTAINER (self), grid);
+
+ overlay = gtk_overlay_new ();
+ gtk_container_add (GTK_CONTAINER (grid), overlay);
+
+ icon = gd_main_icon_box_icon_new (priv->item);
+ gtk_widget_set_hexpand (icon, TRUE);
+ gtk_container_add (GTK_CONTAINER (overlay), icon);
+
+ if (gd_main_box_item_get_pulse (priv->item))
+ {
+ GtkWidget *spinner;
+
+ spinner = gtk_spinner_new ();
+ gtk_widget_set_halign (spinner, GTK_ALIGN_CENTER);
+ gtk_widget_set_size_request (spinner, 32, 32);
+ gtk_widget_set_valign (spinner, GTK_ALIGN_CENTER);
+ gtk_spinner_start (GTK_SPINNER (spinner));
+ gtk_overlay_add_overlay (GTK_OVERLAY (overlay), spinner);
+ }
+
+ priv->check_button = gtk_check_button_new ();
+ gtk_widget_set_can_focus (priv->check_button, FALSE);
+ gtk_widget_set_halign (priv->check_button, GTK_ALIGN_END);
+ gtk_widget_set_valign (priv->check_button, GTK_ALIGN_END);
+ gtk_widget_set_no_show_all (priv->check_button, TRUE);
+ g_object_bind_property (self, "selection-mode", priv->check_button, "visible", G_BINDING_SYNC_CREATE);
+ gtk_overlay_add_overlay (GTK_OVERLAY (overlay), priv->check_button);
+ g_signal_connect_swapped (priv->check_button,
+ "toggled",
+ G_CALLBACK (gd_main_icon_box_check_button_toggled),
+ self);
+
+ if (priv->show_primary_text)
+ {
+ GtkWidget *primary_label;
+
+ primary_label = gtk_label_new (NULL);
+ gtk_widget_set_no_show_all (primary_label, TRUE);
+ gtk_label_set_ellipsize (GTK_LABEL (primary_label), PANGO_ELLIPSIZE_MIDDLE);
+ g_object_bind_property (priv->item, "primary-text", primary_label, "label", G_BINDING_SYNC_CREATE);
+ g_object_bind_property_full (priv->item,
+ "primary-text",
+ primary_label,
+ "visible",
+ G_BINDING_SYNC_CREATE,
+ gd_main_icon_box_child_bind_text_to_visible,
+ NULL,
+ NULL,
+ NULL);
+ gtk_container_add (GTK_CONTAINER (grid), primary_label);
+ }
+
+ if (priv->show_secondary_text)
+ {
+ GtkStyleContext *context;
+ GtkWidget *secondary_label;
+
+ secondary_label = gtk_label_new (NULL);
+ gtk_widget_set_no_show_all (secondary_label, TRUE);
+ gtk_label_set_ellipsize (GTK_LABEL (secondary_label), PANGO_ELLIPSIZE_END);
+ context = gtk_widget_get_style_context (secondary_label);
+ gtk_style_context_add_class (context, "dim-label");
+ g_object_bind_property (priv->item, "secondary-text", secondary_label, "label", G_BINDING_SYNC_CREATE);
+ g_object_bind_property_full (priv->item,
+ "secondary-text",
+ secondary_label,
+ "visible",
+ G_BINDING_SYNC_CREATE,
+ gd_main_icon_box_child_bind_text_to_visible,
+ NULL,
+ NULL,
+ NULL);
+ gtk_container_add (GTK_CONTAINER (grid), secondary_label);
+ }
+
+ gtk_widget_show_all (grid);
+}
+
+static void
+gd_main_icon_box_child_notify_pulse (GdMainIconBoxChild *self)
+{
+ gd_main_icon_box_child_update_layout (self);
+}
+
+static GdMainBoxItem *
+gd_main_icon_box_child_get_item (GdMainBoxChild *child)
+{
+ GdMainIconBoxChild *self = GD_MAIN_ICON_BOX_CHILD (child);
+ GdMainIconBoxChildPrivate *priv;
+
+ priv = gd_main_icon_box_child_get_instance_private (self);
+ return priv->item;
+}
+
+static gint
+gd_main_icon_box_child_get_index (GdMainBoxChild *child)
+{
+ GdMainIconBoxChild *self = GD_MAIN_ICON_BOX_CHILD (child);
+ gint index;
+
+ index = gtk_flow_box_child_get_index (GTK_FLOW_BOX_CHILD (self));
+ return index;
+}
+
+static gboolean
+gd_main_icon_box_child_get_selected (GdMainBoxChild *child)
+{
+ GdMainIconBoxChild *self = GD_MAIN_ICON_BOX_CHILD (child);
+ gboolean selected;
+
+ selected = gtk_flow_box_child_is_selected (GTK_FLOW_BOX_CHILD (self));
+ return selected;
+}
+
+static gboolean
+gd_main_icon_box_child_get_selection_mode (GdMainIconBoxChild *self)
+{
+ GdMainIconBoxChildPrivate *priv;
+
+ priv = gd_main_icon_box_child_get_instance_private (self);
+ return priv->selection_mode;
+}
+
+static gboolean
+gd_main_icon_box_child_get_show_primary_text (GdMainIconBoxChild *self)
+{
+ GdMainIconBoxChildPrivate *priv;
+
+ priv = gd_main_icon_box_child_get_instance_private (self);
+ return priv->show_primary_text;
+}
+
+static gboolean
+gd_main_icon_box_child_get_show_secondary_text (GdMainIconBoxChild *self)
+{
+ GdMainIconBoxChildPrivate *priv;
+
+ priv = gd_main_icon_box_child_get_instance_private (self);
+ return priv->show_secondary_text;
+}
+
+static void
+gd_main_icon_box_child_set_item (GdMainIconBoxChild *self, GdMainBoxItem *item)
+{
+ GdMainIconBoxChildPrivate *priv;
+
+ priv = gd_main_icon_box_child_get_instance_private (self);
+
+ if (priv->item != NULL)
+ g_signal_handlers_disconnect_by_func (priv->item, gd_main_icon_box_child_notify_pulse, self);
+
+ if (!g_set_object (&priv->item, item))
+ return;
+
+ if (priv->item != NULL)
+ {
+ g_signal_connect_object (priv->item,
+ "notify::pulse",
+ G_CALLBACK (gd_main_icon_box_child_notify_pulse),
+ self,
+ G_CONNECT_SWAPPED);
+ }
+
+ g_object_notify (G_OBJECT (self), "item");
+ gtk_widget_queue_draw (GTK_WIDGET (self));
+}
+
+static void
+gd_main_icon_box_child_set_selected (GdMainBoxChild *child, gboolean selected)
+{
+ GdMainIconBoxChild *self = GD_MAIN_ICON_BOX_CHILD (child);
+ GdMainIconBoxChildPrivate *priv;
+
+ priv = gd_main_icon_box_child_get_instance_private (self);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->check_button), selected);
+}
+
+static void
+gd_main_icon_box_child_set_selection_mode (GdMainIconBoxChild *self, gboolean selection_mode)
+{
+ GdMainIconBoxChildPrivate *priv;
+
+ priv = gd_main_icon_box_child_get_instance_private (self);
+
+ if (priv->selection_mode == selection_mode)
+ return;
+
+ priv->selection_mode = selection_mode;
+ g_object_notify (G_OBJECT (self), "selection-mode");
+ gtk_widget_queue_draw (GTK_WIDGET (self));
+}
+
+static void
+gd_main_icon_box_child_set_show_primary_text (GdMainIconBoxChild *self, gboolean show_primary_text)
+{
+ GdMainIconBoxChildPrivate *priv;
+
+ priv = gd_main_icon_box_child_get_instance_private (self);
+
+ if (priv->show_primary_text == show_primary_text)
+ return;
+
+ priv->show_primary_text = show_primary_text;
+ gd_main_icon_box_child_update_layout (self);
+ g_object_notify (G_OBJECT (self), "show-primary-text");
+}
+
+static void
+gd_main_icon_box_child_set_show_secondary_text (GdMainIconBoxChild *self, gboolean show_secondary_text)
+{
+ GdMainIconBoxChildPrivate *priv;
+
+ priv = gd_main_icon_box_child_get_instance_private (self);
+
+ if (priv->show_secondary_text == show_secondary_text)
+ return;
+
+ priv->show_secondary_text = show_secondary_text;
+ gd_main_icon_box_child_update_layout (self);
+ g_object_notify (G_OBJECT (self), "show-secondary-text");
+}
+
+static void
+gd_main_icon_box_child_constructed (GObject *obj)
+{
+ GdMainIconBoxChild *self = GD_MAIN_ICON_BOX_CHILD (obj);
+
+ G_OBJECT_CLASS (gd_main_icon_box_child_parent_class)->constructed (obj);
+
+ gd_main_icon_box_child_update_layout (self);
+}
+
+static void
+gd_main_icon_box_child_dispose (GObject *obj)
+{
+ GdMainIconBoxChild *self = GD_MAIN_ICON_BOX_CHILD (obj);
+ GdMainIconBoxChildPrivate *priv;
+
+ priv = gd_main_icon_box_child_get_instance_private (self);
+
+ g_clear_object (&priv->item);
+
+ G_OBJECT_CLASS (gd_main_icon_box_child_parent_class)->dispose (obj);
+}
+
+static void
+gd_main_icon_box_child_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
+{
+ GdMainIconBoxChild *self = GD_MAIN_ICON_BOX_CHILD (object);
+
+ switch (property_id)
+ {
+ case PROP_ITEM:
+ g_value_set_object (value, gd_main_icon_box_child_get_item (GD_MAIN_BOX_CHILD (self)));
+ break;
+ case PROP_SELECTION_MODE:
+ g_value_set_boolean (value, gd_main_icon_box_child_get_selection_mode (self));
+ break;
+ case PROP_SHOW_PRIMARY_TEXT:
+ g_value_set_boolean (value, gd_main_icon_box_child_get_show_primary_text (self));
+ break;
+ case PROP_SHOW_SECONDARY_TEXT:
+ g_value_set_boolean (value, gd_main_icon_box_child_get_show_secondary_text (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gd_main_icon_box_child_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
+{
+ GdMainIconBoxChild *self = GD_MAIN_ICON_BOX_CHILD (object);
+
+ switch (property_id)
+ {
+ case PROP_ITEM:
+ gd_main_icon_box_child_set_item (self, g_value_get_object (value));
+ break;
+ case PROP_SELECTION_MODE:
+ gd_main_icon_box_child_set_selection_mode (self, g_value_get_boolean (value));
+ break;
+ case PROP_SHOW_PRIMARY_TEXT:
+ gd_main_icon_box_child_set_show_primary_text (self, g_value_get_boolean (value));
+ break;
+ case PROP_SHOW_SECONDARY_TEXT:
+ gd_main_icon_box_child_set_show_secondary_text (self, g_value_get_boolean (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gd_main_icon_box_child_init (GdMainIconBoxChild *self)
+{
+ GtkStyleContext *context;
+
+ context = gtk_widget_get_style_context (GTK_WIDGET (self));
+ gtk_style_context_add_class (context, "tile");
+}
+
+static void
+gd_main_icon_box_child_class_init (GdMainIconBoxChildClass *klass)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+
+ oclass->constructed = gd_main_icon_box_child_constructed;
+ oclass->dispose = gd_main_icon_box_child_dispose;
+ oclass->get_property = gd_main_icon_box_child_get_property;
+ oclass->set_property = gd_main_icon_box_child_set_property;
+
+ g_object_class_override_property (oclass, PROP_ITEM, "item");
+ g_object_class_override_property (oclass, PROP_SELECTION_MODE, "selection-mode");
+ g_object_class_override_property (oclass, PROP_SHOW_PRIMARY_TEXT, "show-primary-text");
+ g_object_class_override_property (oclass, PROP_SHOW_SECONDARY_TEXT, "show-secondary-text");
+}
+
+static void
+gd_main_box_child_interface_init (GdMainBoxChildInterface *iface)
+{
+ iface->get_index = gd_main_icon_box_child_get_index;
+ iface->get_item = gd_main_icon_box_child_get_item;
+ iface->get_selected = gd_main_icon_box_child_get_selected;
+ iface->set_selected = gd_main_icon_box_child_set_selected;
+}
+
+GtkWidget *
+gd_main_icon_box_child_new (GdMainBoxItem *item, gboolean selection_mode)
+{
+ return g_object_new (GD_TYPE_MAIN_ICON_BOX_CHILD,
+ "item", item,
+ "selection-mode", selection_mode,
+ NULL);
+}
diff --git a/subprojects/libgd/libgd/gd-main-icon-box-child.h b/subprojects/libgd/libgd/gd-main-icon-box-child.h
new file mode 100644
index 0000000..40eb271
--- /dev/null
+++ b/subprojects/libgd/libgd/gd-main-icon-box-child.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2016 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Debarshi Ray <debarshir@gnome.org>
+ *
+ */
+
+#ifndef __GD_MAIN_ICON_BOX_CHILD_H__
+#define __GD_MAIN_ICON_BOX_CHILD_H__
+
+#include <gtk/gtk.h>
+
+#include "gd-main-box-item.h"
+
+G_BEGIN_DECLS
+
+#define GD_TYPE_MAIN_ICON_BOX_CHILD gd_main_icon_box_child_get_type()
+G_DECLARE_DERIVABLE_TYPE (GdMainIconBoxChild, gd_main_icon_box_child, GD, MAIN_ICON_BOX_CHILD, GtkFlowBoxChild)
+
+struct _GdMainIconBoxChildClass
+{
+ GtkFlowBoxChildClass parent_class;
+};
+
+GtkWidget * gd_main_icon_box_child_new (GdMainBoxItem *item, gboolean selection_mode);
+
+G_END_DECLS
+
+#endif /* __GD_MAIN_ICON_BOX_CHILD_H__ */
diff --git a/subprojects/libgd/libgd/gd-main-icon-box-icon.c b/subprojects/libgd/libgd/gd-main-icon-box-icon.c
new file mode 100644
index 0000000..e05efba
--- /dev/null
+++ b/subprojects/libgd/libgd/gd-main-icon-box-icon.c
@@ -0,0 +1,366 @@
+/*
+ * Copyright (c) 2017 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Debarshi Ray <debarshir@gnome.org>
+ *
+ */
+
+#include "gd-main-icon-box-icon.h"
+
+#include <cairo.h>
+#include <glib.h>
+
+struct _GdMainIconBoxIcon
+{
+ GtkDrawingArea parent_instance;
+ GdMainBoxItem *item;
+ cairo_surface_t *surface_zoomed;
+ gdouble x;
+ gdouble y;
+};
+
+enum
+{
+ PROP_ITEM = 1,
+ NUM_PROPERTIES
+};
+
+static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
+
+G_DEFINE_TYPE (GdMainIconBoxIcon, gd_main_icon_box_icon, GTK_TYPE_DRAWING_AREA)
+
+static cairo_surface_t *
+gd_zoom_image_surface (cairo_surface_t *surface, gint width_zoomed, gint height_zoomed)
+{
+ cairo_t *cr;
+ cairo_format_t format;
+ cairo_pattern_t *pattern;
+ cairo_surface_t *zoomed = NULL;
+ cairo_surface_type_t surface_type;
+ gdouble scale_x;
+ gdouble scale_y;
+ gdouble zoom_x;
+ gdouble zoom_y;
+ gint height;
+ gint width;
+
+ g_return_val_if_fail (surface != NULL, NULL);
+
+ surface_type = cairo_surface_get_type (surface);
+ g_return_val_if_fail (surface_type == CAIRO_SURFACE_TYPE_IMAGE, NULL);
+
+ format = cairo_image_surface_get_format (surface);
+ zoomed = cairo_surface_create_similar_image (surface, format, width_zoomed, height_zoomed);
+ cairo_surface_get_device_scale (surface, &scale_x, &scale_y);
+ cairo_surface_set_device_scale (zoomed, scale_x, scale_y);
+
+ cr = cairo_create (zoomed);
+
+ pattern = cairo_get_source (cr);
+ cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REFLECT);
+ cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+
+ height = cairo_image_surface_get_height (surface);
+ width = cairo_image_surface_get_width (surface);
+ zoom_x = (double) width_zoomed / (gdouble) width;
+ zoom_y = (double) height_zoomed / (gdouble) height;
+ cairo_scale (cr, zoom_x, zoom_y);
+ cairo_set_source_surface (cr, surface, 0, 0);
+
+ cairo_paint (cr);
+ cairo_destroy (cr);
+
+ return zoomed;
+}
+
+static void
+gd_main_icon_box_icon_get_preferred_size (GdMainIconBoxIcon *self, gint *minimum, gint *natural)
+{
+ cairo_surface_t *surface;
+ cairo_surface_type_t surface_type;
+ gint height_scaled;
+ gint width_scaled;
+ gint scale_factor;
+ gint size = 0;
+ gint size_scaled;
+
+ surface = gd_main_box_item_get_icon (self->item);
+ if (surface == NULL)
+ goto out;
+
+ surface_type = cairo_surface_get_type (surface);
+ g_return_if_fail (surface_type == CAIRO_SURFACE_TYPE_IMAGE);
+
+ scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (self));
+ height_scaled = cairo_image_surface_get_height (surface);
+ width_scaled = cairo_image_surface_get_width (surface);
+
+ size_scaled = MAX (height_scaled, width_scaled);
+ size = size_scaled / scale_factor;
+
+ out:
+ if (minimum != NULL)
+ *minimum = size;
+
+ if (natural != NULL)
+ *natural = size;
+}
+
+static void
+gd_main_icon_box_icon_notify_icon (GdMainIconBoxIcon *self)
+{
+ g_clear_pointer (&self->surface_zoomed, (GDestroyNotify) cairo_surface_destroy);
+ gtk_widget_queue_resize (GTK_WIDGET (self));
+}
+
+static gboolean
+gd_main_icon_box_icon_draw (GtkWidget *widget, cairo_t *cr)
+{
+ GdMainIconBoxIcon *self = GD_MAIN_ICON_BOX_ICON (widget);
+
+ if (self->surface_zoomed == NULL)
+ goto out;
+
+ cairo_save (cr);
+ cairo_set_source_surface (cr, self->surface_zoomed, self->x, self->y);
+ cairo_paint (cr);
+ cairo_restore (cr);
+
+ out:
+ return GDK_EVENT_PROPAGATE;
+}
+
+static void
+gd_main_icon_box_icon_get_preferred_height (GtkWidget *widget, gint *minimum, gint *natural)
+{
+ GdMainIconBoxIcon *self = GD_MAIN_ICON_BOX_ICON (widget);
+ gd_main_icon_box_icon_get_preferred_size (self, minimum, natural);
+}
+
+static void
+gd_main_icon_box_icon_get_preferred_width (GtkWidget *widget, gint *minimum, gint *natural)
+{
+ GdMainIconBoxIcon *self = GD_MAIN_ICON_BOX_ICON (widget);
+ gd_main_icon_box_icon_get_preferred_size (self, minimum, natural);
+}
+
+static void
+gd_main_icon_box_icon_size_allocate (GtkWidget *widget, GtkAllocation *allocation)
+{
+ GdMainIconBoxIcon *self = GD_MAIN_ICON_BOX_ICON (widget);
+ cairo_surface_t *surface;
+ cairo_surface_type_t surface_type;
+ gdouble zoom;
+ gint allocation_height_scaled;
+ gint allocation_width_scaled;
+ gint height_scaled;
+ gint height_zoomed_scaled;
+ gint scale_factor;
+ gint width_scaled;
+ gint width_zoomed_scaled;
+
+ GTK_WIDGET_CLASS (gd_main_icon_box_icon_parent_class)->size_allocate (widget, allocation);
+
+ surface = gd_main_box_item_get_icon (self->item);
+ if (surface == NULL)
+ {
+ g_return_if_fail (self->surface_zoomed == NULL);
+ return;
+ }
+
+ surface_type = cairo_surface_get_type (surface);
+ g_return_if_fail (surface_type == CAIRO_SURFACE_TYPE_IMAGE);
+
+ scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (self));
+
+ allocation_height_scaled = allocation->height * scale_factor;
+ allocation_width_scaled = allocation->width * scale_factor;
+
+ if (self->surface_zoomed != NULL)
+ {
+ height_zoomed_scaled = cairo_image_surface_get_height (self->surface_zoomed);
+ width_zoomed_scaled = cairo_image_surface_get_width (self->surface_zoomed);
+ if (height_zoomed_scaled == allocation_height_scaled && width_zoomed_scaled == allocation_width_scaled)
+ return;
+ }
+
+ height_scaled = cairo_image_surface_get_height (surface);
+ width_scaled = cairo_image_surface_get_width (surface);
+
+ if (height_scaled > width_scaled && allocation_height_scaled > height_scaled)
+ {
+ zoom = (gdouble) allocation_height_scaled / (gdouble) height_scaled;
+ height_zoomed_scaled = allocation_height_scaled;
+ width_zoomed_scaled = (gint) (zoom * (gdouble) width_scaled + 0.5);
+
+ if (allocation_width_scaled < width_zoomed_scaled)
+ {
+ zoom = (gdouble) allocation_width_scaled / (gdouble) width_zoomed_scaled;
+ height_zoomed_scaled = (gint) (zoom * (gdouble) height_zoomed_scaled + 0.5);
+ width_zoomed_scaled = allocation_width_scaled;
+ }
+ }
+ else if (height_scaled <= width_scaled && allocation_width_scaled > width_scaled)
+ {
+ zoom = (gdouble) allocation_width_scaled / (gdouble) width_scaled;
+ height_zoomed_scaled = (gint) (zoom * (gdouble) height_scaled + 0.5);
+ width_zoomed_scaled = allocation_width_scaled;
+
+ if (allocation_height_scaled < height_zoomed_scaled)
+ {
+ zoom = (gdouble) allocation_height_scaled / (gdouble) height_zoomed_scaled;
+ height_zoomed_scaled = allocation_height_scaled;
+ width_zoomed_scaled = (gint) (zoom * (gdouble) width_zoomed_scaled + 0.5);
+ }
+ }
+ else
+ {
+ height_zoomed_scaled = height_scaled;
+ width_zoomed_scaled = width_scaled;
+ }
+
+ g_clear_pointer (&self->surface_zoomed, (GDestroyNotify) cairo_surface_destroy);
+ self->surface_zoomed = gd_zoom_image_surface (surface, width_zoomed_scaled, height_zoomed_scaled);
+
+ self->x = (gdouble) (allocation_width_scaled - width_zoomed_scaled) / (2.0 * (gdouble) scale_factor);
+ self->y = (gdouble) (allocation_height_scaled - height_zoomed_scaled) / (2.0 * (gdouble) scale_factor);
+}
+
+static void
+gd_main_icon_box_icon_dispose (GObject *obj)
+{
+ GdMainIconBoxIcon *self = GD_MAIN_ICON_BOX_ICON (obj);
+
+ g_clear_object (&self->item);
+
+ G_OBJECT_CLASS (gd_main_icon_box_icon_parent_class)->dispose (obj);
+}
+
+static void
+gd_main_icon_box_icon_finalize (GObject *obj)
+{
+ GdMainIconBoxIcon *self = GD_MAIN_ICON_BOX_ICON (obj);
+
+ g_clear_pointer (&self->surface_zoomed, (GDestroyNotify) cairo_surface_destroy);
+
+ G_OBJECT_CLASS (gd_main_icon_box_icon_parent_class)->finalize (obj);
+}
+
+static void
+gd_main_icon_box_icon_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
+{
+ GdMainIconBoxIcon *self = GD_MAIN_ICON_BOX_ICON (object);
+
+ switch (property_id)
+ {
+ case PROP_ITEM:
+ g_value_set_object (value, gd_main_icon_box_icon_get_item (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gd_main_icon_box_icon_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
+{
+ GdMainIconBoxIcon *self = GD_MAIN_ICON_BOX_ICON (object);
+
+ switch (property_id)
+ {
+ case PROP_ITEM:
+ gd_main_icon_box_icon_set_item (self, g_value_get_object (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gd_main_icon_box_icon_init (GdMainIconBoxIcon *self)
+{
+}
+
+static void
+gd_main_icon_box_icon_class_init (GdMainIconBoxIconClass *klass)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *wclass = GTK_WIDGET_CLASS (klass);
+
+ oclass->dispose = gd_main_icon_box_icon_dispose;
+ oclass->finalize = gd_main_icon_box_icon_finalize;
+ oclass->get_property = gd_main_icon_box_icon_get_property;
+ oclass->set_property = gd_main_icon_box_icon_set_property;
+ wclass->draw = gd_main_icon_box_icon_draw;
+ wclass->get_preferred_height = gd_main_icon_box_icon_get_preferred_height;
+ wclass->get_preferred_width = gd_main_icon_box_icon_get_preferred_width;
+ wclass->size_allocate = gd_main_icon_box_icon_size_allocate;
+
+ properties[PROP_ITEM] = g_param_spec_object ("item",
+ "Item",
+ "An item that is rendered by the widget",
+ GD_TYPE_MAIN_BOX_ITEM,
+ G_PARAM_EXPLICIT_NOTIFY |
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (oclass, NUM_PROPERTIES, properties);
+}
+
+GtkWidget *
+gd_main_icon_box_icon_new (GdMainBoxItem *item)
+{
+ g_return_val_if_fail (item == NULL || GD_IS_MAIN_BOX_ITEM (item), NULL);
+ return g_object_new (GD_TYPE_MAIN_ICON_BOX_ICON, "item", item, NULL);
+}
+
+GdMainBoxItem *
+gd_main_icon_box_icon_get_item (GdMainIconBoxIcon *self)
+{
+ g_return_val_if_fail (GD_IS_MAIN_ICON_BOX_ICON (self), NULL);
+ return self->item;
+}
+
+void
+gd_main_icon_box_icon_set_item (GdMainIconBoxIcon *self, GdMainBoxItem *item)
+{
+ g_return_if_fail (GD_IS_MAIN_ICON_BOX_ICON (self));
+ g_return_if_fail (item == NULL || GD_IS_MAIN_BOX_ITEM (item));
+
+ if (self->item == item)
+ return;
+
+ if (self->item != NULL)
+ g_signal_handlers_disconnect_by_func (self->item, gd_main_icon_box_icon_notify_icon, self);
+
+ g_clear_pointer (&self->surface_zoomed, (GDestroyNotify) cairo_surface_destroy);
+ g_set_object (&self->item, item);
+
+ if (self->item != NULL)
+ {
+ g_signal_connect_object (self->item,
+ "notify::icon",
+ G_CALLBACK (gd_main_icon_box_icon_notify_icon),
+ self,
+ G_CONNECT_SWAPPED);
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ITEM]);
+ gtk_widget_queue_resize (GTK_WIDGET (self));
+}
diff --git a/subprojects/libgd/libgd/gd-main-icon-box-icon.h b/subprojects/libgd/libgd/gd-main-icon-box-icon.h
new file mode 100644
index 0000000..7189e0e
--- /dev/null
+++ b/subprojects/libgd/libgd/gd-main-icon-box-icon.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2017 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Debarshi Ray <debarshir@gnome.org>
+ *
+ */
+
+#ifndef __GD_MAIN_ICON_BOX_ICON_H__
+#define __GD_MAIN_ICON_BOX_ICON_H__
+
+#include <gtk/gtk.h>
+
+#include "gd-main-box-item.h"
+
+G_BEGIN_DECLS
+
+#define GD_TYPE_MAIN_ICON_BOX_ICON gd_main_icon_box_icon_get_type()
+G_DECLARE_FINAL_TYPE (GdMainIconBoxIcon, gd_main_icon_box_icon, GD, MAIN_ICON_BOX_ICON, GtkDrawingArea)
+
+GtkWidget * gd_main_icon_box_icon_new (GdMainBoxItem *item);
+GdMainBoxItem * gd_main_icon_box_icon_get_item (GdMainIconBoxIcon *self);
+void gd_main_icon_box_icon_set_item (GdMainIconBoxIcon *self, GdMainBoxItem *item);
+
+G_END_DECLS
+
+#endif /* __GD_MAIN_ICON_BOX_ICON_H__ */
diff --git a/subprojects/libgd/libgd/gd-main-icon-box.c b/subprojects/libgd/libgd/gd-main-icon-box.c
new file mode 100644
index 0000000..341df19
--- /dev/null
+++ b/subprojects/libgd/libgd/gd-main-icon-box.c
@@ -0,0 +1,1042 @@
+/*
+ * Copyright (c) 2016, 2017 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Debarshi Ray <debarshir@gnome.org>
+ *
+ */
+
+#include <math.h>
+
+#include <cairo.h>
+#include <gio/gio.h>
+
+#include "gd-icon-utils.h"
+#include "gd-main-icon-box.h"
+#include "gd-main-icon-box-child.h"
+#include "gd-main-box-child.h"
+#include "gd-main-box-generic.h"
+#include "gd-main-box-item.h"
+
+#define MAIN_ICON_BOX_DND_ICON_OFFSET 20
+
+typedef struct _GdMainIconBoxPrivate GdMainIconBoxPrivate;
+
+struct _GdMainIconBoxPrivate
+{
+ GListModel *model;
+ gboolean dnd_started;
+ gboolean key_pressed;
+ gboolean key_shift_pressed;
+ gboolean left_button_released;
+ gboolean left_button_shift_released;
+ gboolean selection_changed;
+ gboolean selection_mode;
+ gboolean show_primary_text;
+ gboolean show_secondary_text;
+ gchar *last_selected_id;
+ gdouble dnd_start_x;
+ gdouble dnd_start_y;
+ gint dnd_button;
+};
+
+enum
+{
+ PROP_LAST_SELECTED_ID = 1,
+ PROP_MODEL,
+ PROP_SELECTION_MODE,
+ PROP_SHOW_PRIMARY_TEXT,
+ PROP_SHOW_SECONDARY_TEXT,
+ NUM_PROPERTIES
+};
+
+static void gd_main_box_generic_interface_init (GdMainBoxGenericInterface *iface);
+G_DEFINE_TYPE_WITH_CODE (GdMainIconBox, gd_main_icon_box, GTK_TYPE_FLOW_BOX,
+ G_ADD_PRIVATE (GdMainIconBox)
+ G_IMPLEMENT_INTERFACE (GD_TYPE_MAIN_BOX_GENERIC, gd_main_box_generic_interface_init))
+
+GtkWidget *
+gd_main_icon_box_create_widget_func (gpointer item, gpointer user_data)
+{
+ GdMainIconBox *self = GD_MAIN_ICON_BOX (user_data);
+ GdMainIconBoxPrivate *priv;
+ GtkWidget *child;
+
+ g_return_val_if_fail (GD_IS_MAIN_BOX_ITEM (item), NULL);
+
+ priv = gd_main_icon_box_get_instance_private (self);
+
+ child = gd_main_icon_box_child_new (GD_MAIN_BOX_ITEM (item), priv->selection_mode);
+ g_object_bind_property (self, "show-primary-text", child, "show-primary-text", G_BINDING_SYNC_CREATE);
+ g_object_bind_property (self, "show-secondary-text", child, "show-secondary-text", G_BINDING_SYNC_CREATE);
+ gtk_widget_show_all (child);
+
+ return child;
+}
+
+static void
+gd_main_icon_box_update_last_selected_id (GdMainIconBox *self, GdMainBoxChild *child)
+{
+ GdMainIconBoxPrivate *priv;
+ GdMainBoxItem *item;
+ const gchar *id = NULL;
+
+ priv = gd_main_icon_box_get_instance_private (self);
+
+ if (child != NULL)
+ {
+ item = gd_main_box_child_get_item (child);
+ id = gd_main_box_item_get_id (item);
+ }
+
+ if (g_strcmp0 (priv->last_selected_id, id) != 0)
+ {
+ g_free (priv->last_selected_id);
+ priv->last_selected_id = g_strdup (id);
+ g_object_notify (G_OBJECT (self), "last-selected-id");
+ }
+}
+
+static GdMainBoxChild *
+gd_main_icon_box_get_child_at_index (GdMainBoxGeneric *generic, gint index)
+{
+ GdMainIconBox *self = GD_MAIN_ICON_BOX (generic);
+ GtkFlowBoxChild *child;
+
+ child = gtk_flow_box_get_child_at_index (GTK_FLOW_BOX (self), index);
+ return GD_MAIN_BOX_CHILD (child);
+}
+
+static const gchar *
+gd_main_icon_box_get_last_selected_id (GdMainBoxGeneric *generic)
+{
+ GdMainIconBox *self = GD_MAIN_ICON_BOX (generic);
+ GdMainIconBoxPrivate *priv;
+
+ priv = gd_main_icon_box_get_instance_private (self);
+ return priv->last_selected_id;
+}
+
+static GListModel *
+gd_main_icon_box_get_model (GdMainBoxGeneric *generic)
+{
+ GdMainIconBox *self = GD_MAIN_ICON_BOX (generic);
+ GdMainIconBoxPrivate *priv;
+
+ priv = gd_main_icon_box_get_instance_private (self);
+ return priv->model;
+}
+
+static GList *
+gd_main_icon_box_get_selected_children (GdMainBoxGeneric *generic)
+{
+ GdMainIconBox *self = GD_MAIN_ICON_BOX (generic);
+ GList *selected_children;
+
+ selected_children = gtk_flow_box_get_selected_children (GTK_FLOW_BOX (self));
+ return selected_children;
+}
+
+static gboolean
+gd_main_icon_box_get_selection_mode (GdMainIconBox *self)
+{
+ GdMainIconBoxPrivate *priv;
+
+ priv = gd_main_icon_box_get_instance_private (self);
+ return priv->selection_mode;
+}
+
+static gboolean
+gd_main_icon_box_get_show_primary_text (GdMainIconBox *self)
+{
+ GdMainIconBoxPrivate *priv;
+
+ priv = gd_main_icon_box_get_instance_private (self);
+ return priv->show_primary_text;
+}
+
+static gboolean
+gd_main_icon_box_get_show_secondary_text (GdMainIconBox *self)
+{
+ GdMainIconBoxPrivate *priv;
+
+ priv = gd_main_icon_box_get_instance_private (self);
+ return priv->show_secondary_text;
+}
+
+static void
+gd_main_icon_box_select_all_generic (GdMainBoxGeneric *generic)
+{
+ GdMainIconBox *self = GD_MAIN_ICON_BOX (generic);
+ g_signal_emit_by_name (self, "select-all");
+}
+
+static void
+gd_main_icon_box_select_child (GdMainBoxGeneric *generic, GdMainBoxChild *child)
+{
+ GdMainIconBox *self = GD_MAIN_ICON_BOX (generic);
+ gtk_flow_box_select_child (GTK_FLOW_BOX (self), GTK_FLOW_BOX_CHILD (child));
+}
+
+static void
+gd_main_icon_box_set_model (GdMainIconBox *self, GListModel *model)
+{
+ GdMainIconBoxPrivate *priv;
+
+ priv = gd_main_icon_box_get_instance_private (self);
+
+ if (!g_set_object (&priv->model, model))
+ return;
+
+ gtk_flow_box_bind_model (GTK_FLOW_BOX (self),
+ priv->model,
+ gd_main_icon_box_create_widget_func,
+ self,
+ NULL);
+
+ g_object_notify (G_OBJECT (self), "model");
+}
+
+static void
+gd_main_icon_box_set_selection_mode (GdMainIconBox *self, gboolean selection_mode)
+{
+ GdMainIconBoxPrivate *priv;
+ GList *children;
+ GList *l;
+
+ priv = gd_main_icon_box_get_instance_private (self);
+
+ if (priv->selection_mode == selection_mode)
+ return;
+
+ gd_main_icon_box_update_last_selected_id (self, NULL);
+
+ priv->selection_mode = selection_mode;
+ if (priv->selection_mode)
+ gtk_flow_box_set_selection_mode (GTK_FLOW_BOX (self), GTK_SELECTION_MULTIPLE);
+ else
+ gtk_flow_box_set_selection_mode (GTK_FLOW_BOX (self), GTK_SELECTION_NONE);
+
+ children = gtk_container_get_children (GTK_CONTAINER (self));
+ for (l = children; l != NULL; l = l->next)
+ {
+ GdMainBoxChild *child = GD_MAIN_BOX_CHILD (l->data);
+ gd_main_box_child_set_selection_mode (child, priv->selection_mode);
+ }
+
+ g_object_notify (G_OBJECT (self), "last-selected-id");
+ g_object_notify (G_OBJECT (self), "selection-mode");
+
+ g_list_free (children);
+}
+
+static void
+gd_main_icon_box_set_show_primary_text (GdMainIconBox *self, gboolean show_primary_text)
+{
+ GdMainIconBoxPrivate *priv;
+
+ priv = gd_main_icon_box_get_instance_private (self);
+
+ if (priv->show_primary_text == show_primary_text)
+ return;
+
+ priv->show_primary_text = show_primary_text;
+ g_object_notify (G_OBJECT (self), "show-primary-text");
+}
+
+static void
+gd_main_icon_box_set_show_secondary_text (GdMainIconBox *self, gboolean show_secondary_text)
+{
+ GdMainIconBoxPrivate *priv;
+
+ priv = gd_main_icon_box_get_instance_private (self);
+
+ if (priv->show_secondary_text == show_secondary_text)
+ return;
+
+ priv->show_secondary_text = show_secondary_text;
+ g_object_notify (G_OBJECT (self), "show-secondary-text");
+}
+
+static void
+gd_main_icon_box_unselect_all_generic (GdMainBoxGeneric *generic)
+{
+ GdMainIconBox *self = GD_MAIN_ICON_BOX (generic);
+ g_signal_emit_by_name (self, "unselect-all");
+}
+
+static void
+gd_main_icon_box_unselect_child (GdMainBoxGeneric *generic, GdMainBoxChild *child)
+{
+ GdMainIconBox *self = GD_MAIN_ICON_BOX (generic);
+ gtk_flow_box_unselect_child (GTK_FLOW_BOX (self), GTK_FLOW_BOX_CHILD (child));
+}
+
+static void
+gd_main_icon_box_activate_cursor_child (GtkFlowBox *flow_box)
+{
+ GdMainIconBox *self = GD_MAIN_ICON_BOX (flow_box);
+ GdMainIconBoxPrivate *priv;
+ GdkEvent *event = NULL;
+ gboolean initiating = FALSE;
+
+ priv = gd_main_icon_box_get_instance_private (self);
+
+ /* Use GtkFlowBox::activate-cursor-child instead of
+ * GtkWidget::key-press-event to catch key presses because it is
+ * easier to filter out non-activation keys.
+ */
+
+ event = gtk_get_current_event ();
+ if (event == NULL)
+ goto out;
+
+ if (event->type != GDK_KEY_PRESS)
+ goto out;
+
+ if (!priv->selection_mode && (event->key.state & GDK_CONTROL_MASK) != 0)
+ {
+ g_signal_emit_by_name (self, "selection-mode-request");
+ initiating = TRUE;
+ }
+
+ if (priv->selection_mode)
+ {
+ if (!initiating && (event->key.state & GDK_SHIFT_MASK) != 0)
+ priv->key_shift_pressed = TRUE;
+
+ priv->key_pressed = TRUE;
+ }
+
+ out:
+ GTK_FLOW_BOX_CLASS (gd_main_icon_box_parent_class)->activate_cursor_child (flow_box);
+ g_clear_pointer (&event, (GDestroyNotify) gdk_event_free);
+}
+
+static gboolean
+gd_main_icon_box_button_press_event (GtkWidget *widget, GdkEventButton *event)
+{
+ GdMainIconBox *self = GD_MAIN_ICON_BOX (widget);
+ GdMainIconBoxPrivate *priv;
+ GtkFlowBoxChild *child;
+ gboolean res;
+
+ priv = gd_main_icon_box_get_instance_private (self);
+
+ if (event->type != GDK_BUTTON_PRESS)
+ {
+ res = GDK_EVENT_STOP;
+ goto out;
+ }
+
+ if (event->button != GDK_BUTTON_PRIMARY)
+ goto default_behavior;
+
+ child = gtk_flow_box_get_child_at_pos (GTK_FLOW_BOX (self), (gint) event->x, (gint) event->y);
+ if (child == NULL)
+ goto default_behavior;
+
+ if (priv->selection_mode && !gtk_flow_box_child_is_selected (child))
+ goto default_behavior;
+
+ priv->dnd_button = (gint) event->button;
+ priv->dnd_start_x = event->x;
+ priv->dnd_start_y = event->y;
+
+ default_behavior:
+ res = GTK_WIDGET_CLASS (gd_main_icon_box_parent_class)->button_press_event (widget, event);
+
+ out:
+ return res;
+}
+
+static gboolean
+gd_main_icon_box_button_release_event (GtkWidget *widget, GdkEventButton *event)
+{
+ GdMainIconBox *self = GD_MAIN_ICON_BOX (widget);
+ GdMainIconBoxPrivate *priv;
+ GtkFlowBoxChild *child = NULL;
+ gboolean initiating = FALSE;
+ gboolean res;
+
+ priv = gd_main_icon_box_get_instance_private (self);
+
+ priv->dnd_button = -1;
+ priv->dnd_start_x = -1.0;
+ priv->dnd_start_y = -1.0;
+ priv->dnd_started = FALSE;
+
+ if (event->type != GDK_BUTTON_RELEASE)
+ {
+ res = GDK_EVENT_STOP;
+ goto out;
+ }
+
+ if (!priv->selection_mode &&
+ ((event->button == GDK_BUTTON_PRIMARY && (event->state & GDK_CONTROL_MASK) != 0) ||
+ event->button == GDK_BUTTON_SECONDARY))
+ {
+ g_signal_emit_by_name (self, "selection-mode-request");
+ initiating = TRUE;
+ }
+
+ if (priv->selection_mode)
+ {
+ if (event->button == GDK_BUTTON_PRIMARY)
+ {
+ /* GtkFlowBox doesn't do range selection. It will simply
+ * select a single child for shift + left-click. We need to
+ * detect it so that we can handle it later.
+ *
+ * However, range selection is only possible if we were
+ * already in the selection mode. Therefore, skip it if we
+ * have just requested the selection mode.
+ */
+ if (!initiating && (event->state & GDK_SHIFT_MASK) != 0)
+ priv->left_button_shift_released = TRUE;
+
+ priv->left_button_released = TRUE;
+ }
+ else if (event->button == GDK_BUTTON_SECONDARY)
+ {
+ /* GtkFlowBox completely ignores the right mouse
+ * button.
+ */
+
+ child = gtk_flow_box_get_child_at_pos (GTK_FLOW_BOX (self), (gint) event->x, (gint) event->y);
+ if (child != NULL)
+ {
+ gd_main_box_generic_toggle_selection_for_child (GD_MAIN_BOX_GENERIC (self),
+ GD_MAIN_BOX_CHILD (child),
+ (!initiating &&
+ (event->state & GDK_SHIFT_MASK) != 0));
+ }
+ }
+ }
+
+ /* This is for right-clicks and rubberband selection.
+ *
+ * Rubberband selection is unlike other modes of selection because
+ * GtkFlowBox::selected-children-changed is emitted before the mouse
+ * button is released.
+ */
+ if (priv->selection_changed)
+ {
+ g_signal_emit_by_name (self, "selection-changed");
+ gd_main_icon_box_update_last_selected_id (self, GD_MAIN_BOX_CHILD (child));
+ priv->selection_changed = FALSE;
+ }
+
+ res = GTK_WIDGET_CLASS (gd_main_icon_box_parent_class)->button_release_event (widget, event);
+
+ out:
+ return res;
+}
+
+static void
+gd_main_icon_box_child_activated (GtkFlowBox *flow_box, GtkFlowBoxChild *child)
+{
+ GdMainIconBox *self = GD_MAIN_ICON_BOX (flow_box);
+ GdMainIconBoxPrivate *priv;
+ GdkEvent *event = NULL;
+
+ g_return_if_fail (GD_IS_MAIN_BOX_CHILD (child));
+
+ priv = gd_main_icon_box_get_instance_private (self);
+
+ /* GtkFlowBox might emit child-activated in the middle of a
+ * DnD. See https://bugzilla.gnome.org/show_bug.cgi?id=776306
+ */
+ if (priv->dnd_started)
+ goto out;
+
+ if (!priv->selection_mode)
+ {
+ g_signal_emit_by_name (self, "item-activated", GD_MAIN_BOX_CHILD (child));
+ goto out;
+ }
+
+ event = gtk_get_current_event ();
+ if (event == NULL)
+ goto out;
+
+ if (priv->left_button_released && !priv->selection_changed)
+ {
+ /* If a selected child is left-clicked, GtkFlowBox will activate
+ * it without unselecting it.
+ */
+ gd_main_box_generic_toggle_selection_for_child (GD_MAIN_BOX_GENERIC (self),
+ GD_MAIN_BOX_CHILD (child),
+ FALSE); /* One cannot unselect a range. */
+ priv->left_button_released = FALSE;
+ g_signal_emit_by_name (self, "selection-changed");
+ }
+ else if (priv->key_pressed && !priv->selection_changed)
+ {
+ /* If a selected child is activated by a keybinding, GtkFlowBox
+ * will not unselect it.
+ */
+ gd_main_box_generic_toggle_selection_for_child (GD_MAIN_BOX_GENERIC (self), GD_MAIN_BOX_CHILD (child), FALSE);
+ priv->key_pressed = FALSE;
+ g_signal_emit_by_name (self, "selection-changed");
+ }
+ else if (priv->left_button_shift_released || priv->key_shift_pressed)
+ {
+ /* GtkFlowBox doesn't do range selection and simply selects a
+ * single child. We handle it by unselecting the child and then
+ * selecting the range.
+ */
+ gd_main_box_generic_toggle_selection_for_child (GD_MAIN_BOX_GENERIC (self), GD_MAIN_BOX_CHILD (child), FALSE);
+ priv->left_button_shift_released = FALSE;
+ priv->key_shift_pressed = FALSE;
+ gd_main_box_generic_toggle_selection_for_child (GD_MAIN_BOX_GENERIC (self), GD_MAIN_BOX_CHILD (child), TRUE);
+ g_signal_emit_by_name (self, "selection-changed");
+ }
+ else if (priv->selection_changed)
+ {
+ /* This is for non-shift left-clicks and keyboard activation of
+ * unselected children.
+ */
+ g_signal_emit_by_name (self, "selection-changed");
+ }
+
+ g_signal_emit_by_name (self, "item-activated", GD_MAIN_BOX_CHILD (child));
+
+ if (priv->selection_changed)
+ {
+ gd_main_icon_box_update_last_selected_id (self, GD_MAIN_BOX_CHILD (child));
+ priv->selection_changed = FALSE;
+ }
+
+ out:
+ g_clear_pointer (&event, (GDestroyNotify) gdk_event_free);
+}
+
+static void
+gd_main_icon_box_drag_begin (GtkWidget *widget, GdkDragContext *context)
+{
+ GdMainIconBox *self = GD_MAIN_ICON_BOX (widget);
+ GdMainIconBoxPrivate *priv;
+ GdMainBoxItem *item;
+ GtkFlowBoxChild *child;
+ cairo_surface_t *drag_icon = NULL;
+ cairo_surface_t *icon;
+
+ priv = gd_main_icon_box_get_instance_private (self);
+
+ if (priv->dnd_start_x < 0.0 || priv->dnd_start_y < 0.0)
+ goto out;
+
+ child = gtk_flow_box_get_child_at_pos (GTK_FLOW_BOX (self),
+ (gint) priv->dnd_start_x,
+ (gint) priv->dnd_start_y);
+ if (child == NULL)
+ goto out;
+
+ item = gd_main_box_child_get_item (GD_MAIN_BOX_CHILD (child));
+ icon = gd_main_box_item_get_icon (item);
+ if (icon == NULL)
+ goto out;
+
+ if (priv->selection_mode)
+ {
+ GList *selected_children;
+ guint length;
+
+ selected_children = gtk_flow_box_get_selected_children (GTK_FLOW_BOX (self));
+ length = g_list_length (selected_children);
+ if (length > 1)
+ drag_icon = gd_create_surface_with_counter (GTK_WIDGET (self), icon, length);
+
+ g_list_free (selected_children);
+ }
+
+ if (drag_icon == NULL)
+ drag_icon = gd_copy_image_surface (icon);
+
+ cairo_surface_set_device_offset (drag_icon, -MAIN_ICON_BOX_DND_ICON_OFFSET, -MAIN_ICON_BOX_DND_ICON_OFFSET);
+ gtk_drag_set_icon_surface (context, drag_icon);
+
+ out:
+ g_clear_pointer (&drag_icon, (GDestroyNotify) cairo_surface_destroy);
+}
+
+static void
+gd_main_icon_box_add_child_uri_to_array (GdMainBoxChild *child, GPtrArray *uri_array)
+{
+ GdMainBoxItem *item;
+ const gchar *uri;
+
+ item = gd_main_box_child_get_item (child);
+ uri = gd_main_box_item_get_uri (item);
+ g_ptr_array_add (uri_array, g_strdup (uri));
+}
+
+static void
+gd_main_icon_box_drag_data_get (GtkWidget *widget,
+ GdkDragContext *context,
+ GtkSelectionData *data,
+ guint info,
+ guint time)
+{
+ GdMainIconBox *self = GD_MAIN_ICON_BOX (widget);
+ GdMainIconBoxPrivate *priv;
+ GPtrArray *uri_array = NULL;
+
+ priv = gd_main_icon_box_get_instance_private (self);
+
+ if (info != 0)
+ goto out;
+
+ if (priv->dnd_start_x < 0.0 || priv->dnd_start_y < 0.0)
+ goto out;
+
+ uri_array = g_ptr_array_new_with_free_func (g_free);
+
+ if (priv->selection_mode)
+ {
+ GList *l;
+ GList *selected_children;
+
+ selected_children = gtk_flow_box_get_selected_children (GTK_FLOW_BOX (self));
+ for (l = selected_children; l != NULL; l = l->next)
+ {
+ GdMainBoxChild *child = GD_MAIN_BOX_CHILD (l->data);
+ gd_main_icon_box_add_child_uri_to_array (child, uri_array);
+ }
+
+ g_list_free (selected_children);
+ }
+ else
+ {
+ GtkFlowBoxChild *child;
+
+ child = gtk_flow_box_get_child_at_pos (GTK_FLOW_BOX (self),
+ (gint) priv->dnd_start_x,
+ (gint) priv->dnd_start_y);
+
+ if (child != NULL)
+ gd_main_icon_box_add_child_uri_to_array (GD_MAIN_BOX_CHILD (child), uri_array);
+ }
+
+ g_ptr_array_add (uri_array, NULL);
+ gtk_selection_data_set_uris (data, (gchar **) uri_array->pdata);
+
+ out:
+ g_clear_pointer (&uri_array, (GDestroyNotify) g_ptr_array_unref);
+}
+
+static gboolean
+gd_main_icon_box_focus (GtkWidget *widget, GtkDirectionType direction)
+{
+ GdMainIconBox *self = GD_MAIN_ICON_BOX (widget);
+ GdMainIconBoxPrivate *priv;
+ GdkEvent *event = NULL;
+ GdkEvent *fake_event = NULL;
+ gboolean res;
+
+ priv = gd_main_icon_box_get_instance_private (self);
+
+ if (!priv->selection_mode)
+ {
+ res = GTK_WIDGET_CLASS (gd_main_icon_box_parent_class)->focus (widget, direction);
+ goto out;
+ }
+
+ event = gtk_get_current_event ();
+ if (event->type != GDK_KEY_PRESS && event->type != GDK_KEY_RELEASE)
+ {
+ res = GTK_WIDGET_CLASS (gd_main_icon_box_parent_class)->focus (widget, direction);
+ goto out;
+ }
+
+ if ((event->key.state & GDK_CONTROL_MASK) != 0)
+ {
+ res = GTK_WIDGET_CLASS (gd_main_icon_box_parent_class)->focus (widget, direction);
+ goto out;
+ }
+
+ fake_event = gdk_event_copy (event);
+ fake_event->key.state |= GDK_CONTROL_MASK;
+
+ gtk_main_do_event (fake_event);
+ res = GDK_EVENT_STOP;
+
+ out:
+ g_clear_pointer (&fake_event, (GDestroyNotify) gdk_event_free);
+ g_clear_pointer (&event, (GDestroyNotify) gdk_event_free);
+ return res;
+}
+
+static gboolean
+gd_main_icon_box_motion_notify_event (GtkWidget *widget, GdkEventMotion *event)
+{
+ GdMainIconBox *self = GD_MAIN_ICON_BOX (widget);
+ GdMainIconBoxPrivate *priv;
+ GtkTargetList *targets;
+ gboolean res;
+ gint button;
+
+ priv = gd_main_icon_box_get_instance_private (self);
+
+ if (priv->dnd_button < 0)
+ goto out;
+
+ if (!gtk_drag_check_threshold (GTK_WIDGET (self),
+ (gint) priv->dnd_start_x,
+ (gint) priv->dnd_start_y,
+ (gint) event->x,
+ (gint) event->y))
+ goto out;
+
+ button = priv->dnd_button;
+ priv->dnd_button = -1;
+ priv->dnd_started = TRUE;
+
+ targets = gtk_drag_source_get_target_list (GTK_WIDGET (self));
+
+ gtk_drag_begin_with_coordinates (GTK_WIDGET (self),
+ targets,
+ GDK_ACTION_COPY,
+ button,
+ (GdkEvent *) event,
+ (gint) priv->dnd_start_x,
+ (gint) priv->dnd_start_y);
+
+ out:
+ res = GTK_WIDGET_CLASS (gd_main_icon_box_parent_class)->motion_notify_event (widget, event);
+ return res;
+}
+
+static gboolean
+gd_main_icon_box_move_cursor (GtkFlowBox *flow_box, GtkMovementStep step, gint count)
+{
+ GdMainIconBox *self = GD_MAIN_ICON_BOX (flow_box);
+ GdMainIconBoxPrivate *priv;
+ GdkEvent *event = NULL;
+ GdkEvent *fake_event = NULL;
+ gboolean res;
+
+ priv = gd_main_icon_box_get_instance_private (self);
+
+ if (!priv->selection_mode)
+ {
+ res = GTK_FLOW_BOX_CLASS (gd_main_icon_box_parent_class)->move_cursor (flow_box, step, count);
+ goto out;
+ }
+
+ event = gtk_get_current_event ();
+ if (event->type != GDK_KEY_PRESS && event->type != GDK_KEY_RELEASE)
+ {
+ res = GTK_FLOW_BOX_CLASS (gd_main_icon_box_parent_class)->move_cursor (flow_box, step, count);
+ goto out;
+ }
+
+ if ((event->key.state & GDK_CONTROL_MASK) != 0 && (event->key.state & GDK_SHIFT_MASK) == 0)
+ {
+ res = GTK_FLOW_BOX_CLASS (gd_main_icon_box_parent_class)->move_cursor (flow_box, step, count);
+ goto out;
+ }
+
+ fake_event = gdk_event_copy (event);
+ fake_event->key.state |= GDK_CONTROL_MASK;
+ fake_event->key.state &= ~GDK_SHIFT_MASK;
+
+ gtk_main_do_event (fake_event);
+ res = GDK_EVENT_STOP;
+
+ out:
+ g_clear_pointer (&fake_event, (GDestroyNotify) gdk_event_free);
+ g_clear_pointer (&event, (GDestroyNotify) gdk_event_free);
+ return res;
+}
+
+static void
+gd_main_icon_box_remove (GtkContainer *container, GtkWidget *widget)
+{
+ GdMainIconBox *self = GD_MAIN_ICON_BOX (container);
+ GdMainIconBoxPrivate *priv;
+
+ priv = gd_main_icon_box_get_instance_private (self);
+
+ GTK_CONTAINER_CLASS (gd_main_icon_box_parent_class)->remove (container, widget);
+
+ if (priv->selection_changed)
+ {
+ g_signal_emit_by_name (self, "selection-changed");
+ priv->selection_changed = FALSE;
+ }
+}
+
+static void
+gd_main_icon_box_select_all_flow_box (GtkFlowBox *flow_box)
+{
+ GdMainIconBox *self = GD_MAIN_ICON_BOX (flow_box);
+ GdMainIconBoxPrivate *priv;
+
+ priv = gd_main_icon_box_get_instance_private (self);
+
+ GTK_FLOW_BOX_CLASS (gd_main_icon_box_parent_class)->select_all (flow_box);
+
+ if (priv->selection_changed)
+ {
+ g_signal_emit_by_name (self, "selection-changed");
+ priv->selection_changed = FALSE;
+ }
+}
+
+static void
+gd_main_icon_box_selected_children_changed (GtkFlowBox *flow_box)
+{
+ GdMainIconBox *self = GD_MAIN_ICON_BOX (flow_box);
+ GdMainIconBoxPrivate *priv;
+
+ priv = gd_main_icon_box_get_instance_private (self);
+
+ GTK_FLOW_BOX_CLASS (gd_main_icon_box_parent_class)->selected_children_changed (flow_box);
+
+ priv->selection_changed = TRUE;
+
+ /* When a range selection is attempted, we override GtkFlowBox's
+ * default behaviour by changing the selection ourselves. Therefore,
+ * there is no need to update the check buttons until the final
+ * selection is available.
+ */
+ if (!priv->key_shift_pressed && !priv->left_button_shift_released)
+ {
+ GList *children;
+ GList *l;
+
+ children = gtk_container_get_children (GTK_CONTAINER (self));
+ for (l = children; l != NULL; l = l->next)
+ {
+ GtkFlowBoxChild *child = GTK_FLOW_BOX_CHILD (l->data);
+ gboolean selected;
+
+ /* Work around the fact that GtkFlowBoxChild:selected is not
+ * a property.
+ */
+ selected = gtk_flow_box_child_is_selected (child);
+ gd_main_box_child_set_selected (GD_MAIN_BOX_CHILD (child), selected);
+ }
+
+ g_list_free (children);
+ }
+}
+
+static void
+gd_main_icon_box_unselect_all_flow_box (GtkFlowBox *flow_box)
+{
+ GdMainIconBox *self = GD_MAIN_ICON_BOX (flow_box);
+ GdMainIconBoxPrivate *priv;
+
+ priv = gd_main_icon_box_get_instance_private (self);
+
+ GTK_FLOW_BOX_CLASS (gd_main_icon_box_parent_class)->unselect_all (flow_box);
+
+ if (priv->selection_changed)
+ {
+ g_signal_emit_by_name (self, "selection-changed");
+ priv->selection_changed = FALSE;
+ }
+}
+
+static void
+gd_main_icon_box_dispose (GObject *obj)
+{
+ GdMainIconBox *self = GD_MAIN_ICON_BOX (obj);
+ GdMainIconBoxPrivate *priv;
+
+ priv = gd_main_icon_box_get_instance_private (self);
+
+ g_clear_object (&priv->model);
+
+ G_OBJECT_CLASS (gd_main_icon_box_parent_class)->dispose (obj);
+}
+
+static void
+gd_main_icon_box_finalize (GObject *obj)
+{
+ GdMainIconBox *self = GD_MAIN_ICON_BOX (obj);
+ GdMainIconBoxPrivate *priv;
+
+ priv = gd_main_icon_box_get_instance_private (self);
+
+ g_free (priv->last_selected_id);
+
+ G_OBJECT_CLASS (gd_main_icon_box_parent_class)->finalize (obj);
+}
+
+static void
+gd_main_icon_box_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
+{
+ GdMainIconBox *self = GD_MAIN_ICON_BOX (object);
+
+ switch (property_id)
+ {
+ case PROP_LAST_SELECTED_ID:
+ g_value_set_string (value, gd_main_icon_box_get_last_selected_id (GD_MAIN_BOX_GENERIC (self)));
+ break;
+ case PROP_MODEL:
+ g_value_set_object (value, gd_main_icon_box_get_model (GD_MAIN_BOX_GENERIC (self)));
+ break;
+ case PROP_SELECTION_MODE:
+ g_value_set_boolean (value, gd_main_icon_box_get_selection_mode (self));
+ break;
+ case PROP_SHOW_PRIMARY_TEXT:
+ g_value_set_boolean (value, gd_main_icon_box_get_show_primary_text (self));
+ break;
+ case PROP_SHOW_SECONDARY_TEXT:
+ g_value_set_boolean (value, gd_main_icon_box_get_show_secondary_text (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gd_main_icon_box_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
+{
+ GdMainIconBox *self = GD_MAIN_ICON_BOX (object);
+
+ switch (property_id)
+ {
+ case PROP_MODEL:
+ gd_main_icon_box_set_model (self, g_value_get_object (value));
+ break;
+ case PROP_SELECTION_MODE:
+ gd_main_icon_box_set_selection_mode (self, g_value_get_boolean (value));
+ break;
+ case PROP_SHOW_PRIMARY_TEXT:
+ gd_main_icon_box_set_show_primary_text (self, g_value_get_boolean (value));
+ break;
+ case PROP_SHOW_SECONDARY_TEXT:
+ gd_main_icon_box_set_show_secondary_text (self, g_value_get_boolean (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gd_main_icon_box_init (GdMainIconBox *self)
+{
+ GdMainIconBoxPrivate *priv;
+ const GtkTargetEntry targets[] = { { (gchar *) "text/uri-list", GTK_TARGET_OTHER_APP, 0 } };
+
+ priv = gd_main_icon_box_get_instance_private (self);
+
+ gtk_widget_set_can_focus (GTK_WIDGET (self), TRUE);
+ gtk_flow_box_set_homogeneous (GTK_FLOW_BOX (self), TRUE);
+ gtk_flow_box_set_min_children_per_line (GTK_FLOW_BOX (self), 3);
+ gtk_flow_box_set_selection_mode (GTK_FLOW_BOX (self), GTK_SELECTION_NONE);
+
+ /* We need to ensure that rubberband selection and DnD don't step
+ * on each others toes. We set start_button_mask to 0 to retain
+ * control over when to begin a drag.
+ */
+ gtk_drag_source_set (GTK_WIDGET (self), 0, targets, G_N_ELEMENTS (targets), GDK_ACTION_COPY);
+
+ priv->dnd_button = -1;
+ priv->dnd_start_x = -1.0;
+ priv->dnd_start_y = -1.0;
+}
+
+static void
+gd_main_icon_box_class_init (GdMainIconBoxClass *klass)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+ GtkContainerClass *cclass = GTK_CONTAINER_CLASS (klass);
+ GtkFlowBoxClass *fbclass = GTK_FLOW_BOX_CLASS (klass);
+ GtkWidgetClass *wclass = GTK_WIDGET_CLASS (klass);
+ GtkBindingSet *binding_set;
+ GdkModifierType activate_modifiers[] = { 0, /* Otherwise it will go to GtkFlowBoxChild::activate. */
+ GDK_SHIFT_MASK,
+ GDK_CONTROL_MASK,
+ GDK_SHIFT_MASK | GDK_CONTROL_MASK };
+ guint i;
+
+ binding_set = gtk_binding_set_by_class (klass);
+
+ oclass->dispose = gd_main_icon_box_dispose;
+ oclass->finalize = gd_main_icon_box_finalize;
+ oclass->get_property = gd_main_icon_box_get_property;
+ oclass->set_property = gd_main_icon_box_set_property;
+ wclass->button_press_event = gd_main_icon_box_button_press_event;
+ wclass->button_release_event = gd_main_icon_box_button_release_event;
+ wclass->drag_begin = gd_main_icon_box_drag_begin;
+ wclass->drag_data_get = gd_main_icon_box_drag_data_get;
+ wclass->focus = gd_main_icon_box_focus;
+ wclass->motion_notify_event = gd_main_icon_box_motion_notify_event;
+ cclass->remove = gd_main_icon_box_remove;
+ fbclass->activate_cursor_child = gd_main_icon_box_activate_cursor_child;
+ fbclass->child_activated = gd_main_icon_box_child_activated;
+ fbclass->move_cursor = gd_main_icon_box_move_cursor;
+ fbclass->select_all = gd_main_icon_box_select_all_flow_box;
+ fbclass->selected_children_changed = gd_main_icon_box_selected_children_changed;
+ fbclass->unselect_all = gd_main_icon_box_unselect_all_flow_box;
+
+ g_object_class_override_property (oclass, PROP_LAST_SELECTED_ID, "last-selected-id");
+ g_object_class_override_property (oclass, PROP_MODEL, "model");
+ g_object_class_override_property (oclass, PROP_SELECTION_MODE, "gd-selection-mode");
+ g_object_class_override_property (oclass, PROP_SHOW_PRIMARY_TEXT, "show-primary-text");
+ g_object_class_override_property (oclass, PROP_SHOW_SECONDARY_TEXT, "show-secondary-text");
+
+ for (i = 0; i < G_N_ELEMENTS (activate_modifiers); i++)
+ {
+ gtk_binding_entry_add_signal (binding_set,
+ GDK_KEY_space, activate_modifiers[i],
+ "activate-cursor-child",
+ 0);
+ gtk_binding_entry_add_signal (binding_set,
+ GDK_KEY_KP_Space, activate_modifiers[i],
+ "activate-cursor-child",
+ 0);
+ gtk_binding_entry_add_signal (binding_set,
+ GDK_KEY_Return, activate_modifiers[i],
+ "activate-cursor-child",
+ 0);
+ gtk_binding_entry_add_signal (binding_set,
+ GDK_KEY_ISO_Enter, activate_modifiers[i],
+ "activate-cursor-child",
+ 0);
+ gtk_binding_entry_add_signal (binding_set,
+ GDK_KEY_KP_Enter, activate_modifiers[i],
+ "activate-cursor-child",
+ 0);
+ }
+}
+
+static void
+gd_main_box_generic_interface_init (GdMainBoxGenericInterface *iface)
+{
+ iface->get_child_at_index = gd_main_icon_box_get_child_at_index;
+ iface->get_last_selected_id = gd_main_icon_box_get_last_selected_id;
+ iface->get_model = gd_main_icon_box_get_model;
+ iface->get_selected_children = gd_main_icon_box_get_selected_children;
+ iface->select_all = gd_main_icon_box_select_all_generic;
+ iface->select_child = gd_main_icon_box_select_child;
+ iface->unselect_all = gd_main_icon_box_unselect_all_generic;
+ iface->unselect_child = gd_main_icon_box_unselect_child;
+}
+
+GtkWidget *
+gd_main_icon_box_new (void)
+{
+ return g_object_new (GD_TYPE_MAIN_ICON_BOX, NULL);
+}
diff --git a/subprojects/libgd/libgd/gd-main-icon-box.h b/subprojects/libgd/libgd/gd-main-icon-box.h
new file mode 100644
index 0000000..5dc60fe
--- /dev/null
+++ b/subprojects/libgd/libgd/gd-main-icon-box.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2016 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Debarshi Ray <debarshir@gnome.org>
+ *
+ */
+
+#ifndef __GD_MAIN_ICON_BOX_H__
+#define __GD_MAIN_ICON_BOX_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GD_TYPE_MAIN_ICON_BOX gd_main_icon_box_get_type()
+G_DECLARE_DERIVABLE_TYPE (GdMainIconBox, gd_main_icon_box, GD, MAIN_ICON_BOX, GtkFlowBox)
+
+struct _GdMainIconBoxClass
+{
+ GtkFlowBoxClass parent_class;
+
+ /* signals */
+ gboolean (* move_cursor) (GdMainIconBox *self, GtkMovementStep step, gint count);
+};
+
+GtkWidget * gd_main_icon_box_new (void);
+
+G_END_DECLS
+
+#endif /* __GD_MAIN_ICON_BOX_H__ */
diff --git a/subprojects/libgd/libgd/gd-main-icon-view.c b/subprojects/libgd/libgd/gd-main-icon-view.c
new file mode 100644
index 0000000..1f048dc
--- /dev/null
+++ b/subprojects/libgd/libgd/gd-main-icon-view.c
@@ -0,0 +1,434 @@
+/*
+ * Copyright (c) 2011 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Cosimo Cecchi <cosimoc@redhat.com>
+ *
+ */
+
+#include "gd-main-icon-view.h"
+#include "gd-main-view-generic.h"
+#include "gd-toggle-pixbuf-renderer.h"
+#include "gd-two-lines-renderer.h"
+
+#include <math.h>
+#include <glib/gi18n.h>
+#include <cairo-gobject.h>
+
+#define VIEW_ITEM_WIDTH 140
+#define VIEW_ITEM_WRAP_WIDTH 128
+#define VIEW_COLUMN_SPACING 20
+#define VIEW_MARGIN 16
+
+struct _GdMainIconViewPrivate {
+ GtkCellRenderer *pixbuf_cell;
+ GtkCellRenderer *text_cell;
+ gboolean selection_mode;
+};
+
+static void gd_main_view_generic_iface_init (GdMainViewGenericIface *iface);
+G_DEFINE_TYPE_WITH_CODE (GdMainIconView, gd_main_icon_view, GTK_TYPE_ICON_VIEW,
+ G_ADD_PRIVATE (GdMainIconView)
+ G_IMPLEMENT_INTERFACE (GD_TYPE_MAIN_VIEW_GENERIC,
+ gd_main_view_generic_iface_init))
+
+static GtkTreePath*
+get_source_row (GdkDragContext *context)
+{
+ GtkTreeRowReference *ref;
+
+ ref = g_object_get_data (G_OBJECT (context), "gtk-icon-view-source-row");
+
+ if (ref)
+ return gtk_tree_row_reference_get_path (ref);
+ else
+ return NULL;
+}
+
+static void
+set_attributes_from_model (GdMainIconView *self)
+{
+ GdMainIconViewPrivate *priv;
+ GtkTreeModel *model = gtk_icon_view_get_model (GTK_ICON_VIEW (self));
+ GtkCellLayout *layout = GTK_CELL_LAYOUT (self);
+ GType icon_gtype;
+
+ priv = gd_main_icon_view_get_instance_private (self);
+
+ if (!model)
+ return;
+
+ gtk_cell_layout_clear_attributes (layout, priv->pixbuf_cell);
+ gtk_cell_layout_clear_attributes (layout, priv->text_cell);
+
+ gtk_cell_layout_add_attribute (layout, priv->pixbuf_cell,
+ "active", GD_MAIN_COLUMN_SELECTED);
+ gtk_cell_layout_add_attribute (layout, priv->pixbuf_cell,
+ "pulse", GD_MAIN_COLUMN_PULSE);
+
+ icon_gtype = gtk_tree_model_get_column_type (model, GD_MAIN_COLUMN_ICON);
+ if (icon_gtype == GDK_TYPE_PIXBUF)
+ gtk_cell_layout_add_attribute (layout, priv->pixbuf_cell,
+ "pixbuf", GD_MAIN_COLUMN_ICON);
+ else if (icon_gtype == CAIRO_GOBJECT_TYPE_SURFACE)
+ gtk_cell_layout_add_attribute (layout, priv->pixbuf_cell,
+ "surface", GD_MAIN_COLUMN_ICON);
+ else
+ g_assert_not_reached ();
+
+ gtk_cell_layout_add_attribute (layout, priv->text_cell,
+ "text", GD_MAIN_COLUMN_PRIMARY_TEXT);
+ gtk_cell_layout_add_attribute (layout, priv->text_cell,
+ "line-two", GD_MAIN_COLUMN_SECONDARY_TEXT);
+}
+
+static void
+gd_main_icon_view_drag_data_get (GtkWidget *widget,
+ GdkDragContext *drag_context,
+ GtkSelectionData *data,
+ guint info,
+ guint time)
+{
+ GdMainIconView *self = GD_MAIN_ICON_VIEW (widget);
+ GdMainIconViewPrivate *priv;
+ GtkTreeModel *model = gtk_icon_view_get_model (GTK_ICON_VIEW (self));
+
+ priv = gd_main_icon_view_get_instance_private (self);
+
+ if (info != 0)
+ return;
+
+ _gd_main_view_generic_dnd_common (model, priv->selection_mode,
+ get_source_row (drag_context), data);
+
+ GTK_WIDGET_CLASS (gd_main_icon_view_parent_class)->drag_data_get (widget, drag_context,
+ data, info, time);
+}
+
+static void
+gd_main_icon_view_constructed (GObject *obj)
+{
+ GdMainIconView *self = GD_MAIN_ICON_VIEW (obj);
+ GdMainIconViewPrivate *priv;
+ GtkCellRenderer *cell;
+ const GtkTargetEntry targets[] = {
+ { (char *) "text/uri-list", GTK_TARGET_OTHER_APP, 0 }
+ };
+
+ priv = gd_main_icon_view_get_instance_private (self);
+
+ G_OBJECT_CLASS (gd_main_icon_view_parent_class)->constructed (obj);
+
+ gtk_widget_set_hexpand (GTK_WIDGET (self), TRUE);
+ gtk_widget_set_vexpand (GTK_WIDGET (self), TRUE);
+ gtk_icon_view_set_selection_mode (GTK_ICON_VIEW (self), GTK_SELECTION_NONE);
+
+ g_object_set (self,
+ "column-spacing", VIEW_COLUMN_SPACING,
+ "margin", VIEW_MARGIN,
+ NULL);
+
+ priv->pixbuf_cell = cell = gd_toggle_pixbuf_renderer_new ();
+ g_object_set (cell,
+ "xalign", 0.5,
+ "yalign", 0.5,
+ NULL);
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (self), cell, FALSE);
+
+ priv->text_cell = cell = gd_two_lines_renderer_new ();
+ g_object_set (cell,
+ "xalign", 0.5,
+ "yalign", 0.0,
+ "alignment", PANGO_ALIGN_CENTER,
+ "wrap-mode", PANGO_WRAP_WORD_CHAR,
+ "wrap-width", VIEW_ITEM_WRAP_WIDTH,
+ "text-lines", 3,
+ NULL);
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (self), cell, FALSE);
+
+ set_attributes_from_model (self);
+
+ gtk_icon_view_enable_model_drag_source (GTK_ICON_VIEW (self),
+ GDK_BUTTON1_MASK,
+ targets, 1,
+ GDK_ACTION_COPY);
+}
+
+static void
+path_from_line_rects (cairo_t *cr,
+ GdkRectangle *lines,
+ int n_lines)
+{
+ int start_line, end_line;
+ GdkRectangle *r;
+ int i;
+
+ /* Join rows vertically by extending to the middle */
+ for (i = 0; i < n_lines - 1; i++)
+ {
+ GdkRectangle *r1 = &lines[i];
+ GdkRectangle *r2 = &lines[i+1];
+ int gap = r2->y - (r1->y + r1->height);
+ int old_y;
+
+ r1->height += gap / 2;
+ old_y = r2->y;
+ r2->y = r1->y + r1->height;
+ r2->height += old_y - r2->y;
+ }
+
+ cairo_new_path (cr);
+ start_line = 0;
+
+ do
+ {
+ for (i = start_line; i < n_lines; i++)
+ {
+ r = &lines[i];
+ if (i == start_line)
+ cairo_move_to (cr, r->x + r->width, r->y);
+ else
+ cairo_line_to (cr, r->x + r->width, r->y);
+ cairo_line_to (cr, r->x + r->width, r->y + r->height);
+
+ if (i < n_lines - 1 &&
+ (r->x + r->width < lines[i+1].x ||
+ r->x > lines[i+1].x + lines[i+1].width))
+ {
+ i++;
+ break;
+ }
+ }
+ end_line = i;
+ for (i = end_line - 1; i >= start_line; i--)
+ {
+ r = &lines[i];
+ cairo_line_to (cr, r->x, r->y + r->height);
+ cairo_line_to (cr, r->x, r->y);
+ }
+ cairo_close_path (cr);
+ start_line = end_line;
+ }
+ while (end_line < n_lines);
+}
+
+static gboolean
+gd_main_icon_view_draw (GtkWidget *widget,
+ cairo_t *cr)
+{
+ GdMainIconView *self = GD_MAIN_ICON_VIEW (widget);
+ GtkAllocation allocation;
+ GtkStyleContext *context;
+ GdkRectangle line_rect;
+ GdkRectangle rect;
+ GtkTreePath *path;
+ GArray *lines;
+ GtkTreePath *rubberband_start, *rubberband_end;
+
+ GTK_WIDGET_CLASS (gd_main_icon_view_parent_class)->draw (widget, cr);
+
+ _gd_main_view_generic_get_rubberband_range (GD_MAIN_VIEW_GENERIC (self),
+ &rubberband_start, &rubberband_end);
+
+ if (rubberband_start)
+ {
+ cairo_save (cr);
+
+ context = gtk_widget_get_style_context (widget);
+
+ gtk_style_context_save (context);
+ gtk_style_context_add_class (context, GTK_STYLE_CLASS_RUBBERBAND);
+
+ path = gtk_tree_path_copy (rubberband_start);
+
+ line_rect.width = 0;
+ lines = g_array_new (FALSE, FALSE, sizeof (GdkRectangle));
+
+ while (gtk_tree_path_compare (path, rubberband_end) <= 0)
+ {
+ if (gtk_icon_view_get_cell_rect (GTK_ICON_VIEW (widget),
+ path,
+ NULL, &rect))
+ {
+ if (line_rect.width == 0)
+ line_rect = rect;
+ else
+ {
+ if (rect.y == line_rect.y)
+ gdk_rectangle_union (&rect, &line_rect, &line_rect);
+ else
+ {
+ g_array_append_val (lines, line_rect);
+ line_rect = rect;
+ }
+ }
+ }
+ gtk_tree_path_next (path);
+ }
+
+ if (line_rect.width != 0)
+ g_array_append_val (lines, line_rect);
+
+ if (lines->len > 0)
+ {
+ GtkStateFlags state;
+ cairo_path_t *path;
+ GtkBorder border;
+ GdkRGBA border_color;
+
+ path_from_line_rects (cr, (GdkRectangle *)lines->data, lines->len);
+
+ /* For some reason we need to copy and reapply the path, or it gets
+ eaten by gtk_render_background() */
+ path = cairo_copy_path (cr);
+
+ cairo_save (cr);
+ cairo_clip (cr);
+ gtk_widget_get_allocation (widget, &allocation);
+ gtk_render_background (context, cr,
+ 0, 0,
+ allocation.width, allocation.height);
+ cairo_restore (cr);
+
+ cairo_append_path (cr, path);
+ cairo_path_destroy (path);
+
+ state = gtk_widget_get_state_flags (widget);
+ gtk_style_context_get_border_color (context,
+ state,
+ &border_color);
+ gtk_style_context_get_border (context, state,
+ &border);
+
+ cairo_set_line_width (cr, border.left);
+ gdk_cairo_set_source_rgba (cr, &border_color);
+ cairo_stroke (cr);
+ }
+ g_array_free (lines, TRUE);
+
+ gtk_tree_path_free (path);
+
+ gtk_style_context_restore (context);
+ cairo_restore (cr);
+ }
+
+ return FALSE;
+}
+
+static void
+gd_main_icon_view_class_init (GdMainIconViewClass *klass)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *wclass = GTK_WIDGET_CLASS (klass);
+ GtkBindingSet *binding_set;
+ GdkModifierType activate_modifiers[] = { GDK_SHIFT_MASK, GDK_CONTROL_MASK, GDK_SHIFT_MASK | GDK_CONTROL_MASK };
+ guint i;
+
+ binding_set = gtk_binding_set_by_class (klass);
+
+ oclass->constructed = gd_main_icon_view_constructed;
+ wclass->drag_data_get = gd_main_icon_view_drag_data_get;
+ wclass->draw = gd_main_icon_view_draw;
+
+ gtk_widget_class_install_style_property (wclass,
+ g_param_spec_int ("check-icon-size",
+ "Check icon size",
+ "Check icon size",
+ -1, G_MAXINT, 40,
+ G_PARAM_READWRITE));
+
+ for (i = 0; i < G_N_ELEMENTS (activate_modifiers); i++)
+ {
+ gtk_binding_entry_add_signal (binding_set, GDK_KEY_space, activate_modifiers[i],
+ "activate-cursor-item", 0);
+ gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Space, activate_modifiers[i],
+ "activate-cursor-item", 0);
+ gtk_binding_entry_add_signal (binding_set, GDK_KEY_Return, activate_modifiers[i],
+ "activate-cursor-item", 0);
+ gtk_binding_entry_add_signal (binding_set, GDK_KEY_ISO_Enter, activate_modifiers[i],
+ "activate-cursor-item", 0);
+ gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Enter, activate_modifiers[i],
+ "activate-cursor-item", 0);
+ }
+}
+
+static void
+gd_main_icon_view_init (GdMainIconView *self)
+{
+ g_signal_connect (self, "notify::model",
+ G_CALLBACK (set_attributes_from_model), NULL);
+}
+
+static GtkTreePath *
+gd_main_icon_view_get_path_at_pos (GdMainViewGeneric *mv,
+ gint x,
+ gint y)
+{
+ return gtk_icon_view_get_path_at_pos (GTK_ICON_VIEW (mv), x, y);
+}
+
+static void
+gd_main_icon_view_set_selection_mode (GdMainViewGeneric *mv,
+ gboolean selection_mode)
+{
+ GdMainIconView *self = GD_MAIN_ICON_VIEW (mv);
+ GdMainIconViewPrivate *priv;
+
+ priv = gd_main_icon_view_get_instance_private (self);
+
+ priv->selection_mode = selection_mode;
+
+ g_object_set (priv->pixbuf_cell,
+ "toggle-visible", selection_mode,
+ NULL);
+ gtk_widget_queue_draw (GTK_WIDGET (self));
+}
+
+static void
+gd_main_icon_view_scroll_to_path (GdMainViewGeneric *mv,
+ GtkTreePath *path)
+{
+ gtk_icon_view_scroll_to_path (GTK_ICON_VIEW (mv), path, TRUE, 0.5, 0.5);
+}
+
+static void
+gd_main_icon_view_set_model (GdMainViewGeneric *mv,
+ GtkTreeModel *model)
+{
+ gtk_icon_view_set_model (GTK_ICON_VIEW (mv), model);
+}
+
+static GtkTreeModel *
+gd_main_icon_view_get_model (GdMainViewGeneric *mv)
+{
+ return gtk_icon_view_get_model (GTK_ICON_VIEW (mv));
+}
+
+static void
+gd_main_view_generic_iface_init (GdMainViewGenericIface *iface)
+{
+ iface->set_model = gd_main_icon_view_set_model;
+ iface->get_model = gd_main_icon_view_get_model;
+ iface->get_path_at_pos = gd_main_icon_view_get_path_at_pos;
+ iface->scroll_to_path = gd_main_icon_view_scroll_to_path;
+ iface->set_selection_mode = gd_main_icon_view_set_selection_mode;
+}
+
+GtkWidget *
+gd_main_icon_view_new (void)
+{
+ return g_object_new (GD_TYPE_MAIN_ICON_VIEW, NULL);
+}
diff --git a/subprojects/libgd/libgd/gd-main-icon-view.h b/subprojects/libgd/libgd/gd-main-icon-view.h
new file mode 100644
index 0000000..c2d1c22
--- /dev/null
+++ b/subprojects/libgd/libgd/gd-main-icon-view.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2011 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Cosimo Cecchi <cosimoc@redhat.com>
+ *
+ */
+
+#ifndef __GD_MAIN_ICON_VIEW_H__
+#define __GD_MAIN_ICON_VIEW_H__
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GD_TYPE_MAIN_ICON_VIEW gd_main_icon_view_get_type()
+
+#define GD_MAIN_ICON_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ GD_TYPE_MAIN_ICON_VIEW, GdMainIconView))
+
+#define GD_MAIN_ICON_VIEW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ GD_TYPE_MAIN_ICON_VIEW, GdMainIconViewClass))
+
+#define GD_IS_MAIN_ICON_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ GD_TYPE_MAIN_ICON_VIEW))
+
+#define GD_IS_MAIN_ICON_VIEW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+ GD_TYPE_MAIN_ICON_VIEW))
+
+#define GD_MAIN_ICON_VIEW_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ GD_TYPE_MAIN_ICON_VIEW, GdMainIconViewClass))
+
+typedef struct _GdMainIconView GdMainIconView;
+typedef struct _GdMainIconViewClass GdMainIconViewClass;
+typedef struct _GdMainIconViewPrivate GdMainIconViewPrivate;
+
+struct _GdMainIconView
+{
+ GtkIconView parent;
+};
+
+struct _GdMainIconViewClass
+{
+ GtkIconViewClass parent_class;
+};
+
+GType gd_main_icon_view_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gd_main_icon_view_new (void);
+
+G_END_DECLS
+
+#endif /* __GD_MAIN_ICON_VIEW_H__ */
diff --git a/subprojects/libgd/libgd/gd-main-list-view.c b/subprojects/libgd/libgd/gd-main-list-view.c
new file mode 100644
index 0000000..26128af
--- /dev/null
+++ b/subprojects/libgd/libgd/gd-main-list-view.c
@@ -0,0 +1,346 @@
+/*
+ * Copyright (c) 2011 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Cosimo Cecchi <cosimoc@redhat.com>
+ *
+ */
+
+#include "gd-main-list-view.h"
+#include "gd-main-view-generic.h"
+#include "gd-two-lines-renderer.h"
+
+#include <cairo-gobject.h>
+#include <glib/gi18n.h>
+
+struct _GdMainListViewPrivate {
+ GtkTreeViewColumn *tree_col;
+ GtkCellRenderer *pixbuf_cell;
+ GtkCellRenderer *selection_cell;
+ GtkCellRenderer *text_cell;
+
+ gboolean selection_mode;
+};
+
+static void gd_main_view_generic_iface_init (GdMainViewGenericIface *iface);
+G_DEFINE_TYPE_WITH_CODE (GdMainListView, gd_main_list_view, GTK_TYPE_TREE_VIEW,
+ G_IMPLEMENT_INTERFACE (GD_TYPE_MAIN_VIEW_GENERIC,
+ gd_main_view_generic_iface_init))
+
+static gboolean gd_main_list_view_draw (GtkWidget *widget,
+ cairo_t *cr);
+
+static GtkTreePath*
+get_source_row (GdkDragContext *context)
+{
+ GtkTreeRowReference *ref =
+ g_object_get_data (G_OBJECT (context), "gtk-tree-view-source-row");
+
+ if (ref)
+ return gtk_tree_row_reference_get_path (ref);
+ else
+ return NULL;
+}
+
+static void
+set_attributes_from_model (GdMainListView *self)
+{
+ GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
+ GType icon_gtype;
+
+ if (!model)
+ return;
+
+ gtk_tree_view_column_clear_attributes (self->priv->tree_col, self->priv->pixbuf_cell);
+ gtk_tree_view_column_clear_attributes (self->priv->tree_col, self->priv->selection_cell);
+ gtk_tree_view_column_clear_attributes (self->priv->tree_col, self->priv->text_cell);
+
+
+ gtk_tree_view_column_add_attribute (self->priv->tree_col, self->priv->selection_cell,
+ "active", GD_MAIN_COLUMN_SELECTED);
+
+ icon_gtype = gtk_tree_model_get_column_type (model, GD_MAIN_COLUMN_ICON);
+ if (icon_gtype == GDK_TYPE_PIXBUF)
+ gtk_tree_view_column_add_attribute (self->priv->tree_col, self->priv->pixbuf_cell,
+ "pixbuf", GD_MAIN_COLUMN_ICON);
+ else if (icon_gtype == CAIRO_GOBJECT_TYPE_SURFACE)
+ gtk_tree_view_column_add_attribute (self->priv->tree_col, self->priv->pixbuf_cell,
+ "surface", GD_MAIN_COLUMN_ICON);
+ else
+ g_assert_not_reached ();
+
+ gtk_tree_view_column_add_attribute (self->priv->tree_col, self->priv->text_cell,
+ "text", GD_MAIN_COLUMN_PRIMARY_TEXT);
+ gtk_tree_view_column_add_attribute (self->priv->tree_col, self->priv->text_cell,
+ "line-two", GD_MAIN_COLUMN_SECONDARY_TEXT);
+}
+
+static void
+gd_main_list_view_drag_data_get (GtkWidget *widget,
+ GdkDragContext *drag_context,
+ GtkSelectionData *data,
+ guint info,
+ guint time)
+{
+ GdMainListView *self = GD_MAIN_LIST_VIEW (widget);
+ GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
+
+ if (info != 0)
+ return;
+
+ _gd_main_view_generic_dnd_common (model,
+ self->priv->selection_mode,
+ get_source_row (drag_context), data);
+
+ GTK_WIDGET_CLASS (gd_main_list_view_parent_class)->drag_data_get (widget, drag_context,
+ data, info, time);
+}
+
+static void
+gd_main_list_view_constructed (GObject *obj)
+{
+ GdMainListView *self = GD_MAIN_LIST_VIEW (obj);
+ GtkCellRenderer *cell;
+ GtkTreeSelection *selection;
+ const GtkTargetEntry targets[] = {
+ { "text/uri-list", GTK_TARGET_OTHER_APP, 0 }
+ };
+
+ G_OBJECT_CLASS (gd_main_list_view_parent_class)->constructed (obj);
+
+ gtk_widget_set_hexpand (GTK_WIDGET (self), TRUE);
+ gtk_widget_set_vexpand (GTK_WIDGET (self), TRUE);
+
+ g_object_set (self,
+ "headers-visible", FALSE,
+ "enable-search", FALSE,
+ NULL);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
+ gtk_tree_selection_set_mode (selection, GTK_SELECTION_NONE);
+
+ self->priv->tree_col = gtk_tree_view_column_new ();
+ gtk_tree_view_append_column (GTK_TREE_VIEW (self), self->priv->tree_col);
+
+ self->priv->selection_cell = cell = gtk_cell_renderer_toggle_new ();
+ g_object_set (cell,
+ "visible", FALSE,
+ "xpad", 12,
+ "xalign", 1.0,
+ NULL);
+ gtk_tree_view_column_pack_start (self->priv->tree_col, cell, FALSE);
+
+ self->priv->pixbuf_cell = cell = gtk_cell_renderer_pixbuf_new ();
+ g_object_set (cell,
+ "xalign", 0.5,
+ "yalign", 0.5,
+ "xpad", 12,
+ "ypad", 2,
+ NULL);
+ gtk_tree_view_column_pack_start (self->priv->tree_col, cell, FALSE);
+
+ self->priv->text_cell = cell = gd_two_lines_renderer_new ();
+ g_object_set (cell,
+ "xalign", 0.0,
+ "wrap-mode", PANGO_WRAP_WORD_CHAR,
+ "xpad", 12,
+ "text-lines", 2,
+ NULL);
+ gtk_tree_view_column_pack_start (self->priv->tree_col, cell, TRUE);
+
+ set_attributes_from_model (self);
+
+ gtk_tree_view_enable_model_drag_source (GTK_TREE_VIEW (self),
+ GDK_BUTTON1_MASK,
+ targets, 1,
+ GDK_ACTION_COPY);
+}
+
+static void
+gd_main_list_view_class_init (GdMainListViewClass *klass)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *wclass = GTK_WIDGET_CLASS (klass);
+ GtkBindingSet *binding_set;
+ GdkModifierType activate_modifiers[] = { GDK_SHIFT_MASK, GDK_CONTROL_MASK, GDK_SHIFT_MASK | GDK_CONTROL_MASK };
+ guint i;
+
+ binding_set = gtk_binding_set_by_class (klass);
+
+ oclass->constructed = gd_main_list_view_constructed;
+ wclass->drag_data_get = gd_main_list_view_drag_data_get;
+ wclass->draw = gd_main_list_view_draw;
+
+ g_type_class_add_private (klass, sizeof (GdMainListViewPrivate));
+
+ for (i = 0; i < G_N_ELEMENTS (activate_modifiers); i++)
+ {
+ gtk_binding_entry_add_signal (binding_set, GDK_KEY_space, activate_modifiers[i],
+ "select-cursor-row", 1,
+ G_TYPE_BOOLEAN, TRUE);
+ gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Space, activate_modifiers[i],
+ "select-cursor-row", 1,
+ G_TYPE_BOOLEAN, TRUE);
+ gtk_binding_entry_add_signal (binding_set, GDK_KEY_Return, activate_modifiers[i],
+ "select-cursor-row", 1,
+ G_TYPE_BOOLEAN, TRUE);
+ gtk_binding_entry_add_signal (binding_set, GDK_KEY_ISO_Enter, activate_modifiers[i],
+ "select-cursor-row", 1,
+ G_TYPE_BOOLEAN, TRUE);
+ gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Enter, activate_modifiers[i],
+ "select-cursor-row", 1,
+ G_TYPE_BOOLEAN, TRUE);
+ }
+
+}
+
+static void
+gd_main_list_view_init (GdMainListView *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GD_TYPE_MAIN_LIST_VIEW, GdMainListViewPrivate);
+
+ g_signal_connect (self, "notify::model",
+ G_CALLBACK (set_attributes_from_model), NULL);
+}
+
+static GtkTreePath *
+gd_main_list_view_get_path_at_pos (GdMainViewGeneric *mv,
+ gint x,
+ gint y)
+{
+ GtkTreePath *path = NULL;
+
+ gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (mv), x, y, &path,
+ NULL, NULL, NULL);
+
+ return path;
+}
+
+static void
+gd_main_list_view_set_selection_mode (GdMainViewGeneric *mv,
+ gboolean selection_mode)
+{
+ GdMainListView *self = GD_MAIN_LIST_VIEW (mv);
+
+ self->priv->selection_mode = selection_mode;
+
+ g_object_set (self->priv->selection_cell,
+ "visible", selection_mode,
+ NULL);
+ gtk_tree_view_column_queue_resize (self->priv->tree_col);
+}
+
+static gboolean
+gd_main_list_view_draw (GtkWidget *widget,
+ cairo_t *cr)
+{
+ GdMainListView *self = GD_MAIN_LIST_VIEW (widget);
+ GtkStyleContext *context;
+ GdkRectangle lines_rect;
+ GdkRectangle rect;
+ GtkTreePath *path;
+ GtkTreePath *rubberband_start, *rubberband_end;
+
+ GTK_WIDGET_CLASS (gd_main_list_view_parent_class)->draw (widget, cr);
+
+ _gd_main_view_generic_get_rubberband_range (GD_MAIN_VIEW_GENERIC (self),
+ &rubberband_start, &rubberband_end);
+
+ if (rubberband_start)
+ {
+ context = gtk_widget_get_style_context (widget);
+
+ gtk_style_context_save (context);
+ gtk_style_context_add_class (context, GTK_STYLE_CLASS_RUBBERBAND);
+
+ path = gtk_tree_path_copy (rubberband_start);
+
+ lines_rect.width = 0;
+
+ while (gtk_tree_path_compare (path, rubberband_end) <= 0)
+ {
+ gtk_tree_view_get_cell_area (GTK_TREE_VIEW (self),
+ path, self->priv->tree_col, &rect);
+ if (lines_rect.width == 0)
+ lines_rect = rect;
+ else
+ gdk_rectangle_union (&rect, &lines_rect, &lines_rect);
+
+ gtk_tree_path_next (path);
+ }
+ gtk_tree_path_free (path);
+
+ gtk_render_background (context, cr,
+ lines_rect.x, lines_rect.y,
+ lines_rect.width, lines_rect.height);
+ gtk_render_frame (context, cr,
+ lines_rect.x, lines_rect.y,
+ lines_rect.width, lines_rect.height);
+
+
+ gtk_style_context_restore (context);
+ }
+
+ return FALSE;
+}
+
+static void
+gd_main_list_view_scroll_to_path (GdMainViewGeneric *mv,
+ GtkTreePath *path)
+{
+ gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (mv), path, NULL, TRUE, 0.5, 0.5);
+}
+
+static void
+gd_main_list_view_set_model (GdMainViewGeneric *mv,
+ GtkTreeModel *model)
+{
+ gtk_tree_view_set_model (GTK_TREE_VIEW (mv), model);
+}
+
+static GtkTreeModel *
+gd_main_list_view_get_model (GdMainViewGeneric *mv)
+{
+ return gtk_tree_view_get_model (GTK_TREE_VIEW (mv));
+}
+
+static void
+gd_main_view_generic_iface_init (GdMainViewGenericIface *iface)
+{
+ iface->set_model = gd_main_list_view_set_model;
+ iface->get_model = gd_main_list_view_get_model;
+ iface->get_path_at_pos = gd_main_list_view_get_path_at_pos;
+ iface->scroll_to_path = gd_main_list_view_scroll_to_path;
+ iface->set_selection_mode = gd_main_list_view_set_selection_mode;
+}
+
+void
+gd_main_list_view_add_renderer (GdMainListView *self,
+ GtkCellRenderer *renderer,
+ GtkTreeCellDataFunc func,
+ gpointer user_data,
+ GDestroyNotify destroy)
+{
+ gtk_tree_view_column_pack_start (self->priv->tree_col, renderer, FALSE);
+ gtk_tree_view_column_set_cell_data_func (self->priv->tree_col, renderer,
+ func, user_data, destroy);
+}
+
+GtkWidget *
+gd_main_list_view_new (void)
+{
+ return g_object_new (GD_TYPE_MAIN_LIST_VIEW, NULL);
+}
diff --git a/subprojects/libgd/libgd/gd-main-list-view.h b/subprojects/libgd/libgd/gd-main-list-view.h
new file mode 100644
index 0000000..317e9c4
--- /dev/null
+++ b/subprojects/libgd/libgd/gd-main-list-view.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2011 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Cosimo Cecchi <cosimoc@redhat.com>
+ *
+ */
+
+#ifndef __GD_MAIN_LIST_VIEW_H__
+#define __GD_MAIN_LIST_VIEW_H__
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GD_TYPE_MAIN_LIST_VIEW gd_main_list_view_get_type()
+
+#define GD_MAIN_LIST_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ GD_TYPE_MAIN_LIST_VIEW, GdMainListView))
+
+#define GD_MAIN_LIST_VIEW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ GD_TYPE_MAIN_LIST_VIEW, GdMainListViewClass))
+
+#define GD_IS_MAIN_LIST_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ GD_TYPE_MAIN_LIST_VIEW))
+
+#define GD_IS_MAIN_LIST_VIEW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+ GD_TYPE_MAIN_LIST_VIEW))
+
+#define GD_MAIN_LIST_VIEW_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ GD_TYPE_MAIN_LIST_VIEW, GdMainListViewClass))
+
+typedef struct _GdMainListView GdMainListView;
+typedef struct _GdMainListViewClass GdMainListViewClass;
+typedef struct _GdMainListViewPrivate GdMainListViewPrivate;
+
+struct _GdMainListView
+{
+ GtkTreeView parent;
+
+ GdMainListViewPrivate *priv;
+};
+
+struct _GdMainListViewClass
+{
+ GtkTreeViewClass parent_class;
+};
+
+GType gd_main_list_view_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gd_main_list_view_new (void);
+
+void gd_main_list_view_add_renderer (GdMainListView *self,
+ GtkCellRenderer *renderer,
+ GtkTreeCellDataFunc func,
+ gpointer user_data,
+ GDestroyNotify destroy);
+
+G_END_DECLS
+
+#endif /* __GD_MAIN_LIST_VIEW_H__ */
diff --git a/subprojects/libgd/libgd/gd-main-view-generic.c b/subprojects/libgd/libgd/gd-main-view-generic.c
new file mode 100644
index 0000000..51347e0
--- /dev/null
+++ b/subprojects/libgd/libgd/gd-main-view-generic.c
@@ -0,0 +1,330 @@
+/*
+ * Copyright (c) 2011 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Cosimo Cecchi <cosimoc@redhat.com>
+ *
+ */
+
+#include "gd-main-view-generic.h"
+
+enum {
+ VIEW_SELECTION_CHANGED,
+ NUM_SIGNALS
+};
+
+static guint signals[NUM_SIGNALS] = { 0, };
+
+typedef GdMainViewGenericIface GdMainViewGenericInterface;
+G_DEFINE_INTERFACE (GdMainViewGeneric, gd_main_view_generic, GTK_TYPE_WIDGET)
+
+static void
+gd_main_view_generic_default_init (GdMainViewGenericInterface *iface)
+{
+ signals[VIEW_SELECTION_CHANGED] =
+ g_signal_new ("view-selection-changed",
+ GD_TYPE_MAIN_VIEW_GENERIC,
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+}
+
+/**
+ * gd_main_view_generic_set_model:
+ * @self:
+ * @model: (allow-none):
+ *
+ */
+void
+gd_main_view_generic_set_model (GdMainViewGeneric *self,
+ GtkTreeModel *model)
+{
+ GdMainViewGenericInterface *iface;
+
+ iface = GD_MAIN_VIEW_GENERIC_GET_IFACE (self);
+
+ (* iface->set_model) (self, model);
+}
+
+GtkTreePath *
+gd_main_view_generic_get_path_at_pos (GdMainViewGeneric *self,
+ gint x,
+ gint y)
+{
+ GdMainViewGenericInterface *iface;
+
+ iface = GD_MAIN_VIEW_GENERIC_GET_IFACE (self);
+
+ return (* iface->get_path_at_pos) (self, x, y);
+}
+
+void
+gd_main_view_generic_set_selection_mode (GdMainViewGeneric *self,
+ gboolean selection_mode)
+{
+ GdMainViewGenericInterface *iface;
+
+ iface = GD_MAIN_VIEW_GENERIC_GET_IFACE (self);
+
+ (* iface->set_selection_mode) (self, selection_mode);
+}
+
+
+typedef struct {
+ GtkTreePath *rubberband_start;
+ GtkTreePath *rubberband_end;
+} RubberbandInfo;
+
+static void
+rubber_band_info_destroy (RubberbandInfo *info)
+{
+ g_clear_pointer (&info->rubberband_start,
+ gtk_tree_path_free);
+ g_clear_pointer (&info->rubberband_end,
+ gtk_tree_path_free);
+ g_slice_free (RubberbandInfo, info);
+}
+
+static RubberbandInfo*
+get_rubber_band_info (GdMainViewGeneric *self)
+{
+ RubberbandInfo *info;
+
+ info = g_object_get_data (G_OBJECT (self), "gd-main-view-generic-rubber-band");
+ if (info == NULL)
+ {
+ info = g_slice_new0 (RubberbandInfo);
+ g_object_set_data_full (G_OBJECT (self), "gd-main-view-generic-rubber-band",
+ info, (GDestroyNotify)rubber_band_info_destroy);
+ }
+
+ return info;
+}
+
+void
+gd_main_view_generic_set_rubberband_range (GdMainViewGeneric *self,
+ GtkTreePath *start,
+ GtkTreePath *end)
+{
+ RubberbandInfo *info;
+
+ info = get_rubber_band_info (self);
+
+ if (start == NULL || end == NULL)
+ {
+ g_clear_pointer (&info->rubberband_start,
+ gtk_tree_path_free);
+ g_clear_pointer (&info->rubberband_end,
+ gtk_tree_path_free);
+ }
+ else
+ {
+ if (gtk_tree_path_compare (start, end) < 0)
+ {
+ info->rubberband_start = gtk_tree_path_copy (start);
+ info->rubberband_end = gtk_tree_path_copy (end);
+ }
+ else
+ {
+ info->rubberband_start = gtk_tree_path_copy (end);
+ info->rubberband_end = gtk_tree_path_copy (start);
+ }
+ }
+
+ gtk_widget_queue_draw (GTK_WIDGET (self));
+}
+
+void
+_gd_main_view_generic_get_rubberband_range (GdMainViewGeneric *self,
+ GtkTreePath **start,
+ GtkTreePath **end)
+{
+ RubberbandInfo *info;
+
+ info = get_rubber_band_info (self);
+
+ *start = info->rubberband_start;
+ *end = info->rubberband_end;
+}
+
+void
+gd_main_view_generic_scroll_to_path (GdMainViewGeneric *self,
+ GtkTreePath *path)
+{
+ GdMainViewGenericInterface *iface;
+
+ iface = GD_MAIN_VIEW_GENERIC_GET_IFACE (self);
+
+ (* iface->scroll_to_path) (self, path);
+}
+
+/**
+ * gd_main_view_generic_get_model:
+ *
+ * Returns: (transfer none): The associated model
+ */
+GtkTreeModel *
+gd_main_view_generic_get_model (GdMainViewGeneric *self)
+{
+ GdMainViewGenericInterface *iface;
+
+ iface = GD_MAIN_VIEW_GENERIC_GET_IFACE (self);
+
+ return (* iface->get_model) (self);
+}
+
+static gboolean
+build_selection_uris_foreach (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer user_data)
+{
+ GPtrArray *ptr_array = user_data;
+ gchar *uri;
+ gboolean is_selected;
+
+ gtk_tree_model_get (model, iter,
+ GD_MAIN_COLUMN_URI, &uri,
+ GD_MAIN_COLUMN_SELECTED, &is_selected,
+ -1);
+
+ if (is_selected)
+ g_ptr_array_add (ptr_array, uri);
+ else
+ g_free (uri);
+
+ return FALSE;
+}
+
+static gchar **
+model_get_selection_uris (GtkTreeModel *model)
+{
+ GPtrArray *ptr_array = g_ptr_array_new ();
+
+ gtk_tree_model_foreach (model,
+ build_selection_uris_foreach,
+ ptr_array);
+
+ g_ptr_array_add (ptr_array, NULL);
+ return (gchar **) g_ptr_array_free (ptr_array, FALSE);
+}
+
+static gboolean
+set_selection_foreach (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer user_data)
+{
+ gboolean selection = GPOINTER_TO_INT (user_data);
+ GtkTreeModel *actual_model;
+ GtkTreeIter real_iter;
+
+ if (GTK_IS_TREE_MODEL_FILTER (model))
+ {
+ actual_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (model));
+ gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (model),
+ &real_iter, iter);
+ }
+ else if (GTK_IS_TREE_MODEL_SORT (model))
+ {
+ actual_model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (model));
+ gtk_tree_model_sort_convert_iter_to_child_iter (GTK_TREE_MODEL_SORT (model),
+ &real_iter, iter);
+ }
+ else
+ {
+ actual_model = model;
+ real_iter = *iter;
+ }
+
+ if (GTK_IS_LIST_STORE (actual_model))
+ {
+ gtk_list_store_set (GTK_LIST_STORE (actual_model), &real_iter,
+ GD_MAIN_COLUMN_SELECTED, selection,
+ -1);
+ }
+ else
+ {
+ gtk_tree_store_set (GTK_TREE_STORE (actual_model), &real_iter,
+ GD_MAIN_COLUMN_SELECTED, selection,
+ -1);
+ }
+
+ return FALSE;
+}
+
+static void
+set_all_selection (GdMainViewGeneric *self,
+ GtkTreeModel *model,
+ gboolean selection)
+{
+ gtk_tree_model_foreach (model,
+ set_selection_foreach,
+ GINT_TO_POINTER (selection));
+ g_signal_emit (self, signals[VIEW_SELECTION_CHANGED], 0);
+}
+
+void
+gd_main_view_generic_select_all (GdMainViewGeneric *self)
+{
+ GtkTreeModel *model = gd_main_view_generic_get_model (self);
+
+ set_all_selection (self, model, TRUE);
+}
+
+void
+gd_main_view_generic_unselect_all (GdMainViewGeneric *self)
+{
+ GtkTreeModel *model = gd_main_view_generic_get_model (self);
+
+ set_all_selection (self, model, FALSE);
+}
+
+void
+_gd_main_view_generic_dnd_common (GtkTreeModel *model,
+ gboolean selection_mode,
+ GtkTreePath *path,
+ GtkSelectionData *data)
+{
+ gchar **uris;
+
+ if (selection_mode)
+ {
+ uris = model_get_selection_uris (model);
+ }
+ else
+ {
+ GtkTreeIter iter;
+ gboolean res;
+ gchar *uri = NULL;
+
+ if (path != NULL)
+ {
+ res = gtk_tree_model_get_iter (model, &iter, path);
+ if (res)
+ gtk_tree_model_get (model, &iter,
+ GD_MAIN_COLUMN_URI, &uri,
+ -1);
+ }
+
+ uris = g_new0 (gchar *, 2);
+ uris[0] = uri;
+ uris[1] = NULL;
+ }
+
+ gtk_selection_data_set_uris (data, uris);
+ g_strfreev (uris);
+}
diff --git a/subprojects/libgd/libgd/gd-main-view-generic.h b/subprojects/libgd/libgd/gd-main-view-generic.h
new file mode 100644
index 0000000..dd53e0e
--- /dev/null
+++ b/subprojects/libgd/libgd/gd-main-view-generic.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2011 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Cosimo Cecchi <cosimoc@redhat.com>
+ *
+ */
+
+#ifndef __GD_MAIN_VIEW_GENERIC_H__
+#define __GD_MAIN_VIEW_GENERIC_H__
+
+#include <glib-object.h>
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+ GD_MAIN_COLUMN_ID,
+ GD_MAIN_COLUMN_URI,
+ GD_MAIN_COLUMN_PRIMARY_TEXT,
+ GD_MAIN_COLUMN_SECONDARY_TEXT,
+ GD_MAIN_COLUMN_ICON,
+ GD_MAIN_COLUMN_MTIME,
+ GD_MAIN_COLUMN_SELECTED,
+ GD_MAIN_COLUMN_PULSE,
+
+ GD_MAIN_COLUMN_LAST
+} GdMainColumns;
+
+#define GD_TYPE_MAIN_VIEW_GENERIC gd_main_view_generic_get_type()
+
+#define GD_MAIN_VIEW_GENERIC(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ GD_TYPE_MAIN_VIEW_GENERIC, GdMainViewGeneric))
+
+#define GD_MAIN_VIEW_GENERIC_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ GD_TYPE_MAIN_VIEW_GENERIC, GdMainViewGenericIface))
+
+#define GD_IS_MAIN_VIEW_GENERIC(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ GD_TYPE_MAIN_VIEW_GENERIC))
+
+#define GD_IS_MAIN_VIEW_GENERIC_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+ GD_TYPE_MAIN_VIEW_GENERIC))
+
+#define GD_MAIN_VIEW_GENERIC_GET_IFACE(obj) \
+ (G_TYPE_INSTANCE_GET_INTERFACE ((obj), \
+ GD_TYPE_MAIN_VIEW_GENERIC, GdMainViewGenericIface))
+
+typedef struct _GdMainViewGeneric GdMainViewGeneric;
+typedef struct _GdMainViewGenericIface GdMainViewGenericIface;
+
+struct _GdMainViewGenericIface
+{
+ GTypeInterface base_iface;
+
+ /* vtable */
+ void (* set_model) (GdMainViewGeneric *self,
+ GtkTreeModel *model);
+ GtkTreeModel * (* get_model) (GdMainViewGeneric *self);
+
+ GtkTreePath * (* get_path_at_pos) (GdMainViewGeneric *self,
+ gint x,
+ gint y);
+ void (* scroll_to_path) (GdMainViewGeneric *self,
+ GtkTreePath *path);
+ void (* set_selection_mode) (GdMainViewGeneric *self,
+ gboolean selection_mode);
+};
+
+GType gd_main_view_generic_get_type (void) G_GNUC_CONST;
+
+void gd_main_view_generic_set_model (GdMainViewGeneric *self,
+ GtkTreeModel *model);
+GtkTreeModel * gd_main_view_generic_get_model (GdMainViewGeneric *self);
+
+void gd_main_view_generic_scroll_to_path (GdMainViewGeneric *self,
+ GtkTreePath *path);
+void gd_main_view_generic_set_selection_mode (GdMainViewGeneric *self,
+ gboolean selection_mode);
+GtkTreePath * gd_main_view_generic_get_path_at_pos (GdMainViewGeneric *self,
+ gint x,
+ gint y);
+void gd_main_view_generic_select_all (GdMainViewGeneric *self);
+void gd_main_view_generic_unselect_all (GdMainViewGeneric *self);
+void gd_main_view_generic_set_rubberband_range (GdMainViewGeneric *self,
+ GtkTreePath *start,
+ GtkTreePath *end);
+
+/* private */
+void _gd_main_view_generic_dnd_common (GtkTreeModel *model,
+ gboolean selection_mode,
+ GtkTreePath *path,
+ GtkSelectionData *data);
+void _gd_main_view_generic_get_rubberband_range (GdMainViewGeneric *self,
+ GtkTreePath **start,
+ GtkTreePath **end);
+
+G_END_DECLS
+
+#endif /* __GD_MAIN_VIEW_GENERIC_H__ */
diff --git a/subprojects/libgd/libgd/gd-main-view.c b/subprojects/libgd/libgd/gd-main-view.c
new file mode 100644
index 0000000..008162b
--- /dev/null
+++ b/subprojects/libgd/libgd/gd-main-view.c
@@ -0,0 +1,1160 @@
+/*
+ * Copyright (c) 2011 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Cosimo Cecchi <cosimoc@redhat.com>
+ *
+ */
+
+#include "gd-main-view.h"
+
+#include "gd-icon-utils.h"
+#include "gd-main-view-generic.h"
+#include "gd-main-icon-view.h"
+#include "gd-main-list-view.h"
+
+#include <math.h>
+#include <cairo-gobject.h>
+
+#define MAIN_VIEW_TYPE_INITIAL -1
+#define MAIN_VIEW_DND_ICON_OFFSET 20
+#define MAIN_VIEW_RUBBERBAND_SELECT_TRIGGER_LENGTH 32
+
+typedef struct _GdMainViewPrivate GdMainViewPrivate;
+
+struct _GdMainViewPrivate {
+ GdMainViewType current_type;
+ gboolean selection_mode;
+
+ GtkWidget *current_view;
+ GtkTreeModel *model;
+
+ gboolean track_motion;
+ gboolean rubberband_select;
+ GtkTreePath *rubberband_select_first_path;
+ GtkTreePath *rubberband_select_last_path;
+ int button_down_x;
+ int button_down_y;
+
+ gchar *button_press_item_path;
+
+ gchar *last_selected_id;
+};
+
+enum {
+ PROP_VIEW_TYPE = 1,
+ PROP_SELECTION_MODE,
+ PROP_MODEL,
+ NUM_PROPERTIES
+};
+
+enum {
+ ITEM_ACTIVATED = 1,
+ SELECTION_MODE_REQUEST,
+ VIEW_SELECTION_CHANGED,
+ NUM_SIGNALS
+};
+
+static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
+static guint signals[NUM_SIGNALS] = { 0, };
+
+G_DEFINE_TYPE_WITH_PRIVATE (GdMainView, gd_main_view, GTK_TYPE_SCROLLED_WINDOW)
+
+static void
+gd_main_view_dispose (GObject *obj)
+{
+ GdMainView *self = GD_MAIN_VIEW (obj);
+ GdMainViewPrivate *priv;
+
+ priv = gd_main_view_get_instance_private (self);
+
+ g_clear_object (&priv->model);
+
+ G_OBJECT_CLASS (gd_main_view_parent_class)->dispose (obj);
+}
+
+static void
+gd_main_view_finalize (GObject *obj)
+{
+ GdMainView *self = GD_MAIN_VIEW (obj);
+ GdMainViewPrivate *priv;
+
+ priv = gd_main_view_get_instance_private (self);
+
+ g_free (priv->button_press_item_path);
+ g_free (priv->last_selected_id);
+
+ if (priv->rubberband_select_first_path)
+ gtk_tree_path_free (priv->rubberband_select_first_path);
+
+ if (priv->rubberband_select_last_path)
+ gtk_tree_path_free (priv->rubberband_select_last_path);
+
+ G_OBJECT_CLASS (gd_main_view_parent_class)->finalize (obj);
+}
+
+static void
+gd_main_view_init (GdMainView *self)
+{
+ GdMainViewPrivate *priv;
+ GtkStyleContext *context;
+
+ priv = gd_main_view_get_instance_private (self);
+
+ /* so that we get constructed with the right view even at startup */
+ priv->current_type = MAIN_VIEW_TYPE_INITIAL;
+
+ gtk_widget_set_hexpand (GTK_WIDGET (self), TRUE);
+ gtk_widget_set_vexpand (GTK_WIDGET (self), TRUE);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (self), GTK_SHADOW_IN);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (self),
+ GTK_POLICY_NEVER,
+ GTK_POLICY_AUTOMATIC);
+
+ context = gtk_widget_get_style_context (GTK_WIDGET (self));
+ gtk_style_context_add_class (context, "documents-scrolledwin");
+}
+
+static void
+gd_main_view_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GdMainView *self = GD_MAIN_VIEW (object);
+
+ switch (property_id)
+ {
+ case PROP_VIEW_TYPE:
+ g_value_set_int (value, gd_main_view_get_view_type (self));
+ break;
+ case PROP_SELECTION_MODE:
+ g_value_set_boolean (value, gd_main_view_get_selection_mode (self));
+ break;
+ case PROP_MODEL:
+ g_value_set_object (value, gd_main_view_get_model (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gd_main_view_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GdMainView *self = GD_MAIN_VIEW (object);
+
+ switch (property_id)
+ {
+ case PROP_VIEW_TYPE:
+ gd_main_view_set_view_type (self, g_value_get_int (value));
+ break;
+ case PROP_SELECTION_MODE:
+ gd_main_view_set_selection_mode (self, g_value_get_boolean (value));
+ break;
+ case PROP_MODEL:
+ gd_main_view_set_model (self, g_value_get_object (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gd_main_view_class_init (GdMainViewClass *klass)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+
+ oclass->get_property = gd_main_view_get_property;
+ oclass->set_property = gd_main_view_set_property;
+ oclass->dispose = gd_main_view_dispose;
+ oclass->finalize = gd_main_view_finalize;
+
+ properties[PROP_VIEW_TYPE] =
+ g_param_spec_int ("view-type",
+ "View type",
+ "View type",
+ GD_MAIN_VIEW_ICON,
+ GD_MAIN_VIEW_LIST,
+ GD_MAIN_VIEW_ICON,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_SELECTION_MODE] =
+ g_param_spec_boolean ("selection-mode",
+ "Selection mode",
+ "Whether the view is in selection mode",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_MODEL] =
+ g_param_spec_object ("model",
+ "Model",
+ "The GtkTreeModel",
+ GTK_TYPE_TREE_MODEL,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS);
+
+ signals[ITEM_ACTIVATED] =
+ g_signal_new ("item-activated",
+ GD_TYPE_MAIN_VIEW,
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 2,
+ G_TYPE_STRING,
+ GTK_TYPE_TREE_PATH);
+
+ signals[SELECTION_MODE_REQUEST] =
+ g_signal_new ("selection-mode-request",
+ GD_TYPE_MAIN_VIEW,
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
+ signals[VIEW_SELECTION_CHANGED] =
+ g_signal_new ("view-selection-changed",
+ GD_TYPE_MAIN_VIEW,
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
+ g_object_class_install_properties (oclass, NUM_PROPERTIES, properties);
+}
+
+static GdMainViewGeneric *
+get_generic (GdMainView *self)
+{
+ GdMainViewPrivate *priv;
+
+ priv = gd_main_view_get_instance_private (self);
+
+ if (priv->current_view != NULL)
+ return GD_MAIN_VIEW_GENERIC (priv->current_view);
+
+ return NULL;
+}
+
+static void
+do_select_row (GdMainView *self,
+ GtkTreeIter *iter,
+ gboolean value)
+{
+ GdMainViewPrivate *priv;
+ GtkTreeModel *model;
+ GtkTreeIter my_iter;
+ GtkTreePath *path;
+
+ priv = gd_main_view_get_instance_private (self);
+
+ model = priv->model;
+ my_iter = *iter;
+
+ while (GTK_IS_TREE_MODEL_FILTER (model) ||
+ GTK_IS_TREE_MODEL_SORT (model))
+ {
+ GtkTreeIter child_iter;
+
+ if (GTK_IS_TREE_MODEL_FILTER (model))
+ {
+ GtkTreeModelFilter *filter;
+
+ filter = GTK_TREE_MODEL_FILTER (model);
+ gtk_tree_model_filter_convert_iter_to_child_iter (filter, &child_iter, &my_iter);
+ model = gtk_tree_model_filter_get_model (filter);
+ }
+ else
+ {
+ GtkTreeModelSort *sort;
+
+ sort = GTK_TREE_MODEL_SORT (model);
+ gtk_tree_model_sort_convert_iter_to_child_iter (sort, &child_iter, &my_iter);
+ model = gtk_tree_model_sort_get_model (sort);
+ }
+
+ my_iter = child_iter;
+ }
+
+ if (GTK_IS_LIST_STORE (model))
+ {
+ gtk_list_store_set (GTK_LIST_STORE (model), &my_iter,
+ GD_MAIN_COLUMN_SELECTED, value,
+ -1);
+ }
+ else
+ {
+ gtk_tree_store_set (GTK_TREE_STORE (model), &my_iter,
+ GD_MAIN_COLUMN_SELECTED, value,
+ -1);
+ }
+
+ /* And tell the view model that something changed */
+ path = gtk_tree_model_get_path (priv->model, iter);
+ if (path)
+ {
+ gtk_tree_model_row_changed (priv->model, path, iter);
+ gtk_tree_path_free (path);
+ }
+}
+
+static void
+selection_mode_do_select_range (GdMainView *self,
+ GtkTreeIter *first_element,
+ GtkTreeIter *last_element)
+{
+ GdMainViewPrivate *priv;
+ GtkTreeIter iter;
+ GtkTreePath *path, *last_path;
+ gboolean equal;
+
+ priv = gd_main_view_get_instance_private (self);
+
+ path = gtk_tree_model_get_path (priv->model, first_element);
+ last_path = gtk_tree_model_get_path (priv->model, last_element);
+ if (gtk_tree_path_compare (path, last_path) > 0)
+ {
+ gtk_tree_path_free (last_path);
+ last_path = path;
+ iter = *last_element;
+ }
+ else
+ {
+ gtk_tree_path_free (path);
+ iter = *first_element;
+ }
+
+ do
+ {
+ do_select_row (self, &iter, TRUE);
+
+ path = gtk_tree_model_get_path (priv->model, &iter);
+ equal = (gtk_tree_path_compare (path, last_path) == 0);
+ gtk_tree_path_free (path);
+
+ if (equal)
+ break;
+ }
+ while (gtk_tree_model_iter_next (priv->model, &iter));
+
+ gtk_tree_path_free (last_path);
+}
+
+static void
+selection_mode_select_range (GdMainView *self,
+ GtkTreeIter *iter)
+{
+ GdMainViewPrivate *priv;
+ GtkTreeIter other;
+ gboolean found = FALSE;
+ gboolean selected;
+ char *id;
+
+ priv = gd_main_view_get_instance_private (self);
+
+ if (priv->last_selected_id != NULL &&
+ gtk_tree_model_get_iter_first (priv->model, &other))
+ {
+ do
+ {
+ gtk_tree_model_get (priv->model, &other,
+ GD_MAIN_COLUMN_ID, &id,
+ -1);
+ if (g_strcmp0 (id, priv->last_selected_id) == 0)
+ {
+ g_free (id);
+ found = TRUE;
+ break;
+ }
+ g_free (id);
+ }
+ while (gtk_tree_model_iter_next (priv->model, &other));
+ }
+
+ if (!found)
+ {
+ other = *iter;
+ while (gtk_tree_model_iter_previous (priv->model, &other))
+ {
+ gtk_tree_model_get (priv->model, &other,
+ GD_MAIN_COLUMN_SELECTED, &selected,
+ -1);
+
+ if (selected)
+ {
+ found = TRUE;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ {
+ other = *iter;
+ while (gtk_tree_model_iter_next (priv->model, &other))
+ {
+ gtk_tree_model_get (priv->model, &other,
+ GD_MAIN_COLUMN_SELECTED, &selected,
+ -1);
+ if (selected)
+ {
+ found = TRUE;
+ break;
+ }
+ }
+ }
+
+ if (found)
+ selection_mode_do_select_range (self, iter, &other);
+ else
+ {
+ /* no other selected element found, just select the iter */
+ do_select_row (self, iter, TRUE);
+ }
+
+ g_signal_emit (self, signals[VIEW_SELECTION_CHANGED], 0);
+}
+
+static gboolean
+toggle_selection_for_path (GdMainView *self,
+ GtkTreePath *path,
+ gboolean select_range)
+{
+ GdMainViewPrivate *priv;
+ gboolean selected;
+ GtkTreeIter iter;
+ char *id;
+
+ priv = gd_main_view_get_instance_private (self);
+
+ if (priv->model == NULL)
+ return FALSE;
+
+ if (!gtk_tree_model_get_iter (priv->model, &iter, path))
+ return FALSE;
+
+ gtk_tree_model_get (priv->model, &iter,
+ GD_MAIN_COLUMN_SELECTED, &selected,
+ -1);
+
+ if (selected)
+ {
+ do_select_row (self, &iter, FALSE);
+ }
+ else if (!selected)
+ {
+ if (select_range)
+ selection_mode_select_range (self, &iter);
+ else
+ {
+ gtk_tree_model_get (priv->model, &iter,
+ GD_MAIN_COLUMN_ID, &id,
+ -1);
+ g_free (priv->last_selected_id);
+ priv->last_selected_id = id;
+
+ do_select_row (self, &iter, TRUE);
+ }
+ }
+
+ g_signal_emit (self, signals[VIEW_SELECTION_CHANGED], 0);
+
+ return FALSE;
+}
+
+static gboolean
+activate_item_for_path (GdMainView *self,
+ GtkTreePath *path)
+{
+ GdMainViewPrivate *priv;
+ GtkTreeIter iter;
+ gchar *id;
+
+ priv = gd_main_view_get_instance_private (self);
+
+ if (priv->model == NULL)
+ return FALSE;
+
+ if (!gtk_tree_model_get_iter (priv->model, &iter, path))
+ return FALSE;
+
+ gtk_tree_model_get (priv->model, &iter,
+ GD_MAIN_COLUMN_ID, &id,
+ -1);
+
+ g_signal_emit (self, signals[ITEM_ACTIVATED], 0, id, path);
+ g_free (id);
+
+ return FALSE;
+}
+
+static gboolean
+on_button_release_selection_mode (GdMainView *self,
+ GdkEventButton *event,
+ GtkTreePath *path)
+{
+ return toggle_selection_for_path (self, path, ((event->state & GDK_SHIFT_MASK) != 0));
+}
+
+static gboolean
+on_button_release_view_mode (GdMainView *self,
+ GdkEventButton *event,
+ GtkTreePath *path)
+{
+ return activate_item_for_path (self, path);
+}
+
+static gboolean
+event_triggers_selection_mode (GdkEventButton *event)
+{
+ return
+ (event->button == 3) ||
+ ((event->button == 1) && (event->state & GDK_CONTROL_MASK));
+}
+
+static gboolean
+on_button_release_event (GtkWidget *view,
+ GdkEventButton *event,
+ gpointer user_data)
+{
+ GdMainView *self = user_data;
+ GdMainViewPrivate *priv;
+ GdMainViewGeneric *generic = get_generic (self);
+ GtkTreePath *path, *start_path, *end_path, *tmp_path;
+ GtkTreeIter iter;
+ gchar *button_release_item_path;
+ gboolean selection_mode;
+ gboolean res, same_item = FALSE;
+ gboolean is_selected;
+
+ priv = gd_main_view_get_instance_private (self);
+
+ /* eat double/triple click events */
+ if (event->type != GDK_BUTTON_RELEASE)
+ return TRUE;
+
+ path = gd_main_view_generic_get_path_at_pos (generic, event->x, event->y);
+
+ if (path != NULL)
+ {
+ button_release_item_path = gtk_tree_path_to_string (path);
+ if (g_strcmp0 (priv->button_press_item_path, button_release_item_path) == 0)
+ same_item = TRUE;
+
+ g_free (button_release_item_path);
+ }
+
+ g_free (priv->button_press_item_path);
+ priv->button_press_item_path = NULL;
+
+ priv->track_motion = FALSE;
+ if (priv->rubberband_select)
+ {
+ priv->rubberband_select = FALSE;
+ gd_main_view_generic_set_rubberband_range (get_generic (self), NULL, NULL);
+ if (priv->rubberband_select_last_path)
+ {
+ if (!priv->selection_mode)
+ g_signal_emit (self, signals[SELECTION_MODE_REQUEST], 0);
+ if (!priv->selection_mode)
+ {
+ res = FALSE;
+ goto out;
+ }
+
+ start_path = gtk_tree_path_copy (priv->rubberband_select_first_path);
+ end_path = gtk_tree_path_copy (priv->rubberband_select_last_path);
+ if (gtk_tree_path_compare (start_path, end_path) > 0)
+ {
+ tmp_path = start_path;
+ start_path = end_path;
+ end_path = tmp_path;
+ }
+
+ while (gtk_tree_path_compare (start_path, end_path) <= 0)
+ {
+ if (gtk_tree_model_get_iter (priv->model,
+ &iter, start_path))
+ {
+ gtk_tree_model_get (priv->model, &iter,
+ GD_MAIN_COLUMN_SELECTED, &is_selected,
+ -1);
+ do_select_row (self, &iter, !is_selected);
+ }
+
+ gtk_tree_path_next (start_path);
+ }
+
+ g_signal_emit (self, signals[VIEW_SELECTION_CHANGED], 0);
+
+ gtk_tree_path_free (start_path);
+ gtk_tree_path_free (end_path);
+ }
+
+ g_clear_pointer (&priv->rubberband_select_first_path,
+ gtk_tree_path_free);
+ g_clear_pointer (&priv->rubberband_select_last_path,
+ gtk_tree_path_free);
+
+ res = TRUE;
+ goto out;
+ }
+
+ if (!same_item)
+ {
+ res = FALSE;
+ goto out;
+ }
+
+ selection_mode = priv->selection_mode;
+
+ if (!selection_mode)
+ {
+ if (event_triggers_selection_mode (event))
+ {
+ g_signal_emit (self, signals[SELECTION_MODE_REQUEST], 0);
+ if (!priv->selection_mode)
+ {
+ res = FALSE;
+ goto out;
+ }
+ selection_mode = priv->selection_mode;
+ }
+ }
+
+ if (selection_mode)
+ res = on_button_release_selection_mode (self, event, path);
+ else
+ res = on_button_release_view_mode (self, event, path);
+
+ out:
+ gtk_tree_path_free (path);
+ return res;
+}
+
+static gboolean
+on_button_press_event (GtkWidget *view,
+ GdkEventButton *event,
+ gpointer user_data)
+{
+ GdMainView *self = user_data;
+ GdMainViewPrivate *priv;
+ GdMainViewGeneric *generic = get_generic (self);
+ GtkTreePath *path;
+ GList *selection, *l;
+ GtkTreePath *sel_path;
+ gboolean found = FALSE;
+ gboolean force_selection;
+
+ priv = gd_main_view_get_instance_private (self);
+
+ path = gd_main_view_generic_get_path_at_pos (generic, event->x, event->y);
+
+ if (path != NULL)
+ priv->button_press_item_path = gtk_tree_path_to_string (path);
+
+ force_selection = event_triggers_selection_mode (event);
+ if (!priv->selection_mode && !force_selection)
+ {
+ gtk_tree_path_free (path);
+ return FALSE;
+ }
+
+ if (path && !force_selection)
+ {
+ selection = gd_main_view_get_selection (self);
+
+ for (l = selection; l != NULL; l = l->next)
+ {
+ sel_path = l->data;
+ if (gtk_tree_path_compare (path, sel_path) == 0)
+ {
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (selection != NULL)
+ g_list_free_full (selection, (GDestroyNotify) gtk_tree_path_free);
+ }
+
+ /* if we did not find the item in the selection, block
+ * drag and drop, while in selection mode
+ */
+ if (!found)
+ {
+ priv->track_motion = TRUE;
+ priv->rubberband_select = FALSE;
+ priv->rubberband_select_first_path = NULL;
+ priv->rubberband_select_last_path = NULL;
+ priv->button_down_x = event->x;
+ priv->button_down_y = event->y;
+ return TRUE;
+ }
+ else
+ return FALSE;
+}
+
+static gboolean
+on_motion_event (GtkWidget *widget,
+ GdkEventMotion *event,
+ gpointer user_data)
+{
+ GdMainView *self = user_data;
+ GdMainViewPrivate *priv;
+ GtkTreePath *path;
+
+ priv = gd_main_view_get_instance_private (self);
+
+ if (priv->track_motion)
+ {
+ if (!priv->rubberband_select &&
+ (event->x - priv->button_down_x) * (event->x - priv->button_down_x) +
+ (event->y - priv->button_down_y) * (event->y - priv->button_down_y) >
+ MAIN_VIEW_RUBBERBAND_SELECT_TRIGGER_LENGTH * MAIN_VIEW_RUBBERBAND_SELECT_TRIGGER_LENGTH)
+ {
+ priv->rubberband_select = TRUE;
+ if (priv->button_press_item_path)
+ {
+ priv->rubberband_select_first_path =
+ gtk_tree_path_new_from_string (priv->button_press_item_path);
+ }
+ }
+
+ if (priv->rubberband_select)
+ {
+ path = gd_main_view_generic_get_path_at_pos (get_generic (self), event->x, event->y);
+ if (path != NULL)
+ {
+ if (priv->rubberband_select_first_path == NULL)
+ priv->rubberband_select_first_path = gtk_tree_path_copy (path);
+
+ if (priv->rubberband_select_last_path == NULL ||
+ gtk_tree_path_compare (priv->rubberband_select_last_path, path) != 0)
+ {
+ if (priv->rubberband_select_last_path)
+ gtk_tree_path_free (priv->rubberband_select_last_path);
+ priv->rubberband_select_last_path = path;
+
+ gd_main_view_generic_set_rubberband_range (get_generic (self),
+ priv->rubberband_select_first_path,
+ priv->rubberband_select_last_path);
+ }
+ else
+ gtk_tree_path_free (path);
+ }
+ }
+ }
+ return FALSE;
+}
+
+static void
+on_drag_begin (GdMainViewGeneric *generic,
+ GdkDragContext *drag_context,
+ gpointer user_data)
+{
+ GdMainView *self = user_data;
+ GdMainViewPrivate *priv;
+
+ priv = gd_main_view_get_instance_private (self);
+
+ if (priv->button_press_item_path != NULL)
+ {
+ gboolean res;
+ GtkTreeIter iter;
+ gpointer data;
+ cairo_surface_t *surface = NULL;
+ GtkTreePath *path;
+ GType column_gtype;
+
+ path = gtk_tree_path_new_from_string (priv->button_press_item_path);
+ res = gtk_tree_model_get_iter (priv->model,
+ &iter, path);
+ if (res)
+ gtk_tree_model_get (priv->model, &iter,
+ GD_MAIN_COLUMN_ICON, &data,
+ -1);
+
+ column_gtype = gtk_tree_model_get_column_type (priv->model,
+ GD_MAIN_COLUMN_ICON);
+
+ if (column_gtype == CAIRO_GOBJECT_TYPE_SURFACE)
+ {
+ surface = gd_copy_image_surface (data);
+ cairo_surface_destroy (data);
+ }
+ else if (column_gtype == GDK_TYPE_PIXBUF)
+ {
+ surface = gdk_cairo_surface_create_from_pixbuf (data, 1, NULL);
+ g_object_unref (data);
+ }
+ else
+ g_assert_not_reached ();
+
+ if (priv->selection_mode &&
+ surface != NULL)
+ {
+ GList *selection;
+ cairo_surface_t *counter;
+
+ selection = gd_main_view_get_selection (self);
+
+ if (g_list_length (selection) > 1)
+ {
+ counter = gd_create_surface_with_counter (GTK_WIDGET (self), surface, g_list_length (selection));
+ cairo_surface_destroy (surface);
+ surface = counter;
+ }
+
+ if (selection != NULL)
+ g_list_free_full (selection, (GDestroyNotify) gtk_tree_path_free);
+ }
+
+ if (surface != NULL)
+ {
+ cairo_surface_set_device_offset (surface,
+ -MAIN_VIEW_DND_ICON_OFFSET,
+ -MAIN_VIEW_DND_ICON_OFFSET);
+ gtk_drag_set_icon_surface (drag_context, surface);
+ cairo_surface_destroy (surface);
+ }
+
+ gtk_tree_path_free (path);
+ }
+}
+
+static void
+on_view_path_activated (GdMainView *self,
+ GtkTreePath *path)
+{
+ GdMainViewPrivate *priv;
+ GdkModifierType state;
+
+ priv = gd_main_view_get_instance_private (self);
+
+ gtk_get_current_event_state (&state);
+
+ if (priv->selection_mode || (state & GDK_CONTROL_MASK) != 0)
+ {
+ if (!priv->selection_mode)
+ g_signal_emit (self, signals[SELECTION_MODE_REQUEST], 0);
+ toggle_selection_for_path (self, path, ((state & GDK_SHIFT_MASK) != 0));
+ }
+ else
+ activate_item_for_path (self, path);
+}
+
+static void
+on_list_view_row_activated (GtkTreeView *tree_view,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ gpointer user_data)
+{
+ GdMainView *self = user_data;
+ on_view_path_activated (self, path);
+}
+
+static void
+on_icon_view_item_activated (GtkIconView *icon_view,
+ GtkTreePath *path,
+ gpointer user_data)
+{
+ GdMainView *self = user_data;
+ on_view_path_activated (self, path);
+}
+
+static void
+on_view_selection_changed (GtkWidget *view,
+ gpointer user_data)
+{
+ GdMainView *self = user_data;
+
+ g_signal_emit (self, signals[VIEW_SELECTION_CHANGED], 0);
+}
+
+static void
+on_row_deleted_cb (GtkTreeModel *model,
+ GtkTreePath *path,
+ gpointer user_data)
+{
+ GdMainView *self = user_data;
+
+ g_signal_emit (self, signals[VIEW_SELECTION_CHANGED], 0);
+}
+
+static void
+gd_main_view_apply_model (GdMainView *self)
+{
+ GdMainViewPrivate *priv;
+ GdMainViewGeneric *generic = get_generic (self);
+
+ priv = gd_main_view_get_instance_private (self);
+ gd_main_view_generic_set_model (generic, priv->model);
+}
+
+static void
+gd_main_view_apply_selection_mode (GdMainView *self)
+{
+ GdMainViewPrivate *priv;
+ GdMainViewGeneric *generic = get_generic (self);
+
+ priv = gd_main_view_get_instance_private (self);
+
+ gd_main_view_generic_set_selection_mode (generic, priv->selection_mode);
+
+ if (!priv->selection_mode)
+ {
+ g_clear_pointer (&priv->last_selected_id, g_free);
+ if (priv->model != NULL)
+ gd_main_view_unselect_all (self);
+ }
+}
+
+static void
+gd_main_view_rebuild (GdMainView *self)
+{
+ GdMainViewPrivate *priv;
+ GtkStyleContext *context;
+
+ priv = gd_main_view_get_instance_private (self);
+
+ if (priv->current_view != NULL)
+ gtk_widget_destroy (priv->current_view);
+
+ if (priv->current_type == GD_MAIN_VIEW_ICON)
+ {
+ priv->current_view = gd_main_icon_view_new ();
+ g_signal_connect (priv->current_view, "item-activated",
+ G_CALLBACK (on_icon_view_item_activated), self);
+ }
+ else
+ {
+ priv->current_view = gd_main_list_view_new ();
+ g_signal_connect (priv->current_view, "row-activated",
+ G_CALLBACK (on_list_view_row_activated), self);
+ }
+
+ context = gtk_widget_get_style_context (priv->current_view);
+ gtk_style_context_add_class (context, "content-view");
+
+ gtk_container_add (GTK_CONTAINER (self), priv->current_view);
+
+ g_signal_connect (priv->current_view, "button-press-event",
+ G_CALLBACK (on_button_press_event), self);
+ g_signal_connect (priv->current_view, "button-release-event",
+ G_CALLBACK (on_button_release_event), self);
+ g_signal_connect (priv->current_view, "motion-notify-event",
+ G_CALLBACK (on_motion_event), self);
+ g_signal_connect_after (priv->current_view, "drag-begin",
+ G_CALLBACK (on_drag_begin), self);
+ g_signal_connect (priv->current_view, "view-selection-changed",
+ G_CALLBACK (on_view_selection_changed), self);
+
+ gd_main_view_apply_model (self);
+ gd_main_view_apply_selection_mode (self);
+
+ gtk_widget_show_all (GTK_WIDGET (self));
+}
+
+GdMainView *
+gd_main_view_new (GdMainViewType type)
+{
+ return g_object_new (GD_TYPE_MAIN_VIEW,
+ "view-type", type,
+ NULL);
+}
+
+void
+gd_main_view_set_view_type (GdMainView *self,
+ GdMainViewType type)
+{
+ GdMainViewPrivate *priv;
+
+ priv = gd_main_view_get_instance_private (self);
+
+ if (type != priv->current_type)
+ {
+ priv->current_type = type;
+ gd_main_view_rebuild (self);
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_VIEW_TYPE]);
+ }
+}
+
+GdMainViewType
+gd_main_view_get_view_type (GdMainView *self)
+{
+ GdMainViewPrivate *priv;
+
+ priv = gd_main_view_get_instance_private (self);
+ return priv->current_type;
+}
+
+void
+gd_main_view_set_selection_mode (GdMainView *self,
+ gboolean selection_mode)
+{
+ GdMainViewPrivate *priv;
+
+ priv = gd_main_view_get_instance_private (self);
+
+ if (selection_mode != priv->selection_mode)
+ {
+ priv->selection_mode = selection_mode;
+ gd_main_view_apply_selection_mode (self);
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SELECTION_MODE]);
+ }
+}
+
+gboolean
+gd_main_view_get_selection_mode (GdMainView *self)
+{
+ GdMainViewPrivate *priv;
+
+ priv = gd_main_view_get_instance_private (self);
+ return priv->selection_mode;
+}
+
+/**
+ * gd_main_view_set_model:
+ * @self:
+ * @model: (allow-none):
+ *
+ */
+void
+gd_main_view_set_model (GdMainView *self,
+ GtkTreeModel *model)
+{
+ GdMainViewPrivate *priv;
+
+ priv = gd_main_view_get_instance_private (self);
+
+ if (model != priv->model)
+ {
+ if (priv->model)
+ g_signal_handlers_disconnect_by_func (priv->model,
+ on_row_deleted_cb, self);
+
+ g_clear_object (&priv->model);
+
+ if (model)
+ {
+ priv->model = g_object_ref (model);
+ g_signal_connect (priv->model, "row-deleted",
+ G_CALLBACK (on_row_deleted_cb), self);
+ }
+ else
+ {
+ priv->model = NULL;
+ }
+
+ gd_main_view_apply_model (self);
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]);
+ }
+}
+
+/**
+ * gd_main_view_get_model:
+ * @self:
+ *
+ * Returns: (transfer none):
+ */
+GtkTreeModel *
+gd_main_view_get_model (GdMainView *self)
+{
+ GdMainViewPrivate *priv;
+
+ priv = gd_main_view_get_instance_private (self);
+ return priv->model;
+}
+
+/**
+ * gd_main_view_get_generic_view:
+ * @self:
+ *
+ * Returns: (transfer none):
+ */
+GtkWidget *
+gd_main_view_get_generic_view (GdMainView *self)
+{
+ GdMainViewPrivate *priv;
+
+ priv = gd_main_view_get_instance_private (self);
+ return priv->current_view;
+}
+
+static gboolean
+build_selection_list_foreach (GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer user_data)
+{
+ GList **sel = user_data;
+ gboolean is_selected;
+
+ gtk_tree_model_get (model, iter,
+ GD_MAIN_COLUMN_SELECTED, &is_selected,
+ -1);
+
+ if (is_selected)
+ *sel = g_list_prepend (*sel, gtk_tree_path_copy (path));
+
+ return FALSE;
+}
+
+/**
+ * gd_main_view_get_selection:
+ * @self:
+ *
+ * Returns: (element-type GtkTreePath) (transfer full):
+ */
+GList *
+gd_main_view_get_selection (GdMainView *self)
+{
+ GdMainViewPrivate *priv;
+ GList *retval = NULL;
+
+ priv = gd_main_view_get_instance_private (self);
+
+ gtk_tree_model_foreach (priv->model,
+ build_selection_list_foreach,
+ &retval);
+
+ return g_list_reverse (retval);
+}
+
+void
+gd_main_view_select_all (GdMainView *self)
+{
+ GdMainViewGeneric *generic = get_generic (self);
+
+ gd_main_view_generic_select_all (generic);
+}
+
+void
+gd_main_view_unselect_all (GdMainView *self)
+{
+ GdMainViewGeneric *generic = get_generic (self);
+
+ gd_main_view_generic_unselect_all (generic);
+}
diff --git a/subprojects/libgd/libgd/gd-main-view.h b/subprojects/libgd/libgd/gd-main-view.h
new file mode 100644
index 0000000..4f8afe7
--- /dev/null
+++ b/subprojects/libgd/libgd/gd-main-view.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2011 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Cosimo Cecchi <cosimoc@redhat.com>
+ *
+ */
+
+#ifndef __GD_MAIN_VIEW_H__
+#define __GD_MAIN_VIEW_H__
+
+#include <glib-object.h>
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GD_TYPE_MAIN_VIEW gd_main_view_get_type()
+G_DECLARE_DERIVABLE_TYPE (GdMainView, gd_main_view, GD, MAIN_VIEW, GtkScrolledWindow)
+
+typedef enum {
+ GD_MAIN_VIEW_ICON,
+ GD_MAIN_VIEW_LIST
+} GdMainViewType;
+
+struct _GdMainViewClass {
+ GtkScrolledWindowClass parent_class;
+};
+
+GdMainView * gd_main_view_new (GdMainViewType type);
+void gd_main_view_set_view_type (GdMainView *self,
+ GdMainViewType type);
+GdMainViewType gd_main_view_get_view_type (GdMainView *self);
+
+void gd_main_view_set_selection_mode (GdMainView *self,
+ gboolean selection_mode);
+gboolean gd_main_view_get_selection_mode (GdMainView *self);
+
+GList * gd_main_view_get_selection (GdMainView *self);
+
+void gd_main_view_select_all (GdMainView *self);
+void gd_main_view_unselect_all (GdMainView *self);
+
+GtkTreeModel * gd_main_view_get_model (GdMainView *self);
+void gd_main_view_set_model (GdMainView *self,
+ GtkTreeModel *model);
+
+GtkWidget * gd_main_view_get_generic_view (GdMainView *self);
+
+G_END_DECLS
+
+#endif /* __GD_MAIN_VIEW_H__ */
diff --git a/subprojects/libgd/libgd/gd-margin-container.c b/subprojects/libgd/libgd/gd-margin-container.c
new file mode 100644
index 0000000..51bbdd1
--- /dev/null
+++ b/subprojects/libgd/libgd/gd-margin-container.c
@@ -0,0 +1,379 @@
+/*
+ * Copyright (c) 2011 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Cosimo Cecchi <cosimoc@redhat.com>
+ *
+ */
+
+#include "config.h"
+
+#include "gd-margin-container.h"
+
+G_DEFINE_TYPE_WITH_CODE (GdMarginContainer, gd_margin_container, GTK_TYPE_BIN,
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE,
+ NULL))
+
+struct _GdMarginContainerPrivate {
+ gint min_margin;
+ gint max_margin;
+
+ GtkOrientation orientation;
+};
+
+enum {
+ PROP_MIN_MARGIN = 1,
+ PROP_MAX_MARGIN,
+ PROP_ORIENTATION,
+ NUM_PROPERTIES
+};
+
+static void
+gd_margin_container_queue_redraw (GdMarginContainer *self)
+{
+ GtkWidget *child;
+
+ /* Make sure that the widget and children are redrawn with the new setting: */
+ child = gtk_bin_get_child (GTK_BIN (self));
+ if (child)
+ gtk_widget_queue_resize (child);
+
+ gtk_widget_queue_draw (GTK_WIDGET (self));
+}
+
+static void
+gd_margin_container_set_orientation (GdMarginContainer *self,
+ GtkOrientation orientation)
+{
+ if (self->priv->orientation != orientation)
+ {
+ self->priv->orientation = orientation;
+ g_object_notify (G_OBJECT (self), "orientation");
+
+ gd_margin_container_queue_redraw (self);
+ }
+}
+
+static void
+gd_margin_container_set_min_margin (GdMarginContainer *self,
+ gint min_margin)
+{
+ if (self->priv->min_margin != min_margin)
+ {
+ self->priv->min_margin = min_margin;
+ g_object_notify (G_OBJECT (self), "min-margin");
+
+ gd_margin_container_queue_redraw (self);
+ }
+}
+
+static void
+gd_margin_container_set_max_margin (GdMarginContainer *self,
+ gint max_margin)
+{
+ if (self->priv->max_margin != max_margin)
+ {
+ self->priv->max_margin = max_margin;
+ g_object_notify (G_OBJECT (self), "max-margin");
+
+ gd_margin_container_queue_redraw (self);
+ }
+}
+
+static void
+gd_margin_container_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GdMarginContainer *self = GD_MARGIN_CONTAINER (object);
+
+ switch (property_id)
+ {
+ case PROP_MIN_MARGIN:
+ gd_margin_container_set_min_margin (self, g_value_get_int (value));
+ break;
+ case PROP_MAX_MARGIN:
+ gd_margin_container_set_max_margin (self, g_value_get_int (value));
+ break;
+ case PROP_ORIENTATION:
+ gd_margin_container_set_orientation (self, g_value_get_enum (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gd_margin_container_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GdMarginContainer *self = GD_MARGIN_CONTAINER (object);
+
+ switch (property_id)
+ {
+ case PROP_MIN_MARGIN:
+ g_value_set_int (value, self->priv->min_margin);
+ break;
+ case PROP_MAX_MARGIN:
+ g_value_set_int (value, self->priv->max_margin);
+ break;
+ case PROP_ORIENTATION:
+ g_value_set_enum (value, self->priv->orientation);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gd_margin_container_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ GdMarginContainer *self = GD_MARGIN_CONTAINER (widget);
+ GtkWidget *child;
+ GtkAllocation child_allocation;
+ gint avail_width, avail_height;
+
+ child = gtk_bin_get_child (GTK_BIN (widget));
+ gtk_widget_set_allocation (widget, allocation);
+
+ if (child && gtk_widget_get_visible (child))
+ {
+ gint child_nat_width;
+ gint child_nat_height;
+ gint child_width, child_height;
+ gint offset;
+
+ /* available */
+ avail_width = allocation->width;
+ avail_height = allocation->height;
+
+ if (self->priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+ avail_width = MAX (1, avail_width - 2 * self->priv->min_margin);
+ else
+ avail_height = MAX (1, avail_height - 2 * self->priv->min_margin);
+
+ if (gtk_widget_get_request_mode (child) == GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH)
+ {
+ gtk_widget_get_preferred_width (child, NULL, &child_nat_width);
+ child_width = MIN (avail_width, child_nat_width);
+
+ gtk_widget_get_preferred_height_for_width (child, child_width, NULL, &child_nat_height);
+ child_height = MIN (avail_height, child_nat_height);
+
+ offset = MIN ((gint) ((avail_height - child_height) / 2), self->priv->max_margin);
+
+ if (offset > 0)
+ child_allocation.height = avail_height - (offset * 2);
+ else
+ child_allocation.height = avail_height;
+
+ child_allocation.width = MIN (avail_width, child_nat_width);
+ }
+ else
+ {
+ gtk_widget_get_preferred_height (child, NULL, &child_nat_height);
+ child_height = MIN (avail_height, child_nat_height);
+
+ gtk_widget_get_preferred_width_for_height (child, child_height, NULL, &child_nat_width);
+ child_width = MIN (avail_width, child_nat_width);
+
+ offset = MIN ((gint) ((avail_width - child_width) / 2), self->priv->max_margin);
+
+ if (offset > 0)
+ child_allocation.width = avail_width - (offset * 2);
+ else
+ child_allocation.width = avail_width;
+
+ child_allocation.height = MIN (avail_height, child_nat_height);
+ }
+
+ child_allocation.x = offset + allocation->x;
+ child_allocation.y = (avail_height - child_allocation.height) + allocation->y;
+
+ if (self->priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+ child_allocation.x += self->priv->min_margin;
+ else
+ child_allocation.y += self->priv->min_margin;
+
+ gtk_widget_size_allocate (child, &child_allocation);
+ }
+}
+
+static void
+gd_margin_container_get_preferred_size (GtkWidget *widget,
+ GtkOrientation orientation,
+ gint for_size,
+ gint *minimum_size,
+ gint *natural_size)
+{
+ GdMarginContainer *self = GD_MARGIN_CONTAINER (widget);
+ guint natural, minimum;
+ GtkWidget *child;
+
+ if (orientation == self->priv->orientation)
+ {
+ minimum = self->priv->min_margin * 2;
+ natural = self->priv->max_margin * 2;
+ }
+ else
+ {
+ minimum = 0;
+ natural = 0;
+ }
+
+ if ((child = gtk_bin_get_child (GTK_BIN (widget))) && gtk_widget_get_visible (child))
+ {
+ gint child_min, child_nat;
+
+ if (orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ if (for_size < 0)
+ gtk_widget_get_preferred_width (child, &child_min, &child_nat);
+ else
+ {
+ gint min_height;
+
+ gtk_widget_get_preferred_height (child, &min_height, NULL);
+ for_size -= 2 * self->priv->min_margin;
+
+ gtk_widget_get_preferred_width_for_height (child, for_size, &child_min, &child_nat);
+ }
+ }
+ else
+ {
+ if (for_size < 0)
+ gtk_widget_get_preferred_height (child, &child_min, &child_nat);
+ else
+ {
+ gint min_width;
+
+ gtk_widget_get_preferred_width (child, &min_width, NULL);
+ for_size -= 2 * self->priv->min_margin;
+
+ gtk_widget_get_preferred_height_for_width (child, for_size, &child_min, &child_nat);
+ }
+ }
+
+ natural += child_nat;
+
+ if (orientation != self->priv->orientation)
+ minimum += child_min;
+ }
+
+ if (minimum_size != NULL)
+ *minimum_size = minimum;
+ if (natural_size != NULL)
+ *natural_size = natural;
+}
+
+static void
+gd_margin_container_get_preferred_width (GtkWidget *widget,
+ gint *minimum_size,
+ gint *natural_size)
+{
+ gd_margin_container_get_preferred_size (widget, GTK_ORIENTATION_HORIZONTAL,
+ -1, minimum_size, natural_size);
+}
+
+static void
+gd_margin_container_get_preferred_height (GtkWidget *widget,
+ gint *minimum_size,
+ gint *natural_size)
+{
+ gd_margin_container_get_preferred_size (widget, GTK_ORIENTATION_VERTICAL,
+ -1, minimum_size, natural_size);
+}
+
+static void
+gd_margin_container_get_preferred_width_for_height (GtkWidget *widget,
+ gint for_size,
+ gint *minimum_size,
+ gint *natural_size)
+{
+ gd_margin_container_get_preferred_size (widget, GTK_ORIENTATION_HORIZONTAL,
+ for_size, minimum_size, natural_size);
+}
+
+static void
+gd_margin_container_get_preferred_height_for_width (GtkWidget *widget,
+ gint for_size,
+ gint *minimum_size,
+ gint *natural_size)
+{
+ gd_margin_container_get_preferred_size (widget, GTK_ORIENTATION_VERTICAL,
+ for_size, minimum_size, natural_size);
+}
+
+static void
+gd_margin_container_init (GdMarginContainer *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GD_TYPE_MARGIN_CONTAINER,
+ GdMarginContainerPrivate);
+
+ self->priv->orientation = GTK_ORIENTATION_HORIZONTAL;
+
+ gtk_widget_set_has_window (GTK_WIDGET (self), FALSE);
+ gtk_widget_set_redraw_on_allocate (GTK_WIDGET (self), FALSE);
+}
+
+static void
+gd_margin_container_class_init (GdMarginContainerClass *klass)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *wclass = GTK_WIDGET_CLASS (klass);
+
+ oclass->get_property = gd_margin_container_get_property;
+ oclass->set_property = gd_margin_container_set_property;
+
+ wclass->size_allocate = gd_margin_container_size_allocate;
+ wclass->get_preferred_width = gd_margin_container_get_preferred_width;
+ wclass->get_preferred_height = gd_margin_container_get_preferred_height;
+ wclass->get_preferred_width_for_height = gd_margin_container_get_preferred_width_for_height;
+ wclass->get_preferred_height_for_width = gd_margin_container_get_preferred_height_for_width;
+
+ gtk_container_class_handle_border_width (GTK_CONTAINER_CLASS (klass));
+
+ g_object_class_install_property (oclass, PROP_MIN_MARGIN,
+ g_param_spec_int ("min-margin",
+ "Min margin",
+ "Minimum margin around the child",
+ 0, G_MAXINT, 6,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+ g_object_class_install_property (oclass, PROP_MAX_MARGIN,
+ g_param_spec_int ("max-margin",
+ "Max margin",
+ "Maximum margin around the child",
+ 0, G_MAXINT, 6,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+ g_object_class_override_property (oclass, PROP_ORIENTATION,
+ "orientation");
+;
+ g_type_class_add_private (klass, sizeof (GdMarginContainerPrivate));
+}
+
+GdMarginContainer *
+gd_margin_container_new (void)
+{
+ return g_object_new (GD_TYPE_MARGIN_CONTAINER, NULL);
+}
diff --git a/subprojects/libgd/libgd/gd-margin-container.h b/subprojects/libgd/libgd/gd-margin-container.h
new file mode 100644
index 0000000..3937ea7
--- /dev/null
+++ b/subprojects/libgd/libgd/gd-margin-container.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2011 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Cosimo Cecchi <cosimoc@redhat.com>
+ *
+ */
+
+#ifndef _GD_MARGIN_CONTAINER_H
+#define _GD_MARGIN_CONTAINER_H
+
+#include <glib-object.h>
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GD_TYPE_MARGIN_CONTAINER gd_margin_container_get_type()
+
+#define GD_MARGIN_CONTAINER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ GD_TYPE_MARGIN_CONTAINER, GdMarginContainer))
+
+#define GD_MARGIN_CONTAINER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ GD_TYPE_MARGIN_CONTAINER, GdMarginContainerClass))
+
+#define GD_IS_MARGIN_CONTAINER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ GD_TYPE_MARGIN_CONTAINER))
+
+#define GD_IS_MARGIN_CONTAINER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+ GD_TYPE_MARGIN_CONTAINER))
+
+#define GD_MARGIN_CONTAINER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ GD_TYPE_MARGIN_CONTAINER, GdMarginContainerClass))
+
+typedef struct _GdMarginContainer GdMarginContainer;
+typedef struct _GdMarginContainerClass GdMarginContainerClass;
+typedef struct _GdMarginContainerPrivate GdMarginContainerPrivate;
+
+struct _GdMarginContainer
+{
+ GtkBin parent;
+
+ GdMarginContainerPrivate *priv;
+};
+
+struct _GdMarginContainerClass
+{
+ GtkBinClass parent_class;
+};
+
+GType gd_margin_container_get_type (void) G_GNUC_CONST;
+
+GdMarginContainer *gd_margin_container_new (void);
+
+G_END_DECLS
+
+#endif /* _GD_MARGIN_CONTAINER_H */
diff --git a/subprojects/libgd/libgd/gd-notification.c b/subprojects/libgd/libgd/gd-notification.c
new file mode 100644
index 0000000..8153436
--- /dev/null
+++ b/subprojects/libgd/libgd/gd-notification.c
@@ -0,0 +1,875 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * gd-notification
+ * Based on gtk-notification from gnome-contacts:
+ * http://git.gnome.org/browse/gnome-contacts/tree/src/gtk-notification.c?id=3.3.91
+ *
+ * Copyright (C) Erick PĂ©rez Castellanos 2011 <erick.red@gmail.com>
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.";
+ */
+
+#include "gd-notification.h"
+
+/**
+ * SECTION:gdnotification
+ * @short_description: Report notification messages to the user
+ * @include: gtk/gtk.h
+ * @see_also: #GtkStatusbar, #GtkMessageDialog, #GtkInfoBar
+ *
+ * #GdNotification is a widget made for showing notifications to
+ * the user, allowing them to close the notification or wait for it
+ * to time out.
+ *
+ * #GdNotification provides one signal (#GdNotification::dismissed), for when the notification
+ * times out or is closed.
+ *
+ */
+
+#define GTK_PARAM_READWRITE G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB
+#define SHADOW_OFFSET_X 2
+#define SHADOW_OFFSET_Y 3
+#define ANIMATION_TIME 200 /* msec */
+#define ANIMATION_STEP 40 /* msec */
+
+enum {
+ PROP_0,
+ PROP_TIMEOUT,
+ PROP_SHOW_CLOSE_BUTTON
+};
+
+struct _GdNotificationPrivate {
+ GtkWidget *close_button;
+ gboolean show_close_button;
+
+ GdkWindow *bin_window;
+
+ int animate_y; /* from 0 to allocation.height */
+ gboolean waiting_for_viewable;
+ gboolean revealed;
+ gboolean dismissed;
+ gboolean sent_dismissed;
+ guint animate_timeout;
+
+ gint timeout;
+ guint timeout_source_id;
+};
+
+enum {
+ DISMISSED,
+ LAST_SIGNAL
+};
+
+static guint notification_signals[LAST_SIGNAL] = { 0 };
+
+static gboolean gd_notification_draw (GtkWidget *widget,
+ cairo_t *cr);
+static void gd_notification_get_preferred_width (GtkWidget *widget,
+ gint *minimum_size,
+ gint *natural_size);
+static void gd_notification_get_preferred_height_for_width (GtkWidget *widget,
+ gint width,
+ gint *minimum_height,
+ gint *natural_height);
+static void gd_notification_get_preferred_height (GtkWidget *widget,
+ gint *minimum_size,
+ gint *natural_size);
+static void gd_notification_get_preferred_width_for_height (GtkWidget *widget,
+ gint height,
+ gint *minimum_width,
+ gint *natural_width);
+static void gd_notification_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation);
+static gboolean gd_notification_timeout_cb (gpointer user_data);
+static void gd_notification_show (GtkWidget *widget);
+static void gd_notification_add (GtkContainer *container,
+ GtkWidget *child);
+
+/* signals handlers */
+static void gd_notification_close_button_clicked_cb (GtkWidget *widget,
+ gpointer user_data);
+
+G_DEFINE_TYPE(GdNotification, gd_notification, GTK_TYPE_BIN);
+
+static void
+gd_notification_init (GdNotification *notification)
+{
+ GtkWidget *close_button_image;
+ GtkStyleContext *context;
+ GdNotificationPrivate *priv;
+
+ context = gtk_widget_get_style_context (GTK_WIDGET (notification));
+ gtk_style_context_add_class (context, GTK_STYLE_CLASS_FRAME);
+ gtk_style_context_add_class (context, "app-notification");
+
+ gtk_widget_set_halign (GTK_WIDGET (notification), GTK_ALIGN_CENTER);
+ gtk_widget_set_valign (GTK_WIDGET (notification), GTK_ALIGN_START);
+
+ gtk_widget_set_has_window (GTK_WIDGET (notification), TRUE);
+
+ priv = notification->priv =
+ G_TYPE_INSTANCE_GET_PRIVATE (notification,
+ GD_TYPE_NOTIFICATION,
+ GdNotificationPrivate);
+
+ priv->animate_y = 0;
+ priv->close_button = gtk_button_new ();
+ gtk_widget_set_parent (priv->close_button, GTK_WIDGET (notification));
+ gtk_widget_show (priv->close_button);
+ g_object_set (priv->close_button,
+ "relief", GTK_RELIEF_NONE,
+ "focus-on-click", FALSE,
+ NULL);
+ g_signal_connect (priv->close_button,
+ "clicked",
+ G_CALLBACK (gd_notification_close_button_clicked_cb),
+ notification);
+ close_button_image = gtk_image_new_from_icon_name ("window-close-symbolic", GTK_ICON_SIZE_BUTTON);
+ gtk_button_set_image (GTK_BUTTON (notification->priv->close_button), close_button_image);
+
+ priv->timeout_source_id = 0;
+}
+
+static void
+gd_notification_finalize (GObject *object)
+{
+ GdNotification *notification;
+ GdNotificationPrivate *priv;
+
+ g_return_if_fail (GTK_IS_NOTIFICATION (object));
+
+ notification = GD_NOTIFICATION (object);
+ priv = notification->priv;
+
+ if (priv->animate_timeout != 0)
+ g_source_remove (priv->animate_timeout);
+
+ if (priv->timeout_source_id != 0)
+ g_source_remove (priv->timeout_source_id);
+
+ G_OBJECT_CLASS (gd_notification_parent_class)->finalize (object);
+}
+
+static void
+gd_notification_destroy (GtkWidget *widget)
+{
+ GdNotification *notification = GD_NOTIFICATION (widget);
+ GdNotificationPrivate *priv = notification->priv;
+
+ if (!priv->sent_dismissed)
+ {
+ g_signal_emit (notification, notification_signals[DISMISSED], 0);
+ priv->sent_dismissed = TRUE;
+ }
+
+ if (priv->close_button)
+ {
+ gtk_widget_unparent (priv->close_button);
+ priv->close_button = NULL;
+ }
+
+ GTK_WIDGET_CLASS (gd_notification_parent_class)->destroy (widget);
+}
+
+static void
+gd_notification_realize (GtkWidget *widget)
+{
+ GdNotification *notification = GD_NOTIFICATION (widget);
+ GdNotificationPrivate *priv = notification->priv;
+ GtkBin *bin = GTK_BIN (widget);
+ GtkAllocation allocation;
+ GtkWidget *child;
+ GdkWindow *window;
+ GdkWindowAttr attributes;
+ gint attributes_mask;
+
+ gtk_widget_set_realized (widget, TRUE);
+
+ gtk_widget_get_allocation (widget, &allocation);
+
+ attributes.x = allocation.x;
+ attributes.y = allocation.y;
+ attributes.width = allocation.width;
+ attributes.height = allocation.height;
+ attributes.window_type = GDK_WINDOW_CHILD;
+ attributes.wclass = GDK_INPUT_OUTPUT;
+ attributes.visual = gtk_widget_get_visual (widget);
+
+ attributes.event_mask = GDK_VISIBILITY_NOTIFY_MASK | GDK_EXPOSURE_MASK;
+
+ attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
+
+ window = gdk_window_new (gtk_widget_get_parent_window (widget),
+ &attributes, attributes_mask);
+ gtk_widget_set_window (widget, window);
+ gtk_widget_register_window (widget, window);
+
+ attributes.x = 0;
+ attributes.y = attributes.height + priv->animate_y;
+ attributes.event_mask = gtk_widget_get_events (widget) |
+ GDK_EXPOSURE_MASK |
+ GDK_VISIBILITY_NOTIFY_MASK |
+ GDK_ENTER_NOTIFY_MASK |
+ GDK_LEAVE_NOTIFY_MASK;
+
+ priv->bin_window = gdk_window_new (window, &attributes, attributes_mask);
+ gtk_widget_register_window (widget, priv->bin_window);
+
+ child = gtk_bin_get_child (bin);
+ if (child)
+ gtk_widget_set_parent_window (child, priv->bin_window);
+ gtk_widget_set_parent_window (priv->close_button, priv->bin_window);
+
+ gdk_window_show (priv->bin_window);
+}
+
+static void
+gd_notification_unrealize (GtkWidget *widget)
+{
+ GdNotification *notification = GD_NOTIFICATION (widget);
+ GdNotificationPrivate *priv = notification->priv;
+
+ gtk_widget_unregister_window (widget, priv->bin_window);
+ gdk_window_destroy (priv->bin_window);
+ priv->bin_window = NULL;
+
+ GTK_WIDGET_CLASS (gd_notification_parent_class)->unrealize (widget);
+}
+
+static int
+animation_target (GdNotification *notification)
+{
+ GdNotificationPrivate *priv = notification->priv;
+ GtkAllocation allocation;
+
+ if (priv->revealed) {
+ gtk_widget_get_allocation (GTK_WIDGET (notification), &allocation);
+ return allocation.height;
+ } else {
+ return 0;
+ }
+}
+
+static gboolean
+animation_timeout_cb (gpointer user_data)
+{
+ GdNotification *notification = GD_NOTIFICATION (user_data);
+ GdNotificationPrivate *priv = notification->priv;
+ GtkAllocation allocation;
+ int target, delta;
+
+ target = animation_target (notification);
+
+ if (priv->animate_y != target) {
+ gtk_widget_get_allocation (GTK_WIDGET (notification), &allocation);
+
+ delta = allocation.height * ANIMATION_STEP / ANIMATION_TIME;
+
+ if (priv->revealed)
+ priv->animate_y += delta;
+ else
+ priv->animate_y -= delta;
+
+ priv->animate_y = CLAMP (priv->animate_y, 0, allocation.height);
+
+ if (priv->bin_window != NULL)
+ gdk_window_move (priv->bin_window,
+ 0,
+ -allocation.height + priv->animate_y);
+ return G_SOURCE_CONTINUE;
+ }
+
+ if (priv->dismissed && priv->animate_y == 0)
+ gtk_widget_destroy (GTK_WIDGET (notification));
+
+ priv->animate_timeout = 0;
+ return G_SOURCE_REMOVE;
+}
+
+static void
+start_animation (GdNotification *notification)
+{
+ GdNotificationPrivate *priv = notification->priv;
+ int target;
+
+ if (priv->animate_timeout != 0)
+ return; /* Already running */
+
+ target = animation_target (notification);
+ if (priv->animate_y != target)
+ notification->priv->animate_timeout =
+ gdk_threads_add_timeout (ANIMATION_STEP,
+ animation_timeout_cb,
+ notification);
+}
+
+static void
+gd_notification_show (GtkWidget *widget)
+{
+ GdNotification *notification = GD_NOTIFICATION (widget);
+ GdNotificationPrivate *priv = notification->priv;
+
+ GTK_WIDGET_CLASS (gd_notification_parent_class)->show (widget);
+ priv->revealed = TRUE;
+ priv->waiting_for_viewable = TRUE;
+}
+
+static void
+gd_notification_hide (GtkWidget *widget)
+{
+ GdNotification *notification = GD_NOTIFICATION (widget);
+ GdNotificationPrivate *priv = notification->priv;
+
+ GTK_WIDGET_CLASS (gd_notification_parent_class)->hide (widget);
+ priv->revealed = FALSE;
+ priv->waiting_for_viewable = FALSE;
+}
+
+static void
+gd_notification_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+ GdNotification *notification = GD_NOTIFICATION (object);
+
+ g_return_if_fail (GTK_IS_NOTIFICATION (object));
+
+ switch (prop_id) {
+ case PROP_TIMEOUT:
+ gd_notification_set_timeout (notification,
+ g_value_get_int (value));
+ break;
+ case PROP_SHOW_CLOSE_BUTTON:
+ gd_notification_set_show_close_button (notification,
+ g_value_get_boolean (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gd_notification_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+ g_return_if_fail (GTK_IS_NOTIFICATION (object));
+ GdNotification *notification = GD_NOTIFICATION (object);
+
+ switch (prop_id) {
+ case PROP_TIMEOUT:
+ g_value_set_int (value, notification->priv->timeout);
+ break;
+ case PROP_SHOW_CLOSE_BUTTON:
+ g_value_set_boolean (value,
+ notification->priv->show_close_button);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gd_notification_forall (GtkContainer *container,
+ gboolean include_internals,
+ GtkCallback callback,
+ gpointer callback_data)
+{
+ GtkBin *bin = GTK_BIN (container);
+ GdNotification *notification = GD_NOTIFICATION (container);
+ GdNotificationPrivate *priv = notification->priv;
+ GtkWidget *child;
+
+ child = gtk_bin_get_child (bin);
+ if (child)
+ (* callback) (child, callback_data);
+
+ if (include_internals)
+ (* callback) (priv->close_button, callback_data);
+}
+
+static void
+unqueue_autohide (GdNotification *notification)
+{
+ GdNotificationPrivate *priv = notification->priv;
+
+ if (priv->timeout_source_id)
+ {
+ g_source_remove (priv->timeout_source_id);
+ priv->timeout_source_id = 0;
+ }
+}
+
+static void
+queue_autohide (GdNotification *notification)
+{
+ GdNotificationPrivate *priv = notification->priv;
+
+ if (priv->timeout_source_id == 0 &&
+ priv->timeout != -1)
+ priv->timeout_source_id =
+ gdk_threads_add_timeout (priv->timeout * 1000,
+ gd_notification_timeout_cb,
+ notification);
+}
+
+static gboolean
+gd_notification_visibility_notify_event (GtkWidget *widget,
+ GdkEventVisibility *event)
+{
+ GdNotification *notification = GD_NOTIFICATION (widget);
+ GdNotificationPrivate *priv = notification->priv;
+
+ if (!gtk_widget_get_visible (widget))
+ return FALSE;
+
+ if (priv->waiting_for_viewable)
+ {
+ start_animation (notification);
+ priv->waiting_for_viewable = FALSE;
+ }
+
+ queue_autohide (notification);
+
+ return FALSE;
+}
+
+static gboolean
+gd_notification_enter_notify (GtkWidget *widget,
+ GdkEventCrossing *event)
+{
+ GdNotification *notification = GD_NOTIFICATION (widget);
+ GdNotificationPrivate *priv = notification->priv;
+
+ if ((event->window == priv->bin_window) &&
+ (event->detail != GDK_NOTIFY_INFERIOR))
+ {
+ unqueue_autohide (notification);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+gd_notification_leave_notify (GtkWidget *widget,
+ GdkEventCrossing *event)
+{
+ GdNotification *notification = GD_NOTIFICATION (widget);
+ GdNotificationPrivate *priv = notification->priv;
+
+ if ((event->window == priv->bin_window) &&
+ (event->detail != GDK_NOTIFY_INFERIOR))
+ {
+ queue_autohide (notification);
+ }
+
+ return FALSE;
+}
+
+static void
+gd_notification_class_init (GdNotificationClass *klass)
+{
+ GObjectClass* object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass);
+ GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
+
+ object_class->finalize = gd_notification_finalize;
+ object_class->set_property = gd_notification_set_property;
+ object_class->get_property = gd_notification_get_property;
+
+ widget_class->show = gd_notification_show;
+ widget_class->hide = gd_notification_hide;
+ widget_class->destroy = gd_notification_destroy;
+ widget_class->get_preferred_width = gd_notification_get_preferred_width;
+ widget_class->get_preferred_height_for_width = gd_notification_get_preferred_height_for_width;
+ widget_class->get_preferred_height = gd_notification_get_preferred_height;
+ widget_class->get_preferred_width_for_height = gd_notification_get_preferred_width_for_height;
+ widget_class->size_allocate = gd_notification_size_allocate;
+ widget_class->draw = gd_notification_draw;
+ widget_class->realize = gd_notification_realize;
+ widget_class->unrealize = gd_notification_unrealize;
+ widget_class->visibility_notify_event = gd_notification_visibility_notify_event;
+ widget_class->enter_notify_event = gd_notification_enter_notify;
+ widget_class->leave_notify_event = gd_notification_leave_notify;
+
+ container_class->add = gd_notification_add;
+ container_class->forall = gd_notification_forall;
+ gtk_container_class_handle_border_width (container_class);
+
+
+ /**
+ * GdNotification:timeout:
+ *
+ * The time it takes to hide the widget, in seconds.
+ *
+ * Since: 0.1
+ */
+ g_object_class_install_property (object_class,
+ PROP_TIMEOUT,
+ g_param_spec_int("timeout", "timeout",
+ "The time it takes to hide the widget, in seconds",
+ -1, G_MAXINT, -1,
+ GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+ g_object_class_install_property (object_class,
+ PROP_SHOW_CLOSE_BUTTON,
+ g_param_spec_boolean("show-close-button", "show-close-button",
+ "Whether to show a stock close button that dismisses the notification",
+ TRUE,
+ GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+ notification_signals[DISMISSED] = g_signal_new ("dismissed",
+ G_OBJECT_CLASS_TYPE (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GdNotificationClass, dismissed),
+ NULL,
+ NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ g_type_class_add_private (object_class, sizeof (GdNotificationPrivate));
+}
+
+static void
+get_padding_and_border (GdNotification *notification,
+ GtkBorder *border)
+{
+ GtkStyleContext *context;
+ GtkStateFlags state;
+ GtkBorder tmp;
+
+ context = gtk_widget_get_style_context (GTK_WIDGET (notification));
+ state = gtk_widget_get_state_flags (GTK_WIDGET (notification));
+
+ gtk_style_context_get_padding (context, state, border);
+
+ gtk_style_context_get_border (context, state, &tmp);
+ border->top += tmp.top;
+ border->right += tmp.right;
+ border->bottom += tmp.bottom;
+ border->left += tmp.left;
+}
+
+static gboolean
+gd_notification_draw (GtkWidget *widget, cairo_t *cr)
+{
+ GdNotification *notification = GD_NOTIFICATION (widget);
+ GdNotificationPrivate *priv = notification->priv;
+ GtkStyleContext *context;
+
+ if (gtk_cairo_should_draw_window (cr, priv->bin_window))
+ {
+ context = gtk_widget_get_style_context (widget);
+
+ gtk_render_background (context, cr,
+ 0, 0,
+ gtk_widget_get_allocated_width (widget),
+ gtk_widget_get_allocated_height (widget));
+ gtk_render_frame (context,cr,
+ 0, 0,
+ gtk_widget_get_allocated_width (widget),
+ gtk_widget_get_allocated_height (widget));
+
+
+ if (GTK_WIDGET_CLASS (gd_notification_parent_class)->draw)
+ GTK_WIDGET_CLASS (gd_notification_parent_class)->draw(widget, cr);
+ }
+
+ return FALSE;
+}
+
+static void
+gd_notification_add (GtkContainer *container,
+ GtkWidget *child)
+{
+ GtkBin *bin = GTK_BIN (container);
+ GdNotification *notification = GD_NOTIFICATION (bin);
+ GdNotificationPrivate *priv = notification->priv;
+
+ g_return_if_fail (gtk_bin_get_child (bin) == NULL);
+
+ gtk_widget_set_parent_window (child, priv->bin_window);
+
+ GTK_CONTAINER_CLASS (gd_notification_parent_class)->add (container, child);
+}
+
+
+static void
+gd_notification_get_preferred_width (GtkWidget *widget, gint *minimum_size, gint *natural_size)
+{
+ GdNotification *notification = GD_NOTIFICATION (widget);
+ GdNotificationPrivate *priv = notification->priv;
+ GtkBin *bin = GTK_BIN (widget);
+ gint child_min, child_nat;
+ GtkWidget *child;
+ GtkBorder padding;
+ gint minimum, natural;
+
+ get_padding_and_border (notification, &padding);
+
+ minimum = 0;
+ natural = 0;
+
+ child = gtk_bin_get_child (bin);
+ if (child && gtk_widget_get_visible (child))
+ {
+ gtk_widget_get_preferred_width (child,
+ &child_min, &child_nat);
+ minimum += child_min;
+ natural += child_nat;
+ }
+
+ if (priv->show_close_button)
+ {
+ gtk_widget_get_preferred_width (priv->close_button,
+ &child_min, &child_nat);
+ minimum += child_min;
+ natural += child_nat;
+ }
+
+ minimum += padding.left + padding.right + 2 * SHADOW_OFFSET_X;
+ natural += padding.left + padding.right + 2 * SHADOW_OFFSET_X;
+
+ if (minimum_size)
+ *minimum_size = minimum;
+
+ if (natural_size)
+ *natural_size = natural;
+}
+
+static void
+gd_notification_get_preferred_width_for_height (GtkWidget *widget,
+ gint height,
+ gint *minimum_width,
+ gint *natural_width)
+{
+ GdNotification *notification = GD_NOTIFICATION (widget);
+ GdNotificationPrivate *priv = notification->priv;
+ GtkBin *bin = GTK_BIN (widget);
+ gint child_min, child_nat, child_height;
+ GtkWidget *child;
+ GtkBorder padding;
+ gint minimum, natural;
+
+ get_padding_and_border (notification, &padding);
+
+ minimum = 0;
+ natural = 0;
+
+ child_height = height - SHADOW_OFFSET_Y - padding.top - padding.bottom;
+
+ child = gtk_bin_get_child (bin);
+ if (child && gtk_widget_get_visible (child))
+ {
+ gtk_widget_get_preferred_width_for_height (child, child_height,
+ &child_min, &child_nat);
+ minimum += child_min;
+ natural += child_nat;
+ }
+
+ if (priv->show_close_button)
+ {
+ gtk_widget_get_preferred_width_for_height (priv->close_button, child_height,
+ &child_min, &child_nat);
+ minimum += child_min;
+ natural += child_nat;
+ }
+
+ minimum += padding.left + padding.right + 2 * SHADOW_OFFSET_X;
+ natural += padding.left + padding.right + 2 * SHADOW_OFFSET_X;
+
+ if (minimum_width)
+ *minimum_width = minimum;
+
+ if (natural_width)
+ *natural_width = natural;
+}
+
+static void
+gd_notification_get_preferred_height_for_width (GtkWidget *widget,
+ gint width,
+ gint *minimum_height,
+ gint *natural_height)
+{
+ GdNotification *notification = GD_NOTIFICATION (widget);
+ GdNotificationPrivate *priv = notification->priv;
+ GtkBin *bin = GTK_BIN (widget);
+ gint child_min, child_nat, child_width, button_width = 0;
+ GtkWidget *child;
+ GtkBorder padding;
+ gint minimum = 0, natural = 0;
+
+ get_padding_and_border (notification, &padding);
+
+ if (priv->show_close_button)
+ {
+ gtk_widget_get_preferred_height (priv->close_button,
+ &minimum, &natural);
+ gtk_widget_get_preferred_width (priv->close_button,
+ NULL, &button_width);
+ }
+
+ child = gtk_bin_get_child (bin);
+ if (child && gtk_widget_get_visible (child))
+ {
+ child_width = width - button_width -
+ 2 * SHADOW_OFFSET_X - padding.left - padding.right;
+
+ gtk_widget_get_preferred_height_for_width (child, child_width,
+ &child_min, &child_nat);
+ minimum = MAX (minimum, child_min);
+ natural = MAX (natural, child_nat);
+ }
+
+ minimum += padding.top + padding.bottom + SHADOW_OFFSET_Y;
+ natural += padding.top + padding.bottom + SHADOW_OFFSET_Y;
+
+ if (minimum_height)
+ *minimum_height = minimum;
+
+ if (natural_height)
+ *natural_height = natural;
+}
+
+static void
+gd_notification_get_preferred_height (GtkWidget *widget,
+ gint *minimum_height,
+ gint *natural_height)
+{
+ gint width;
+
+ gd_notification_get_preferred_width (widget, &width, NULL);
+ gd_notification_get_preferred_height_for_width (widget, width,
+ minimum_height, natural_height);
+}
+
+static void
+gd_notification_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ GdNotification *notification = GD_NOTIFICATION (widget);
+ GdNotificationPrivate *priv = notification->priv;
+ GtkBin *bin = GTK_BIN (widget);
+ GtkAllocation child_allocation;
+ GtkBorder padding;
+ GtkRequisition button_req;
+ GtkWidget *child;
+
+ gtk_widget_set_allocation (widget, allocation);
+
+ /* If somehow the notification changes while not hidden
+ and we're not animating, immediately follow the resize */
+ if (priv->animate_y > 0 &&
+ !priv->animate_timeout)
+ priv->animate_y = allocation->height;
+
+ get_padding_and_border (notification, &padding);
+
+ if (gtk_widget_get_realized (widget))
+ {
+ gdk_window_move_resize (gtk_widget_get_window (widget),
+ allocation->x,
+ allocation->y,
+ allocation->width,
+ allocation->height);
+ gdk_window_move_resize (priv->bin_window,
+ 0,
+ -allocation->height + priv->animate_y,
+ allocation->width,
+ allocation->height);
+ }
+
+ child_allocation.x = SHADOW_OFFSET_X + padding.left;
+ child_allocation.y = padding.top;
+
+ if (priv->show_close_button)
+ gtk_widget_get_preferred_size (priv->close_button, &button_req, NULL);
+ else
+ button_req.width = button_req.height = 0;
+
+ child_allocation.height = MAX (1, allocation->height - SHADOW_OFFSET_Y - padding.top - padding.bottom);
+ child_allocation.width = MAX (1, (allocation->width - button_req.width -
+ 2 * SHADOW_OFFSET_X - padding.left - padding.right));
+
+ child = gtk_bin_get_child (bin);
+ if (child && gtk_widget_get_visible (child))
+ gtk_widget_size_allocate (child, &child_allocation);
+
+ if (priv->show_close_button)
+ {
+ child_allocation.x += child_allocation.width;
+ child_allocation.width = button_req.width;
+ child_allocation.y += (child_allocation.height - button_req.height) / 2;
+ child_allocation.height = button_req.height;
+
+ gtk_widget_size_allocate (priv->close_button, &child_allocation);
+ }
+}
+
+static gboolean
+gd_notification_timeout_cb (gpointer user_data)
+{
+ GdNotification *notification = GD_NOTIFICATION (user_data);
+
+ gd_notification_dismiss (notification);
+
+ return G_SOURCE_REMOVE;
+}
+
+void
+gd_notification_set_timeout (GdNotification *notification,
+ gint timeout_sec)
+{
+ GdNotificationPrivate *priv = notification->priv;
+
+ priv->timeout = timeout_sec;
+ g_object_notify (G_OBJECT (notification), "timeout");
+}
+
+void
+gd_notification_set_show_close_button (GdNotification *notification,
+ gboolean show_close_button)
+{
+ GdNotificationPrivate *priv = notification->priv;
+
+ priv->show_close_button = show_close_button;
+
+ gtk_widget_set_visible (priv->close_button, show_close_button);
+ gtk_widget_queue_resize (GTK_WIDGET (notification));
+}
+
+void
+gd_notification_dismiss (GdNotification *notification)
+{
+ GdNotificationPrivate *priv = notification->priv;
+
+ unqueue_autohide (notification);
+
+ priv->dismissed = TRUE;
+ priv->revealed = FALSE;
+ start_animation (notification);
+}
+
+static void
+gd_notification_close_button_clicked_cb (GtkWidget *widget, gpointer user_data)
+{
+ GdNotification *notification = GD_NOTIFICATION(user_data);
+
+ gd_notification_dismiss (notification);
+}
+
+GtkWidget *
+gd_notification_new (void)
+{
+ return g_object_new (GD_TYPE_NOTIFICATION, NULL);
+}
diff --git a/subprojects/libgd/libgd/gd-notification.h b/subprojects/libgd/libgd/gd-notification.h
new file mode 100644
index 0000000..8efa191
--- /dev/null
+++ b/subprojects/libgd/libgd/gd-notification.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * gd-notification
+ * Based on gtk-notification from gnome-contacts:
+ * http://git.gnome.org/browse/gnome-contacts/tree/src/gtk-notification.c?id=3.3.91
+ *
+ * Copyright (C) Erick PĂ©rez Castellanos 2011 <erick.red@gmail.com>
+ * Copyright (C) 2012 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.";
+ */
+
+#ifndef _GD_NOTIFICATION_H_
+#define _GD_NOTIFICATION_H_
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GD_TYPE_NOTIFICATION (gd_notification_get_type ())
+#define GD_NOTIFICATION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GD_TYPE_NOTIFICATION, GdNotification))
+#define GD_NOTIFICATION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GD_TYPE_NOTIFICATION, GdNotificationClass))
+#define GTK_IS_NOTIFICATION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GD_TYPE_NOTIFICATION))
+#define GTK_IS_NOTIFICATION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GD_TYPE_NOTIFICATION))
+#define GD_NOTIFICATION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GD_TYPE_NOTIFICATION, GdNotificationClass))
+
+typedef struct _GdNotificationPrivate GdNotificationPrivate;
+typedef struct _GdNotificationClass GdNotificationClass;
+typedef struct _GdNotification GdNotification;
+
+struct _GdNotificationClass {
+ GtkBinClass parent_class;
+
+ /* Signals */
+ void (*dismissed) (GdNotification *self);
+};
+
+struct _GdNotification {
+ GtkBin parent_instance;
+
+ /*< private > */
+ GdNotificationPrivate *priv;
+};
+
+GType gd_notification_get_type (void) G_GNUC_CONST;
+
+GtkWidget *gd_notification_new (void);
+void gd_notification_set_timeout (GdNotification *notification,
+ gint timeout_sec);
+void gd_notification_dismiss (GdNotification *notification);
+void gd_notification_set_show_close_button (GdNotification *notification,
+ gboolean show_close_button);
+
+G_END_DECLS
+
+#endif /* _GD_NOTIFICATION_H_ */
diff --git a/subprojects/libgd/libgd/gd-styled-text-renderer.c b/subprojects/libgd/libgd/gd-styled-text-renderer.c
new file mode 100644
index 0000000..50a315e
--- /dev/null
+++ b/subprojects/libgd/libgd/gd-styled-text-renderer.c
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2011 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Cosimo Cecchi <cosimoc@redhat.com>
+ *
+ */
+
+#include "gd-styled-text-renderer.h"
+
+G_DEFINE_TYPE (GdStyledTextRenderer, gd_styled_text_renderer, GTK_TYPE_CELL_RENDERER_TEXT);
+
+struct _GdStyledTextRendererPrivate {
+ GList *style_classes;
+};
+
+static void
+gd_styled_text_renderer_render (GtkCellRenderer *cell,
+ cairo_t *cr,
+ GtkWidget *widget,
+ const GdkRectangle *background_area,
+ const GdkRectangle *cell_area,
+ GtkCellRendererState flags)
+{
+ GdStyledTextRenderer *self = GD_STYLED_TEXT_RENDERER (cell);
+ GtkStyleContext *context;
+ const gchar *style_class;
+ GList *l;
+
+ context = gtk_widget_get_style_context (widget);
+ gtk_style_context_save (context);
+
+ for (l = self->priv->style_classes; l != NULL; l = l->next)
+ {
+ style_class = l->data;
+ gtk_style_context_add_class (context, style_class);
+ }
+
+ GTK_CELL_RENDERER_CLASS (gd_styled_text_renderer_parent_class)->render
+ (cell, cr, widget,
+ background_area, cell_area, flags);
+
+ gtk_style_context_restore (context);
+}
+
+static void
+gd_styled_text_renderer_finalize (GObject *obj)
+{
+ GdStyledTextRenderer *self = GD_STYLED_TEXT_RENDERER (obj);
+
+ if (self->priv->style_classes != NULL)
+ {
+ g_list_free_full (self->priv->style_classes, g_free);
+ self->priv->style_classes = NULL;
+ }
+
+ G_OBJECT_CLASS (gd_styled_text_renderer_parent_class)->finalize (obj);
+}
+
+static void
+gd_styled_text_renderer_class_init (GdStyledTextRendererClass *klass)
+{
+ GtkCellRendererClass *crclass = GTK_CELL_RENDERER_CLASS (klass);
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+
+ oclass->finalize = gd_styled_text_renderer_finalize;
+ crclass->render = gd_styled_text_renderer_render;
+
+ g_type_class_add_private (klass, sizeof (GdStyledTextRendererPrivate));
+}
+
+static void
+gd_styled_text_renderer_init (GdStyledTextRenderer *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GD_TYPE_STYLED_TEXT_RENDERER,
+ GdStyledTextRendererPrivate);
+}
+
+GtkCellRenderer *
+gd_styled_text_renderer_new (void)
+{
+ return g_object_new (GD_TYPE_STYLED_TEXT_RENDERER,
+ NULL);
+}
+
+void
+gd_styled_text_renderer_add_class (GdStyledTextRenderer *self,
+ const gchar *class)
+{
+ if (g_list_find_custom (self->priv->style_classes, class, (GCompareFunc) g_strcmp0))
+ return;
+
+ self->priv->style_classes = g_list_append (self->priv->style_classes, g_strdup (class));
+}
+
+void
+gd_styled_text_renderer_remove_class (GdStyledTextRenderer *self,
+ const gchar *class)
+{
+ GList *class_element;
+
+ class_element = g_list_find_custom (self->priv->style_classes, class, (GCompareFunc) g_strcmp0);
+
+ if (class_element == NULL)
+ return;
+
+ self->priv->style_classes = g_list_remove_link (self->priv->style_classes,
+ class_element);
+ g_free (class_element->data);
+ g_list_free_1 (class_element);
+}
diff --git a/subprojects/libgd/libgd/gd-styled-text-renderer.h b/subprojects/libgd/libgd/gd-styled-text-renderer.h
new file mode 100644
index 0000000..fc1995b
--- /dev/null
+++ b/subprojects/libgd/libgd/gd-styled-text-renderer.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2011 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Cosimo Cecchi <cosimoc@redhat.com>
+ *
+ */
+
+#ifndef _GD_STYLED_TEXT_RENDERER_H
+#define _GD_STYLED_TEXT_RENDERER_H
+
+#include <glib-object.h>
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GD_TYPE_STYLED_TEXT_RENDERER gd_styled_text_renderer_get_type()
+
+#define GD_STYLED_TEXT_RENDERER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ GD_TYPE_STYLED_TEXT_RENDERER, GdStyledTextRenderer))
+
+#define GD_STYLED_TEXT_RENDERER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ GD_TYPE_STYLED_TEXT_RENDERER, GdStyledTextRendererClass))
+
+#define GD_IS_STYLED_TEXT_RENDERER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ GD_TYPE_STYLED_TEXT_RENDERER))
+
+#define GD_IS_STYLED_TEXT_RENDERER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+ GD_TYPE_STYLED_TEXT_RENDERER))
+
+#define GD_STYLED_TEXT_RENDERER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ GD_TYPE_STYLED_TEXT_RENDERER, GdStyledTextRendererClass))
+
+typedef struct _GdStyledTextRenderer GdStyledTextRenderer;
+typedef struct _GdStyledTextRendererClass GdStyledTextRendererClass;
+typedef struct _GdStyledTextRendererPrivate GdStyledTextRendererPrivate;
+
+struct _GdStyledTextRenderer
+{
+ GtkCellRendererText parent;
+
+ GdStyledTextRendererPrivate *priv;
+};
+
+struct _GdStyledTextRendererClass
+{
+ GtkCellRendererTextClass parent_class;
+};
+
+GType gd_styled_text_renderer_get_type (void) G_GNUC_CONST;
+
+GtkCellRenderer *gd_styled_text_renderer_new (void);
+void gd_styled_text_renderer_add_class (GdStyledTextRenderer *self,
+ const gchar *class);
+void gd_styled_text_renderer_remove_class (GdStyledTextRenderer *self,
+ const gchar *class);
+
+G_END_DECLS
+
+#endif /* _GD_STYLED_TEXT_RENDERER_H */
diff --git a/subprojects/libgd/libgd/gd-tagged-entry.c b/subprojects/libgd/libgd/gd-tagged-entry.c
new file mode 100644
index 0000000..380962c
--- /dev/null
+++ b/subprojects/libgd/libgd/gd-tagged-entry.c
@@ -0,0 +1,1242 @@
+/*
+ * Copyright (c) 2011 Red Hat, Inc.
+ * Copyright (c) 2013 Ignacio Casal Quinteiro
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Cosimo Cecchi <cosimoc@redhat.com>
+ *
+ */
+
+#include "gd-tagged-entry.h"
+
+#include <math.h>
+
+#define BUTTON_INTERNAL_SPACING 6
+
+struct _GdTaggedEntryTagPrivate {
+ GdTaggedEntry *entry;
+ GdkWindow *window;
+ PangoLayout *layout;
+
+ gchar *label;
+ gchar *style;
+ gboolean has_close_button;
+
+ cairo_surface_t *close_surface;
+ GtkStateFlags last_button_state;
+};
+
+struct _GdTaggedEntryPrivate {
+ GList *tags;
+
+ GdTaggedEntryTag *in_child;
+ gboolean in_child_button;
+ gboolean in_child_active;
+ gboolean in_child_button_active;
+ gboolean button_visible;
+};
+
+enum {
+ SIGNAL_TAG_CLICKED,
+ SIGNAL_TAG_BUTTON_CLICKED,
+ LAST_SIGNAL
+};
+
+enum {
+ PROP_0,
+ PROP_TAG_BUTTON_VISIBLE,
+ NUM_PROPERTIES
+};
+
+enum {
+ PROP_TAG_0,
+ PROP_TAG_LABEL,
+ PROP_TAG_HAS_CLOSE_BUTTON,
+ PROP_TAG_STYLE,
+ NUM_TAG_PROPERTIES
+};
+
+G_DEFINE_TYPE (GdTaggedEntry, gd_tagged_entry, GTK_TYPE_SEARCH_ENTRY)
+G_DEFINE_TYPE (GdTaggedEntryTag, gd_tagged_entry_tag, G_TYPE_OBJECT)
+
+static guint signals[LAST_SIGNAL] = { 0, };
+static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
+static GParamSpec *tag_properties[NUM_TAG_PROPERTIES] = { NULL, };
+
+static void gd_tagged_entry_get_text_area_size (GtkEntry *entry,
+ gint *x,
+ gint *y,
+ gint *width,
+ gint *height);
+static gint gd_tagged_entry_tag_get_width (GdTaggedEntryTag *tag,
+ GdTaggedEntry *entry);
+static GtkStyleContext * gd_tagged_entry_tag_get_context (GdTaggedEntryTag *tag,
+ GdTaggedEntry *entry);
+
+static void
+gd_tagged_entry_tag_get_margin (GdTaggedEntryTag *tag,
+ GdTaggedEntry *entry,
+ GtkBorder *margin)
+{
+ GtkStyleContext *context;
+
+ context = gd_tagged_entry_tag_get_context (tag, entry);
+ gtk_style_context_set_state (context, GTK_STATE_FLAG_NORMAL);
+ gtk_style_context_get_margin (context,
+ gtk_style_context_get_state (context),
+ margin);
+ gtk_style_context_restore (context);
+}
+
+static void
+gd_tagged_entry_tag_ensure_close_surface (GdTaggedEntryTag *tag,
+ GtkStyleContext *context)
+{
+ GtkIconInfo *info;
+ GdkPixbuf *pixbuf;
+ gint icon_size;
+ gint scale_factor;
+
+ if (tag->priv->close_surface != NULL)
+ return;
+
+ gtk_icon_size_lookup (GTK_ICON_SIZE_MENU,
+ &icon_size, NULL);
+ scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (tag->priv->entry));
+
+ info = gtk_icon_theme_lookup_icon_for_scale (gtk_icon_theme_get_default (),
+ "window-close-symbolic",
+ icon_size, scale_factor,
+ GTK_ICON_LOOKUP_GENERIC_FALLBACK);
+
+ /* FIXME: we need a fallback icon in case the icon is not found */
+ pixbuf = gtk_icon_info_load_symbolic_for_context (info, context, NULL, NULL);
+ tag->priv->close_surface = gdk_cairo_surface_create_from_pixbuf (pixbuf, scale_factor, tag->priv->window);
+
+ g_object_unref (info);
+ g_object_unref (pixbuf);
+}
+
+static gint
+gd_tagged_entry_tag_panel_get_height (GdTaggedEntryTag *tag,
+ GdTaggedEntry *entry)
+{
+ GtkWidget *widget = GTK_WIDGET (entry);
+ gint height, req_height;
+ GtkRequisition requisition;
+ GtkAllocation allocation;
+ GtkBorder margin;
+
+ gtk_widget_get_allocation (widget, &allocation);
+ gtk_widget_get_preferred_size (widget, &requisition, NULL);
+ gd_tagged_entry_tag_get_margin (tag, entry, &margin);
+
+ /* the tag panel height is the whole entry height, minus the tag margins */
+ req_height = requisition.height - gtk_widget_get_margin_top (widget) - gtk_widget_get_margin_bottom (widget);
+ height = MIN (req_height, allocation.height) - margin.top - margin.bottom;
+
+ return height;
+}
+
+static void
+gd_tagged_entry_tag_panel_get_position (GdTaggedEntry *self,
+ gint *x_out,
+ gint *y_out)
+{
+ GtkWidget *widget = GTK_WIDGET (self);
+ gint text_x, text_y, text_width, text_height, req_height;
+ GtkAllocation allocation;
+ GtkRequisition requisition;
+
+ gtk_widget_get_allocation (widget, &allocation);
+ gtk_widget_get_preferred_size (widget, &requisition, NULL);
+ req_height = requisition.height - gtk_widget_get_margin_top (widget) - gtk_widget_get_margin_bottom (widget);
+
+ gd_tagged_entry_get_text_area_size (GTK_ENTRY (self), &text_x, &text_y, &text_width, &text_height);
+
+ /* allocate the panel immediately after the text area */
+ if (x_out)
+ *x_out = allocation.x + text_x + text_width;
+ if (y_out)
+ *y_out = allocation.y + (gint) floor ((allocation.height - req_height) / 2);
+}
+
+static gint
+gd_tagged_entry_tag_panel_get_width (GdTaggedEntry *self)
+{
+ GdTaggedEntryTag *tag;
+ gint width;
+ GList *l;
+
+ width = 0;
+
+ for (l = self->priv->tags; l != NULL; l = l->next)
+ {
+ tag = l->data;
+ width += gd_tagged_entry_tag_get_width (tag, self);
+ }
+
+ return width;
+}
+
+static void
+gd_tagged_entry_tag_ensure_layout (GdTaggedEntryTag *tag,
+ GdTaggedEntry *entry)
+{
+ if (tag->priv->layout != NULL)
+ return;
+
+ tag->priv->layout = pango_layout_new (gtk_widget_get_pango_context (GTK_WIDGET (entry)));
+ pango_layout_set_text (tag->priv->layout, tag->priv->label, -1);
+}
+
+static GtkStateFlags
+gd_tagged_entry_tag_get_state (GdTaggedEntryTag *tag,
+ GdTaggedEntry *entry)
+{
+ GtkStateFlags state = GTK_STATE_FLAG_NORMAL;
+
+ if (entry->priv->in_child == tag)
+ state |= GTK_STATE_FLAG_PRELIGHT;
+
+ if (entry->priv->in_child_active)
+ state |= GTK_STATE_FLAG_ACTIVE;
+
+ return state;
+}
+
+static GtkStateFlags
+gd_tagged_entry_tag_get_button_state (GdTaggedEntryTag *tag,
+ GdTaggedEntry *entry)
+{
+ GtkStateFlags state = GTK_STATE_FLAG_NORMAL;
+
+ if (entry->priv->in_child == tag)
+ {
+ if (entry->priv->in_child_button_active)
+ state |= GTK_STATE_FLAG_ACTIVE;
+
+ else if (entry->priv->in_child_button)
+ state |= GTK_STATE_FLAG_PRELIGHT;
+ }
+
+ return state;
+}
+
+static GtkStyleContext *
+gd_tagged_entry_tag_get_context (GdTaggedEntryTag *tag,
+ GdTaggedEntry *entry)
+{
+ GtkWidget *widget = GTK_WIDGET (entry);
+ GtkStyleContext *retval;
+ GList *l, *list;
+
+ retval = gtk_widget_get_style_context (widget);
+ gtk_style_context_save (retval);
+
+ list = gtk_style_context_list_classes (retval);
+ for (l = list; l; l = l->next)
+ gtk_style_context_remove_class (retval, l->data);
+ g_list_free (list);
+ gtk_style_context_add_class (retval, tag->priv->style);
+
+ return retval;
+}
+
+static gint
+gd_tagged_entry_tag_get_width (GdTaggedEntryTag *tag,
+ GdTaggedEntry *entry)
+{
+ GtkBorder button_padding, button_border, button_margin;
+ GtkStyleContext *context;
+ GtkStateFlags state;
+ gint layout_width;
+ gint button_width;
+ gint scale_factor;
+
+ gd_tagged_entry_tag_ensure_layout (tag, entry);
+ pango_layout_get_pixel_size (tag->priv->layout, &layout_width, NULL);
+
+ context = gd_tagged_entry_tag_get_context (tag, entry);
+ state = gd_tagged_entry_tag_get_state (tag, entry);
+
+ gtk_style_context_set_state (context, state);
+ gtk_style_context_get_padding (context,
+ gtk_style_context_get_state (context),
+ &button_padding);
+ gtk_style_context_get_border (context,
+ gtk_style_context_get_state (context),
+ &button_border);
+ gtk_style_context_get_margin (context,
+ gtk_style_context_get_state (context),
+ &button_margin);
+
+ gd_tagged_entry_tag_ensure_close_surface (tag, context);
+
+ gtk_style_context_restore (context);
+
+ button_width = 0;
+ if (entry->priv->button_visible && tag->priv->has_close_button)
+ {
+ scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (entry));
+ button_width = cairo_image_surface_get_width (tag->priv->close_surface) / scale_factor +
+ BUTTON_INTERNAL_SPACING;
+ }
+
+ return layout_width + button_padding.left + button_padding.right +
+ button_border.left + button_border.right +
+ button_margin.left + button_margin.right +
+ button_width;
+}
+
+static void
+gd_tagged_entry_tag_get_size (GdTaggedEntryTag *tag,
+ GdTaggedEntry *entry,
+ gint *width_out,
+ gint *height_out)
+{
+ gint width, panel_height;
+
+ width = gd_tagged_entry_tag_get_width (tag, entry);
+ panel_height = gd_tagged_entry_tag_panel_get_height (tag, entry);
+
+ if (width_out)
+ *width_out = width;
+ if (height_out)
+ *height_out = panel_height;
+}
+
+static void
+gd_tagged_entry_tag_get_relative_allocations (GdTaggedEntryTag *tag,
+ GdTaggedEntry *entry,
+ GtkStyleContext *context,
+ GtkAllocation *background_allocation_out,
+ GtkAllocation *layout_allocation_out,
+ GtkAllocation *button_allocation_out)
+{
+ GtkAllocation background_allocation, layout_allocation, button_allocation;
+ gint width, height, x, y, pix_width, pix_height;
+ gint layout_width, layout_height;
+ gint scale_factor;
+ GtkBorder padding, border;
+ GtkStateFlags state;
+
+ width = gdk_window_get_width (tag->priv->window);
+ height = gdk_window_get_height (tag->priv->window);
+ scale_factor = gdk_window_get_scale_factor (tag->priv->window);
+
+ state = gd_tagged_entry_tag_get_state (tag, entry);
+ gtk_style_context_save (context);
+ gtk_style_context_set_state (context, state);
+ gtk_style_context_get_margin (context,
+ gtk_style_context_get_state (context),
+ &padding);
+ gtk_style_context_restore (context);
+
+ width -= padding.left + padding.right;
+ height -= padding.top + padding.bottom;
+ x = padding.left;
+ y = padding.top;
+
+ background_allocation.x = x;
+ background_allocation.y = y;
+ background_allocation.width = width;
+ background_allocation.height = height;
+
+ layout_allocation = button_allocation = background_allocation;
+
+ gtk_style_context_save (context);
+ gtk_style_context_set_state (context, state);
+ gtk_style_context_get_padding (context,
+ gtk_style_context_get_state (context),
+ &padding);
+ gtk_style_context_get_border (context,
+ gtk_style_context_get_state (context),
+ &border);
+ gtk_style_context_restore (context);
+
+ gd_tagged_entry_tag_ensure_layout (tag, entry);
+ pango_layout_get_pixel_size (tag->priv->layout, &layout_width, &layout_height);
+
+ layout_allocation.x += border.left + padding.left;
+ layout_allocation.y += (layout_allocation.height - layout_height) / 2;
+
+ if (entry->priv->button_visible && tag->priv->has_close_button)
+ {
+ pix_width = cairo_image_surface_get_width (tag->priv->close_surface) / scale_factor;
+ pix_height = cairo_image_surface_get_height (tag->priv->close_surface) / scale_factor;
+ }
+ else
+ {
+ pix_width = 0;
+ pix_height = 0;
+ }
+
+ button_allocation.x += width - pix_width - border.right - padding.right;
+ button_allocation.y += (height - pix_height) / 2;
+ button_allocation.width = pix_width;
+ button_allocation.height = pix_height;
+
+ if (background_allocation_out)
+ *background_allocation_out = background_allocation;
+ if (layout_allocation_out)
+ *layout_allocation_out = layout_allocation;
+ if (button_allocation_out)
+ *button_allocation_out = button_allocation;
+}
+
+static gboolean
+gd_tagged_entry_tag_event_is_button (GdTaggedEntryTag *tag,
+ GdTaggedEntry *entry,
+ gdouble event_x,
+ gdouble event_y)
+{
+ GtkAllocation button_allocation;
+ GtkStyleContext *context;
+
+ if (!entry->priv->button_visible || !tag->priv->has_close_button)
+ return FALSE;
+
+ context = gd_tagged_entry_tag_get_context (tag, entry);
+ gd_tagged_entry_tag_get_relative_allocations (tag, entry, context, NULL, NULL, &button_allocation);
+
+ gtk_style_context_restore (context);
+
+ /* see if the event falls into the button allocation */
+ if ((event_x >= button_allocation.x &&
+ event_x <= button_allocation.x + button_allocation.width) &&
+ (event_y >= button_allocation.y &&
+ event_y <= button_allocation.y + button_allocation.height))
+ return TRUE;
+
+ return FALSE;
+}
+
+gboolean
+gd_tagged_entry_tag_get_area (GdTaggedEntryTag *tag,
+ cairo_rectangle_int_t *rect)
+{
+ GtkStyleContext *context;
+ GtkAllocation background_allocation;
+ int window_x, window_y;
+ GtkAllocation alloc;
+
+ g_return_val_if_fail (GD_IS_TAGGED_ENTRY_TAG (tag), FALSE);
+ g_return_val_if_fail (rect != NULL, FALSE);
+
+ gdk_window_get_position (tag->priv->window, &window_x, &window_y);
+ gtk_widget_get_allocation (GTK_WIDGET (tag->priv->entry), &alloc);
+ context = gd_tagged_entry_tag_get_context (tag, tag->priv->entry);
+ gd_tagged_entry_tag_get_relative_allocations (tag, tag->priv->entry, context,
+ &background_allocation,
+ NULL, NULL);
+ gtk_style_context_restore (context);
+
+ rect->x = window_x - alloc.x + background_allocation.x;
+ rect->y = window_y - alloc.y + background_allocation.y;
+ rect->width = background_allocation.width;
+ rect->height = background_allocation.height;
+
+ return TRUE;
+}
+
+static void
+gd_tagged_entry_tag_draw (GdTaggedEntryTag *tag,
+ cairo_t *cr,
+ GdTaggedEntry *entry)
+{
+ GtkStyleContext *context;
+ GtkStateFlags state;
+ GtkAllocation background_allocation, layout_allocation, button_allocation;
+
+ context = gd_tagged_entry_tag_get_context (tag, entry);
+ gd_tagged_entry_tag_get_relative_allocations (tag, entry, context,
+ &background_allocation,
+ &layout_allocation,
+ &button_allocation);
+
+ cairo_save (cr);
+ gtk_cairo_transform_to_window (cr, GTK_WIDGET (entry), tag->priv->window);
+
+ gtk_style_context_save (context);
+
+ state = gd_tagged_entry_tag_get_state (tag, entry);
+ gtk_style_context_set_state (context, state);
+ gtk_render_background (context, cr,
+ background_allocation.x, background_allocation.y,
+ background_allocation.width, background_allocation.height);
+ gtk_render_frame (context, cr,
+ background_allocation.x, background_allocation.y,
+ background_allocation.width, background_allocation.height);
+
+ gtk_render_layout (context, cr,
+ layout_allocation.x, layout_allocation.y,
+ tag->priv->layout);
+
+ gtk_style_context_restore (context);
+
+ if (!entry->priv->button_visible || !tag->priv->has_close_button)
+ goto done;
+
+ gtk_style_context_add_class (context, GTK_STYLE_CLASS_BUTTON);
+ state = gd_tagged_entry_tag_get_button_state (tag, entry);
+ gtk_style_context_set_state (context, state);
+
+ /* if the state changed since last time we draw the pixbuf,
+ * clear and redraw it.
+ */
+ if (state != tag->priv->last_button_state)
+ {
+ g_clear_pointer (&tag->priv->close_surface, cairo_surface_destroy);
+ gd_tagged_entry_tag_ensure_close_surface (tag, context);
+
+ tag->priv->last_button_state = state;
+ }
+
+ gtk_render_background (context, cr,
+ button_allocation.x, button_allocation.y,
+ button_allocation.width, button_allocation.height);
+ gtk_render_frame (context, cr,
+ button_allocation.x, button_allocation.y,
+ button_allocation.width, button_allocation.height);
+
+ gtk_render_icon_surface (context, cr,
+ tag->priv->close_surface,
+ button_allocation.x, button_allocation.y);
+
+done:
+ gtk_style_context_restore (context);
+
+ cairo_restore (cr);
+}
+
+static void
+gd_tagged_entry_tag_unrealize (GdTaggedEntryTag *tag)
+{
+ if (tag->priv->window == NULL)
+ return;
+
+ gdk_window_set_user_data (tag->priv->window, NULL);
+ gdk_window_destroy (tag->priv->window);
+ tag->priv->window = NULL;
+}
+
+static void
+gd_tagged_entry_tag_realize (GdTaggedEntryTag *tag,
+ GdTaggedEntry *entry)
+{
+ GtkWidget *widget = GTK_WIDGET (entry);
+ GdkWindowAttr attributes;
+ gint attributes_mask;
+ gint tag_width, tag_height;
+
+ if (tag->priv->window != NULL)
+ return;
+
+ attributes.window_type = GDK_WINDOW_CHILD;
+ attributes.wclass = GDK_INPUT_ONLY;
+ attributes.event_mask = gtk_widget_get_events (widget);
+ attributes.event_mask |= GDK_BUTTON_PRESS_MASK
+ | GDK_BUTTON_RELEASE_MASK | GDK_LEAVE_NOTIFY_MASK | GDK_ENTER_NOTIFY_MASK
+ | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK;
+
+ gd_tagged_entry_tag_get_size (tag, entry, &tag_width, &tag_height);
+ attributes.x = 0;
+ attributes.y = 0;
+ attributes.width = tag_width;
+ attributes.height = tag_height;
+
+ attributes_mask = GDK_WA_X | GDK_WA_Y;
+
+ tag->priv->window = gdk_window_new (gtk_widget_get_window (widget),
+ &attributes, attributes_mask);
+ gdk_window_set_user_data (tag->priv->window, widget);
+}
+
+static gboolean
+gd_tagged_entry_draw (GtkWidget *widget,
+ cairo_t *cr)
+{
+ GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);
+ GdTaggedEntryTag *tag;
+ GList *l;
+
+ GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->draw (widget, cr);
+
+ for (l = self->priv->tags; l != NULL; l = l->next)
+ {
+ tag = l->data;
+ gd_tagged_entry_tag_draw (tag, cr, self);
+ }
+
+ return FALSE;
+}
+
+static void
+gd_tagged_entry_map (GtkWidget *widget)
+{
+ GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);
+ GdTaggedEntryTag *tag;
+ GList *l;
+
+ if (gtk_widget_get_realized (widget) && !gtk_widget_get_mapped (widget))
+ {
+ GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->map (widget);
+
+ for (l = self->priv->tags; l != NULL; l = l->next)
+ {
+ tag = l->data;
+ gdk_window_show (tag->priv->window);
+ }
+ }
+}
+
+static void
+gd_tagged_entry_unmap (GtkWidget *widget)
+{
+ GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);
+ GdTaggedEntryTag *tag;
+ GList *l;
+
+ if (gtk_widget_get_mapped (widget))
+ {
+ for (l = self->priv->tags; l != NULL; l = l->next)
+ {
+ tag = l->data;
+ gdk_window_hide (tag->priv->window);
+ }
+
+ GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->unmap (widget);
+ }
+}
+
+static void
+gd_tagged_entry_realize (GtkWidget *widget)
+{
+ GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);
+ GdTaggedEntryTag *tag;
+ GList *l;
+
+ GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->realize (widget);
+
+ for (l = self->priv->tags; l != NULL; l = l->next)
+ {
+ tag = l->data;
+ gd_tagged_entry_tag_realize (tag, self);
+ }
+}
+
+static void
+gd_tagged_entry_unrealize (GtkWidget *widget)
+{
+ GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);
+ GdTaggedEntryTag *tag;
+ GList *l;
+
+ GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->unrealize (widget);
+
+ for (l = self->priv->tags; l != NULL; l = l->next)
+ {
+ tag = l->data;
+ gd_tagged_entry_tag_unrealize (tag);
+ }
+}
+
+static void
+gd_tagged_entry_get_text_area_size (GtkEntry *entry,
+ gint *x,
+ gint *y,
+ gint *width,
+ gint *height)
+{
+ GdTaggedEntry *self = GD_TAGGED_ENTRY (entry);
+ gint tag_panel_width;
+
+ GTK_ENTRY_CLASS (gd_tagged_entry_parent_class)->get_text_area_size (entry, x, y, width, height);
+
+ tag_panel_width = gd_tagged_entry_tag_panel_get_width (self);
+
+ if (width)
+ *width -= tag_panel_width;
+}
+
+static void
+gd_tagged_entry_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);
+ gint x, y, width, height;
+ GdTaggedEntryTag *tag;
+ GList *l;
+
+ gtk_widget_set_allocation (widget, allocation);
+ GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->size_allocate (widget, allocation);
+
+ if (gtk_widget_get_realized (widget))
+ {
+ gd_tagged_entry_tag_panel_get_position (self, &x, &y);
+
+ for (l = self->priv->tags; l != NULL; l = l->next)
+ {
+ GtkBorder margin;
+
+ tag = l->data;
+ gd_tagged_entry_tag_get_size (tag, self, &width, &height);
+ gd_tagged_entry_tag_get_margin (tag, self, &margin);
+ gdk_window_move_resize (tag->priv->window, x, y + margin.top, width, height);
+
+ x += width;
+ }
+
+ gtk_widget_queue_draw (widget);
+ }
+}
+
+static void
+gd_tagged_entry_get_preferred_width (GtkWidget *widget,
+ gint *minimum,
+ gint *natural)
+{
+ GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);
+ gint tag_panel_width;
+
+ GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->get_preferred_width (widget, minimum, natural);
+
+ tag_panel_width = gd_tagged_entry_tag_panel_get_width (self);
+
+ if (minimum)
+ *minimum += tag_panel_width;
+ if (natural)
+ *natural += tag_panel_width;
+}
+
+static void
+gd_tagged_entry_finalize (GObject *obj)
+{
+ GdTaggedEntry *self = GD_TAGGED_ENTRY (obj);
+
+ if (self->priv->tags != NULL)
+ {
+ g_list_free_full (self->priv->tags, g_object_unref);
+ self->priv->tags = NULL;
+ }
+
+ G_OBJECT_CLASS (gd_tagged_entry_parent_class)->finalize (obj);
+}
+
+static GdTaggedEntryTag *
+gd_tagged_entry_find_tag_by_window (GdTaggedEntry *self,
+ GdkWindow *window)
+{
+ GdTaggedEntryTag *tag = NULL, *elem;
+ GList *l;
+
+ for (l = self->priv->tags; l != NULL; l = l->next)
+ {
+ elem = l->data;
+ if (elem->priv->window == window)
+ {
+ tag = elem;
+ break;
+ }
+ }
+
+ return tag;
+}
+
+static gint
+gd_tagged_entry_enter_notify (GtkWidget *widget,
+ GdkEventCrossing *event)
+{
+ GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);
+ GdTaggedEntryTag *tag;
+
+ tag = gd_tagged_entry_find_tag_by_window (self, event->window);
+
+ if (tag != NULL)
+ {
+ self->priv->in_child = tag;
+ gtk_widget_queue_draw (widget);
+ }
+
+ return GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->enter_notify_event (widget, event);
+}
+
+static gint
+gd_tagged_entry_leave_notify (GtkWidget *widget,
+ GdkEventCrossing *event)
+{
+ GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);
+
+ if (self->priv->in_child != NULL)
+ {
+ self->priv->in_child = NULL;
+ gtk_widget_queue_draw (widget);
+ }
+
+ return GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->leave_notify_event (widget, event);
+}
+
+static gint
+gd_tagged_entry_motion_notify (GtkWidget *widget,
+ GdkEventMotion *event)
+{
+ GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);
+ GdTaggedEntryTag *tag;
+
+ tag = gd_tagged_entry_find_tag_by_window (self, event->window);
+
+ if (tag != NULL)
+ {
+ gdk_event_request_motions (event);
+
+ self->priv->in_child = tag;
+ self->priv->in_child_button = gd_tagged_entry_tag_event_is_button (tag, self, event->x, event->y);
+ gtk_widget_queue_draw (widget);
+
+ return FALSE;
+ }
+
+ return GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->motion_notify_event (widget, event);
+}
+
+static gboolean
+gd_tagged_entry_button_release_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);
+ GdTaggedEntryTag *tag;
+
+ tag = gd_tagged_entry_find_tag_by_window (self, event->window);
+
+ if (tag != NULL)
+ {
+ self->priv->in_child_active = FALSE;
+
+ if (gd_tagged_entry_tag_event_is_button (tag, self, event->x, event->y))
+ {
+ self->priv->in_child_button_active = FALSE;
+ g_signal_emit (self, signals[SIGNAL_TAG_BUTTON_CLICKED], 0, tag);
+ }
+ else
+ {
+ g_signal_emit (self, signals[SIGNAL_TAG_CLICKED], 0, tag);
+ }
+
+ gtk_widget_queue_draw (widget);
+
+ return TRUE;
+ }
+
+ return GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->button_release_event (widget, event);
+}
+
+static gboolean
+gd_tagged_entry_button_press_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ GdTaggedEntry *self = GD_TAGGED_ENTRY (widget);
+ GdTaggedEntryTag *tag;
+
+ tag = gd_tagged_entry_find_tag_by_window (self, event->window);
+
+ if (tag != NULL)
+ {
+ if (gd_tagged_entry_tag_event_is_button (tag, self, event->x, event->y))
+ self->priv->in_child_button_active = TRUE;
+ else
+ self->priv->in_child_active = TRUE;
+
+ gtk_widget_queue_draw (widget);
+
+ return TRUE;
+ }
+
+ return GTK_WIDGET_CLASS (gd_tagged_entry_parent_class)->button_press_event (widget, event);
+}
+
+static void
+gd_tagged_entry_init (GdTaggedEntry *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GD_TYPE_TAGGED_ENTRY, GdTaggedEntryPrivate);
+ self->priv->button_visible = TRUE;
+}
+
+static void
+gd_tagged_entry_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GdTaggedEntry *self = GD_TAGGED_ENTRY (object);
+
+ switch (property_id)
+ {
+ case PROP_TAG_BUTTON_VISIBLE:
+ g_value_set_boolean (value, gd_tagged_entry_get_tag_button_visible (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+gd_tagged_entry_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GdTaggedEntry *self = GD_TAGGED_ENTRY (object);
+
+ switch (property_id)
+ {
+ case PROP_TAG_BUTTON_VISIBLE:
+ gd_tagged_entry_set_tag_button_visible (self, g_value_get_boolean (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+gd_tagged_entry_class_init (GdTaggedEntryClass *klass)
+{
+ GtkWidgetClass *wclass = GTK_WIDGET_CLASS (klass);
+ GtkEntryClass *eclass = GTK_ENTRY_CLASS (klass);
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+
+ oclass->finalize = gd_tagged_entry_finalize;
+ oclass->set_property = gd_tagged_entry_set_property;
+ oclass->get_property = gd_tagged_entry_get_property;
+
+ wclass->realize = gd_tagged_entry_realize;
+ wclass->unrealize = gd_tagged_entry_unrealize;
+ wclass->map = gd_tagged_entry_map;
+ wclass->unmap = gd_tagged_entry_unmap;
+ wclass->size_allocate = gd_tagged_entry_size_allocate;
+ wclass->get_preferred_width = gd_tagged_entry_get_preferred_width;
+ wclass->draw = gd_tagged_entry_draw;
+ wclass->enter_notify_event = gd_tagged_entry_enter_notify;
+ wclass->leave_notify_event = gd_tagged_entry_leave_notify;
+ wclass->motion_notify_event = gd_tagged_entry_motion_notify;
+ wclass->button_press_event = gd_tagged_entry_button_press_event;
+ wclass->button_release_event = gd_tagged_entry_button_release_event;
+
+ eclass->get_text_area_size = gd_tagged_entry_get_text_area_size;
+
+ signals[SIGNAL_TAG_CLICKED] =
+ g_signal_new ("tag-clicked",
+ GD_TYPE_TAGGED_ENTRY,
+ G_SIGNAL_RUN_FIRST | G_SIGNAL_DETAILED,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 1, GD_TYPE_TAGGED_ENTRY_TAG);
+ signals[SIGNAL_TAG_BUTTON_CLICKED] =
+ g_signal_new ("tag-button-clicked",
+ GD_TYPE_TAGGED_ENTRY,
+ G_SIGNAL_RUN_FIRST | G_SIGNAL_DETAILED,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 1, GD_TYPE_TAGGED_ENTRY_TAG);
+
+ properties[PROP_TAG_BUTTON_VISIBLE] =
+ g_param_spec_boolean ("tag-close-visible", "Tag close icon visibility",
+ "Whether the close button should be shown in tags.", TRUE,
+ G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS);
+
+ g_type_class_add_private (klass, sizeof (GdTaggedEntryPrivate));
+ g_object_class_install_properties (oclass, NUM_PROPERTIES, properties);
+}
+
+static void
+gd_tagged_entry_tag_init (GdTaggedEntryTag *self)
+{
+ GdTaggedEntryTagPrivate *priv;
+
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GD_TYPE_TAGGED_ENTRY_TAG, GdTaggedEntryTagPrivate);
+ priv = self->priv;
+
+ priv->last_button_state = GTK_STATE_FLAG_NORMAL;
+}
+
+static void
+gd_tagged_entry_tag_finalize (GObject *obj)
+{
+ GdTaggedEntryTag *tag = GD_TAGGED_ENTRY_TAG (obj);
+ GdTaggedEntryTagPrivate *priv = tag->priv;
+
+ if (priv->window != NULL)
+ gd_tagged_entry_tag_unrealize (tag);
+
+ g_clear_object (&priv->layout);
+ g_clear_pointer (&priv->close_surface, cairo_surface_destroy);
+ g_free (priv->label);
+ g_free (priv->style);
+
+ G_OBJECT_CLASS (gd_tagged_entry_tag_parent_class)->finalize (obj);
+}
+
+static void
+gd_tagged_entry_tag_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GdTaggedEntryTag *self = GD_TAGGED_ENTRY_TAG (object);
+
+ switch (property_id)
+ {
+ case PROP_TAG_LABEL:
+ g_value_set_string (value, gd_tagged_entry_tag_get_label (self));
+ break;
+ case PROP_TAG_HAS_CLOSE_BUTTON:
+ g_value_set_boolean (value, gd_tagged_entry_tag_get_has_close_button (self));
+ break;
+ case PROP_TAG_STYLE:
+ g_value_set_string (value, gd_tagged_entry_tag_get_style (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+gd_tagged_entry_tag_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GdTaggedEntryTag *self = GD_TAGGED_ENTRY_TAG (object);
+
+ switch (property_id)
+ {
+ case PROP_TAG_LABEL:
+ gd_tagged_entry_tag_set_label (self, g_value_get_string (value));
+ break;
+ case PROP_TAG_HAS_CLOSE_BUTTON:
+ gd_tagged_entry_tag_set_has_close_button (self, g_value_get_boolean (value));
+ break;
+ case PROP_TAG_STYLE:
+ gd_tagged_entry_tag_set_style (self, g_value_get_string (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+gd_tagged_entry_tag_class_init (GdTaggedEntryTagClass *klass)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+
+ oclass->finalize = gd_tagged_entry_tag_finalize;
+ oclass->set_property = gd_tagged_entry_tag_set_property;
+ oclass->get_property = gd_tagged_entry_tag_get_property;
+
+ tag_properties[PROP_TAG_LABEL] =
+ g_param_spec_string ("label", "Label",
+ "Text to show on the tag.", NULL,
+ G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ tag_properties[PROP_TAG_HAS_CLOSE_BUTTON] =
+ g_param_spec_boolean ("has-close-button", "Tag has a close button",
+ "Whether the tag has a close button.", TRUE,
+ G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ tag_properties[PROP_TAG_STYLE] =
+ g_param_spec_string ("style", "Style",
+ "Style of the tag.", "entry-tag",
+ G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ g_type_class_add_private (klass, sizeof (GdTaggedEntryTagPrivate));
+ g_object_class_install_properties (oclass, NUM_TAG_PROPERTIES, tag_properties);
+}
+
+GdTaggedEntry *
+gd_tagged_entry_new (void)
+{
+ return g_object_new (GD_TYPE_TAGGED_ENTRY, NULL);
+}
+
+gboolean
+gd_tagged_entry_insert_tag (GdTaggedEntry *self,
+ GdTaggedEntryTag *tag,
+ gint position)
+{
+ if (g_list_find (self->priv->tags, tag) != NULL)
+ return FALSE;
+
+ tag->priv->entry = self;
+
+ self->priv->tags = g_list_insert (self->priv->tags, g_object_ref (tag), position);
+
+ if (gtk_widget_get_realized (GTK_WIDGET (self)))
+ gd_tagged_entry_tag_realize (tag, self);
+
+ if (gtk_widget_get_mapped (GTK_WIDGET (self)))
+ gdk_window_show_unraised (tag->priv->window);
+
+ gtk_widget_queue_resize (GTK_WIDGET (self));
+
+ return TRUE;
+}
+
+gboolean
+gd_tagged_entry_add_tag (GdTaggedEntry *self,
+ GdTaggedEntryTag *tag)
+{
+ return gd_tagged_entry_insert_tag (self, tag, -1);
+}
+
+gboolean
+gd_tagged_entry_remove_tag (GdTaggedEntry *self,
+ GdTaggedEntryTag *tag)
+{
+ if (!g_list_find (self->priv->tags, tag))
+ return FALSE;
+
+ gd_tagged_entry_tag_unrealize (tag);
+
+ self->priv->tags = g_list_remove (self->priv->tags, tag);
+ g_object_unref (tag);
+
+ gtk_widget_queue_resize (GTK_WIDGET (self));
+
+ return TRUE;
+}
+
+GdTaggedEntryTag *
+gd_tagged_entry_tag_new (const gchar *label)
+{
+ return g_object_new (GD_TYPE_TAGGED_ENTRY_TAG, "label", label, NULL);
+}
+
+void
+gd_tagged_entry_tag_set_label (GdTaggedEntryTag *tag,
+ const gchar *label)
+{
+ GdTaggedEntryTagPrivate *priv;
+
+ g_return_if_fail (GD_IS_TAGGED_ENTRY_TAG (tag));
+
+ priv = tag->priv;
+
+ if (g_strcmp0 (priv->label, label) != 0)
+ {
+ GtkWidget *entry;
+
+ g_free (priv->label);
+ priv->label = g_strdup (label);
+ g_clear_object (&priv->layout);
+
+ entry = GTK_WIDGET (tag->priv->entry);
+ if (entry)
+ gtk_widget_queue_resize (entry);
+ }
+}
+
+const gchar *
+gd_tagged_entry_tag_get_label (GdTaggedEntryTag *tag)
+{
+ g_return_val_if_fail (GD_IS_TAGGED_ENTRY_TAG (tag), NULL);
+
+ return tag->priv->label;
+}
+
+void
+gd_tagged_entry_tag_set_has_close_button (GdTaggedEntryTag *tag,
+ gboolean has_close_button)
+{
+ GdTaggedEntryTagPrivate *priv;
+
+ g_return_if_fail (GD_IS_TAGGED_ENTRY_TAG (tag));
+
+ priv = tag->priv;
+
+ has_close_button = has_close_button != FALSE;
+ if (priv->has_close_button != has_close_button)
+ {
+ GtkWidget *entry;
+
+ priv->has_close_button = has_close_button;
+ g_clear_object (&priv->layout);
+
+ entry = GTK_WIDGET (priv->entry);
+ if (entry)
+ gtk_widget_queue_resize (entry);
+ }
+}
+
+gboolean
+gd_tagged_entry_tag_get_has_close_button (GdTaggedEntryTag *tag)
+{
+ g_return_val_if_fail (GD_IS_TAGGED_ENTRY_TAG (tag), FALSE);
+
+ return tag->priv->has_close_button;
+}
+
+void
+gd_tagged_entry_tag_set_style (GdTaggedEntryTag *tag,
+ const gchar *style)
+{
+ GdTaggedEntryTagPrivate *priv;
+
+ g_return_if_fail (GD_IS_TAGGED_ENTRY_TAG (tag));
+
+ priv = tag->priv;
+
+ if (g_strcmp0 (priv->style, style) != 0)
+ {
+ GtkWidget *entry;
+
+ g_free (priv->style);
+ priv->style = g_strdup (style);
+ g_clear_object (&priv->layout);
+
+ entry = GTK_WIDGET (tag->priv->entry);
+ if (entry)
+ gtk_widget_queue_resize (entry);
+ }
+}
+
+const gchar *
+gd_tagged_entry_tag_get_style (GdTaggedEntryTag *tag)
+{
+ g_return_val_if_fail (GD_IS_TAGGED_ENTRY_TAG (tag), NULL);
+
+ return tag->priv->style;
+}
+
+void
+gd_tagged_entry_set_tag_button_visible (GdTaggedEntry *self,
+ gboolean visible)
+{
+ g_return_if_fail (GD_IS_TAGGED_ENTRY (self));
+
+ if (self->priv->button_visible == visible)
+ return;
+
+ self->priv->button_visible = visible;
+ gtk_widget_queue_resize (GTK_WIDGET (self));
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TAG_BUTTON_VISIBLE]);
+}
+
+gboolean
+gd_tagged_entry_get_tag_button_visible (GdTaggedEntry *self)
+{
+ g_return_val_if_fail (GD_IS_TAGGED_ENTRY (self), FALSE);
+
+ return self->priv->button_visible;
+}
diff --git a/subprojects/libgd/libgd/gd-tagged-entry.h b/subprojects/libgd/libgd/gd-tagged-entry.h
new file mode 100644
index 0000000..ba9f673
--- /dev/null
+++ b/subprojects/libgd/libgd/gd-tagged-entry.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2011 Red Hat, Inc.
+ * Copyright (c) 2013 Ignacio Casal Quinteiro
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Cosimo Cecchi <cosimoc@redhat.com>
+ *
+ */
+
+#ifndef __GD_TAGGED_ENTRY_H__
+#define __GD_TAGGED_ENTRY_H__
+
+#include <glib-object.h>
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GD_TYPE_TAGGED_ENTRY gd_tagged_entry_get_type()
+#define GD_TAGGED_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GD_TYPE_TAGGED_ENTRY, GdTaggedEntry))
+#define GD_TAGGED_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GD_TYPE_TAGGED_ENTRY, GdTaggedEntryClass))
+#define GD_IS_TAGGED_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GD_TYPE_TAGGED_ENTRY))
+#define GD_IS_TAGGED_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GD_TYPE_TAGGED_ENTRY))
+#define GD_TAGGED_ENTRY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GD_TYPE_TAGGED_ENTRY, GdTaggedEntryClass))
+
+typedef struct _GdTaggedEntry GdTaggedEntry;
+typedef struct _GdTaggedEntryClass GdTaggedEntryClass;
+typedef struct _GdTaggedEntryPrivate GdTaggedEntryPrivate;
+
+typedef struct _GdTaggedEntryTag GdTaggedEntryTag;
+typedef struct _GdTaggedEntryTagClass GdTaggedEntryTagClass;
+typedef struct _GdTaggedEntryTagPrivate GdTaggedEntryTagPrivate;
+
+struct _GdTaggedEntry
+{
+ GtkSearchEntry parent;
+
+ GdTaggedEntryPrivate *priv;
+};
+
+struct _GdTaggedEntryClass
+{
+ GtkSearchEntryClass parent_class;
+};
+
+#define GD_TYPE_TAGGED_ENTRY_TAG gd_tagged_entry_tag_get_type()
+#define GD_TAGGED_ENTRY_TAG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GD_TYPE_TAGGED_ENTRY_TAG, GdTaggedEntryTag))
+#define GD_TAGGED_ENTRY_TAG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GD_TYPE_TAGGED_ENTRY_TAG, GdTaggedEntryTagClass))
+#define GD_IS_TAGGED_ENTRY_TAG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GD_TYPE_TAGGED_ENTRY_TAG))
+#define GD_IS_TAGGED_ENTRY_TAG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GD_TYPE_TAGGED_ENTRY_TAG))
+#define GD_TAGGED_ENTRY_TAG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GD_TYPE_TAGGED_ENTRY_TAG, GdTaggedEntryTagClass))
+
+struct _GdTaggedEntryTag
+{
+ GObject parent;
+
+ GdTaggedEntryTagPrivate *priv;
+};
+
+struct _GdTaggedEntryTagClass
+{
+ GObjectClass parent_class;
+};
+
+GType gd_tagged_entry_get_type (void) G_GNUC_CONST;
+
+GdTaggedEntry *gd_tagged_entry_new (void);
+
+void gd_tagged_entry_set_tag_button_visible (GdTaggedEntry *self,
+ gboolean visible);
+gboolean gd_tagged_entry_get_tag_button_visible (GdTaggedEntry *self);
+
+gboolean gd_tagged_entry_insert_tag (GdTaggedEntry *self,
+ GdTaggedEntryTag *tag,
+ gint position);
+
+gboolean gd_tagged_entry_add_tag (GdTaggedEntry *self,
+ GdTaggedEntryTag *tag);
+
+gboolean gd_tagged_entry_remove_tag (GdTaggedEntry *self,
+ GdTaggedEntryTag *tag);
+
+GType gd_tagged_entry_tag_get_type (void) G_GNUC_CONST;
+
+GdTaggedEntryTag *gd_tagged_entry_tag_new (const gchar *label);
+
+void gd_tagged_entry_tag_set_label (GdTaggedEntryTag *tag,
+ const gchar *label);
+const gchar *gd_tagged_entry_tag_get_label (GdTaggedEntryTag *tag);
+
+void gd_tagged_entry_tag_set_has_close_button (GdTaggedEntryTag *tag,
+ gboolean has_close_button);
+gboolean gd_tagged_entry_tag_get_has_close_button (GdTaggedEntryTag *tag);
+
+void gd_tagged_entry_tag_set_style (GdTaggedEntryTag *tag,
+ const gchar *style);
+const gchar *gd_tagged_entry_tag_get_style (GdTaggedEntryTag *tag);
+
+gboolean gd_tagged_entry_tag_get_area (GdTaggedEntryTag *tag,
+ cairo_rectangle_int_t *rect);
+
+G_END_DECLS
+
+#endif /* __GD_TAGGED_ENTRY_H__ */
diff --git a/subprojects/libgd/libgd/gd-toggle-pixbuf-renderer.c b/subprojects/libgd/libgd/gd-toggle-pixbuf-renderer.c
new file mode 100644
index 0000000..fa23cb6
--- /dev/null
+++ b/subprojects/libgd/libgd/gd-toggle-pixbuf-renderer.c
@@ -0,0 +1,271 @@
+/*
+ * Copyright (c) 2011 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Cosimo Cecchi <cosimoc@redhat.com>
+ *
+ */
+
+#include "gd-toggle-pixbuf-renderer.h"
+
+G_DEFINE_TYPE (GdTogglePixbufRenderer, gd_toggle_pixbuf_renderer, GTK_TYPE_CELL_RENDERER_PIXBUF);
+
+enum {
+ PROP_ACTIVE = 1,
+ PROP_TOGGLE_VISIBLE,
+ PROP_PULSE,
+ NUM_PROPERTIES
+};
+
+static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
+
+struct _GdTogglePixbufRendererPrivate {
+ gboolean active;
+ gboolean toggle_visible;
+
+ guint pulse;
+};
+
+static void
+render_check (GdTogglePixbufRenderer *self,
+ cairo_t *cr,
+ GtkWidget *widget,
+ const GdkRectangle *cell_area,
+ gint icon_size,
+ gint xpad,
+ gint ypad)
+{
+ GtkStyleContext *context;
+ gint check_x, check_y, x_offset;
+ GtkTextDirection direction;
+
+ context = gtk_widget_get_style_context (widget);
+
+ if (!self->priv->toggle_visible)
+ return;
+
+ direction = gtk_widget_get_direction (widget);
+ if (direction == GTK_TEXT_DIR_RTL)
+ x_offset = xpad;
+ else
+ x_offset = cell_area->width - icon_size - xpad;
+
+ check_x = cell_area->x + x_offset;
+ check_y = cell_area->y + cell_area->height - icon_size - ypad;
+
+ gtk_style_context_save (context);
+ gtk_style_context_add_class (context, GTK_STYLE_CLASS_CHECK);
+
+ if (self->priv->active)
+ gtk_style_context_set_state (context, gtk_widget_get_state_flags (widget) | GTK_STATE_FLAG_CHECKED);
+
+ gtk_render_background (context, cr,
+ check_x, check_y,
+ icon_size, icon_size);
+ gtk_render_frame (context, cr,
+ check_x, check_y,
+ icon_size, icon_size);
+ gtk_render_check (context, cr,
+ check_x, check_y,
+ icon_size, icon_size);
+ gtk_style_context_restore (context);
+}
+
+static void
+render_activity (GdTogglePixbufRenderer *self,
+ cairo_t *cr,
+ GtkWidget *widget,
+ const GdkRectangle *cell_area,
+ gint icon_size,
+ gint xpad,
+ gint ypad)
+{
+ gint x, y, width, height;
+
+ if (self->priv->pulse == 0)
+ return;
+
+ width = cell_area->width / 4;
+ height = cell_area->height / 4;
+
+ x = cell_area->x + (cell_area->width / 2) - (width / 2) - xpad;
+ y = cell_area->y + (cell_area->height / 2) - (height / 2) - ypad;
+
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
+ gtk_paint_spinner (gtk_widget_get_style (widget),
+ cr,
+ GTK_STATE_FLAG_ACTIVE,
+ widget,
+ NULL,
+ (guint) self->priv->pulse - 1,
+ x, y,
+ width, height);
+ G_GNUC_END_IGNORE_DEPRECATIONS;
+}
+
+static void
+gd_toggle_pixbuf_renderer_render (GtkCellRenderer *cell,
+ cairo_t *cr,
+ GtkWidget *widget,
+ const GdkRectangle *background_area,
+ const GdkRectangle *cell_area,
+ GtkCellRendererState flags)
+{
+ gint icon_size = -1;
+ GdTogglePixbufRenderer *self = GD_TOGGLE_PIXBUF_RENDERER (cell);
+ gint xpad, ypad;
+
+ GTK_CELL_RENDERER_CLASS (gd_toggle_pixbuf_renderer_parent_class)->render
+ (cell, cr, widget,
+ background_area, cell_area, flags);
+
+ gtk_cell_renderer_get_padding (cell, &xpad, &ypad);
+ gtk_widget_style_get (widget,
+ "check-icon-size", &icon_size,
+ NULL);
+
+ if (icon_size == -1)
+ icon_size = 40;
+
+ render_activity (self, cr, widget, cell_area, icon_size, xpad, ypad);
+ render_check (self, cr, widget, cell_area, icon_size, xpad, ypad);
+}
+
+static void
+gd_toggle_pixbuf_renderer_get_size (GtkCellRenderer *cell,
+ GtkWidget *widget,
+ const GdkRectangle *cell_area,
+ gint *x_offset,
+ gint *y_offset,
+ gint *width,
+ gint *height)
+{
+ gint icon_size;
+
+ gtk_widget_style_get (widget,
+ "check-icon-size", &icon_size,
+ NULL);
+
+ GTK_CELL_RENDERER_CLASS (gd_toggle_pixbuf_renderer_parent_class)->get_size
+ (cell, widget, cell_area,
+ x_offset, y_offset, width, height);
+
+ *width += icon_size / 4;
+}
+
+static void
+gd_toggle_pixbuf_renderer_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GdTogglePixbufRenderer *self = GD_TOGGLE_PIXBUF_RENDERER (object);
+
+ switch (property_id)
+ {
+ case PROP_ACTIVE:
+ g_value_set_boolean (value, self->priv->active);
+ break;
+ case PROP_TOGGLE_VISIBLE:
+ g_value_set_boolean (value, self->priv->toggle_visible);
+ break;
+ case PROP_PULSE:
+ g_value_set_uint (value, self->priv->pulse);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gd_toggle_pixbuf_renderer_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GdTogglePixbufRenderer *self = GD_TOGGLE_PIXBUF_RENDERER (object);
+
+ switch (property_id)
+ {
+ case PROP_ACTIVE:
+ self->priv->active = g_value_get_boolean (value);
+ break;
+ case PROP_TOGGLE_VISIBLE:
+ self->priv->toggle_visible = g_value_get_boolean (value);
+ break;
+ case PROP_PULSE:
+ self->priv->pulse = g_value_get_uint (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gd_toggle_pixbuf_renderer_class_init (GdTogglePixbufRendererClass *klass)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+ GtkCellRendererClass *crclass = GTK_CELL_RENDERER_CLASS (klass);
+
+ crclass->render = gd_toggle_pixbuf_renderer_render;
+ crclass->get_size = gd_toggle_pixbuf_renderer_get_size;
+ oclass->get_property = gd_toggle_pixbuf_renderer_get_property;
+ oclass->set_property = gd_toggle_pixbuf_renderer_set_property;
+
+ properties[PROP_ACTIVE] =
+ g_param_spec_boolean ("active",
+ "Active",
+ "Whether the cell renderer is active",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS);
+ properties[PROP_TOGGLE_VISIBLE] =
+ g_param_spec_boolean ("toggle-visible",
+ "Toggle visible",
+ "Whether to draw the toggle indicator",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS);
+ properties[PROP_PULSE] =
+ g_param_spec_uint ("pulse",
+ "Pulse",
+ "Set to any value other than 0 to display a "
+ "spinner on top of the pixbuf.",
+ 0,
+ G_MAXUINT,
+ 0,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS);
+
+ g_type_class_add_private (klass, sizeof (GdTogglePixbufRendererPrivate));
+ g_object_class_install_properties (oclass, NUM_PROPERTIES, properties);
+}
+
+static void
+gd_toggle_pixbuf_renderer_init (GdTogglePixbufRenderer *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GD_TYPE_TOGGLE_PIXBUF_RENDERER,
+ GdTogglePixbufRendererPrivate);
+ self->priv->pulse = 0;
+}
+
+GtkCellRenderer *
+gd_toggle_pixbuf_renderer_new (void)
+{
+ return g_object_new (GD_TYPE_TOGGLE_PIXBUF_RENDERER, NULL);
+}
diff --git a/subprojects/libgd/libgd/gd-toggle-pixbuf-renderer.h b/subprojects/libgd/libgd/gd-toggle-pixbuf-renderer.h
new file mode 100644
index 0000000..fe54cf4
--- /dev/null
+++ b/subprojects/libgd/libgd/gd-toggle-pixbuf-renderer.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2011 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Cosimo Cecchi <cosimoc@redhat.com>
+ *
+ */
+
+#ifndef _GD_TOGGLE_PIXBUF_RENDERER_H
+#define _GD_TOGGLE_PIXBUF_RENDERER_H
+
+#include <glib-object.h>
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GD_TYPE_TOGGLE_PIXBUF_RENDERER gd_toggle_pixbuf_renderer_get_type()
+
+#define GD_TOGGLE_PIXBUF_RENDERER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ GD_TYPE_TOGGLE_PIXBUF_RENDERER, GdTogglePixbufRenderer))
+
+#define GD_TOGGLE_PIXBUF_RENDERER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ GD_TYPE_TOGGLE_PIXBUF_RENDERER, GdTogglePixbufRendererClass))
+
+#define GD_IS_TOGGLE_PIXBUF_RENDERER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ GD_TYPE_TOGGLE_PIXBUF_RENDERER))
+
+#define GD_IS_TOGGLE_PIXBUF_RENDERER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+ GD_TYPE_TOGGLE_PIXBUF_RENDERER))
+
+#define GD_TOGGLE_PIXBUF_RENDERER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ GD_TYPE_TOGGLE_PIXBUF_RENDERER, GdTogglePixbufRendererClass))
+
+typedef struct _GdTogglePixbufRenderer GdTogglePixbufRenderer;
+typedef struct _GdTogglePixbufRendererClass GdTogglePixbufRendererClass;
+typedef struct _GdTogglePixbufRendererPrivate GdTogglePixbufRendererPrivate;
+
+struct _GdTogglePixbufRenderer
+{
+ GtkCellRendererPixbuf parent;
+
+ GdTogglePixbufRendererPrivate *priv;
+};
+
+struct _GdTogglePixbufRendererClass
+{
+ GtkCellRendererPixbufClass parent_class;
+};
+
+GType gd_toggle_pixbuf_renderer_get_type (void) G_GNUC_CONST;
+
+GtkCellRenderer *gd_toggle_pixbuf_renderer_new (void);
+
+G_END_DECLS
+
+#endif /* _GD_TOGGLE_PIXBUF_RENDERER_H */
diff --git a/subprojects/libgd/libgd/gd-two-lines-renderer.c b/subprojects/libgd/libgd/gd-two-lines-renderer.c
new file mode 100644
index 0000000..a2c3fdc
--- /dev/null
+++ b/subprojects/libgd/libgd/gd-two-lines-renderer.c
@@ -0,0 +1,616 @@
+/*
+ * Copyright (c) 2011 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Cosimo Cecchi <cosimoc@redhat.com>
+ *
+ */
+
+#include "gd-two-lines-renderer.h"
+#include <string.h>
+
+#define SUBTITLE_DIM_PERCENTAGE 0.55
+#define SUBTITLE_SIZE_PERCENTAGE 0.82
+
+G_DEFINE_TYPE (GdTwoLinesRenderer, gd_two_lines_renderer, GTK_TYPE_CELL_RENDERER_TEXT)
+
+struct _GdTwoLinesRendererPrivate {
+ gchar *line_two;
+ gint text_lines;
+};
+
+enum {
+ PROP_TEXT_LINES = 1,
+ PROP_LINE_TWO,
+ NUM_PROPERTIES
+};
+
+static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
+
+static PangoLayout *
+create_layout_with_attrs (GtkWidget *widget,
+ const GdkRectangle *cell_area,
+ GdTwoLinesRenderer *self,
+ PangoEllipsizeMode ellipsize)
+{
+ PangoLayout *layout;
+ gint wrap_width, xpad;
+ PangoWrapMode wrap_mode;
+ PangoAlignment alignment;
+
+ g_object_get (self,
+ "wrap-width", &wrap_width,
+ "wrap-mode", &wrap_mode,
+ "alignment", &alignment,
+ "xpad", &xpad,
+ NULL);
+
+ layout = pango_layout_new (gtk_widget_get_pango_context (widget));
+
+ pango_layout_set_ellipsize (layout, ellipsize);
+ pango_layout_set_alignment (layout, alignment);
+
+ if (wrap_width != -1)
+ {
+ pango_layout_set_width (layout, wrap_width * PANGO_SCALE);
+ pango_layout_set_wrap (layout, wrap_mode);
+ }
+ else
+ {
+ if (cell_area != NULL)
+ pango_layout_set_width (layout, (cell_area->width - 2 * xpad) * PANGO_SCALE);
+ else
+ pango_layout_set_width (layout, -1);
+
+ pango_layout_set_wrap (layout, PANGO_WRAP_CHAR);
+ }
+
+ return layout;
+}
+
+static void
+apply_subtitle_style_to_layout (GtkStyleContext *context,
+ PangoLayout *layout,
+ GtkStateFlags flags)
+{
+ PangoFontDescription *desc;
+ PangoAttrList *layout_attr;
+ PangoAttribute *attr_alpha;
+
+ gtk_style_context_save (context);
+ gtk_style_context_set_state (context, flags);
+ gtk_style_context_get (context, gtk_style_context_get_state (context),
+ "font", &desc,
+ NULL);
+ gtk_style_context_restore (context);
+
+ /* Set the font size */
+ pango_font_description_set_size (desc, pango_font_description_get_size (desc) * SUBTITLE_SIZE_PERCENTAGE);
+ pango_layout_set_font_description (layout, desc);
+ pango_font_description_free (desc);
+
+ /* Set the font alpha */
+ layout_attr = pango_attr_list_new ();
+ attr_alpha = pango_attr_foreground_alpha_new (SUBTITLE_DIM_PERCENTAGE * 65535);
+ pango_attr_list_insert (layout_attr, attr_alpha);
+
+ pango_layout_set_attributes (layout, layout_attr);
+ pango_attr_list_unref (layout_attr);
+}
+
+static void
+gd_two_lines_renderer_prepare_layouts (GdTwoLinesRenderer *self,
+ const GdkRectangle *cell_area,
+ GtkWidget *widget,
+ PangoLayout **layout_one,
+ PangoLayout **layout_two)
+{
+ PangoLayout *line_one;
+ PangoLayout *line_two = NULL;
+ gchar *text = NULL;
+
+ g_object_get (self,
+ "text", &text,
+ NULL);
+
+ line_one = create_layout_with_attrs (widget, cell_area,
+ self, PANGO_ELLIPSIZE_MIDDLE);
+
+ if (self->priv->line_two == NULL ||
+ g_strcmp0 (self->priv->line_two, "") == 0)
+ {
+ pango_layout_set_height (line_one, - (self->priv->text_lines));
+
+ if (text != NULL)
+ pango_layout_set_text (line_one, text, -1);
+ }
+ else
+ {
+ GtkStyleContext *context;
+
+ line_two = create_layout_with_attrs (widget, cell_area,
+ self, PANGO_ELLIPSIZE_END);
+
+ context = gtk_widget_get_style_context (widget);
+ gtk_style_context_save (context);
+ apply_subtitle_style_to_layout (context, line_two, GTK_STATE_FLAG_NORMAL);
+ gtk_style_context_restore (context);
+
+ pango_layout_set_height (line_one, - (self->priv->text_lines - 1));
+ pango_layout_set_height (line_two, -1);
+ pango_layout_set_text (line_two, self->priv->line_two, -1);
+
+ if (text != NULL)
+ pango_layout_set_text (line_one, text, -1);
+ }
+
+ if (layout_one)
+ *layout_one = line_one;
+ if (layout_two)
+ *layout_two = line_two;
+
+ g_free (text);
+}
+
+static void
+gd_two_lines_renderer_get_size (GtkCellRenderer *cell,
+ GtkWidget *widget,
+ PangoLayout *layout_1,
+ PangoLayout *layout_2,
+ gint *width,
+ gint *height,
+ const GdkRectangle *cell_area,
+ gint *x_offset_1,
+ gint *x_offset_2,
+ gint *y_offset)
+{
+ GdTwoLinesRenderer *self = GD_TWO_LINES_RENDERER (cell);
+ gint xpad, ypad;
+ PangoLayout *layout_one, *layout_two;
+ GdkRectangle layout_one_rect, layout_two_rect, layout_union;
+
+ if (layout_1 == NULL)
+ {
+ gd_two_lines_renderer_prepare_layouts (self, cell_area, widget, &layout_one, &layout_two);
+ }
+ else
+ {
+ layout_one = g_object_ref (layout_1);
+
+ if (layout_2 != NULL)
+ layout_two = g_object_ref (layout_2);
+ else
+ layout_two = NULL;
+ }
+
+ gtk_cell_renderer_get_padding (cell, &xpad, &ypad);
+ pango_layout_get_pixel_extents (layout_one, NULL, (PangoRectangle *) &layout_one_rect);
+
+ if (layout_two != NULL)
+ {
+ pango_layout_get_pixel_extents (layout_two, NULL, (PangoRectangle *) &layout_two_rect);
+
+ layout_union.width = MAX (layout_one_rect.width, layout_two_rect.width);
+ layout_union.height = layout_one_rect.height + layout_two_rect.height;
+ }
+ else
+ {
+ layout_union = layout_one_rect;
+ }
+
+ if (cell_area)
+ {
+ gfloat xalign, yalign;
+
+ gtk_cell_renderer_get_alignment (cell, &xalign, &yalign);
+
+ layout_union.width = MIN (layout_union.width, cell_area->width - 2 * xpad);
+ layout_union.height = MIN (layout_union.height, cell_area->height - 2 * ypad);
+
+ if (x_offset_1)
+ {
+ if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
+ *x_offset_1 = (1.0 - xalign) * (cell_area->width - (layout_one_rect.width + (2 * xpad)));
+ else
+ *x_offset_1 = xalign * (cell_area->width - (layout_one_rect.width + (2 * xpad)));
+
+ *x_offset_1 = MAX (*x_offset_1, 0);
+ }
+ if (x_offset_2)
+ {
+ if (layout_two != NULL)
+ {
+ if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
+ *x_offset_2 = (1.0 - xalign) * (cell_area->width - (layout_two_rect.width + (2 * xpad)));
+ else
+ *x_offset_2 = xalign * (cell_area->width - (layout_two_rect.width + (2 * xpad)));
+
+ *x_offset_2 = MAX (*x_offset_2, 0);
+ }
+ else
+ {
+ *x_offset_2 = 0;
+ }
+ }
+
+ if (y_offset)
+ {
+ *y_offset = yalign * (cell_area->height - (layout_union.height + (2 * ypad)));
+ *y_offset = MAX (*y_offset, 0);
+ }
+ }
+ else
+ {
+ if (x_offset_1) *x_offset_1 = 0;
+ if (x_offset_2) *x_offset_2 = 0;
+ if (y_offset) *y_offset = 0;
+ }
+
+ g_clear_object (&layout_one);
+ g_clear_object (&layout_two);
+
+ if (height)
+ *height = ypad * 2 + layout_union.height;
+
+ if (width)
+ *width = xpad * 2 + layout_union.width;
+}
+
+static void
+gd_two_lines_renderer_render (GtkCellRenderer *cell,
+ cairo_t *cr,
+ GtkWidget *widget,
+ const GdkRectangle *background_area,
+ const GdkRectangle *cell_area,
+ GtkCellRendererState flags)
+{
+ GdTwoLinesRenderer *self = GD_TWO_LINES_RENDERER (cell);
+ GtkStyleContext *context;
+ gint line_one_height;
+ GtkStateFlags state;
+ GdkRectangle area, render_area = *cell_area;
+ gint xpad, ypad, x_offset_1, x_offset_2, y_offset;
+ PangoLayout *layout_one, *layout_two;
+ PangoRectangle layout_rect;
+
+ /* fetch common information */
+ context = gtk_widget_get_style_context (widget);
+ gd_two_lines_renderer_prepare_layouts (self, cell_area, widget, &layout_one, &layout_two);
+ gd_two_lines_renderer_get_size (cell, widget,
+ layout_one, layout_two,
+ NULL, NULL,
+ cell_area,
+ &x_offset_1, &x_offset_2, &y_offset);
+ gtk_cell_renderer_get_padding (cell, &xpad, &ypad);
+
+ area = *cell_area;
+ area.x += xpad;
+ area.y += ypad;
+
+ /* now render the first layout */
+ pango_layout_get_pixel_extents (layout_one, NULL, &layout_rect);
+
+ render_area = area;
+ render_area.x += x_offset_1 - layout_rect.x;
+ render_area.y += y_offset;
+
+ gtk_render_layout (context, cr,
+ render_area.x,
+ render_area.y,
+ layout_one);
+
+ /* render the second layout */
+ if (layout_two != NULL)
+ {
+ pango_layout_get_pixel_size (layout_one,
+ NULL, &line_one_height);
+
+ gtk_style_context_save (context);
+
+ apply_subtitle_style_to_layout (context, layout_two, flags);
+
+ state = gtk_cell_renderer_get_state (cell, widget, flags);
+ gtk_style_context_set_state (context, state);
+
+ pango_layout_get_pixel_extents (layout_two, NULL, &layout_rect);
+
+ render_area = area;
+ render_area.x += x_offset_2 - layout_rect.x;
+ render_area.y += y_offset + line_one_height;
+
+ gtk_render_layout (context, cr,
+ render_area.x,
+ render_area.y,
+ layout_two);
+
+ gtk_style_context_restore (context);
+ }
+
+ g_clear_object (&layout_one);
+ g_clear_object (&layout_two);
+}
+
+static void
+gd_two_lines_renderer_get_preferred_width (GtkCellRenderer *cell,
+ GtkWidget *widget,
+ gint *minimum_size,
+ gint *natural_size)
+{
+ PangoContext *context;
+ PangoFontMetrics *metrics;
+ PangoFontDescription *font_desc;
+ GtkStyleContext *style_context;
+ gint nat_width, min_width;
+ gint xpad, char_width, wrap_width, text_width;
+ gint width_chars, ellipsize_chars;
+
+ g_object_get (cell,
+ "xpad", &xpad,
+ "width-chars", &width_chars,
+ "wrap-width", &wrap_width,
+ NULL);
+ style_context = gtk_widget_get_style_context (widget);
+ gtk_cell_renderer_get_padding (cell, &xpad, NULL);
+
+ gd_two_lines_renderer_get_size (cell, widget,
+ NULL, NULL,
+ &text_width, NULL,
+ NULL,
+ NULL, NULL, NULL);
+
+ /* Fetch the average size of a character */
+ context = gtk_widget_get_pango_context (widget);
+ gtk_style_context_save (style_context);
+ gtk_style_context_set_state (style_context, 0);
+ gtk_style_context_get (style_context, gtk_style_context_get_state (style_context),
+ "font", &font_desc, NULL);
+ gtk_style_context_restore (style_context);
+ metrics = pango_context_get_metrics (context, font_desc,
+ pango_context_get_language (context));
+
+ char_width = pango_font_metrics_get_approximate_char_width (metrics);
+
+ pango_font_metrics_unref (metrics);
+ pango_font_description_free (font_desc);
+
+ /* enforce minimum width for ellipsized labels at ~3 chars */
+ ellipsize_chars = 3;
+
+ /* If no width-chars set, minimum for wrapping text will be the wrap-width */
+ if (wrap_width > -1)
+ min_width = xpad * 2 + MIN (text_width, wrap_width);
+ else
+ min_width = xpad * 2 +
+ MIN (text_width,
+ (PANGO_PIXELS (char_width) * MAX (width_chars, ellipsize_chars)));
+
+ if (width_chars > 0)
+ nat_width = xpad * 2 +
+ MAX ((PANGO_PIXELS (char_width) * width_chars), text_width);
+ else
+ nat_width = xpad * 2 + text_width;
+
+ nat_width = MAX (nat_width, min_width);
+
+ if (minimum_size)
+ *minimum_size = min_width;
+
+ if (natural_size)
+ *natural_size = nat_width;
+}
+
+static void
+gd_two_lines_renderer_get_preferred_height_for_width (GtkCellRenderer *cell,
+ GtkWidget *widget,
+ gint width,
+ gint *minimum_size,
+ gint *natural_size)
+{
+ GdTwoLinesRenderer *self = GD_TWO_LINES_RENDERER (cell);
+ PangoLayout *layout_one, *layout_two;
+ gint text_height, wrap_width;
+ gint xpad, ypad;
+
+ gtk_cell_renderer_get_padding (cell, &xpad, &ypad);
+ g_object_get (cell, "wrap-width", &wrap_width, NULL);
+ gd_two_lines_renderer_prepare_layouts (self, NULL, widget, &layout_one, &layout_two);
+
+ if (wrap_width != -1)
+ wrap_width = MIN (width - 2 * xpad, wrap_width);
+ else
+ wrap_width = width - 2 * xpad;
+
+ pango_layout_set_width (layout_one, wrap_width);
+ if (layout_two != NULL)
+ pango_layout_set_width (layout_two, wrap_width);
+
+ gd_two_lines_renderer_get_size (cell, widget,
+ layout_one, layout_two,
+ NULL, &text_height,
+ NULL,
+ NULL, NULL, NULL);
+
+ text_height += 2 * ypad;
+
+ if (minimum_size != NULL)
+ *minimum_size = text_height;
+
+ if (natural_size != NULL)
+ *natural_size = text_height;
+
+ g_clear_object (&layout_one);
+ g_clear_object (&layout_two);
+}
+
+static void
+gd_two_lines_renderer_get_preferred_height (GtkCellRenderer *cell,
+ GtkWidget *widget,
+ gint *minimum_size,
+ gint *natural_size)
+{
+ gint min_width;
+
+ gtk_cell_renderer_get_preferred_width (cell, widget, &min_width, NULL);
+ gd_two_lines_renderer_get_preferred_height_for_width (cell, widget, min_width,
+ minimum_size, natural_size);
+}
+
+static void
+gd_two_lines_renderer_get_aligned_area (GtkCellRenderer *cell,
+ GtkWidget *widget,
+ GtkCellRendererState flags,
+ const GdkRectangle *cell_area,
+ GdkRectangle *aligned_area)
+{
+ gint x_offset, x_offset_1, x_offset_2, y_offset;
+
+ /* fetch common information */
+ gd_two_lines_renderer_get_size (cell, widget,
+ NULL, NULL,
+ &aligned_area->width, &aligned_area->height,
+ cell_area,
+ &x_offset_1, &x_offset_2, &y_offset);
+
+ x_offset = MIN (x_offset_1, x_offset_2);
+
+ aligned_area->x = cell_area->x + x_offset;
+ aligned_area->y = cell_area->y + y_offset;
+}
+
+static void
+gd_two_lines_renderer_set_line_two (GdTwoLinesRenderer *self,
+ const gchar *line_two)
+{
+ if (g_strcmp0 (self->priv->line_two, line_two) == 0)
+ return;
+
+ g_free (self->priv->line_two);
+ self->priv->line_two = g_strdup (line_two);
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LINE_TWO]);
+}
+
+static void
+gd_two_lines_renderer_set_text_lines (GdTwoLinesRenderer *self,
+ gint text_lines)
+{
+ if (self->priv->text_lines == text_lines)
+ return;
+
+ self->priv->text_lines = text_lines;
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TEXT_LINES]);
+}
+
+static void
+gd_two_lines_renderer_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GdTwoLinesRenderer *self = GD_TWO_LINES_RENDERER (object);
+
+ switch (property_id)
+ {
+ case PROP_TEXT_LINES:
+ gd_two_lines_renderer_set_text_lines (self, g_value_get_int (value));
+ break;
+ case PROP_LINE_TWO:
+ gd_two_lines_renderer_set_line_two (self, g_value_get_string (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gd_two_lines_renderer_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GdTwoLinesRenderer *self = GD_TWO_LINES_RENDERER (object);
+
+ switch (property_id)
+ {
+ case PROP_TEXT_LINES:
+ g_value_set_int (value, self->priv->text_lines);
+ break;
+ case PROP_LINE_TWO:
+ g_value_set_string (value, self->priv->line_two);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gd_two_lines_renderer_finalize (GObject *object)
+{
+ GdTwoLinesRenderer *self = GD_TWO_LINES_RENDERER (object);
+
+ g_free (self->priv->line_two);
+
+ G_OBJECT_CLASS (gd_two_lines_renderer_parent_class)->finalize (object);
+}
+
+static void
+gd_two_lines_renderer_class_init (GdTwoLinesRendererClass *klass)
+{
+ GtkCellRendererClass *cclass = GTK_CELL_RENDERER_CLASS (klass);
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+
+ cclass->render = gd_two_lines_renderer_render;
+ cclass->get_preferred_width = gd_two_lines_renderer_get_preferred_width;
+ cclass->get_preferred_height = gd_two_lines_renderer_get_preferred_height;
+ cclass->get_preferred_height_for_width = gd_two_lines_renderer_get_preferred_height_for_width;
+ cclass->get_aligned_area = gd_two_lines_renderer_get_aligned_area;
+
+ oclass->set_property = gd_two_lines_renderer_set_property;
+ oclass->get_property = gd_two_lines_renderer_get_property;
+ oclass->finalize = gd_two_lines_renderer_finalize;
+
+ properties[PROP_TEXT_LINES] =
+ g_param_spec_int ("text-lines",
+ "Lines of text",
+ "The total number of lines to be displayed",
+ 2, G_MAXINT, 2,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_LINE_TWO] =
+ g_param_spec_string ("line-two",
+ "Second line",
+ "Second line",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ g_type_class_add_private (klass, sizeof (GdTwoLinesRendererPrivate));
+ g_object_class_install_properties (oclass, NUM_PROPERTIES, properties);
+}
+
+static void
+gd_two_lines_renderer_init (GdTwoLinesRenderer *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GD_TYPE_TWO_LINES_RENDERER,
+ GdTwoLinesRendererPrivate);
+}
+
+GtkCellRenderer *
+gd_two_lines_renderer_new (void)
+{
+ return g_object_new (GD_TYPE_TWO_LINES_RENDERER, NULL);
+}
diff --git a/subprojects/libgd/libgd/gd-two-lines-renderer.h b/subprojects/libgd/libgd/gd-two-lines-renderer.h
new file mode 100644
index 0000000..23bf70f
--- /dev/null
+++ b/subprojects/libgd/libgd/gd-two-lines-renderer.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2011 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Cosimo Cecchi <cosimoc@redhat.com>
+ *
+ */
+
+#ifndef _GD_TWO_LINES_RENDERER_H
+#define _GD_TWO_LINES_RENDERER_H
+
+#include <glib-object.h>
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GD_TYPE_TWO_LINES_RENDERER gd_two_lines_renderer_get_type()
+
+#define GD_TWO_LINES_RENDERER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ GD_TYPE_TWO_LINES_RENDERER, GdTwoLinesRenderer))
+
+#define GD_TWO_LINES_RENDERER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ GD_TYPE_TWO_LINES_RENDERER, GdTwoLinesRendererClass))
+
+#define GD_IS_TWO_LINES_RENDERER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ GD_TYPE_TWO_LINES_RENDERER))
+
+#define GD_IS_TWO_LINES_RENDERER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+ GD_TYPE_TWO_LINES_RENDERER))
+
+#define GD_TWO_LINES_RENDERER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ GD_TYPE_TWO_LINES_RENDERER, GdTwoLinesRendererClass))
+
+typedef struct _GdTwoLinesRenderer GdTwoLinesRenderer;
+typedef struct _GdTwoLinesRendererClass GdTwoLinesRendererClass;
+typedef struct _GdTwoLinesRendererPrivate GdTwoLinesRendererPrivate;
+
+struct _GdTwoLinesRenderer
+{
+ GtkCellRendererText parent;
+
+ GdTwoLinesRendererPrivate *priv;
+};
+
+struct _GdTwoLinesRendererClass
+{
+ GtkCellRendererTextClass parent_class;
+};
+
+GType gd_two_lines_renderer_get_type (void) G_GNUC_CONST;
+
+GtkCellRenderer *gd_two_lines_renderer_new (void);
+
+G_END_DECLS
+
+#endif /* _GD_TWO_LINES_RENDERER_H */
diff --git a/subprojects/libgd/libgd/gd-types-catalog.c b/subprojects/libgd/libgd/gd-types-catalog.c
new file mode 100644
index 0000000..75f7d57
--- /dev/null
+++ b/subprojects/libgd/libgd/gd-types-catalog.c
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2012 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include "gd-types-catalog.h"
+
+#ifdef LIBGD__BOX_COMMON
+# include "gd-main-box-child.h"
+# include "gd-main-box-generic.h"
+# include "gd-main-box-item.h"
+#endif
+
+#ifdef LIBGD_MAIN_ICON_BOX
+# include "gd-main-icon-box.h"
+# include "gd-main-icon-box-child.h"
+#endif
+
+#ifdef LIBGD_MAIN_BOX
+# include "gd-main-box.h"
+#endif
+
+#ifdef LIBGD__VIEW_COMMON
+# include "gd-main-view-generic.h"
+# include "gd-styled-text-renderer.h"
+# include "gd-two-lines-renderer.h"
+#endif
+
+#ifdef LIBGD_MAIN_ICON_VIEW
+# include "gd-main-icon-view.h"
+# include "gd-toggle-pixbuf-renderer.h"
+#endif
+
+#ifdef LIBGD_MAIN_LIST_VIEW
+# include "gd-main-list-view.h"
+#endif
+
+#ifdef LIBGD_MAIN_VIEW
+# include "gd-main-view.h"
+#endif
+
+#ifdef LIBGD_MARGIN_CONTAINER
+# include "gd-margin-container.h"
+#endif
+
+#ifdef LIBGD_TAGGED_ENTRY
+# include "gd-tagged-entry.h"
+#endif
+
+#ifdef LIBGD_NOTIFICATION
+# include "gd-notification.h"
+#endif
+
+/**
+ * gd_ensure_types:
+ *
+ * This functions must be called during initialization
+ * to make sure the widget types are available to GtkBuilder.
+ */
+void
+gd_ensure_types (void)
+{
+#ifdef LIBGD__BOX_COMMON
+ g_type_ensure (GD_TYPE_MAIN_BOX_CHILD);
+ g_type_ensure (GD_TYPE_MAIN_BOX_GENERIC);
+ g_type_ensure (GD_TYPE_MAIN_BOX_ITEM);
+#endif
+
+#ifdef LIBGD_MAIN_ICON_BOX
+ g_type_ensure (GD_TYPE_MAIN_ICON_BOX);
+ g_type_ensure (GD_TYPE_MAIN_ICON_BOX_CHILD);
+#endif
+
+#ifdef LIBGD_MAIN_BOX
+ g_type_ensure (GD_TYPE_MAIN_BOX);
+#endif
+
+#ifdef LIBGD__VIEW_COMMON
+ g_type_ensure (GD_TYPE_MAIN_VIEW_GENERIC);
+ g_type_ensure (GD_TYPE_STYLED_TEXT_RENDERER);
+ g_type_ensure (GD_TYPE_TWO_LINES_RENDERER);
+#endif
+
+#ifdef LIBGD_MAIN_ICON_VIEW
+ g_type_ensure (GD_TYPE_MAIN_ICON_VIEW);
+ g_type_ensure (GD_TYPE_TOGGLE_PIXBUF_RENDERER);
+#endif
+
+#ifdef LIBGD_MAIN_LIST_VIEW
+ g_type_ensure (GD_TYPE_MAIN_LIST_VIEW);
+#endif
+
+#ifdef LIBGD_MAIN_VIEW
+ g_type_ensure (GD_TYPE_MAIN_VIEW);
+#endif
+
+#ifdef LIBGD_MARGIN_CONTAINER
+ g_type_ensure (GD_TYPE_MARGIN_CONTAINER);
+#endif
+
+#ifdef LIBGD_TAGGED_ENTRY
+ g_type_ensure (GD_TYPE_TAGGED_ENTRY);
+#endif
+
+#ifdef LIBGD_NOTIFICATION
+ g_type_ensure (GD_TYPE_NOTIFICATION);
+#endif
+}
+
diff --git a/subprojects/libgd/libgd/gd-types-catalog.h b/subprojects/libgd/libgd/gd-types-catalog.h
new file mode 100644
index 0000000..fc99416
--- /dev/null
+++ b/subprojects/libgd/libgd/gd-types-catalog.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2012 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef __GD_TYPES_CATALOG_H__
+#define __GD_TYPES_CATALOG_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+void gd_ensure_types (void);
+
+G_END_DECLS
+
+#endif /* __GD_TYPES_CATALOG_H__ */
diff --git a/subprojects/libgd/libgd/gd.h b/subprojects/libgd/libgd/gd.h
new file mode 100644
index 0000000..69685c9
--- /dev/null
+++ b/subprojects/libgd/libgd/gd.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2012 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef __GD_H__
+#define __GD_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#include <libgd/gd-types-catalog.h>
+
+#ifdef LIBGD_GTK_HACKS
+# include <libgd/gd-icon-utils.h>
+#endif
+
+#ifdef LIBGD__BOX_COMMON
+# include <libgd/gd-main-box-child.h>
+# include <libgd/gd-main-box-generic.h>
+# include <libgd/gd-main-box-item.h>
+#endif
+
+#ifdef LIBGD_MAIN_ICON_BOX
+# include <libgd/gd-main-icon-box.h>
+# include <libgd/gd-main-icon-box-child.h>
+#endif
+
+#ifdef LIBGD_MAIN_BOX
+# include <libgd/gd-main-box.h>
+#endif
+
+#ifdef LIBGD__VIEW_COMMON
+# include <libgd/gd-main-view-generic.h>
+# include <libgd/gd-styled-text-renderer.h>
+# include <libgd/gd-two-lines-renderer.h>
+#endif
+
+#ifdef LIBGD_MAIN_ICON_VIEW
+# include <libgd/gd-main-icon-view.h>
+# include <libgd/gd-toggle-pixbuf-renderer.h>
+#endif
+
+#ifdef LIBGD_MAIN_LIST_VIEW
+# include <libgd/gd-main-list-view.h>
+#endif
+
+#ifdef LIBGD_MAIN_VIEW
+# include <libgd/gd-main-view.h>
+#endif
+
+#ifdef LIBGD_MARGIN_CONTAINER
+# include <libgd/gd-margin-container.h>
+#endif
+
+#ifdef LIBGD_TAGGED_ENTRY
+# include <libgd/gd-tagged-entry.h>
+#endif
+
+#ifdef LIBGD_NOTIFICATION
+# include <libgd/gd-notification.h>
+#endif
+
+G_END_DECLS
+
+#endif /* __GD_H__ */
diff --git a/subprojects/libgd/libgd/meson.build b/subprojects/libgd/libgd/meson.build
new file mode 100644
index 0000000..1d2c61f
--- /dev/null
+++ b/subprojects/libgd/libgd/meson.build
@@ -0,0 +1,202 @@
+gnome = import('gnome')
+
+sources = [
+ 'gd.h',
+ 'gd-types-catalog.c'
+]
+built_sources = []
+c_args = []
+private_c_args = [
+ '-DG_LOG_DOMAIN="libgd"',
+ '-DG_DISABLE_DEPRECATED',
+]
+
+if (get_option('with-gtk-hacks') or
+ get_option('with-main-icon-box') or
+ get_option('with-main-view'))
+ sources += [
+ 'gd-icon-utils.c',
+ 'gd-icon-utils.h',
+ ]
+ c_args += '-DLIBGD_GTK_HACKS=1'
+endif
+
+if (get_option('with-main-box') or
+ get_option('with-main-icon-box'))
+ sources += [
+ 'gd-main-box-child.c',
+ 'gd-main-box-child.h',
+ 'gd-main-box-generic.c',
+ 'gd-main-box-generic.h',
+ 'gd-main-box-item.c',
+ 'gd-main-box-item.h'
+ ]
+ c_args += '-DLIBGD__BOX_COMMON=1'
+
+ if get_option('with-main-icon-box')
+ sources += [
+ 'gd-main-icon-box.c',
+ 'gd-main-icon-box.h',
+ 'gd-main-icon-box-child.c',
+ 'gd-main-icon-box-child.h',
+ 'gd-main-icon-box-icon.c',
+ 'gd-main-icon-box-icon.h',
+ 'gd-icon-utils.c',
+ 'gd-icon-utils.h',
+ ]
+ c_args += '-DLIBGD_MAIN_ICON_BOX=1'
+ endif
+
+ if get_option('with-main-box')
+ sources += [
+ 'gd-main-box.c',
+ 'gd-main-box.h',
+ ]
+ c_args += '-DLIBGD_MAIN_BOX=1'
+ endif
+endif
+
+if (get_option('with-main-icon-view') or
+ get_option('with-main-list-view') or
+ get_option('with-main-view'))
+ sources += [
+ 'gd-main-view-generic.c',
+ 'gd-main-view-generic.h',
+ 'gd-styled-text-renderer.c',
+ 'gd-styled-text-renderer.h',
+ 'gd-two-lines-renderer.c',
+ 'gd-two-lines-renderer.h',
+ ]
+ c_args += '-DLIBGD__VIEW_COMMON=1'
+
+ if (get_option('with-main-icon-view') or
+ get_option('with-main-view'))
+ sources += [
+ 'gd-main-icon-view.c',
+ 'gd-main-icon-view.h',
+ 'gd-toggle-pixbuf-renderer.c',
+ 'gd-toggle-pixbuf-renderer.h'
+ ]
+ c_args += '-DLIBGD_MAIN_ICON_VIEW=1'
+ endif
+
+ if (get_option('with-main-list-view') or
+ get_option('with-main-view'))
+ sources += [
+ 'gd-main-list-view.c',
+ 'gd-main-list-view.h',
+ ]
+ c_args += '-DLIBGD_MAIN_LIST_VIEW=1'
+ endif
+
+ if get_option('with-main-view')
+ sources += [
+ 'gd-main-view.c',
+ 'gd-main-view.h',
+ ]
+ c_args += '-DLIBGD_MAIN_VIEW=1'
+ endif
+endif
+
+if get_option('with-margin-container')
+ sources += [
+ 'gd-margin-container.c',
+ 'gd-margin-container.h',
+ ]
+ c_args += '-DLIBGD_MARGIN_CONTAINER=1'
+endif
+
+if get_option('with-tagged-entry')
+ sources += [
+ 'gd-tagged-entry.c',
+ 'gd-tagged-entry.h',
+ ]
+ c_args += '-DLIBGD_TAGGED_ENTRY=1'
+endif
+
+if get_option('with-notification')
+ sources += [
+ 'gd-notification.c',
+ 'gd-notification.h',
+ ]
+ c_args += '-DLIBGD_NOTIFICATION=1'
+endif
+
+if sources.length() == 2
+ error('You must include a feature to be built!')
+endif
+
+# --------- Building -----------
+
+static = get_option('static')
+install_introspection = get_option('with-introspection')
+with_vapi = get_option('with-vapi')
+
+if static
+ libgd_lib = static_library('gd', sources,
+ dependencies: [libgtk, libm],
+ include_directories: libgd_include,
+ c_args: c_args + private_c_args
+ )
+endif
+
+# Currently in Meson building gir requires a shared library
+if not static or (install_introspection or with_vapi)
+ if not static and pkglibdir == ''
+ error('Installing shared library but pkglibdir is unset!')
+ endif
+
+ libgd_shared_lib = shared_library('gd', sources,
+ dependencies: [libgtk, libm],
+ include_directories: libgd_include,
+ c_args: c_args + private_c_args,
+ install: not static,
+ install_dir: pkglibdir
+ )
+
+ if not static
+ libgd_lib = libgd_shared_lib
+ endif
+endif
+
+if install_introspection or with_vapi
+ if install_introspection
+ if pkgdatadir == ''
+ error('Installing introspection but pkgdatadir is unset!')
+ elif pkglibdir == ''
+ error('Installing introspection but pkglibdir is unset!')
+ endif
+ endif
+
+ libgd_gir = gnome.generate_gir(libgd_shared_lib,
+ sources : sources,
+ nsversion : '1.0',
+ namespace : 'Gd',
+ symbol_prefix : 'gd',
+ identifier_prefix : 'Gd',
+ includes : 'Gtk-3.0',
+ include_directories: libgd_include,
+ install: install_introspection,
+ install_dir_gir: join_paths(pkgdatadir, 'gir-1.0'),
+ install_dir_typelib: join_paths(pkglibdir, 'girepository-1.0'),
+ extra_args: [
+ '--c-include=libgd/gd.h',
+ ]
+ )
+ built_sources += libgd_gir
+
+ if get_option('with-vapi')
+ libgd_vapi_dep = gnome.generate_vapi('gd-1.0',
+ sources: libgd_gir[0],
+ packages: ['gtk+-3.0']
+ )
+ endif
+endif
+
+libgd_dep = declare_dependency(
+ link_with: libgd_lib,
+ include_directories: libgd_include,
+ dependencies: libgtk,
+ compile_args: c_args,
+ sources: built_sources
+)
diff --git a/subprojects/libgd/meson.build b/subprojects/libgd/meson.build
new file mode 100644
index 0000000..6d1242c
--- /dev/null
+++ b/subprojects/libgd/meson.build
@@ -0,0 +1,24 @@
+project('libgd', 'c',
+ meson_version: '>= 0.38.0',
+ default_options: ['static=true'],
+)
+
+if not meson.is_subproject()
+ message('WARNING: This project is only intended to be used as a subproject!')
+endif
+
+pkglibdir = get_option('pkglibdir')
+pkgdatadir = get_option('pkgdatadir')
+
+libgtk = dependency('gtk+-3.0', version: '>= 3.7.10')
+cc = meson.get_compiler('c')
+libm = cc.find_library('m', required: false)
+libgd_include = include_directories('.')
+
+subdir('libgd')
+
+if get_option('with-tagged-entry')
+ foreach t : ['test-tagged-entry', 'test-tagged-entry-2']
+ executable(t, t + '.c', dependencies : libgd_dep)
+ endforeach
+endif
diff --git a/subprojects/libgd/meson_options.txt b/subprojects/libgd/meson_options.txt
new file mode 100644
index 0000000..fcab3a0
--- /dev/null
+++ b/subprojects/libgd/meson_options.txt
@@ -0,0 +1,25 @@
+option('pkglibdir', type: 'string', value: '',
+ description: 'The private directory the shared library/typelib will be installed into.'
+)
+option('pkgdatadir', type: 'string', value: '',
+ description: 'The private directory the gir file will be installed into.'
+)
+option('static', type: 'boolean', value: false,
+ description: 'Build as a static library'
+)
+option('with-introspection', type: 'boolean', value: false,
+ description: 'Build gobject-introspection support'
+)
+option('with-vapi', type: 'boolean', value: false,
+ description: 'Build vapi file'
+)
+# Widget options
+option('with-gtk-hacks', type: 'boolean', value: false)
+option('with-main-view', type: 'boolean', value: false)
+option('with-main-icon-view', type: 'boolean', value: false)
+option('with-main-list-view', type: 'boolean', value: false)
+option('with-margin-container', type: 'boolean', value: false)
+option('with-tagged-entry', type: 'boolean', value: false)
+option('with-notification', type: 'boolean', value: false)
+option('with-main-box', type: 'boolean', value: false)
+option('with-main-icon-box', type: 'boolean', value: false) \ No newline at end of file
diff --git a/subprojects/libgd/meson_readme.md b/subprojects/libgd/meson_readme.md
new file mode 100644
index 0000000..c3e7f26
--- /dev/null
+++ b/subprojects/libgd/meson_readme.md
@@ -0,0 +1,88 @@
+See README for general information. Read below for usage with Meson.
+
+Usage
+=====
+
+libgd is intended to be used as a submodule from other projects. This requires passing default_options to the subproject
+which was added in Meson 0.38.0. To see a full list of options you can run `mesonconf $your_build_dir`. If building a
+non-static library `pkglibdir` must be set to a private location to install to which you will also want to pass (an absolute path)
+with the `install_rpath` keyword to any executables. For introspection files you also must set `pkgdatadir`.
+
+So given a Meson project using git you would run this to do initial setup:
+
+```
+mkdir subprojects
+git submodule add https://git.gnome.org/browse/libgd subprojects/libgd
+```
+
+Then from within your `meson.build` file:
+
+Static Library
+--------------
+
+```meson
+libgd = subproject('libgd',
+ default_options: [
+ 'with-tagged-entry=true'
+ ]
+)
+# Pass as dependency to another target
+libgd_dep = libgd.get_variable('libgd_dep')
+```
+
+```c
+#include "libgd/gd.h"
+
+int main(int argc, char **argv)
+{
+ gd_ensure_types(); /* As a test */
+ return 0;
+}
+```
+
+Introspection
+-------------
+
+```meson
+pkglibdir = join_paths(get_option('libdir'), meson.project_name())
+pkgdatadir = join_paths(get_option('datadir'), meson.project_name())
+libgd = subproject('libgd',
+ default_options: [
+ 'pkglibdir=' + pkglibdir,
+ 'pkgdatadir=' + pkgdatadir,
+ 'with-tagged-entry=true',
+ 'with-introspection=true',
+ 'static=false',
+ ]
+)
+```
+
+```python
+import os
+import gi
+gi.require_version('GIRepository', '2.0')
+from gi.repository import GIRepository
+pkglibdir = '/usr/lib/foo' # This would be defined at build time
+pkggirdir = os.path.join(pkglibdir, 'girepository-1.0')
+GIRepository.Repository.prepend_search_path(pkggirdir)
+GIRepository.Repository.prepend_library_path(pkglibdir)
+gi.require_version('Gd', '1.0')
+```
+
+Vala
+----
+
+```meson
+pkglibdir = join_paths(get_option('libdir'), meson.project_name())
+libgd = subproject('libgd',
+ default_options: [
+ 'pkglibdir=' + pkglibdir,
+ 'with-tagged-entry=true',
+ 'with-vapi=true'
+ ]
+)
+# Pass as dependency to a Vala target
+libgd_vapi_dep = libgd.get_variable('libgd_vapi_dep')
+```
+
+<!-- TODO: Make a Vala example -->
diff --git a/subprojects/libgd/test-tagged-entry-2.c b/subprojects/libgd/test-tagged-entry-2.c
new file mode 100644
index 0000000..465ab83
--- /dev/null
+++ b/subprojects/libgd/test-tagged-entry-2.c
@@ -0,0 +1,131 @@
+#include <gtk/gtk.h>
+#include <libgd/gd-tagged-entry.h>
+
+static GdTaggedEntryTag *toggle_tag;
+
+static void
+on_tag_clicked (GdTaggedEntry *entry,
+ GdTaggedEntryTag *tag,
+ gpointer useless)
+{
+ g_print ("tag clicked: %s\n", gd_tagged_entry_tag_get_label (tag));
+}
+
+static void
+on_tag_button_clicked (GdTaggedEntry *entry,
+ GdTaggedEntryTag *tag,
+ gpointer useless)
+{
+ g_print ("tag button clicked: %s\n", gd_tagged_entry_tag_get_label (tag));
+}
+
+static void
+on_toggle_visible (GtkButton *button,
+ GtkWidget *entry)
+{
+ gboolean active;
+
+ active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button));
+
+ g_print ("%s tagged entry\n", active ? "show" : "hide");
+ gtk_widget_set_visible (entry, active);
+}
+
+static void
+on_toggle_tag (GtkButton *button,
+ GdTaggedEntry *entry)
+{
+ gboolean active;
+
+ active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button));
+
+ if (active)
+ {
+ g_print ("adding tag 'Toggle Tag'\n");
+ gd_tagged_entry_insert_tag (entry, toggle_tag, 0);
+ }
+ else
+ {
+ g_print ("removing tag 'Toggle Tag'\n");
+ gd_tagged_entry_remove_tag (entry, toggle_tag);
+ }
+}
+
+gint
+main (gint argc,
+ gchar ** argv)
+{
+ GtkWidget *box;
+ GtkWidget *entry;
+ GtkWidget *search_bar;
+ GtkWidget *search_container;
+ GtkWidget *toggle_tag_button;
+ GtkWidget *toggle_visible_button;
+ GtkWidget *revealer;
+ GtkWidget *window;
+ GdTaggedEntryTag *tag;
+
+ gtk_init (&argc, &argv);
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_widget_set_size_request (window, 640, 600);
+
+ box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ gtk_container_add (GTK_CONTAINER (window), box);
+
+ revealer = gtk_revealer_new ();
+ gtk_revealer_set_reveal_child (GTK_REVEALER (revealer), TRUE);
+ gtk_container_add (GTK_CONTAINER (box), revealer);
+
+ search_bar = gtk_search_bar_new ();
+ gtk_search_bar_set_search_mode (GTK_SEARCH_BAR (search_bar), TRUE);
+ gtk_container_add (GTK_CONTAINER (revealer), search_bar);
+
+ search_container = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_widget_set_halign (search_container, GTK_ALIGN_CENTER);
+ gtk_container_add (GTK_CONTAINER (search_bar), search_container);
+
+ entry = GTK_WIDGET (gd_tagged_entry_new ());
+ gtk_widget_set_size_request (entry, 500, -1);
+ g_signal_connect (entry, "tag-clicked",
+ G_CALLBACK (on_tag_clicked), NULL);
+ g_signal_connect (entry, "tag-button-clicked",
+ G_CALLBACK (on_tag_button_clicked), NULL);
+ gtk_container_add (GTK_CONTAINER (search_container), entry);
+
+ tag = gd_tagged_entry_tag_new ("Blah1");
+ gd_tagged_entry_add_tag (GD_TAGGED_ENTRY (entry), tag);
+ g_object_unref (tag);
+
+ tag = gd_tagged_entry_tag_new ("Blah2");
+ gd_tagged_entry_tag_set_has_close_button (tag, FALSE);
+ gd_tagged_entry_insert_tag (GD_TAGGED_ENTRY (entry), tag, -1);
+ g_object_unref (tag);
+
+ tag = gd_tagged_entry_tag_new ("Blah3");
+ gd_tagged_entry_tag_set_has_close_button (tag, FALSE);
+ gd_tagged_entry_insert_tag (GD_TAGGED_ENTRY (entry), tag, 0);
+ g_object_unref (tag);
+
+ toggle_visible_button = gtk_toggle_button_new_with_label ("Visible");
+ gtk_widget_set_vexpand (toggle_visible_button, TRUE);
+ gtk_widget_set_valign (toggle_visible_button, GTK_ALIGN_END);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle_visible_button), TRUE);
+ g_signal_connect (toggle_visible_button, "toggled",
+ G_CALLBACK (on_toggle_visible), entry);
+ gtk_container_add (GTK_CONTAINER (box), toggle_visible_button);
+
+ toggle_tag = gd_tagged_entry_tag_new ("Toggle Tag");
+
+ toggle_tag_button = gtk_toggle_button_new_with_label ("Toggle Tag");
+ g_signal_connect (toggle_tag_button, "toggled",
+ G_CALLBACK (on_toggle_tag), entry);
+ gtk_container_add (GTK_CONTAINER (box), toggle_tag_button);
+
+ gtk_widget_show_all (window);
+ gtk_main ();
+
+ gtk_widget_destroy (window);
+
+ return 0;
+}
diff --git a/subprojects/libgd/test-tagged-entry.c b/subprojects/libgd/test-tagged-entry.c
new file mode 100644
index 0000000..0f583d9
--- /dev/null
+++ b/subprojects/libgd/test-tagged-entry.c
@@ -0,0 +1,111 @@
+#include <gtk/gtk.h>
+#include <libgd/gd-tagged-entry.h>
+
+static GdTaggedEntryTag *toggle_tag;
+
+static void
+on_tag_clicked (GdTaggedEntry *entry,
+ GdTaggedEntryTag *tag,
+ gpointer useless)
+{
+ g_print ("tag clicked: %s\n", gd_tagged_entry_tag_get_label (tag));
+}
+
+static void
+on_tag_button_clicked (GdTaggedEntry *entry,
+ GdTaggedEntryTag *tag,
+ gpointer useless)
+{
+ g_print ("tag button clicked: %s\n", gd_tagged_entry_tag_get_label (tag));
+}
+
+static void
+on_toggle_visible (GtkButton *button,
+ GtkWidget *entry)
+{
+ gboolean active;
+
+ active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button));
+
+ g_print ("%s tagged entry\n", active ? "show" : "hide");
+ gtk_widget_set_visible (entry, active);
+}
+
+static void
+on_toggle_tag (GtkButton *button,
+ GdTaggedEntry *entry)
+{
+ gboolean active;
+
+ active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button));
+
+ if (active)
+ {
+ g_print ("adding tag 'Toggle Tag'\n");
+ gd_tagged_entry_insert_tag (entry, toggle_tag, 0);
+ }
+ else
+ {
+ g_print ("removing tag 'Toggle Tag'\n");
+ gd_tagged_entry_remove_tag (entry, toggle_tag);
+ }
+}
+
+gint
+main (gint argc,
+ gchar ** argv)
+{
+ GtkWidget *window, *box, *entry, *toggle_visible_button, *toggle_tag_button;
+ GdTaggedEntryTag *tag;
+
+ gtk_init (&argc, &argv);
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_widget_set_size_request (window, 300, 0);
+
+ box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ gtk_container_add (GTK_CONTAINER (window), box);
+
+ entry = GTK_WIDGET (gd_tagged_entry_new ());
+ g_signal_connect(entry, "tag-clicked",
+ G_CALLBACK (on_tag_clicked), NULL);
+ g_signal_connect(entry, "tag-button-clicked",
+ G_CALLBACK (on_tag_button_clicked), NULL);
+ gtk_container_add (GTK_CONTAINER (box), entry);
+
+ tag = gd_tagged_entry_tag_new ("Blah1");
+ gd_tagged_entry_add_tag (GD_TAGGED_ENTRY (entry), tag);
+ g_object_unref (tag);
+
+ tag = gd_tagged_entry_tag_new ("Blah2");
+ gd_tagged_entry_tag_set_has_close_button (tag, FALSE);
+ gd_tagged_entry_insert_tag (GD_TAGGED_ENTRY (entry), tag, -1);
+ g_object_unref (tag);
+
+ tag = gd_tagged_entry_tag_new ("Blah3");
+ gd_tagged_entry_tag_set_has_close_button (tag, FALSE);
+ gd_tagged_entry_insert_tag (GD_TAGGED_ENTRY (entry), tag, 0);
+ g_object_unref (tag);
+
+ toggle_visible_button = gtk_toggle_button_new_with_label ("Visible");
+ gtk_widget_set_vexpand (toggle_visible_button, TRUE);
+ gtk_widget_set_valign (toggle_visible_button, GTK_ALIGN_END);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle_visible_button), TRUE);
+ g_signal_connect (toggle_visible_button, "toggled",
+ G_CALLBACK (on_toggle_visible), entry);
+ gtk_container_add (GTK_CONTAINER (box), toggle_visible_button);
+
+ toggle_tag = gd_tagged_entry_tag_new ("Toggle Tag");
+
+ toggle_tag_button = gtk_toggle_button_new_with_label ("Toggle Tag");
+ g_signal_connect (toggle_tag_button, "toggled",
+ G_CALLBACK (on_toggle_tag), entry);
+ gtk_container_add (GTK_CONTAINER (box), toggle_tag_button);
+
+ gtk_widget_show_all (window);
+ gtk_main ();
+
+ gtk_widget_destroy (window);
+
+ return 0;
+}