diff options
Diffstat (limited to 'subprojects/libgd')
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; +} |